From f111c32ff9dc63f4db08a27089b666e8fdce3eaf Mon Sep 17 00:00:00 2001 From: Tomasz N Date: Wed, 20 Nov 2024 01:06:36 +0100 Subject: fix(messages): no message kind for search pattern #31272 --- runtime/doc/ui.txt | 1 + src/nvim/search.c | 1 + test/functional/ui/messages_spec.lua | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 4e8253c47a..9cbb84a19d 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -800,6 +800,7 @@ must handle. "rpc_error" Error response from |rpcrequest()| "return_prompt" |press-enter| prompt after a multiple messages "quickfix" Quickfix navigation message + "search_cmd" Entered search command "search_count" Search count message ("S" flag of 'shortmess') "wmsg" Warning ("search hit BOTTOM", |W10|, …) New kinds may be added in the future; clients should treat unknown diff --git a/src/nvim/search.c b/src/nvim/search.c index debc5697d1..159ab35f6f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1203,6 +1203,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen // Compute msg_row early. msg_start(); + msg_ext_set_kind("search_cmd"); // Get the offset, so we know how long it is. if (!cmd_silent diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 734877d262..ca936d482e 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1140,6 +1140,18 @@ stack traceback: exec_lua([[vim.print({ foo = "bar" })]]) screen:expect_unchanged() end) + + it('emits single message for normal search)', function() + feed('axx?x') + screen:expect({ + messages = { + { + content = { { '?x ' } }, + kind = 'search_cmd', + }, + }, + }) + end) end) describe('ui/builtin messages', function() -- cgit From ac7e0ff32ff18be69e2469ff7fc66370be20a6e1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 20 Nov 2024 08:24:58 +0800 Subject: fix(move): redraw for 'concealcursor' after changing w_wcol (#31276) --- src/nvim/move.c | 9 ++------ test/functional/ui/syntax_conceal_spec.lua | 33 +++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/nvim/move.c b/src/nvim/move.c index 6324466dcc..b298592683 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -150,25 +150,20 @@ static void redraw_for_cursorline(win_T *wp) } } -/// Redraw when w_virtcol changes and +/// Redraw when 'concealcursor' is active, or when w_virtcol changes and: /// - 'cursorcolumn' is set, or /// - 'cursorlineopt' contains "screenline", or -/// - 'concealcursor' is active, or /// - Visual mode is active. static void redraw_for_cursorcolumn(win_T *wp) FUNC_ATTR_NONNULL_ALL { - if (wp->w_valid & VALID_VIRTCOL) { - return; - } - // If the cursor moves horizontally when 'concealcursor' is active, then the // current line needs to be redrawn to calculate the correct cursor position. if (wp->w_p_cole > 0 && conceal_cursor_line(wp)) { redrawWinline(wp, wp->w_cursor.lnum); } - if (pum_visible()) { + if ((wp->w_valid & VALID_VIRTCOL) || pum_visible()) { return; } diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 57d76e54df..80e38d974a 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -198,7 +198,7 @@ describe('Screen', function() end) end) -- a region of text (implicit concealing) - it('cursor position is correct when entering Insert mode with cocu=ni #13916', function() + it('cursor position when entering Insert mode with cocu=ni #13916', function() insert([[foobarfoobarfoobar]]) -- move to end of line feed('$') @@ -217,6 +217,37 @@ describe('Screen', function() {4:-- INSERT --} | ]]) end) + + it('cursor position when scrolling in Normal mode with cocu=n #31271', function() + insert(('foo\n'):rep(9) .. 'foofoobarfoofoo' .. ('\nfoo'):rep(9)) + command('set concealcursor=n') + command('syn match Foo /bar/ conceal cchar=&') + feed('gg510gg$') + screen:expect([[ + foo |*4 + foofoo{1:&}foofo^o | + foo |*4 + | + ]]) + feed('zz') + screen:expect_unchanged() + feed('zt') + screen:expect([[ + foofoo{1:&}foofo^o | + foo |*8 + | + ]]) + feed('zt') + screen:expect_unchanged() + feed('zb') + screen:expect([[ + foo |*8 + foofoo{1:&}foofo^o | + | + ]]) + feed('zb') + screen:expect_unchanged() + end) end) -- match and conceal describe('let the conceal level be', function() -- cgit From fd57f39766c9f26da739214288f90be74223c3cd Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 20 Nov 2024 08:53:12 +0100 Subject: vim-patch:4927dae: runtime(compiler): fix escaping of arguments passed to :CompilerSet See newly added help entry referring to option-backslash closes: vim/vim#16084 https://github.com/vim/vim/commit/4927daef608d4bbcdce8a1098cdeeaed3112c849 Co-authored-by: Konfekt --- runtime/compiler/cppcheck.vim | 4 ++-- runtime/compiler/groff.vim | 4 ++-- runtime/compiler/mypy.vim | 4 ++-- runtime/compiler/pandoc.vim | 4 ++-- runtime/compiler/powershell.vim | 7 ++++--- runtime/compiler/pylint.vim | 3 ++- runtime/compiler/ruff.vim | 3 ++- runtime/compiler/tex.vim | 7 ++++--- 8 files changed, 20 insertions(+), 16 deletions(-) diff --git a/runtime/compiler/cppcheck.vim b/runtime/compiler/cppcheck.vim index 4df12d1714..033613c091 100644 --- a/runtime/compiler/cppcheck.vim +++ b/runtime/compiler/cppcheck.vim @@ -1,7 +1,7 @@ " vim compiler file " Compiler: cppcheck (C++ static checker) " Maintainer: Vincent B. (twinside@free.fr) -" Last Change: 2024 Nov 08 by @Konfekt +" Last Change: 2024 Nov 19 by @Konfekt if exists("current_compiler") | finish | endif let current_compiler = "cppcheck" @@ -25,7 +25,7 @@ let &l:makeprg = 'cppcheck --quiet' \ (filereadable('compile_commands.json') ? '--project=compile_commands.json' : \ (!empty(glob('*'..s:slash..'compile_commands.json', 1, 1)) ? '--project='..glob('*'..s:slash..'compile_commands.json', 1, 1)[0] : \ (empty(&path) ? '' : '-I')..join(map(filter(split(&path, ','), 'isdirectory(v:val)'),'shellescape(v:val)'), ' -I'))))) -exe 'CompilerSet makeprg='..escape(&l:makeprg, ' "') +exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"') CompilerSet errorformat= \%f:%l:%c:\ %tarning:\ %m, diff --git a/runtime/compiler/groff.vim b/runtime/compiler/groff.vim index 640146d6a1..3e9ae0488f 100644 --- a/runtime/compiler/groff.vim +++ b/runtime/compiler/groff.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Groff " Maintainer: Konfekt -" Last Change: 2024 Sep 8 +" Last Change: 2024 Nov 19 " " Expects output file extension, say `:make html` or `:make pdf`. " Supported devices as of Sept 2024 are: (x)html, pdf, ps, dvi, lj4, lbp ... @@ -30,7 +30,7 @@ execute 'CompilerSet makeprg=groff'..escape( \ ' '..s:groff_compiler_lang().. \ ' -K'..get(b:, 'groff_compiler_encoding', get(g:, 'groff_compiler_encoding', 'utf8')).. \ ' '..get(b:, 'groff_compiler_args', get(g:, 'groff_compiler_args', '')).. - \ ' -mom -T$* -- %:S > %:r:S.$*', ' ') + \ ' -mom -T$* -- %:S > %:r:S.$*', ' \|"') " From Gavin Freeborn's https://github.com/Gavinok/vim-troff under Vim License " https://github.com/Gavinok/vim-troff/blob/91017b1423caa80aba541c997909a4f810edd275/compiler/troff.vim#L39 CompilerSet errorformat=%o:\ (%f):%l:%m, diff --git a/runtime/compiler/mypy.vim b/runtime/compiler/mypy.vim index 891488626a..907b98b777 100644 --- a/runtime/compiler/mypy.vim +++ b/runtime/compiler/mypy.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Mypy (Python static checker) " Maintainer: @Konfekt -" Last Change: 2024 Nov 07 +" Last Change: 2024 Nov 19 if exists("current_compiler") | finish | endif let current_compiler = "mypy" @@ -12,7 +12,7 @@ set cpo&vim " CompilerSet makeprg=mypy let &l:makeprg = 'mypy --show-column-numbers ' \ ..get(b:, 'mypy_makeprg_params', get(g:, 'mypy_makeprg_params', '--strict --ignore-missing-imports')) -exe 'CompilerSet makeprg='..escape(&l:makeprg, ' "') +exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"') CompilerSet errorformat=%f:%l:%c:\ %t%*[^:]:\ %m let &cpo = s:cpo_save diff --git a/runtime/compiler/pandoc.vim b/runtime/compiler/pandoc.vim index 6c15e104c3..5d90a518c9 100644 --- a/runtime/compiler/pandoc.vim +++ b/runtime/compiler/pandoc.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Pandoc " Maintainer: Konfekt -" Last Change: 2024 Sep 8 +" Last Change: 2024 Nov 19 " " Expects output file extension, say `:make html` or `:make pdf`. " Passes additional arguments to pandoc, say `:make html --self-contained`. @@ -56,7 +56,7 @@ execute 'CompilerSet makeprg=pandoc'..escape( \ ' '..s:PandocLang().. \ ' --from='..s:PandocFiletype(&filetype).. \ ' '..get(b:, 'pandoc_compiler_args', get(g:, 'pandoc_compiler_args', '')).. - \ ' --output %:r:S.$* -- %:S', ' ') + \ ' --output %:r:S.$* -- %:S', ' \|"') CompilerSet errorformat=\"%f\",\ line\ %l:\ %m let &cpo = s:keepcpo diff --git a/runtime/compiler/powershell.vim b/runtime/compiler/powershell.vim index 821fea4085..3d37d7c847 100644 --- a/runtime/compiler/powershell.vim +++ b/runtime/compiler/powershell.vim @@ -3,8 +3,9 @@ " URL: https://github.com/PProvost/vim-ps1 " Contributors: Enno Nagel " Last Change: 2024 Mar 29 -" 2024 Apr 03 by The Vim Project (removed :CompilerSet definition) -" 2024 Apr 05 by The Vim Project (avoid leaving behind g:makeprg) +" 2024 Apr 03 by the Vim Project (removed :CompilerSet definition) +" 2024 Apr 05 by the Vim Project (avoid leaving behind g:makeprg) +" 2024 Nov 19 by the Vim Project (properly escape makeprg setting) if exists("current_compiler") finish @@ -49,7 +50,7 @@ let s:makeprg = g:ps1_makeprg_cmd .. ' %:p:S' " + CategoryInfo : ObjectNotFound: (Write-Ouput:String) [], CommandNotFoundException " + FullyQualifiedErrorId : CommandNotFoundException -execute 'CompilerSet makeprg=' .. escape(s:makeprg, ' ') +execute 'CompilerSet makeprg=' .. escape(s:makeprg, ' \|"') " Showing error in context with underlining. CompilerSet errorformat=%+G+%m diff --git a/runtime/compiler/pylint.vim b/runtime/compiler/pylint.vim index 4c9c23e125..96abf315ab 100644 --- a/runtime/compiler/pylint.vim +++ b/runtime/compiler/pylint.vim @@ -2,6 +2,7 @@ " Compiler: Pylint for Python " Maintainer: Daniel Moch " Last Change: 2024 Nov 07 by The Vim Project (added params variable) +" 2024 Nov 19 by the Vim Project (properly escape makeprg setting) if exists("current_compiler") | finish | endif let current_compiler = "pylint" @@ -13,7 +14,7 @@ set cpo&vim let &l:makeprg = 'pylint ' . \ '--output-format=text --msg-template="{path}:{line}:{column}:{C}: [{symbol}] {msg}" --reports=no ' . \ get(b:, "pylint_makeprg_params", get(g:, "pylint_makeprg_params", '--jobs=0')) -exe 'CompilerSet makeprg='..escape(&l:makeprg, ' "') +exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"') CompilerSet errorformat=%A%f:%l:%c:%t:\ %m,%A%f:%l:\ %m,%A%f:(%l):\ %m,%-Z%p^%.%#,%-G%.%# let &cpo = s:cpo_save diff --git a/runtime/compiler/ruff.vim b/runtime/compiler/ruff.vim index 11a69740d8..318f4fe5cb 100644 --- a/runtime/compiler/ruff.vim +++ b/runtime/compiler/ruff.vim @@ -2,6 +2,7 @@ " Compiler: Ruff (Python linter) " Maintainer: @pbnj-dragon " Last Change: 2024 Nov 07 +" 2024 Nov 19 by the Vim Project (properly escape makeprg setting) if exists("current_compiler") | finish | endif let current_compiler = "ruff" @@ -12,7 +13,7 @@ set cpo&vim " CompilerSet makeprg=ruff let &l:makeprg= 'ruff check --output-format=concise ' \ ..get(b:, 'ruff_makeprg_params', get(g:, 'ruff_makeprg_params', '--preview')) -exe 'CompilerSet makeprg='..escape(&l:makeprg, ' "') +exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"') CompilerSet errorformat=%f:%l:%c:\ %m,%f:%l:\ %m,%f:%l:%c\ -\ %m,%f: let &cpo = s:cpo_save diff --git a/runtime/compiler/tex.vim b/runtime/compiler/tex.vim index 282b3a0588..bc1623729a 100644 --- a/runtime/compiler/tex.vim +++ b/runtime/compiler/tex.vim @@ -3,8 +3,9 @@ " Maintainer: Artem Chuprina " Contributors: Enno Nagel " Last Change: 2024 Mar 29 -" 2024 Apr 03 by The Vim Project (removed :CompilerSet definition) -" 2024 Apr 05 by The Vim Project (avoid leaving behind g:makeprg) +" 2024 Apr 03 by the Vim Project (removed :CompilerSet definition) +" 2024 Apr 05 by the Vim Project (avoid leaving behind g:makeprg) +" 2024 Nov 19 by the Vim Project (properly escape makeprg setting) if exists("current_compiler") finish @@ -27,7 +28,7 @@ if exists('b:tex_ignore_makefile') || exists('g:tex_ignore_makefile') || let current_compiler = "latex" endif let s:makeprg=current_compiler .. ' -interaction=nonstopmode' - execute 'CompilerSet makeprg=' .. escape(s:makeprg, ' ') + execute 'CompilerSet makeprg=' .. escape(s:makeprg, ' \|"') else let current_compiler = 'make' endif -- cgit From de9ed1ca5478481679df23f894b92b6756be7aed Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 19 Nov 2024 22:29:18 +0100 Subject: vim-patch:9.1.0874: filetype: karel files are not detected Problem: filetype: karel files are not detected Solution: detect '*.kl' files as karel filetype, include syntax and filetype plugin (Kirill Morozov) closes: vim/vim#16075 https://github.com/vim/vim/commit/fdac54d7bbf6d68a8bf741e734b86d0f1998ac86 Co-authored-by: Kirill Morozov Co-authored-by: KnoP-01 --- runtime/ftplugin/karel.vim | 16 ++++++ runtime/lua/vim/filetype.lua | 2 + runtime/syntax/karel.vim | 112 +++++++++++++++++++++++++++++++++++++ test/old/testdir/test_filetype.vim | 1 + 4 files changed, 131 insertions(+) create mode 100644 runtime/ftplugin/karel.vim create mode 100644 runtime/syntax/karel.vim diff --git a/runtime/ftplugin/karel.vim b/runtime/ftplugin/karel.vim new file mode 100644 index 0000000000..8ccc2b32ce --- /dev/null +++ b/runtime/ftplugin/karel.vim @@ -0,0 +1,16 @@ +" Vim filetype plugin file +" Language: KAREL +" Last Change: 2024-11-18 +" Maintainer: Kirill Morozov +" Credits: Patrick Meiser-Knosowski for the initial implementation. + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +setlocal comments=:-- +setlocal commentstring=--\ %s +setlocal suffixesadd+=.kl,.KL + +let b:undo_ftplugin = "setlocal com< cms< sua<" diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index e1e73d63fe..9010adf777 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -653,6 +653,8 @@ local extension = { jsp = 'jsp', jl = 'julia', just = 'just', + kl = 'karel', + KL = 'karel', kdl = 'kdl', kv = 'kivy', kix = 'kix', diff --git a/runtime/syntax/karel.vim b/runtime/syntax/karel.vim new file mode 100644 index 0000000000..85c78529e6 --- /dev/null +++ b/runtime/syntax/karel.vim @@ -0,0 +1,112 @@ +" Vim syntax file +" Language: KAREL +" Last Change: 2024-11-17 +" Maintainer: Kirill Morozov +" Credits: Jay Strybis for the initial implementation and Patrick Knosowski +" for a couple of fixes. + +if exists("b:current_syntax") + finish +endif + +" KAREL is case-insensitive +syntax case ignore + +" Identifiers +syn match karelIdentifier /[a-zA-Z0-9_]\+/ +hi def link karelIdentifier Identifier + +" Constants +syn keyword karelConstant CR +syn region karelString start="'" end="'" +syn match karelReal /\d\+\.\d\+/ +syn match karelInteger /\d\+/ +syn keyword karelBoolean true false +hi def link karelConstant Constant +hi def link karelString String +hi def link karelInteger Number +hi def link karelReal Float +hi def link karelBoolean Boolean + +" Directives +syn match karelDirective /%[a-zA-Z]\+/ +hi def link karelDirective PreProc + +" Operators +syn keyword karelOperator AND OR NOT DIV MOD +syn match karelOperator /[\+\-\*\/\<\=\>\:\#\@]/ +syn match karelOperator /<=/ +syn match karelOperator />=/ +syn match karelOperator /<>/ +syn match karelOperator />= Date: Tue, 19 Nov 2024 22:30:50 +0100 Subject: vim-patch:9.1.0875: filetype: hyprlang detection can be improved Problem: filetype: hyprlang detection can be improved Solution: detect '/hypr/*.conf' files as hyprlang filetype, include basic syntax highlighting (Luca Saccarola) fixes: vim/vim#15875 closes: vim/vim#16064 https://github.com/vim/vim/commit/a13bd294ab2d9ab38634c9ec51fa76205af6eb62 Co-authored-by: Luca Saccarola --- runtime/lua/vim/filetype.lua | 1 + runtime/syntax/hyprlang.vim | 58 ++++++++++++++++++++++++++++++++++++++ test/old/testdir/test_filetype.vim | 2 +- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 runtime/syntax/hyprlang.vim diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 9010adf777..c23beb960c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2214,6 +2214,7 @@ local pattern = { ['/screengrab/.*%.conf$'] = 'dosini', ['^${GNUPGHOME}/gpg%.conf$'] = 'gpg', ['/boot/grub/grub%.conf$'] = 'grub', + ['/hypr/.*%.conf$'] = 'hyprlang', ['^lilo%.conf'] = starsetf('lilo'), ['^named.*%.conf$'] = 'named', ['^rndc.*%.conf$'] = 'named', diff --git a/runtime/syntax/hyprlang.vim b/runtime/syntax/hyprlang.vim new file mode 100644 index 0000000000..f36c58c646 --- /dev/null +++ b/runtime/syntax/hyprlang.vim @@ -0,0 +1,58 @@ +" Vim syntax file +" Language: hyprlang +" Maintainer: Luca Saccarola +" Last Change: 2024 nov 15 + +if exists("b:current_syntax") + finish +endif +let b:current_syntax = "hyprlang" + +syn case ignore + +syn match hyprCommand '^\s*\zs\S\+\ze\s*=' contains=hyprVariable +syn match hyprValue '=\s*\zs.\+\ze$' contains=hyprNumber,hyprFloat,hyprBoolean,hyprString,hyprColor,hyprModifier,hyprVariable,hyprComment + +syn match hyprVariable '\$\w\+' contained + +" Category +syn region hyprCategory matchgroup=hyprCategoryD start='^\s*\k\+\s*{' end='^\s*}' contains=hyprCommand,hyprValue,hyprComment,hyprCategory,hyprCategoryD + +" Variables Types +syn match hyprNumber '\%[-+]\<\d\+\>\%[%]' contained +syn match hyprFloat '\%[-+]\<\d\+\.\d\+\>\%[%]' contained +syn match hyprString '["\'].*["\']' contained +syn match hyprColor 'rgb(\(\w\|\d\)\{6})' contained +syn match hyprColor 'rgba(\(\w\|\d\)\{8})' contained +syn match hyprColor '0x\(\w\|\d\)\{8}' contained +syn keyword hyprBoolean true false yes no on off contained + +" Super Shift Alt Ctrl Control +syn keyword hyprModifier contained + \ super supershift superalt superctrl supercontrol + \ super_shift super_alt super_ctrl super_control + \ shift shiftsuper shiftalt shiftctrl shiftcontrol + \ shift_super shift_alt shift_ctrl shift_control + \ alt altsuper altshift altctrl altcontrol + \ alt_super alt_shift alt_ctrl alt_control + \ ctrl ctrlsuper ctrlshift ctrlalt ctrlcontrol + \ ctrl_super ctrl_shift ctrl_alt ctrl_control + \ control controlsuper controlshift controlalt controlctrl + \ control_super control_shift control_alt control_ctrl + +" Comments +syn match hyprComment '#.*$' + +" Link to default groups +hi def link hyprVariable Identifier +hi def link hyprCategoryD Special +hi def link hyprComment Comment +hi def link hyprNumber Constant +hi def link hyprModifier Constant +hi def link hyprFloat hyprNumber +hi def link hyprBoolean Boolean +hi def link hyprString String +hi def link hyprColor Structure +hi def link hyprCommand Keyword + +" vim: ts=8 sts=2 sw=2 et diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 2749a6a200..889386ae5b 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -353,7 +353,7 @@ func s:GetFilenameChecks() abort \ 'htmlm4': ['file.html.m4'], \ 'httest': ['file.htt', 'file.htb'], \ 'hurl': ['file.hurl'], - \ 'hyprlang': ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf'], + \ 'hyprlang': ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf', '/hypr/foo.conf'], \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], \ 'ibasic': ['file.iba', 'file.ibi'], \ 'icemenu': ['/.icewm/menu', 'any/.icewm/menu'], -- cgit From 23ead4f2cb19b05d83f1c515b35bd5896ee77365 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 19 Nov 2024 22:33:30 +0100 Subject: vim-patch:9.1.0876: filetype: openCL files are not recognized Problem: filetype: openCL files are not recognized Solution: detect '*.cl' files as opencl or lisp filetype, include a opencl syntax and filetype plugin (Wu, Zhenyu) closes: vim/vim#15825 https://github.com/vim/vim/commit/e2c27ca8eff7cc8ec852b531d5a7f328a343a761 Co-authored-by: Wu, Zhenyu --- runtime/ftplugin/opencl.vim | 12 ++++++++++++ runtime/lua/vim/filetype.lua | 2 +- runtime/lua/vim/filetype/detect.lua | 10 ++++++++++ runtime/syntax/opencl.vim | 13 +++++++++++++ test/old/testdir/test_filetype.vim | 18 +++++++++++++++++- 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 runtime/ftplugin/opencl.vim create mode 100644 runtime/syntax/opencl.vim diff --git a/runtime/ftplugin/opencl.vim b/runtime/ftplugin/opencl.vim new file mode 100644 index 0000000000..e8570fbe95 --- /dev/null +++ b/runtime/ftplugin/opencl.vim @@ -0,0 +1,12 @@ +" Vim filetype plugin file +" Language: OpenCL +" Maintainer: Wu, Zhenyu +" Last Change: 2024 Nov 19 + +if exists("b:did_ftplugin") | finish | endif +let b:did_ftplugin = 1 + +setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,:// +setlocal commentstring=/*\ %s\ */ define& include& + +let b:undo_ftplugin = "setl commentstring< comments<" diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c23beb960c..285b7e2328 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -300,6 +300,7 @@ local extension = { cho = 'chordpro', chordpro = 'chordpro', ck = 'chuck', + cl = detect.cl, eni = 'cl', icl = 'clean', cljx = 'clojure', @@ -688,7 +689,6 @@ local extension = { ily = 'lilypond', liquid = 'liquid', liq = 'liquidsoap', - cl = 'lisp', L = 'lisp', lisp = 'lisp', el = 'lisp', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 98b001bd51..81b94c69db 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -180,6 +180,16 @@ function M.changelog(_, bufnr) return 'changelog' end +--- @type vim.filetype.mapfn +function M.cl(_, bufnr) + local lines = table.concat(getlines(bufnr, 1, 4)) + if lines:match('/%*') then + return 'opencl' + else + return 'lisp' + end +end + --- @type vim.filetype.mapfn function M.class(_, bufnr) -- Check if not a Java class (starts with '\xca\xfe\xba\xbe') diff --git a/runtime/syntax/opencl.vim b/runtime/syntax/opencl.vim new file mode 100644 index 0000000000..c237aa30f9 --- /dev/null +++ b/runtime/syntax/opencl.vim @@ -0,0 +1,13 @@ +" Vim syntax file +" Language: OpenCL +" Last Change: 2024 Nov 19 +" Maintainer: Wu, Zhenyu + +if exists("b:current_syntax") + finish +endif + +" TODO: support openCL specific keywords +runtime! syntax/c.vim + +let current_syntax = "opencl" diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 889386ae5b..1caed9a9da 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -422,7 +422,7 @@ func s:GetFilenameChecks() abort \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'], \ 'liquidsoap': ['file.liq'], \ 'liquid': ['file.liquid'], - \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc', 'file.stsg', 'any/local/share/supertux2/config'], + \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc', 'file.stsg', 'any/local/share/supertux2/config'], \ 'lite': ['file.lite', 'file.lt'], \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'], \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], @@ -1192,6 +1192,22 @@ func Test_cfg_file() filetype off endfunc +func Test_cl_file() + filetype on + + call writefile(['/*', ' * Xfile.cl', ' */', 'int f() {}'], 'Xfile.cl') + split Xfile.cl + call assert_equal('opencl', &filetype) + bwipe! + + call writefile(['()'], 'Xfile.cl') + split Xfile.cl + call assert_equal('lisp', &filetype) + bwipe! + + filetype off +endfunc + func Test_d_file() filetype on -- cgit From f55c842ec7eabd2e12749411babdcadba47438bc Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 20 Nov 2024 08:51:39 +0100 Subject: vim-patch:9.1.0880: filetype: C3 files are not recognized Problem: filetype: C3 files are not recognized Solution: detect '*.c3*' files as c3 filetype (Turiiya) closes: vim/vim#16087 https://github.com/vim/vim/commit/c8dfcfc53ba5ed69b5d4e534fd7e8694de014e6a Co-authored-by: Turiiya <34311583+ttytm@users.noreply.github.com> --- runtime/lua/vim/filetype.lua | 3 +++ test/old/testdir/test_filetype.vim | 1 + 2 files changed, 4 insertions(+) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 285b7e2328..4383c0983e 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -275,6 +275,9 @@ local extension = { mdh = 'c', epro = 'c', qc = 'c', + c3 = 'c3', + c3i = 'c3', + c3t = 'c3', cabal = 'cabal', cairo = 'cairo', capnp = 'capnp', diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 1caed9a9da..62e339c1ca 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -144,6 +144,7 @@ func s:GetFilenameChecks() abort \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'], \ 'bzr': ['bzr_log.any', 'bzr_log.file'], \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg', 'file.mdh', 'file.epro'], + \ 'c3': ['file.c3', 'file.c3i', 'file.c3t'], \ 'cabal': ['file.cabal'], \ 'cabalconfig': ['cabal.config', expand("$HOME/.config/cabal/config")] + s:WhenConfigHome('$XDG_CONFIG_HOME/cabal/config'), \ 'cabalproject': ['cabal.project', 'cabal.project.local'], -- cgit From 454ae672aad172a299dcff7c33c5e61a3b631c90 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 14 Nov 2024 11:53:20 +0000 Subject: feat(lsp): deprecate non-method client functions Deprecated: - `client.request()` -> `client:request()` - `client.request_sync()` -> `client:request_sync()` - `client.notify()` -> `client:notify()` - `client.cancel_request()` -> `client:cancel_request()` - `client.stop()` -> `client:stop()` - `client.is_stopped()` `client:is_stopped()` - `client.supports_method()` -> `client:supports_method()` - `client.on_attach()` -> `client:on_attach()` Fixed docgen to link class fields to the full function doc. --- runtime/doc/deprecated.txt | 8 ++ runtime/doc/lsp.txt | 169 +++++++++++++++++-------- runtime/doc/lua.txt | 10 +- runtime/doc/news.txt | 1 + runtime/lua/vim/lsp.lua | 54 ++++---- runtime/lua/vim/lsp/_changetracking.lua | 6 +- runtime/lua/vim/lsp/_tagfunc.lua | 2 +- runtime/lua/vim/lsp/_watchfiles.lua | 2 +- runtime/lua/vim/lsp/buf.lua | 30 +++-- runtime/lua/vim/lsp/client.lua | 214 ++++++++++++++------------------ runtime/lua/vim/lsp/codelens.lua | 2 +- runtime/lua/vim/lsp/completion.lua | 6 +- runtime/lua/vim/lsp/inlay_hint.lua | 4 +- runtime/lua/vim/lsp/semantic_tokens.lua | 8 +- runtime/lua/vim/lsp/util.lua | 4 +- scripts/gen_vimdoc.lua | 27 ++-- scripts/luacats_parser.lua | 25 ++-- test/functional/plugin/lsp/testutil.lua | 13 +- test/functional/plugin/lsp_spec.lua | 176 +++++++++++++------------- 19 files changed, 403 insertions(+), 358 deletions(-) diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 5e809ad26c..d0cbfefb47 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -56,6 +56,14 @@ LSP No longer support client to server response handlers. Only server to client requests/notification handlers are supported. • *vim.lsp.handlers.signature_help()* Use |vim.lsp.buf.signature_help()| instead. +• `client.request()` Use |Client:request()| instead. +• `client.request_sync()` Use |Client:request_sync()| instead. +• `client.notify()` Use |Client:notify()| instead. +• `client.cancel_request()` Use |Client:cancel_request()| instead. +• `client.stop()` Use |Client:stop()| instead. +• `client.is_stopped()` Use |Client:is_stopped()| instead. +• `client.supports_method()` Use |Client:supports_method()| instead. +• `client.on_attach()` Use |Client:on_attach()| instead. ------------------------------------------------------------------------------ DEPRECATED IN 0.10 *deprecated-0.10* diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 64bef849fc..ae26abd34d 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -972,54 +972,24 @@ Lua module: vim.lsp.client *lsp-client* • {capabilities} (`lsp.ClientCapabilities`) The capabilities provided by the client (editor or tool) • {dynamic_capabilities} (`lsp.DynamicCapabilities`) - • {request} (`fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer?`) - Sends a request to the server. This is a thin - wrapper around {client.rpc.request} with some - additional checking. If {handler} is not - specified and if there's no respective global - handler, then an error will occur. Returns: - {status}, {client_id}?. {status} is a boolean - indicating if the notification was successful. - If it is `false`, then it will always be - `false` (the client has shutdown). If {status} - is `true`, the function returns {request_id} - as the second result. You can use this with - `client.cancel_request(request_id)` to cancel - the request. - • {request_sync} (`fun(method: string, params: table?, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError?, result:any}?, string?`) - err # a dict - • {notify} (`fun(method: string, params: table?): boolean`) - Sends a notification to an LSP server. - Returns: a boolean to indicate if the - notification was successful. If it is false, - then it will always be false (the client has - shutdown). - • {cancel_request} (`fun(id: integer): boolean`) Cancels a - request with a given request id. Returns: same - as `notify()`. - • {stop} (`fun(force?: boolean)`) Stops a client, - optionally with force. By default, it will - just ask the server to shutdown without force. - If you request to stop a client which has - previously been requested to shutdown, it will - automatically escalate and force shutdown. - • {on_attach} (`fun(bufnr: integer)`) Runs the on_attach - function from the client's config if it was - defined. Useful for buffer-local setup. - • {supports_method} (`fun(method: string, opts?: {bufnr: integer?}): boolean`) - Checks if a client supports a given method. - Always returns true for unknown off-spec - methods. {opts} is a optional - `{bufnr?: integer}` table. Some language - server capabilities can be file specific. - • {is_stopped} (`fun(): boolean`) Checks whether a client is - stopped. Returns: true if the client is fully - stopped. + • {request} (`fun(self: vim.lsp.Client, method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer?`) + See |Client:request()|. + • {request_sync} (`fun(self: vim.lsp.Client, method: string, params: table, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError?, result:any}?, string?`) + See |Client:request_sync()|. + • {notify} (`fun(self: vim.lsp.Client, method: string, params: table?): boolean`) + See |Client:notify()|. + • {cancel_request} (`fun(self: vim.lsp.Client, id: integer): boolean`) + See |Client:cancel_request()|. + • {stop} (`fun(self: vim.lsp.Client, force: boolean?)`) + See |Client:stop()|. + • {is_stopped} (`fun(self: vim.lsp.Client): boolean`) See + |Client:is_stopped()|. • {exec_cmd} (`fun(self: vim.lsp.Client, command: lsp.Command, context: {bufnr?: integer}?, handler: lsp.Handler?)`) - Execute a lsp command, either via client - command function (if available) or via - workspace/executeCommand (if supported by the - server) + See |Client:exec_cmd()|. + • {on_attach} (`fun(self: vim.lsp.Client, bufnr: integer)`) + See |Client:on_attach()|. + • {supports_method} (`fun(self: vim.lsp.Client, method: string, bufnr: integer?)`) + See |Client:supports_method()|. *vim.lsp.Client.Progress* Extends: |vim.Ringbuf| @@ -1150,6 +1120,18 @@ Lua module: vim.lsp.client *lsp-client* on initialization. +Client:cancel_request({id}) *Client:cancel_request()* + Cancels a request with a given request id. + + Parameters: ~ + • {id} (`integer`) id of request to cancel + + Return: ~ + (`boolean`) status indicating if the notification was successful. + + See also: ~ + • |Client:notify()| + Client:exec_cmd({command}, {context}, {handler}) *Client:exec_cmd()* Execute a lsp command, either via client command function (if available) or via workspace/executeCommand (if supported by the server) @@ -1159,6 +1141,95 @@ Client:exec_cmd({command}, {context}, {handler}) *Client:exec_cmd()* • {context} (`{bufnr?: integer}?`) • {handler} (`lsp.Handler?`) only called if a server command +Client:is_stopped() *Client:is_stopped()* + Checks whether a client is stopped. + + Return: ~ + (`boolean`) true if client is stopped or in the process of being + stopped; false otherwise + +Client:notify({method}, {params}) *Client:notify()* + Sends a notification to an LSP server. + + Parameters: ~ + • {method} (`string`) LSP method name. + • {params} (`table?`) LSP request params. + + Return: ~ + (`boolean`) status indicating if the notification was successful. If + it is false, then the client has shutdown. + +Client:on_attach({bufnr}) *Client:on_attach()* + Runs the on_attach function from the client's config if it was defined. + Useful for buffer-local setup. + + Parameters: ~ + • {bufnr} (`integer`) Buffer number + + *Client:request()* +Client:request({method}, {params}, {handler}, {bufnr}) + Sends a request to the server. + + This is a thin wrapper around {client.rpc.request} with some additional + checks for capabilities and handler availability. + + Parameters: ~ + • {method} (`string`) LSP method name. + • {params} (`table?`) LSP request params. + • {handler} (`lsp.Handler?`) Response |lsp-handler| for this method. + • {bufnr} (`integer?`) Buffer handle. 0 for current (default). + + Return (multiple): ~ + (`boolean`) status indicates whether the request was successful. If it + is `false`, then it will always be `false` (the client has shutdown). + (`integer?`) request_id Can be used with |Client:cancel_request()|. + `nil` is request failed. + + See also: ~ + • |vim.lsp.buf_request_all()| + + *Client:request_sync()* +Client:request_sync({method}, {params}, {timeout_ms}, {bufnr}) + Sends a request to the server and synchronously waits for the response. + + This is a wrapper around |Client:request()| + + Parameters: ~ + • {method} (`string`) LSP method name. + • {params} (`table`) LSP request params. + • {timeout_ms} (`integer?`) Maximum time in milliseconds to wait for a + result. Defaults to 1000 + • {bufnr} (`integer`) Buffer handle (0 for current). + + Return (multiple): ~ + (`{err: lsp.ResponseError?, result:any}?`) `result` and `err` from the + |lsp-handler|. `nil` is the request was unsuccessful + (`string?`) err On timeout, cancel or error, where `err` is a string + describing the failure reason. + + See also: ~ + • |vim.lsp.buf_request_sync()| + +Client:stop({force}) *Client:stop()* + Stops a client, optionally with force. + + By default, it will just request the server to shutdown without force. If + you request to stop a client which has previously been requested to + shutdown, it will automatically escalate and force shutdown. + + Parameters: ~ + • {force} (`boolean?`) + +Client:supports_method({method}, {bufnr}) *Client:supports_method()* + Checks if a client supports a given method. Always returns true for + unknown off-spec methods. + + Note: Some language server capabilities can be file specific. + + Parameters: ~ + • {method} (`string`) + • {bufnr} (`integer?`) + ============================================================================== Lua module: vim.lsp.buf *lsp-buf* @@ -1599,12 +1670,12 @@ get({filter}) *vim.lsp.inlay_hint.get()* local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer local client = vim.lsp.get_client_by_id(hint.client_id) - local resp = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0) + local resp = client:request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0) local resolved_hint = assert(resp and resp.result, resp.err) vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding) location = resolved_hint.label[1].location - client.request('textDocument/hover', { + client:request('textDocument/hover', { textDocument = { uri = location.uri }, position = location.range.start, }) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 243c907180..4d4a51872a 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1937,12 +1937,10 @@ vim.show_pos({bufnr}, {row}, {col}, {filter}) *vim.show_pos()* *vim.Ringbuf* Fields: ~ - • {clear} (`fun()`) Clear all items - • {push} (`fun(item: T)`) Adds an item, overriding the oldest item if - the buffer is full. - • {pop} (`fun(): T?`) Removes and returns the first unread item - • {peek} (`fun(): T?`) Returns the first unread item without removing - it + • {clear} (`fun()`) See |Ringbuf:clear()|. + • {push} (`fun(item: T)`) See |Ringbuf:push()|. + • {pop} (`fun(): T?`) See |Ringbuf:pop()|. + • {peek} (`fun(): T?`) See |Ringbuf:peek()|. Ringbuf:clear() *Ringbuf:clear()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index d19df84023..32deb85278 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -218,6 +218,7 @@ LSP • The client now supports `'utf-8'` and `'utf-32'` position encodings. • |vim.lsp.buf.hover()| now highlights hover ranges using the |hl-LspReferenceTarget| highlight group. +• Functions in |vim.lsp.Client| can now be called as methods. LUA diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 0de3b4ee4d..c032d25cb1 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -349,17 +349,17 @@ end ---@param bufnr integer function lsp._set_defaults(client, bufnr) if - client.supports_method(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc') + client:supports_method(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc') then vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc' end if - client.supports_method(ms.textDocument_completion) and is_empty_or_default(bufnr, 'omnifunc') + client:supports_method(ms.textDocument_completion) and is_empty_or_default(bufnr, 'omnifunc') then vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc' end if - client.supports_method(ms.textDocument_rangeFormatting) + client:supports_method(ms.textDocument_rangeFormatting) and is_empty_or_default(bufnr, 'formatprg') and is_empty_or_default(bufnr, 'formatexpr') then @@ -367,14 +367,14 @@ function lsp._set_defaults(client, bufnr) end vim._with({ buf = bufnr }, function() if - client.supports_method(ms.textDocument_hover) + client:supports_method(ms.textDocument_hover) and is_empty_or_default(bufnr, 'keywordprg') and vim.fn.maparg('K', 'n', false, false) == '' then vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr, desc = 'vim.lsp.buf.hover()' }) end end) - if client.supports_method(ms.textDocument_diagnostic) then + if client:supports_method(ms.textDocument_diagnostic) then lsp.diagnostic._enable(bufnr) end end @@ -485,12 +485,12 @@ local function text_document_did_save_handler(bufnr) local name = api.nvim_buf_get_name(bufnr) local old_name = changetracking._get_and_set_name(client, bufnr, name) if old_name and name ~= old_name then - client.notify(ms.textDocument_didClose, { + client:notify(ms.textDocument_didClose, { textDocument = { uri = vim.uri_from_fname(old_name), }, }) - client.notify(ms.textDocument_didOpen, { + client:notify(ms.textDocument_didOpen, { textDocument = { version = 0, uri = uri, @@ -506,7 +506,7 @@ local function text_document_did_save_handler(bufnr) if type(save_capability) == 'table' and save_capability.includeText then included_text = text(bufnr) end - client.notify(ms.textDocument_didSave, { + client:notify(ms.textDocument_didSave, { textDocument = { uri = uri, }, @@ -527,10 +527,10 @@ local function buf_detach_client(bufnr, client) changetracking.reset_buf(client, bufnr) - if client.supports_method(ms.textDocument_didClose) then + if client:supports_method(ms.textDocument_didClose) then local uri = vim.uri_from_bufnr(bufnr) local params = { textDocument = { uri = uri } } - client.notify(ms.textDocument_didClose, params) + client:notify(ms.textDocument_didClose, params) end client.attached_buffers[bufnr] = nil @@ -564,12 +564,12 @@ local function buf_attach(bufnr) }, reason = protocol.TextDocumentSaveReason.Manual, ---@type integer } - if client.supports_method(ms.textDocument_willSave) then - client.notify(ms.textDocument_willSave, params) + if client:supports_method(ms.textDocument_willSave) then + client:notify(ms.textDocument_willSave, params) end - if client.supports_method(ms.textDocument_willSaveWaitUntil) then + if client:supports_method(ms.textDocument_willSaveWaitUntil) then local result, err = - client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) + client:request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) if result and result.result then util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) elseif err then @@ -603,8 +603,8 @@ local function buf_attach(bufnr) local params = { textDocument = { uri = uri } } for _, client in ipairs(clients) do changetracking.reset_buf(client, bufnr) - if client.supports_method(ms.textDocument_didClose) then - client.notify(ms.textDocument_didClose, params) + if client:supports_method(ms.textDocument_didClose) then + client:notify(ms.textDocument_didClose, params) end end for _, client in ipairs(clients) do @@ -662,7 +662,7 @@ function lsp.buf_attach_client(bufnr, client_id) -- Send didOpen for the client if it is initialized. If it isn't initialized -- then it will send didOpen on initialize. if client.initialized then - client:_on_attach(bufnr) + client:on_attach(bufnr) end return true end @@ -740,13 +740,13 @@ function lsp.stop_client(client_id, force) for _, id in ipairs(ids) do if type(id) == 'table' then if id.stop then - id.stop(force) + id:stop(force) end else --- @cast id -vim.lsp.Client local client = all_clients[id] if client then - client.stop(force) + client:stop(force) end end end @@ -790,7 +790,7 @@ function lsp.get_clients(filter) and (filter.id == nil or client.id == filter.id) and (filter.bufnr == nil or client.attached_buffers[bufnr]) and (filter.name == nil or client.name == filter.name) - and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr })) + and (filter.method == nil or client:supports_method(filter.method, filter.bufnr)) and (filter._uninitialized or client.initialized) then clients[#clients + 1] = client @@ -812,7 +812,7 @@ api.nvim_create_autocmd('VimLeavePre', { local active_clients = lsp.get_clients() log.info('exit_handler', active_clients) for _, client in pairs(all_clients) do - client.stop() + client:stop() end local timeouts = {} --- @type table @@ -847,7 +847,7 @@ api.nvim_create_autocmd('VimLeavePre', { if not vim.wait(max_timeout, check_clients_closed, poll_time) then for client_id, client in pairs(active_clients) do if timeouts[client_id] ~= nil then - client.stop(true) + client:stop(true) end end end @@ -883,11 +883,11 @@ function lsp.buf_request(bufnr, method, params, handler, on_unsupported) local clients = lsp.get_clients({ bufnr = bufnr }) local client_request_ids = {} --- @type table for _, client in ipairs(clients) do - if client.supports_method(method, { bufnr = bufnr }) then + if client:supports_method(method, bufnr) then method_supported = true local cparams = type(params) == 'function' and params(client, bufnr) or params --[[@as table?]] - local request_success, request_id = client.request(method, cparams, handler, bufnr) + local request_success, request_id = client:request(method, cparams, handler, bufnr) -- This could only fail if the client shut down in the time since we looked -- it up and we did the request, which should be rare. if request_success then @@ -910,7 +910,7 @@ function lsp.buf_request(bufnr, method, params, handler, on_unsupported) local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do local client = all_clients[client_id] - client.cancel_request(request_id) + client:cancel_request(request_id) end end @@ -1049,7 +1049,7 @@ function lsp.formatexpr(opts) end local bufnr = api.nvim_get_current_buf() for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do - if client.supports_method(ms.textDocument_rangeFormatting) then + if client:supports_method(ms.textDocument_rangeFormatting) then local params = util.make_formatting_params() local end_line = vim.fn.getline(end_lnum) --[[@as string]] local end_col = vim.str_utfindex(end_line, client.offset_encoding) @@ -1065,7 +1065,7 @@ function lsp.formatexpr(opts) }, } local response = - client.request_sync(ms.textDocument_rangeFormatting, params, timeout_ms, bufnr) + client:request_sync(ms.textDocument_rangeFormatting, params, timeout_ms, bufnr) if response and response.result then lsp.util.apply_text_edits(response.result, bufnr, client.offset_encoding) return 0 diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua index b2be53269f..8588502697 100644 --- a/runtime/lua/vim/lsp/_changetracking.lua +++ b/runtime/lua/vim/lsp/_changetracking.lua @@ -40,7 +40,7 @@ local M = {} --- @class vim.lsp.CTGroupState --- @field buffers table --- @field debounce integer debounce duration in ms ---- @field clients table clients using this state. {client_id, client} +--- @field clients table clients using this state. {client_id, client} ---@param group vim.lsp.CTGroup ---@return string @@ -273,8 +273,8 @@ local function send_changes(bufnr, sync_kind, state, buf_state) end local uri = vim.uri_from_bufnr(bufnr) for _, client in pairs(state.clients) do - if not client.is_stopped() and vim.lsp.buf_is_attached(bufnr, client.id) then - client.notify(protocol.Methods.textDocument_didChange, { + if not client:is_stopped() and vim.lsp.buf_is_attached(bufnr, client.id) then + client:notify(protocol.Methods.textDocument_didChange, { textDocument = { uri = uri, version = util.buf_versions[bufnr], diff --git a/runtime/lua/vim/lsp/_tagfunc.lua b/runtime/lua/vim/lsp/_tagfunc.lua index f75d43f373..f6ffc63824 100644 --- a/runtime/lua/vim/lsp/_tagfunc.lua +++ b/runtime/lua/vim/lsp/_tagfunc.lua @@ -59,7 +59,7 @@ local function query_definition(pattern) remaining = remaining - 1 end local params = util.make_position_params(win, client.offset_encoding) - client.request(ms.textDocument_definition, params, on_response, bufnr) + client:request(ms.textDocument_definition, params, on_response, bufnr) end vim.wait(1000, function() return remaining == 0 diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index c4cdb5aea8..248969885c 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -116,7 +116,7 @@ function M.register(reg, client_id) local params = { changes = change_queues[client_id], } - client.notify(ms.workspace_didChangeWatchedFiles, params) + client:notify(ms.workspace_didChangeWatchedFiles, params) queue_timers[client_id] = nil change_queues[client_id] = nil change_cache[client_id] = nil diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 6383855a30..10c0dbefdc 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -232,7 +232,7 @@ local function get_locations(method, opts) end for _, client in ipairs(clients) do local params = util.make_position_params(win, client.offset_encoding) - client.request(method, params, function(_, result) + client:request(method, params, function(_, result) on_response(_, result, client) end) end @@ -568,12 +568,14 @@ function M.format(opts) end if opts.async then + --- @param idx integer + --- @param client vim.lsp.Client local function do_format(idx, client) if not client then return end local params = set_range(client, util.make_formatting_params(opts.formatting_options)) - client.request(method, params, function(...) + client:request(method, params, function(...) local handler = client.handlers[method] or lsp.handlers[method] handler(...) do_format(next(clients, idx)) @@ -584,7 +586,7 @@ function M.format(opts) local timeout_ms = opts.timeout_ms or 1000 for _, client in pairs(clients) do local params = set_range(client, util.make_formatting_params(opts.formatting_options)) - local result, err = client.request_sync(method, params, timeout_ms, bufnr) + local result, err = client:request_sync(method, params, timeout_ms, bufnr) if result and result.result then util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then @@ -648,6 +650,8 @@ function M.rename(new_name, opts) )[1] end + --- @param idx integer + --- @param client? vim.lsp.Client local function try_use_client(idx, client) if not client then return @@ -659,15 +663,15 @@ function M.rename(new_name, opts) params.newName = name local handler = client.handlers[ms.textDocument_rename] or lsp.handlers[ms.textDocument_rename] - client.request(ms.textDocument_rename, params, function(...) + client:request(ms.textDocument_rename, params, function(...) handler(...) try_use_client(next(clients, idx)) end, bufnr) end - if client.supports_method(ms.textDocument_prepareRename) then + if client:supports_method(ms.textDocument_prepareRename) then local params = util.make_position_params(win, client.offset_encoding) - client.request(ms.textDocument_prepareRename, params, function(err, result) + client:request(ms.textDocument_prepareRename, params, function(err, result) if err or result == nil then if next(clients, idx) then try_use_client(next(clients, idx)) @@ -706,7 +710,7 @@ function M.rename(new_name, opts) end, bufnr) else assert( - client.supports_method(ms.textDocument_rename), + client:supports_method(ms.textDocument_rename), 'Client must support textDocument/rename' ) if new_name then @@ -781,7 +785,7 @@ function M.references(context, opts) params.context = context or { includeDeclaration = true, } - client.request(ms.textDocument_references, params, function(_, result) + client:request(ms.textDocument_references, params, function(_, result) local items = util.locations_to_items(result or {}, client.offset_encoding) vim.list_extend(all_items, items) remaining = remaining - 1 @@ -813,7 +817,7 @@ local function request_with_id(client_id, method, params, handler, bufnr) ) return end - client.request(method, params, handler, bufnr) + client:request(method, params, handler, bufnr) end --- @param item lsp.TypeHierarchyItem|lsp.CallHierarchyItem @@ -880,7 +884,7 @@ local function hierarchy(method) for _, client in ipairs(clients) do local params = util.make_position_params(win, client.offset_encoding) --- @param result lsp.CallHierarchyItem[]|lsp.TypeHierarchyItem[]? - client.request(prepare_method, params, function(err, result, ctx) + client:request(prepare_method, params, function(err, result, ctx) if err then vim.notify(err.message, vim.log.levels.WARN) elseif result then @@ -1131,8 +1135,8 @@ local function on_code_action_results(results, opts) local action = choice.action local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number') - if not action.edit and client.supports_method(ms.codeAction_resolve) then - client.request(ms.codeAction_resolve, action, function(err, resolved_action) + if not action.edit and client:supports_method(ms.codeAction_resolve) then + client:request(ms.codeAction_resolve, action, function(err, resolved_action) if err then if action.command then apply_action(action, client, choice.ctx) @@ -1253,7 +1257,7 @@ function M.code_action(opts) }) end - client.request(ms.textDocument_codeAction, params, on_result, bufnr) + client:request(ms.textDocument_codeAction, params, on_result, bufnr) end end diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 11ecb87507..3b79da99ea 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -219,70 +219,28 @@ local validate = vim.validate --- @field private registrations table --- @field dynamic_capabilities lsp.DynamicCapabilities --- ---- Sends a request to the server. ---- This is a thin wrapper around {client.rpc.request} with some additional ---- checking. ---- If {handler} is not specified and if there's no respective global ---- handler, then an error will occur. ---- Returns: {status}, {client_id}?. {status} is a boolean indicating if ---- the notification was successful. If it is `false`, then it will always ---- be `false` (the client has shutdown). ---- If {status} is `true`, the function returns {request_id} as the second ---- result. You can use this with `client.cancel_request(request_id)` to cancel ---- the request. ---- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer? ---- ---- Sends a request to the server and synchronously waits for the response. ---- This is a wrapper around {client.request} ---- Returns: { err=err, result=result }, a dict, where `err` and `result` ---- come from the |lsp-handler|. On timeout, cancel or error, returns `(nil, ---- err)` where `err` is a string describing the failure reason. If the request ---- was unsuccessful returns `nil`. ---- @field request_sync fun(method: string, params: table?, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dict ---- ---- Sends a notification to an LSP server. ---- Returns: a boolean to indicate if the notification was successful. If ---- it is false, then it will always be false (the client has shutdown). ---- @field notify fun(method: string, params: table?): boolean ---- ---- Cancels a request with a given request id. ---- Returns: same as `notify()`. ---- @field cancel_request fun(id: integer): boolean ---- ---- Stops a client, optionally with force. ---- By default, it will just ask the server to shutdown without force. ---- If you request to stop a client which has previously been requested to ---- shutdown, it will automatically escalate and force shutdown. ---- @field stop fun(force?: boolean) ---- ---- Runs the on_attach function from the client's config if it was defined. ---- Useful for buffer-local setup. ---- @field on_attach fun(bufnr: integer) ---- --- @field private _before_init_cb? vim.lsp.client.before_init_cb --- @field private _on_attach_cbs vim.lsp.client.on_attach_cb[] --- @field private _on_init_cbs vim.lsp.client.on_init_cb[] --- @field private _on_exit_cbs vim.lsp.client.on_exit_cb[] --- @field private _on_error_cb? fun(code: integer, err: string) ---- ---- Checks if a client supports a given method. ---- Always returns true for unknown off-spec methods. ---- {opts} is a optional `{bufnr?: integer}` table. ---- Some language server capabilities can be file specific. ---- @field supports_method fun(method: string, opts?: {bufnr: integer?}): boolean ---- ---- Checks whether a client is stopped. ---- Returns: true if the client is fully stopped. ---- @field is_stopped fun(): boolean local Client = {} Client.__index = Client ---- @param cls table ---- @param meth any ---- @return function -local function method_wrapper(cls, meth) - return function(...) - return meth(cls, ...) +--- @param obj table +--- @param cls table +--- @param name string +local function method_wrapper(obj, cls, name) + local meth = assert(cls[name]) + obj[name] = function(...) + local arg = select(1, ...) + if arg and getmetatable(arg) == cls then + -- First argument is self, call meth directly + return meth(...) + end + vim.deprecate('client.' .. name, 'client:' .. name, '0.13') + -- First argument is not self, insert it + return meth(obj, ...) end end @@ -499,24 +457,23 @@ function Client.create(config) end, } - self.request = method_wrapper(self, Client._request) - self.request_sync = method_wrapper(self, Client._request_sync) - self.notify = method_wrapper(self, Client._notify) - self.cancel_request = method_wrapper(self, Client._cancel_request) - self.stop = method_wrapper(self, Client._stop) - self.is_stopped = method_wrapper(self, Client._is_stopped) - self.on_attach = method_wrapper(self, Client._on_attach) - self.supports_method = method_wrapper(self, Client._supports_method) - --- @type table title of unfinished progress sequences by token self.progress.pending = {} --- @type vim.lsp.rpc.Dispatchers local dispatchers = { - notification = method_wrapper(self, Client._notification), - server_request = method_wrapper(self, Client._server_request), - on_error = method_wrapper(self, Client._on_error), - on_exit = method_wrapper(self, Client._on_exit), + notification = function(...) + return self:_notification(...) + end, + server_request = function(...) + return self:_server_request(...) + end, + on_error = function(...) + return self:_on_error(...) + end, + on_exit = function(...) + return self:_on_exit(...) + end, } -- Start the RPC client. @@ -533,6 +490,15 @@ function Client.create(config) setmetatable(self, Client) + method_wrapper(self, Client, 'request') + method_wrapper(self, Client, 'request_sync') + method_wrapper(self, Client, 'notify') + method_wrapper(self, Client, 'cancel_request') + method_wrapper(self, Client, 'stop') + method_wrapper(self, Client, 'is_stopped') + method_wrapper(self, Client, 'on_attach') + method_wrapper(self, Client, 'supports_method') + return self end @@ -616,7 +582,7 @@ function Client:initialize() end if next(self.settings) then - self:_notify(ms.workspace_didChangeConfiguration, { settings = self.settings }) + self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings }) end -- If server is being restarted, make sure to re-attach to any previously attached buffers. @@ -628,7 +594,7 @@ function Client:initialize() for buf in pairs(reattach_bufs) do -- The buffer may have been detached in the on_init callback. if self.attached_buffers[buf] then - self:_on_attach(buf) + self:on_attach(buf) end end @@ -645,14 +611,14 @@ end --- Returns the default handler if the user hasn't set a custom one. --- --- @param method (string) LSP method name ---- @return lsp.Handler|nil handler for the given method, if defined, or the default from |vim.lsp.handlers| +--- @return lsp.Handler? handler for the given method, if defined, or the default from |vim.lsp.handlers| function Client:_resolve_handler(method) return self.handlers[method] or lsp.handlers[method] end --- Returns the buffer number for the given {bufnr}. --- ---- @param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer +--- @param bufnr integer? Buffer number to resolve. Defaults to current buffer --- @return integer bufnr local function resolve_bufnr(bufnr) validate('bufnr', bufnr, 'number', true) @@ -662,7 +628,6 @@ local function resolve_bufnr(bufnr) return bufnr end ---- @private --- Sends a request to the server. --- --- This is a thin wrapper around {client.rpc.request} with some additional @@ -671,15 +636,14 @@ end --- @param method string LSP method name. --- @param params? table LSP request params. --- @param handler? lsp.Handler Response |lsp-handler| for this method. ---- @param bufnr integer Buffer handle (0 for current). ---- @return boolean status, integer? request_id {status} is a bool indicating ---- whether the request was successful. If it is `false`, then it will ---- always be `false` (the client has shutdown). If it was ---- successful, then it will return {request_id} as the ---- second result. You can use this with `client.cancel_request(request_id)` +--- @param bufnr? integer Buffer handle. 0 for current (default). +--- @return boolean status indicates whether the request was successful. +--- If it is `false`, then it will always be `false` (the client has shutdown). +--- @return integer? request_id Can be used with |Client:cancel_request()|. +--- `nil` is request failed. --- to cancel the-request. --- @see |vim.lsp.buf_request_all()| -function Client:_request(method, params, handler, bufnr) +function Client:request(method, params, handler, bufnr) if not handler then handler = assert( self:_resolve_handler(method), @@ -688,8 +652,8 @@ function Client:_request(method, params, handler, bufnr) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state changetracking.flush(self, bufnr) - local version = lsp.util.buf_versions[bufnr] bufnr = resolve_bufnr(bufnr) + local version = lsp.util.buf_versions[bufnr] log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr) local success, request_id = self.rpc.request(method, params, function(err, result) local context = { @@ -743,29 +707,27 @@ local function err_message(...) end end ---- @private --- Sends a request to the server and synchronously waits for the response. --- ---- This is a wrapper around {client.request} +--- This is a wrapper around |Client:request()| --- ---- @param method (string) LSP method name. ---- @param params (table) LSP request params. ---- @param timeout_ms (integer|nil) Maximum time in milliseconds to wait for +--- @param method string LSP method name. +--- @param params table LSP request params. +--- @param timeout_ms integer? Maximum time in milliseconds to wait for --- a result. Defaults to 1000 ---- @param bufnr (integer) Buffer handle (0 for current). ---- @return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dict, where ---- `err` and `result` come from the |lsp-handler|. ---- On timeout, cancel or error, returns `(nil, err)` where `err` is a ---- string describing the failure reason. If the request was unsuccessful ---- returns `nil`. +--- @param bufnr integer Buffer handle (0 for current). +--- @return {err: lsp.ResponseError?, result:any}? `result` and `err` from the |lsp-handler|. +--- `nil` is the request was unsuccessful +--- @return string? err On timeout, cancel or error, where `err` is a +--- string describing the failure reason. --- @see |vim.lsp.buf_request_sync()| -function Client:_request_sync(method, params, timeout_ms, bufnr) +function Client:request_sync(method, params, timeout_ms, bufnr) local request_result = nil local function _sync_handler(err, result) request_result = { err = err, result = result } end - local success, request_id = self:_request(method, params, _sync_handler, bufnr) + local success, request_id = self:request(method, params, _sync_handler, bufnr) if not success then return nil end @@ -776,22 +738,20 @@ function Client:_request_sync(method, params, timeout_ms, bufnr) if not wait_result then if request_id then - self:_cancel_request(request_id) + self:cancel_request(request_id) end return nil, wait_result_reason[reason] end return request_result end ---- @package --- Sends a notification to an LSP server. --- --- @param method string LSP method name. ---- @param params table|nil LSP request params. ---- @return boolean status true if the notification was successful. ---- If it is false, then it will always be false ---- (the client has shutdown). -function Client:_notify(method, params) +--- @param params table? LSP request params. +--- @return boolean status indicating if the notification was successful. +--- If it is false, then the client has shutdown. +function Client:notify(method, params) if method ~= ms.textDocument_didChange then changetracking.flush(self) end @@ -814,13 +774,12 @@ function Client:_notify(method, params) return client_active end ---- @private --- Cancels a request with a given request id. --- ---- @param id (integer) id of request to cancel ---- @return boolean status true if notification was successful. false otherwise ---- @see |vim.lsp.client.notify()| -function Client:_cancel_request(id) +--- @param id integer id of request to cancel +--- @return boolean status indicating if the notification was successful. +--- @see |Client:notify()| +function Client:cancel_request(id) validate('id', id, 'number') local request = self.requests[id] if request and request.type == 'pending' then @@ -834,15 +793,14 @@ function Client:_cancel_request(id) return self.rpc.notify(ms.dollar_cancelRequest, { id = id }) end ---- @private --- Stops a client, optionally with force. --- ---- By default, it will just ask the - server to shutdown without force. If +--- By default, it will just request the server to shutdown without force. If --- you request to stop a client which has previously been requested to --- shutdown, it will automatically escalate and force shutdown. --- ---- @param force boolean|nil -function Client:_stop(force) +--- @param force? boolean +function Client:stop(force) local rpc = self.rpc if rpc.is_closing() then @@ -966,12 +924,11 @@ function Client:_get_registration(method, bufnr) end end ---- @private --- Checks whether a client is stopped. --- --- @return boolean # true if client is stopped or in the process of being --- stopped; false otherwise -function Client:_is_stopped() +function Client:is_stopped() return self.rpc.is_closing() end @@ -1013,7 +970,7 @@ function Client:exec_cmd(command, context, handler) command = cmdname, arguments = command.arguments, } - self.request(ms.workspace_executeCommand, params, handler, context.bufnr) + self:request(ms.workspace_executeCommand, params, handler, context.bufnr) end --- Default handler for the 'textDocument/didOpen' LSP notification. @@ -1021,14 +978,14 @@ end --- @param bufnr integer Number of the buffer, or 0 for current function Client:_text_document_did_open_handler(bufnr) changetracking.init(self, bufnr) - if not self.supports_method(ms.textDocument_didOpen) then + if not self:supports_method(ms.textDocument_didOpen) then return end if not api.nvim_buf_is_loaded(bufnr) then return end - self.notify(ms.textDocument_didOpen, { + self:notify(ms.textDocument_didOpen, { textDocument = { version = lsp.util.buf_versions[bufnr], uri = vim.uri_from_bufnr(bufnr), @@ -1049,8 +1006,9 @@ function Client:_text_document_did_open_handler(bufnr) end --- Runs the on_attach function from the client's config if it was defined. +--- Useful for buffer-local setup. --- @param bufnr integer Buffer number -function Client:_on_attach(bufnr) +function Client:on_attach(bufnr) self:_text_document_did_open_handler(bufnr) lsp._set_defaults(self, bufnr) @@ -1085,10 +1043,18 @@ function Client:write_error(code, err) err_message(self._log_prefix, ': Error ', client_error, ': ', vim.inspect(err)) end ---- @private +--- Checks if a client supports a given method. +--- Always returns true for unknown off-spec methods. +--- +--- Note: Some language server capabilities can be file specific. --- @param method string ---- @param opts? {bufnr: integer?} -function Client:_supports_method(method, opts) +--- @param bufnr? integer +function Client:supports_method(method, bufnr) + -- Deprecated form + if type(bufnr) == 'table' then + --- @diagnostic disable-next-line:no-unknown + bufnr = bufnr.bufnr + end local required_capability = lsp._request_name_to_capability[method] -- if we don't know about the method, assume that the client supports it. if not required_capability then @@ -1101,12 +1067,12 @@ function Client:_supports_method(method, opts) local rmethod = lsp._resolve_to_request[method] if rmethod then if self:_supports_registration(rmethod) then - local reg = self:_get_registration(rmethod, opts and opts.bufnr) + local reg = self:_get_registration(rmethod, bufnr) return vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider') or false end else if self:_supports_registration(method) then - return self:_get_registration(method, opts and opts.bufnr) ~= nil + return self:_get_registration(method, bufnr) ~= nil end end return false @@ -1207,7 +1173,7 @@ function Client:_add_workspace_folder(dir) local wf = assert(get_workspace_folders(dir)) - self:_notify(ms.workspace_didChangeWorkspaceFolders, { + self:notify(ms.workspace_didChangeWorkspaceFolders, { event = { added = wf, removed = {} }, }) @@ -1222,7 +1188,7 @@ end function Client:_remove_workspace_folder(dir) local wf = assert(get_workspace_folders(dir)) - self:_notify(ms.workspace_didChangeWorkspaceFolders, { + self:notify(ms.workspace_didChangeWorkspaceFolders, { event = { added = {}, removed = wf }, }) diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index fdbdda695a..a11f84d6c6 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -231,7 +231,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback) countdown() else assert(client) - client.request(ms.codeLens_resolve, lens, function(_, result) + client:request(ms.codeLens_resolve, lens, function(_, result) if api.nvim_buf_is_loaded(bufnr) and result and result.command then lens.command = result.command -- Eager display to have some sort of incremental feedback diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index 92bc110a97..0f388a88fd 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -404,7 +404,7 @@ local function request(clients, bufnr, win, callback) for _, client in pairs(clients) do local client_id = client.id local params = lsp.util.make_position_params(win, client.offset_encoding) - local ok, request_id = client.request(ms.textDocument_completion, params, function(err, result) + local ok, request_id = client:request(ms.textDocument_completion, params, function(err, result) responses[client_id] = { err = err, result = result } remaining_requests = remaining_requests - 1 if remaining_requests == 0 then @@ -421,7 +421,7 @@ local function request(clients, bufnr, win, callback) for client_id, request_id in pairs(request_ids) do local client = lsp.get_client_by_id(client_id) if client then - client.cancel_request(request_id) + client:cancel_request(request_id) end end end @@ -582,7 +582,7 @@ local function on_complete_done() local changedtick = vim.b[bufnr].changedtick --- @param result lsp.CompletionItem - client.request(ms.completionItem_resolve, completion_item, function(err, result) + client:request(ms.completionItem_resolve, completion_item, function(err, result) if changedtick ~= vim.b[bufnr].changedtick then return end diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index f1ae9a8e9e..f1a58de621 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -122,12 +122,12 @@ end --- local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer --- --- local client = vim.lsp.get_client_by_id(hint.client_id) ---- local resp = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0) +--- local resp = client:request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0) --- local resolved_hint = assert(resp and resp.result, resp.err) --- vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding) --- --- location = resolved_hint.label[1].location ---- client.request('textDocument/hover', { +--- client:request('textDocument/hover', { --- textDocument = { uri = location.uri }, --- position = location.range.start, --- }) diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 215e5f41aa..01421fea29 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -273,7 +273,7 @@ function STHighlighter:send_request() if client and current_result.version ~= version and active_request.version ~= version then -- cancel stale in-flight request if active_request.request_id then - client.cancel_request(active_request.request_id) + client:cancel_request(active_request.request_id) active_request = {} state.active_request = active_request end @@ -288,7 +288,7 @@ function STHighlighter:send_request() method = method .. '/delta' params.previousResultId = current_result.result_id end - local success, request_id = client.request(method, params, function(err, response, ctx) + local success, request_id = client:request(method, params, function(err, response, ctx) -- look client up again using ctx.client_id instead of using a captured -- client object local c = vim.lsp.get_client_by_id(ctx.client_id) @@ -519,7 +519,7 @@ function STHighlighter:reset() if state.active_request.request_id then local client = vim.lsp.get_client_by_id(client_id) assert(client) - client.cancel_request(state.active_request.request_id) + client:cancel_request(state.active_request.request_id) state.active_request = {} end end @@ -547,7 +547,7 @@ function STHighlighter:mark_dirty(client_id) if state.active_request.request_id then local client = vim.lsp.get_client_by_id(client_id) assert(client) - client.cancel_request(state.active_request.request_id) + client:cancel_request(state.active_request.request_id) state.active_request = {} end end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 6eab0f3da4..9e352dbcfd 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2122,7 +2122,7 @@ function M._refresh(method, opts) local first = vim.fn.line('w0', window) local last = vim.fn.line('w$', window) for _, client in ipairs(clients) do - client.request(method, { + client:request(method, { textDocument = textDocument, range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding), }, nil, bufnr) @@ -2131,7 +2131,7 @@ function M._refresh(method, opts) end else for _, client in ipairs(clients) do - client.request(method, { + client:request(method, { textDocument = textDocument, range = make_line_range_params( bufnr, diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 9cd5b598da..1125021bdc 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -538,7 +538,8 @@ end --- @param generics? table --- @param classes? table --- @param exclude_types? true -local function render_fields_or_params(xs, generics, classes, exclude_types) +--- @param cfg nvim.gen_vimdoc.Config +local function render_fields_or_params(xs, generics, classes, exclude_types, cfg) local ret = {} --- @type string[] xs = vim.tbl_filter(should_render_field_or_param, xs) @@ -558,7 +559,9 @@ local function render_fields_or_params(xs, generics, classes, exclude_types) p.desc = pdesc inline_type(p, classes) - local nm, ty, desc = p.name, p.type, p.desc + local nm, ty = p.name, p.type + + local desc = p.classvar and string.format('See |%s|.', cfg.fn_helptag_fmt(p)) or p.desc local fnm = p.kind == 'operator' and fmt('op(%s)', nm) or fmt_field_name(nm) local pnm = fmt(' • %-' .. indent .. 's', fnm) @@ -591,7 +594,8 @@ end --- @param class nvim.luacats.parser.class --- @param classes table -local function render_class(class, classes) +--- @param cfg nvim.gen_vimdoc.Config +local function render_class(class, classes, cfg) if class.access or class.nodoc or class.inlinedoc then return end @@ -610,7 +614,7 @@ local function render_class(class, classes) table.insert(ret, md_to_vimdoc(class.desc, INDENTATION, INDENTATION, TEXT_WIDTH)) end - local fields_txt = render_fields_or_params(class.fields, nil, classes) + local fields_txt = render_fields_or_params(class.fields, nil, classes, nil, cfg) if not fields_txt:match('^%s*$') then table.insert(ret, '\n Fields: ~\n') table.insert(ret, fields_txt) @@ -621,11 +625,12 @@ local function render_class(class, classes) end --- @param classes table -local function render_classes(classes) +--- @param cfg nvim.gen_vimdoc.Config +local function render_classes(classes, cfg) local ret = {} --- @type string[] for _, class in vim.spairs(classes) do - ret[#ret + 1] = render_class(class, classes) + ret[#ret + 1] = render_class(class, classes, cfg) end return table.concat(ret) @@ -656,10 +661,6 @@ local function render_fun_header(fun, cfg) local proto = fun.table and nm or nm .. '(' .. table.concat(args, ', ') .. ')' - if not cfg.fn_helptag_fmt then - cfg.fn_helptag_fmt = fn_helptag_fmt_common - end - local tag = '*' .. cfg.fn_helptag_fmt(fun) .. '*' if #proto + #tag > TEXT_WIDTH - 8 then @@ -774,7 +775,8 @@ local function render_fun(fun, classes, cfg) end if fun.params and #fun.params > 0 then - local param_txt = render_fields_or_params(fun.params, fun.generics, classes, cfg.exclude_types) + local param_txt = + render_fields_or_params(fun.params, fun.generics, classes, cfg.exclude_types, cfg) if not param_txt:match('^%s*$') then table.insert(ret, '\n Parameters: ~\n') ret[#ret + 1] = param_txt @@ -957,6 +959,7 @@ end --- @param cfg nvim.gen_vimdoc.Config local function gen_target(cfg) + cfg.fn_helptag_fmt = cfg.fn_helptag_fmt or fn_helptag_fmt_common print('Target:', cfg.filename) local sections = {} --- @type table @@ -987,7 +990,7 @@ local function gen_target(cfg) print(' Processing file:', f) local funs_txt = render_funs(funs, all_classes, cfg) if next(classes) then - local classes_txt = render_classes(classes) + local classes_txt = render_classes(classes, cfg) if vim.trim(classes_txt) ~= '' then funs_txt = classes_txt .. '\n' .. funs_txt end diff --git a/scripts/luacats_parser.lua b/scripts/luacats_parser.lua index 9a763e4d7b..8a50077aa8 100644 --- a/scripts/luacats_parser.lua +++ b/scripts/luacats_parser.lua @@ -1,9 +1,6 @@ local luacats_grammar = require('scripts.luacats_grammar') ---- @class nvim.luacats.parser.param ---- @field name string ---- @field type string ---- @field desc string +--- @class nvim.luacats.parser.param : nvim.luacats.Param --- @class nvim.luacats.parser.return --- @field name string @@ -41,21 +38,14 @@ local luacats_grammar = require('scripts.luacats_grammar') --- @field notes? nvim.luacats.parser.note[] --- @field see? nvim.luacats.parser.note[] ---- @class nvim.luacats.parser.field ---- @field name string ---- @field type string ---- @field desc string ---- @field access? 'private'|'package'|'protected' +--- @class nvim.luacats.parser.field : nvim.luacats.Field +--- @field classvar? string --- @field nodoc? true ---- @class nvim.luacats.parser.class ---- @field kind 'class' ---- @field parent? string ---- @field name string ---- @field desc string +--- @class nvim.luacats.parser.class : nvim.luacats.Class +--- @field desc? string --- @field nodoc? true --- @field inlinedoc? true ---- @field access? 'private'|'package'|'protected' --- @field fields nvim.luacats.parser.field[] --- @field notes? string[] @@ -332,7 +322,10 @@ local function process_lua_line(line, state, classes, classvars, has_indent) end -- Add method as the field to the class - table.insert(classes[class].fields, fun2field(cur_obj)) + local cls = classes[class] + local field = fun2field(cur_obj) + field.classvar = cur_obj.classvar + table.insert(cls.fields, field) return end diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index a36cbac568..95fc22b96b 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -182,16 +182,17 @@ function M.test_rpc_server(config) ) end local client = setmetatable({}, { - __index = function(_, name) + __index = function(t, name) -- Workaround for not being able to yield() inside __index for Lua 5.1 :( -- Otherwise I would just return the value here. - return function(...) + return function(arg1, ...) + local ismethod = arg1 == t return exec_lua(function(...) - if type(_G.TEST_RPC_CLIENT[name]) == 'function' then - return _G.TEST_RPC_CLIENT[name](...) - else - return _G.TEST_RPC_CLIENT[name] + local client = _G.TEST_RPC_CLIENT + if type(client[name]) == 'function' then + return client[name](ismethod and client or arg1, ...) end + return client[name] end, ...) end end, diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5222216faf..0f68b7eae2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -232,7 +232,7 @@ describe('LSP', function() -- client is a dummy object which will queue up commands to be run -- once the server initializes. It can't accept lua callbacks or -- other types that may be unserializable for now. - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -254,8 +254,8 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client) - client.notify('test') - client.stop() + client:notify('test') + client:stop() end, on_exit = function(code, signal) eq(101, code, 'exit code') -- See fake-lsp-server.lua @@ -275,7 +275,7 @@ describe('LSP', function() test_rpc_server({ test_name = 'basic_init_did_change_configuration', on_init = function(client, _) - client.stop() + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -333,9 +333,9 @@ describe('LSP', function() test_name = 'basic_init', on_init = function(client) eq(0, client.server_capabilities().textDocumentSync.change) - client.request('shutdown') - client.notify('exit') - client.stop() + client:request('shutdown') + client:notify('exit') + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -377,7 +377,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -395,7 +395,7 @@ describe('LSP', function() return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.stop() + client:stop() end end, } @@ -430,7 +430,7 @@ describe('LSP', function() return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.notify('finish') + client:notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then @@ -439,7 +439,7 @@ describe('LSP', function() return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) eq('basic_init', api.nvim_get_var('lsp_detached')) - client.stop() + client:stop() end end, } @@ -472,7 +472,7 @@ describe('LSP', function() return keymap.callback == vim.lsp.buf.hover end) ) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -524,7 +524,7 @@ describe('LSP', function() eq('v:lua.vim.lsp.tagfunc', get_buf_option('tagfunc', BUFFER_1)) eq('v:lua.vim.lsp.omnifunc', get_buf_option('omnifunc', BUFFER_2)) eq('v:lua.vim.lsp.formatexpr()', get_buf_option('formatexpr', BUFFER_2)) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -554,7 +554,7 @@ describe('LSP', function() eq('tfu', get_buf_option('tagfunc')) eq('ofu', get_buf_option('omnifunc')) eq('fex', get_buf_option('formatexpr')) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -711,10 +711,10 @@ describe('LSP', function() ctx.method, result ) - client.notify('workspace/configuration', server_result) + client:notify('workspace/configuration', server_result) end if ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -756,7 +756,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_capabilities', on_init = function(client) - client.stop() + client:stop() local full_kind = exec_lua(function() return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full end) @@ -798,7 +798,7 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } @@ -898,7 +898,7 @@ describe('LSP', function() end) end) else - client.stop() + client:stop() end end, }) @@ -929,20 +929,20 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } end) - it('client.supports_methods() should validate capabilities', function() + it('client:supports_methods() should validate capabilities', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, } test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_init = function(client) - client.stop() + client:stop() local expected_sync_capabilities = { change = 1, openClose = true, @@ -958,11 +958,11 @@ describe('LSP', function() eq(true, client.server_capabilities().codeLensProvider.resolveProvider) -- known methods for resolved capabilities - eq(true, client.supports_method('textDocument/hover')) - eq(false, client.supports_method('textDocument/definition')) + eq(true, client:supports_method('textDocument/hover')) + eq(false, client:supports_method('textDocument/definition')) -- unknown methods are assumed to be supported. - eq(true, client.supports_method('unknown-method')) + eq(true, client:supports_method('unknown-method')) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -989,7 +989,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1018,7 +1018,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1042,7 +1042,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_request_cancelled', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1053,7 +1053,7 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1072,7 +1072,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_content_modified', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1084,10 +1084,10 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1103,13 +1103,13 @@ describe('LSP', function() test_name = 'check_pending_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1123,10 +1123,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1141,14 +1141,14 @@ describe('LSP', function() test_name = 'check_cancel_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') - client.cancel_request(2) + client:request('slow_request') + client:cancel_request(2) local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1162,7 +1162,7 @@ describe('LSP', function() end) eq(nil, request) if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1178,19 +1178,19 @@ describe('LSP', function() test_name = 'check_tracked_requests_cleared', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.cancel_request(2) + client:cancel_request(2) request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1204,10 +1204,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1225,11 +1225,11 @@ describe('LSP', function() command('let g:requests = 0') command('autocmd LspRequest * let g:requests+=1') client = _client - client.request('slow_request') + client:request('slow_request') eq(1, eval('g:requests')) - client.cancel_request(2) + client:cancel_request(2) eq(2, eval('g:requests')) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1240,10 +1240,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1277,7 +1277,7 @@ describe('LSP', function() end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1286,7 +1286,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1333,11 +1333,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1378,11 +1378,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1428,11 +1428,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1479,11 +1479,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1529,7 +1529,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - eq(true, client.supports_method('textDocument/inlayHint')) + eq(true, client:supports_method('textDocument/inlayHint')) exec_lua(function() assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) end) @@ -1545,11 +1545,11 @@ describe('LSP', function() end) end if ctx.method == 'textDocument/inlayHint' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1598,11 +1598,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1652,11 +1652,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1699,11 +1699,11 @@ describe('LSP', function() on_handler = function(err, result, ctx) if ctx.method == 'start' then n.command('normal! 1Go') - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1752,11 +1752,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1806,11 +1806,11 @@ describe('LSP', function() }) vim.api.nvim_command(_G.BUFFER .. 'bwipeout') end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1830,7 +1830,7 @@ describe('LSP', function() on_setup = function() end, on_init = function(_client) client = _client - client.stop(true) + client:stop(true) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1882,7 +1882,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -2348,7 +2348,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client, _) - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -4284,7 +4284,7 @@ describe('LSP', function() end) ) end - client.stop() + client:stop() end end, } @@ -4331,7 +4331,7 @@ describe('LSP', function() return type(vim.lsp.commands['dummy2']) end) ) - client.stop() + client:stop() end end, } @@ -4372,7 +4372,7 @@ describe('LSP', function() vim.lsp.buf.code_action() end) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, }) @@ -4447,7 +4447,7 @@ describe('LSP', function() return type(vim.lsp.commands['executed_type_annotate']) end) ) - client.stop() + client:stop() end end, } @@ -4564,7 +4564,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4653,7 +4653,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4762,7 +4762,7 @@ describe('LSP', function() return notify_msg end) eq('[LSP] Format request failed, no matching language servers.', notify_msg) - client.stop() + client:stop() end, } end) @@ -4795,7 +4795,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4836,7 +4836,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4883,7 +4883,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4930,7 +4930,7 @@ describe('LSP', function() end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -5477,7 +5477,7 @@ describe('LSP', function() result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, { bufnr = bufnr }), + supported = client:supports_method(method, { bufnr = bufnr }), } end -- cgit From 0e2f92ed79e3c976ab5a41d40380e761e7f69d3a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 20 Nov 2024 22:51:58 +0800 Subject: build(clint): make NOLINT work with header checks (#31281) Problem: NOLINT doesn't work with header checks. Solution: Move these checks after ProcessLine() calls. --- src/clint.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/clint.py b/src/clint.py index b57bbe354b..8044607098 100755 --- a/src/clint.py +++ b/src/clint.py @@ -897,7 +897,7 @@ def CheckIncludes(filename, lines, error): if (not name.endswith('.h.generated.h') and not name.endswith('/defs.h') and not name.endswith('_defs.h') and - not name.endswith('h.inline.generated.h') and + not name.endswith('.h.inline.generated.h') and not name.endswith('_defs.generated.h') and not name.endswith('_enum.generated.h')): error(filename, i, 'build/include_defs', 5, @@ -2206,12 +2206,6 @@ def ProcessFileData(filename, file_extension, lines, error, error = RecordedError - if file_extension == 'h': - CheckForHeaderGuard(filename, lines, error) - CheckIncludes(filename, lines, error) - if filename.endswith('/defs.h') or filename.endswith('_defs.h'): - CheckNonSymbols(filename, lines, error) - RemoveMultiLineComments(filename, lines, error) clean_lines = CleansedLines(lines, init_lines) for line in range(clean_lines.NumLines()): @@ -2219,6 +2213,12 @@ def ProcessFileData(filename, file_extension, lines, error, nesting_state, error, extra_check_functions) + if file_extension == 'h': + CheckForHeaderGuard(filename, lines, error) + CheckIncludes(filename, lines, error) + if filename.endswith('/defs.h') or filename.endswith('_defs.h'): + CheckNonSymbols(filename, lines, error) + # We check here rather than inside ProcessLine so that we see raw # lines rather than "cleaned" lines. CheckForBadCharacters(filename, lines, error) -- cgit From 1b6442034f6a821d357fe59cd75fdae47a7f7cff Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 20 Nov 2024 21:11:20 +0100 Subject: fix(messages): more ext_messages kinds #31279 Add kinds for various commands that output a list, the 'wildmode' list, and for number prompts. --- runtime/doc/ui.txt | 3 + src/nvim/buffer.c | 1 + src/nvim/cmdexpand.c | 1 + src/nvim/eval/funcs.c | 1 + src/nvim/eval/vars.c | 1 + src/nvim/ex_docmd.c | 2 - src/nvim/highlight_group.c | 2 + src/nvim/input.c | 1 + src/nvim/mapping.c | 3 + src/nvim/message.c | 4 + src/nvim/option.c | 2 + src/nvim/spellsuggest.c | 1 + src/nvim/ui.c | 2 +- test/functional/lua/ui_event_spec.lua | 20 +++++ test/functional/ui/messages_spec.lua | 161 ++++++++++++++++++---------------- 15 files changed, 125 insertions(+), 80 deletions(-) diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 9cbb84a19d..55eba484bc 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -795,13 +795,16 @@ must handle. "echo" |:echo| message "echomsg" |:echomsg| message "echoerr" |:echoerr| message + "list_cmd" List output for various commands (|:ls|, |:set|, …) "lua_error" Error in |:lua| code "lua_print" |print()| from |:lua| code "rpc_error" Error response from |rpcrequest()| + "number_prompt" Number input prompt (|inputlist()|, |z=|, …) "return_prompt" |press-enter| prompt after a multiple messages "quickfix" Quickfix navigation message "search_cmd" Entered search command "search_count" Search count message ("S" flag of 'shortmess') + "wildlist" 'wildmode' "list" message "wmsg" Warning ("search hit BOTTOM", |W10|, …) New kinds may be added in the future; clients should treat unknown kinds as the empty kind. diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index abcce0dfe8..21079a1a3c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2819,6 +2819,7 @@ void buflist_list(exarg_T *eap) garray_T buflist; buf_T **buflist_data = NULL; + msg_ext_set_kind("list_cmd"); if (vim_strchr(eap->arg, 't')) { ga_init(&buflist, sizeof(buf_T *), 50); for (buf = firstbuf; buf != NULL; buf = buf->b_next) { diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 700d554821..9b1193b4e0 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -1084,6 +1084,7 @@ int showmatches(expand_T *xp, bool wildmenu) ui_flush(); cmdline_row = msg_row; msg_didany = false; // lines_left will be set again + msg_ext_set_kind("wildlist"); msg_start(); // prepare for paging } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 717280642d..f700e732a9 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3540,6 +3540,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } + msg_ext_set_kind("list_cmd"); msg_start(); msg_row = Rows - 1; // for when 'cmdheight' > 1 lines_left = Rows; // avoid more prompt diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 35ad00f373..3ecb446cd6 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1403,6 +1403,7 @@ static void list_one_var(dictitem_T *v, const char *prefix, int *first) static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len, const VarType type, const char *string, int *first) { + msg_ext_set_kind("list_cmd"); // don't use msg() to avoid overwriting "v:statusmsg" msg_start(); msg_puts(prefix); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index f5ecedf827..9968f32de1 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -980,8 +980,6 @@ void handle_did_throw(void) force_abort = true; } - msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 - if (messages != NULL) { do { msglist_T *next = messages->next; diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index b3c4aca1af..8f026e9600 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -1001,6 +1001,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) { // If no argument, list current highlighting. if (!init && ends_excmd((uint8_t)(*line))) { + msg_ext_set_kind("list_cmd"); for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { // TODO(brammool): only call when the group has attributes set highlight_list_one(i); @@ -1038,6 +1039,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (id == 0) { semsg(_(e_highlight_group_name_not_found_str), line); } else { + msg_ext_set_kind("list_cmd"); highlight_list_one(id); } return; diff --git a/src/nvim/input.c b/src/nvim/input.c index 3d3240c59f..0c1a8af45f 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -223,6 +223,7 @@ int get_number(int colon, bool *mouse_used) /// the line number. int prompt_for_number(bool *mouse_used) { + msg_ext_set_kind("number_prompt"); // When using ":silent" assume that was entered. if (mouse_used != NULL) { msg_puts(_("Type number and or click with the mouse " diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 1a6b2c3581..1896f042f2 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -581,6 +581,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, const bool has_lhs = (args->lhs[0] != NUL); const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop; const bool do_print = !has_lhs || (maptype != MAPTYPE_UNMAP && !has_rhs); + if (do_print) { + msg_ext_set_kind("list_cmd"); + } // check for :unmap without argument if (maptype == MAPTYPE_UNMAP && !has_lhs) { diff --git a/src/nvim/message.c b/src/nvim/message.c index 47f33c8967..c927a2546c 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -750,6 +750,10 @@ bool emsg_multiline(const char *s, bool multiline) msg_scroll = true; msg_source(hl_id); + if (msg_ext_kind == NULL) { + msg_ext_set_kind("emsg"); + } + // Display the error message itself. msg_nowait = false; // Wait for this msg. return msg_hl_keep(s, hl_id, false, multiline); diff --git a/src/nvim/option.c b/src/nvim/option.c index 8e94c342f7..1ce737bc59 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1276,6 +1276,7 @@ static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char * gotocmdline(true); // cursor at status line *did_show = true; // remember that we did a line } + msg_ext_set_kind("list_cmd"); showoneopt(&options[opt_idx], opt_flags); if (p_verbose > 0) { @@ -4048,6 +4049,7 @@ static void showoptions(bool all, int opt_flags) vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT); + msg_ext_set_kind("list_cmd"); // Highlight title if (opt_flags & OPT_GLOBAL) { msg_puts_title(_("\n--- Global option values ---")); diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index b37f01e769..0ddf4ffa38 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -516,6 +516,7 @@ void spell_suggest(int count) spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, true, need_cap, true); + msg_ext_set_kind("list_cmd"); if (GA_EMPTY(&sug.su_ga)) { msg(_("Sorry, no suggestions"), 0); } else if (count > 0) { diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 7c81110ae9..eba821a53d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -721,7 +721,7 @@ void ui_call_event(char *name, bool fast, Array args) // Prompt messages should be shown immediately so must be safe if (strcmp(name, "msg_show") == 0) { char *kind = args.items[0].data.string.data; - fast = !kind || (strncmp(kind, "confirm", 7) != 0 && strcmp(kind, "return_prompt") != 0); + fast = !kind || ((strncmp(kind, "confirm", 7) != 0 && strstr(kind, "_prompt") == NULL)); } map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, { diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index c8616e3e11..f1cf657d78 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -285,6 +285,26 @@ describe('vim.ui_attach', function() }, }, }) + feed(':call inputlist(["Select:", "One", "Two"])') + screen:expect({ + grid = [[ + E122: {10:Function} Foo already exists, add !| + to replace it | + Type number and or click with th| + e mouse (q or empty cancels): | + {1:^~ }| + ]], + messages = { + { + content = { { 'Select:\nOne\nTwo\n' } }, + kind = 'list_cmd', + }, + { + content = { { 'Type number and or click with the mouse (q or empty cancels): ' } }, + kind = 'number_prompt', + }, + }, + }) end) end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index ca936d482e..9a6dfd8ed1 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -64,7 +64,7 @@ describe('ui/ext_messages', function() } end) - it('msg_show kind=confirm,confirm_sub,emsg,wmsg,quickfix', function() + it('msg_show kinds', function() feed('iline 1\nline 2') -- kind=confirm @@ -172,7 +172,7 @@ describe('ui/ext_messages', function() }, { content = { { 'E605: Exception not caught: foo', 9, 7 } }, - kind = '', + kind = 'emsg', }, { content = { { 'Press ENTER or type command to continue', 6, 19 } }, @@ -198,6 +198,48 @@ describe('ui/ext_messages', function() }, }, } + + -- search_cmd + feed('?line') + screen:expect({ + grid = [[ + ^line 1 | + line 2 | + {1:~ }|*3 + ]], + messages = { { + content = { { '?line ' } }, + kind = 'search_cmd', + } }, + }) + + -- highlight + feed(':hi ErrorMsg') + screen:expect({ + grid = [[ + ^line 1 | + line 2 | + {1:~ }|*3 + ]], + messages = { + { + content = { + { '\nErrorMsg ' }, + { 'xxx', 9, 7 }, + { ' ' }, + { 'ctermfg=', 18, 6 }, + { '15 ' }, + { 'ctermbg=', 18, 6 }, + { '1 ' }, + { 'guifg=', 18, 6 }, + { 'White ' }, + { 'guibg=', 18, 6 }, + { 'Red' }, + }, + kind = 'list_cmd', + }, + }, + }) end) it(':echoerr', function() @@ -408,34 +450,6 @@ describe('ui/ext_messages', function() } end) - it(':hi Group output', function() - feed(':hi ErrorMsg') - screen:expect { - grid = [[ - ^ | - {1:~ }|*4 - ]], - messages = { - { - content = { - { '\nErrorMsg ' }, - { 'xxx', 9, 7 }, - { ' ' }, - { 'ctermfg=', 18, 6 }, - { '15 ' }, - { 'ctermbg=', 18, 6 }, - { '1 ' }, - { 'guifg=', 18, 6 }, - { 'White ' }, - { 'guibg=', 18, 6 }, - { 'Red' }, - }, - kind = '', - }, - }, - } - end) - it("doesn't crash with column adjustment #10069", function() feed(':let [x,y] = [1,2]') feed(':let x y') @@ -445,8 +459,8 @@ describe('ui/ext_messages', function() {1:~ }|*4 ]], messages = { - { content = { { 'x #1' } }, kind = '' }, - { content = { { 'y #2' } }, kind = '' }, + { content = { { 'x #1' } }, kind = 'list_cmd' }, + { content = { { 'y #2' } }, kind = 'list_cmd' }, { content = { { 'Press ENTER or type command to continue', 6, 19 } }, kind = 'return_prompt', @@ -947,7 +961,7 @@ stack traceback: { '*', 18, 1 }, { ' k' }, }, - kind = '', + kind = 'list_cmd', }, }, } @@ -964,10 +978,12 @@ stack traceback: ^ | {1:~ }|*6 ]], - messages = { { - content = { { 'wildmenu wildmode' } }, - kind = '', - } }, + messages = { + { + content = { { 'wildmenu wildmode' } }, + kind = 'wildlist', + }, + }, cmdline = { { firstc = ':', @@ -983,43 +999,46 @@ stack traceback: feed('ihelllo') feed('z=') - screen:expect { + screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| - ]], + {100:helllo} | + {1:~ }|*3 + {1:^~ }| + ]], messages = { { - content = { - { - 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and or click with the mouse (q or empty cancels): ', - }, - }, - kind = '', + content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, + kind = 'list_cmd', + }, + { + content = { { 'Type number and or click with the mouse (q or empty cancels): ' } }, + kind = 'number_prompt', }, }, - } + }) feed('1') - screen:expect { + screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| - ]], + {100:helllo} | + {1:~ }|*3 + {1:^~ }| + ]], messages = { { - content = { - { - 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and or click with the mouse (q or empty cancels): ', - }, - }, + content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, + kind = 'list_cmd', + }, + { + content = { { 'Type number and or click with the mouse (q or empty cancels): ' } }, + kind = 'number_prompt', + }, + { + content = { { '1' } }, kind = '', }, - { content = { { '1' } }, kind = '' }, }, - } + }) feed('') screen:expect { @@ -1056,7 +1075,7 @@ stack traceback: {1:~ }|*4 ]], messages = { - { content = { { '\n 1 %a "[No Name]" line 1' } }, kind = '' }, + { content = { { '\n 1 %a "[No Name]" line 1' } }, kind = 'list_cmd' }, }, } @@ -1140,18 +1159,6 @@ stack traceback: exec_lua([[vim.print({ foo = "bar" })]]) screen:expect_unchanged() end) - - it('emits single message for normal search)', function() - feed('axx?x') - screen:expect({ - messages = { - { - content = { { '?x ' } }, - kind = 'search_cmd', - }, - }, - }) - end) end) describe('ui/builtin messages', function() @@ -1887,7 +1894,7 @@ describe('ui/ext_messages', function() {3:[No Name] }| ]], messages = { - { content = { { ' cmdheight=0' } }, kind = '' }, + { content = { { ' cmdheight=0' } }, kind = 'list_cmd' }, }, }) @@ -1903,7 +1910,7 @@ describe('ui/ext_messages', function() {3:[No Name] }| ]], messages = { - { content = { { ' laststatus=3' } }, kind = '' }, + { content = { { ' laststatus=3' } }, kind = 'list_cmd' }, }, }) @@ -1923,7 +1930,7 @@ describe('ui/ext_messages', function() {3:[No Name] }| ]], messages = { - { content = { { ' cmdheight=0' } }, kind = '' }, + { content = { { ' cmdheight=0' } }, kind = 'list_cmd' }, }, }) end) -- cgit From 629483e24eed3f2c07e55e0540c553361e0345a2 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Thu, 21 Nov 2024 04:19:07 +0800 Subject: feat(lsp): require `offset_encoding` param #31249 Problem: Since [version 3.17](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocuments), LSP supports specifying the position encoding (aka offset encoding) supported by the client through `positionEncoding`. Since #31209, Nvim fully supports `utf-8`, `utf-16`, and `utf-32` encodings. Previously, nvim assumed all clients for a buffer had the same `offset_encoding`, so: * Nvim provides `vim.lsp._get_offset_encoding()` to get `offset_encoding`, but this function is incorrect because `offset_encoding` is per-client, not per-buffer. * Based on the strategy of `vim.lsp._get_offset_encoding()`, `vim.lsp.util.make_position_params()`, `vim.lsp.util.make_range_params()`, and `vim.lsp.util.make_given_range_params()` do not require the caller to pass `offset_encoding`, which is invalid. * https://github.com/neovim/neovim/issues/25272 Solution: * Mark `vim.lsp._get_offset_encoding()` as `@deprecated`. * Change the type annotations of `vim.lsp.util.make_position_params()`, `vim.lsp.util.make_range_params()`, `vim.lsp.util.make_given_range_params()` to require the `offset_encoding` param. --- runtime/doc/lsp.txt | 17 +++++------------ runtime/doc/news.txt | 3 +++ runtime/lua/vim/lsp/util.lua | 42 +++++++++++++++++++++++++++++------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index ae26abd34d..3cb3e590f4 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2025,12 +2025,10 @@ make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding}) selection. • {bufnr} (`integer?`) buffer handle or 0 for current, defaults to current - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) defaults to - `offset_encoding` of first client of `bufnr` + • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) Return: ~ - (`table`) { textDocument = { uri = `current_file_uri` }, range = { - start = `start_position`, end = `end_position` } } + (`{ textDocument: { uri: lsp.DocumentUri }, range: lsp.Range }`) *vim.lsp.util.make_position_params()* make_position_params({window}, {offset_encoding}) @@ -2040,9 +2038,7 @@ make_position_params({window}, {offset_encoding}) Parameters: ~ • {window} (`integer?`) window handle or 0 for current, defaults to current - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) defaults to - `offset_encoding` of first client of buffer of - `window` + • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) Return: ~ (`lsp.TextDocumentPositionParams`) @@ -2060,13 +2056,10 @@ make_range_params({window}, {offset_encoding}) Parameters: ~ • {window} (`integer?`) window handle or 0 for current, defaults to current - • {offset_encoding} (`"utf-8"|"utf-16"|"utf-32"?`) defaults to - `offset_encoding` of first client of buffer of - `window` + • {offset_encoding} (`"utf-8"|"utf-16"|"utf-32"`) Return: ~ - (`table`) { textDocument = { uri = `current_file_uri` }, range = { - start = `current_position`, end = `current_position` } } + (`{ textDocument: { uri: lsp.DocumentUri }, range: lsp.Range }`) *vim.lsp.util.make_text_document_params()* make_text_document_params({bufnr}) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 32deb85278..f3d82786bd 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -95,6 +95,9 @@ LSP Instead use: >lua vim.diagnostic.config(config, vim.lsp.diagnostic.get_namespace(client_id)) < +• |vim.lsp.util.make_position_params()|, |vim.lsp.util.make_range_params()| + and |vim.lsp.util.make_given_range_params()| now require the `offset_encoding` + parameter. LUA diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 9e352dbcfd..cfa8a194d9 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1848,12 +1848,11 @@ function M.try_trim_markdown_code_blocks(lines) end ---@param window integer?: window handle or 0 for current, defaults to current ----@param offset_encoding? 'utf-8'|'utf-16'|'utf-32'? defaults to `offset_encoding` of first client of buffer of `window` +---@param offset_encoding 'utf-8'|'utf-16'|'utf-32' local function make_position_param(window, offset_encoding) window = window or 0 local buf = api.nvim_win_get_buf(window) local row, col = unpack(api.nvim_win_get_cursor(window)) - offset_encoding = offset_encoding or M._get_offset_encoding(buf) row = row - 1 local line = api.nvim_buf_get_lines(buf, row, row + 1, true)[1] if not line then @@ -1868,13 +1867,19 @@ end --- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. --- ---@param window integer?: window handle or 0 for current, defaults to current ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? defaults to `offset_encoding` of first client of buffer of `window` +---@param offset_encoding 'utf-8'|'utf-16'|'utf-32' ---@return lsp.TextDocumentPositionParams ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams function M.make_position_params(window, offset_encoding) window = window or 0 local buf = api.nvim_win_get_buf(window) - offset_encoding = offset_encoding or M._get_offset_encoding(buf) + if offset_encoding == nil then + vim.notify_once( + 'warning: offset_encoding is required, using the offset_encoding from the first client', + vim.log.levels.WARN + ) + offset_encoding = M._get_offset_encoding(buf) + end return { textDocument = M.make_text_document_params(buf), position = make_position_param(window, offset_encoding), @@ -1882,6 +1887,7 @@ function M.make_position_params(window, offset_encoding) end --- Utility function for getting the encoding of the first LSP client on the given buffer. +---@deprecated ---@param bufnr integer buffer handle or 0 for current, defaults to current ---@return string encoding first client if there is one, nil otherwise function M._get_offset_encoding(bufnr) @@ -1904,7 +1910,7 @@ function M._get_offset_encoding(bufnr) offset_encoding = this_offset_encoding elseif offset_encoding ~= this_offset_encoding then vim.notify_once( - 'warning: multiple different client offset_encodings detected for buffer, this is not supported yet', + 'warning: multiple different client offset_encodings detected for buffer, vim.lsp.util._get_offset_encoding() uses the offset_encoding from the first client', vim.log.levels.WARN ) end @@ -1919,12 +1925,17 @@ end --- `textDocument/rangeFormatting`. --- ---@param window integer? window handle or 0 for current, defaults to current ----@param offset_encoding "utf-8"|"utf-16"|"utf-32"? defaults to `offset_encoding` of first client of buffer of `window` ----@return table { textDocument = { uri = `current_file_uri` }, range = { start = ----`current_position`, end = `current_position` } } +---@param offset_encoding "utf-8"|"utf-16"|"utf-32" +---@return { textDocument: { uri: lsp.DocumentUri }, range: lsp.Range } function M.make_range_params(window, offset_encoding) local buf = api.nvim_win_get_buf(window or 0) - offset_encoding = offset_encoding or M._get_offset_encoding(buf) + if offset_encoding == nil then + vim.notify_once( + 'warning: offset_encoding is required, using the offset_encoding from the first client', + vim.log.levels.WARN + ) + offset_encoding = M._get_offset_encoding(buf) + end local position = make_position_param(window, offset_encoding) return { textDocument = M.make_text_document_params(buf), @@ -1940,15 +1951,20 @@ end ---@param end_pos [integer,integer]? {row,col} mark-indexed position. --- Defaults to the end of the last visual selection. ---@param bufnr integer? buffer handle or 0 for current, defaults to current ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? defaults to `offset_encoding` of first client of `bufnr` ----@return table { textDocument = { uri = `current_file_uri` }, range = { start = ----`start_position`, end = `end_position` } } +---@param offset_encoding 'utf-8'|'utf-16'|'utf-32' +---@return { textDocument: { uri: lsp.DocumentUri }, range: lsp.Range } function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) validate('start_pos', start_pos, 'table', true) validate('end_pos', end_pos, 'table', true) validate('offset_encoding', offset_encoding, 'string', true) bufnr = bufnr or api.nvim_get_current_buf() - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) + if offset_encoding == nil then + vim.notify_once( + 'warning: offset_encoding is required, using the offset_encoding from the first client', + vim.log.levels.WARN + ) + offset_encoding = M._get_offset_encoding(bufnr) + end --- @type [integer, integer] local A = { unpack(start_pos or api.nvim_buf_get_mark(bufnr, '<')) } --- @type [integer, integer] -- cgit From cedf155fb5c4d687747e59f5d3fdad76a40aa069 Mon Sep 17 00:00:00 2001 From: zshuzh <40901142+zshuzh@users.noreply.github.com> Date: Wed, 20 Nov 2024 22:01:59 +0000 Subject: refactor(options): impl default 'titlestring' by format flags #30843 Problem: Unnecessary C impl of default 'titlestring'. Solutin: Define it using format flags. --- runtime/doc/news.txt | 5 +++ runtime/doc/options.txt | 4 ++ runtime/doc/vim_diff.txt | 5 +++ runtime/lua/vim/_meta/options.lua | 7 +++ src/nvim/buffer.c | 93 ++------------------------------------- src/nvim/options.lua | 4 ++ 6 files changed, 29 insertions(+), 89 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f3d82786bd..ed342d9229 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -324,6 +324,11 @@ These existing features changed their behavior. current window, and it no longer throws |E444| when there is only one window on the screen. Global variable `vim.g.pager` is removed. +• Default 'titlestring' is now implemented with 'statusline' "%" format items. + This means the default, empty value is essentially an alias to: + `%t%(\ %M%)%(\ \(%{expand(\"%:~:h\")}\)%)%a\ -\ Nvim`. This is only an + implementation simplification, not a behavior change. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c972a05c4d..64ad2d2956 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6590,6 +6590,10 @@ A jump table for the options with a short description can be found at |Q_op|. expanded according to the rules used for 'statusline'. If it contains an invalid '%' format, the value is used as-is and no error or warning will be given when the value is set. + + The default behaviour is equivalent to: >vim + set titlestring=%t%(\ %M%)%(\ \(%{expand(\"%:~:h\")}\)%)%a\ -\ Nvim +< This option cannot be set in a modeline when 'modelineexpr' is off. Example: >vim diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 8fa94a2601..50075c3bde 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -616,6 +616,11 @@ Autocommands: - |TermResponse| is fired for any OSC sequence received from the terminal, instead of the Primary Device Attributes response. |v:termresponse| +Options: +- 'titlestring' uses printf-style '%' items (see: 'statusline') to implement + the default behaviour. The implementation is equivalent to setting + 'titlestring' to `%t%(\ %M%)%(\ \(%{expand(\"%:~:h\")}\)%)%a\ -\ Nvim`. + ============================================================================== Missing features *nvim-missing* diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index cb783720ac..e485009ca2 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -7122,6 +7122,13 @@ vim.go.titleold = vim.o.titleold --- expanded according to the rules used for 'statusline'. If it contains --- an invalid '%' format, the value is used as-is and no error or warning --- will be given when the value is set. +--- +--- The default behaviour is equivalent to: +--- +--- ```vim +--- set titlestring=%t%(\ %M%)%(\ \(%{expand(\"%:~:h\")}\)%)%a\ -\ Nvim +--- ``` +--- --- This option cannot be set in a modeline when 'modelineexpr' is off. --- --- Example: diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 21079a1a3c..1908516e85 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3342,96 +3342,11 @@ void maketitle(void) title_str = p_titlestring; } } else { - // Format: "fname + (path) (1 of 2) - VIM". - -#define SPACE_FOR_FNAME (sizeof(buf) - 100) -#define SPACE_FOR_DIR (sizeof(buf) - 20) -#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - Nvim". - char *buf_p = buf; - if (curbuf->b_fname == NULL) { - const size_t size = xstrlcpy(buf_p, _("[No Name]"), - SPACE_FOR_FNAME + 1); - buf_p += MIN(size, SPACE_FOR_FNAME); - } else { - buf_p += transstr_buf(path_tail(curbuf->b_fname), -1, buf_p, SPACE_FOR_FNAME + 1, true); - } - - switch (bufIsChanged(curbuf) - | (curbuf->b_p_ro << 1) - | (!MODIFIABLE(curbuf) << 2)) { - case 0: - break; - case 1: - buf_p = strappend(buf_p, " +"); break; - case 2: - buf_p = strappend(buf_p, " ="); break; - case 3: - buf_p = strappend(buf_p, " =+"); break; - case 4: - case 6: - buf_p = strappend(buf_p, " -"); break; - case 5: - case 7: - buf_p = strappend(buf_p, " -+"); break; - default: - abort(); - } - - if (curbuf->b_fname != NULL) { - // Get path of file, replace home dir with ~. - *buf_p++ = ' '; - *buf_p++ = '('; - home_replace(curbuf, curbuf->b_ffname, buf_p, - (SPACE_FOR_DIR - (size_t)(buf_p - buf)), true); -#ifdef BACKSLASH_IN_FILENAME - // Avoid "c:/name" to be reduced to "c". - if (isalpha((uint8_t)(*buf_p)) && *(buf_p + 1) == ':') { - buf_p += 2; - } -#endif - // Remove the file name. - char *p = path_tail_with_sep(buf_p); - if (p == buf_p) { - // Must be a help buffer. - xstrlcpy(buf_p, _("help"), SPACE_FOR_DIR - (size_t)(buf_p - buf)); - } else { - *p = NUL; - } - - // Translate unprintable chars and concatenate. Keep some - // room for the server name. When there is no room (very long - // file name) use (...). - if ((size_t)(buf_p - buf) < SPACE_FOR_DIR) { - char *const tbuf = transstr(buf_p, true); - const size_t free_space = SPACE_FOR_DIR - (size_t)(buf_p - buf) + 1; - const size_t dir_len = xstrlcpy(buf_p, tbuf, free_space); - buf_p += MIN(dir_len, free_space - 1); - xfree(tbuf); - } else { - const size_t free_space = SPACE_FOR_ARGNR - (size_t)(buf_p - buf) + 1; - const size_t dots_len = xstrlcpy(buf_p, "...", free_space); - buf_p += MIN(dots_len, free_space - 1); - } - *buf_p++ = ')'; - *buf_p = NUL; - } else { - *buf_p = NUL; - } - - append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf))); - - xstrlcat(buf_p, " - Nvim", (sizeof(buf) - (size_t)(buf_p - buf))); - - if (maxlen > 0) { - // Make it shorter by removing a bit in the middle. - if (vim_strsize(buf) > maxlen) { - trunc_string(buf, buf, maxlen, sizeof(buf)); - } - } + // Format: "fname + (path) (1 of 2) - Nvim". + char *default_titlestring = "%t%( %M%)%( (%{expand(\"%:~:h\")})%)%a - Nvim"; + build_stl_str_hl(curwin, buf, sizeof(buf), default_titlestring, + kOptTitlestring, 0, 0, maxlen, NULL, NULL, NULL, NULL); title_str = buf; -#undef SPACE_FOR_FNAME -#undef SPACE_FOR_DIR -#undef SPACE_FOR_ARGNR } } bool mustset = value_change(title_str, &lasttitle); diff --git a/src/nvim/options.lua b/src/nvim/options.lua index baacb2b858..d61cba892b 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -9157,6 +9157,10 @@ return { expanded according to the rules used for 'statusline'. If it contains an invalid '%' format, the value is used as-is and no error or warning will be given when the value is set. + + The default behaviour is equivalent to: >vim + set titlestring=%t%(\ %M%)%(\ \(%{expand(\"%:~:h\")}\)%)%a\ -\ Nvim + < This option cannot be set in a modeline when 'modelineexpr' is off. Example: >vim -- cgit From 07db909eb5ae2a559771068be64439eba394cd61 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Wed, 20 Nov 2024 23:50:30 +0100 Subject: docs: misc (#31138) Co-authored-by: zeertzjq --- runtime/doc/deprecated.txt | 2 +- runtime/doc/news.txt | 2 +- runtime/doc/pi_netrw.txt | 2 +- runtime/doc/pi_tar.txt | 2 +- runtime/doc/vim_diff.txt | 2 +- runtime/ftplugin/checkhealth.vim | 1 - runtime/ftplugin/query.lua | 1 - runtime/indent/query.lua | 1 - runtime/syntax/checkhealth.vim | 1 - runtime/syntax/query.lua | 1 - src/nvim/highlight_group.c | 2 +- src/nvim/mbyte.c | 2 +- src/nvim/option.c | 2 +- src/nvim/popupmenu.c | 2 +- src/nvim/tui/termkey/termkey-internal.h | 2 +- test/functional/autocmd/completedone_spec.lua | 2 +- test/functional/core/startup_spec.lua | 2 +- test/functional/lua/system_spec.lua | 2 +- test/functional/plugin/lsp_spec.lua | 2 +- test/functional/provider/clipboard_spec.lua | 2 +- 20 files changed, 15 insertions(+), 20 deletions(-) diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index d0cbfefb47..c6ca5e5ce9 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -51,7 +51,7 @@ LSP • vim.lsp.buf_request_all The `error` key has been renamed to `err` inside the result parameter of the handler. • *vim.lsp.with()* Pass configuration to equivalent - functions in `vim.lsp.buf.*'. + functions in `vim.lsp.buf.*`. • |vim.lsp.handlers| No longer support client to server response handlers. Only server to client requests/notification handlers are supported. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ed342d9229..0f2e6f0a08 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -227,7 +227,7 @@ LUA • |vim.fs.rm()| can delete files and directories. • |vim.validate()| now has a new signature which uses less tables, - is more peformant and easier to read. + is more performant and easier to read. • |vim.str_byteindex()| and |vim.str_utfindex()| gained overload signatures supporting two new parameters, `encoding` and `strict_indexing`. diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 09d1369d46..04dd854637 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -1,4 +1,4 @@ -*pi_netrw.txt* For Vim version 8.2. Last change: 2020 Aug 15 +*pi_netrw.txt* Nvim ------------------------------------------------ NETRW REFERENCE MANUAL by Charles E. Campbell diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index c8570044e5..96b26d92e7 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -1,4 +1,4 @@ -*pi_tar.txt* For Vim version 8.2. Last change: 2020 Jan 07 +*pi_tar.txt* Nvim +====================+ | Tar File Interface | diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 50075c3bde..76c6b729db 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -414,7 +414,7 @@ TUI: - 'term' reflects the terminal type derived from |$TERM| and other environment checks. For debugging only; not reliable during startup. >vim :echo &term -- "builtin_x" means one of the |builtin-terms| was chosen, because the expected +- "builtin_x" means one of the |builtin-terms| was chosen, because the expected terminfo file was not found on the system. - Nvim will use 256-colour capability on Linux virtual terminals. Vim uses only 8 colours plus bright foreground on Linux VTs. diff --git a/runtime/ftplugin/checkhealth.vim b/runtime/ftplugin/checkhealth.vim index 00f24a2912..cc53d723c2 100644 --- a/runtime/ftplugin/checkhealth.vim +++ b/runtime/ftplugin/checkhealth.vim @@ -1,6 +1,5 @@ " Vim filetype plugin " Language: Nvim :checkhealth buffer -" Last Change: 2022 Nov 10 if exists("b:did_ftplugin") finish diff --git a/runtime/ftplugin/query.lua b/runtime/ftplugin/query.lua index 32d615c65c..711ee35775 100644 --- a/runtime/ftplugin/query.lua +++ b/runtime/ftplugin/query.lua @@ -1,6 +1,5 @@ -- Neovim filetype plugin file -- Language: Treesitter query --- Last Change: 2024 Jul 03 if vim.b.did_ftplugin == 1 then return diff --git a/runtime/indent/query.lua b/runtime/indent/query.lua index c5b4f1f03d..17b8e9d2ac 100644 --- a/runtime/indent/query.lua +++ b/runtime/indent/query.lua @@ -1,6 +1,5 @@ -- Neovim indent file -- Language: Treesitter query --- Last Change: 2024 Jul 03 -- it's a lisp! vim.cmd([[runtime! indent/lisp.vim]]) diff --git a/runtime/syntax/checkhealth.vim b/runtime/syntax/checkhealth.vim index a4f6e016cb..14c80640ba 100644 --- a/runtime/syntax/checkhealth.vim +++ b/runtime/syntax/checkhealth.vim @@ -1,6 +1,5 @@ " Vim syntax file " Language: Nvim :checkhealth buffer -" Last Change: 2022 Nov 10 if exists("b:current_syntax") finish diff --git a/runtime/syntax/query.lua b/runtime/syntax/query.lua index 2dfe29f69b..0de08b4dfb 100644 --- a/runtime/syntax/query.lua +++ b/runtime/syntax/query.lua @@ -1,6 +1,5 @@ -- Neovim syntax file -- Language: Treesitter query --- Last Change: 2024 Jul 03 -- it's a lisp! vim.cmd([[runtime! syntax/lisp.vim]]) diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 8f026e9600..cc1b833c3c 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -303,7 +303,7 @@ static const char *highlight_init_both[] = { "default link @tag.builtin Special", // :help - // Higlight "===" and "---" heading delimiters specially. + // Highlight "===" and "---" heading delimiters specially. "default @markup.heading.1.delimiter.vimdoc guibg=bg guifg=bg guisp=fg gui=underdouble,nocombine ctermbg=NONE ctermfg=NONE cterm=underdouble,nocombine", "default @markup.heading.2.delimiter.vimdoc guibg=bg guifg=bg guisp=fg gui=underline,nocombine ctermbg=NONE ctermfg=NONE cterm=underline,nocombine", diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 65f718f925..b5a8588edd 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1119,7 +1119,7 @@ int utf_char2bytes(const int c, char *const buf) /// stateful algorithm to determine grapheme clusters. Still available /// to support some legacy code which hasn't been refactored yet. /// -/// To check if a char would combine with a preceeding space, use +/// To check if a char would combine with a preceding space, use /// utf_iscomposing_first() instead. /// /// Based on code from Markus Kuhn. diff --git a/src/nvim/option.c b/src/nvim/option.c index 1ce737bc59..1cfe4cd08b 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -228,7 +228,7 @@ static void set_init_default_backupskip(void) #endif { p = vim_getenv(names[i]); - plen = 0; // will be calcuated below + plen = 0; // will be calculated below } if (p != NULL && *p != NUL) { bool has_trailing_path_sep = false; diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 7df6a1a5d7..d1951d4f96 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -880,7 +880,7 @@ static void pum_adjust_info_position(win_T *wp, int height, int width) /// Used for nvim__complete_set /// /// @param selected the selected compl item. -/// @parma info Info string. +/// @param info Info string. /// @return a win_T pointer. win_T *pum_set_info(int selected, char *info) { diff --git a/src/nvim/tui/termkey/termkey-internal.h b/src/nvim/tui/termkey/termkey-internal.h index 107591f950..97fae939c5 100644 --- a/src/nvim/tui/termkey/termkey-internal.h +++ b/src/nvim/tui/termkey/termkey-internal.h @@ -47,7 +47,7 @@ struct TermKey { int canonflags; unsigned char *buffer; size_t buffstart; // First offset in buffer - size_t buffcount; // NUMBER of entires valid in buffer + size_t buffcount; // NUMBER of entries valid in buffer size_t buffsize; // Total malloc'ed size size_t hightide; // Position beyond buffstart at which peekkey() should next start // normally 0, but see also termkey_interpret_csi diff --git a/test/functional/autocmd/completedone_spec.lua b/test/functional/autocmd/completedone_spec.lua index 33beb16db2..36dc73842d 100644 --- a/test/functional/autocmd/completedone_spec.lua +++ b/test/functional/autocmd/completedone_spec.lua @@ -32,7 +32,7 @@ describe('CompleteDone', function() feed('') eq('cancel', eval('g:donereason')) end) - it('when overriden by another complete()', function() + it('when overridden by another complete()', function() call('complete', call('col', '.'), { 'bar', 'baz' }) eq('cancel', eval('g:donereason')) end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 7062211187..f3c477d210 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -343,7 +343,7 @@ describe('startup', function() local screen = Screen.new(25, 3) -- Remote UI connected by --embed. -- TODO: a lot of tests in this file already use the new default color scheme. - -- once we do the batch update of tests to use it, remove this workarond + -- once we do the batch update of tests to use it, remove this workaround screen._default_attr_ids = nil command([[echo has('ttyin') has('ttyout')]]) screen:expect([[ diff --git a/test/functional/lua/system_spec.lua b/test/functional/lua/system_spec.lua index afbada007d..3f847ca3be 100644 --- a/test/functional/lua/system_spec.lua +++ b/test/functional/lua/system_spec.lua @@ -114,7 +114,7 @@ describe('vim.system', function() end) if t.is_os('win') then - it('can resolve windows command extentions.', function() + it('can resolve windows command extensions', function() t.write_file('test.bat', 'echo hello world') system_sync({ 'chmod', '+x', 'test.bat' }) system_sync({ './test' }) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 0f68b7eae2..f14e24bb19 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3499,7 +3499,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'zig', { '(' }) end) - -- Note that although the higlight positions below are 0-indexed, the 2nd parameter + -- Note that although the highlight positions below are 0-indexed, the 2nd parameter -- corresponds to the 3rd line because the first line is the ``` from the -- Markdown block. local expected = { 3, 4, 3, 11 } diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 722442acbd..2b54ea93e0 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -544,7 +544,7 @@ describe('clipboard (with fake clipboard.vim)', function() ]]) feed('gg^') -- Goto start of top line enter visual block mode feed('3ljy^k') -- yank 4x2 block & goto initial location - feed('P') -- Paste it infront + feed('P') -- Paste it before cursor expect([[ aabbaabbcc ddeeddeeff -- cgit From 01026ba47ba8a656bb5cd09afbb25b4b33c0b752 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 21 Nov 2024 15:37:35 +0800 Subject: vim-patch:9.1.0862: 'wildmenu' not enabled by default in nocp mode (#31261) Problem: 'wildmenu' not enabled by default in nocp mode Solution: promote the default Vim value to true, it has been enabled in defaults.vim anyhow, so remove it there (Luca Saccarola) closes: vim/vim#16055 https://github.com/vim/vim/commit/437bc13ea101835511bf4b5029c84482c1e30e62 Co-authored-by: Luca Saccarola --- runtime/doc/vim_diff.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 76c6b729db..adc866af6b 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -89,7 +89,6 @@ Defaults *nvim-defaults* - 'undodir' defaults to ~/.local/state/nvim/undo// (|xdg|), auto-created - 'viewoptions' includes "unix,slash", excludes "options" - 'viminfo' includes "!" -- 'wildmenu' is enabled - 'wildoptions' defaults to "pum,tagfile" - |editorconfig| plugin is enabled, .editorconfig settings are applied. -- cgit From 534544cbf7ac92aef44336cc9da1bfc02a441e6e Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 18 Nov 2024 17:15:05 +0000 Subject: test: move exec_lua logic to separate module By making it a separate module, the embedded Nvim session can require this module directly instead of setup code sending over the module via RPC. Also make exec_lua wrap _G.print so messages can be seen in the test output immediately as the exec_lua returns. --- test/functional/testnvim.lua | 122 +--------------------------- test/functional/testnvim/exec_lua.lua | 148 ++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 121 deletions(-) create mode 100644 test/functional/testnvim/exec_lua.lua diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index 60b2f872fc..43c38d18c0 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -800,81 +800,6 @@ function M.exec_capture(code) return M.api.nvim_exec2(code, { output = true }).output end ---- @param f function ---- @return table -local function get_upvalues(f) - local i = 1 - local upvalues = {} --- @type table - while true do - local n, v = debug.getupvalue(f, i) - if not n then - break - end - upvalues[n] = v - i = i + 1 - end - return upvalues -end - ---- @param f function ---- @param upvalues table -local function set_upvalues(f, upvalues) - local i = 1 - while true do - local n = debug.getupvalue(f, i) - if not n then - break - end - if upvalues[n] then - debug.setupvalue(f, i, upvalues[n]) - end - i = i + 1 - end -end - ---- @type fun(f: function): table -_G.__get_upvalues = nil - ---- @type fun(f: function, upvalues: table) -_G.__set_upvalues = nil - ---- @param self table ---- @param bytecode string ---- @param upvalues table ---- @param ... any[] ---- @return any[] result ---- @return table upvalues -local function exec_lua_handler(self, bytecode, upvalues, ...) - local f = assert(loadstring(bytecode)) - self.set_upvalues(f, upvalues) - local ret = { f(...) } --- @type any[] - --- @type table - local new_upvalues = self.get_upvalues(f) - - do -- Check return value types for better error messages - local invalid_types = { - ['thread'] = true, - ['function'] = true, - ['userdata'] = true, - } - - for k, v in pairs(ret) do - if invalid_types[type(v)] then - error( - string.format( - "Return index %d with value '%s' of type '%s' cannot be serialized over RPC", - k, - tostring(v), - type(v) - ) - ) - end - end - end - - return ret, new_upvalues -end - --- Execute Lua code in the wrapped Nvim session. --- --- When `code` is passed as a function, it is converted into Lua byte code. @@ -921,52 +846,7 @@ function M.exec_lua(code, ...) end assert(session, 'no Nvim session') - - if not session.exec_lua_setup then - assert( - session:request( - 'nvim_exec_lua', - [[ - _G.__test_exec_lua = { - get_upvalues = loadstring((select(1,...))), - set_upvalues = loadstring((select(2,...))), - handler = loadstring((select(3,...))) - } - setmetatable(_G.__test_exec_lua, { __index = _G.__test_exec_lua }) - ]], - { string.dump(get_upvalues), string.dump(set_upvalues), string.dump(exec_lua_handler) } - ) - ) - session.exec_lua_setup = true - end - - local stat, rv = session:request( - 'nvim_exec_lua', - 'return { _G.__test_exec_lua:handler(...) }', - { string.dump(code), get_upvalues(code), ... } - ) - - if not stat then - error(rv[2]) - end - - --- @type any[], table - local ret, upvalues = unpack(rv) - - -- Update upvalues - if next(upvalues) then - local caller = debug.getinfo(2) - local f = caller.func - -- On PUC-Lua, if the function is a tail call, then func will be nil. - -- In this case we need to use the current function. - if not f then - assert(caller.source == '=(tail call)') - f = debug.getinfo(1).func - end - set_upvalues(f, upvalues) - end - - return unpack(ret, 1, table.maxn(ret)) + return require('test.functional.testnvim.exec_lua')(session, 2, code, ...) end function M.get_pathsep() diff --git a/test/functional/testnvim/exec_lua.lua b/test/functional/testnvim/exec_lua.lua new file mode 100644 index 0000000000..ddd9905ce7 --- /dev/null +++ b/test/functional/testnvim/exec_lua.lua @@ -0,0 +1,148 @@ +--- @param f function +--- @return table +local function get_upvalues(f) + local i = 1 + local upvalues = {} --- @type table + while true do + local n, v = debug.getupvalue(f, i) + if not n then + break + end + upvalues[n] = v + i = i + 1 + end + return upvalues +end + +--- @param f function +--- @param upvalues table +local function set_upvalues(f, upvalues) + local i = 1 + while true do + local n = debug.getupvalue(f, i) + if not n then + break + end + if upvalues[n] then + debug.setupvalue(f, i, upvalues[n]) + end + i = i + 1 + end +end + +--- @param messages string[] +--- @param ... ... +local function add_print(messages, ...) + local msg = {} --- @type string[] + for i = 1, select('#', ...) do + msg[#msg + 1] = tostring(select(i, ...)) + end + table.insert(messages, table.concat(msg, '\t')) +end + +local invalid_types = { + ['thread'] = true, + ['function'] = true, + ['userdata'] = true, +} + +--- @param r any[] +local function check_returns(r) + for k, v in pairs(r) do + if invalid_types[type(v)] then + error( + string.format( + "Return index %d with value '%s' of type '%s' cannot be serialized over RPC", + k, + tostring(v), + type(v) + ), + 2 + ) + end + end +end + +local M = {} + +--- This is run in the context of the remote Nvim instance. +--- @param bytecode string +--- @param upvalues table +--- @param ... any[] +--- @return any[] result +--- @return table upvalues +--- @return string[] messages +function M.handler(bytecode, upvalues, ...) + local messages = {} --- @type string[] + local orig_print = _G.print + + function _G.print(...) + add_print(messages, ...) + return orig_print(...) + end + + local f = assert(loadstring(bytecode)) + + set_upvalues(f, upvalues) + + -- Run in pcall so we can return any print messages + local ret = { pcall(f, ...) } --- @type any[] + + _G.print = orig_print + + local new_upvalues = get_upvalues(f) + + -- Check return value types for better error messages + check_returns(ret) + + return ret, new_upvalues, messages +end + +--- @param session test.Session +--- @param lvl integer +--- @param code function +--- @param ... ... +local function run(session, lvl, code, ...) + local stat, rv = session:request( + 'nvim_exec_lua', + [[return { require('test.functional.testnvim.exec_lua').handler(...) }]], + { string.dump(code), get_upvalues(code), ... } + ) + + if not stat then + error(rv[2], 2) + end + + --- @type any[], table, string[] + local ret, upvalues, messages = unpack(rv) + + for _, m in ipairs(messages) do + print(m) + end + + if not ret[1] then + error(ret[2], 2) + end + + -- Update upvalues + if next(upvalues) then + local caller = debug.getinfo(lvl) + local i = 0 + + -- On PUC-Lua, if the function is a tail call, then func will be nil. + -- In this case we need to use the caller. + while not caller.func do + i = i + 1 + caller = debug.getinfo(lvl + i) + end + set_upvalues(caller.func, upvalues) + end + + return unpack(ret, 2, table.maxn(ret)) +end + +return setmetatable(M, { + __call = function(_, ...) + return run(...) + end, +}) -- cgit From be89d520d7e9ad6c574c259a10f282177fb5dd4a Mon Sep 17 00:00:00 2001 From: bfredl Date: Wed, 13 Nov 2024 13:25:10 +0100 Subject: refactor(windows)!: only support UCRT, even for mingw The newer UCRT runtime has native support for UTF-8, including forcing it as the active codepage even before `main()` is called. This means the c runtime will properly convert windows WCHAR:s into UTF-8 bytes, as early as the argv/argc params to `main()` . Whereas MSVCRT does not support this reliably and required us to use `wmain()`. Only MSVC supports using manifest files directly as source files. The solution for other Windows toolchains is to use a .rc file. --- runtime/doc/news.txt | 4 ++++ src/nvim/CMakeLists.txt | 4 ++-- src/nvim/main.c | 15 +++------------ src/nvim/os/nvim.rc | 2 ++ test/functional/fixtures/CMakeLists.txt | 3 --- 5 files changed, 11 insertions(+), 17 deletions(-) create mode 100644 src/nvim/os/nvim.rc diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index fc714526d4..c8d0df5c96 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -45,6 +45,10 @@ API • Renamed `nvim__id_dictionary` (unsupported/experimental API) to `nvim__id_dict`. +BUILD + +On Windows, only building with the UCRT runtime is supported. + DEFAULTS • |]d-default| and |[d-default| accept a count. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index cb71144130..1b6827322d 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -120,8 +120,8 @@ elseif(MINGW) # Use POSIX compatible stdio in Mingw target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO) - # Enable wmain - target_link_libraries(nvim_bin PRIVATE -municode) + # wrapper for nvim.manifest + target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.rc) elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") target_compile_options(main_lib INTERFACE -Wno-conversion diff --git a/src/nvim/main.c b/src/nvim/main.c index 695bd4c95a..dc4969759d 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -111,6 +111,9 @@ #ifdef MSWIN # include "nvim/os/os_win_console.h" +# ifndef _UCRT +# error UCRT is the only supported C runtime on windows +# endif #endif #if defined(MSWIN) && !defined(MAKE_LIB) @@ -241,22 +244,10 @@ void early_init(mparm_T *paramp) #ifdef MAKE_LIB int nvim_main(int argc, char **argv); // silence -Wmissing-prototypes int nvim_main(int argc, char **argv) -#elif defined(MSWIN) -int wmain(int argc, wchar_t **argv_w) // multibyte args on Windows. #7060 #else int main(int argc, char **argv) #endif { -#if defined(MSWIN) && !defined(MAKE_LIB) - char **argv = xmalloc((size_t)argc * sizeof(char *)); - for (int i = 0; i < argc; i++) { - char *buf = NULL; - utf16_to_utf8(argv_w[i], -1, &buf); - assert(buf); - argv[i] = buf; - } -#endif - argv0 = argv[0]; if (!appname_is_valid()) { diff --git a/src/nvim/os/nvim.rc b/src/nvim/os/nvim.rc new file mode 100644 index 0000000000..e838c93c16 --- /dev/null +++ b/src/nvim/os/nvim.rc @@ -0,0 +1,2 @@ +#include "winuser.h" +2 RT_MANIFEST nvim.manifest diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index 150407fe46..a388f9cb33 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -1,7 +1,4 @@ add_library(test_lib INTERFACE) -if(MINGW) - target_link_libraries(test_lib INTERFACE -municode) -endif() if(WIN32) target_compile_definitions(test_lib INTERFACE MSWIN) endif() -- cgit From ff75f345ab5fa57c6560db021e8eb099aff90472 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 22 Nov 2024 06:52:32 +0800 Subject: fix(highlight): 'winhl' shouldn't take priority over API (#31288) --- src/nvim/api/window.c | 1 + src/nvim/buffer_defs.h | 2 +- src/nvim/option.c | 8 +++++++- test/functional/api/highlight_spec.lua | 14 ++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 5a4972ef23..ee7729ce81 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -476,6 +476,7 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) } win->w_ns_hl = (NS)ns_id; + win->w_ns_hl_winhl = -1; win->w_hl_needs_update = true; redraw_later(win, UPD_NOT_VALID); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index d33734ccfe..a8f3fc45b9 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1035,7 +1035,7 @@ struct window_S { synblock_T *w_s; ///< for :ownsyntax int w_ns_hl; - int w_ns_hl_winhl; + int w_ns_hl_winhl; ///< when set to -1, 'winhighlight' shouldn't be used int w_ns_hl_active; int *w_ns_hl_attr; diff --git a/src/nvim/option.c b/src/nvim/option.c index 1cfe4cd08b..d3cbe9f056 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1736,8 +1736,14 @@ bool parse_winhl_opt(const char *winhl, win_T *wp) p = wp->w_p_winhl; } + if (wp != NULL && wp->w_ns_hl_winhl < 0) { + // 'winhighlight' shouldn't be used for this window. + // Only check that the value is valid. + wp = NULL; + } + if (!*p) { - if (wp != NULL && wp->w_ns_hl_winhl && wp->w_ns_hl == wp->w_ns_hl_winhl) { + if (wp != NULL && wp->w_ns_hl_winhl > 0 && wp->w_ns_hl == wp->w_ns_hl_winhl) { wp->w_ns_hl = 0; wp->w_hl_needs_update = true; } diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index dd0611f184..1bbfe203b6 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -710,4 +710,18 @@ describe('API: set/get highlight namespace', function() api.nvim_win_set_hl_ns(0, ns) eq(ns, api.nvim_get_hl_ns({ winid = 0 })) end) + + it('setting namespace takes priority over &winhighlight', function() + command('set winhighlight=Visual:Search') + n.insert('foobar') + local ns = api.nvim_create_namespace('') + api.nvim_win_set_hl_ns(0, ns) + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('enew') -- switching buffer keeps namespace #30904 + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('set winhighlight=') + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('set winhighlight=Visual:Search') + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + end) end) -- cgit From 359763307546ae7a401e9458fefeb6f4cd37a40f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 22 Nov 2024 08:08:12 +0800 Subject: test(autocmd/termxx_spec): fix TextChangedT test flakiness (#31296) Problem: The E937 error appears for too short in TextChangedT test. Solution: Only feed an Enter key after seeing the error. --- test/functional/autocmd/termxx_spec.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index baf2bb6071..950ef2f58a 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -213,9 +213,11 @@ describe('autocmd TextChangedT', function() end) it('cannot delete terminal buffer', function() - command([[autocmd TextChangedT * call nvim_input('') | bwipe!]]) + command('autocmd TextChangedT * bwipe!') tt.feed_data('a') screen:expect({ any = 'E937: ' }) + feed('') + command('autocmd! TextChangedT') matches( '^E937: Attempt to delete a buffer that is in use: term://', api.nvim_get_vvar('errmsg') -- cgit From c2f08d294a7f30d92368ffed9115edf96279dcb9 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 19 Nov 2024 09:45:43 +0100 Subject: build(deps): bump tree-sitter-c to v0.23.2 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 1623d0ff57..c42790ed5d 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -38,8 +38,8 @@ LIBICONV_SHA256 8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313 UTF8PROC_URL https://github.com/JuliaStrings/utf8proc/archive/3de4596fbe28956855df2ecb3c11c0bbc3535838.tar.gz UTF8PROC_SHA256 fb4a16bb659b58afb7f921fcc8928d0b3c1fcab135366c8a4f9ca7de1b1cfada -TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.23.0.tar.gz -TREESITTER_C_SHA256 ee58c925e2e507c23d735aad46bf7fb0af31ca06d6f4f41bc008216d9232b0cb +TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.23.2.tar.gz +TREESITTER_C_SHA256 d8b9c1b2ffb6a42caf9bc76e07c52507d4e60b17175ed9beb0e779be8db1200c TREESITTER_LUA_URL https://github.com/tree-sitter-grammars/tree-sitter-lua/archive/v0.2.0.tar.gz TREESITTER_LUA_SHA256 6c41227cd0a59047b19d31f0031d4d901f08bfd78d6fc7f55c89e5b8374c794e TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.4.0.tar.gz -- cgit From 6e44a6a289c538c9e05a5114ddb7f91f581e8965 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 19 Nov 2024 09:46:55 +0100 Subject: fix(treesitter): update queries --- runtime/queries/c/highlights.scm | 13 +++++++++++-- runtime/queries/lua/highlights.scm | 2 -- runtime/queries/markdown_inline/highlights.scm | 13 ++++--------- runtime/queries/query/highlights.scm | 2 +- runtime/queries/vim/highlights.scm | 1 + test/functional/treesitter/highlight_spec.lua | 2 +- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index eba272d5c9..bd6857fd17 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -252,13 +252,22 @@ ; Preproc def / undef (preproc_def - name: (_) @constant) + name: (_) @constant.macro) (preproc_call directive: (preproc_directive) @_u - argument: (_) @constant + argument: (_) @constant.macro (#eq? @_u "#undef")) +(preproc_ifdef + name: (identifier) @constant.macro) + +(preproc_elifdef + name: (identifier) @constant.macro) + +(preproc_defined + (identifier) @constant.macro) + (call_expression function: (identifier) @function.call) diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm index 01c280f2d5..79ab165aa7 100644 --- a/runtime/queries/lua/highlights.scm +++ b/runtime/queries/lua/highlights.scm @@ -151,8 +151,6 @@ ((identifier) @constant (#lua-match? @constant "^[A-Z][A-Z_0-9]*$")) -(vararg_expression) @constant - (nil) @constant.builtin [ diff --git a/runtime/queries/markdown_inline/highlights.scm b/runtime/queries/markdown_inline/highlights.scm index 148ef0fad0..5fb9e911dd 100644 --- a/runtime/queries/markdown_inline/highlights.scm +++ b/runtime/queries/markdown_inline/highlights.scm @@ -40,14 +40,12 @@ (image_description) ] @markup.link.label -(inline_link - (link_text) @_label - (link_destination) @_url +((inline_link + (link_destination) @_url) @_label (#set! @_label url @_url)) -(image - (image_description) @_label - (link_destination) @_url +((image + (link_destination) @_url) @_label (#set! @_label url @_url)) ; Conceal image links @@ -93,9 +91,6 @@ (email_autolink) ] @markup.link.url @nospell -((link_destination) @_url - (#set! @_url url @_url)) - ((uri_autolink) @_url (#offset! @_url 0 1 0 -1) (#set! @_url url @_url)) diff --git a/runtime/queries/query/highlights.scm b/runtime/queries/query/highlights.scm index e459b44602..f839ec985c 100644 --- a/runtime/queries/query/highlights.scm +++ b/runtime/queries/query/highlights.scm @@ -43,7 +43,7 @@ "#" ] @punctuation.special -"_" @constant +"_" @character.special ((parameters (identifier) @number) diff --git a/runtime/queries/vim/highlights.scm b/runtime/queries/vim/highlights.scm index 14e5a8128f..df7b3cf483 100644 --- a/runtime/queries/vim/highlights.scm +++ b/runtime/queries/vim/highlights.scm @@ -287,6 +287,7 @@ "=~" "!~" "=" + "^=" "+=" "-=" "*=" diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 5c6be869c6..60b1510603 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -1108,7 +1108,7 @@ describe('treesitter highlighting (markdown)', function() }) screen:expect({ grid = [[ - {25:[}{100:This link text}{25:](}{101:https://example.com}{25:)} is| + {100:[This link text](}{101:https://example.com}{100:)} is| a hyperlink^. | {1:~ }|*3 | -- cgit From 7d8db544417e7811ae6e3c5398e9d0481a0ada22 Mon Sep 17 00:00:00 2001 From: glepnir Date: Fri, 22 Nov 2024 15:34:49 +0800 Subject: fix(lsp): delete b:lsp_floating_preview buf var after win close Problem: After floating preview window closed the buf var still exist Solution: delete after floating window closed. --- runtime/lua/vim/lsp/util.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index cfa8a194d9..24837c3b44 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1344,6 +1344,10 @@ local function close_preview_window(winnr, bufnrs) local augroup = 'preview_window_' .. winnr pcall(api.nvim_del_augroup_by_name, augroup) + local buf = vim.w[winnr].buf_hold_win + if buf and api.nvim_buf_is_valid(buf) then + vim.b[buf].lsp_floating_window = nil + end pcall(api.nvim_win_close, winnr, true) end) end @@ -1609,6 +1613,7 @@ function M.open_floating_preview(contents, syntax, opts) { silent = true, noremap = true, nowait = true } ) close_preview_autocmd(opts.close_events, floating_winnr, { floating_bufnr, bufnr }) + vim.w[floating_winnr].buf_hold_win = bufnr -- save focus_id if opts.focus_id then -- cgit From c697c49a769794fdab0b8e77bb69d4435faa3d24 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 22 Nov 2024 19:16:54 +0800 Subject: test(filetype): symlink detection works after expand('') (#31307) Also add a test for #31306, which currently fails. --- test/functional/lua/filetype_spec.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index b6011d5268..6a4f8d70f3 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -199,7 +199,19 @@ describe('filetype.lua', function() finally(function() uv.fs_unlink('Xfiletype/.config/git') end) - clear({ args = { '--clean', 'Xfiletype/.config/git/config' } }) + local args = { '--clean', 'Xfiletype/.config/git/config' } + clear({ args = args }) eq('gitconfig', api.nvim_get_option_value('filetype', {})) + table.insert(args, 2, '--cmd') + table.insert(args, 3, "autocmd BufRead * call expand('')") + clear({ args = args }) + eq('gitconfig', api.nvim_get_option_value('filetype', {})) + end) + + pending('works with :doautocmd BufRead #31306', function() + clear({ args = { '--clean' } }) + eq('', api.nvim_get_option_value('filetype', {})) + command('doautocmd BufRead README.md') + eq('markdown', api.nvim_get_option_value('filetype', {})) end) end) -- cgit From bff07f6dd0d8e58748f36670685dd6157a67976b Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Fri, 22 Nov 2024 18:32:51 +0600 Subject: fix(api): don't try to get/set option for invalid option name (#31302) Problem: `validate_option_value_args()` returns `OK` even if option name is invalid or if option doesn't have the supported scope, which leads to Neovim still trying to erroneously get/set the option in those cases, which can lead to an assertion failure when `option_has_scope()` is invoked. This issue miraculously doesn't exist in release builds since the assertion is skipped and `(get/set)_option_value_for` returns if there is an error set, but that is not the intended location for that error to be caught. Solution: Make `validate_option_value_args()` return `FAIL` if there is an error set, which causes the API option functions to return early instead of trying to get/set an invalid option. --- src/nvim/api/options.c | 2 +- test/functional/api/vim_spec.lua | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 3289daeb6f..8fd9904f7a 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -95,7 +95,7 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } } - return OK; + return ERROR_SET(err) ? FAIL : OK; #undef HAS_KEY_X } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3f1e378bc1..2eeb5c18a1 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1770,6 +1770,11 @@ describe('API', function() end) it('validation', function() + eq("Unknown option 'foobar'", pcall_err(api.nvim_set_option_value, 'foobar', 'baz', {})) + eq( + "Unknown option 'foobar'", + pcall_err(api.nvim_set_option_value, 'foobar', 'baz', { win = api.nvim_get_current_win() }) + ) eq( "Invalid 'scope': expected 'local' or 'global'", pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 'bogus' }) -- cgit From 9a681ad09e2add96d47bf3f39cca8029f3bf09df Mon Sep 17 00:00:00 2001 From: andrew snelling <72226000+snelling-a@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:51:30 +0100 Subject: fix(lsp): hover keymap (#31208) * fix: use function call in keymap * fix: test --- runtime/lua/vim/lsp.lua | 4 +++- test/functional/plugin/lsp_spec.lua | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index c032d25cb1..6d29c9e4df 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -371,7 +371,9 @@ function lsp._set_defaults(client, bufnr) and is_empty_or_default(bufnr, 'keywordprg') and vim.fn.maparg('K', 'n', false, false) == '' then - vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr, desc = 'vim.lsp.buf.hover()' }) + vim.keymap.set('n', 'K', function() + vim.lsp.buf.hover() + end, { buffer = bufnr, desc = 'vim.lsp.buf.hover()' }) end end) if client:supports_method(ms.textDocument_diagnostic) then diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f14e24bb19..332a1a48bb 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -466,10 +466,17 @@ describe('LSP', function() true, exec_lua(function() local keymap --- @type table + local called = false + local origin = vim.lsp.buf.hover + vim.lsp.buf.hover = function() + called = true + end vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, true) end) - return keymap.callback == vim.lsp.buf.hover + keymap.callback() + vim.lsp.buf.hover = origin + return called end) ) client:stop() @@ -480,13 +487,13 @@ describe('LSP', function() eq('', get_buf_option('omnifunc')) eq('', get_buf_option('formatexpr')) eq( - '', + true, exec_lua(function() local keymap --- @type string vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, false) end) - return keymap + return keymap:match('') ~= nil end) ) end, -- cgit From 8516c2dc1f301c439695629fff771227dbe00d30 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 23 Nov 2024 14:22:06 +0600 Subject: refactor(options): autogenerate valid values and flag enums for options (#31089) Problem: Option metadata like list of valid values for an option and option flags are not listed in the `options.lua` file and are instead manually defined in C, which means option metadata is split between several places. Solution: Put metadata such as list of valid values for an option and option flags in `options.lua`, and autogenerate the corresponding C variables and enums. Supersedes #28659 Co-authored-by: glepnir --- src/nvim/CMakeLists.txt | 6 +- src/nvim/api/vim.c | 2 +- src/nvim/buffer.c | 20 +- src/nvim/buffer_defs.h | 2 - src/nvim/bufwrite.c | 10 +- src/nvim/change.c | 2 +- src/nvim/charset.c | 8 +- src/nvim/cmdexpand.c | 10 +- src/nvim/cursor.c | 6 +- src/nvim/diff.c | 6 +- src/nvim/drawline.c | 14 +- src/nvim/drawscreen.c | 10 +- src/nvim/edit.c | 54 ++--- src/nvim/ex_getln.c | 46 ++-- src/nvim/ex_session.c | 80 +++---- src/nvim/generators/gen_options.lua | 92 ++++++++ src/nvim/getchar.c | 2 +- src/nvim/grid.c | 8 +- src/nvim/indent.c | 6 +- src/nvim/insexpand.c | 28 +-- src/nvim/main.c | 2 +- src/nvim/mark.c | 4 +- src/nvim/mbyte.c | 8 +- src/nvim/message.c | 6 +- src/nvim/move.c | 2 +- src/nvim/normal.c | 53 ++--- src/nvim/ops.c | 38 ++-- src/nvim/option.c | 15 +- src/nvim/option_vars.h | 140 +----------- src/nvim/options.lua | 229 +++++++++++++++++++ src/nvim/optionstr.c | 434 +++++++++++++++--------------------- src/nvim/popupmenu.c | 8 +- src/nvim/quickfix.c | 6 +- src/nvim/search.c | 6 +- src/nvim/spell.c | 4 +- src/nvim/spellsuggest.c | 4 +- src/nvim/state.c | 6 +- src/nvim/tag.c | 16 +- src/nvim/terminal.c | 20 +- src/nvim/ui.c | 8 +- src/nvim/ui_compositor.c | 6 +- src/nvim/undo.c | 2 +- src/nvim/window.c | 10 +- 43 files changed, 774 insertions(+), 665 deletions(-) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 38b54082c3..e2b1036d95 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -322,6 +322,7 @@ set(GENERATED_KEYSETS_DEFS ${GENERATED_DIR}/keysets_defs.generated.h) set(GENERATED_OPTIONS ${GENERATED_DIR}/options.generated.h) set(GENERATED_OPTIONS_ENUM ${GENERATED_DIR}/options_enum.generated.h) set(GENERATED_OPTIONS_MAP ${GENERATED_DIR}/options_map.generated.h) +set(GENERATED_OPTION_VARS ${GENERATED_DIR}/option_vars.generated.h) set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h) set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h) set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h) @@ -657,6 +658,7 @@ list(APPEND NVIM_GENERATED_FOR_HEADERS "${GENERATED_EVENTS_ENUM}" "${GENERATED_KEYSETS_DEFS}" "${GENERATED_OPTIONS_ENUM}" + "${GENERATED_OPTION_VARS}" ) list(APPEND NVIM_GENERATED_FOR_SOURCES @@ -686,8 +688,8 @@ add_custom_command(OUTPUT ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP} DEPENDS ${LUA_GEN_DEPS} ${EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/auevents.lua ) -add_custom_command(OUTPUT ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} - COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} +add_custom_command(OUTPUT ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} + COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} DEPENDS ${LUA_GEN_DEPS} ${OPTIONS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/options.lua ) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 83f9aa573d..ab52612f9f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2156,7 +2156,7 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) { wp->w_cursorline = statuscol.foldinfo.fi_lnum; } - statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR); + statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & kOptCuloptFlagNumber); } statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 1908516e85..56ddadeb5c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1393,7 +1393,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) // If the buffer to be deleted is not the current one, delete it here. if (buf != curbuf) { - if (jop_flags & JOP_CLEAN) { + if (jop_flags & kOptJopFlagClean) { // Remove the buffer to be deleted from the jump list. mark_jumplist_forget_file(curwin, buf_fnum); } @@ -1419,7 +1419,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) { buf = au_new_curbuf.br_buf; } else if (curwin->w_jumplistlen > 0) { - if (jop_flags & JOP_CLEAN) { + if (jop_flags & kOptJopFlagClean) { // Remove the buffer from the jump list. mark_jumplist_forget_file(curwin, buf_fnum); } @@ -1429,7 +1429,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) if (curwin->w_jumplistlen > 0) { int jumpidx = curwin->w_jumplistidx; - if (jop_flags & JOP_CLEAN) { + if (jop_flags & kOptJopFlagClean) { // If the index is the same as the length, the current position was not yet added to the // jump list. So we can safely go back to the last entry and search from there. if (jumpidx == curwin->w_jumplistlen) { @@ -1443,7 +1443,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) } forward = jumpidx; - while ((jop_flags & JOP_CLEAN) || jumpidx != curwin->w_jumplistidx) { + while ((jop_flags & kOptJopFlagClean) || jumpidx != curwin->w_jumplistidx) { buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); if (buf != NULL) { @@ -1460,7 +1460,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) } } if (buf != NULL) { // found a valid buffer: stop searching - if (jop_flags & JOP_CLEAN) { + if (jop_flags & kOptJopFlagClean) { curwin->w_jumplistidx = jumpidx; update_jumplist = false; } @@ -2159,11 +2159,11 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) // If 'switchbuf' contains "split", "vsplit" or "newtab" and the // current buffer isn't empty: open new tab or window - if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB)) + if (wp == NULL && (swb_flags & (kOptSwbFlagVsplit | kOptSwbFlagSplit | kOptSwbFlagNewtab)) && !buf_is_empty(curbuf)) { - if (swb_flags & SWB_NEWTAB) { + if (swb_flags & kOptSwbFlagNewtab) { tabpage_new(); - } else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0) + } else if (win_split(0, (swb_flags & kOptSwbFlagVsplit) ? WSP_VERT : 0) == FAIL) { return FAIL; } @@ -2183,7 +2183,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; } - if (jop_flags & JOP_VIEW && restore_view) { + if (jop_flags & kOptJopFlagView && restore_view) { mark_view_restore(fm); } return OK; @@ -3638,7 +3638,7 @@ void ex_buffer_all(exarg_T *eap) // Open the buffer in this window. swap_exists_action = SEA_DIALOG; - set_curbuf(buf, DOBUF_GOTO, !(jop_flags & JOP_CLEAN)); + set_curbuf(buf, DOBUF_GOTO, !(jop_flags & kOptJopFlagClean)); if (!bufref_valid(&bufref)) { // Autocommands deleted the buffer. swap_exists_action = SEA_NONE; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index a8f3fc45b9..bb6eef3c29 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -316,8 +316,6 @@ typedef struct { char *b_p_spf; // 'spellfile' char *b_p_spl; // 'spelllang' char *b_p_spo; // 'spelloptions' -#define SPO_CAMEL 0x1 -#define SPO_NPBUFFER 0x2 unsigned b_p_spo_flags; // 'spelloptions' flags int b_cjk; // all CJK letters as OK uint8_t b_syn_chartab[32]; // syntax iskeyword option diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 5f830b4219..95639bed70 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -725,9 +725,9 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o FileInfo file_info; const bool no_prepend_dot = false; - if ((bkc & BKC_YES) || append) { // "yes" + if ((bkc & kOptBkcFlagYes) || append) { // "yes" *backup_copyp = true; - } else if ((bkc & BKC_AUTO)) { // "auto" + } else if ((bkc & kOptBkcFlagAuto)) { // "auto" // Don't rename the file when: // - it's a hard link // - it's a symbolic link @@ -773,19 +773,19 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o } // Break symlinks and/or hardlinks if we've been asked to. - if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) { + if ((bkc & kOptBkcFlagBreaksymlink) || (bkc & kOptBkcFlagBreakhardlink)) { #ifdef UNIX bool file_info_link_ok = os_fileinfo_link(fname, &file_info); // Symlinks. - if ((bkc & BKC_BREAKSYMLINK) + if ((bkc & kOptBkcFlagBreaksymlink) && file_info_link_ok && !os_fileinfo_id_equal(&file_info, file_info_old)) { *backup_copyp = false; } // Hardlinks. - if ((bkc & BKC_BREAKHARDLINK) + if ((bkc & kOptBkcFlagBreakhardlink) && os_fileinfo_hardlinks(file_info_old) > 1 && (!file_info_link_ok || os_fileinfo_id_equal(&file_info, file_info_old))) { diff --git a/src/nvim/change.c b/src/nvim/change.c index f3a8e0b208..faaae96af9 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -914,7 +914,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // fixpos is true, we don't want to end up positioned at the NUL, // unless "restart_edit" is set or 'virtualedit' contains "onemore". if (col > 0 && fixpos && restart_edit == 0 - && (get_ve_flags(curwin) & VE_ONEMORE) == 0) { + && (get_ve_flags(curwin) & kOptVeFlagOnemore) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 1afd590b0e..f72420a00f 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -95,7 +95,7 @@ int buf_init_chartab(buf_T *buf, bool global) int c = 0; while (c < ' ') { - g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + g_chartab[c++] = (dy_flags & kOptDyFlagUhex) ? 4 : 2; } while (c <= '~') { @@ -109,7 +109,7 @@ int buf_init_chartab(buf_T *buf, bool global) g_chartab[c++] = (CT_PRINT_CHAR | CT_FNAME_CHAR) + 1; } else { // the rest is unprintable by default - g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + g_chartab[c++] = (dy_flags & kOptDyFlagUhex) ? 4 : 2; } } } @@ -237,7 +237,7 @@ static int parse_isopt(const char *var, buf_T *buf, bool only_check) if (c < ' ' || c > '~') { if (tilde) { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) - + ((dy_flags & DY_UHEX) ? 4 : 2)); + + ((dy_flags & kOptDyFlagUhex) ? 4 : 2)); g_chartab[c] &= (uint8_t) ~CT_PRINT_CHAR; } else { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + 1); @@ -614,7 +614,7 @@ void transchar_nonprint(const buf_T *buf, char *charbuf, int c) } assert(c <= 0xff); - if (dy_flags & DY_UHEX || c > 0x7f) { + if (dy_flags & kOptDyFlagUhex || c > 0x7f) { // 'display' has "uhex" transchar_hex(charbuf, c); } else { diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 9b1193b4e0..d977b20cc4 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -100,7 +100,7 @@ static int compl_selected; static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { - return (wop_flags & WOP_FUZZY) + return (wop_flags & kOptWopFlagFuzzy) && xp->xp_context != EXPAND_BOOL_SETTINGS && xp->xp_context != EXPAND_COLORS && xp->xp_context != EXPAND_COMPILER @@ -133,7 +133,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) bool cmdline_fuzzy_complete(const char *const fuzzystr) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { - return (wop_flags & WOP_FUZZY) && *fuzzystr != NUL; + return (wop_flags & kOptWopFlagFuzzy) && *fuzzystr != NUL; } /// Sort function for the completion matches. @@ -806,7 +806,7 @@ static char *find_longest_match(expand_T *xp, int options) } if (i < xp->xp_numfiles) { if (!(options & WILD_NO_BEEP)) { - vim_beep(BO_WILD); + vim_beep(kOptBoFlagWildmode); } break; } @@ -1069,7 +1069,7 @@ int showmatches(expand_T *xp, bool wildmenu) bool compl_use_pum = (ui_has(kUICmdline) ? ui_has(kUIPopupmenu) - : wildmenu && (wop_flags & WOP_PUM)) + : wildmenu && (wop_flags & kOptWopFlagPum)) || ui_has(kUIWildmenu); if (compl_use_pum) { @@ -1939,7 +1939,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa case CMD_tjump: case CMD_stjump: case CMD_ptjump: - if (wop_flags & WOP_TAGFILE) { + if (wop_flags & kOptWopFlagTagfile) { xp->xp_context = EXPAND_TAGS_LISTFILES; } else { xp->xp_context = EXPAND_TAGS; diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 35afca2fe9..2b18c51571 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -105,7 +105,7 @@ static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, col || (State & MODE_TERMINAL) || restart_edit != NUL || (VIsual_active && *p_sel != 'o') - || ((get_ve_flags(wp) & VE_ONEMORE) && wcol < MAXCOL); + || ((get_ve_flags(wp) & kOptVeFlagOnemore) && wcol < MAXCOL); char *line = ml_get_buf(wp->w_buffer, pos->lnum); int linelen = ml_get_buf_len(wp->w_buffer, pos->lnum); @@ -345,7 +345,7 @@ void check_cursor_col(win_T *win) // - 'virtualedit' is set if ((State & MODE_INSERT) || restart_edit || (VIsual_active && *p_sel != 'o') - || (cur_ve_flags & VE_ONEMORE) + || (cur_ve_flags & kOptVeFlagOnemore) || virtual_active(win)) { win->w_cursor.col = len; } else { @@ -362,7 +362,7 @@ void check_cursor_col(win_T *win) // line. if (oldcol == MAXCOL) { win->w_cursor.coladd = 0; - } else if (cur_ve_flags == VE_ALL) { + } else if (cur_ve_flags == kOptVeFlagAll) { if (oldcoladd > win->w_cursor.col) { win->w_cursor.coladd = oldcoladd - win->w_cursor.col; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index f1dd08f0e6..20f93049c1 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2417,7 +2417,7 @@ int diffopt_changed(void) char *p = p_dip; while (*p != NUL) { - // Note: Keep this in sync with p_dip_values + // Note: Keep this in sync with opt_dip_values. if (strncmp(p, "filler", 6) == 0) { p += 6; diff_flags_new |= DIFF_FILLER; @@ -2464,7 +2464,7 @@ int diffopt_changed(void) p += 8; diff_flags_new |= DIFF_INTERNAL; } else if (strncmp(p, "algorithm:", 10) == 0) { - // Note: Keep this in sync with p_dip_algorithm_values. + // Note: Keep this in sync with opt_dip_algorithm_values. p += 10; if (strncmp(p, "myers", 5) == 0) { p += 5; @@ -2745,7 +2745,7 @@ bool diff_infold(win_T *wp, linenr_T lnum) void nv_diffgetput(bool put, size_t count) { if (bt_prompt(curbuf)) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); return; } diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 79f3298eb4..10811d40f8 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -395,7 +395,7 @@ static bool use_cursor_line_highlight(win_T *wp, linenr_T lnum) { return wp->w_p_cul && lnum == wp->w_cursorline - && (wp->w_p_culopt_flags & CULOPT_NBR); + && (wp->w_p_culopt_flags & kOptCuloptFlagNumber); } /// Setup for drawing the 'foldcolumn', if there is one. @@ -507,10 +507,10 @@ static bool use_cursor_line_nr(win_T *wp, winlinevars_T *wlv) { return wp->w_p_cul && wlv->lnum == wp->w_cursorline - && (wp->w_p_culopt_flags & CULOPT_NBR) + && (wp->w_p_culopt_flags & kOptCuloptFlagNumber) && (wlv->row == wlv->startrow + wlv->filler_lines || (wlv->row > wlv->startrow + wlv->filler_lines - && (wp->w_p_culopt_flags & CULOPT_LINE))); + && (wp->w_p_culopt_flags & kOptCuloptFlagLine))); } static int get_line_number_attr(win_T *wp, winlinevars_T *wlv) @@ -1165,11 +1165,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s wlv.filler_todo = wlv.filler_lines; // Cursor line highlighting for 'cursorline' in the current window. - if (wp->w_p_cul && wp->w_p_culopt_flags != CULOPT_NBR && lnum == wp->w_cursorline + if (wp->w_p_cul && wp->w_p_culopt_flags != kOptCuloptFlagNumber && lnum == wp->w_cursorline // Do not show the cursor line in the text when Visual mode is active, // because it's not clear what is selected then. && !(wp == curwin && VIsual_active)) { - cul_screenline = (is_wrapped && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); + cul_screenline = (is_wrapped && (wp->w_p_culopt_flags & kOptCuloptFlagScreenline)); if (!cul_screenline) { apply_cursorline_highlight(wp, &wlv); } else { @@ -1949,7 +1949,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s decor_attr = 0; if (extra_check) { - const bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0; + const bool no_plain_buffer = (wp->w_s->b_p_spo_flags & kOptSpoFlagNoplainbuffer) != 0; bool can_spell = !no_plain_buffer; // Get extmark and syntax attributes, unless still at the start of the line @@ -2324,7 +2324,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (wlv.n_extra == 0) { wlv.n_extra = byte2cells(mb_c) - 1; } - if ((dy_flags & DY_UHEX) && wp->w_p_rl) { + if ((dy_flags & kOptDyFlagUhex) && wp->w_p_rl) { rl_mirror_ascii(wlv.p_extra, NULL); // reverse "<12>" } wlv.sc_extra = NUL; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index e90a0d945f..3719d38df2 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1882,7 +1882,7 @@ static void win_update(win_T *wp) unsigned save_ve_flags = curwin->w_ve_flags; if (curwin->w_p_lbr) { - curwin->w_ve_flags = VE_ALL; + curwin->w_ve_flags = kOptVeFlagAll; } getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); @@ -1891,7 +1891,7 @@ static void win_update(win_T *wp) // Highlight to the end of the line, unless 'virtualedit' has // "block". if (curwin->w_curswant == MAXCOL) { - if (get_ve_flags(curwin) & VE_BLOCK) { + if (get_ve_flags(curwin) & kOptVeFlagBlock) { pos_T pos; int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; @@ -2227,7 +2227,7 @@ static void win_update(win_T *wp) && wp->w_lines[idx].wl_valid && wp->w_lines[idx].wl_lnum == lnum && lnum > wp->w_topline - && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) + && !(dy_flags & (kOptDyFlagLastline | kOptDyFlagTruncate)) && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows && win_get_fill(wp, lnum) == 0) { // This line is not going to fit. Don't draw anything here, @@ -2354,7 +2354,7 @@ redr_statuscol: // Window ends in filler lines. wp->w_botline = lnum; wp->w_filler_rows = wp->w_grid.rows - srow; - } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" + } else if (dy_flags & kOptDyFlagTruncate) { // 'display' has "truncate" // Last line isn't finished: Display "@@@" in the last screen line. grid_line_start(&wp->w_grid, wp->w_grid.rows - 1); grid_line_fill(0, MIN(wp->w_grid.cols, 3), wp->w_p_fcs_chars.lastline, at_attr); @@ -2362,7 +2362,7 @@ redr_statuscol: grid_line_flush(); set_empty_rows(wp, srow); wp->w_botline = lnum; - } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" + } else if (dy_flags & kOptDyFlagLastline) { // 'display' has "lastline" // Last line isn't finished: Display "@@@" at the end. // If this would split a doublewidth char in two, we need to display "@@@@" instead grid_line_start(&wp->w_grid, wp->w_grid.rows - 1); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index f06dc124f0..248f419807 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -440,7 +440,7 @@ static int insert_check(VimState *state) msg_scroll = false; // Open fold at the cursor line, according to 'foldopen'. - if (fdo_flags & FDO_INSERT) { + if (fdo_flags & kOptFdoFlagInsert) { foldOpenCursor(); } @@ -751,7 +751,7 @@ static int insert_handle_key(InsertState *s) ins_ctrl_o(); // don't move the cursor left when 'virtualedit' has "onemore". - if (get_ve_flags(curwin) & VE_ONEMORE) { + if (get_ve_flags(curwin) & kOptVeFlagOnemore) { ins_at_eol = false; s->nomove = true; } @@ -2518,7 +2518,7 @@ int oneright(void) // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' // contains "onemore". - if (ptr[l] == NUL && (get_ve_flags(curwin) & VE_ONEMORE) == 0) { + if (ptr[l] == NUL && (get_ve_flags(curwin) & kOptVeFlagOnemore) == 0) { return FAIL; } curwin->w_cursor.col += l; @@ -2600,7 +2600,7 @@ void cursor_up_inner(win_T *wp, linenr_T n) // If we entered a fold, move to the beginning, unless in // Insert mode or when 'foldopen' contains "all": it will open // in a moment. - if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) { + if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & kOptFdoFlagAll))) { hasFolding(wp, lnum, &lnum, NULL); } } @@ -3223,7 +3223,7 @@ static void ins_reg(void) check_cursor(curwin); } if (regname == NUL || !valid_yank_reg(regname, false)) { - vim_beep(BO_REG); + vim_beep(kOptBoFlagRegister); need_redraw = true; // remove the '"' } else { if (literally == Ctrl_O || literally == Ctrl_P) { @@ -3235,7 +3235,7 @@ static void ins_reg(void) do_put(regname, NULL, BACKWARD, 1, (literally == Ctrl_P ? PUT_FIXINDENT : 0) | PUT_CURSEND); } else if (insert_reg(regname, literally) == FAIL) { - vim_beep(BO_REG); + vim_beep(kOptBoFlagRegister); need_redraw = true; // remove the '"' } else if (stop_insert_mode) { // When the '=' register was used and a function was invoked that @@ -3314,7 +3314,7 @@ static void ins_ctrl_g(void) // Unknown CTRL-G command, reserved for future expansion. default: - vim_beep(BO_CTRLG); + vim_beep(kOptBoFlagCtrlg); } } @@ -3412,7 +3412,7 @@ static bool ins_esc(int *count, int cmdchar, bool nomove) && (curwin->w_cursor.col != 0 || curwin->w_cursor.coladd > 0) && (restart_edit == NUL || (gchar_cursor() == NUL && !VIsual_active)) && !revins_on) { - if (curwin->w_cursor.coladd > 0 || get_ve_flags(curwin) == VE_ALL) { + if (curwin->w_cursor.coladd > 0 || get_ve_flags(curwin) == kOptVeFlagAll) { oneleft(); if (restart_edit != NUL) { curwin->w_cursor.coladd++; @@ -3598,7 +3598,7 @@ static void ins_del(void) const int temp = curwin->w_cursor.col; if (!can_bs(BS_EOL) // only if "eol" included || do_join(2, false, true, false, false) == FAIL) { - vim_beep(BO_BS); + vim_beep(kOptBoFlagBackspace); } else { curwin->w_cursor.col = temp; // Adjust orig_line_count in case more lines have been deleted than @@ -3610,7 +3610,7 @@ static void ins_del(void) } } } else if (del_char(false) == FAIL) { // delete char under cursor - vim_beep(BO_BS); + vim_beep(kOptBoFlagBackspace); } did_ai = false; did_si = false; @@ -3649,7 +3649,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) || (!can_bs(BS_INDENT) && !arrow_used && ai_col > 0 && curwin->w_cursor.col <= ai_col) || (!can_bs(BS_EOL) && curwin->w_cursor.col == 0)))) { - vim_beep(BO_BS); + vim_beep(kOptBoFlagBackspace); return false; } @@ -3962,7 +3962,7 @@ static void ins_left(void) { const bool end_change = dont_sync_undo == kFalse; // end undoable change - if ((fdo_flags & FDO_HOR) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped) { foldOpenCursor(); } undisplay_dollar(); @@ -3985,14 +3985,14 @@ static void ins_left(void) coladvance(curwin, MAXCOL); curwin->w_set_curswant = true; // so we stay at the end } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } dont_sync_undo = kFalse; } static void ins_home(int c) { - if ((fdo_flags & FDO_HOR) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped) { foldOpenCursor(); } undisplay_dollar(); @@ -4008,7 +4008,7 @@ static void ins_home(int c) static void ins_end(int c) { - if ((fdo_flags & FDO_HOR) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped) { foldOpenCursor(); } undisplay_dollar(); @@ -4025,7 +4025,7 @@ static void ins_end(int c) static void ins_s_left(void) { const bool end_change = dont_sync_undo == kFalse; // end undoable change - if ((fdo_flags & FDO_HOR) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped) { foldOpenCursor(); } undisplay_dollar(); @@ -4037,7 +4037,7 @@ static void ins_s_left(void) bck_word(1, false, false); curwin->w_set_curswant = true; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } dont_sync_undo = kFalse; } @@ -4046,7 +4046,7 @@ static void ins_s_left(void) static void ins_right(void) { const bool end_change = dont_sync_undo == kFalse; // end undoable change - if ((fdo_flags & FDO_HOR) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped) { foldOpenCursor(); } undisplay_dollar(); @@ -4075,7 +4075,7 @@ static void ins_right(void) curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } dont_sync_undo = kFalse; } @@ -4083,7 +4083,7 @@ static void ins_right(void) static void ins_s_right(void) { const bool end_change = dont_sync_undo == kFalse; // end undoable change - if ((fdo_flags & FDO_HOR) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped) { foldOpenCursor(); } undisplay_dollar(); @@ -4096,7 +4096,7 @@ static void ins_s_right(void) fwd_word(1, false, 0); curwin->w_set_curswant = true; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } dont_sync_undo = kFalse; } @@ -4120,7 +4120,7 @@ static void ins_up(bool startcol) start_arrow(&tpos); can_cindent = true; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } } @@ -4142,7 +4142,7 @@ static void ins_pageup(void) start_arrow(&tpos); can_cindent = true; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } } @@ -4165,7 +4165,7 @@ static void ins_down(bool startcol) start_arrow(&tpos); can_cindent = true; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } } @@ -4187,7 +4187,7 @@ static void ins_pagedown(void) start_arrow(&tpos); can_cindent = true; } else { - vim_beep(BO_CRSR); + vim_beep(kOptBoFlagCursor); } } @@ -4535,7 +4535,7 @@ static int ins_digraph(void) int ins_copychar(linenr_T lnum) { if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { - vim_beep(BO_COPY); + vim_beep(kOptBoFlagCopy); return NUL; } @@ -4558,7 +4558,7 @@ int ins_copychar(linenr_T lnum) int c = ci.chr.value < 0 ? (uint8_t)(*ci.ptr) : ci.chr.value; if (c == NUL) { - vim_beep(BO_COPY); + vim_beep(kOptBoFlagCopy); } return c; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index ace62ea729..2d9d4417dd 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1070,23 +1070,23 @@ static int command_line_wildchar_complete(CommandLineState *s) { int res; int options = WILD_NO_BEEP; - if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) { + if (wim_flags[s->wim_index] & kOptWimFlagLastused) { options |= WILD_BUFLASTUSED; } if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice // if 'wildmode' contains "list" may still need to list if (s->xpc.xp_numfiles > 1 && !s->did_wild_list - && ((wim_flags[s->wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) { - showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + && ((wim_flags[s->wim_index] & kOptWimFlagList) + || (p_wmnu && (wim_flags[s->wim_index] & kOptWimFlagFull) != 0))) { + showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0)); redrawcmd(); s->did_wild_list = true; } - if (wim_flags[s->wim_index] & WIM_LONGEST) { + if (wim_flags[s->wim_index] & kOptWimFlagLongest) { res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); - } else if (wim_flags[s->wim_index] & WIM_FULL) { + } else if (wim_flags[s->wim_index] & kOptWimFlagFull) { res = nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); } else { res = OK; // don't insert 'wildchar' now @@ -1097,7 +1097,7 @@ static int command_line_wildchar_complete(CommandLineState *s) // if 'wildmode' first contains "longest", get longest // common part - if (wim_flags[0] & WIM_LONGEST) { + if (wim_flags[0] & kOptWimFlagLongest) { res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); } else { res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); @@ -1118,12 +1118,12 @@ static int command_line_wildchar_complete(CommandLineState *s) if (res == OK && s->xpc.xp_numfiles > 1) { // a "longest" that didn't do anything is skipped (but not // "list:longest") - if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) { + if (wim_flags[0] == kOptWimFlagLongest && ccline.cmdpos == j) { s->wim_index = 1; } - if ((wim_flags[s->wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) { - if (!(wim_flags[0] & WIM_LONGEST)) { + if ((wim_flags[s->wim_index] & kOptWimFlagList) + || (p_wmnu && (wim_flags[s->wim_index] & kOptWimFlagFull) != 0)) { + if (!(wim_flags[0] & kOptWimFlagLongest)) { int p_wmnu_save = p_wmnu; p_wmnu = 0; // remove match @@ -1131,17 +1131,17 @@ static int command_line_wildchar_complete(CommandLineState *s) p_wmnu = p_wmnu_save; } - showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0)); redrawcmd(); s->did_wild_list = true; - if (wim_flags[s->wim_index] & WIM_LONGEST) { + if (wim_flags[s->wim_index] & kOptWimFlagLongest) { nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); - } else if (wim_flags[s->wim_index] & WIM_FULL) { + } else if (wim_flags[s->wim_index] & kOptWimFlagFull) { nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); } } else { - vim_beep(BO_WILD); + vim_beep(kOptBoFlagWildmode); } } else if (s->xpc.xp_numfiles == -1) { s->xpc.xp_context = EXPAND_NOTHING; @@ -1380,9 +1380,9 @@ static int command_line_execute(VimState *state, int key) if (s->c == K_S_TAB && KeyTyped) { if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK) { if (s->xpc.xp_numfiles > 1 - && ((!s->did_wild_list && (wim_flags[s->wim_index] & WIM_LIST)) || p_wmnu)) { + && ((!s->did_wild_list && (wim_flags[s->wim_index] & kOptWimFlagList)) || p_wmnu)) { // Trigger the popup menu when wildoptions=pum - showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0)); } nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); @@ -1511,7 +1511,7 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s redrawcmdline(); curwin->w_cursor = s->match_end; } else { - vim_beep(BO_ERROR); + vim_beep(kOptBoFlagError); } restore_last_search_pattern(); return FAIL; @@ -2820,19 +2820,19 @@ int check_opt_wim(void) } for (char *p = p_wim; *p; p++) { - // Note: Keep this in sync with p_wim_values. + // Note: Keep this in sync with opt_wim_values. for (i = 0; ASCII_ISALPHA(p[i]); i++) {} if (p[i] != NUL && p[i] != ',' && p[i] != ':') { return FAIL; } if (i == 7 && strncmp(p, "longest", 7) == 0) { - new_wim_flags[idx] |= WIM_LONGEST; + new_wim_flags[idx] |= kOptWimFlagLongest; } else if (i == 4 && strncmp(p, "full", 4) == 0) { - new_wim_flags[idx] |= WIM_FULL; + new_wim_flags[idx] |= kOptWimFlagFull; } else if (i == 4 && strncmp(p, "list", 4) == 0) { - new_wim_flags[idx] |= WIM_LIST; + new_wim_flags[idx] |= kOptWimFlagList; } else if (i == 8 && strncmp(p, "lastused", 8) == 0) { - new_wim_flags[idx] |= WIM_BUFLASTUSED; + new_wim_flags[idx] |= kOptWimFlagLastused; } else { return FAIL; } diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 50ee197ef4..e7837467e4 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -71,7 +71,7 @@ static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) static int ses_winsizes(FILE *fd, bool restore_size, win_T *tab_firstwin) { - if (restore_size && (ssop_flags & SSOP_WINSIZE)) { + if (restore_size && (ssop_flags & kOptSsopFlagWinsize)) { int n = 0; for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) { if (!ses_do_win(wp)) { @@ -198,13 +198,13 @@ static int ses_do_win(win_T *wp) if (wp->w_buffer->b_fname == NULL // When 'buftype' is "nofile" can't restore the window contents. || (!wp->w_buffer->terminal && bt_nofilename(wp->w_buffer))) { - return ssop_flags & SSOP_BLANK; + return ssop_flags & kOptSsopFlagBlank; } if (bt_help(wp->w_buffer)) { - return ssop_flags & SSOP_HELP; + return ssop_flags & kOptSsopFlagHelp; } if (bt_terminal(wp->w_buffer)) { - return ssop_flags & SSOP_TERMINAL; + return ssop_flags & kOptSsopFlagTerminal; } return true; } @@ -257,7 +257,7 @@ static char *ses_get_fname(buf_T *buf, const unsigned *flagp) // directory is. if (buf->b_sfname != NULL && flagp == &ssop_flags - && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) + && (ssop_flags & (kOptSsopFlagCurdir | kOptSsopFlagSesdir)) && !p_acd && !did_lcd) { return buf->b_sfname; @@ -289,7 +289,7 @@ static char *ses_escape_fname(char *name, unsigned *flagp) char *p; char *sname = home_replace_save(NULL, name); - // Always SSOP_SLASH: change all backslashes to forward slashes. + // Always kOptSsopFlagSlash: change all backslashes to forward slashes. for (p = sname; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\\') { *p = '/'; @@ -328,7 +328,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Always restore cursor position for ":mksession". For ":mkview" only // when 'viewoptions' contains "cursor". - bool do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); + bool do_cursor = (flagp == &ssop_flags || *flagp & kOptSsopFlagCursor); // Local argument list. if (wp->w_alist == &global_alist) { @@ -336,7 +336,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr } else { if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, flagp == &vop_flags - || !(*flagp & SSOP_CURDIR) + || !(*flagp & kOptSsopFlagCurdir) || wp->w_localdir != NULL, flagp) == FAIL) { return FAIL; } @@ -417,7 +417,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr && *alt->b_fname != NUL && alt->b_p_bl // do not set balt if buffer is terminal and "terminal" is not set in options - && !(bt_terminal(alt) && !(ssop_flags & SSOP_TERMINAL)) + && !(bt_terminal(alt) && !(ssop_flags & kOptSsopFlagTerminal)) && (fputs("balt ", fd) < 0 || ses_fname(fd, alt, flagp, true) == FAIL)) { return FAIL; @@ -425,7 +425,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr } // Local mappings and abbreviations. - if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + if ((*flagp & (kOptSsopFlagOptions | kOptSsopFlagLocaloptions)) && makemap(fd, wp->w_buffer) == FAIL) { return FAIL; } @@ -438,10 +438,10 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr win_T *save_curwin = curwin; curwin = wp; curbuf = curwin->w_buffer; - if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) { + if (*flagp & (kOptSsopFlagOptions | kOptSsopFlagLocaloptions)) { f = makeset(fd, OPT_LOCAL, - flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); - } else if (*flagp & SSOP_FOLDS) { + flagp == &vop_flags || !(*flagp & kOptSsopFlagOptions)); + } else if (*flagp & kOptSsopFlagFolds) { f = makefoldset(fd); } else { f = OK; @@ -453,7 +453,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr } // Save Folds when 'buftype' is empty and for help files. - if ((*flagp & SSOP_FOLDS) + if ((*flagp & kOptSsopFlagFolds) && wp->w_buffer->b_ffname != NULL && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer))) { @@ -516,7 +516,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Local directory, if the current flag is not view options or the "curdir" // option is included. if (wp->w_localdir != NULL - && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { + && (flagp != &vop_flags || (*flagp & kOptSsopFlagCurdir))) { if (fputs("lcd ", fd) < 0 || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL || fprintf(fd, "\n") < 0) { @@ -574,7 +574,7 @@ static int store_session_globals(FILE *fd) /// Writes commands for restoring the current buffers, for :mksession. /// -/// Legacy 'sessionoptions'/'viewoptions' flags SSOP_UNIX, SSOP_SLASH are +/// Legacy 'sessionoptions'/'viewoptions' flags kOptSsopFlagUnix, kOptSsopFlagSlash are /// always enabled. /// /// @param dirnow Current directory name @@ -591,13 +591,13 @@ static int makeopens(FILE *fd, char *dirnow) int cur_arg_idx = 0; int next_arg_idx = 0; - if (ssop_flags & SSOP_BUFFERS) { + if (ssop_flags & kOptSsopFlagBuffers) { only_save_windows = false; // Save ALL buffers } // Begin by setting v:this_session, and then other sessionable variables. PUTLINE_FAIL("let v:this_session=expand(\":p\")"); - if (ssop_flags & SSOP_GLOBALS) { + if (ssop_flags & kOptSsopFlagGlobals) { if (store_session_globals(fd) == FAIL) { return FAIL; } @@ -605,15 +605,15 @@ static int makeopens(FILE *fd, char *dirnow) // Close all windows and tabs but one. PUTLINE_FAIL("silent only"); - if ((ssop_flags & SSOP_TABPAGES) + if ((ssop_flags & kOptSsopFlagTabpages) && put_line(fd, "silent tabonly") == FAIL) { return FAIL; } // Now a :cd command to the session directory or the current directory - if (ssop_flags & SSOP_SESDIR) { + if (ssop_flags & kOptSsopFlagSesdir) { PUTLINE_FAIL("exe \"cd \" . escape(expand(\":p:h\"), ' ')"); - } else if (ssop_flags & SSOP_CURDIR) { + } else if (ssop_flags & kOptSsopFlagCurdir) { char *sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); char *fname_esc = ses_escape_fname(sname, &ssop_flags); if (fprintf(fd, "cd %s\n", fname_esc) < 0) { @@ -637,7 +637,7 @@ static int makeopens(FILE *fd, char *dirnow) } // save 'shortmess' if not storing options - if ((ssop_flags & SSOP_OPTIONS) == 0) { + if ((ssop_flags & kOptSsopFlagOptions) == 0) { PUTLINE_FAIL("let s:shortmess_save = &shortmess"); } @@ -654,8 +654,8 @@ static int makeopens(FILE *fd, char *dirnow) // can be disrupted by prior `edit` or `tabedit` calls). FOR_ALL_BUFFERS(buf) { if (!(only_save_windows && buf->b_nwindows == 0) - && !(buf->b_help && !(ssop_flags & SSOP_HELP)) - && !(bt_terminal(buf) && !(ssop_flags & SSOP_TERMINAL)) + && !(buf->b_help && !(ssop_flags & kOptSsopFlagHelp)) + && !(bt_terminal(buf) && !(ssop_flags & kOptSsopFlagTerminal)) && buf->b_fname != NULL && buf->b_p_bl) { if (fprintf(fd, "badd +%" PRId64 " ", @@ -670,11 +670,11 @@ static int makeopens(FILE *fd, char *dirnow) // the global argument list if (ses_arglist(fd, "argglobal", &global_alist.al_ga, - !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { + !(ssop_flags & kOptSsopFlagCurdir), &ssop_flags) == FAIL) { return FAIL; } - if (ssop_flags & SSOP_RESIZE) { + if (ssop_flags & kOptSsopFlagResize) { // Note: after the restore we still check it worked! if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", (int64_t)Rows, (int64_t)Columns) < 0) { @@ -692,7 +692,7 @@ static int makeopens(FILE *fd, char *dirnow) restore_stal = true; } - if ((ssop_flags & SSOP_TABPAGES)) { + if ((ssop_flags & kOptSsopFlagTabpages)) { // "tabpages" is in 'sessionoptions': Similar to ses_win_rec() below, // populate the tab pages first so later local options won't be copied // to the new tabs. @@ -720,7 +720,7 @@ static int makeopens(FILE *fd, char *dirnow) // 'sessionoptions'. // Don't use goto_tabpage(), it may change directory and trigger // autocommands. - if ((ssop_flags & SSOP_TABPAGES)) { + if ((ssop_flags & kOptSsopFlagTabpages)) { if (tp == curtab) { tab_firstwin = firstwin; tab_topframe = topframe; @@ -820,7 +820,7 @@ static int makeopens(FILE *fd, char *dirnow) // Restore the tab-local working directory if specified // Do this before the windows, so that the window-local directory can // override the tab-local directory. - if ((ssop_flags & SSOP_CURDIR) && tp->tp_localdir != NULL) { + if ((ssop_flags & kOptSsopFlagCurdir) && tp->tp_localdir != NULL) { if (fputs("tcd ", fd) < 0 || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL || put_eol(fd) == FAIL) { @@ -862,12 +862,12 @@ static int makeopens(FILE *fd, char *dirnow) // Don't continue in another tab page when doing only the current one // or when at the last tab page. - if (!(ssop_flags & SSOP_TABPAGES)) { + if (!(ssop_flags & kOptSsopFlagTabpages)) { break; } } - if (ssop_flags & SSOP_TABPAGES) { + if (ssop_flags & kOptSsopFlagTabpages) { if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { return FAIL; } @@ -894,7 +894,7 @@ static int makeopens(FILE *fd, char *dirnow) } // Restore 'shortmess'. - if (ssop_flags & SSOP_OPTIONS) { + if (ssop_flags & kOptSsopFlagOptions) { if (fprintf(fd, "set shortmess=%s\n", p_shm) < 0) { return FAIL; } @@ -937,8 +937,8 @@ void ex_loadview(exarg_T *eap) /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". /// /// Legacy 'sessionoptions'/'viewoptions' flags are always enabled: -/// - SSOP_UNIX: line-endings are LF -/// - SSOP_SLASH: filenames are written with "/" slash +/// - kOptSsopFlagUnix: line-endings are LF +/// - kOptSsopFlagSlash: filenames are written with "/" slash void ex_mkrc(exarg_T *eap) { bool view_session = false; // :mkview, :mksession @@ -1002,10 +1002,10 @@ void ex_mkrc(exarg_T *eap) } if (!view_session || (eap->cmdidx == CMD_mksession - && (*flagp & SSOP_OPTIONS))) { + && (*flagp & kOptSsopFlagOptions))) { int flags = OPT_GLOBAL; - if (eap->cmdidx == CMD_mksession && (*flagp & SSOP_SKIP_RTP)) { + if (eap->cmdidx == CMD_mksession && (*flagp & kOptSsopFlagSkiprtp)) { flags |= OPT_SKIPRTP; } failed |= (makemap(fd, NULL) == FAIL @@ -1028,12 +1028,12 @@ void ex_mkrc(exarg_T *eap) || os_chdir(dirnow) != 0) { *dirnow = NUL; } - if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { + if (*dirnow != NUL && (ssop_flags & kOptSsopFlagSesdir)) { if (vim_chdirfile(fname, kCdCauseOther) == OK) { shorten_fnames(true); } } else if (*dirnow != NUL - && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { + && (ssop_flags & kOptSsopFlagCurdir) && globaldir != NULL) { if (os_chdir(globaldir) == 0) { shorten_fnames(true); } @@ -1042,8 +1042,8 @@ void ex_mkrc(exarg_T *eap) failed |= (makeopens(fd, dirnow) == FAIL); // restore original dir - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != + if (*dirnow != NUL && ((ssop_flags & kOptSsopFlagSesdir) + || ((ssop_flags & kOptSsopFlagCurdir) && globaldir != NULL))) { if (os_chdir(dirnow) != 0) { emsg(_(e_prev_dir)); diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 02f3ac3257..779b31e7a0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -1,10 +1,12 @@ local options_file = arg[1] local options_enum_file = arg[2] local options_map_file = arg[3] +local option_vars_file = arg[4] local opt_fd = assert(io.open(options_file, 'w')) local opt_enum_fd = assert(io.open(options_enum_file, 'w')) local opt_map_fd = assert(io.open(options_map_file, 'w')) +local opt_vars_fd = assert(io.open(option_vars_file, 'w')) local w = function(s) if s:match('^ %.') then @@ -24,6 +26,10 @@ local function map_w(s) opt_map_fd:write(s .. '\n') end +local function vars_w(s) + opt_vars_fd:write(s .. '\n') +end + --- @module 'nvim.options' local options = require('options') local options_meta = options.options @@ -138,6 +144,92 @@ map_w('static ' .. hashfun) opt_map_fd:close() +vars_w('// IWYU pragma: private, include "nvim/option_vars.h"') + +-- Generate enums for option flags. +for _, option in ipairs(options_meta) do + if option.flags and (type(option.flags) == 'table' or option.values) then + vars_w('') + vars_w('typedef enum {') + + local opt_name = lowercase_to_titlecase(option.abbreviation or option.full_name) + --- @type table + local enum_values + + if type(option.flags) == 'table' then + enum_values = option.flags --[[ @as table ]] + else + enum_values = {} + for i, flag_name in ipairs(option.values) do + assert(type(flag_name) == 'string') + enum_values[flag_name] = math.pow(2, i - 1) + end + end + + -- Sort the keys by the flag value so that the enum can be generated in order. + --- @type string[] + local flag_names = vim.tbl_keys(enum_values) + table.sort(flag_names, function(a, b) + return enum_values[a] < enum_values[b] + end) + + for _, flag_name in pairs(flag_names) do + vars_w( + (' kOpt%sFlag%s = 0x%02x,'):format( + opt_name, + lowercase_to_titlecase(flag_name), + enum_values[flag_name] + ) + ) + end + + vars_w(('} Opt%sFlags;'):format(opt_name)) + end +end + +-- Generate valid values for each option. +for _, option in ipairs(options_meta) do + --- @type function + local preorder_traversal + --- @param prefix string + --- @param values vim.option_valid_values + preorder_traversal = function(prefix, values) + vars_w('') + vars_w( + ('EXTERN const char *(%s_values[%s]) INIT( = {'):format(prefix, #vim.tbl_keys(values) + 1) + ) + + --- @type [string,vim.option_valid_values][] + local children = {} + + for _, value in ipairs(values) do + if type(value) == 'string' then + vars_w((' "%s",'):format(value)) + else + assert(type(value) == 'table' and type(value[1]) == 'string' and type(value[2]) == 'table') + + vars_w((' "%s",'):format(value[1])) + table.insert(children, value) + end + end + + vars_w(' NULL') + vars_w('});') + + for _, value in pairs(children) do + -- Remove trailing colon from the added prefix to prevent syntax errors. + preorder_traversal(prefix .. '_' .. value[1]:gsub(':$', ''), value[2]) + end + end + + -- Since option values can be nested, we need to do preorder traversal to generate the values. + if option.values then + preorder_traversal(('opt_%s'):format(option.abbreviation or option.full_name), option.values) + end +end + +opt_vars_fd:close() + local redraw_flags = { ui_option = 'kOptFlagUIOption', tabline = 'kOptFlagRedrTabl', diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 82abd2888a..bca224c7d1 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -471,7 +471,7 @@ void beep_flush(void) { if (emsg_silent == 0) { flush_buffers(FLUSH_MINIMAL); - vim_beep(BO_ERROR); + vim_beep(kOptBoFlagError); } } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index acb336c725..e863cb3476 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -383,7 +383,7 @@ void grid_line_start(ScreenGrid *grid, int row) assert((size_t)grid_line_maxcol <= linebuf_size); - if (rdb_flags & RDB_INVALID) { + if (rdb_flags & kOptRdbFlagInvalid) { // Current batch must not depend on previous contents of linebuf_char. // Set invalid values which will cause assertion failures later if they are used. memset(linebuf_char, 0xFF, sizeof(schar_T) * linebuf_size); @@ -602,7 +602,7 @@ void grid_line_flush(void) void grid_line_flush_if_valid_row(void) { if (grid_line_row < 0 || grid_line_row >= grid_line_grid->rows) { - if (rdb_flags & RDB_INVALID) { + if (rdb_flags & kOptRdbFlagInvalid) { abort(); } else { grid_line_grid = NULL; @@ -639,7 +639,7 @@ static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int || (cols > 1 && linebuf_char[col + 1] == 0 && linebuf_char[col + 1] != grid->chars[off_to + 1])) || exmode_active // TODO(bfredl): what in the actual fuck - || rdb_flags & RDB_NODELTA)); + || rdb_flags & kOptRdbFlagNodelta)); } /// Move one buffered line to the window grid, but only the characters that @@ -784,7 +784,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol size_t off = off_to + (size_t)col; if (grid->chars[off] != schar_from_ascii(' ') || grid->attrs[off] != bg_attr - || rdb_flags & RDB_NODELTA) { + || rdb_flags & kOptRdbFlagNodelta) { grid->chars[off] = schar_from_ascii(' '); grid->attrs[off] = bg_attr; if (clear_dirty_start == -1) { diff --git a/src/nvim/indent.c b/src/nvim/indent.c index e487728901..7da61091d4 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -793,7 +793,7 @@ bool briopt_check(char *briopt, win_T *wp) } while (*p != NUL) { - // Note: Keep this in sync with p_briopt_values + // Note: Keep this in sync with opt_briopt_values. if (strncmp(p, "shift:", 6) == 0 && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { p += 6; @@ -872,7 +872,7 @@ int get_breakindent_win(win_T *wp, char *line) || prev_tick != buf_get_changedtick(wp->w_buffer) || prev_listopt != wp->w_briopt_list || prev_no_ts != no_ts - || prev_dy_uhex != (dy_flags & DY_UHEX) + || prev_dy_uhex != (dy_flags & kOptDyFlagUhex) || prev_flp == NULL || strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0 || prev_line == NULL || strcmp(prev_line, line) != 0) { @@ -893,7 +893,7 @@ int get_breakindent_win(win_T *wp, char *line) prev_listopt = wp->w_briopt_list; prev_list = 0; prev_no_ts = no_ts; - prev_dy_uhex = (dy_flags & DY_UHEX); + prev_dy_uhex = (dy_flags & kOptDyFlagUhex); xfree(prev_flp); prev_flp = xstrdup(get_flp_value(wp->w_buffer)); // add additional indent for numbered lists diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index bd7ee55722..d3517667fb 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -475,7 +475,7 @@ bool check_compl_option(bool dict_opt) msg((dict_opt ? _("'dictionary' option is empty") : _("'thesaurus' option is empty")), HLF_E); if (emsg_silent == 0 && !in_assert_fails) { - vim_beep(BO_COMPL); + vim_beep(kOptBoFlagComplete); setcursor(); ui_flush(); os_delay(2004, false); @@ -1069,7 +1069,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return (get_cot_flags() & COT_ANY_MENU) != 0; + return (get_cot_flags() & (kOptCotFlagMenu | kOptCotFlagMenuone)) != 0; } /// Check that there are two or more matches to be shown in the popup menu. @@ -1088,7 +1088,7 @@ static bool pum_enough_matches(void) comp = comp->cp_next; } while (!is_first_match(comp)); - if (get_cot_flags() & COT_MENUONE) { + if (get_cot_flags() & kOptCotFlagMenuone) { return i >= 1; } return i >= 2; @@ -1171,8 +1171,8 @@ static int ins_compl_build_pum(void) const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0; int max_fuzzy_score = 0; unsigned cur_cot_flags = get_cot_flags(); - bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; - bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; + bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; + bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; do { // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, @@ -2236,7 +2236,7 @@ bool ins_compl_prep(int c) // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = (get_cot_flags() & COT_LONGEST) != 0; + compl_get_longest = (get_cot_flags() & kOptCotFlagLongest) != 0; compl_used_match = true; } @@ -2682,9 +2682,9 @@ static void set_completion(colnr_T startcol, list_T *list) { int flags = CP_ORIGINAL_TEXT; unsigned cur_cot_flags = get_cot_flags(); - bool compl_longest = (cur_cot_flags & COT_LONGEST) != 0; - bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; - bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; + bool compl_longest = (cur_cot_flags & kOptCotFlagLongest) != 0; + bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0; + bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; // If already doing completions stop it. if (ctrl_x_mode_not_default()) { @@ -3731,8 +3731,8 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a bool found_end = false; compl_T *found_compl = NULL; unsigned cur_cot_flags = get_cot_flags(); - bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; - bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; + bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; + bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; while (--todo >= 0) { if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { @@ -3836,8 +3836,8 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match const bool started = compl_started; buf_T *const orig_curbuf = curbuf; unsigned cur_cot_flags = get_cot_flags(); - bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; - bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; + bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0; + bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; // When user complete function return -1 for findstart which is next // time of 'always', compl_shown_match become NULL. @@ -3976,7 +3976,7 @@ void ins_compl_check_keys(int frequency, bool in_compl_func) } } } - if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT)) { + if (compl_pending != 0 && !got_int && !(cot_flags & kOptCotFlagNoinsert)) { int todo = compl_pending > 0 ? compl_pending : -compl_pending; compl_pending = 0; diff --git a/src/nvim/main.c b/src/nvim/main.c index dc4969759d..17990a6735 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -628,7 +628,7 @@ int main(int argc, char **argv) } // WORKAROUND(mhi): #3023 - if (cb_flags & CB_UNNAMEDMASK) { + if (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus)) { eval_has_provider("clipboard", false); } diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 3dbcbbd47b..f1de557f50 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -234,7 +234,7 @@ void setpcmark(void) curwin->w_pcmark.lnum = 1; } - if (jop_flags & JOP_STACK) { + if (jop_flags & kOptJopFlagStack) { // jumpoptions=stack: if we're somewhere in the middle of the jumplist // discard everything after the current index. if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) { @@ -1473,7 +1473,7 @@ void cleanup_jumplist(win_T *wp, bool loadfiles) mustfree = false; } else if (i > from + 1) { // non-adjacent duplicate // jumpoptions=stack: remove duplicates only when adjacent. - mustfree = !(jop_flags & JOP_STACK); + mustfree = !(jop_flags & kOptJopFlagStack); } else { // adjacent duplicate mustfree = true; } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index b5a8588edd..3e35cdaa15 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1400,11 +1400,11 @@ int utf_fold(int a) int mb_toupper(int a) { // If 'casemap' contains "keepascii" use ASCII style toupper(). - if (a < 128 && (cmp_flags & CMP_KEEPASCII)) { + if (a < 128 && (cmp_flags & kOptCmpFlagKeepascii)) { return TOUPPER_ASC(a); } - if (!(cmp_flags & CMP_INTERNAL)) { + if (!(cmp_flags & kOptCmpFlagInternal)) { return (int)towupper((wint_t)a); } @@ -1426,11 +1426,11 @@ bool mb_islower(int a) int mb_tolower(int a) { // If 'casemap' contains "keepascii" use ASCII style tolower(). - if (a < 128 && (cmp_flags & CMP_KEEPASCII)) { + if (a < 128 && (cmp_flags & kOptCmpFlagKeepascii)) { return TOLOWER_ASC(a); } - if (!(cmp_flags & CMP_INTERNAL)) { + if (!(cmp_flags & kOptCmpFlagInternal)) { return (int)towlower((wint_t)a); } diff --git a/src/nvim/message.c b/src/nvim/message.c index c927a2546c..c758d5d76f 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2277,7 +2277,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse } } while (msg_col & 7); } else if (c == BELL) { // beep (from ":sh") - vim_beep(BO_SH); + vim_beep(kOptBoFlagShell); } } } @@ -2332,7 +2332,7 @@ int msg_scrollsize(void) bool msg_do_throttle(void) { - return msg_use_grid() && !(rdb_flags & RDB_NOTHROTTLE); + return msg_use_grid() && !(rdb_flags & kOptRdbFlagNothrottle); } /// Scroll the screen up one line for displaying the next message line. @@ -2597,7 +2597,7 @@ void show_sb_text(void) // weird, typing a command without output results in one line. msgchunk_T *mp = msg_sb_start(last_msgchunk); if (mp == NULL || mp->sb_prev == NULL) { - vim_beep(BO_MESS); + vim_beep(kOptBoFlagMess); } else { do_more_prompt('G'); wait_return(false); diff --git a/src/nvim/move.c b/src/nvim/move.c index b298592683..dbd86bb0c8 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -170,7 +170,7 @@ static void redraw_for_cursorcolumn(win_T *wp) if (wp->w_p_cuc) { // When 'cursorcolumn' is set need to redraw with UPD_SOME_VALID. redraw_later(wp, UPD_SOME_VALID); - } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { + } else if (wp->w_p_cul && (wp->w_p_culopt_flags & kOptCuloptFlagScreenline)) { // When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID. redraw_later(wp, UPD_VALID); } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 55aa385b33..ba84380529 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1348,7 +1348,7 @@ static void normal_check_folds(NormalState *s) if (hasAnyFolding(curwin) && !char_avail()) { foldCheckClose(); - if (fdo_flags & FDO_ALL) { + if (fdo_flags & kOptFdoFlagAll) { foldOpenCursor(); } } @@ -2310,7 +2310,7 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) return; } - if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagSearch) && KeyTyped && oap->op_type == OP_NOP) { foldOpenCursor(); } // clear any search statistics @@ -3752,7 +3752,7 @@ static void nv_right(cmdarg_T *cap) } } } - if (n != cap->count1 && (fdo_flags & FDO_HOR) && KeyTyped + if (n != cap->count1 && (fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } @@ -3811,7 +3811,7 @@ static void nv_left(cmdarg_T *cap) break; } } - if (n != cap->count1 && (fdo_flags & FDO_HOR) && KeyTyped + if (n != cap->count1 && (fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } @@ -3929,7 +3929,7 @@ static void nv_dollar(cmdarg_T *cap) if (cursor_down(cap->count1 - 1, cap->oap->op_type == OP_NOP) == false) { clearopbeep(cap->oap); - } else if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + } else if ((fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -4016,7 +4016,7 @@ static int normal_search(cmdarg_T *cap, int dir, char *pat, size_t patlen, int o cap->oap->motion_type = kMTLineWise; } curwin->w_cursor.coladd = 0; - if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) { + if (cap->oap->op_type == OP_NOP && (fdo_flags & kOptFdoFlagSearch) && KeyTyped) { foldOpenCursor(); } } @@ -4065,7 +4065,7 @@ static void nv_csearch(cmdarg_T *cap) curwin->w_cursor.coladd = 0; } adjust_for_sel(cap); - if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -4181,7 +4181,7 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos) setpcmark(); curwin->w_cursor = *pos; curwin->w_set_curswant = true; - if ((fdo_flags & FDO_BLOCK) && KeyTyped + if ((fdo_flags & kOptFdoFlagBlock) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } @@ -4261,7 +4261,7 @@ static void nv_brackets(cmdarg_T *cap) if (cap->oap->op_type == OP_NOP) { beginline(BL_WHITE | BL_FIX); } - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagBlock) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -4319,7 +4319,7 @@ static void nv_brackets(cmdarg_T *cap) } curwin->w_set_curswant = true; } - if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) { + if (cap->oap->op_type == OP_NOP && (fdo_flags & kOptFdoFlagSearch) && KeyTyped) { foldOpenCursor(); } } else { @@ -4371,7 +4371,7 @@ static void nv_percent(cmdarg_T *cap) } if (cap->oap->op_type == OP_NOP && lnum != curwin->w_cursor.lnum - && (fdo_flags & FDO_PERCENT) + && (fdo_flags & kOptFdoFlagPercent) && KeyTyped) { foldOpenCursor(); } @@ -4395,7 +4395,7 @@ static void nv_brace(cmdarg_T *cap) // Don't leave the cursor on the NUL past end of line. adjust_cursor(cap->oap); curwin->w_cursor.coladd = 0; - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagBlock) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -4426,7 +4426,7 @@ static void nv_findpar(cmdarg_T *cap) } curwin->w_cursor.coladd = 0; - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagBlock) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -4864,7 +4864,7 @@ static void nv_optrans(cmdarg_T *cap) static void nv_gomark(cmdarg_T *cap) { int name; - MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + MarkMove flags = jop_flags & kOptJopFlagView ? kMarkSetView : 0; // flags for moving to the mark if (cap->oap->op_type != OP_NOP) { // When there is a pending operator, do not restore the view as this is usually unexpected. flags = 0; @@ -4893,7 +4893,7 @@ static void nv_gomark(cmdarg_T *cap) if (cap->oap->op_type == OP_NOP && move_res & kMarkMoveSuccess && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedCursor) - && (fdo_flags & FDO_MARK) + && (fdo_flags & kOptFdoFlagMark) && old_KeyTyped) { foldOpenCursor(); } @@ -4904,7 +4904,7 @@ static void nv_gomark(cmdarg_T *cap) static void nv_pcmark(cmdarg_T *cap) { fmark_T *fm = NULL; - MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + MarkMove flags = jop_flags & kOptJopFlagView ? kMarkSetView : 0; // flags for moving to the mark MarkMoveRes move_res = 0; // Result from moving to the mark const bool old_KeyTyped = KeyTyped; // getting file may reset it. @@ -4943,7 +4943,7 @@ static void nv_pcmark(cmdarg_T *cap) } if (cap->oap->op_type == OP_NOP && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine) - && (fdo_flags & FDO_MARK) + && (fdo_flags & kOptFdoFlagMark) && old_KeyTyped) { foldOpenCursor(); } @@ -5094,7 +5094,7 @@ static void n_start_visual_mode(int c) // Corner case: the 0 position in a tab may change when going into // virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting. // - if (c == Ctrl_V && (get_ve_flags(curwin) & VE_BLOCK) && gchar_cursor() == TAB) { + if (c == Ctrl_V && (get_ve_flags(curwin) & kOptVeFlagBlock) && gchar_cursor() == TAB) { validate_virtcol(curwin); coladvance(curwin, curwin->w_virtcol); } @@ -5915,7 +5915,7 @@ static void nv_bck_word(cmdarg_T *cap) curwin->w_set_curswant = true; if (bck_word(cap->count1, cap->arg, false) == false) { clearopbeep(cap->oap); - } else if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + } else if ((fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -5975,7 +5975,7 @@ static void nv_wordcmd(cmdarg_T *cap) clearopbeep(cap->oap); } else { adjust_for_sel(cap); - if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -5993,7 +5993,7 @@ static void adjust_cursor(oparg_T *oap) if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL && (!VIsual_active || *p_sel == 'o') && !virtual_active(curwin) - && (get_ve_flags(curwin) & VE_ONEMORE) == 0) { + && (get_ve_flags(curwin) & kOptVeFlagOnemore) == 0) { curwin->w_cursor.col--; // prevent cursor from moving on the trail byte mb_adjust_cursor(); @@ -6008,7 +6008,7 @@ static void nv_beginline(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; beginline(cap->arg); - if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagHor) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } ins_at_eol = false; // Don't move cursor past eol (only necessary in a @@ -6097,7 +6097,7 @@ static void nv_goto(cmdarg_T *cap) lnum = MIN(MAX(lnum, 1), curbuf->b_ml.ml_line_count); curwin->w_cursor.lnum = lnum; beginline(BL_SOL | BL_FIX); - if ((fdo_flags & FDO_JUMP) && KeyTyped && cap->oap->op_type == OP_NOP) { + if ((fdo_flags & kOptFdoFlagJump) && KeyTyped && cap->oap->op_type == OP_NOP) { foldOpenCursor(); } } @@ -6167,7 +6167,7 @@ static void nv_esc(cmdarg_T *cap) curwin->w_set_curswant = true; redraw_curbuf_later(UPD_INVERTED); } else if (no_reason) { - vim_beep(BO_ESC); + vim_beep(kOptBoFlagEsc); } clearop(cap->oap); } @@ -6176,7 +6176,7 @@ static void nv_esc(cmdarg_T *cap) void set_cursor_for_append_to_line(void) { curwin->w_set_curswant = true; - if (get_ve_flags(curwin) == VE_ALL) { + if (get_ve_flags(curwin) == kOptVeFlagAll) { const int save_State = State; // Pretend Insert mode here to allow the cursor on the // character past the end of the line @@ -6501,7 +6501,8 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) int regname = cap->oap->regname; bool keep_registers = cap->cmdchar == 'P'; // '+' and '*' could be the same selection - bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK); + bool clipoverwrite = (regname == '+' || regname == '*') + && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus)); if (regname == 0 || regname == '"' || clipoverwrite || ascii_isdigit(regname) || regname == '-') { // The delete might overwrite the register we want to put, save it first diff --git a/src/nvim/ops.c b/src/nvim/ops.c index c100bc36b4..63c78936ba 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2187,7 +2187,7 @@ void op_insert(oparg_T *oap, int count1) if (u_save_cursor() == FAIL) { return; } - curwin->w_ve_flags = VE_ALL; + curwin->w_ve_flags = kOptVeFlagAll; coladvance_force(oap->op_type == OP_APPEND ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { @@ -2873,7 +2873,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } - bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE); + bool ve_allows = (cur_ve_flags == kOptVeFlagAll || cur_ve_flags == kOptVeFlagOnemore); bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum && one_past_line; if (ve_allows || !(eol || eof)) { @@ -3057,7 +3057,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) goto end; } - if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) { + if (cur_ve_flags == kOptVeFlagAll && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { int viscol = getviscol(); OptInt ts = curbuf->b_p_ts; @@ -3086,7 +3086,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) colnr_T endcol2 = 0; if (dir == FORWARD && c != NUL) { - if (cur_ve_flags == VE_ALL) { + if (cur_ve_flags == kOptVeFlagAll) { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2); } else { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); @@ -3100,7 +3100,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) } col += curwin->w_cursor.coladd; - if (cur_ve_flags == VE_ALL + if (cur_ve_flags == kOptVeFlagAll && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { if (dir == FORWARD && c == NUL) { col++; @@ -3560,7 +3560,7 @@ error: // Make sure the cursor is not after the NUL. int len = get_cursor_line_len(); if (curwin->w_cursor.col > len) { - if (cur_ve_flags == VE_ALL) { + if (cur_ve_flags == kOptVeFlagAll) { curwin->w_cursor.coladd = curwin->w_cursor.col - len; } curwin->w_cursor.col = len; @@ -3592,7 +3592,7 @@ void adjust_cursor_eol(void) const bool adj_cursor = (curwin->w_cursor.col > 0 && gchar_cursor() == NUL - && (cur_ve_flags & VE_ONEMORE) == 0 + && (cur_ve_flags & kOptVeFlagOnemore) == 0 && !(restart_edit || (State & MODE_INSERT))); if (!adj_cursor) { return; @@ -3601,7 +3601,7 @@ void adjust_cursor_eol(void) // Put the cursor on the last character in the line. dec_cursor(); - if (cur_ve_flags == VE_ALL) { + if (cur_ve_flags == kOptVeFlagAll) { colnr_T scol, ecol; // Coladd is set to the width of the last character. @@ -6076,7 +6076,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_DELETE: VIsual_reselect = false; // don't reselect now if (empty_region_error) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } else { op_delete(oap); @@ -6092,7 +6092,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_YANK: if (empty_region_error) { if (!gui_yank) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } } else { @@ -6106,7 +6106,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_CHANGE: VIsual_reselect = false; // don't reselect now if (empty_region_error) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } else { // This is a new edit command, not a restart. Need to @@ -6169,7 +6169,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_LOWER: case OP_ROT13: if (empty_region_error) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } else { op_tilde(oap); @@ -6211,7 +6211,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_APPEND: VIsual_reselect = false; // don't reselect now if (empty_region_error) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } else { // This is a new edit command, not a restart. Need to @@ -6246,7 +6246,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_REPLACE: VIsual_reselect = false; // don't reselect now if (empty_region_error) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } else { // Restore linebreak, so that when the user edits it looks as before. @@ -6284,7 +6284,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_NR_ADD: case OP_NR_SUB: if (empty_region_error) { - vim_beep(BO_OPER); + vim_beep(kOptBoFlagOperator); CancelRedo(); } else { VIsual_active = true; @@ -6344,7 +6344,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) yankreg_T *target = NULL; bool explicit_cb_reg = (*name == '*' || *name == '+'); - bool implicit_cb_reg = (*name == NUL) && (cb_flags & CB_UNNAMEDMASK); + bool implicit_cb_reg = (*name == NUL) && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus)); if (!explicit_cb_reg && !implicit_cb_reg) { goto end; } @@ -6363,7 +6363,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) if (explicit_cb_reg) { target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER]; - if (writing && (cb_flags & (*name == '*' ? CB_UNNAMED : CB_UNNAMEDPLUS))) { + if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) { clipboard_needs_update = false; } goto end; @@ -6377,8 +6377,8 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) goto end; } - if (cb_flags & CB_UNNAMEDPLUS) { - *name = (cb_flags & CB_UNNAMED && writing) ? '"' : '+'; + if (cb_flags & kOptCbFlagUnnamedplus) { + *name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+'; target = &y_regs[PLUS_REGISTER]; } else { *name = '*'; diff --git a/src/nvim/option.c b/src/nvim/option.c index d3cbe9f056..669dac9773 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -6013,19 +6013,19 @@ int fill_culopt_flags(char *val, win_T *wp) p = val; } while (*p != NUL) { - // Note: Keep this in sync with p_culopt_values. + // Note: Keep this in sync with opt_culopt_values. if (strncmp(p, "line", 4) == 0) { p += 4; - culopt_flags_new |= CULOPT_LINE; + culopt_flags_new |= kOptCuloptFlagLine; } else if (strncmp(p, "both", 4) == 0) { p += 4; - culopt_flags_new |= CULOPT_LINE | CULOPT_NBR; + culopt_flags_new |= kOptCuloptFlagLine | kOptCuloptFlagNumber; } else if (strncmp(p, "number", 6) == 0) { p += 6; - culopt_flags_new |= CULOPT_NBR; + culopt_flags_new |= kOptCuloptFlagNumber; } else if (strncmp(p, "screenline", 10) == 0) { p += 10; - culopt_flags_new |= CULOPT_SCRLINE; + culopt_flags_new |= kOptCuloptFlagScreenline; } if (*p != ',' && *p != NUL) { @@ -6037,7 +6037,7 @@ int fill_culopt_flags(char *val, win_T *wp) } // Can't have both "line" and "screenline". - if ((culopt_flags_new & CULOPT_LINE) && (culopt_flags_new & CULOPT_SCRLINE)) { + if ((culopt_flags_new & kOptCuloptFlagLine) && (culopt_flags_new & kOptCuloptFlagScreenline)) { return FAIL; } wp->w_p_culopt_flags = culopt_flags_new; @@ -6147,7 +6147,8 @@ char *get_flp_value(buf_T *buf) /// Get the local or global value of 'virtualedit' flags. unsigned get_ve_flags(win_T *wp) { - return (wp->w_ve_flags ? wp->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); + return (wp->w_ve_flags ? wp->w_ve_flags : ve_flags) + & ~(unsigned)(kOptVeFlagNone | kOptVeFlagNoneU); } /// Get the local or global value of 'showbreak'. diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index e0a972f06c..3bb2035e7c 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -5,6 +5,10 @@ #include "nvim/sign_defs.h" #include "nvim/types_defs.h" +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "option_vars.generated.h" // NOLINT(build/include_defs) +#endif + // option_vars.h: definition of global variables for settable options #define HIGHLIGHT_INIT \ @@ -264,12 +268,6 @@ enum { STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ 0, }) -// flags used for parsed 'wildmode' -#define WIM_FULL 0x01 -#define WIM_LONGEST 0x02 -#define WIM_LIST 0x04 -#define WIM_BUFLASTUSED 0x08 - // arguments for can_bs() // each defined char should be unique over all values // except for BS_START, that intentionally also matches BS_NOSTOP @@ -280,11 +278,6 @@ enum { #define BS_START 's' // "Start" #define BS_NOSTOP 'p' // "nostoP -// flags for the 'culopt' option -#define CULOPT_LINE 0x01 // Highlight complete line -#define CULOPT_SCRLINE 0x02 // Highlight screen line -#define CULOPT_NBR 0x04 // Highlight Number column - #define LISPWORD_VALUE \ "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object" @@ -319,47 +312,17 @@ EXTERN char *p_bg; ///< 'background' EXTERN int p_bk; ///< 'backup' EXTERN char *p_bkc; ///< 'backupcopy' EXTERN unsigned bkc_flags; ///< flags from 'backupcopy' -#define BKC_YES 0x001 -#define BKC_AUTO 0x002 -#define BKC_NO 0x004 -#define BKC_BREAKSYMLINK 0x008 -#define BKC_BREAKHARDLINK 0x010 EXTERN char *p_bdir; ///< 'backupdir' EXTERN char *p_bex; ///< 'backupext' EXTERN char *p_bo; ///< 'belloff' EXTERN char breakat_flags[256]; ///< which characters are in 'breakat' EXTERN unsigned bo_flags; - -// values for the 'belloff' option -#define BO_ALL 0x0001 -#define BO_BS 0x0002 -#define BO_CRSR 0x0004 -#define BO_COMPL 0x0008 -#define BO_COPY 0x0010 -#define BO_CTRLG 0x0020 -#define BO_ERROR 0x0040 -#define BO_ESC 0x0080 -#define BO_EX 0x0100 -#define BO_HANGUL 0x0200 -#define BO_IM 0x0400 -#define BO_LANG 0x0800 -#define BO_MESS 0x1000 -#define BO_MATCH 0x2000 -#define BO_OPER 0x4000 -#define BO_REG 0x8000 -#define BO_SH 0x10000 -#define BO_SPELL 0x20000 -#define BO_TERM 0x40000 -#define BO_WILD 0x80000 - EXTERN char *p_bsk; ///< 'backupskip' EXTERN char *p_breakat; ///< 'breakat' EXTERN char *p_bh; ///< 'bufhidden' EXTERN char *p_bt; ///< 'buftype' EXTERN char *p_cmp; ///< 'casemap' EXTERN unsigned cmp_flags; -#define CMP_INTERNAL 0x001 -#define CMP_KEEPASCII 0x002 EXTERN char *p_enc; ///< 'encoding' EXTERN int p_deco; ///< 'delcombine' EXTERN char *p_ccv; ///< 'charconvert' @@ -367,9 +330,6 @@ EXTERN char *p_cino; ///< 'cinoptions' EXTERN char *p_cedit; ///< 'cedit' EXTERN char *p_cb; ///< 'clipboard' EXTERN unsigned cb_flags; -#define CB_UNNAMED 0x001 -#define CB_UNNAMEDPLUS 0x002 -#define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS) EXTERN OptInt p_cwh; ///< 'cmdwinheight' EXTERN OptInt p_ch; ///< 'cmdheight' EXTERN char *p_cms; ///< 'commentstring' @@ -380,18 +340,6 @@ EXTERN char *p_cia; ///< 'completeitemalign' EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign' EXTERN char *p_cot; ///< 'completeopt' EXTERN unsigned cot_flags; ///< flags from 'completeopt' -// Keep in sync with p_cot_values in optionstr.c -#define COT_MENU 0x001 -#define COT_MENUONE 0x002 -#define COT_ANY_MENU 0x003 // combination of menu flags -#define COT_LONGEST 0x004 // false: insert full match, - // true: insert longest prefix -#define COT_PREVIEW 0x008 -#define COT_POPUP 0x010 -#define COT_ANY_PREVIEW 0x018 // combination of preview flags -#define COT_NOINSERT 0x020 // false: select & insert, true: noinsert -#define COT_NOSELECT 0x040 // false: select & insert, true: noselect -#define COT_FUZZY 0x080 // true: fuzzy match enabled #ifdef BACKSLASH_IN_FILENAME EXTERN char *p_csl; ///< 'completeslash' #endif @@ -410,11 +358,6 @@ EXTERN int p_dg; ///< 'digraph' EXTERN char *p_dir; ///< 'directory' EXTERN char *p_dy; ///< 'display' EXTERN unsigned dy_flags; -#define DY_LASTLINE 0x001 -#define DY_TRUNCATE 0x002 -#define DY_UHEX 0x004 -// legacy flag, not used -#define DY_MSGSEP 0x008 EXTERN char *p_ead; ///< 'eadirection' EXTERN int p_emoji; ///< 'emoji' EXTERN int p_ea; ///< 'equalalways' @@ -442,17 +385,6 @@ EXTERN char *p_fcl; ///< 'foldclose' EXTERN OptInt p_fdls; ///< 'foldlevelstart' EXTERN char *p_fdo; ///< 'foldopen' EXTERN unsigned fdo_flags; -#define FDO_ALL 0x001 -#define FDO_BLOCK 0x002 -#define FDO_HOR 0x004 -#define FDO_MARK 0x008 -#define FDO_PERCENT 0x010 -#define FDO_QUICKFIX 0x020 -#define FDO_SEARCH 0x040 -#define FDO_TAG 0x080 -#define FDO_INSERT 0x100 -#define FDO_UNDO 0x200 -#define FDO_JUMP 0x400 EXTERN char *p_fex; ///< 'formatexpr' EXTERN char *p_flp; ///< 'formatlistpat' EXTERN char *p_fo; ///< 'formatoptions' @@ -488,9 +420,6 @@ EXTERN char *p_isp; ///< 'isprint' EXTERN int p_js; ///< 'joinspaces' EXTERN char *p_jop; ///< 'jumpooptions' EXTERN unsigned jop_flags; -#define JOP_STACK 0x01 -#define JOP_VIEW 0x02 -#define JOP_CLEAN 0x04 EXTERN char *p_keymap; ///< 'keymap' EXTERN char *p_kp; ///< 'keywordprg' EXTERN char *p_km; ///< 'keymodel' @@ -506,7 +435,6 @@ EXTERN char *p_lispwords; ///< 'lispwords' EXTERN OptInt p_ls; ///< 'laststatus' EXTERN OptInt p_stal; ///< 'showtabline' EXTERN char *p_lcs; ///< 'listchars' - EXTERN int p_lz; ///< 'lazyredraw' EXTERN int p_lpl; ///< 'loadplugins' EXTERN int p_magic; ///< 'magic' @@ -551,14 +479,6 @@ EXTERN char *p_qe; ///< 'quoteescape' EXTERN int p_ro; ///< 'readonly' EXTERN char *p_rdb; ///< 'redrawdebug' EXTERN unsigned rdb_flags; -#define RDB_COMPOSITOR 0x001 -#define RDB_NOTHROTTLE 0x002 -#define RDB_INVALID 0x004 -#define RDB_NODELTA 0x008 -#define RDB_LINE 0x010 -#define RDB_FLUSH 0x020 -#define RDB_INTERSECT 0x040 - EXTERN OptInt p_rdt; ///< 'redrawtime' EXTERN OptInt p_re; ///< 'regexpengine' EXTERN OptInt p_report; ///< 'report' @@ -580,26 +500,6 @@ EXTERN char *p_sel; ///< 'selection' EXTERN char *p_slm; ///< 'selectmode' EXTERN char *p_ssop; ///< 'sessionoptions' EXTERN unsigned ssop_flags; - -#define SSOP_BUFFERS 0x001 -#define SSOP_WINPOS 0x002 -#define SSOP_RESIZE 0x004 -#define SSOP_WINSIZE 0x008 -#define SSOP_LOCALOPTIONS 0x010 -#define SSOP_OPTIONS 0x020 -#define SSOP_HELP 0x040 -#define SSOP_BLANK 0x080 -#define SSOP_GLOBALS 0x100 -#define SSOP_SLASH 0x200 // Deprecated, always set. -#define SSOP_UNIX 0x400 // Deprecated, always set. -#define SSOP_SESDIR 0x800 -#define SSOP_CURDIR 0x1000 -#define SSOP_FOLDS 0x2000 -#define SSOP_CURSOR 0x4000 -#define SSOP_TABPAGES 0x8000 -#define SSOP_TERMINAL 0x10000 -#define SSOP_SKIP_RTP 0x20000 - EXTERN char *p_sh; ///< 'shell' EXTERN char *p_shcf; ///< 'shellcmdflag' EXTERN char *p_sp; ///< 'shellpipe' @@ -636,13 +536,6 @@ EXTERN OptInt p_tpm; ///< 'tabpagemax' EXTERN char *p_tal; ///< 'tabline' EXTERN char *p_tpf; ///< 'termpastefilter' EXTERN unsigned tpf_flags; ///< flags from 'termpastefilter' -#define TPF_BS 0x001 -#define TPF_HT 0x002 -#define TPF_FF 0x004 -#define TPF_ESC 0x008 -#define TPF_DEL 0x010 -#define TPF_C0 0x020 -#define TPF_C1 0x040 EXTERN char *p_tfu; ///< 'tagfunc' EXTERN char *p_spc; ///< 'spellcapcheck' EXTERN char *p_spf; ///< 'spellfile' @@ -655,28 +548,14 @@ EXTERN int p_sol; ///< 'startofline' EXTERN char *p_su; ///< 'suffixes' EXTERN char *p_swb; ///< 'switchbuf' EXTERN unsigned swb_flags; -// Keep in sync with p_swb_values in optionstr.c -#define SWB_USEOPEN 0x001 -#define SWB_USETAB 0x002 -#define SWB_SPLIT 0x004 -#define SWB_NEWTAB 0x008 -#define SWB_VSPLIT 0x010 -#define SWB_USELAST 0x020 EXTERN char *p_spk; ///< 'splitkeep' EXTERN char *p_syn; ///< 'syntax' EXTERN char *p_tcl; ///< 'tabclose' EXTERN unsigned tcl_flags; ///< flags from 'tabclose' -#define TCL_LEFT 0x001 -#define TCL_USELAST 0x002 EXTERN OptInt p_ts; ///< 'tabstop' EXTERN int p_tbs; ///< 'tagbsearch' EXTERN char *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' -#define TC_FOLLOWIC 0x01 -#define TC_IGNORE 0x02 -#define TC_MATCH 0x04 -#define TC_FOLLOWSCS 0x08 -#define TC_SMART 0x10 EXTERN OptInt p_tl; ///< 'taglength' EXTERN int p_tr; ///< 'tagrelative' EXTERN char *p_tags; ///< 'tags' @@ -707,16 +586,10 @@ EXTERN char *p_vsts; ///< 'varsofttabstop' EXTERN char *p_vts; ///< 'vartabstop' EXTERN char *p_vdir; ///< 'viewdir' EXTERN char *p_vop; ///< 'viewoptions' -EXTERN unsigned vop_flags; ///< uses SSOP_ flags +EXTERN unsigned vop_flags; ///< uses OptSsopFlags EXTERN int p_vb; ///< 'visualbell' EXTERN char *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; -#define VE_BLOCK 5U // includes "all" -#define VE_INSERT 6U // includes "all" -#define VE_ALL 4U -#define VE_ONEMORE 8U -#define VE_NONE 16U // "none" -#define VE_NONEU 32U // "NONE" EXTERN OptInt p_verbose; ///< 'verbose' #ifdef IN_OPTION_C char *p_vfile = empty_string_option; ///< used before options are initialized @@ -726,9 +599,6 @@ extern char *p_vfile; ///< 'verbosefile' EXTERN int p_warn; ///< 'warn' EXTERN char *p_wop; ///< 'wildoptions' EXTERN unsigned wop_flags; -#define WOP_FUZZY 0x01 -#define WOP_TAGFILE 0x02 -#define WOP_PUM 0x04 EXTERN OptInt p_window; ///< 'window' EXTERN char *p_wak; ///< 'winaltkeys' EXTERN char *p_wig; ///< 'wildignore' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index d61cba892b..2d712ee101 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -14,6 +14,8 @@ --- @field deny_duplicates? boolean --- @field enable_if? string --- @field defaults? vim.option_defaults +--- @field values? vim.option_valid_values +--- @field flags? true|table --- @field secure? true --- @field noglob? true --- @field normal_fname_chars? true @@ -43,6 +45,7 @@ --- @alias vim.option_scope 'global'|'buf'|'win' --- @alias vim.option_type 'boolean'|'number'|'string' --- @alias vim.option_value boolean|number|string +--- @alias vim.option_valid_values (string|[string,vim.option_valid_values])[] --- @alias vim.option_redraw --- |'statuslines' @@ -112,6 +115,7 @@ return { abbreviation = 'ambw', cb = 'did_set_ambiwidth', defaults = { if_true = 'single' }, + values = { 'single', 'double' }, desc = [=[ Tells Vim what to do with characters with East Asian Width Class Ambiguous (such as Euro, Registered Sign, Copyright Sign, Greek @@ -306,6 +310,7 @@ return { abbreviation = 'bg', cb = 'did_set_background', defaults = { if_true = 'dark' }, + values = { 'light', 'dark' }, desc = [=[ When set to "dark" or "light", adjusts the default color groups for that background type. The |TUI| or other UI sets this on startup @@ -341,6 +346,7 @@ return { abbreviation = 'bs', cb = 'did_set_backspace', defaults = { if_true = 'indent,eol,start' }, + values = { 'indent', 'eol', 'start', 'nostop' }, deny_duplicates = true, desc = [=[ Influences the working of , , CTRL-W and CTRL-U in Insert @@ -390,6 +396,8 @@ return { abbreviation = 'bkc', cb = 'did_set_backupcopy', defaults = { condition = 'UNIX', if_false = 'auto', if_true = 'auto' }, + values = { 'yes', 'auto', 'no', 'breaksymlink', 'breakhardlink' }, + flags = true, deny_duplicates = true, desc = [=[ When writing a file and a backup is made, this option tells how it's @@ -587,6 +595,29 @@ return { abbreviation = 'bo', cb = 'did_set_belloff', defaults = { if_true = 'all' }, + values = { + 'all', + 'backspace', + 'cursor', + 'complete', + 'copy', + 'ctrlg', + 'error', + 'esc', + 'ex', + 'hangul', + 'insertmode', + 'lang', + 'mess', + 'showmatch', + 'operator', + 'register', + 'shell', + 'spell', + 'term', + 'wildmode', + }, + flags = true, deny_duplicates = true, desc = [=[ Specifies for which events the bell will not be rung. It is a comma- @@ -708,6 +739,7 @@ return { if_true = ' \t!@*-+;:,./?', doc = '" ^I!@*-+;:,./?"', }, + flags = true, desc = [=[ This option lets you choose which characters might cause a line break if 'linebreak' is on. Only works for ASCII characters. @@ -738,6 +770,8 @@ return { abbreviation = 'briopt', cb = 'did_set_breakindentopt', defaults = { if_true = '' }, + -- Keep this in sync with briopt_check(). + values = { 'shift:', 'min:', 'sbr', 'list:', 'column:' }, deny_duplicates = true, desc = [=[ Settings for 'breakindent'. It can consist of the following optional @@ -800,6 +834,7 @@ return { abbreviation = 'bh', cb = 'did_set_bufhidden', defaults = { if_true = '' }, + values = { 'hide', 'unload', 'delete', 'wipe' }, desc = [=[ This option specifies what happens when a buffer is no longer displayed in a window: @@ -852,6 +887,15 @@ return { abbreviation = 'bt', cb = 'did_set_buftype', defaults = { if_true = '' }, + values = { + 'nofile', + 'nowrite', + 'quickfix', + 'help', + 'acwrite', + 'terminal', + 'prompt', + }, desc = [=[ The value of this option specifies the type of a buffer: normal buffer @@ -911,6 +955,8 @@ return { abbreviation = 'cmp', cb = 'did_set_casemap', defaults = { if_true = 'internal,keepascii' }, + values = { 'internal', 'keepascii' }, + flags = true, deny_duplicates = true, desc = [=[ Specifies details about changing the case of letters. It may contain @@ -1175,6 +1221,8 @@ return { abbreviation = 'cb', cb = 'did_set_clipboard', defaults = { if_true = '' }, + values = { 'unnamed', 'unnamedplus' }, + flags = true, desc = [=[ This option is a list of comma-separated names. These names are recognized: @@ -1348,6 +1396,7 @@ return { abbreviation = 'cpt', cb = 'did_set_complete', defaults = { if_true = '.,w,b,u,t' }, + values = { '.', 'w', 'b', 'u', 'k', 'kspell', 's', 'i', 'd', ']', 't', 'U', 'f' }, deny_duplicates = true, desc = [=[ This option specifies how keyword completion |ins-completion| works @@ -1418,6 +1467,7 @@ return { abbreviation = 'cia', cb = 'did_set_completeitemalign', defaults = { if_true = 'abbr,kind,menu' }, + flags = true, deny_duplicates = true, desc = [=[ A comma-separated list of |complete-items| that controls the alignment @@ -1438,6 +1488,17 @@ return { abbreviation = 'cot', cb = 'did_set_completeopt', defaults = { if_true = 'menu,preview' }, + values = { + 'menu', + 'menuone', + 'longest', + 'preview', + 'popup', + 'noinsert', + 'noselect', + 'fuzzy', + }, + flags = true, deny_duplicates = true, desc = [=[ A comma-separated list of options for Insert mode completion @@ -1493,6 +1554,7 @@ return { abbreviation = 'csl', cb = 'did_set_completeslash', defaults = { if_true = '' }, + values = { 'slash', 'backslash' }, desc = [=[ only modifiable in MS-Windows When this option is set it overrules 'shellslash' for completion: @@ -1908,6 +1970,13 @@ return { abbreviation = 'culopt', cb = 'did_set_cursorlineopt', defaults = { if_true = 'both' }, + -- Keep this in sync with fill_culopt_flags(). + values = { 'line', 'screenline', 'number', 'both' }, + flags = { + Line = 0x01, + Screenline = 0x02, + Number = 0x04, + }, deny_duplicates = true, desc = [=[ Comma-separated list of settings for how 'cursorline' is displayed. @@ -1935,6 +2004,7 @@ return { { cb = 'did_set_debug', defaults = { if_true = '' }, + values = { 'msg', 'throw', 'beep' }, desc = [=[ These values can be used: msg Error messages that would otherwise be omitted will be given @@ -2077,6 +2147,26 @@ return { abbreviation = 'dip', cb = 'did_set_diffopt', defaults = { if_true = 'internal,filler,closeoff' }, + -- Keep this in sync with diffopt_changed(). + values = { + 'filler', + 'context:', + 'iblank', + 'icase', + 'iwhite', + 'iwhiteall', + 'iwhiteeol', + 'horizontal', + 'vertical', + 'closeoff', + 'hiddenoff', + 'foldcolumn:', + 'followwrap', + 'internal', + 'indent-heuristic', + 'linematch:', + { 'algorithm:', { 'myers', 'minimal', 'patience', 'histogram' } }, + }, deny_duplicates = true, desc = [=[ Option settings for diff mode. It can consist of the following items. @@ -2269,6 +2359,8 @@ return { abbreviation = 'dy', cb = 'did_set_display', defaults = { if_true = 'lastline' }, + values = { 'lastline', 'truncate', 'uhex', 'msgsep' }, + flags = true, deny_duplicates = true, desc = [=[ Change the way text is displayed. This is a comma-separated list of @@ -2302,6 +2394,7 @@ return { abbreviation = 'ead', cb = 'did_set_eadirection', defaults = { if_true = 'both' }, + values = { 'both', 'ver', 'hor' }, desc = [=[ Tells when the 'equalalways' option applies: ver vertically, width of windows is not affected @@ -2689,6 +2782,7 @@ return { if_false = 'unix', doc = 'Windows: "dos", Unix: "unix"', }, + values = { 'unix', 'dos', 'mac' }, desc = [=[ This gives the of the current buffer, which is used for reading/writing the buffer from/to a file: @@ -2993,6 +3087,7 @@ return { abbreviation = 'fcl', cb = 'did_set_foldclose', defaults = { if_true = '' }, + values = { 'all' }, deny_duplicates = true, desc = [=[ When set to "all", a fold is closed when the cursor isn't in it and @@ -3012,6 +3107,28 @@ return { abbreviation = 'fdc', cb = 'did_set_foldcolumn', defaults = { if_true = '0' }, + values = { + 'auto', + 'auto:1', + 'auto:2', + 'auto:3', + 'auto:4', + 'auto:5', + 'auto:6', + 'auto:7', + 'auto:8', + 'auto:9', + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + }, desc = [=[ When and how to draw the foldcolumn. Valid values are: "auto": resize to the minimum amount of folds to display. @@ -3148,6 +3265,7 @@ return { abbreviation = 'fdm', cb = 'did_set_foldmethod', defaults = { if_true = 'manual' }, + values = { 'manual', 'expr', 'marker', 'indent', 'syntax', 'diff' }, desc = [=[ The kind of folding used for the current window. Possible values: |fold-manual| manual Folds are created manually. @@ -3202,6 +3320,20 @@ return { abbreviation = 'fdo', cb = 'did_set_foldopen', defaults = { if_true = 'block,hor,mark,percent,quickfix,search,tag,undo' }, + values = { + 'all', + 'block', + 'hor', + 'mark', + 'percent', + 'quickfix', + 'search', + 'tag', + 'insert', + 'undo', + 'jump', + }, + flags = true, deny_duplicates = true, desc = [=[ Specifies for which type of commands folds will be opened, if the @@ -4163,6 +4295,7 @@ return { abbreviation = 'icm', cb = 'did_set_inccommand', defaults = { if_true = 'nosplit' }, + values = { 'nosplit', 'split' }, desc = [=[ When nonempty, shows the effects of |:substitute|, |:smagic|, |:snomagic| and user commands with the |:command-preview| flag as you @@ -4568,6 +4701,8 @@ return { abbreviation = 'jop', cb = 'did_set_jumpoptions', defaults = { if_true = 'clean' }, + values = { 'stack', 'view', 'clean' }, + flags = true, deny_duplicates = true, desc = [=[ List of words that change the behavior of the |jumplist|. @@ -4616,6 +4751,7 @@ return { abbreviation = 'km', cb = 'did_set_keymodel', defaults = { if_true = '' }, + values = { 'startsel', 'stopsel' }, deny_duplicates = true, desc = [=[ List of comma-separated words, which enable special things that keys @@ -4903,6 +5039,7 @@ return { abbreviation = 'lop', cb = 'did_set_lispoptions', defaults = { if_true = '' }, + values = { 'expr:0', 'expr:1' }, deny_duplicates = true, desc = [=[ Comma-separated list of items that influence the Lisp indenting when @@ -5559,6 +5696,7 @@ return { abbreviation = 'mousem', cb = 'did_set_mousemodel', defaults = { if_true = 'popup_setpos' }, + values = { 'extend', 'popup', 'popup_setpos', 'mac' }, desc = [=[ Sets the model to use for the mouse. The name mostly specifies what the right mouse button is used for: @@ -5635,6 +5773,7 @@ return { { cb = 'did_set_mousescroll', defaults = { if_true = 'ver:3,hor:6' }, + values = { 'hor:', 'ver:' }, desc = [=[ This option controls the number of lines / columns to scroll by when scrolling with a mouse wheel (|scroll-mouse-wheel|). The option is @@ -5770,6 +5909,7 @@ return { abbreviation = 'nf', cb = 'did_set_nrformats', defaults = { if_true = 'bin,hex' }, + values = { 'bin', 'octal', 'hex', 'alpha', 'unsigned', 'blank' }, deny_duplicates = true, desc = [=[ This defines what bases Vim will consider for numbers when using the @@ -6301,6 +6441,15 @@ return { abbreviation = 'rdb', cb = 'did_set_redrawdebug', defaults = { if_true = '' }, + values = { + 'compositor', + 'nothrottle', + 'invalid', + 'nodelta', + 'line', + 'flush', + }, + flags = true, desc = [=[ Flags to change the way redrawing works, for debugging purposes. Most useful with 'writedelay' set to some reasonable value. @@ -6473,6 +6622,7 @@ return { abbreviation = 'rlc', cb = 'did_set_rightleftcmd', defaults = { if_true = 'search' }, + values = { 'search' }, desc = [=[ Each word in this option enables the command line editing to work in right-to-left mode for a group of commands: @@ -6756,6 +6906,7 @@ return { abbreviation = 'sbo', cb = 'did_set_scrollopt', defaults = { if_true = 'ver,jump' }, + values = { 'ver', 'hor', 'jump' }, deny_duplicates = true, desc = [=[ This is a comma-separated list of words that specifies how @@ -6821,6 +6972,7 @@ return { abbreviation = 'sel', cb = 'did_set_selection', defaults = { if_true = 'inclusive' }, + values = { 'inclusive', 'exclusive', 'old' }, desc = [=[ This option defines the behavior of the selection. It is only used in Visual and Select mode. @@ -6851,6 +7003,7 @@ return { abbreviation = 'slm', cb = 'did_set_selectmode', defaults = { if_true = '' }, + values = { 'mouse', 'key', 'cmd' }, deny_duplicates = true, desc = [=[ This is a comma-separated list of words, which specifies when to start @@ -6873,6 +7026,28 @@ return { abbreviation = 'ssop', cb = 'did_set_sessionoptions', defaults = { if_true = 'blank,buffers,curdir,folds,help,tabpages,winsize,terminal' }, + -- Also used for 'viewoptions'. + values = { + 'buffers', + 'winpos', + 'resize', + 'winsize', + 'localoptions', + 'options', + 'help', + 'blank', + 'globals', + 'slash', + 'unix', + 'sesdir', + 'curdir', + 'folds', + 'cursor', + 'tabpages', + 'terminal', + 'skiprtp', + }, + flags = true, deny_duplicates = true, desc = [=[ Changes the effect of the |:mksession| command. It is a comma- @@ -7527,6 +7702,7 @@ return { abbreviation = 'sloc', cb = 'did_set_showcmdloc', defaults = { if_true = 'last' }, + values = { 'last', 'statusline', 'tabline' }, desc = [=[ This option can be used to display the (partially) entered command in another location. Possible values are: @@ -7678,6 +7854,30 @@ return { abbreviation = 'scl', cb = 'did_set_signcolumn', defaults = { if_true = 'auto' }, + values = { + 'yes', + 'no', + 'auto', + 'auto:1', + 'auto:2', + 'auto:3', + 'auto:4', + 'auto:5', + 'auto:6', + 'auto:7', + 'auto:8', + 'auto:9', + 'yes:1', + 'yes:2', + 'yes:3', + 'yes:4', + 'yes:5', + 'yes:6', + 'yes:7', + 'yes:8', + 'yes:9', + 'number', + }, desc = [=[ When and how to draw the signcolumn. Valid values are: "auto" only when there is a sign to display @@ -7944,6 +8144,8 @@ return { abbreviation = 'spo', cb = 'did_set_spelloptions', defaults = { if_true = '' }, + values = { 'camel', 'noplainbuffer' }, + flags = true, deny_duplicates = true, desc = [=[ A comma-separated list of options for spell checking: @@ -7969,6 +8171,8 @@ return { abbreviation = 'sps', cb = 'did_set_spellsuggest', defaults = { if_true = 'best' }, + -- Keep this in sync with spell_check_sps(). + values = { 'best', 'fast', 'double', 'expr:', 'file:', 'timeout:' }, deny_duplicates = true, desc = [=[ Methods used for spelling suggestions. Both for the |z=| command and @@ -8064,6 +8268,7 @@ return { abbreviation = 'spk', cb = 'did_set_splitkeep', defaults = { if_true = 'cursor' }, + values = { 'cursor', 'screen', 'topline' }, desc = [=[ The value of this option determines the scroll behavior when opening, closing or resizing horizontal splits. @@ -8486,6 +8691,8 @@ return { abbreviation = 'swb', cb = 'did_set_switchbuf', defaults = { if_true = 'uselast' }, + values = { 'useopen', 'usetab', 'split', 'newtab', 'vsplit', 'uselast' }, + flags = true, deny_duplicates = true, desc = [=[ This option controls the behavior when switching between buffers. @@ -8583,6 +8790,8 @@ return { abbreviation = 'tcl', cb = 'did_set_tabclose', defaults = { if_true = '' }, + values = { 'left', 'uselast' }, + flags = true, deny_duplicates = true, desc = [=[ This option controls the behavior when closing tab pages (e.g., using @@ -8765,6 +8974,8 @@ return { abbreviation = 'tc', cb = 'did_set_tagcase', defaults = { if_true = 'followic' }, + values = { 'followic', 'ignore', 'match', 'followscs', 'smart' }, + flags = true, desc = [=[ This option specifies how case is handled when searching the tags file: @@ -8929,6 +9140,8 @@ return { abbreviation = 'tpf', cb = 'did_set_termpastefilter', defaults = { if_true = 'BS,HT,ESC,DEL' }, + values = { 'BS', 'HT', 'FF', 'ESC', 'DEL', 'C0', 'C1' }, + flags = true, deny_duplicates = true, desc = [=[ A comma-separated list of options for specifying control characters @@ -9506,6 +9719,7 @@ return { abbreviation = 'vop', cb = 'did_set_viewoptions', defaults = { if_true = 'folds,cursor,curdir' }, + flags = true, deny_duplicates = true, desc = [=[ Changes the effect of the |:mkview| command. It is a comma-separated @@ -9533,6 +9747,15 @@ return { abbreviation = 've', cb = 'did_set_virtualedit', defaults = { if_true = '' }, + values = { 'block', 'insert', 'all', 'onemore', 'none', 'NONE' }, + flags = { + Block = 5, + Insert = 6, + All = 4, + Onemore = 8, + None = 16, + NoneU = 32, + }, deny_duplicates = true, desc = [=[ A comma-separated list of these words: @@ -9777,6 +10000,9 @@ return { abbreviation = 'wim', cb = 'did_set_wildmode', defaults = { if_true = 'full' }, + -- Keep this in sync with check_opt_wim(). + values = { 'full', 'longest', 'list', 'lastused' }, + flags = true, deny_duplicates = false, desc = [=[ Completion mode that is used for the character specified with @@ -9835,6 +10061,8 @@ return { abbreviation = 'wop', cb = 'did_set_wildoptions', defaults = { if_true = 'pum,tagfile' }, + values = { 'fuzzy', 'tagfile', 'pum' }, + flags = true, deny_duplicates = true, desc = [=[ A list of words that change how |cmdline-completion| is done. @@ -9867,6 +10095,7 @@ return { abbreviation = 'wak', cb = 'did_set_winaltkeys', defaults = { if_true = 'menu' }, + values = { 'yes', 'menu', 'no' }, desc = [=[ only used in Win32 Some GUI versions allow the access to menu entries by using the ALT diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index bfb26a0be6..b47517b1a2 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -68,84 +68,6 @@ static const char e_wrong_number_of_characters_for_field_str[] static const char e_wrong_character_width_for_field_str[] = N_("E1512: Wrong character width for field \"%s\""); -static char *(p_ambw_values[]) = { "single", "double", NULL }; -static char *(p_bg_values[]) = { "light", "dark", NULL }; -static char *(p_bkc_values[]) = { "yes", "auto", "no", "breaksymlink", "breakhardlink", NULL }; -static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", "copy", "ctrlg", "error", - "esc", "ex", "hangul", "insertmode", "lang", "mess", "showmatch", - "operator", "register", "shell", "spell", "term", "wildmode", - NULL }; -// Note: Keep this in sync with briopt_check() -static char *(p_briopt_values[]) = { "shift:", "min:", "sbr", "list:", "column:", NULL }; -// Note: Keep this in sync with diffopt_changed() -static char *(p_dip_values[]) = { "filler", "context:", "iblank", "icase", - "iwhite", "iwhiteall", "iwhiteeol", "horizontal", "vertical", - "closeoff", "hiddenoff", "foldcolumn:", "followwrap", "internal", - "indent-heuristic", "linematch:", "algorithm:", NULL }; -static char *(p_dip_algorithm_values[]) = { "myers", "minimal", "patience", "histogram", NULL }; -static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", "blank", NULL }; -static char *(p_ff_values[]) = { "unix", "dos", "mac", NULL }; -static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; -static char *(p_cmp_values[]) = { "internal", "keepascii", NULL }; -// Note: Keep this in sync with fill_culopt_flags() -static char *(p_culopt_values[]) = { "line", "screenline", "number", "both", NULL }; -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", NULL }; -static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", "quickfix", "search", - "tag", "insert", "undo", "jump", NULL }; -// Note: Keep this in sync with spell_check_sps() -static char *(p_sps_values[]) = { "best", "fast", "double", "expr:", "file:", "timeout:", NULL }; -/// Also used for 'viewoptions'! Keep in sync with SSOP_ flags. -static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "localoptions", - "options", "help", "blank", "globals", "slash", "unix", "sesdir", - "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp", - NULL }; -// Keep in sync with SWB_ flags in option_vars.h -static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", - NULL }; -static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL }; -static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL }; -// Keep in sync with TCL_ flags in option_vars.h -static char *(p_tcl_values[]) = { "left", "uselast", NULL }; -static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; -// Note: Keep this in sync with check_opt_wim() -static char *(p_wim_values[]) = { "full", "longest", "list", "lastused", NULL }; -static char *(p_wop_values[]) = { "fuzzy", "tagfile", "pum", NULL }; -static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; -static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL }; -static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL }; -static char *(p_slm_values[]) = { "mouse", "key", "cmd", NULL }; -static char *(p_km_values[]) = { "startsel", "stopsel", NULL }; -static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL }; -static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL }; -static char *(p_ead_values[]) = { "both", "ver", "hor", NULL }; -static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", "help", "acwrite", - "terminal", "prompt", NULL }; -static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", "wipe", NULL }; -static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL }; -static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", - "syntax", "diff", NULL }; -static char *(p_fcl_values[]) = { "all", NULL }; -static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "popup", - "noinsert", "noselect", "fuzzy", NULL }; -#ifdef BACKSLASH_IN_FILENAME -static char *(p_csl_values[]) = { "slash", "backslash", NULL }; -#endif - -static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", - "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", - "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", - "yes:9", "number", NULL }; -static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", - "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", - "5", "6", "7", "8", "9", NULL }; -static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL }; -static char *(p_icm_values[]) = { "nosplit", "split", NULL }; -static char *(p_jop_values[]) = { "stack", "view", "clean", NULL }; -static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; -static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line", - "flush", NULL }; -static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL }; - /// All possible flags for 'shm'. /// the literal chars before 0 are removed flags. these are safely ignored static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES, @@ -158,23 +80,23 @@ static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES, /// option values. void didset_string_options(void) { - opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); - opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true); - opt_strings_flags(p_bo, p_bo_values, &bo_flags, true); - opt_strings_flags(p_cot, p_cot_values, &cot_flags, true); - opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true); - opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); - opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); - opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); - opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); - opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); - opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); - opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); - opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); - opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); - opt_strings_flags(p_tcl, p_tcl_values, &tcl_flags, true); - opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); - opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); + opt_strings_flags(p_cmp, opt_cmp_values, &cmp_flags, true); + opt_strings_flags(p_bkc, opt_bkc_values, &bkc_flags, true); + opt_strings_flags(p_bo, opt_bo_values, &bo_flags, true); + opt_strings_flags(p_cot, opt_cot_values, &cot_flags, true); + opt_strings_flags(p_ssop, opt_ssop_values, &ssop_flags, true); + opt_strings_flags(p_vop, opt_ssop_values, &vop_flags, true); + opt_strings_flags(p_fdo, opt_fdo_values, &fdo_flags, true); + opt_strings_flags(p_dy, opt_dy_values, &dy_flags, true); + opt_strings_flags(p_jop, opt_jop_values, &jop_flags, true); + opt_strings_flags(p_rdb, opt_rdb_values, &rdb_flags, true); + opt_strings_flags(p_tc, opt_tc_values, &tc_flags, false); + opt_strings_flags(p_tpf, opt_tpf_values, &tpf_flags, true); + opt_strings_flags(p_ve, opt_ve_values, &ve_flags, true); + opt_strings_flags(p_swb, opt_swb_values, &swb_flags, true); + opt_strings_flags(p_tcl, opt_tcl_values, &tcl_flags, true); + opt_strings_flags(p_wop, opt_wop_values, &wop_flags, true); + opt_strings_flags(p_cb, opt_cb_values, &cb_flags, true); } char *illegal_char(char *errbuf, size_t errbuflen, int c) @@ -300,7 +222,7 @@ int check_signcolumn(char *scl, win_T *wp) return FAIL; } - if (check_opt_strings(val, p_scl_values, false) == OK) { + if (check_opt_strings(val, opt_scl_values, false) == OK) { if (wp == NULL) { return OK; } @@ -428,7 +350,7 @@ bool check_illegal_path_names(char *val, uint32_t flags) /// An option that accepts a list of flags is changed. /// e.g. 'viewoptions', 'switchbuf', 'casemap', etc. -static const char *did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list) +static const char *did_set_opt_flags(char *val, const char **values, unsigned *flagp, bool list) { if (opt_strings_flags(val, values, flagp, list) != OK) { return e_invarg; @@ -438,7 +360,7 @@ static const char *did_set_opt_flags(char *val, char **values, unsigned *flagp, /// An option that accepts a list of string values is changed. /// e.g. 'nrformats', 'scrollopt', 'wildoptions', etc. -static const char *did_set_opt_strings(char *val, char **values, bool list) +static const char *did_set_opt_strings(char *val, const char **values, bool list) { return did_set_opt_flags(val, values, NULL, list); } @@ -456,7 +378,7 @@ static const char *did_set_option_listflag(char *val, char *flags, char *errbuf, } /// Expand an option that accepts a list of string values. -static int expand_set_opt_string(optexpand_T *args, char **values, size_t numValues, +static int expand_set_opt_string(optexpand_T *args, const char **values, size_t numValues, int *numMatches, char ***matches) { regmatch_T *regmatch = args->oe_regmatch; @@ -473,7 +395,7 @@ static int expand_set_opt_string(optexpand_T *args, char **values, size_t numVal (*matches)[count++] = xstrdup(option_val); } - for (char **val = values; *val != NULL; val++) { + for (const char **val = values; *val != NULL; val++) { if (include_orig_val && *option_val != NUL) { if (strcmp(*val, option_val) == 0) { continue; @@ -568,7 +490,7 @@ static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatch /// The 'ambiwidth' option is changed. const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED) { - if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { + if (check_opt_strings(p_ambw, opt_ambw_values, false) != OK) { return e_invarg; } return check_chars_options(); @@ -577,8 +499,8 @@ const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED) int expand_set_ambiwidth(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_ambw_values, - ARRAY_SIZE(p_ambw_values) - 1, + opt_ambw_values, + ARRAY_SIZE(opt_ambw_values) - 1, numMatches, matches); } @@ -586,7 +508,7 @@ int expand_set_ambiwidth(optexpand_T *args, int *numMatches, char ***matches) /// The 'background' option is changed. const char *did_set_background(optset_T *args) { - if (check_opt_strings(p_bg, p_bg_values, false) != OK) { + if (check_opt_strings(p_bg, opt_bg_values, false) != OK) { return e_invarg; } @@ -615,8 +537,8 @@ const char *did_set_background(optset_T *args) int expand_set_background(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_bg_values, - ARRAY_SIZE(p_bg_values) - 1, + opt_bg_values, + ARRAY_SIZE(opt_bg_values) - 1, numMatches, matches); } @@ -628,7 +550,7 @@ const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED) if (*p_bs != '2') { return e_invarg; } - } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { + } else if (check_opt_strings(p_bs, opt_bs_values, true) != OK) { return e_invarg; } return NULL; @@ -637,8 +559,8 @@ const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED) int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_bs_values, - ARRAY_SIZE(p_bs_values) - 1, + opt_bs_values, + ARRAY_SIZE(opt_bs_values) - 1, numMatches, matches); } @@ -664,15 +586,15 @@ const char *did_set_backupcopy(optset_T *args) // make the local value empty: use the global value *flags = 0; } else { - if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { + if (opt_strings_flags(bkc, opt_bkc_values, flags, true) != OK) { return e_invarg; } - if (((*flags & BKC_AUTO) != 0) - + ((*flags & BKC_YES) != 0) - + ((*flags & BKC_NO) != 0) != 1) { + if (((*flags & kOptBkcFlagAuto) != 0) + + ((*flags & kOptBkcFlagYes) != 0) + + ((*flags & kOptBkcFlagNo) != 0) != 1) { // Must have exactly one of "auto", "yes" and "no". - opt_strings_flags(oldval, p_bkc_values, flags, true); + opt_strings_flags(oldval, opt_bkc_values, flags, true); return e_invarg; } } @@ -683,8 +605,8 @@ const char *did_set_backupcopy(optset_T *args) int expand_set_backupcopy(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_bkc_values, - ARRAY_SIZE(p_bkc_values) - 1, + opt_bkc_values, + ARRAY_SIZE(opt_bkc_values) - 1, numMatches, matches); } @@ -703,14 +625,14 @@ const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED) /// The 'belloff' option is changed. const char *did_set_belloff(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true); + return did_set_opt_flags(p_bo, opt_bo_values, &bo_flags, true); } int expand_set_belloff(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_bo_values, - ARRAY_SIZE(p_bo_values) - 1, + opt_bo_values, + ARRAY_SIZE(opt_bo_values) - 1, numMatches, matches); } @@ -752,8 +674,8 @@ const char *did_set_breakindentopt(optset_T *args) int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_briopt_values, - ARRAY_SIZE(p_briopt_values) - 1, + opt_briopt_values, + ARRAY_SIZE(opt_briopt_values) - 1, numMatches, matches); } @@ -762,14 +684,14 @@ int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matche const char *did_set_bufhidden(optset_T *args) { buf_T *buf = (buf_T *)args->os_buf; - return did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false); + return did_set_opt_strings(buf->b_p_bh, opt_bh_values, false); } int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_bufhidden_values, - ARRAY_SIZE(p_bufhidden_values) - 1, + opt_bh_values, + ARRAY_SIZE(opt_bh_values) - 1, numMatches, matches); } @@ -782,7 +704,7 @@ const char *did_set_buftype(optset_T *args) // When 'buftype' is set, check for valid value. if ((buf->terminal && buf->b_p_bt[0] != 't') || (!buf->terminal && buf->b_p_bt[0] == 't') - || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) { + || check_opt_strings(buf->b_p_bt, opt_bt_values, false) != OK) { return e_invarg; } if (win->w_status_height || global_stl_height()) { @@ -797,8 +719,8 @@ const char *did_set_buftype(optset_T *args) int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_buftype_values, - ARRAY_SIZE(p_buftype_values) - 1, + opt_bt_values, + ARRAY_SIZE(opt_bt_values) - 1, numMatches, matches); } @@ -806,14 +728,14 @@ int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches) /// The 'casemap' option is changed. const char *did_set_casemap(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true); + return did_set_opt_flags(p_cmp, opt_cmp_values, &cmp_flags, true); } int expand_set_casemap(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_cmp_values, - ARRAY_SIZE(p_cmp_values) - 1, + opt_cmp_values, + ARRAY_SIZE(opt_cmp_values) - 1, numMatches, matches); } @@ -904,14 +826,14 @@ const char *did_set_cinoptions(optset_T *args) /// The 'clipboard' option is changed. const char *did_set_clipboard(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true); + return did_set_opt_flags(p_cb, opt_cb_values, &cb_flags, true); } int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_cb_values, - ARRAY_SIZE(p_cb_values) - 1, + opt_cb_values, + ARRAY_SIZE(opt_cb_values) - 1, numMatches, matches); } @@ -1009,12 +931,9 @@ const char *did_set_complete(optset_T *args) int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches) { - static char *(p_cpt_values[]) = { - ".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", "f", NULL - }; return expand_set_opt_string(args, - p_cpt_values, - ARRAY_SIZE(p_cpt_values) - 1, + opt_cpt_values, + ARRAY_SIZE(opt_cpt_values) - 1, numMatches, matches); } @@ -1079,11 +998,11 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) buf->b_cot_flags = 0; } - if (check_opt_strings(cot, p_cot_values, true) != OK) { + if (check_opt_strings(cot, opt_cot_values, true) != OK) { return e_invarg; } - if (opt_strings_flags(cot, p_cot_values, flags, true) != OK) { + if (opt_strings_flags(cot, opt_cot_values, flags, true) != OK) { return e_invarg; } @@ -1093,8 +1012,8 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_cot_values, - ARRAY_SIZE(p_cot_values) - 1, + opt_cot_values, + ARRAY_SIZE(opt_cot_values) - 1, numMatches, matches); } @@ -1104,8 +1023,8 @@ int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches) const char *did_set_completeslash(optset_T *args) { buf_T *buf = (buf_T *)args->os_buf; - if (check_opt_strings(p_csl, p_csl_values, false) != OK - || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) { + if (check_opt_strings(p_csl, opt_csl_values, false) != OK + || check_opt_strings(buf->b_p_csl, opt_csl_values, false) != OK) { return e_invarg; } return NULL; @@ -1114,8 +1033,8 @@ const char *did_set_completeslash(optset_T *args) int expand_set_completeslash(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_csl_values, - ARRAY_SIZE(p_csl_values) - 1, + opt_csl_values, + ARRAY_SIZE(opt_csl_values) - 1, numMatches, matches); } @@ -1164,8 +1083,8 @@ const char *did_set_cursorlineopt(optset_T *args) int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_culopt_values, - ARRAY_SIZE(p_culopt_values) - 1, + opt_culopt_values, + ARRAY_SIZE(opt_culopt_values) - 1, numMatches, matches); } @@ -1173,14 +1092,14 @@ int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches /// The 'debug' option is changed. const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_debug, p_debug_values, false); + return did_set_opt_strings(p_debug, opt_debug_values, false); } int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_debug_values, - ARRAY_SIZE(p_debug_values) - 1, + opt_debug_values, + ARRAY_SIZE(opt_debug_values) - 1, numMatches, matches); } @@ -1204,8 +1123,8 @@ int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) if (xp->xp_pattern - args->oe_set_arg >= (int)algo_len && strncmp(xp->xp_pattern - algo_len, "algorithm:", algo_len) == 0) { return expand_set_opt_string(args, - p_dip_algorithm_values, - ARRAY_SIZE(p_dip_algorithm_values) - 1, + opt_dip_algorithm_values, + ARRAY_SIZE(opt_dip_algorithm_values) - 1, numMatches, matches); } @@ -1213,8 +1132,8 @@ int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) } return expand_set_opt_string(args, - p_dip_values, - ARRAY_SIZE(p_dip_values) - 1, + opt_dip_values, + ARRAY_SIZE(opt_dip_values) - 1, numMatches, matches); } @@ -1222,7 +1141,7 @@ int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) /// The 'display' option is changed. const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED) { - if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { + if (opt_strings_flags(p_dy, opt_dy_values, &dy_flags, true) != OK) { return e_invarg; } init_chartab(); @@ -1233,8 +1152,8 @@ const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED) int expand_set_display(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_dy_values, - ARRAY_SIZE(p_dy_values) - 1, + opt_dy_values, + ARRAY_SIZE(opt_dy_values) - 1, numMatches, matches); } @@ -1242,14 +1161,14 @@ int expand_set_display(optexpand_T *args, int *numMatches, char ***matches) /// The 'eadirection' option is changed. const char *did_set_eadirection(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_ead, p_ead_values, false); + return did_set_opt_strings(p_ead, opt_ead_values, false); } int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_ead_values, - ARRAY_SIZE(p_ead_values) - 1, + opt_ead_values, + ARRAY_SIZE(opt_ead_values) - 1, numMatches, matches); } @@ -1334,7 +1253,7 @@ const char *did_set_fileformat(optset_T *args) int opt_flags = args->os_flags; if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { return e_modifiable; - } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { + } else if (check_opt_strings(*varp, opt_ff_values, false) != OK) { return e_invarg; } redraw_titles(); @@ -1351,8 +1270,8 @@ const char *did_set_fileformat(optset_T *args) int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_ff_values, - ARRAY_SIZE(p_ff_values) - 1, + opt_ff_values, + ARRAY_SIZE(opt_ff_values) - 1, numMatches, matches); } @@ -1361,17 +1280,17 @@ int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches) /// fileformat options. char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) { - if (idx >= (int)ARRAY_SIZE(p_ff_values)) { + if (idx >= (int)ARRAY_SIZE(opt_ff_values)) { return NULL; } - return p_ff_values[idx]; + return (char *)opt_ff_values[idx]; } /// The 'fileformats' option is changed. const char *did_set_fileformats(optset_T *args) { - return did_set_opt_strings(p_ffs, p_ff_values, true); + return did_set_opt_strings(p_ffs, opt_ff_values, true); } /// The 'filetype' or the 'syntax' option is changed. @@ -1395,14 +1314,14 @@ const char *did_set_filetype_or_syntax(optset_T *args) /// The 'foldclose' option is changed. const char *did_set_foldclose(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_fcl, p_fcl_values, true); + return did_set_opt_strings(p_fcl, opt_fcl_values, true); } int expand_set_foldclose(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_fcl_values, - ARRAY_SIZE(p_fcl_values) - 1, + opt_fcl_values, + ARRAY_SIZE(opt_fcl_values) - 1, numMatches, matches); } @@ -1411,7 +1330,7 @@ int expand_set_foldclose(optexpand_T *args, int *numMatches, char ***matches) const char *did_set_foldcolumn(optset_T *args) { char **varp = (char **)args->os_varp; - if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { + if (**varp == NUL || check_opt_strings(*varp, opt_fdc_values, false) != OK) { return e_invarg; } return NULL; @@ -1420,8 +1339,8 @@ const char *did_set_foldcolumn(optset_T *args) int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_fdc_values, - ARRAY_SIZE(p_fdc_values) - 1, + opt_fdc_values, + ARRAY_SIZE(opt_fdc_values) - 1, numMatches, matches); } @@ -1474,7 +1393,7 @@ const char *did_set_foldmethod(optset_T *args) { win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; - if (check_opt_strings(*varp, p_fdm_values, false) != OK || **varp == NUL) { + if (check_opt_strings(*varp, opt_fdm_values, false) != OK || **varp == NUL) { return e_invarg; } foldUpdateAll(win); @@ -1487,8 +1406,8 @@ const char *did_set_foldmethod(optset_T *args) int expand_set_foldmethod(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_fdm_values, - ARRAY_SIZE(p_fdm_values) - 1, + opt_fdm_values, + ARRAY_SIZE(opt_fdm_values) - 1, numMatches, matches); } @@ -1496,14 +1415,14 @@ int expand_set_foldmethod(optexpand_T *args, int *numMatches, char ***matches) /// The 'foldopen' option is changed. const char *did_set_foldopen(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true); + return did_set_opt_flags(p_fdo, opt_fdo_values, &fdo_flags, true); } int expand_set_foldopen(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_fdo_values, - ARRAY_SIZE(p_fdo_values) - 1, + opt_fdo_values, + ARRAY_SIZE(opt_fdo_values) - 1, numMatches, matches); } @@ -1586,14 +1505,14 @@ const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED) if (cmdpreview) { return e_invarg; } - return did_set_opt_strings(p_icm, p_icm_values, false); + return did_set_opt_strings(p_icm, opt_icm_values, false); } int expand_set_inccommand(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_icm_values, - ARRAY_SIZE(p_icm_values) - 1, + opt_icm_values, + ARRAY_SIZE(opt_icm_values) - 1, numMatches, matches); } @@ -1632,14 +1551,14 @@ const char *did_set_isopt(optset_T *args) /// The 'jumpoptions' option is changed. const char *did_set_jumpoptions(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true); + return did_set_opt_flags(p_jop, opt_jop_values, &jop_flags, true); } int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_jop_values, - ARRAY_SIZE(p_jop_values) - 1, + opt_jop_values, + ARRAY_SIZE(opt_jop_values) - 1, numMatches, matches); } @@ -1699,7 +1618,7 @@ const char *did_set_keymap(optset_T *args) /// The 'keymodel' option is changed. const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED) { - if (check_opt_strings(p_km, p_km_values, true) != OK) { + if (check_opt_strings(p_km, opt_km_values, true) != OK) { return e_invarg; } km_stopsel = (vim_strchr(p_km, 'o') != NULL); @@ -1710,8 +1629,8 @@ const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED) int expand_set_keymodel(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_km_values, - ARRAY_SIZE(p_km_values) - 1, + opt_km_values, + ARRAY_SIZE(opt_km_values) - 1, numMatches, matches); } @@ -1729,10 +1648,9 @@ const char *did_set_lispoptions(optset_T *args) int expand_set_lispoptions(optexpand_T *args, int *numMatches, char ***matches) { - static char *(p_lop_values[]) = { "expr:0", "expr:1", NULL }; return expand_set_opt_string(args, - p_lop_values, - ARRAY_SIZE(p_lop_values) - 1, + opt_lop_values, + ARRAY_SIZE(opt_lop_values) - 1, numMatches, matches); } @@ -1789,14 +1707,14 @@ int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches) /// The 'mousemodel' option is changed. const char *did_set_mousemodel(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_mousem, p_mousem_values, false); + return did_set_opt_strings(p_mousem, opt_mousem_values, false); } int expand_set_mousemodel(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_mousem_values, - ARRAY_SIZE(p_mousem_values) - 1, + opt_mousem_values, + ARRAY_SIZE(opt_mousem_values) - 1, numMatches, matches); } @@ -1868,10 +1786,9 @@ const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED) int expand_set_mousescroll(optexpand_T *args, int *numMatches, char ***matches) { - static char *(p_mousescroll_values[]) = { "hor:", "ver:", NULL }; return expand_set_opt_string(args, - p_mousescroll_values, - ARRAY_SIZE(p_mousescroll_values) - 1, + opt_mousescroll_values, + ARRAY_SIZE(opt_mousescroll_values) - 1, numMatches, matches); } @@ -1881,14 +1798,14 @@ const char *did_set_nrformats(optset_T *args) { char **varp = (char **)args->os_varp; - return did_set_opt_strings(*varp, p_nf_values, true); + return did_set_opt_strings(*varp, opt_nf_values, true); } int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_nf_values, - ARRAY_SIZE(p_nf_values) - 1, + opt_nf_values, + ARRAY_SIZE(opt_nf_values) - 1, numMatches, matches); } @@ -1912,7 +1829,7 @@ const char *did_set_optexpr(optset_T *args) /// The 'redrawdebug' option is changed. const char *did_set_redrawdebug(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true); + return did_set_opt_flags(p_rdb, opt_rdb_values, &rdb_flags, true); } /// The 'rightleftcmd' option is changed. @@ -1930,10 +1847,9 @@ const char *did_set_rightleftcmd(optset_T *args) int expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char ***matches) { - static char *(p_rlc_values[]) = { "search", NULL }; return expand_set_opt_string(args, - p_rlc_values, - ARRAY_SIZE(p_rlc_values) - 1, + opt_rlc_values, + ARRAY_SIZE(opt_rlc_values) - 1, numMatches, matches); } @@ -1947,14 +1863,14 @@ const char *did_set_rulerformat(optset_T *args) /// The 'scrollopt' option is changed. const char *did_set_scrollopt(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_sbo, p_scbopt_values, true); + return did_set_opt_strings(p_sbo, opt_sbo_values, true); } int expand_set_scrollopt(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_scbopt_values, - ARRAY_SIZE(p_scbopt_values) - 1, + opt_sbo_values, + ARRAY_SIZE(opt_sbo_values) - 1, numMatches, matches); } @@ -1962,7 +1878,7 @@ int expand_set_scrollopt(optexpand_T *args, int *numMatches, char ***matches) /// The 'selection' option is changed. const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED) { - if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) { + if (*p_sel == NUL || check_opt_strings(p_sel, opt_sel_values, false) != OK) { return e_invarg; } if (VIsual_active) { @@ -1975,8 +1891,8 @@ const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED) int expand_set_selection(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_sel_values, - ARRAY_SIZE(p_sel_values) - 1, + opt_sel_values, + ARRAY_SIZE(opt_sel_values) - 1, numMatches, matches); } @@ -1984,14 +1900,14 @@ int expand_set_selection(optexpand_T *args, int *numMatches, char ***matches) /// The 'selectmode' option is changed. const char *did_set_selectmode(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_slm, p_slm_values, true); + return did_set_opt_strings(p_slm, opt_slm_values, true); } int expand_set_selectmode(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_slm_values, - ARRAY_SIZE(p_slm_values) - 1, + opt_slm_values, + ARRAY_SIZE(opt_slm_values) - 1, numMatches, matches); } @@ -1999,13 +1915,13 @@ int expand_set_selectmode(optexpand_T *args, int *numMatches, char ***matches) /// The 'sessionoptions' option is changed. const char *did_set_sessionoptions(optset_T *args) { - if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { + if (opt_strings_flags(p_ssop, opt_ssop_values, &ssop_flags, true) != OK) { return e_invarg; } - if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { + if ((ssop_flags & kOptSsopFlagCurdir) && (ssop_flags & kOptSsopFlagSesdir)) { // Don't allow both "sesdir" and "curdir". const char *oldval = args->os_oldval.string.data; - opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); + opt_strings_flags(oldval, opt_ssop_values, &ssop_flags, true); return e_invarg; } return NULL; @@ -2014,8 +1930,8 @@ const char *did_set_sessionoptions(optset_T *args) int expand_set_sessionoptions(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_ssop_values, - ARRAY_SIZE(p_ssop_values) - 1, + opt_ssop_values, + ARRAY_SIZE(opt_ssop_values) - 1, numMatches, matches); } @@ -2099,7 +2015,7 @@ const char *did_set_showbreak(optset_T *args) /// The 'showcmdloc' option is changed. const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED) { - const char *errmsg = did_set_opt_strings(p_sloc, p_sloc_values, false); + const char *errmsg = did_set_opt_strings(p_sloc, opt_sloc_values, false); if (errmsg == NULL) { comp_col(); @@ -2111,8 +2027,8 @@ const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED) int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_sloc_values, - ARRAY_SIZE(p_sloc_values) - 1, + opt_sloc_values, + ARRAY_SIZE(opt_sloc_values) - 1, numMatches, matches); } @@ -2137,8 +2053,8 @@ const char *did_set_signcolumn(optset_T *args) int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_scl_values, - ARRAY_SIZE(p_scl_values) - 1, + opt_scl_values, + ARRAY_SIZE(opt_scl_values) - 1, numMatches, matches); } @@ -2185,11 +2101,11 @@ const char *did_set_spelloptions(optset_T *args) const char *val = args->os_newval.string.data; if (!(opt_flags & OPT_LOCAL) - && opt_strings_flags(val, p_spo_values, &spo_flags, true) != OK) { + && opt_strings_flags(val, opt_spo_values, &spo_flags, true) != OK) { return e_invarg; } if (!(opt_flags & OPT_GLOBAL) - && opt_strings_flags(val, p_spo_values, &win->w_s->b_p_spo_flags, true) != OK) { + && opt_strings_flags(val, opt_spo_values, &win->w_s->b_p_spo_flags, true) != OK) { return e_invarg; } return NULL; @@ -2198,8 +2114,8 @@ const char *did_set_spelloptions(optset_T *args) int expand_set_spelloptions(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_spo_values, - ARRAY_SIZE(p_spo_values) - 1, + opt_spo_values, + ARRAY_SIZE(opt_spo_values) - 1, numMatches, matches); } @@ -2216,8 +2132,8 @@ const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED) int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_sps_values, - ARRAY_SIZE(p_sps_values) - 1, + opt_sps_values, + ARRAY_SIZE(opt_sps_values) - 1, numMatches, matches); } @@ -2225,14 +2141,14 @@ int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches) /// The 'splitkeep' option is changed. const char *did_set_splitkeep(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_spk, p_spk_values, false); + return did_set_opt_strings(p_spk, opt_spk_values, false); } int expand_set_splitkeep(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_spk_values, - ARRAY_SIZE(p_spk_values) - 1, + opt_spk_values, + ARRAY_SIZE(opt_spk_values) - 1, numMatches, matches); } @@ -2291,14 +2207,14 @@ static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerf /// The 'switchbuf' option is changed. const char *did_set_switchbuf(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true); + return did_set_opt_flags(p_swb, opt_swb_values, &swb_flags, true); } int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_swb_values, - ARRAY_SIZE(p_swb_values) - 1, + opt_swb_values, + ARRAY_SIZE(opt_swb_values) - 1, numMatches, matches); } @@ -2306,14 +2222,14 @@ int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches) /// The 'tabclose' option is changed. const char *did_set_tabclose(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_tcl, p_tcl_values, &tcl_flags, true); + return did_set_opt_flags(p_tcl, opt_tcl_values, &tcl_flags, true); } int expand_set_tabclose(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_tcl_values, - ARRAY_SIZE(p_tcl_values) - 1, + opt_tcl_values, + ARRAY_SIZE(opt_tcl_values) - 1, numMatches, matches); } @@ -2345,7 +2261,7 @@ const char *did_set_tagcase(optset_T *args) // make the local value empty: use the global value *flags = 0; } else if (*p == NUL - || opt_strings_flags(p, p_tc_values, flags, false) != OK) { + || opt_strings_flags(p, opt_tc_values, flags, false) != OK) { return e_invarg; } return NULL; @@ -2354,8 +2270,8 @@ const char *did_set_tagcase(optset_T *args) int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_tc_values, - ARRAY_SIZE(p_tc_values) - 1, + opt_tc_values, + ARRAY_SIZE(opt_tc_values) - 1, numMatches, matches); } @@ -2363,14 +2279,14 @@ int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches) /// The 'termpastefilter' option is changed. const char *did_set_termpastefilter(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true); + return did_set_opt_flags(p_tpf, opt_tpf_values, &tpf_flags, true); } int expand_set_termpastefilter(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_tpf_values, - ARRAY_SIZE(p_tpf_values) - 1, + opt_tpf_values, + ARRAY_SIZE(opt_tpf_values) - 1, numMatches, matches); } @@ -2474,7 +2390,7 @@ const char *did_set_verbosefile(optset_T *args) /// The 'viewoptions' option is changed. const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true); + return did_set_opt_flags(p_vop, opt_ssop_values, &vop_flags, true); } /// The 'virtualedit' option is changed. @@ -2494,7 +2410,7 @@ const char *did_set_virtualedit(optset_T *args) // make the local value empty: use the global value *flags = 0; } else { - if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + if (opt_strings_flags(ve, opt_ve_values, flags, true) != OK) { return e_invarg; } else if (strcmp(ve, args->os_oldval.string.data) != 0) { // Recompute cursor position in case the new 've' setting @@ -2509,8 +2425,8 @@ const char *did_set_virtualedit(optset_T *args) int expand_set_virtualedit(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_ve_values, - ARRAY_SIZE(p_ve_values) - 1, + opt_ve_values, + ARRAY_SIZE(opt_ve_values) - 1, numMatches, matches); } @@ -2542,8 +2458,8 @@ const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED) int expand_set_wildmode(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_wim_values, - ARRAY_SIZE(p_wim_values) - 1, + opt_wim_values, + ARRAY_SIZE(opt_wim_values) - 1, numMatches, matches); } @@ -2551,14 +2467,14 @@ int expand_set_wildmode(optexpand_T *args, int *numMatches, char ***matches) /// The 'wildoptions' option is changed. const char *did_set_wildoptions(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true); + return did_set_opt_flags(p_wop, opt_wop_values, &wop_flags, true); } int expand_set_wildoptions(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_wop_values, - ARRAY_SIZE(p_wop_values) - 1, + opt_wop_values, + ARRAY_SIZE(opt_wop_values) - 1, numMatches, matches); } @@ -2566,7 +2482,7 @@ int expand_set_wildoptions(optexpand_T *args, int *numMatches, char ***matches) /// The 'winaltkeys' option is changed. const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED) { - if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) { + if (*p_wak == NUL || check_opt_strings(p_wak, opt_wak_values, false) != OK) { return e_invarg; } return NULL; @@ -2575,8 +2491,8 @@ const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED) int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char ***matches) { return expand_set_opt_string(args, - p_wak_values, - ARRAY_SIZE(p_wak_values) - 1, + opt_wak_values, + ARRAY_SIZE(opt_wak_values) - 1, numMatches, matches); } @@ -2608,7 +2524,7 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char *val, char **values, int list) +static int check_opt_strings(char *val, const char **values, int list) { return opt_strings_flags(val, values, NULL, list); } @@ -2621,7 +2537,7 @@ static int check_opt_strings(char *val, char **values, int list) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int opt_strings_flags(const char *val, char **values, unsigned *flagp, bool list) +static int opt_strings_flags(const char *val, const char **values, unsigned *flagp, bool list) { unsigned new_flags = 0; @@ -2651,7 +2567,7 @@ static int opt_strings_flags(const char *val, char **values, unsigned *flagp, bo /// @return OK if "p" is a valid fileformat name, FAIL otherwise. int check_ff_value(char *p) { - return check_opt_strings(p, p_ff_values, false); + return check_opt_strings(p, opt_ff_values, false); } static const char e_conflicts_with_value_of_listchars[] diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index d1951d4f96..953d2e75a6 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -456,7 +456,7 @@ static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr) int *attrs = xmalloc(sizeof(int) * (size_t)vim_strsize(text)); bool in_fuzzy = State == MODE_CMDLINE ? cmdline_compl_is_fuzzy() - : (get_cot_flags() & COT_FUZZY) != 0; + : (get_cot_flags() & kOptCotFlagFuzzy) != 0; size_t leader_len = strlen(leader); garray_T *ga = NULL; @@ -844,7 +844,7 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma } // delete the empty last line ml_delete_buf(buf, buf->b_ml.ml_line_count, false); - if (get_cot_flags() & COT_POPUP) { + if (get_cot_flags() & kOptCotFlagPopup) { extmark_splice(buf, 1, 0, 1, 0, 0, buf->b_ml.ml_line_count, 0, inserted_bytes, kExtmarkNoUndo); } } @@ -940,7 +940,7 @@ static bool pum_set_selected(int n, int repeat) pum_selected = n; unsigned cur_cot_flags = get_cot_flags(); - bool use_float = (cur_cot_flags & COT_POPUP) != 0; + bool use_float = (cur_cot_flags & kOptCotFlagPopup) != 0; // when new leader add and info window is shown and no selected we still // need use the first index item to update the info float window position. bool force_select = use_float && pum_selected < 0 && win_float_find_preview(); @@ -1006,7 +1006,7 @@ static bool pum_set_selected(int n, int repeat) if ((pum_array[pum_selected].pum_info != NULL) && (Rows > 10) && (repeat <= 1) - && (cur_cot_flags & COT_ANY_PREVIEW)) { + && (cur_cot_flags & (kOptCotFlagPreview | kOptCotFlagPopup))) { win_T *curwin_save = curwin; tabpage_T *curtab_save = curtab; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6526b0d0bf..76c794b5a9 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2697,7 +2697,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Didn't find it, go to the window before the quickfix // window, unless 'switchbuf' contains 'uselast': in this case we // try to jump to the previously used window first. - if ((swb_flags & SWB_USELAST) && win_valid(prevwin) + if ((swb_flags & kOptSwbFlagUselast) && win_valid(prevwin) && !prevwin->w_p_wfb) { win = prevwin; } else if (altwin != NULL) { @@ -2754,7 +2754,7 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin, bool *opened_windo // If no usable window is found and 'switchbuf' contains "usetab" // then search in other tabs. - if (!usable_win && (swb_flags & SWB_USETAB)) { + if (!usable_win && (swb_flags & kOptSwbFlagUsetab)) { usable_win = qf_goto_tabwin_with_file(qf_fnum); } @@ -3032,7 +3032,7 @@ static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, int qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol, qf_ptr->qf_pattern); - if ((fdo_flags & FDO_QUICKFIX) && openfold) { + if ((fdo_flags & kOptFdoFlagQuickfix) && openfold) { foldOpenCursor(); } if (print_message) { diff --git a/src/nvim/search.c b/src/nvim/search.c index 159ab35f6f..f06b679b0d 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1423,7 +1423,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen cmdline_search_stat(dirc, &pos, &curwin->w_cursor, show_top_bot_msg, msgbuf, msgbuflen, (count != 1 || has_offset - || (!(fdo_flags & FDO_SEARCH) + || (!(fdo_flags & kOptFdoFlagSearch) && hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL))), SEARCH_STAT_DEF_MAX_COUNT, @@ -2350,7 +2350,7 @@ void showmatch(int c) } if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep - vim_beep(BO_MATCH); + vim_beep(kOptBoFlagShowmatch); return; } @@ -2535,7 +2535,7 @@ int current_search(int count, bool forward) } } - if (fdo_flags & FDO_SEARCH && KeyTyped) { + if (fdo_flags & kOptFdoFlagSearch && KeyTyped) { foldOpenCursor(); } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index a0fdd46b04..00e977710f 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -245,7 +245,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount size_t nrlen = 0; // found a number first size_t wrongcaplen = 0; bool count_word = docount; - bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0; + bool use_camel_case = (wp->w_s->b_p_spo_flags & kOptSpoFlagCamel) != 0; bool is_camel_case = false; matchinf_T mi; // Most things are put in "mi" so that it can be passed to functions quickly. @@ -1407,7 +1407,7 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a : p - buf) > wp->w_cursor.col)) { colnr_T col = (colnr_T)(p - buf); - bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0; + bool no_plain_buffer = (wp->w_s->b_p_spo_flags & kOptSpoFlagNoplainbuffer) != 0; bool can_spell = !no_plain_buffer; switch (decor_spell_nav_col(wp, lnum, &decor_lnum, col)) { case kTrue: diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 0ddf4ffa38..3a985ab004 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -402,7 +402,7 @@ int spell_check_sps(void) if (*s != NUL && !ascii_isdigit(*s)) { f = -1; } - // Note: Keep this in sync with p_sps_values. + // Note: Keep this in sync with opt_sps_values. } else if (strcmp(buf, "best") == 0) { f = SPS_BEST; } else if (strcmp(buf, "fast") == 0) { @@ -464,7 +464,7 @@ void spell_suggest(int count) // Use the Visually selected text as the bad word. But reject // a multi-line selection. if (curwin->w_cursor.lnum != VIsual.lnum) { - vim_beep(BO_SPELL); + vim_beep(kOptBoFlagSpell); return; } badlen = (int)curwin->w_cursor.col - (int)VIsual.col; diff --git a/src/nvim/state.c b/src/nvim/state.c index 908f724792..32e2a8d652 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -146,9 +146,9 @@ bool virtual_active(win_T *wp) if (virtual_op != kNone) { return virtual_op; } - return cur_ve_flags == VE_ALL - || ((cur_ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) - || ((cur_ve_flags & VE_INSERT) && (State & MODE_INSERT)); + return cur_ve_flags == kOptVeFlagAll + || ((cur_ve_flags & kOptVeFlagBlock) && VIsual_active && VIsual_mode == Ctrl_V) + || ((cur_ve_flags & kOptVeFlagInsert) && (State & MODE_INSERT)); } /// MODE_VISUAL, MODE_SELECT and MODE_OP_PENDING State are never set, they are diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 3e0bb32391..5844d8d3f2 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -451,7 +451,7 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose) curwin->w_cursor.col = saved_fmark.mark.col; curwin->w_set_curswant = true; check_cursor(curwin); - if ((fdo_flags & FDO_TAG) && old_KeyTyped) { + if ((fdo_flags & kOptFdoFlagTag) && old_KeyTyped) { foldOpenCursor(); } @@ -2294,17 +2294,17 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc // Change the value of 'ignorecase' according to 'tagcase' for the // duration of this function. switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) { - case TC_FOLLOWIC: break; - case TC_IGNORE: + case kOptTcFlagFollowic: break; + case kOptTcFlagIgnore: p_ic = true; break; - case TC_MATCH: + case kOptTcFlagMatch: p_ic = false; break; - case TC_FOLLOWSCS: + case kOptTcFlagFollowscs: p_ic = ignorecase(pat); break; - case TC_SMART: + case kOptTcFlagSmart: p_ic = ignorecase_opt(pat, true, true); break; default: @@ -2846,7 +2846,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help) // If it was a CTRL-W CTRL-] command split window now. For ":tab tag" // open a new tab page. - if (postponed_split && (swb_flags & (SWB_USEOPEN | SWB_USETAB))) { + if (postponed_split && (swb_flags & (kOptSwbFlagUseopen | kOptSwbFlagUsetab))) { buf_T *const existing_buf = buflist_findname_exp(fname); if (existing_buf != NULL) { @@ -3015,7 +3015,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help) if (curbuf->b_help) { set_topline(curwin, curwin->w_cursor.lnum); } - if ((fdo_flags & FDO_TAG) && old_KeyTyped) { + if ((fdo_flags & kOptFdoFlagTag) && old_KeyTyped) { foldOpenCursor(); } } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 5ff7f721ba..b4496d6758 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -598,12 +598,12 @@ bool terminal_enter(void) int save_w_p_cuc = curwin->w_p_cuc; OptInt save_w_p_so = curwin->w_p_so; OptInt save_w_p_siso = curwin->w_p_siso; - if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) { + if (curwin->w_p_cul && curwin->w_p_culopt_flags & kOptCuloptFlagNumber) { if (strcmp(curwin->w_p_culopt, "number") != 0) { save_w_p_culopt = curwin->w_p_culopt; curwin->w_p_culopt = xstrdup("number"); } - curwin->w_p_culopt_flags = CULOPT_NBR; + curwin->w_p_culopt_flags = kOptCuloptFlagNumber; } else { curwin->w_p_cul = false; } @@ -868,28 +868,28 @@ static bool is_filter_char(int c) unsigned flag = 0; switch (c) { case 0x08: - flag = TPF_BS; + flag = kOptTpfFlagBS; break; case 0x09: - flag = TPF_HT; + flag = kOptTpfFlagHT; break; case 0x0A: case 0x0D: break; case 0x0C: - flag = TPF_FF; + flag = kOptTpfFlagFF; break; case 0x1b: - flag = TPF_ESC; + flag = kOptTpfFlagESC; break; case 0x7F: - flag = TPF_DEL; + flag = kOptTpfFlagDEL; break; default: if (c < ' ') { - flag = TPF_C0; + flag = kOptTpfFlagC0; } else if (c >= 0x80 && c <= 0x9F) { - flag = TPF_C1; + flag = kOptTpfFlagC1; } } return !!(tpf_flags & flag); @@ -1181,7 +1181,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) /// Called when the terminal wants to ring the system bell. static int term_bell(void *data) { - vim_beep(BO_TERM); + vim_beep(kOptBoFlagTerm); return 1; } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index eba821a53d..f7b5f28cad 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -325,7 +325,7 @@ void ui_busy_stop(void) /// Emit a bell or visualbell as a warning /// -/// val is one of the BO_ values, e.g., BO_OPER +/// val is one of the OptBoFlags values, e.g., kOptBoFlagOperator void vim_beep(unsigned val) { called_vim_beep = true; @@ -334,7 +334,7 @@ void vim_beep(unsigned val) return; } - if (!((bo_flags & val) || (bo_flags & BO_ALL))) { + if (!((bo_flags & val) || (bo_flags & kOptBoFlagAll))) { static int beeps = 0; static uint64_t start_time = 0; @@ -477,7 +477,7 @@ void ui_line(ScreenGrid *grid, int row, bool invalid_row, int startcol, int endc (const sattr_T *)grid->attrs + off); // 'writedelay': flush & delay each time. - if (p_wd && (rdb_flags & RDB_LINE)) { + if (p_wd && (rdb_flags & kOptRdbFlagLine)) { // If 'writedelay' is active, set the cursor to indicate what was drawn. ui_call_grid_cursor_goto(grid->handle, row, MIN(clearcol, (int)grid->cols - 1)); @@ -564,7 +564,7 @@ void ui_flush(void) } ui_call_flush(); - if (p_wd && (rdb_flags & RDB_FLUSH)) { + if (p_wd && (rdb_flags & kOptRdbFlagFlush)) { os_sleep((uint64_t)llabs(p_wd)); } } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index e28e8d4da7..4cddc3dc82 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -425,7 +425,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag for (int i = skipstart; i < (endcol - skipend) - startcol; i++) { if (attrbuf[i] < 0) { - if (rdb_flags & RDB_INVALID) { + if (rdb_flags & kOptRdbFlagInvalid) { abort(); } else { attrbuf[i] = 0; @@ -441,7 +441,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag static void compose_debug(Integer startrow, Integer endrow, Integer startcol, Integer endcol, int syn_id, bool delay) { - if (!(rdb_flags & RDB_COMPOSITOR) || startcol >= endcol) { + if (!(rdb_flags & kOptRdbFlagCompositor) || startcol >= endcol) { return; } @@ -637,7 +637,7 @@ void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, I } } else { ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols); - if (rdb_flags & RDB_COMPOSITOR) { + if (rdb_flags & kOptRdbFlagCompositor) { debug_delay(2); } } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 0f8857a6bd..233e323f37 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2528,7 +2528,7 @@ static void u_undoredo(bool undo, bool do_buf_event) /// @param absolute used ":undo N" static void u_undo_end(bool did_undo, bool absolute, bool quiet) { - if ((fdo_flags & FDO_UNDO) && KeyTyped) { + if ((fdo_flags & kOptFdoFlagUndo) && KeyTyped) { foldOpenCursor(); } diff --git a/src/nvim/window.c b/src/nvim/window.c index c3f3e075f1..ac4c5a8e4a 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -186,13 +186,13 @@ win_T *swbuf_goto_win_with_buf(buf_T *buf) // If 'switchbuf' contains "useopen": jump to first window in the current // tab page containing "buf" if one exists. - if (swb_flags & SWB_USEOPEN) { + if (swb_flags & kOptSwbFlagUseopen) { wp = buf_jump_open_win(buf); } // If 'switchbuf' contains "usetab": jump to first window in any tab page // containing "buf" if one exists. - if (wp == NULL && (swb_flags & SWB_USETAB)) { + if (wp == NULL && (swb_flags & kOptSwbFlagUsetab)) { wp = buf_jump_open_tab(buf); } @@ -583,7 +583,7 @@ wingotofile: // If 'switchbuf' is set to 'useopen' or 'usetab' and the // file is already opened in a window, then jump to it. win_T *wp = NULL; - if ((swb_flags & (SWB_USEOPEN | SWB_USETAB)) + if ((swb_flags & (kOptSwbFlagUseopen | kOptSwbFlagUsetab)) && cmdmod.cmod_tab == 0) { wp = swbuf_goto_win_with_buf(buflist_findname_exp(ptr)); } @@ -3448,13 +3448,13 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp) static tabpage_T *alt_tabpage(void) { // Use the last accessed tab page, if possible. - if ((tcl_flags & TCL_USELAST) && valid_tabpage(lastused_tabpage)) { + if ((tcl_flags & kOptTclFlagUselast) && valid_tabpage(lastused_tabpage)) { return lastused_tabpage; } // Use the next tab page, if possible. bool forward = curtab->tp_next != NULL - && ((tcl_flags & TCL_LEFT) == 0 || curtab == first_tabpage); + && ((tcl_flags & kOptTclFlagLeft) == 0 || curtab == first_tabpage); tabpage_T *tp; if (forward) { -- cgit From 91295db97d89b140849e7dc9e3bcb1c5a9208f62 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 23 Nov 2024 13:43:58 +0100 Subject: vim-patch:4dd6c22: runtime(apache): Update syntax keyword definition closes: vim/vim#16105 https://github.com/vim/vim/commit/4dd6c22ebeea86b9522e650d133f6b109a39177e Co-authored-by: nisbet-hubbard <87453615+nisbet-hubbard@users.noreply.github.com> --- runtime/syntax/apache.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/syntax/apache.vim b/runtime/syntax/apache.vim index e73045e4c8..d2b8be733b 100644 --- a/runtime/syntax/apache.vim +++ b/runtime/syntax/apache.vim @@ -3,7 +3,7 @@ " Maintainer: David Necas (Yeti) " License: This file can be redistribued and/or modified under the same terms " as Vim itself. -" Last Change: 2022 Apr 25 +" Last Change: 2024 Nov 23 " Notes: Last synced with apache-2.2.3, version 1.x is no longer supported " TODO: see particular FIXME's scattered through the file " make it really linewise? @@ -14,6 +14,7 @@ if exists("b:current_syntax") finish endif +syn iskeyword @,48-57,_,192-255,- syn case ignore " Base constructs -- cgit From 46d124a9339f24eb5e5dc1ed7e6fdad99599cec8 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 23 Nov 2024 13:44:25 +0100 Subject: vim-patch:cacfccf: runtime(netrw): update netrw's decompress logic Detect a few more default archive types, correctly handle file extensions with digits in it. fixes: vim/vim#16099 closes: vim/vim#16104 https://github.com/vim/vim/commit/cacfccf803949e62a29c85d4525372a10ea7e070 Co-authored-by: Christian Brabandt --- runtime/autoload/netrw.vim | 38 +++++++++++++++++++++++++++++++++++--- runtime/doc/pi_netrw.txt | 32 ++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index 1df545278f..3a4d3377c5 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -36,6 +36,7 @@ " 2024 Nov 07 by Vim Project: fix a few issues with netrw tree listing (#15996) " 2024 Nov 10 by Vim Project: directory symlink not resolved in tree view (#16020) " 2024 Nov 14 by Vim Project: small fixes to netrw#BrowseX (#16056) +" 2024 Nov 23 by Vim Project: update decompress defaults (#16104) " }}} " Former Maintainer: Charles E Campbell " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim @@ -360,7 +361,39 @@ call s:NetrwInit("g:netrw_cygdrive","/cygdrive") " Default values - d-g ---------- {{{3 call s:NetrwInit("s:didstarstar",0) call s:NetrwInit("g:netrw_dirhistcnt" , 0) -call s:NetrwInit("g:netrw_decompress" , '{ ".gz" : "gunzip", ".bz2" : "bunzip2", ".zip" : "unzip", ".tar" : "tar -xf", ".xz" : "unxz" }') +let s:xz_opt = has('unix') ? "XZ_OPT=-T0" : + \ (has("win32") && &shell =~? '\vcmd(\.exe)?$' ? + \ "setx XZ_OPT=-T0 &&" : "") +call s:NetrwInit("g:netrw_decompress ", "{" + \ .."'.lz4': 'lz4 -d'," + \ .."'.lzo': 'lzop -d'," + \ .."'.lz': 'lzip -dk'," + \ .."'.7z': '7za x'," + \ .."'.001': '7za x'," + \ .."'.zip': 'unzip'," + \ .."'.bz': 'bunzip2 -k'," + \ .."'.bz2': 'bunzip2 -k'," + \ .."'.gz': 'gunzip -k'," + \ .."'.lzma': 'unlzma -T0 -k'," + \ .."'.xz': 'unxz -T0 -k'," + \ .."'.zst': 'zstd -T0 -d'," + \ .."'.Z': 'uncompress -k'," + \ .."'.tar': 'tar -xvf'," + \ .."'.tar.bz': 'tar -xvjf'," + \ .."'.tar.bz2': 'tar -xvjf'," + \ .."'.tbz': 'tar -xvjf'," + \ .."'.tbz2': 'tar -xvjf'," + \ .."'.tar.gz': 'tar -xvzf'," + \ .."'.tgz': 'tar -xvzf'," + \ .."'.tar.lzma': '"..s:xz_opt.." tar -xvf --lzma'," + \ .."'.tlz': '"..s:xz_opt.." tar -xvf --lzma'," + \ .."'.tar.xz': '"..s:xz_opt.." tar -xvfJ'," + \ .."'.txz': '"..s:xz_opt.." tar -xvfJ'," + \ .."'.tar.zst': '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd'," + \ .."'.tzst': '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd'," + \ .."'.rar': '"..(executable("unrar")?"unrar x -ad":"rar x -ad").."'" + \ .."}") +unlet s:xz_opt call s:NetrwInit("g:netrw_dirhistmax" , 10) call s:NetrwInit("g:netrw_fastbrowse" , 1) call s:NetrwInit("g:netrw_ftp_browse_reject", '^total\s\+\d\+$\|^Trying\s\+\d\+.*$\|^KERBEROS_V\d rejected\|^Security extensions not\|No such file\|: connect to address [0-9a-fA-F:]*: No route to host$') @@ -6495,7 +6528,6 @@ fun! s:NetrwMarkFileArgList(islocal,tomflist) NetrwKeepj call winrestview(svpos) endif endif - endfun " --------------------------------------------------------------------- @@ -6521,7 +6553,7 @@ fun! s:NetrwMarkFileCompress(islocal) " for every filename in the marked list for fname in s:netrwmarkfilelist_{curbufnr} - let sfx= substitute(fname,'^.\{-}\(\.\a\+\)$','\1','') + let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','') if exists("g:netrw_decompress['".sfx."']") " fname has a suffix indicating that its compressed; apply associated decompression routine let exe= g:netrw_decompress[sfx] diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 04dd854637..01e6b5fa6a 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -2645,10 +2645,34 @@ your browsing preferences. (see also: |netrw-settings|) netrw last saw |g:netrw_cursor| >= 5 or when netrw was initially run. - *g:netrw_decompress* = { ".gz" : "gunzip" , - ".bz2" : "bunzip2" , - ".zip" : "unzip" , - ".tar" : "tar -xf"} + *g:netrw_decompress* = { ".lz4": "lz4 -d", + ".lzo": "lzop -d", + ".lz": "lzip -dk", + ".7z": "7za x", + ".001": "7za x", + ".tar.bz": "tar -xvjf", + ".tar.bz2": "tar -xvjf", + ".tbz": "tar -xvjf", + ".tbz2": "tar -xvjf", + ".tar.gz": "tar -xvzf", + ".tgz": "tar -xvzf", + ".tar.zst": "tar --use-compress-program=unzstd -xvf", + ".tzst": "tar --use-compress-program=unzstd -xvf", + ".tar": "tar -xvf", + ".zip": "unzip", + ".bz": "bunzip2 -k", + ".bz2": "bunzip2 -k", + ".gz": "gunzip -k", + ".lzma": "unlzma -T0 -k", + ".xz": "unxz -T0 -k", + ".zst": "zstd -T0 -d", + ".Z": "uncompress -k", + ".rar": "unrar x -ad", + ".tar.lzma": "tar --lzma -xvf", + ".tlz": "tar --lzma -xvf", + ".tar.xz": "tar -xvJf", + ".txz": "tar -xvJf"} + A dictionary mapping suffices to decompression programs. -- cgit From d9b08c58c34ba6c8083f533d5f7a0fd21e262d00 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 24 Nov 2024 06:54:39 +0800 Subject: vim-patch:partial:a01148d: runtime(doc): Expand docs on :! vs. :term (#31321) fixes: vim/vim#16071 closes: vim/vim#16089 https://github.com/vim/vim/commit/a01148d2cb2f8d2820a5b95474d11db0d1802360 Co-authored-by: matveyt --- runtime/doc/various.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 1ded5154a7..d967e7c75b 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -282,6 +282,16 @@ gx Opens the current filepath or URL (decided by requires using the call operator (&). > :!Write-Output "1`n2" | & "C:\Windows\System32\sort.exe" /r < + Vim builds command line using options 'shell', 'shcf', + 'sxq' and 'shq' in the following order: + `&sh &shcf &sxq &shq {cmd} &shq &sxq` + So setting both 'sxq' and 'shq' is possible but rarely + useful. Additional escaping inside `{cmd}` may also + be due to 'sxe' option. + + Also, all |cmdline-special| characters in {cmd} are + replaced by Vim before passing them to shell. + *E34* Any "!" in {cmd} is replaced with the previous external command (see also 'cpoptions'), unless -- cgit From 2a1f604c77a161f076f7d520d66fc6f051b625e7 Mon Sep 17 00:00:00 2001 From: glepnir Date: Sat, 23 Nov 2024 19:11:30 +0800 Subject: fix(lsp): delete bufvar inside WinClosed event Problem: floaing preview window can be closed by some ex commands like `only` `fclose` which will not clean the bufvar Solution: use WinClosed event with floating_winnr for clean bufnr, and add test cases for vim.lsp.util.open_floating_preview --- runtime/lua/vim/lsp/util.lua | 21 ++++++++++++++----- test/functional/plugin/lsp/utils_spec.lua | 34 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 24837c3b44..0f608ffbd1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1344,10 +1344,6 @@ local function close_preview_window(winnr, bufnrs) local augroup = 'preview_window_' .. winnr pcall(api.nvim_del_augroup_by_name, augroup) - local buf = vim.w[winnr].buf_hold_win - if buf and api.nvim_buf_is_valid(buf) then - vim.b[buf].lsp_floating_window = nil - end pcall(api.nvim_win_close, winnr, true) end) end @@ -1613,7 +1609,6 @@ function M.open_floating_preview(contents, syntax, opts) { silent = true, noremap = true, nowait = true } ) close_preview_autocmd(opts.close_events, floating_winnr, { floating_bufnr, bufnr }) - vim.w[floating_winnr].buf_hold_win = bufnr -- save focus_id if opts.focus_id then @@ -1622,6 +1617,22 @@ function M.open_floating_preview(contents, syntax, opts) api.nvim_buf_set_var(bufnr, 'lsp_floating_preview', floating_winnr) end + local augroup_name = ('closing_floating_preview_%d'):format(floating_winnr) + local ok = + pcall(api.nvim_get_autocmds, { group = augroup_name, pattern = tostring(floating_winnr) }) + if not ok then + api.nvim_create_autocmd('WinClosed', { + group = api.nvim_create_augroup(augroup_name, {}), + pattern = tostring(floating_winnr), + callback = function() + if api.nvim_buf_is_valid(bufnr) then + vim.b[bufnr].lsp_floating_preview = nil + end + api.nvim_del_augroup_by_name(augroup_name) + end, + }) + end + if do_stylize then vim.wo[floating_winnr].conceallevel = 2 end diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 813b8de812..ce6e6b2535 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -5,6 +5,8 @@ local Screen = require('test.functional.ui.screen') local feed = n.feed local eq = t.eq local exec_lua = n.exec_lua +local command, api = n.command, n.api +local pcall_err = t.pcall_err describe('vim.lsp.util', function() before_each(n.clear) @@ -265,6 +267,38 @@ describe('vim.lsp.util', function() eq(56, opts.height) end) + + describe('vim.lsp.util.open_floating_preview', function() + local var_name = 'lsp_floating_preview' + local curbuf = api.nvim_get_current_buf() + + it('clean bufvar after fclose', function() + exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + end) + eq(true, api.nvim_win_is_valid(api.nvim_buf_get_var(curbuf, var_name))) + command('fclose') + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + + it('clean bufvar after CursorMoved', function() + local result = exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + local winnr = vim.b[vim.api.nvim_get_current_buf()].lsp_floating_preview + local result = vim.api.nvim_win_is_valid(winnr) + vim.api.nvim_feedkeys(vim.keycode('G'), 'txn', false) + return result + end) + eq(true, result) + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + end) end) end) end) -- cgit From 9738b405fd6d5bf7e4d64b316f57d581f7fa49df Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 24 Nov 2024 15:04:50 +0100 Subject: vim-patch:ccc024f: runtime(apache): Update syntax directives for apache server 2.4.62 closes: vim/vim#16109 https://github.com/vim/vim/commit/ccc024f3a7978b82b4c3c249a9fda1337f1a1468 Co-authored-by: nisbet-hubbard <87453615+nisbet-hubbard@users.noreply.github.com> --- runtime/syntax/apache.vim | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/syntax/apache.vim b/runtime/syntax/apache.vim index d2b8be733b..65317fd36a 100644 --- a/runtime/syntax/apache.vim +++ b/runtime/syntax/apache.vim @@ -3,8 +3,8 @@ " Maintainer: David Necas (Yeti) " License: This file can be redistribued and/or modified under the same terms " as Vim itself. -" Last Change: 2024 Nov 23 -" Notes: Last synced with apache-2.2.3, version 1.x is no longer supported +" Last Change: 2024 Nov 24 +" Notes: Last synced with apache-2.4.62, version 1.x is no longer supported " TODO: see particular FIXME's scattered through the file " make it really linewise? " + add `display' where appropriate @@ -46,9 +46,9 @@ syn keyword apacheMethodOption GET POST PUT DELETE CONNECT OPTIONS TRACE PATCH P " Added as suggested by Mikko Koivunalho syn keyword apacheMethodOption BASELINE-CONTROL CHECKIN CHECKOUT LABEL MERGE MKACTIVITY MKWORKSPACE REPORT UNCHECKOUT UPDATE VERSION-CONTROL contained syn case ignore -syn match apacheSection "<\/\=\(Directory\|DirectoryMatch\|Files\|FilesMatch\|IfModule\|IfDefine\|Location\|LocationMatch\|VirtualHost\)[^>]*>" contains=apacheAnything +syn match apacheSection "<\/\=\(Directory\|Files\|If\|Else\|Location\|VirtualHost\)[^>]*>" contains=apacheAnything syn match apacheSection "<\/\=\(RequireAll\|RequireAny\|RequireNone\)>" contains=apacheAnything -syn match apacheLimitSection "<\/\=\(Limit\|LimitExcept\)[^>]*>" contains=apacheLimitSectionKeyword,apacheMethodOption,apacheError +syn match apacheLimitSection "<\/\=Limit[^>]*>" contains=apacheLimitSectionKeyword,apacheMethodOption,apacheError syn keyword apacheLimitSectionKeyword Limit LimitExcept contained syn match apacheAuthType "AuthType\s.*$" contains=apacheAuthTypeValue syn keyword apacheAuthTypeValue Basic Digest @@ -69,7 +69,7 @@ syn keyword apacheDeclaration AuthAuthoritative AuthGroupFile AuthUserFile syn keyword apacheDeclaration AuthBasicAuthoritative AuthBasicProvider syn keyword apacheDeclaration AuthDigestAlgorithm AuthDigestDomain AuthDigestNcCheck AuthDigestNonceFormat AuthDigestNonceLifetime AuthDigestProvider AuthDigestQop AuthDigestShmemSize syn keyword apacheOption none auth auth-int MD5 MD5-sess -syn match apacheSection "<\/\=\(]*>" contains=apacheAnything +syn match apacheSection "<\/\=Auth[ntz]ProviderAlias[^>]*>" contains=apacheAnything syn keyword apacheDeclaration Anonymous Anonymous_Authoritative Anonymous_LogEmail Anonymous_MustGiveEmail Anonymous_NoUserID Anonymous_VerifyEmail syn keyword apacheDeclaration AuthDBDUserPWQuery AuthDBDUserRealmQuery syn keyword apacheDeclaration AuthDBMGroupFile AuthDBMAuthoritative @@ -156,7 +156,7 @@ syn keyword apacheDeclaration PerlCleanupHandler PerlChildInitHandler PerlChildE syn keyword apacheDeclaration PerlRestartHandler PerlDispatchHandler syn keyword apacheDeclaration PerlFreshRestart PerlSendHeader syn keyword apacheDeclaration php_value php_flag php_admin_value php_admin_flag -syn match apacheSection "<\/\=\(Proxy\|ProxyMatch\)[^>]*>" contains=apacheAnything +syn match apacheSection "<\/\=\(Macro\|MDomain\|Proxy\)[^>]*>" contains=apacheAnything syn keyword apacheDeclaration AllowCONNECT NoProxy ProxyBadHeader ProxyBlock ProxyDomain ProxyErrorOverride ProxyIOBufferSize ProxyMaxForwards ProxyPass ProxyPassMatch ProxyPassReverse ProxyPassReverseCookieDomain ProxyPassReverseCookiePath ProxyPreserveHost ProxyReceiveBufferSize ProxyRemote ProxyRemoteMatch ProxyRequests ProxyTimeout ProxyVia syn keyword apacheDeclaration RewriteBase RewriteCond RewriteEngine RewriteLock RewriteLog RewriteLogLevel RewriteMap RewriteOptions RewriteRule syn keyword apacheOption inherit @@ -174,8 +174,8 @@ syn keyword apacheDeclaration SuexecUserGroup syn keyword apacheDeclaration UserDir syn keyword apacheDeclaration CookieDomain CookieExpires CookieName CookieStyle CookieTracking syn keyword apacheOption Netscape Cookie Cookie2 RFC2109 RFC2965 -syn match apacheSection "<\/\=\(]*>" contains=apacheAnything syn keyword apacheDeclaration VirtualDocumentRoot VirtualDocumentRootIP VirtualScriptAlias VirtualScriptAliasIP +syn keyword apacheDeclaration AcceptErrorsNonFatal AsyncFilter AsyncRequestWorkerFactor AuthBasicFake AuthBasicUseDigestAlgorithm AuthBearerAuthoritative AuthBearerProvider AuthBearerProxy AuthDBMType AuthDBMUserFile AuthFormAuthoritative AuthFormBody AuthFormDisableNoStore AuthFormFakeBasicAuth AuthFormLocation AuthFormLoginRequiredLocation AuthFormLoginSuccessLocation AuthFormLogoutLocation AuthFormMethod AuthFormMimetype AuthFormPassword AuthFormProvider AuthFormSitePassphrase AuthFormSize AuthFormUsername AuthLDAPAuthorizePrefix AuthLDAPBindAuthoritative AuthLDAPCompareAsUser AuthLDAPInitialBindAsUser AuthLDAPInitialBindPattern AuthLDAPMaxSubGroupDepth AuthLDAPRemoteUserAttribute AuthLDAPSearchAsUser AuthLDAPSubGroupAttribute AuthLDAPSubGroupClass AuthLDAPURL AuthMerging AuthnCacheContext AuthnCacheEnable AuthnCacheProvideFor AuthnCacheSOCache AuthnCacheTimeout AuthnzFcgiCheckAuthnProvider AuthnzFcgiDefineProvider AuthtJwtClaim AuthtJwtDriver AuthtJwtSign AuthtJwtVerify AuthzDBDLoginToReferer AuthzDBDQuery AuthzDBDRedirectQuery AuthzSendForbiddenOnFailure BalancerGrowth BalancerInherit BalancerMember BalancerPersist BrotliAlterETag BrotliCompressionMaxInputBlock BrotliCompressionQuality BrotliCompressionWindow BrotliFilterNote BufferSize CacheDetailHeader CacheHeader CacheIgnoreQueryString CacheIgnoreURLSessionIdentifiers CacheKeyBaseURL CacheLock CacheLockMaxAge CacheLockPath CacheMinExpire CacheQuickHandler CacheReadSize CacheReadTime CacheSocache CacheSocacheMaxSize CacheSocacheMaxTime CacheSocacheMinTime CacheSocacheReadSize CacheSocacheReadTime CacheStaleOnError CacheStoreExpired CGIDScriptTimeout CGIPassAuth CGIScriptTimeout CGIVar CheckBasenameMatch ChrootDir CookieHTTPOnly CookieSameSite CookieSecure CryptoCipher CryptoDriver CryptoIV CryptoKey CryptoSize CTAuditStorage CTLogClient CTLogConfigDB CTMaxSCTAge CTProxyAwareness CTSCTStorage CTServerHelloSCTLimit CTStaticLogConfig CTStaticSCTs DBDInitSQL DefaultRuntimeDir DefaultStateDir DeflateAlterETag DeflateInflateLimitRequestBody DeflateInflateRatioBurst DeflateInflateRatioLimit DirectoryCheckHandler DTracePrivileges FallbackResource Files FilesMatch FirehoseConnectionInput FirehoseConnectionOutput FirehoseProxyConnectionInput FirehoseProxyConnectionOutput FirehoseRequestInput FirehoseRequestOutput FlushMaxPipelined FlushMaxThreshold GlobalLog GprofDir H2CopyFiles H2Direct H2EarlyHint H2EarlyHints H2MaxDataFrameLen H2MaxSessionStreams H2MaxWorkerIdleSeconds H2MaxWorkers H2MinWorkers H2ModernTLSOnly H2OutputBuffering H2Padding H2ProxyRequests H2Push H2PushDiarySize H2PushPriority H2PushResource H2SerializeHeaders H2StreamMaxMemSize H2StreamTimeout H2TLSCoolDownSecs H2TLSWarmUpSize H2Upgrade H2WebSockets H2WindowSize HeartbeatAddress HeartbeatListen HeartbeatMaxServers HeartbeatStorage HeartbeatStorage HostnameLookups HttpProtocolOptions IndexForbiddenReturn404 IndexHeadInsert InputSed ISAPIFakeAsync KeptBodySize LDAPConnectionPoolTTL LDAPLibraryDebug LDAPReferralHopLimit LDAPReferrals LDAPRetries LDAPRetryDelay LDAPTimeout Location LocationMatch LogIOTrackTTFB LogIOTrackTTFU LogMessage LuaAuthzProvider LuaCodeCache LuaHookAccessChecker LuaHookAuthChecker LuaHookCheckUserID LuaHookFixups LuaHookInsertFilter LuaHookLog LuaHookMapToStorage LuaHookPreTranslate LuaHookTranslateName LuaHookTypeChecker LuaInherit LuaInputFilter LuaMapHandler LuaOutputFilter LuaPackageCPath LuaPackagePath LuaQuickHandler LuaRoot LuaScope MacroIgnoreBadNesting MacroIgnoreEmptyArgs MaxConnectionsPerChild MaxRangeOverlaps MaxRangeReversals MaxRanges MaxRequestWorkers MDActivationDelay MDBaseServer MDCAChallenges MDCertificateAgreement MDCertificateAuthority MDCertificateCheck MDCertificateFile MDCertificateKeyFile MDCertificateMonitor MDCertificateProtocol MDCertificateStatus MDChallengeDns01 MDChallengeDns01Version MDCheckInterval MDContactEmail MDDriveMode MDExternalAccountBinding MDHttpProxy MDMatchNames MDMember MDMembers MDMessageCmd MDMustStaple MDNotifyCmd MDomain MDPortMap MDPrivateKeys MDRenewMode MDRenewWindow MDRequireHttps MDRetryDelay MDRetryFailover MDServerStatus MDStapleOthers MDStapling MDStaplingKeepResponse MDStaplingRenewWindow MDStoreDir MDStoreLocks MDWarnWindow MemcacheConnTTL MergeSlashes MergeTrailers MimeOptions ModemStandard Mutex Order OutputSed PolicyConditional PolicyConditionalURL PolicyEnvironment PolicyFilter PolicyKeepalive PolicyKeepaliveURL PolicyLength PolicyLengthURL PolicyMaxage PolicyMaxageURL PolicyNocache PolicyNocacheURL PolicyType PolicyTypeURL PolicyValidation PolicyValidationURL PolicyVary PolicyVaryURL PolicyVersion PolicyVersionURL PrivilegesMode Protocol Protocols ProtocolsHonorOrder Proxy100Continue ProxyAddHeaders ProxyExpressDBMFile ProxyExpressDBMType ProxyExpressEnable ProxyFCGIBackendType ProxyFCGISetEnvIf ProxyFtpDirCharset ProxyFtpEscapeWildcards ProxyFtpListOnWildcard ProxyHCExpr ProxyHCTemplate ProxyHCTPsize ProxyHTMLBufSize ProxyHTMLCharsetOut ProxyHTMLDocType ProxyHTMLEnable ProxyHTMLEvents ProxyHTMLExtended ProxyHTMLFixups ProxyHTMLInterp ProxyHTMLLinks ProxyHTMLMeta ProxyHTMLStripComments ProxyHTMLURLMap ProxySCGIInternalRedirect ProxySCGISendfile ProxySet ProxySourceAddress ProxyStatus ProxyWebsocketAsync ProxyWebsocketAsyncDelay ProxyWebsocketFallbackToProxyHttp ProxyWebsocketIdleTimeout QualifyRedirectURL ReadBufferSize ReceiveBufferSize RedisConnPoolTTL RedisTimeout ReflectorHeader RegexDefaultOptions RegisterHttpMethod RemoteIPHeader RemoteIPInternalProxy RemoteIPInternalProxyList RemoteIPProxiesHeader RemoteIPProxyProtocol RemoteIPProxyProtocolExceptions RemoteIPTrustedProxy RemoteIPTrustedProxyList RemoveLanguage RequestReadTimeout SeeRequestTail Session SessionCookieMaxAge SessionCookieName SessionCookieName2 SessionCookieRemove SessionCryptoCipher SessionCryptoDriver SessionCryptoPassphrase SessionCryptoPassphraseFile SessionDBDCookieName SessionDBDCookieName2 SessionDBDCookieRemove SessionDBDDeleteLabel SessionDBDInsertLabel SessionDBDPerUser SessionDBDSelectLabel SessionDBDUpdateLabel SessionEnv SessionExclude SessionExpiryUpdateInterval SessionHeader SessionInclude SessionMaxAge SSIETag SSILastModified SSILegacyExprParser SSLCARevocationCheck SSLClientHelloVars SSLOCSPDefaultResponder SSLOCSPEnable SSLOCSPNoverify SSLOCSPOverrideResponder SSLOCSPProxyURL SSLOCSPResponderCertificateFile SSLOCSPResponderTimeout SSLOCSPResponseMaxAge SSLOCSPResponseTimeSkew SSLOCSPUseRequestNonce SSLOpenSSLConfCmd SSLPolicy SSLProxyCARevocationCheck SSLProxyCheckPeerName SSLSRPUnknownUserSeed SSLSRPVerifierFile SSLStaplingCache SSLStaplingErrorCacheTimeout SSLStaplingFakeTryLater SSLStaplingForceURL SSLStaplingResponderTimeout SSLStaplingResponseMaxAge SSLStaplingResponseTimeSkew SSLStaplingReturnResponderErrors SSLStaplingStandardCacheTimeout SSLUseStapling StrictHostCheck Substitute SubstituteInheritBefore SubstituteMaxLineLength Suexec UNCList UnDefine UndefMacro Use UseCanonicalPhysicalPort VHostCGIMode VHostCGIPrivs VHostGroup VHostPrivs VHostSecure VHostUser Warning WatchdogInterval xml2EncAlias xml2EncDefault xml2StartParse " Define the default highlighting -- cgit From ef4f13d85c2612f8dd920f4290f607ec6fdb89cc Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 24 Nov 2024 15:05:10 +0100 Subject: vim-patch:c1e6621: runtime(c3): include c3 filetype plugin closes: vim/vim#16090 https://github.com/vim/vim/commit/c1e6621a59ab6351fef24c1b28fb30ff34191b04 Co-authored-by: Turiiya <34311583+ttytm@users.noreply.github.com> --- runtime/ftplugin/c3.vim | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 runtime/ftplugin/c3.vim diff --git a/runtime/ftplugin/c3.vim b/runtime/ftplugin/c3.vim new file mode 100644 index 0000000000..6db665a03a --- /dev/null +++ b/runtime/ftplugin/c3.vim @@ -0,0 +1,14 @@ +" Vim filetype plugin +" Language: C3 +" Maintainer: Turiiya <34311583+ttytm@users.noreply.github.com> +" Last Change: 2024 Nov 24 + +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +setl comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,:// +setl commentstring=//\ %s + +let b:undo_ftplugin = 'setl com< cms<' -- cgit From 5c603064421b8829cf106c845902fcc41d3e31f2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 24 Nov 2024 22:36:33 +0800 Subject: vim-patch:9.1.0883: message history cleanup is missing some tests (#31331) Problem: message history cleanup is missing some tests Solution: Add tests, refactor common code into did_set_msghistory() (Shougo Matsushita) closes: vim/vim#16078 https://github.com/vim/vim/commit/9f860a14c308f7a9a27a6850d36790615717a710 Co-authored-by: Shougo Matsushita Co-authored-by: Milly --- runtime/doc/options.txt | 1 + runtime/lua/vim/_meta/options.lua | 1 + src/nvim/message.c | 15 ++++++++++----- src/nvim/option.c | 7 +++++++ src/nvim/options.lua | 2 ++ test/old/testdir/test_cmdline.vim | 26 ++++++++++++++++++++++++++ 6 files changed, 47 insertions(+), 5 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 64ad2d2956..ea56633c77 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4295,6 +4295,7 @@ A jump table for the options with a short description can be found at |Q_op|. global Determines how many entries are remembered in the |:messages| history. The maximum value is 10000. + Setting it to zero clears the message history. *'nrformats'* *'nf'* 'nrformats' 'nf' string (default "bin,hex") diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e485009ca2..c635d9bd3b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -4381,6 +4381,7 @@ vim.go.mouset = vim.go.mousetime --- Determines how many entries are remembered in the `:messages` history. --- The maximum value is 10000. +--- Setting it to zero clears the message history. --- --- @type integer vim.o.msghistory = 500 diff --git a/src/nvim/message.c b/src/nvim/message.c index c758d5d76f..0b1156a6bd 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -982,11 +982,6 @@ static void add_msg_hist_multihl(const char *s, int len, int hl_id, bool multili return; } - // Don't let the message history get too big - while (msg_hist_len > p_mhi) { - delete_first_msg(); - } - // allocate an entry and add the message at the end of the history struct msg_hist *p = xmalloc(sizeof(struct msg_hist)); if (s) { @@ -1018,6 +1013,8 @@ static void add_msg_hist_multihl(const char *s, int len, int hl_id, bool multili first_msg_hist = last_msg_hist; } msg_hist_len++; + + check_msg_hist(); } /// Delete the first (oldest) message from the history. @@ -1041,6 +1038,14 @@ int delete_first_msg(void) return OK; } +void check_msg_hist(void) +{ + // Don't let the message history get too big + while (msg_hist_len > 0 && msg_hist_len > p_mhi) { + (void)delete_first_msg(); + } +} + /// :messages command implementation void ex_messages(exarg_T *eap) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/option.c b/src/nvim/option.c index 669dac9773..fdc2d8da7d 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2199,6 +2199,13 @@ static const char *did_set_modified(optset_T *args) return NULL; } +/// Process the updated 'msghistory' option value. +static const char *did_set_msghistory(optset_T *args FUNC_ATTR_UNUSED) +{ + check_msg_hist(); + return NULL; +} + /// Process the updated 'number' or 'relativenumber' option value. static const char *did_set_number_relativenumber(optset_T *args) { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2d712ee101..bcb05b107b 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -5894,10 +5894,12 @@ return { }, { abbreviation = 'mhi', + cb = 'did_set_msghistory', defaults = { if_true = 500 }, desc = [=[ Determines how many entries are remembered in the |:messages| history. The maximum value is 10000. + Setting it to zero clears the message history. ]=], full_name = 'msghistory', scope = { 'global' }, diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 290af4a4ca..6fa3ee5250 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -4160,4 +4160,30 @@ func Test_cd_bslash_completion_windows() let &shellslash = save_shellslash endfunc +func Test_msghistory() + " After setting 'msghistory' to 2 and outputting a message 4 times with + " :echomsg, is the number of output lines of :messages 2? + set msghistory=2 + echomsg 'foo' + echomsg 'bar' + echomsg 'baz' + echomsg 'foobar' + call assert_equal(['baz', 'foobar'], GetMessages()) + + " When the number of messages is 10 and 'msghistory' is changed to 5, is the + " number of output lines of :messages 5? + set msghistory=10 + for num in range(1, 10) + echomsg num + endfor + set msghistory=5 + call assert_equal(5, len(GetMessages())) + + " Check empty list + set msghistory=0 + call assert_true(empty(GetMessages())) + + set msghistory& +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 21371c9259e8b60278f361b1961b297ae3a4b41e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 24 Nov 2024 22:46:21 +0800 Subject: vim-patch:d7745ac: runtime(netrw): Fixing powershell execution issues on Windows (#31333) closes: vim/vim#16094 https://github.com/vim/vim/commit/d7745acbd8fe1e4feb356a6dc7fc185eeab17d67 Co-authored-by: GuyBrush --- runtime/autoload/netrw.vim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index 3a4d3377c5..6a26a7d0b1 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -37,6 +37,7 @@ " 2024 Nov 10 by Vim Project: directory symlink not resolved in tree view (#16020) " 2024 Nov 14 by Vim Project: small fixes to netrw#BrowseX (#16056) " 2024 Nov 23 by Vim Project: update decompress defaults (#16104) +" 2024 Nov 23 by Vim Project: fix powershell escaping issues (#16094) " }}} " Former Maintainer: Charles E Campbell " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim @@ -11313,9 +11314,7 @@ endfun " --------------------------------------------------------------------- " s:NetrwExe: executes a string using "!" {{{2 fun! s:NetrwExe(cmd) -" call Dfunc("s:NetrwExe(a:cmd<".a:cmd.">)") - if has("win32") -" call Decho("using win32:",expand("")) + if has("win32") && exepath(&shell) !~? '\v[\/]?(cmd|pwsh|powershell)(\.exe)?$' && !g:netrw_cygwin let savedShell=[&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] set shell& shellcmdflag& shellxquote& shellxescape& set shellquote& shellpipe& shellredir& shellslash& @@ -11325,13 +11324,11 @@ fun! s:NetrwExe(cmd) let [&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] = savedShell endtry else -" call Decho("exe ".a:cmd,'~'.expand("")) exe a:cmd endif if v:shell_error call netrw#ErrorMsg(s:WARNING,"shell signalled an error",106) endif -" call Dret("s:NetrwExe : v:shell_error=".v:shell_error) endfun " --------------------------------------------------------------------- -- cgit From af112e605d4d8f3015744b69ec7544d732f8dad9 Mon Sep 17 00:00:00 2001 From: Axel Date: Sun, 24 Nov 2024 16:44:37 +0100 Subject: docs(build): suggest ucrt64 for msys2 #31312 Problem: The default invocation would install the non-ucrt gcc package, the build would fail now that UCRT is not supported. #22534 Solution: Mention mingw-w64-ucrt pacman package instead. --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index e5b1dcc707..f4596723fb 100644 --- a/BUILD.md +++ b/BUILD.md @@ -131,12 +131,12 @@ https://github.com/cascent/neovim-cygwin was built on Cygwin 2.9.0. Newer `libuv 1. From the MSYS2 shell, install these packages: ``` pacman -S \ - mingw-w64-x86_64-{gcc,cmake,make,ninja,diffutils} + mingw-w64-ucrt-x86_64-{gcc,cmake,make,ninja,diffutils} ``` 2. From the Windows Command Prompt (`cmd.exe`), set up the `PATH` and build. ```cmd - set PATH=c:\msys64\mingw64\bin;c:\msys64\usr\bin;%PATH% + set PATH=c:\msys64\ucrt64\bin;c:\msys64\usr\bin;%PATH% ``` 3. You have two options: - Build using `cmake` and `Ninja` generator: -- cgit From ff00d583af8f1ce21a508d701d09c54d2621b16e Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 24 Nov 2024 15:05:48 +0100 Subject: vim-patch:9.1.0886: filetype: debian control file not detected Problem: filetype: debian control file not detected Solution: detect 'debian/control' files as debcontrol filetype (author) closes: vim/vim#16067 https://github.com/vim/vim/commit/57b947e3c3b8d52b914158979263855d785445d5 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 ++ runtime/lua/vim/filetype/detect.lua | 3 ++- test/old/testdir/test_filetype.vim | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 4383c0983e..aa566973b6 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1490,6 +1490,7 @@ local filename = { ['NEWS.dch'] = 'debchangelog', ['NEWS.Debian'] = 'debchangelog', ['/debian/control'] = 'debcontrol', + ['/DEBIAN/control'] = 'debcontrol', ['/debian/copyright'] = 'debcopyright', ['/etc/apt/sources.list'] = 'debsources', ['denyhosts.conf'] = 'denyhosts', @@ -2319,6 +2320,7 @@ local pattern = { ['%.cmake%.in$'] = 'cmake', ['^crontab%.'] = starsetf('crontab'), ['^cvs%d+$'] = 'cvs', + ['/DEBIAN/control$'] = 'debcontrol', ['^php%.ini%-'] = 'dosini', ['^php%-fpm%.conf'] = 'dosini', ['^www%.conf'] = 'dosini', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 81b94c69db..4f2fef5b1f 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -237,7 +237,8 @@ end --- Debian Control --- @type vim.filetype.mapfn function M.control(_, bufnr) - if getline(bufnr, 1):find('^Source:') then + local line1 = getline(bufnr, 1) + if line1 and findany(line1, { '^Source:', '^Package:' }) then return 'debcontrol' end end diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 62e339c1ca..58064ea412 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -211,7 +211,7 @@ func s:GetFilenameChecks() abort \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'], - \ 'debcontrol': ['/debian/control', 'any/debian/control'], + \ 'debcontrol': ['/debian/control', 'any/debian/control', 'any/DEBIAN/control'], \ 'debcopyright': ['/debian/copyright', 'any/debian/copyright'], \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'], \ 'deb822sources': ['/etc/apt/sources.list.d/file.sources', 'any/etc/apt/sources.list.d/file.sources'], -- cgit From 9e7b0bcf51924716461f838a33a9508b718934b5 Mon Sep 17 00:00:00 2001 From: Theo Fabi <92238946+theofabilous@users.noreply.github.com> Date: Sun, 24 Nov 2024 23:04:27 -0500 Subject: fix(editorconfig): fix indent style for `local.mk` (#31342) --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 07993e25b9..0d54d6cb33 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,6 +13,6 @@ max_line_length = 100 [*.py] indent_size = 4 -[{Makefile,**/Makefile,runtime/doc/*.txt}] +[{Makefile,**/Makefile,*.mk,runtime/doc/*.txt}] indent_style = tab indent_size = 8 -- cgit From beec377e905baca73e772080c4f276c800ad2a40 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Mon, 25 Nov 2024 15:07:56 +0600 Subject: refactor(options): fix confusing naming of `scope` and `req_scope` (#31317) Problem: The name `scope` is often used to refer to option flags because `OPT_LOCAL` and `OPT_GLOBAL` are often used to determine the option scope. This leads to the name `req_scope` being used for actual option scopes instead. Solution: Since the end-goal is to remove `OPT_LOCAL` and `OPT_GLOBAL` entirely and replace them with `OptScope`, rename `OptScope` variables to `scope` and the old scope flag variables to `opt_flags`. --- src/nvim/api/deprecated.c | 22 ++++----- src/nvim/api/options.c | 57 +++++++++++------------ src/nvim/eval.c | 21 +++++---- src/nvim/eval/vars.c | 8 ++-- src/nvim/option.c | 113 +++++++++++++++++++++++----------------------- 5 files changed, 111 insertions(+), 110 deletions(-) diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index b38a7d4173..493856905f 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -636,12 +636,12 @@ void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object /// Gets the value of a global or local (buffer, window) option. /// /// @param[in] from Pointer to buffer or window for local option value. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param name The option name. /// @param[out] err Details of an error that may have occurred. /// /// @return the option value. -static Object get_option_from(void *from, OptScope req_scope, String name, Error *err) +static Object get_option_from(void *from, OptScope scope, String name, Error *err) { VALIDATE_S(name.size > 0, "option name", "", { return (Object)OBJECT_INIT; @@ -650,9 +650,9 @@ static Object get_option_from(void *from, OptScope req_scope, String name, Error OptIndex opt_idx = find_option(name.data); OptVal value = NIL_OPTVAL; - if (option_has_scope(opt_idx, req_scope)) { - value = get_option_value_for(opt_idx, req_scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, - req_scope, from, err); + if (option_has_scope(opt_idx, scope)) { + value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, + scope, from, err); if (ERROR_SET(err)) { return (Object)OBJECT_INIT; } @@ -668,12 +668,12 @@ static Object get_option_from(void *from, OptScope req_scope, String name, Error /// Sets the value of a global or local (buffer, window) option. /// /// @param[in] to Pointer to buffer or window for local option value. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param name The option name. /// @param value New option value. /// @param[out] err Details of an error that may have occurred. -static void set_option_to(uint64_t channel_id, void *to, OptScope req_scope, String name, - Object value, Error *err) +static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String name, Object value, + Error *err) { VALIDATE_S(name.size > 0, "option name", "", { return; @@ -698,12 +698,12 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope req_scope, Str // For global-win-local options -> setlocal // For win-local options -> setglobal and setlocal (opt_flags == 0) const int opt_flags - = (req_scope == kOptScopeWin && !option_has_scope(opt_idx, kOptScopeGlobal)) + = (scope == kOptScopeWin && !option_has_scope(opt_idx, kOptScopeGlobal)) ? 0 - : ((req_scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); + : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, opt_flags, req_scope, to, err); + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 8fd9904f7a..bfd63ca489 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -23,15 +23,15 @@ #endif static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *opt_idxp, - int *scope, OptScope *req_scope, void **from, char **filetype, + int *opt_flags, OptScope *scope, void **from, char **filetype, Error *err) { #define HAS_KEY_X(d, v) HAS_KEY(d, option, v) if (HAS_KEY_X(opts, scope)) { if (!strcmp(opts->scope.data, "local")) { - *scope = OPT_LOCAL; + *opt_flags = OPT_LOCAL; } else if (!strcmp(opts->scope.data, "global")) { - *scope = OPT_GLOBAL; + *opt_flags = OPT_GLOBAL; } else { VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, { return FAIL; @@ -39,14 +39,14 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } } - *req_scope = kOptScopeGlobal; + *scope = kOptScopeGlobal; if (filetype != NULL && HAS_KEY_X(opts, filetype)) { *filetype = opts->filetype.data; } if (HAS_KEY_X(opts, win)) { - *req_scope = kOptScopeWin; + *scope = kOptScopeWin; *from = find_window_by_handle(opts->win, err); if (ERROR_SET(err)) { return FAIL; @@ -54,12 +54,12 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } if (HAS_KEY_X(opts, buf)) { - VALIDATE(!(HAS_KEY_X(opts, scope) && *scope == OPT_GLOBAL), "%s", + VALIDATE(!(HAS_KEY_X(opts, scope) && *opt_flags == OPT_GLOBAL), "%s", "cannot use both global 'scope' and 'buf'", { return FAIL; }); - *scope = OPT_LOCAL; - *req_scope = kOptScopeBuf; + *opt_flags = OPT_LOCAL; + *scope = kOptScopeBuf; *from = find_buffer_by_handle(opts->buf, err); if (ERROR_SET(err)) { return FAIL; @@ -81,10 +81,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * if (*opt_idxp == kOptInvalid) { // unknown option api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name); - } else if (*req_scope == kOptScopeBuf || *req_scope == kOptScopeWin) { + } else if (*scope == kOptScopeBuf || *scope == kOptScopeWin) { // if 'buf' or 'win' is passed, make sure the option supports it - if (!option_has_scope(*opt_idxp, *req_scope)) { - char *tgt = *req_scope == kOptScopeBuf ? "buf" : "win"; + if (!option_has_scope(*opt_idxp, *scope)) { + char *tgt = *scope == kOptScopeBuf ? "buf" : "win"; char *global = option_has_scope(*opt_idxp, kOptScopeGlobal) ? "global " : ""; char *req = option_has_scope(*opt_idxp, kOptScopeBuf) ? "buffer-local " @@ -151,13 +151,13 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) FUNC_API_SINCE(9) FUNC_API_RET_ALLOC { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, &filetype, - err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, + &filetype, err)) { return (Object)OBJECT_INIT; } @@ -181,7 +181,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) from = ftbuf; } - OptVal value = get_option_value_for(opt_idx, scope, req_scope, from, err); + OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); if (ftbuf != NULL) { // restore curwin/curbuf and a few other things @@ -224,10 +224,11 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( FUNC_API_SINCE(9) { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *to = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &to, NULL, err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &to, NULL, + err)) { return; } @@ -237,9 +238,9 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( // - option is global or local to window (global-local) // // Then force scope to local since we don't want to change the global option - if (req_scope == kOptScopeWin && scope == 0) { + if (scope == kOptScopeWin && opt_flags == 0) { if (option_has_scope(opt_idx, kOptScopeGlobal)) { - scope = OPT_LOCAL; + opt_flags = OPT_LOCAL; } } @@ -255,7 +256,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( }); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, scope, req_scope, to, err); + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } @@ -310,16 +311,16 @@ Dict nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, Error FUNC_API_SINCE(11) { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *from = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, NULL, + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, NULL, err)) { return (Dict)ARRAY_DICT_INIT; } - buf_T *buf = (req_scope == kOptScopeBuf) ? (buf_T *)from : curbuf; - win_T *win = (req_scope == kOptScopeWin) ? (win_T *)from : curwin; + buf_T *buf = (scope == kOptScopeBuf) ? (buf_T *)from : curbuf; + win_T *win = (scope == kOptScopeWin) ? (win_T *)from : curwin; - return get_vimoption(name, scope, buf, win, arena, err); + return get_vimoption(name, opt_flags, buf, win, arena, err); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 35f0bde871..47f0a13b29 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4114,10 +4114,10 @@ int eval_option(const char **const arg, typval_T *const rettv, const bool evalua { const bool working = (**arg == '+'); // has("+option") OptIndex opt_idx; - int scope; + int opt_flags; // Isolate the option name and find its value. - char *const option_end = (char *)find_option_var_end(arg, &opt_idx, &scope); + char *const option_end = (char *)find_option_var_end(arg, &opt_idx, &opt_flags); if (option_end == NULL) { if (rettv != NULL) { @@ -4145,7 +4145,7 @@ int eval_option(const char **const arg, typval_T *const rettv, const bool evalua ret = FAIL; } else if (rettv != NULL) { - OptVal value = is_tty_opt ? get_tty_option(*arg) : get_option_value(opt_idx, scope); + OptVal value = is_tty_opt ? get_tty_option(*arg) : get_option_value(opt_idx, opt_flags); assert(value.type != kOptValTypeNil); *rettv = optval_as_tv(value, true); @@ -7988,24 +7988,25 @@ void ex_execute(exarg_T *eap) /// Skip over the name of an option variable: "&option", "&g:option" or "&l:option". /// -/// @param[in,out] arg Points to the "&" or '+' when called, to "option" when returning. -/// @param[out] opt_idxp Set to option index in options[] table. -/// @param[out] scope Set to option scope. +/// @param[in,out] arg Points to the "&" or '+' when called, to "option" when returning. +/// @param[out] opt_idxp Set to option index in options[] table. +/// @param[out] opt_flags Option flags. /// /// @return NULL when no option name found. Otherwise pointer to the char after the option name. -const char *find_option_var_end(const char **const arg, OptIndex *const opt_idxp, int *const scope) +const char *find_option_var_end(const char **const arg, OptIndex *const opt_idxp, + int *const opt_flags) { const char *p = *arg; p++; if (*p == 'g' && p[1] == ':') { - *scope = OPT_GLOBAL; + *opt_flags = OPT_GLOBAL; p += 2; } else if (*p == 'l' && p[1] == ':') { - *scope = OPT_LOCAL; + *opt_flags = OPT_LOCAL; p += 2; } else { - *scope = 0; + *opt_flags = 0; } const char *end = find_option_end(p, opt_idxp); diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 3ecb446cd6..8c8a8ac5e0 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -810,9 +810,9 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, // Find the end of the name. char *arg_end = NULL; OptIndex opt_idx; - int scope; + int opt_flags; - char *const p = (char *)find_option_var_end((const char **)&arg, &opt_idx, &scope); + char *const p = (char *)find_option_var_end((const char **)&arg, &opt_idx, &opt_flags); if (p == NULL || (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) { emsg(_(e_letunexp)); @@ -824,7 +824,7 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, bool is_tty_opt = is_tty_option(arg); bool hidden = is_option_hidden(opt_idx); - OptVal curval = is_tty_opt ? get_tty_option(arg) : get_option_value(opt_idx, scope); + OptVal curval = is_tty_opt ? get_tty_option(arg) : get_option_value(opt_idx, opt_flags); OptVal newval = NIL_OPTVAL; if (curval.type == kOptValTypeNil) { @@ -881,7 +881,7 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, } } - const char *err = set_option_value_handle_tty(arg, opt_idx, newval, scope); + const char *err = set_option_value_handle_tty(arg, opt_idx, newval, opt_flags); arg_end = p; if (err != NULL) { emsg(_(err)); diff --git a/src/nvim/option.c b/src/nvim/option.c index fdc2d8da7d..27b80c0ac8 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -429,7 +429,7 @@ void set_init_1(bool clean_arg) /// Get default value for option, based on the option's type and scope. /// /// @param opt_idx Option index in options[] table. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// /// @return Default value of option for the scope specified in opt_flags. static OptVal get_option_default(const OptIndex opt_idx, int opt_flags) @@ -480,7 +480,7 @@ static void change_option_default(const OptIndex opt_idx, OptVal value) /// This does not take care of side effects! /// /// @param opt_idx Option index in options[] table. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). static void set_option_default(const OptIndex opt_idx, int opt_flags) { OptVal def_val = get_option_default(opt_idx, opt_flags); @@ -497,7 +497,7 @@ static void set_option_default(const OptIndex opt_idx, int opt_flags) /// Set all options (except terminal options) to their default value. /// -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). static void set_options_default(int opt_flags) { for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) { @@ -1475,7 +1475,7 @@ void did_set_title(void) /// set_options_bin - called when 'bin' changes value. /// -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). void set_options_bin(int oldval, int newval, int opt_flags) { // The option values that are changed when 'bin' changes are @@ -1652,7 +1652,7 @@ void check_options(void) /// /// @param wp Window. /// @param opt_idx Option index in options[] table. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// /// @return True if option was set from a modeline or in secure mode, false if it wasn't. int was_set_insecurely(win_T *const wp, OptIndex opt_idx, int opt_flags) @@ -3391,18 +3391,18 @@ uint32_t get_option_flags(OptIndex opt_idx) /// Gets the value for an option. /// -/// @param opt_idx Option index in options[] table. -/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination). +/// @param opt_idx Option index in options[] table. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// /// @return [allocated] Option value. Returns NIL_OPTVAL for invalid option index. -OptVal get_option_value(OptIndex opt_idx, int scope) +OptVal get_option_value(OptIndex opt_idx, int opt_flags) { if (opt_idx == kOptInvalid) { // option not in the options[] table. return NIL_OPTVAL; } vimoption_T *opt = &options[opt_idx]; - void *varp = get_varp_scope(opt, scope); + void *varp = get_varp_scope(opt, opt_flags); return optval_copy(optval_from_varp(opt_idx, varp)); } @@ -3475,7 +3475,7 @@ static bool is_option_local_value_unset(OptIndex opt_idx) /// @param opt_idx Index in options[] table. Must not be kOptInvalid. /// @param[in] varp Option variable pointer, cannot be NULL. /// @param old_value Old option value. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. /// SID_NONE: Don't set script ID. @@ -3487,8 +3487,7 @@ static bool is_option_local_value_unset(OptIndex opt_idx) /// @return NULL on success, an untranslated error message on error. static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value, OptVal new_value, int opt_flags, scid_T set_sid, const bool direct, - const bool value_replaced, char *errbuf, // NOLINT(readability-non-const-parameter) - size_t errbuflen) + const bool value_replaced, char *errbuf, size_t errbuflen) { vimoption_T *opt = &options[opt_idx]; const char *errmsg = NULL; @@ -3688,7 +3687,7 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval, /// /// @param opt_idx Index in options[] table. Must not be kOptInvalid. /// @param value New option value. Might get freed. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. /// SID_NONE: Don't set script ID. @@ -3792,7 +3791,7 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag /// /// @param opt_idx Option index in options[] table. /// @param value Option value. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. /// SID_NONE: Don't set script ID. @@ -3814,14 +3813,14 @@ void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set /// /// @param opt_idx Option index in options[] table. /// @param value Option value. -/// @param opt_flags Option flags. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. /// SID_NONE: Don't set script ID. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param[in] from Target buffer/window. void set_option_direct_for(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid, - OptScope req_scope, void *const from) + OptScope scope, void *const from) { buf_T *save_curbuf = curbuf; win_T *save_curwin = curwin; @@ -3829,7 +3828,7 @@ void set_option_direct_for(OptIndex opt_idx, OptVal value, int opt_flags, scid_T // Don't use switch_option_context(), as that calls aucmd_prepbuf(), which may have unintended // side-effects when setting an option directly. Just change the values of curbuf and curwin if // needed, no need to properly switch the window / buffer. - switch (req_scope) { + switch (scope) { case kOptScopeGlobal: break; case kOptScopeWin: @@ -3912,7 +3911,7 @@ const char *set_option_value_handle_tty(const char *name, OptIndex opt_idx, cons /// /// @param opt_idx Option index in options[] table. /// @param value Option value. If NIL_OPTVAL, the option value is cleared. -/// @param opt_flags OPT_LOCAL or 0 (both) +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). void set_option_value_give_err(const OptIndex opt_idx, OptVal value, int opt_flags) { const char *errmsg = set_option_value(opt_idx, value, opt_flags); @@ -3925,14 +3924,14 @@ void set_option_value_give_err(const OptIndex opt_idx, OptVal value, int opt_fla /// Switch current context to get/set option value for window/buffer. /// /// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param[in] from Target buffer/window. /// @param[out] err Error message, if any. /// /// @return true if context was switched, false otherwise. -static bool switch_option_context(void *const ctx, OptScope req_scope, void *const from, Error *err) +static bool switch_option_context(void *const ctx, OptScope scope, void *const from, Error *err) { - switch (req_scope) { + switch (scope) { case kOptScopeGlobal: return false; case kOptScopeWin: { @@ -3971,9 +3970,9 @@ static bool switch_option_context(void *const ctx, OptScope req_scope, void *con /// Restore context after getting/setting option for window/buffer. See switch_option_context() for /// params. -static void restore_option_context(void *const ctx, OptScope req_scope) +static void restore_option_context(void *const ctx, OptScope scope) { - switch (req_scope) { + switch (scope) { case kOptScopeGlobal: break; case kOptScopeWin: @@ -3991,28 +3990,28 @@ static void restore_option_context(void *const ctx, OptScope req_scope) /// @param[out] flagsp Set to the option flags (see OptFlags) (if not NULL). /// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param[out] hidden Whether option is hidden. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param[in] from Target buffer/window. /// @param[out] err Error message, if any. /// /// @return Option value. Must be freed by caller. -OptVal get_option_value_for(OptIndex opt_idx, int scope, const OptScope req_scope, void *const from, +OptVal get_option_value_for(OptIndex opt_idx, int opt_flags, const OptScope scope, void *const from, Error *err) { switchwin_T switchwin; aco_save_T aco; - void *ctx = req_scope == kOptScopeWin ? (void *)&switchwin - : (req_scope == kOptScopeBuf ? (void *)&aco : NULL); + void *ctx = scope == kOptScopeWin ? (void *)&switchwin + : (scope == kOptScopeBuf ? (void *)&aco : NULL); - bool switched = switch_option_context(ctx, req_scope, from, err); + bool switched = switch_option_context(ctx, scope, from, err); if (ERROR_SET(err)) { return NIL_OPTVAL; } - OptVal retv = get_option_value(opt_idx, scope); + OptVal retv = get_option_value(opt_idx, opt_flags); if (switched) { - restore_option_context(ctx, req_scope); + restore_option_context(ctx, scope); } return retv; @@ -4024,19 +4023,19 @@ OptVal get_option_value_for(OptIndex opt_idx, int scope, const OptScope req_scop /// @param opt_idx Option index in options[] table. /// @param[in] value Option value. /// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param[in] from Target buffer/window. /// @param[out] err Error message, if any. void set_option_value_for(const char *name, OptIndex opt_idx, OptVal value, const int opt_flags, - const OptScope req_scope, void *const from, Error *err) + const OptScope scope, void *const from, Error *err) FUNC_ATTR_NONNULL_ARG(1) { switchwin_T switchwin; aco_save_T aco; - void *ctx = req_scope == kOptScopeWin ? (void *)&switchwin - : (req_scope == kOptScopeBuf ? (void *)&aco : NULL); + void *ctx = scope == kOptScopeWin ? (void *)&switchwin + : (scope == kOptScopeBuf ? (void *)&aco : NULL); - bool switched = switch_option_context(ctx, req_scope, from, err); + bool switched = switch_option_context(ctx, scope, from, err); if (ERROR_SET(err)) { return; } @@ -4047,14 +4046,14 @@ void set_option_value_for(const char *name, OptIndex opt_idx, OptVal value, cons } if (switched) { - restore_option_context(ctx, req_scope); + restore_option_context(ctx, scope); } } /// if 'all' == false: show changed options /// if 'all' == true: show all normal options /// -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). static void showoptions(bool all, int opt_flags) { #define INC 20 @@ -4181,7 +4180,7 @@ void ui_refresh_options(void) /// showoneopt: show the value of one option /// must not be called with a hidden option! /// -/// @param opt_flags OPT_LOCAL or OPT_GLOBAL +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). static void showoneopt(vimoption_T *opt, int opt_flags) { int save_silent = silent_mode; @@ -4466,18 +4465,18 @@ static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp) return OK; } -void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) +void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win) { OptIndex opt_idx = get_opt_idx(p); - if ((scope & OPT_GLOBAL) && !option_is_global_only(opt_idx)) { + if ((opt_flags & OPT_GLOBAL) && !option_is_global_only(opt_idx)) { if (option_is_window_local(opt_idx)) { return GLOBAL_WO(get_varp_from(p, buf, win)); } return p->var; } - if ((scope & OPT_LOCAL) && option_is_global_local(opt_idx)) { + if ((opt_flags & OPT_LOCAL) && option_is_global_local(opt_idx)) { switch (opt_idx) { case kOptFormatprg: return &(buf->b_p_fp); @@ -4548,17 +4547,17 @@ void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) /// Get pointer to option variable, depending on local or global scope. /// -/// @param scope can be OPT_LOCAL, OPT_GLOBAL or a combination. -void *get_varp_scope(vimoption_T *p, int scope) +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). +void *get_varp_scope(vimoption_T *p, int opt_flags) { - return get_varp_scope_from(p, scope, curbuf, curwin); + return get_varp_scope_from(p, opt_flags, curbuf, curwin); } /// Get pointer to option variable at 'opt_idx', depending on local or global /// scope. -void *get_option_varp_scope_from(OptIndex opt_idx, int scope, buf_T *buf, win_T *win) +void *get_option_varp_scope_from(OptIndex opt_idx, int opt_flags, buf_T *buf, win_T *win) { - return get_varp_scope_from(&(options[opt_idx]), scope, buf, win); + return get_varp_scope_from(&(options[opt_idx]), opt_flags, buf, win); } void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) @@ -5359,7 +5358,7 @@ static char expand_option_name[5] = { 't', '_', NUL, NUL, NUL }; static int expand_option_flags = 0; static bool expand_option_append = false; -/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) { expand_option_flags = opt_flags; @@ -5911,12 +5910,12 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c /// Get the value for the numeric or string option///opp in a nice format into /// NameBuff[]. Must not be called with a hidden option! /// -/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// /// TODO(famiu): Replace this with optval_to_cstr() if possible. -static void option_value2string(vimoption_T *opt, int scope) +static void option_value2string(vimoption_T *opt, int opt_flags) { - void *varp = get_varp_scope(opt, scope); + void *varp = get_varp_scope(opt, opt_flags); assert(varp != NULL); if (option_has_type(get_opt_idx(opt), kOptValTypeNumber)) { @@ -6233,7 +6232,7 @@ int default_fileformat(void) /// Sets 'fileformat'. /// /// @param eol_style End-of-line style. -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). void set_fileformat(int eol_style, int opt_flags) { char *p = NULL; @@ -6364,14 +6363,14 @@ int get_sidescrolloff_value(win_T *wp) return (int)(wp->w_p_siso < 0 ? p_siso : wp->w_p_siso); } -Dict get_vimoption(String name, int scope, buf_T *buf, win_T *win, Arena *arena, Error *err) +Dict get_vimoption(String name, int opt_flags, buf_T *buf, win_T *win, Arena *arena, Error *err) { OptIndex opt_idx = find_option_len(name.data, name.size); VALIDATE_S(opt_idx != kOptInvalid, "option (not found)", name.data, { return (Dict)ARRAY_DICT_INIT; }); - return vimoption2dict(&options[opt_idx], scope, buf, win, arena); + return vimoption2dict(&options[opt_idx], opt_flags, buf, win, arena); } Dict get_all_vimoptions(Arena *arena) @@ -6384,7 +6383,7 @@ Dict get_all_vimoptions(Arena *arena) return retval; } -static Dict vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win, Arena *arena) +static Dict vimoption2dict(vimoption_T *opt, int opt_flags, buf_T *buf, win_T *win, Arena *arena) { OptIndex opt_idx = get_opt_idx(opt); Dict dict = arena_dict(arena, 13); @@ -6411,7 +6410,7 @@ static Dict vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *w PUT_C(dict, "was_set", BOOLEAN_OBJ(opt->flags & kOptFlagWasSet)); LastSet last_set = { .channel_id = 0 }; - if (req_scope == OPT_GLOBAL) { + if (opt_flags == OPT_GLOBAL) { last_set = opt->last_set; } else { // Scope is either OPT_LOCAL or a fallback mode was requested. @@ -6421,7 +6420,7 @@ static Dict vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *w if (option_has_scope(opt_idx, kOptScopeWin)) { last_set = win->w_p_script_ctx[opt->scope_idx[kOptScopeWin]]; } - if (req_scope != OPT_LOCAL && last_set.script_ctx.sc_sid == 0) { + if (opt_flags != OPT_LOCAL && last_set.script_ctx.sc_sid == 0) { last_set = opt->last_set; } } -- cgit From b47b0b3f757d58a76242ac325a35cac6fbeffc07 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 25 Nov 2024 14:54:43 +0100 Subject: fix(grid): double grid_line_start() with ext_messages #31292 Problem: Hit double grid_line_start() assert when redrawing from ext_messages msg_ruler event. Solution: Do not start() batched grid calls when win_redr_ruler() will not puts() anything. --- src/nvim/drawscreen.c | 8 ++++++-- test/functional/ui/messages_spec.lua | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 3719d38df2..19a8093a16 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1092,9 +1092,13 @@ int showmode(void) win_T *ruler_win = curwin->w_status_height == 0 ? curwin : lastwin_nofloating(); if (redrawing() && ruler_win->w_status_height == 0 && global_stl_height() == 0 && !(p_ch == 0 && !ui_has(kUIMessages))) { - grid_line_start(&msg_grid_adj, Rows - 1); + if (!ui_has(kUIMessages)) { + grid_line_start(&msg_grid_adj, Rows - 1); + } win_redr_ruler(ruler_win); - grid_line_flush(); + if (!ui_has(kUIMessages)) { + grid_line_flush(); + } } redraw_cmdline = false; diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 9a6dfd8ed1..164b840b35 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1159,6 +1159,21 @@ stack traceback: exec_lua([[vim.print({ foo = "bar" })]]) screen:expect_unchanged() end) + + it('ruler redraw does not crash due to double grid_line_start()', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.ui_attach(ns, { ext_messages = true }, function(event, ...) + if event == 'msg_ruler' then + vim.api.nvim__redraw({ flush = true }) + end + end) + vim.o.ruler = true + vim.o.laststatus = 0 + ]]) + feed('i') + n.assert_alive() + end) end) describe('ui/builtin messages', function() -- cgit From a811d4babd22e21a3cd598b097fa0824fcff7fc6 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 25 Nov 2024 09:00:42 -0600 Subject: fix(tui): only reset cursor color if it was changed (#31337) We already track this information so we might as well use it. This eliminates a bunch of unnecessary OSC sequences. --- src/nvim/tui/tui.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2839a665da..cb893ec777 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -107,7 +107,7 @@ struct TUIData { bool busy, is_invisible, want_invisible; bool cork, overflow; bool set_cursor_color_as_str; - bool cursor_color_changed; + bool cursor_has_color; bool is_starting; bool did_set_grapheme_cluster_mode; FILE *screenshot; @@ -336,7 +336,7 @@ static void terminfo_start(TUIData *tui) tui->cork = false; tui->overflow = false; tui->set_cursor_color_as_str = false; - tui->cursor_color_changed = false; + tui->cursor_has_color = false; tui->showing_mode = SHAPE_IDX_N; tui->unibi_ext.enable_mouse = -1; tui->unibi_ext.disable_mouse = -1; @@ -521,7 +521,7 @@ static void terminfo_stop(TUIData *tui) // Exit alternate screen. unibi_out(tui, unibi_exit_ca_mode); } - if (tui->cursor_color_changed) { + if (tui->cursor_has_color) { unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } // Disable bracketed paste @@ -1302,11 +1302,12 @@ static void tui_set_mode(TUIData *tui, ModeShape mode) UNIBI_SET_NUM_VAR(tui->params[0], aep.rgb_bg_color); } unibi_out_ext(tui, tui->unibi_ext.set_cursor_color); - tui->cursor_color_changed = true; + tui->cursor_has_color = true; } - } else if (c.id == 0) { + } else if (c.id == 0 && tui->cursor_has_color) { // No cursor color for this mode; reset to default. tui->want_invisible = false; + tui->cursor_has_color = false; unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } -- cgit From 165b099fa38c5f4a9855cda3d13575bf63767647 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Tue, 26 Nov 2024 00:06:05 +0800 Subject: refactor(lsp): rename `offset_encoding` to `position_encoding` #31286 Problem: LSP spec uses the term "position encoding" where we say "offset encoding". Solution: - Rename it everywhere except `vim.lsp.Client.offset_encoding` (which would be breaking). - Mention "position encoding" in the documentation for `vim.lsp.Client.offset_encoding`. --- runtime/doc/lsp.txt | 98 ++++++++------- runtime/doc/news.txt | 2 +- runtime/lua/vim/lsp/_changetracking.lua | 10 +- runtime/lua/vim/lsp/_tagfunc.lua | 16 +-- runtime/lua/vim/lsp/buf.lua | 8 +- runtime/lua/vim/lsp/client.lua | 11 +- runtime/lua/vim/lsp/completion.lua | 6 +- runtime/lua/vim/lsp/diagnostic.lua | 6 +- runtime/lua/vim/lsp/sync.lua | 52 ++++---- runtime/lua/vim/lsp/util.lua | 140 +++++++++++---------- .../plugin/lsp/incremental_sync_spec.lua | 10 +- test/functional/script/luacats_grammar_spec.lua | 4 +- 12 files changed, 188 insertions(+), 175 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 3cb3e590f4..1253c01547 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -904,8 +904,9 @@ Lua module: vim.lsp.client *lsp-client* • {rpc} (`vim.lsp.rpc.PublicClient`) RPC client object, for low level interaction with the client. See |vim.lsp.rpc.start()|. - • {offset_encoding} (`string`) The encoding used for communicating - with the server. You can modify this in the + • {offset_encoding} (`string`) Called "position encoding" in LSP + spec, the encoding used for communicating with + the server. You can modify this in the `config`'s `on_init` method before text is sent to the server. • {handlers} (`table`) The handlers @@ -1062,9 +1063,10 @@ Lua module: vim.lsp.client *lsp-client* • {get_language_id}? (`fun(bufnr: integer, filetype: string): string`) Language ID as string. Defaults to the buffer filetype. - • {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) The encoding that - the LSP server expects. Client does not verify - this is correct. + • {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position + encoding" in LSP spec, the encoding that the LSP + server expects. Client does not verify this is + correct. • {on_error}? (`fun(code: integer, err: string)`) Callback invoked when the client operation throws an error. `code` is a number describing the error. @@ -1840,7 +1842,7 @@ Lua module: vim.lsp.util *lsp-util* *vim.lsp.util.apply_text_document_edit()* -apply_text_document_edit({text_document_edit}, {index}, {offset_encoding}) +apply_text_document_edit({text_document_edit}, {index}, {position_encoding}) Applies a `TextDocumentEdit`, which is a list of changes to a single document. @@ -1848,30 +1850,30 @@ apply_text_document_edit({text_document_edit}, {index}, {offset_encoding}) • {text_document_edit} (`lsp.TextDocumentEdit`) • {index} (`integer?`) Optional index of the edit, if from a list of edits (or nil, if not from a list) - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) See also: ~ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit *vim.lsp.util.apply_text_edits()* -apply_text_edits({text_edits}, {bufnr}, {offset_encoding}) +apply_text_edits({text_edits}, {bufnr}, {position_encoding}) Applies a list of text edits to a buffer. Parameters: ~ - • {text_edits} (`lsp.TextEdit[]`) - • {bufnr} (`integer`) Buffer id - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) + • {text_edits} (`lsp.TextEdit[]`) + • {bufnr} (`integer`) Buffer id + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`) See also: ~ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit *vim.lsp.util.apply_workspace_edit()* -apply_workspace_edit({workspace_edit}, {offset_encoding}) +apply_workspace_edit({workspace_edit}, {position_encoding}) Applies a `WorkspaceEdit`. Parameters: ~ - • {workspace_edit} (`lsp.WorkspaceEdit`) - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) (required) + • {workspace_edit} (`lsp.WorkspaceEdit`) + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`) (required) See also: ~ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit @@ -1883,13 +1885,13 @@ buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* • {bufnr} (`integer?`) Buffer id *vim.lsp.util.buf_highlight_references()* -buf_highlight_references({bufnr}, {references}, {offset_encoding}) +buf_highlight_references({bufnr}, {references}, {position_encoding}) Shows a list of document highlights for a certain buffer. Parameters: ~ - • {bufnr} (`integer`) Buffer id - • {references} (`lsp.DocumentHighlight[]`) objects to highlight - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) + • {bufnr} (`integer`) Buffer id + • {references} (`lsp.DocumentHighlight[]`) objects to highlight + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`) See also: ~ • https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent @@ -1964,7 +1966,7 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* • 'shiftwidth' *vim.lsp.util.locations_to_items()* -locations_to_items({locations}, {offset_encoding}) +locations_to_items({locations}, {position_encoding}) Returns the items with the byte position calculated correctly and in sorted order, for display in quickfix and location lists. @@ -1975,9 +1977,9 @@ locations_to_items({locations}, {offset_encoding}) |setloclist()|. Parameters: ~ - • {locations} (`lsp.Location[]|lsp.LocationLink[]`) - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) default to first - client of buffer + • {locations} (`lsp.Location[]|lsp.LocationLink[]`) + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) default to first + client of buffer Return: ~ (`vim.quickfix.entry[]`) See |setqflist()| for the format @@ -2012,33 +2014,33 @@ make_formatting_params({options}) • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting *vim.lsp.util.make_given_range_params()* -make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding}) +make_given_range_params({start_pos}, {end_pos}, {bufnr}, {position_encoding}) Using the given range in the current buffer, creates an object that is similar to |vim.lsp.util.make_range_params()|. Parameters: ~ - • {start_pos} (`[integer,integer]?`) {row,col} mark-indexed - position. Defaults to the start of the last visual - selection. - • {end_pos} (`[integer,integer]?`) {row,col} mark-indexed - position. Defaults to the end of the last visual - selection. - • {bufnr} (`integer?`) buffer handle or 0 for current, - defaults to current - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) + • {start_pos} (`[integer,integer]?`) {row,col} mark-indexed + position. Defaults to the start of the last + visual selection. + • {end_pos} (`[integer,integer]?`) {row,col} mark-indexed + position. Defaults to the end of the last visual + selection. + • {bufnr} (`integer?`) buffer handle or 0 for current, + defaults to current + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`) Return: ~ (`{ textDocument: { uri: lsp.DocumentUri }, range: lsp.Range }`) *vim.lsp.util.make_position_params()* -make_position_params({window}, {offset_encoding}) +make_position_params({window}, {position_encoding}) Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. Parameters: ~ - • {window} (`integer?`) window handle or 0 for current, - defaults to current - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) + • {window} (`integer?`) window handle or 0 for current, + defaults to current + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`) Return: ~ (`lsp.TextDocumentPositionParams`) @@ -2047,16 +2049,16 @@ make_position_params({window}, {offset_encoding}) • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams *vim.lsp.util.make_range_params()* -make_range_params({window}, {offset_encoding}) +make_range_params({window}, {position_encoding}) Using the current position in the current buffer, creates an object that can be used as a building block for several LSP requests, such as `textDocument/codeAction`, `textDocument/colorPresentation`, `textDocument/rangeFormatting`. Parameters: ~ - • {window} (`integer?`) window handle or 0 for current, - defaults to current - • {offset_encoding} (`"utf-8"|"utf-16"|"utf-32"`) + • {window} (`integer?`) window handle or 0 for current, + defaults to current + • {position_encoding} (`"utf-8"|"utf-16"|"utf-32"`) Return: ~ (`{ textDocument: { uri: lsp.DocumentUri }, range: lsp.Range }`) @@ -2138,17 +2140,17 @@ rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()* • {ignoreIfExists}? (`boolean`) *vim.lsp.util.show_document()* -show_document({location}, {offset_encoding}, {opts}) +show_document({location}, {position_encoding}, {opts}) Shows document and optionally jumps to the location. Parameters: ~ - • {location} (`lsp.Location|lsp.LocationLink`) - • {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) - • {opts} (`table?`) A table with the following fields: - • {reuse_win}? (`boolean`) Jump to existing window - if buffer is already open. - • {focus}? (`boolean`) Whether to focus/jump to - location if possible. (defaults: true) + • {location} (`lsp.Location|lsp.LocationLink`) + • {position_encoding} (`'utf-8'|'utf-16'|'utf-32'?`) + • {opts} (`table?`) A table with the following fields: + • {reuse_win}? (`boolean`) Jump to existing + window if buffer is already open. + • {focus}? (`boolean`) Whether to focus/jump to + location if possible. (defaults: true) Return: ~ (`boolean`) `true` if succeeded diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index c0d0f7b1bc..58ab7ef44c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -100,7 +100,7 @@ LSP vim.diagnostic.config(config, vim.lsp.diagnostic.get_namespace(client_id)) < • |vim.lsp.util.make_position_params()|, |vim.lsp.util.make_range_params()| - and |vim.lsp.util.make_given_range_params()| now require the `offset_encoding` + and |vim.lsp.util.make_given_range_params()| now require the `position_encoding` parameter. LUA diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua index 8588502697..c2ff66b90e 100644 --- a/runtime/lua/vim/lsp/_changetracking.lua +++ b/runtime/lua/vim/lsp/_changetracking.lua @@ -18,14 +18,14 @@ local M = {} --- --- None: One group for all clients --- Full: One group for all clients ---- Incremental: One group per `offset_encoding` +--- Incremental: One group per `position_encoding` --- --- Sending changes can be debounced per buffer. To simplify the implementation the --- smallest debounce interval is used and we don't group clients by different intervals. --- --- @class vim.lsp.CTGroup --- @field sync_kind integer TextDocumentSyncKind, considers config.flags.allow_incremental_sync ---- @field offset_encoding "utf-8"|"utf-16"|"utf-32" +--- @field position_encoding "utf-8"|"utf-16"|"utf-32" --- --- @class vim.lsp.CTBufferState --- @field name string name of the buffer @@ -46,7 +46,7 @@ local M = {} ---@return string local function group_key(group) if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then - return tostring(group.sync_kind) .. '\0' .. group.offset_encoding + return tostring(group.sync_kind) .. '\0' .. group.position_encoding end return tostring(group.sync_kind) end @@ -72,7 +72,7 @@ local function get_group(client) end return { sync_kind = sync_kind, - offset_encoding = client.offset_encoding, + position_encoding = client.offset_encoding, } end @@ -310,7 +310,7 @@ local function send_changes_for_group(bufnr, firstline, lastline, new_lastline, -- The contents would further change and startline/endline may no longer fit local changes = incremental_changes( buf_state, - group.offset_encoding, + group.position_encoding, bufnr, firstline, lastline, diff --git a/runtime/lua/vim/lsp/_tagfunc.lua b/runtime/lua/vim/lsp/_tagfunc.lua index f6ffc63824..554f0cb991 100644 --- a/runtime/lua/vim/lsp/_tagfunc.lua +++ b/runtime/lua/vim/lsp/_tagfunc.lua @@ -6,12 +6,12 @@ local ms = lsp.protocol.Methods ---@param name string ---@param range lsp.Range ---@param uri string ----@param offset_encoding string +---@param position_encoding string ---@return {name: string, filename: string, cmd: string, kind?: string} -local function mk_tag_item(name, range, uri, offset_encoding) +local function mk_tag_item(name, range, uri, position_encoding) local bufnr = vim.uri_to_bufnr(uri) -- This is get_line_byte_from_position is 0-indexed, call cursor expects a 1-indexed position - local byte = util._get_line_byte_from_position(bufnr, range.start, offset_encoding) + 1 + local byte = util._get_line_byte_from_position(bufnr, range.start, position_encoding) + 1 return { name = name, filename = vim.uri_to_fname(uri), @@ -32,9 +32,9 @@ local function query_definition(pattern) --- @param range lsp.Range --- @param uri string - --- @param offset_encoding string - local add = function(range, uri, offset_encoding) - table.insert(results, mk_tag_item(pattern, range, uri, offset_encoding)) + --- @param position_encoding string + local add = function(range, uri, position_encoding) + table.insert(results, mk_tag_item(pattern, range, uri, position_encoding)) end local remaining = #clients @@ -78,11 +78,11 @@ local function query_workspace_symbols(pattern) local results = {} for client_id, responses in pairs(assert(results_by_client)) do local client = lsp.get_client_by_id(client_id) - local offset_encoding = client and client.offset_encoding or 'utf-16' + local position_encoding = client and client.offset_encoding or 'utf-16' local symbols = responses.result --[[@as lsp.SymbolInformation[]|nil]] for _, symbol in pairs(symbols or {}) do local loc = symbol.location - local item = mk_tag_item(symbol.name, loc.range, loc.uri, offset_encoding) + local item = mk_tag_item(symbol.name, loc.range, loc.uri, position_encoding) item.kind = lsp.protocol.SymbolKind[symbol.kind] or 'Unknown' table.insert(results, item) end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 10c0dbefdc..aca6bf27f4 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -638,14 +638,14 @@ function M.rename(new_name, opts) local cword = vim.fn.expand('') --- @param range lsp.Range - --- @param offset_encoding string - local function get_text_at_range(range, offset_encoding) + --- @param position_encoding string + local function get_text_at_range(range, position_encoding) return api.nvim_buf_get_text( bufnr, range.start.line, - util._get_line_byte_from_position(bufnr, range.start, offset_encoding), + util._get_line_byte_from_position(bufnr, range.start, position_encoding), range['end'].line, - util._get_line_byte_from_position(bufnr, range['end'], offset_encoding), + util._get_line_byte_from_position(bufnr, range['end'], position_encoding), {} )[1] end diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 3b79da99ea..7c2b7192f5 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -94,7 +94,8 @@ local validate = vim.validate --- Language ID as string. Defaults to the buffer filetype. --- @field get_language_id? fun(bufnr: integer, filetype: string): string --- ---- The encoding that the LSP server expects. Client does not verify this is correct. +--- Called "position encoding" in LSP spec, the encoding that the LSP server expects. +--- Client does not verify this is correct. --- @field offset_encoding? 'utf-8'|'utf-16'|'utf-32' --- --- Callback invoked when the client operation throws an error. `code` is a number describing the error. @@ -148,8 +149,10 @@ local validate = vim.validate --- See |vim.lsp.rpc.start()|. --- @field rpc vim.lsp.rpc.PublicClient --- ---- The encoding used for communicating with the server. You can modify this in ---- the `config`'s `on_init` method before text is sent to the server. +--- Called "position encoding" in LSP spec, +--- the encoding used for communicating with the server. +--- You can modify this in the `config`'s `on_init` method +--- before text is sent to the server. --- @field offset_encoding string --- --- The handlers used by the client as described in |lsp-handler|. @@ -278,7 +281,7 @@ local function validate_encoding(encoding) return valid_encodings[encoding:lower()] or error( string.format( - "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", + "Invalid position encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding ) ) diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index 0f388a88fd..1e950f625e 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -550,7 +550,7 @@ local function on_complete_done() return end - local offset_encoding = client.offset_encoding or 'utf-16' + local position_encoding = client.offset_encoding or 'utf-16' local resolve_provider = (client.server_capabilities.completionProvider or {}).resolveProvider local function clear_word() @@ -576,7 +576,7 @@ local function on_complete_done() if completion_item.additionalTextEdits and next(completion_item.additionalTextEdits) then clear_word() - lsp.util.apply_text_edits(completion_item.additionalTextEdits, bufnr, offset_encoding) + lsp.util.apply_text_edits(completion_item.additionalTextEdits, bufnr, position_encoding) apply_snippet_and_command() elseif resolve_provider and type(completion_item) == 'table' then local changedtick = vim.b[bufnr].changedtick @@ -591,7 +591,7 @@ local function on_complete_done() if err then vim.notify_once(err.message, vim.log.levels.WARN) elseif result and result.additionalTextEdits then - lsp.util.apply_text_edits(result.additionalTextEdits, bufnr, offset_encoding) + lsp.util.apply_text_edits(result.additionalTextEdits, bufnr, position_encoding) if result.command then completion_item.command = result.command end diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 8fd30c7668..12984f8c26 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -77,7 +77,7 @@ end local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) local buf_lines = get_buf_lines(bufnr) local client = vim.lsp.get_client_by_id(client_id) - local offset_encoding = client and client.offset_encoding or 'utf-16' + local position_encoding = client and client.offset_encoding or 'utf-16' --- @param diagnostic lsp.Diagnostic --- @return vim.Diagnostic return vim.tbl_map(function(diagnostic) @@ -95,9 +95,9 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) --- @type vim.Diagnostic return { lnum = start.line, - col = vim.str_byteindex(line, offset_encoding, start.character, false), + col = vim.str_byteindex(line, position_encoding, start.character, false), end_lnum = _end.line, - end_col = vim.str_byteindex(line, offset_encoding, _end.character, false), + end_col = vim.str_byteindex(line, position_encoding, _end.character, false), severity = severity_lsp_to_vim(diagnostic.severity), message = message, source = diagnostic.source, diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index 3df45ebff0..621f63b25f 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -48,21 +48,21 @@ local str_utfindex = vim.str_utfindex local str_utf_start = vim.str_utf_start local str_utf_end = vim.str_utf_end --- Given a line, byte idx, alignment, and offset_encoding convert to the aligned +-- Given a line, byte idx, alignment, and position_encoding convert to the aligned -- utf-8 index and either the utf-16, or utf-32 index. ---@param line string the line to index into ---@param byte integer the byte idx ----@param offset_encoding string utf-8|utf-16|utf-32|nil (default: utf-8) +---@param position_encoding string utf-8|utf-16|utf-32|nil (default: utf-8) ---@return integer byte_idx of first change position ---@return integer char_idx of first change position -local function align_end_position(line, byte, offset_encoding) +local function align_end_position(line, byte, position_encoding) local char --- @type integer -- If on the first byte, or an empty string: the trivial case if byte == 1 or #line == 0 then char = byte -- Called in the case of extending an empty line "" -> "a" elseif byte == #line + 1 then - char = str_utfindex(line, offset_encoding) + 1 + char = str_utfindex(line, position_encoding) + 1 else -- Modifying line, find the nearest utf codepoint local offset = str_utf_start(line, byte) @@ -73,9 +73,9 @@ local function align_end_position(line, byte, offset_encoding) end if byte <= #line then --- Convert to 0 based for input, and from 0 based for output - char = str_utfindex(line, offset_encoding, byte - 1) + 1 + char = str_utfindex(line, position_encoding, byte - 1) + 1 else - char = str_utfindex(line, offset_encoding) + 1 + char = str_utfindex(line, position_encoding) + 1 end -- Extending line, find the nearest utf codepoint for the last valid character end @@ -93,7 +93,7 @@ end ---@param firstline integer firstline from on_lines, adjusted to 1-index ---@param lastline integer lastline from on_lines, adjusted to 1-index ---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index ----@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8) +---@param position_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8) ---@return vim.lsp.sync.Range result table include line_idx, byte_idx, and char_idx of first change position local function compute_start_range( prev_lines, @@ -101,7 +101,7 @@ local function compute_start_range( firstline, lastline, new_lastline, - offset_encoding + position_encoding ) local char_idx --- @type integer? local byte_idx --- @type integer? @@ -115,7 +115,7 @@ local function compute_start_range( if line then line_idx = firstline - 1 byte_idx = #line + 1 - char_idx = str_utfindex(line, offset_encoding) + 1 + char_idx = str_utfindex(line, position_encoding) + 1 else line_idx = firstline byte_idx = 1 @@ -152,11 +152,11 @@ local function compute_start_range( char_idx = 1 elseif start_byte_idx == #prev_line + 1 then byte_idx = start_byte_idx - char_idx = str_utfindex(prev_line, offset_encoding) + 1 + char_idx = str_utfindex(prev_line, position_encoding) + 1 else byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx) --- Convert to 0 based for input, and from 0 based for output - char_idx = vim.str_utfindex(prev_line, offset_encoding, byte_idx - 1) + 1 + char_idx = vim.str_utfindex(prev_line, position_encoding, byte_idx - 1) + 1 end -- Return the start difference (shared for new and prev lines) @@ -174,7 +174,7 @@ end ---@param firstline integer ---@param lastline integer ---@param new_lastline integer ----@param offset_encoding string +---@param position_encoding string ---@return vim.lsp.sync.Range prev_end_range ---@return vim.lsp.sync.Range curr_end_range local function compute_end_range( @@ -184,7 +184,7 @@ local function compute_end_range( firstline, lastline, new_lastline, - offset_encoding + position_encoding ) -- A special case for the following `firstline == new_lastline` case where lines are deleted. -- Even if the buffer has become empty, nvim behaves as if it has an empty line with eol. @@ -193,7 +193,7 @@ local function compute_end_range( return { line_idx = lastline - 1, byte_idx = #prev_line + 1, - char_idx = str_utfindex(prev_line, offset_encoding) + 1, + char_idx = str_utfindex(prev_line, position_encoding) + 1, }, { line_idx = 1, byte_idx = 1, char_idx = 1 } end -- If firstline == new_lastline, the first change occurred on a line that was deleted. @@ -259,7 +259,7 @@ local function compute_end_range( prev_end_byte_idx = 1 end local prev_byte_idx, prev_char_idx = - align_end_position(prev_line, prev_end_byte_idx, offset_encoding) + align_end_position(prev_line, prev_end_byte_idx, position_encoding) local prev_end_range = { line_idx = prev_line_idx, byte_idx = prev_byte_idx, char_idx = prev_char_idx } @@ -274,7 +274,7 @@ local function compute_end_range( curr_end_byte_idx = 1 end local curr_byte_idx, curr_char_idx = - align_end_position(curr_line, curr_end_byte_idx, offset_encoding) + align_end_position(curr_line, curr_end_byte_idx, position_encoding) curr_end_range = { line_idx = curr_line_idx, byte_idx = curr_byte_idx, char_idx = curr_char_idx } end @@ -317,7 +317,7 @@ local function extract_text(lines, start_range, end_range, line_ending) end end --- rangelength depends on the offset encoding +-- rangelength depends on the position encoding -- bytes for utf-8 (clangd with extension) -- codepoints for utf-16 -- codeunits for utf-32 @@ -326,10 +326,10 @@ end ---@param lines string[] ---@param start_range vim.lsp.sync.Range ---@param end_range vim.lsp.sync.Range ----@param offset_encoding string +---@param position_encoding string ---@param line_ending string ---@return integer -local function compute_range_length(lines, start_range, end_range, offset_encoding, line_ending) +local function compute_range_length(lines, start_range, end_range, position_encoding, line_ending) local line_ending_length = #line_ending -- Single line case if start_range.line_idx == end_range.line_idx then @@ -339,7 +339,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi local start_line = lines[start_range.line_idx] local range_length --- @type integer if start_line and #start_line > 0 then - range_length = str_utfindex(start_line, offset_encoding) + range_length = str_utfindex(start_line, position_encoding) - start_range.char_idx + 1 + line_ending_length @@ -352,7 +352,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi for idx = start_range.line_idx + 1, end_range.line_idx - 1 do -- Length full line plus newline character if #lines[idx] > 0 then - range_length = range_length + str_utfindex(lines[idx], offset_encoding) + #line_ending + range_length = range_length + str_utfindex(lines[idx], position_encoding) + #line_ending else range_length = range_length + line_ending_length end @@ -372,7 +372,7 @@ end ---@param firstline integer line to begin search for first difference ---@param lastline integer line to begin search in old_lines for last difference ---@param new_lastline integer line to begin search in new_lines for last difference ----@param offset_encoding string encoding requested by language server +---@param position_encoding string encoding requested by language server ---@param line_ending string ---@return lsp.TextDocumentContentChangeEvent : see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent function M.compute_diff( @@ -381,7 +381,7 @@ function M.compute_diff( firstline, lastline, new_lastline, - offset_encoding, + position_encoding, line_ending ) -- Find the start of changes between the previous and current buffer. Common between both. @@ -393,7 +393,7 @@ function M.compute_diff( firstline + 1, lastline + 1, new_lastline + 1, - offset_encoding + position_encoding ) -- Find the last position changed in the previous and current buffer. -- prev_end_range is sent to the server as as the end of the changed range. @@ -405,7 +405,7 @@ function M.compute_diff( firstline + 1, lastline + 1, new_lastline + 1, - offset_encoding + position_encoding ) -- Grab the changed text of from start_range to curr_end_range in the current buffer. @@ -414,7 +414,7 @@ function M.compute_diff( -- Compute the range of the replaced text. Deprecated but still required for certain language servers local range_length = - compute_range_length(prev_lines, start_range, prev_end_range, offset_encoding, line_ending) + compute_range_length(prev_lines, start_range, prev_end_range, position_encoding, line_ending) -- convert to 0 based indexing local result = { diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 0f608ffbd1..3de76f93a6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -277,9 +277,9 @@ end --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position ---@param position lsp.Position ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' ---@return integer -local function get_line_byte_from_position(bufnr, position, offset_encoding) +local function get_line_byte_from_position(bufnr, position, position_encoding) -- LSP's line and characters are 0-indexed -- Vim's line and columns are 1-indexed local col = position.character @@ -287,7 +287,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- character if col > 0 then local line = get_line(bufnr, position.line) or '' - return vim.str_byteindex(line, offset_encoding, col, false) + return vim.str_byteindex(line, position_encoding, col, false) end return col end @@ -295,12 +295,12 @@ end --- Applies a list of text edits to a buffer. ---@param text_edits lsp.TextEdit[] ---@param bufnr integer Buffer id ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit -function M.apply_text_edits(text_edits, bufnr, offset_encoding) +function M.apply_text_edits(text_edits, bufnr, position_encoding) validate('text_edits', text_edits, 'table', false) validate('bufnr', bufnr, 'number', false) - validate('offset_encoding', offset_encoding, 'string', false) + validate('position_encoding', position_encoding, 'string', false) if not next(text_edits) then return @@ -359,9 +359,9 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Convert from LSP style ranges to Neovim style ranges. local start_row = text_edit.range.start.line - local start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding) + local start_col = get_line_byte_from_position(bufnr, text_edit.range.start, position_encoding) local end_row = text_edit.range['end'].line - local end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding) + local end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], position_encoding) local text = vim.split(text_edit.newText, '\n', { plain = true }) local max = api.nvim_buf_line_count(bufnr) @@ -430,14 +430,14 @@ end --- ---@param text_document_edit lsp.TextDocumentEdit ---@param index? integer: Optional index of the edit, if from a list of edits (or nil, if not from a list) ----@param offset_encoding? 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding? 'utf-8'|'utf-16'|'utf-32' ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit, index, offset_encoding) +function M.apply_text_document_edit(text_document_edit, index, position_encoding) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) - if offset_encoding == nil then + if position_encoding == nil then vim.notify_once( - 'apply_text_document_edit must be called with valid offset encoding', + 'apply_text_document_edit must be called with valid position encoding', vim.log.levels.WARN ) return @@ -459,7 +459,7 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) return end - M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding) + M.apply_text_edits(text_document_edit.edits, bufnr, position_encoding) end local function path_components(path) @@ -619,12 +619,12 @@ end --- Applies a `WorkspaceEdit`. --- ---@param workspace_edit lsp.WorkspaceEdit ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' (required) +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' (required) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -function M.apply_workspace_edit(workspace_edit, offset_encoding) - if offset_encoding == nil then +function M.apply_workspace_edit(workspace_edit, position_encoding) + if position_encoding == nil then vim.notify_once( - 'apply_workspace_edit must be called with valid offset encoding', + 'apply_workspace_edit must be called with valid position encoding', vim.log.levels.WARN ) return @@ -641,7 +641,7 @@ function M.apply_workspace_edit(workspace_edit, offset_encoding) elseif change.kind then --- @diagnostic disable-line:undefined-field error(string.format('Unsupported change: %q', vim.inspect(change))) else - M.apply_text_document_edit(change, idx, offset_encoding) + M.apply_text_document_edit(change, idx, position_encoding) end end return @@ -654,7 +654,7 @@ function M.apply_workspace_edit(workspace_edit, offset_encoding) for uri, changes in pairs(all_changes) do local bufnr = vim.uri_to_bufnr(uri) - M.apply_text_edits(changes, bufnr, offset_encoding) + M.apply_text_edits(changes, bufnr, position_encoding) end end @@ -904,17 +904,20 @@ end --- Shows document and optionally jumps to the location. --- ---@param location lsp.Location|lsp.LocationLink ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? +---@param position_encoding 'utf-8'|'utf-16'|'utf-32'? ---@param opts? vim.lsp.util.show_document.Opts ---@return boolean `true` if succeeded -function M.show_document(location, offset_encoding, opts) +function M.show_document(location, position_encoding, opts) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return false end - if offset_encoding == nil then - vim.notify_once('show_document must be called with valid offset encoding', vim.log.levels.WARN) + if position_encoding == nil then + vim.notify_once( + 'show_document must be called with valid position encoding', + vim.log.levels.WARN + ) return false end local bufnr = vim.uri_to_bufnr(uri) @@ -946,7 +949,7 @@ function M.show_document(location, offset_encoding, opts) if range then -- Jump to new location (adjusting for encoding of characters) local row = range.start.line - local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) + local col = get_line_byte_from_position(bufnr, range.start, position_encoding) api.nvim_win_set_cursor(win, { row + 1, col }) vim._with({ win = win }, function() -- Open folds under the cursor @@ -961,12 +964,12 @@ end --- ---@deprecated use `vim.lsp.util.show_document` with `{focus=true}` instead ---@param location lsp.Location|lsp.LocationLink ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? +---@param position_encoding 'utf-8'|'utf-16'|'utf-32'? ---@param reuse_win boolean? Jump to existing window if buffer is already open. ---@return boolean `true` if the jump succeeded -function M.jump_to_location(location, offset_encoding, reuse_win) +function M.jump_to_location(location, position_encoding, reuse_win) vim.deprecate('vim.lsp.util.jump_to_location', nil, '0.12') - return M.show_document(location, offset_encoding, { reuse_win = reuse_win, focus = true }) + return M.show_document(location, position_encoding, { reuse_win = reuse_win, focus = true }) end --- Previews a location in a floating window @@ -1661,18 +1664,18 @@ do --[[ References ]] --- ---@param bufnr integer Buffer id ---@param references lsp.DocumentHighlight[] objects to highlight - ---@param offset_encoding 'utf-8'|'utf-16'|'utf-32' + ---@param position_encoding 'utf-8'|'utf-16'|'utf-32' ---@see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent - function M.buf_highlight_references(bufnr, references, offset_encoding) + function M.buf_highlight_references(bufnr, references, position_encoding) validate('bufnr', bufnr, 'number', true) - validate('offset_encoding', offset_encoding, 'string', false) + validate('position_encoding', position_encoding, 'string', false) for _, reference in ipairs(references) do local range = reference.range local start_line = range.start.line local end_line = range['end'].line - local start_idx = get_line_byte_from_position(bufnr, range.start, offset_encoding) - local end_idx = get_line_byte_from_position(bufnr, range['end'], offset_encoding) + local start_idx = get_line_byte_from_position(bufnr, range.start, position_encoding) + local end_idx = get_line_byte_from_position(bufnr, range['end'], position_encoding) local document_highlight_kind = { [protocol.DocumentHighlightKind.Text] = 'LspReferenceText', @@ -1706,16 +1709,16 @@ end) --- |setloclist()|. --- ---@param locations lsp.Location[]|lsp.LocationLink[] ----@param offset_encoding? 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding? 'utf-8'|'utf-16'|'utf-32' --- default to first client of buffer ---@return vim.quickfix.entry[] # See |setqflist()| for the format -function M.locations_to_items(locations, offset_encoding) - if offset_encoding == nil then +function M.locations_to_items(locations, position_encoding) + if position_encoding == nil then vim.notify_once( - 'locations_to_items must be called with valid offset encoding', + 'locations_to_items must be called with valid position encoding', vim.log.levels.WARN ) - offset_encoding = vim.lsp.get_clients({ bufnr = 0 })[1].offset_encoding + position_encoding = vim.lsp.get_clients({ bufnr = 0 })[1].offset_encoding end local items = {} --- @type vim.quickfix.entry[] @@ -1752,8 +1755,8 @@ function M.locations_to_items(locations, offset_encoding) local end_row = end_pos.line local line = lines[row] or '' local end_line = lines[end_row] or '' - local col = vim.str_byteindex(line, offset_encoding, pos.character, false) - local end_col = vim.str_byteindex(end_line, offset_encoding, end_pos.character, false) + local col = vim.str_byteindex(line, position_encoding, pos.character, false) + local end_col = vim.str_byteindex(end_line, position_encoding, end_pos.character, false) items[#items + 1] = { filename = filename, @@ -1864,8 +1867,8 @@ function M.try_trim_markdown_code_blocks(lines) end ---@param window integer?: window handle or 0 for current, defaults to current ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' -local function make_position_param(window, offset_encoding) +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' +local function make_position_param(window, position_encoding) window = window or 0 local buf = api.nvim_win_get_buf(window) local row, col = unpack(api.nvim_win_get_cursor(window)) @@ -1875,7 +1878,7 @@ local function make_position_param(window, offset_encoding) return { line = 0, character = 0 } end - col = vim.str_utfindex(line, offset_encoding, col, false) + col = vim.str_utfindex(line, position_encoding, col, false) return { line = row, character = col } end @@ -1883,22 +1886,22 @@ end --- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. --- ---@param window integer?: window handle or 0 for current, defaults to current ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' ---@return lsp.TextDocumentPositionParams ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams -function M.make_position_params(window, offset_encoding) +function M.make_position_params(window, position_encoding) window = window or 0 local buf = api.nvim_win_get_buf(window) - if offset_encoding == nil then + if position_encoding == nil then vim.notify_once( - 'warning: offset_encoding is required, using the offset_encoding from the first client', + 'warning: position_encoding is required, using the offset_encoding from the first client', vim.log.levels.WARN ) - offset_encoding = M._get_offset_encoding(buf) + position_encoding = M._get_offset_encoding(buf) end return { textDocument = M.make_text_document_params(buf), - position = make_position_param(window, offset_encoding), + position = make_position_param(window, position_encoding), } end @@ -1941,18 +1944,18 @@ end --- `textDocument/rangeFormatting`. --- ---@param window integer? window handle or 0 for current, defaults to current ----@param offset_encoding "utf-8"|"utf-16"|"utf-32" +---@param position_encoding "utf-8"|"utf-16"|"utf-32" ---@return { textDocument: { uri: lsp.DocumentUri }, range: lsp.Range } -function M.make_range_params(window, offset_encoding) +function M.make_range_params(window, position_encoding) local buf = api.nvim_win_get_buf(window or 0) - if offset_encoding == nil then + if position_encoding == nil then vim.notify_once( - 'warning: offset_encoding is required, using the offset_encoding from the first client', + 'warning: position_encoding is required, using the offset_encoding from the first client', vim.log.levels.WARN ) - offset_encoding = M._get_offset_encoding(buf) + position_encoding = M._get_offset_encoding(buf) end - local position = make_position_param(window, offset_encoding) + local position = make_position_param(window, position_encoding) return { textDocument = M.make_text_document_params(buf), range = { start = position, ['end'] = position }, @@ -1967,19 +1970,19 @@ end ---@param end_pos [integer,integer]? {row,col} mark-indexed position. --- Defaults to the end of the last visual selection. ---@param bufnr integer? buffer handle or 0 for current, defaults to current ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' ---@return { textDocument: { uri: lsp.DocumentUri }, range: lsp.Range } -function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) +function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding) validate('start_pos', start_pos, 'table', true) validate('end_pos', end_pos, 'table', true) - validate('offset_encoding', offset_encoding, 'string', true) + validate('position_encoding', position_encoding, 'string', true) bufnr = bufnr or api.nvim_get_current_buf() - if offset_encoding == nil then + if position_encoding == nil then vim.notify_once( - 'warning: offset_encoding is required, using the offset_encoding from the first client', + 'warning: position_encoding is required, using the offset_encoding from the first client', vim.log.levels.WARN ) - offset_encoding = M._get_offset_encoding(bufnr) + position_encoding = M._get_offset_encoding(bufnr) end --- @type [integer, integer] local A = { unpack(start_pos or api.nvim_buf_get_mark(bufnr, '<')) } @@ -1988,12 +1991,12 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) -- convert to 0-index A[1] = A[1] - 1 B[1] = B[1] - 1 - -- account for offset_encoding. + -- account for position_encoding. if A[2] > 0 then - A[2] = M.character_offset(bufnr, A[1], A[2], offset_encoding) + A[2] = M.character_offset(bufnr, A[1], A[2], position_encoding) end if B[2] > 0 then - B[2] = M.character_offset(bufnr, B[1], B[2], offset_encoding) + B[2] = M.character_offset(bufnr, B[1], B[2], position_encoding) end -- we need to offset the end character position otherwise we loose the last -- character of the selection, as LSP end position is exclusive @@ -2100,9 +2103,9 @@ end ---@param bufnr integer ---@param start_line integer ---@param end_line integer ----@param offset_encoding 'utf-8'|'utf-16'|'utf-32' +---@param position_encoding 'utf-8'|'utf-16'|'utf-32' ---@return lsp.Range -local function make_line_range_params(bufnr, start_line, end_line, offset_encoding) +local function make_line_range_params(bufnr, start_line, end_line, position_encoding) local last_line = api.nvim_buf_line_count(bufnr) - 1 ---@type lsp.Position @@ -2111,7 +2114,12 @@ local function make_line_range_params(bufnr, start_line, end_line, offset_encodi if end_line == last_line and not vim.bo[bufnr].endofline then end_pos = { line = end_line, - character = M.character_offset(bufnr, end_line, #get_line(bufnr, end_line), offset_encoding), + character = M.character_offset( + bufnr, + end_line, + #get_line(bufnr, end_line), + position_encoding + ), } else end_pos = { line = end_line + 1, character = 0 } diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index f60e159d64..0bca1fa4ca 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -23,7 +23,7 @@ before_each(function() -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] --- @diagnostic disable-next-line:duplicate-set-field - function _G.test_register(bufnr, id, offset_encoding, line_ending) + function _G.test_register(bufnr, id, position_encoding, line_ending) local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline) @@ -38,7 +38,7 @@ before_each(function() firstline, lastline, new_lastline, - offset_encoding, + position_encoding, line_ending ) @@ -63,15 +63,15 @@ local function test_edit( prev_buffer, edit_operations, expected_text_changes, - offset_encoding, + position_encoding, line_ending ) - offset_encoding = offset_encoding or 'utf-16' + position_encoding = position_encoding or 'utf-16' line_ending = line_ending or '\n' api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer) exec_lua(function() - return _G.test_register(0, 'test1', offset_encoding, line_ending) + return _G.test_register(0, 'test1', position_encoding, line_ending) end) for _, edit in ipairs(edit_operations) do diff --git a/test/functional/script/luacats_grammar_spec.lua b/test/functional/script/luacats_grammar_spec.lua index 8bc55879d4..6e73f6894b 100644 --- a/test/functional/script/luacats_grammar_spec.lua +++ b/test/functional/script/luacats_grammar_spec.lua @@ -118,9 +118,9 @@ describe('luacats grammar', function() type = '"rfc2396" | "rfc2732" | "rfc3986" | nil', }) - test('@param offset_encoding "utf-8" | "utf-16" | "utf-32" | nil', { + test('@param position_encoding "utf-8" | "utf-16" | "utf-32" | nil', { kind = 'param', - name = 'offset_encoding', + name = 'position_encoding', type = '"utf-8" | "utf-16" | "utf-32" | nil', }) -- cgit From 99e7323aa386865035ad79483a7da0c5b106464f Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Mon, 25 Nov 2024 17:11:22 +0100 Subject: feat(diagnostic): inherit parent 'path' option in open_float (#31273) Diagnostics sometimes contain stack traces, it's useful being able to use `CTRL-W F` on them to open related files from within the diagnostic window. --- runtime/lua/vim/diagnostic.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 4fb8c6a686..2de996feeb 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1962,6 +1962,7 @@ function M.open_float(opts, ...) opts.focus_id = scope end local float_bufnr, winnr = vim.lsp.util.open_floating_preview(lines, 'plaintext', opts) + vim.bo[float_bufnr].path = vim.bo[bufnr].path for i, hl in ipairs(highlights) do local line = lines[i] local prefix_len = hl.prefix and hl.prefix.length or 0 -- cgit From 8d55cc218cfed54136677398ca76c45987b15f29 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 24 Nov 2024 10:40:56 +0000 Subject: feat(keysets): teach Union and LuaRefOf --- runtime/lua/vim/_meta/api.lua | 2 +- runtime/lua/vim/_meta/api_keysets.lua | 100 ++++++++++++------------- runtime/lua/vim/_meta/api_keysets_extra.lua | 45 ++++++++++++ scripts/gen_eval_files.lua | 53 +++++++++++++- src/nvim/api/autocmd.c | 2 +- src/nvim/api/command.c | 4 +- src/nvim/api/keysets_defs.h | 109 +++++++++++++++++----------- src/nvim/api/private/defs.h | 2 + src/nvim/generators/c_grammar.lua | 36 ++++++++- src/nvim/generators/gen_api_dispatch.lua | 10 ++- src/nvim/highlight.c | 4 +- 11 files changed, 258 insertions(+), 109 deletions(-) diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 3c9b9d4f44..acd12b353d 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1012,7 +1012,7 @@ function vim.api.nvim_create_namespace(name) end --- ``` --- --- @param name string Name of the new user command. Must begin with an uppercase letter. ---- @param command any Replacement command to execute when this user command is executed. When called +--- @param command string|fun(args: vim.api.keyset.create_user_command.command_args) Replacement command to execute when this user command is executed. When called --- from Lua, the command can also be a Lua function. The function is called with a --- single table argument that contains the following keys: --- - name: (string) Command name diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index bf184dee2d..e11dddb2d3 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -4,11 +4,11 @@ error('Cannot require a meta file') --- @class vim.api.keyset.buf_attach ---- @field on_lines? function ---- @field on_bytes? function ---- @field on_changedtick? function ---- @field on_detach? function ---- @field on_reload? function +--- @field on_lines? fun(_: "lines", bufnr: integer, changedtick: integer, first: integer, last_old: integer, last_new: integer, byte_count: integer, deleted_codepoints?: integer, deleted_codeunits?: integer): boolean? +--- @field on_bytes? fun(_: "bytes", bufnr: integer, changedtick: integer, start_row: integer, start_col: integer, start_byte: integer, old_end_row: integer, old_end_col: integer, old_end_byte: integer, new_end_row: integer, new_end_col: integer, new_end_byte: integer): boolean? +--- @field on_changedtick? fun(_: "changedtick", bufnr: integer, changedtick: integer) +--- @field on_detach? fun(_: "detach", bufnr: integer) +--- @field on_reload? fun(_: "reload", bufnr: integer) --- @field utf_sizes? boolean --- @field preview? boolean @@ -18,9 +18,9 @@ error('Cannot require a meta file') --- @class vim.api.keyset.clear_autocmds --- @field buffer? integer ---- @field event? any ---- @field group? any ---- @field pattern? any +--- @field event? string|string[] +--- @field group? integer|string +--- @field pattern? string|string[] --- @class vim.api.keyset.cmd --- @field cmd? string @@ -28,12 +28,12 @@ error('Cannot require a meta file') --- @field count? integer --- @field reg? string --- @field bang? boolean ---- @field args? any[] +--- @field args? string[] --- @field magic? table --- @field mods? table ---- @field nargs? any ---- @field addr? any ---- @field nextcmd? any +--- @field nargs? integer|string +--- @field addr? string +--- @field nextcmd? string --- @class vim.api.keyset.cmd_magic --- @field file? boolean @@ -72,20 +72,20 @@ error('Cannot require a meta file') --- @field info? string --- @class vim.api.keyset.context ---- @field types? any[] +--- @field types? string[] --- @class vim.api.keyset.create_augroup ---- @field clear? any +--- @field clear? boolean --- @class vim.api.keyset.create_autocmd --- @field buffer? integer ---- @field callback? any +--- @field callback? string|(fun(args: vim.api.keyset.create_autocmd.callback_args): boolean?) --- @field command? string --- @field desc? string ---- @field group? any +--- @field group? integer|string --- @field nested? boolean --- @field once? boolean ---- @field pattern? any +--- @field pattern? string|string[] --- @class vim.api.keyset.echo_opts --- @field verbose? boolean @@ -103,19 +103,19 @@ error('Cannot require a meta file') --- @class vim.api.keyset.exec_autocmds --- @field buffer? integer ---- @field group? any +--- @field group? integer|string --- @field modeline? boolean ---- @field pattern? any +--- @field pattern? string|string[] --- @field data? any --- @class vim.api.keyset.exec_opts --- @field output? boolean --- @class vim.api.keyset.get_autocmds ---- @field event? any ---- @field group? any ---- @field pattern? any ---- @field buffer? any +--- @field event? string|string[] +--- @field group? integer|string +--- @field pattern? string|string[] +--- @field buffer? integer|integer[] --- @class vim.api.keyset.get_commands --- @field builtin? boolean @@ -154,17 +154,17 @@ error('Cannot require a meta file') --- @field altfont? boolean --- @field nocombine? boolean --- @field default? boolean ---- @field cterm? any ---- @field foreground? any ---- @field fg? any ---- @field background? any ---- @field bg? any ---- @field ctermfg? any ---- @field ctermbg? any ---- @field special? any ---- @field sp? any ---- @field link? any ---- @field global_link? any +--- @field cterm? integer|string +--- @field foreground? integer|string +--- @field fg? integer|string +--- @field background? integer|string +--- @field bg? integer|string +--- @field ctermfg? integer|string +--- @field ctermbg? integer|string +--- @field special? integer|string +--- @field sp? integer|string +--- @field link? integer|string +--- @field global_link? integer|string --- @field fallback? boolean --- @field blend? integer --- @field fg_indexed? boolean @@ -201,7 +201,7 @@ error('Cannot require a meta file') --- @field wins? any[] --- @class vim.api.keyset.open_term ---- @field on_input? function +--- @field on_input? fun(_: "input", term: integer, bufnr: integer, data: any) --- @field force_crlf? boolean --- @class vim.api.keyset.option @@ -227,20 +227,20 @@ error('Cannot require a meta file') --- @field do_source? boolean --- @class vim.api.keyset.set_decoration_provider ---- @field on_start? function ---- @field on_buf? function ---- @field on_win? function ---- @field on_line? function ---- @field on_end? function ---- @field _on_hl_def? function ---- @field _on_spell_nav? function +--- @field on_start? fun(_: "start", tick: integer) +--- @field on_buf? fun(_: "buf", bufnr: integer, tick: integer) +--- @field on_win? fun(_: "win", winid: integer, bufnr: integer, toprow: integer, botrow: integer) +--- @field on_line? fun(_: "line", winid: integer, bufnr: integer, row: integer) +--- @field on_end? fun(_: "end", tick: integer) +--- @field _on_hl_def? fun(_: "hl_def") +--- @field _on_spell_nav? fun(_: "spell_nav") --- @class vim.api.keyset.set_extmark --- @field id? integer --- @field end_line? integer --- @field end_row? integer --- @field end_col? integer ---- @field hl_group? number|string +--- @field hl_group? integer|string --- @field virt_text? any[] --- @field virt_text_pos? string --- @field virt_text_win_col? integer @@ -258,10 +258,10 @@ error('Cannot require a meta file') --- @field virt_lines_leftcol? boolean --- @field strict? boolean --- @field sign_text? string ---- @field sign_hl_group? number|string ---- @field number_hl_group? number|string ---- @field line_hl_group? number|string ---- @field cursorline_hl_group? number|string +--- @field sign_hl_group? integer|string +--- @field number_hl_group? integer|string +--- @field line_hl_group? integer|string +--- @field cursorline_hl_group? integer|string --- @field conceal? string --- @field spell? boolean --- @field ui_watched? boolean @@ -292,7 +292,7 @@ error('Cannot require a meta file') --- @field relative? string --- @field split? string --- @field win? integer ---- @field bufpos? any[] +--- @field bufpos? integer[] --- @field external? boolean --- @field focusable? boolean --- @field mouse? boolean @@ -315,12 +315,12 @@ error('Cannot require a meta file') --- @field end_vcol? integer --- @class vim.api.keyset.xdl_diff ---- @field on_hunk? function +--- @field on_hunk? fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer? --- @field result_type? string --- @field algorithm? string --- @field ctxlen? integer --- @field interhunkctxlen? integer ---- @field linematch? any +--- @field linematch? boolean|integer --- @field ignore_whitespace? boolean --- @field ignore_whitespace_change? boolean --- @field ignore_whitespace_change_at_eol? boolean diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua index 81bce50746..e8e901acb2 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -73,6 +73,51 @@ error('Cannot require a meta file') --- @field buflocal? boolean --- @field buffer? integer +--- @class vim.api.keyset.create_autocmd.callback_args +--- @field id integer autocommand id +--- @field event string name of the triggered event |autocmd-events| +--- @field group? integer autocommand group id, if any +--- @field match string expanded value of +--- @field buf integer expanded value of +--- @field file string expanded value of +--- @field data? any arbitrary data passed from |nvim_exec_autocmds()| *event-data* + +--- @class vim.api.keyset.create_user_command.command_args +--- @field name string Command name +--- +--- The args passed to the command, if any +--- @field args string +--- +--- The args split by unescaped whitespace +--- (when more than one argument is allowed), if any +--- @field fargs string[] +--- +--- Number of arguments |:command-nargs| +--- @field nargs string +--- +--- "true" if the command was executed with a ! modifier +--- @field bang boolean +--- +--- The starting line of the command range +--- @field line1 integer +--- +--- The final line of the command range +--- @field line2 integer +--- +--- The number of items in the command range: 0, 1, or 2 +--- @field range integer +--- +--- Any count supplied +--- @field count integer +--- The optional register, if specified +--- @field reg string +--- Command modifiers, if any +--- @field mods string +--- +--- Command modifiers in a structured format. Has the same structure as the +--- "mods" key of |nvim_parse_cmd()|. +--- @field smods table + --- @class vim.api.keyset.command_info --- @field name string --- @field definition string diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index a9431ae2e5..498bf6b26f 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -47,6 +47,18 @@ local LUA_API_RETURN_OVERRIDES = { nvim_win_get_config = 'vim.api.keyset.win_config', } +local LUA_API_KEYSET_OVERRIDES = { + create_autocmd = { + callback = 'string|(fun(args: vim.api.keyset.create_autocmd.callback_args): boolean?)', + }, +} + +local LUA_API_PARAM_OVERRIDES = { + nvim_create_user_command = { + command = 'string|fun(args: vim.api.keyset.create_user_command.command_args)', + }, +} + local LUA_META_HEADER = { '--- @meta _', '-- THIS FILE IS GENERATED', @@ -118,7 +130,7 @@ local API_TYPES = { LuaRef = 'function', Dict = 'table', Float = 'number', - HLGroupID = 'number|string', + HLGroupID = 'integer|string', void = '', } @@ -133,6 +145,10 @@ end --- @param t string --- @return string local function api_type(t) + if vim.startswith(t, '*') then + return api_type(t:sub(2)) .. '?' + end + local as0 = t:match('^ArrayOf%((.*)%)') if as0 then local as = split(as0, ', ') @@ -149,6 +165,33 @@ local function api_type(t) return 'table' end + local u = t:match('^Union%((.*)%)') + if u then + local us = vim.split(u, ',%s*') + return table.concat(vim.tbl_map(api_type, us), '|') + end + + local l = t:match('^LuaRefOf%((.*)%)') + if l then + --- @type string + l = l:gsub('%s+', ' ') + --- @type string?, string? + local as, r = l:match('%((.*)%),%s*(.*)') + if not as then + --- @type string + as = assert(l:match('%((.*)%)')) + end + + local as1 = {} --- @type string[] + for a in vim.gsplit(as, ',%s') do + local a1 = vim.split(a, '%s+', { trimempty = true }) + local nm = a1[2]:gsub('%*(.*)$', '%1?') + as1[#as1 + 1] = nm .. ': ' .. api_type(a1[1]) + end + + return ('fun(%s)%s'):format(table.concat(as1, ', '), r and ': ' .. api_type(r) or '') + end + return API_TYPES[t] or t end @@ -251,11 +294,13 @@ local function get_api_meta() sees[#sees + 1] = see.desc end + local pty_overrides = LUA_API_PARAM_OVERRIDES[fun.name] or {} + local params = {} --- @type [string,string][] for _, p in ipairs(fun.params) do params[#params + 1] = { p.name, - api_type(p.type), + api_type(pty_overrides[p.name] or p.type), not deprecated and p.desc or nil, } end @@ -382,9 +427,11 @@ local function get_api_keysets_meta() local keysets = metadata.keysets for _, k in ipairs(keysets) do + local pty_overrides = LUA_API_KEYSET_OVERRIDES[k.name] or {} local params = {} for _, key in ipairs(k.keys) do - table.insert(params, { key .. '?', api_type(k.types[key] or 'any') }) + local pty = pty_overrides[key] or k.types[key] or 'any' + table.insert(params, { key .. '?', api_type(pty) }) end ret[k.name] = { signature = 'NA', diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 22932fd1a2..db87500d08 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -631,7 +631,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou FUNC_API_SINCE(9) { char *augroup_name = name.data; - bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); + bool clear_autocmds = GET_BOOL_OR_TRUE(opts, create_augroup, clear); int augroup = -1; WITH_SCRIPT_CONTEXT(channel_id, { diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index ab57d5c009..709e18af73 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -226,8 +226,8 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err addr = "?"; break; } - PUT_KEY(result, cmd, addr, CSTR_AS_OBJ(addr)); - PUT_KEY(result, cmd, nextcmd, CSTR_AS_OBJ(ea.nextcmd)); + PUT_KEY(result, cmd, addr, cstr_as_string(addr)); + PUT_KEY(result, cmd, nextcmd, cstr_as_string(ea.nextcmd)); // TODO(bfredl): nested keydict would be nice.. Dict mods = arena_dict(arena, 20); diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 96aabb851f..48f5f7246c 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -8,18 +8,18 @@ typedef struct { typedef struct { OptionalKeys is_set__context_; - Array types; + ArrayOf(String) types; } Dict(context); typedef struct { OptionalKeys is_set__set_decoration_provider_; - LuaRef on_start; - LuaRef on_buf; - LuaRef on_win; - LuaRef on_line; - LuaRef on_end; - LuaRef _on_hl_def; - LuaRef _on_spell_nav; + LuaRefOf(("start" _, Integer tick)) on_start; + LuaRefOf(("buf" _, Integer bufnr, Integer tick)) on_buf; + LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow)) on_win; + LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row)) on_line; + LuaRefOf(("end" _, Integer tick)) on_end; + LuaRefOf(("hl_def" _)) _on_hl_def; + LuaRefOf(("spell_nav" _)) _on_spell_nav; } Dict(set_decoration_provider); typedef struct { @@ -116,7 +116,7 @@ typedef struct { String relative; String split; Window win; - Array bufpos; + ArrayOf(Integer) bufpos; Boolean external; Boolean focusable; Boolean mouse; @@ -172,17 +172,17 @@ typedef struct { Boolean altfont; Boolean nocombine; Boolean default_ DictKey(default); - Object cterm; - Object foreground; - Object fg; - Object background; - Object bg; - Object ctermfg; - Object ctermbg; - Object special; - Object sp; - Object link; - Object global_link; + Union(Integer, String) cterm; + Union(Integer, String) foreground; + Union(Integer, String) fg; + Union(Integer, String) background; + Union(Integer, String) bg; + Union(Integer, String) ctermfg; + Union(Integer, String) ctermbg; + Union(Integer, String) special; + Union(Integer, String) sp; + HLGroupID link; + HLGroupID global_link; Boolean fallback; Integer blend; Boolean fg_indexed; @@ -230,9 +230,9 @@ typedef struct { typedef struct { OptionalKeys is_set__clear_autocmds_; Buffer buffer; - Object event; - Object group; - Object pattern; + Union(String, ArrayOf(String)) event; + Union(Integer, String) group; + Union(String, ArrayOf(String)) pattern; } Dict(clear_autocmds); typedef struct { @@ -241,31 +241,32 @@ typedef struct { Object callback; String command; String desc; - Object group; + Union(Integer, String) group; Boolean nested; Boolean once; - Object pattern; + Union(String, ArrayOf(String)) pattern; } Dict(create_autocmd); typedef struct { OptionalKeys is_set__exec_autocmds_; Buffer buffer; - Object group; + Union(Integer, String) group; Boolean modeline; - Object pattern; + Union(String, ArrayOf(String)) pattern; Object data; } Dict(exec_autocmds); typedef struct { OptionalKeys is_set__get_autocmds_; - Object event; - Object group; - Object pattern; - Object buffer; + Union(String, ArrayOf(String)) event; + Union(Integer, String) group; + Union(String, ArrayOf(String)) pattern; + Union(Integer, ArrayOf(Integer)) buffer; } Dict(get_autocmds); typedef struct { - Object clear; + OptionalKeys is_set__create_augroup_; + Boolean clear; } Dict(create_augroup); typedef struct { @@ -275,12 +276,12 @@ typedef struct { Integer count; String reg; Boolean bang; - Array args; + ArrayOf(String) args; Dict magic; Dict mods; - Object nargs; - Object addr; - Object nextcmd; + Union(Integer, String) nargs; + String addr; + String nextcmd; } Dict(cmd); typedef struct { @@ -333,11 +334,30 @@ typedef struct { typedef struct { OptionalKeys is_set__buf_attach_; - LuaRef on_lines; - LuaRef on_bytes; - LuaRef on_changedtick; - LuaRef on_detach; - LuaRef on_reload; + LuaRefOf(("lines" _, + Integer bufnr, + Integer changedtick, + Integer first, + Integer last_old, + Integer last_new, + Integer byte_count, + Integer *deleted_codepoints, + Integer *deleted_codeunits), *Boolean) on_lines; + LuaRefOf(("bytes" _, + Integer bufnr, + Integer changedtick, + Integer start_row, + Integer start_col, + Integer start_byte, + Integer old_end_row, + Integer old_end_col, + Integer old_end_byte, + Integer new_end_row, + Integer new_end_col, + Integer new_end_byte), *Boolean) on_bytes; + LuaRefOf(("changedtick" _, Integer bufnr, Integer changedtick)) on_changedtick; + LuaRefOf(("detach" _, Integer bufnr)) on_detach; + LuaRefOf(("reload" _, Integer bufnr)) on_reload; Boolean utf_sizes; Boolean preview; } Dict(buf_attach); @@ -350,7 +370,7 @@ typedef struct { typedef struct { OptionalKeys is_set__open_term_; - LuaRef on_input; + LuaRefOf(("input" _, Integer term, Integer bufnr, any data)) on_input; Boolean force_crlf; } Dict(open_term); @@ -361,12 +381,13 @@ typedef struct { typedef struct { OptionalKeys is_set__xdl_diff_; - LuaRef on_hunk; + LuaRefOf((Integer start_a, Integer count_a, Integer start_b, Integer count_b), + *Integer) on_hunk; String result_type; String algorithm; Integer ctxlen; Integer interhunkctxlen; - Object linematch; + Union(Boolean, Integer) linematch; Boolean ignore_whitespace; Boolean ignore_whitespace_change; Boolean ignore_whitespace_change_at_eol; diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 26d5ac09a8..6dee86dcf5 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -21,6 +21,8 @@ # define Dict(name) KeyDict_##name # define DictHash(name) KeyDict_##name##_get_field # define DictKey(name) +# define LuaRefOf(...) LuaRef +# define Union(...) Object # include "api/private/defs.h.inline.generated.h" #endif diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index ed6e30ea10..a0a9c86789 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -3,7 +3,7 @@ local lpeg = vim.lpeg -- lpeg grammar for building api metadata from a set of header files. It -- ignores comments and preprocessor commands and parses a very small subset -- of C prototypes with a limited set of types -local P, R, S = lpeg.P, lpeg.R, lpeg.S +local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V local C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg --- @param pat vim.lpeg.Pattern @@ -35,9 +35,39 @@ local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(not_nl), 'comment'))) local c_preproc = P('#') * rep(not_nl) local dllexport = P('DLLEXPORT') * rep1(ws) -local typed_container = ((P('ArrayOf(') + P('DictOf(') + P('Dict(')) * rep1(any - P(')')) * P(')')) +--- @param x vim.lpeg.Pattern +local function comma1(x) + return x * rep(fill * P(',') * fill * x) +end + +-- Define the grammar + +local basic_id = letter * rep(alpha) + +local str = P('"') * rep(any - P('"')) * P('"') + +--- @param x vim.lpeg.Pattern +local function paren(x) + return P('(') * fill * x * fill * P(')') +end + +-- stylua: ignore start +local typed_container = P({ + 'S', + ID = V('S') + basic_id, + S = ( + (P('Union') * paren(comma1(V('ID')))) + + (P('ArrayOf') * paren(basic_id * opt(P(',') * fill * rep1(num)))) + + (P('DictOf') * paren(basic_id)) + + (P('LuaRefOf') * paren( + paren(comma1((V('ID') + str) * rep1(ws) * opt(P('*')) * basic_id)) + * opt(P(',') * fill * opt(P('*')) * V('ID')) + )) + + (P('Dict') * paren(basic_id))), +}) +-- stylua: ignore end -local c_id = (typed_container + (letter * rep(alpha))) +local c_id = typed_container + basic_id local c_void = P('void') local c_param_type = ( diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index a78f746fee..402382acd2 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -347,12 +347,16 @@ for _, k in ipairs(keysets) do local function typename(type) if type == 'HLGroupID' then return 'kObjectTypeInteger' + elseif not type or vim.startswith(type, 'Union') then + return 'kObjectTypeNil' + elseif vim.startswith(type, 'LuaRefOf') then + return 'kObjectTypeLuaRef' elseif type == 'StringArray' then return 'kUnpackTypeStringArray' - elseif type ~= nil then - return 'kObjectType' .. type + elseif vim.startswith(type, 'ArrayOf') then + return 'kObjectTypeArray' else - return 'kObjectTypeNil' + return 'kObjectType' .. type end end diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 5f0a2e0e4e..b063a7708d 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -1075,10 +1075,10 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e return hlattrs; } if (HAS_KEY_X(dict, global_link)) { - *link_id = object_to_hl_id(dict->global_link, "link", err); + *link_id = (int)dict->global_link; mask |= HL_GLOBAL; } else { - *link_id = object_to_hl_id(dict->link, "link", err); + *link_id = (int)dict->link; } if (ERROR_SET(err)) { -- cgit From 29c72cdf4a4913c152f037865cb28c78a8930340 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 25 Nov 2024 11:48:11 -0600 Subject: fix(lsp): retrigger diagnostics request on server cancellation (#31345) Co-authored-by: Jesse --- runtime/doc/lsp.txt | 3 +- runtime/lua/vim/lsp/diagnostic.lua | 12 +++++- runtime/lua/vim/lsp/handlers.lua | 3 +- runtime/lua/vim/lsp/protocol.lua | 1 + test/functional/fixtures/fake-lsp-server.lua | 15 +++++++ test/functional/plugin/lsp/diagnostic_spec.lua | 57 ++++++++++++++++++++++++++ test/functional/plugin/lsp_spec.lua | 34 ++++++++++++++- 7 files changed, 120 insertions(+), 5 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 1253c01547..350edc068f 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1534,12 +1534,13 @@ get_namespace({client_id}, {is_pull}) client. Defaults to push *vim.lsp.diagnostic.on_diagnostic()* -on_diagnostic({_}, {result}, {ctx}) +on_diagnostic({error}, {result}, {ctx}) |lsp-handler| for the method "textDocument/diagnostic" See |vim.diagnostic.config()| for configuration options. Parameters: ~ + • {error} (`lsp.ResponseError?`) • {result} (`lsp.DocumentDiagnosticReport`) • {ctx} (`lsp.HandlerContext`) diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 12984f8c26..3f135d84f3 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -246,10 +246,18 @@ end --- --- See |vim.diagnostic.config()| for configuration options. --- ----@param _ lsp.ResponseError? +---@param error lsp.ResponseError? ---@param result lsp.DocumentDiagnosticReport ---@param ctx lsp.HandlerContext -function M.on_diagnostic(_, result, ctx) +function M.on_diagnostic(error, result, ctx) + if error ~= nil and error.code == protocol.ErrorCodes.ServerCancelled then + if error.data == nil or error.data.retriggerRequest ~= false then + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + client:request(ctx.method, ctx.params) + end + return + end + if result == nil or result.kind == 'unchanged' then return end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 5c28d88b38..1945040bda 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -659,7 +659,8 @@ for k, fn in pairs(M) do }) end - if err then + -- ServerCancelled errors should be propagated to the request handler + if err and err.code ~= protocol.ErrorCodes.ServerCancelled then -- LSP spec: -- interface ResponseError: -- code: integer; diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 7db48b0c06..3d29dad90a 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -174,6 +174,7 @@ local constants = { -- Defined by the protocol. RequestCancelled = -32800, ContentModified = -32801, + ServerCancelled = -32802, }, -- Describes the content type that a client supports in various diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index f813927f77..5d7ab2ad12 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -386,6 +386,21 @@ function tests.check_forward_content_modified() } end +function tests.check_forward_server_cancelled() + skeleton { + on_init = function() + return { capabilities = {} } + end, + body = function() + expect_request('error_code_test', function() + return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 } + end) + expect_notification('finish') + notify('finish') + end, + } +end + function tests.check_pending_request_tracked() skeleton { on_init = function(_) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 5afbe22793..ca9196562c 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -209,10 +209,16 @@ describe('vim.lsp.diagnostic', function() before_each(function() exec_lua(create_server_definition) exec_lua(function() + _G.requests = 0 _G.server = _G._create_server({ capabilities = { diagnosticProvider = {}, }, + handlers = { + [vim.lsp.protocol.Methods.textDocument_diagnostic] = function() + _G.requests = _G.requests + 1 + end, + }, }) function _G.get_extmarks(bufnr, client_id0) @@ -373,5 +379,56 @@ describe('vim.lsp.diagnostic', function() end) ) end) + + it('handles server cancellation', function() + eq( + 1, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + -- Empty data defaults to retriggering request + data = {}, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = true }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = false }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + end) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 332a1a48bb..e30d1ba411 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1066,6 +1066,39 @@ describe('LSP', function() } end) + it('should forward ServerCancelled to callback', function() + local expected_handlers = { + { NIL, {}, { method = 'finish', client_id = 1 } }, + { + { code = -32802 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'check_forward_server_cancelled', + on_init = function(_client) + _client:request('error_code_test') + client = _client + end, + on_exit = function(code, signal) + eq(0, code, 'exit code') + eq(0, signal, 'exit signal') + eq(0, #expected_handlers, 'did not call expected handler') + end, + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') + if ctx.method ~= 'finish' then + client:notify('finish') + end + if ctx.method == 'finish' then + client:stop() + end + end, + } + end) + it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, @@ -1089,7 +1122,6 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') - -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then client:notify('finish') end -- cgit From f81131cca2b4bf28f3d0a2411b13d0082a580903 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 25 Nov 2024 16:32:03 -0600 Subject: fix(tui): also reset cursor color if it was invisible (#31348) --- src/nvim/tui/tui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index cb893ec777..6e0012096b 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1304,7 +1304,7 @@ static void tui_set_mode(TUIData *tui, ModeShape mode) unibi_out_ext(tui, tui->unibi_ext.set_cursor_color); tui->cursor_has_color = true; } - } else if (c.id == 0 && tui->cursor_has_color) { + } else if (c.id == 0 && (tui->want_invisible || tui->cursor_has_color)) { // No cursor color for this mode; reset to default. tui->want_invisible = false; tui->cursor_has_color = false; -- cgit From c644228e1dfe9f70aae53292b328be98dc95b8f7 Mon Sep 17 00:00:00 2001 From: Marcus Caisey Date: Mon, 25 Nov 2024 22:33:11 +0000 Subject: fix(defaults): omit empty line from unimpaired mapping messages (#31347) Problem: The default unimpaired mappings display an empty line after the command's output. This results (with default configuration) in the `Press ENTER or type command to continue` prompt to be displayed, like so: ``` (2 of 16): item2 Press ENTER or type command to continue ``` Solution: The cause is that we're checking the second return value from `pcall(vim.api.nvim_cmd, opts, {})` to determine whether the call was successful. `nvim_cmd` returns an empty string on success, so this value is an empty string in the successful path which we then display. The fix is simple: check the first return value instead which is the "status code" of the call. --- runtime/lua/vim/_defaults.lua | 4 ++-- test/functional/editor/defaults_spec.lua | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 06f6ed6829..2687f34302 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -222,8 +222,8 @@ do --- Execute a command and print errors without a stacktrace. --- @param opts table Arguments to |nvim_cmd()| local function cmd(opts) - local _, err = pcall(vim.api.nvim_cmd, opts, {}) - if err then + local ok, err = pcall(vim.api.nvim_cmd, opts, {}) + if not ok then vim.api.nvim_err_writeln(err:sub(#'Vim:' + 1)) end end diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua index 82d285cc9a..3b0be5c9d9 100644 --- a/test/functional/editor/defaults_spec.lua +++ b/test/functional/editor/defaults_spec.lua @@ -95,7 +95,34 @@ describe('default', function() describe('key mappings', function() describe('unimpaired-style mappings', function() - it('do not show a full stack trace #30625', function() + it('show the command ouptut when successful', function() + n.clear({ args_rm = { '--cmd' } }) + local screen = Screen.new(40, 8) + n.fn.setqflist({ + { filename = 'file1', text = 'item1' }, + { filename = 'file2', text = 'item2' }, + }) + + n.feed(']q') + + screen:set_default_attr_ids({ + [1] = { foreground = Screen.colors.NvimDarkGrey4 }, + [2] = { + background = Screen.colors.NvimLightGray3, + foreground = Screen.colors.NvimDarkGrey3, + }, + }) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*5 + {2:file2 0,0-1 All}| + (2 of 2): item2 | + ]], + }) + end) + + it('do not show a full stack trace when unsuccessful #30625', function() n.clear({ args_rm = { '--cmd' } }) local screen = Screen.new(40, 8) screen:set_default_attr_ids({ -- cgit From 66bb1e577c96d8eb63c04dcc737394b4ce2b0f5d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 26 Nov 2024 07:53:07 +0800 Subject: vim-patch:9.1.0888: leftcol property not available in getwininfo() (#31349) Problem: leftcol property not available in getwininfo() Solution: add leftcol property property (glepnir) closes: vim/vim#16119 https://github.com/vim/vim/commit/0a850673e3d4193d55f47bcbbc0b0da5f155307d Co-authored-by: glepnir --- runtime/doc/builtin.txt | 2 ++ runtime/lua/vim/_meta/vimfn.lua | 2 ++ src/nvim/eval.lua | 2 ++ src/nvim/eval/window.c | 1 + test/old/testdir/test_bufwintabinfo.vim | 12 ++++++++++++ 5 files changed, 19 insertions(+) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index ada3b7103c..585db21a0b 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4315,6 +4315,8 @@ getwininfo([{winid}]) *getwininfo()* botline last complete displayed buffer line bufnr number of buffer in the window height window height (excluding winbar) + leftcol first column displayed; only used when + 'wrap' is off loclist 1 if showing a location list quickfix 1 if quickfix or location list window terminal 1 if a terminal window diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 5eb15e1eee..f9b5d93a4b 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3885,6 +3885,8 @@ function vim.fn.gettext(text) end --- botline last complete displayed buffer line --- bufnr number of buffer in the window --- height window height (excluding winbar) +--- leftcol first column displayed; only used when +--- 'wrap' is off --- loclist 1 if showing a location list --- quickfix 1 if quickfix or location list window --- terminal 1 if a terminal window diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index a418b34909..cd3ccf543e 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4800,6 +4800,8 @@ M.funcs = { botline last complete displayed buffer line bufnr number of buffer in the window height window height (excluding winbar) + leftcol first column displayed; only used when + 'wrap' is off loclist 1 if showing a location list quickfix 1 if quickfix or location list window terminal 1 if a terminal window diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 86495f1cb6..a9d3e89177 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -326,6 +326,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("leftcol"), wp->w_leftcol); tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); tv_dict_add_nr(dict, S_LEN("width"), wp->w_width_inner); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); diff --git a/test/old/testdir/test_bufwintabinfo.vim b/test/old/testdir/test_bufwintabinfo.vim index 57492e07c9..0a4bd0b674 100644 --- a/test/old/testdir/test_bufwintabinfo.vim +++ b/test/old/testdir/test_bufwintabinfo.vim @@ -114,6 +114,18 @@ func Test_getbufwintabinfo() wincmd t | only endfunc +function Test_get_wininfo_leftcol() + set nowrap + set winwidth=10 + vsp + call setline(1, ['abcdefghijklmnopqrstuvwxyz']) + norm! 5zl + call assert_equal(5, getwininfo()[0].leftcol) + bwipe! + set wrap& + set winwidth& +endfunc + function Test_get_buf_options() let opts = bufnr()->getbufvar('&') call assert_equal(v:t_dict, type(opts)) -- cgit From 3d707e6f14b7db64b3510f58bf9321c71163f552 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 26 Nov 2024 06:15:50 -0800 Subject: fix(lua): remove vim.loader.disable() #31344 Problem: `vim.loader.disable` does not conform to `:help dev-name-common` and `:help dev-patterns`. Solution: - Add `enable` parameter to `vim.loader.enable` - Remove `vim.loader.disable` - Note the change in `:help news-breaking-dev` (HEAD changes). - This is not a breaking change (except to "HEAD") because `vim.loader` is marked "experimental". previous: 26765e8461c1ba1e9a351632212cf89900221781 --- runtime/doc/lua.txt | 20 +++++++----- runtime/doc/news.txt | 6 +++- runtime/lua/vim/loader.lua | 65 +++++++++++++++++++------------------ test/functional/lua/loader_spec.lua | 18 +++++++--- 4 files changed, 63 insertions(+), 46 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 4d4a51872a..cf53825c68 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2485,22 +2485,24 @@ vim.validate({name}, {value}, {validator}, {optional}, {message}) ============================================================================== Lua module: vim.loader *vim.loader* -vim.loader.disable() *vim.loader.disable()* +vim.loader.enable({enable}) *vim.loader.enable()* WARNING: This feature is experimental/unstable. - Disables the experimental Lua module loader: - • removes the loaders - • adds the default Nvim loader + Enables or disables the experimental Lua module loader: -vim.loader.enable() *vim.loader.enable()* - WARNING: This feature is experimental/unstable. - - Enables the experimental Lua module loader: - • overrides loadfile + Enable (`enable=true`): + • overrides |loadfile()| • adds the Lua loader using the byte-compilation cache • adds the libs loader • removes the default Nvim loader + Disable (`enable=false`): + • removes the loaders + • adds the default Nvim loader + + Parameters: ~ + • {enable} (`boolean?`) true/nil to enable, false to disable + vim.loader.find({modname}, {opts}) *vim.loader.find()* WARNING: This feature is experimental/unstable. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 58ab7ef44c..9c69970400 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -11,13 +11,17 @@ For changes in the previous release, see |news-0.10|. Type |gO| to see the table of contents. ============================================================================== -BREAKING CHANGES IN HEAD *news-breaking-dev* +BREAKING CHANGES IN HEAD OR EXPERIMENTAL *news-breaking-dev* ====== Remove this section before release. ====== The following changes to UNRELEASED features were made during the development cycle (Nvim HEAD, the "master" branch). +EXPERIMENTS + +• Removed `vim.loader.disable()`. Use `vim.loader.enable(false)` instead. + OPTIONS • 'jumpoptions' flag "unload" has been renamed to "clean". diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 0cce0ab21d..71d0188128 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -399,50 +399,51 @@ function M.reset(path) end end ---- Enables the experimental Lua module loader: ---- * overrides loadfile +--- Enables or disables the experimental Lua module loader: +--- +--- Enable (`enable=true`): +--- * overrides |loadfile()| --- * adds the Lua loader using the byte-compilation cache --- * adds the libs loader --- * removes the default Nvim loader --- ---- @since 0 -function M.enable() - if M.enabled then - return - end - M.enabled = true - vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') - _G.loadfile = loadfile_cached - -- add Lua loader - table.insert(loaders, 2, loader_cached) - -- add libs loader - table.insert(loaders, 3, loader_lib_cached) - -- remove Nvim loader - for l, loader in ipairs(loaders) do - if loader == vim._load_package then - table.remove(loaders, l) - break - end - end -end - ---- Disables the experimental Lua module loader: +--- Disable (`enable=false`): --- * removes the loaders --- * adds the default Nvim loader --- --- @since 0 -function M.disable() - if not M.enabled then +--- +--- @param enable? (boolean) true/nil to enable, false to disable +function M.enable(enable) + enable = enable == nil and true or enable + if enable == M.enabled then return end - M.enabled = false - _G.loadfile = _loadfile - for l, loader in ipairs(loaders) do - if loader == loader_cached or loader == loader_lib_cached then - table.remove(loaders, l) + M.enabled = enable + + if enable then + vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') + _G.loadfile = loadfile_cached + -- add Lua loader + table.insert(loaders, 2, loader_cached) + -- add libs loader + table.insert(loaders, 3, loader_lib_cached) + -- remove Nvim loader + for l, loader in ipairs(loaders) do + if loader == vim._load_package then + table.remove(loaders, l) + break + end + end + else + _G.loadfile = _loadfile + for l, loader in ipairs(loaders) do + if loader == loader_cached or loader == loader_lib_cached then + table.remove(loaders, l) + end end + table.insert(loaders, 2, vim._load_package) end - table.insert(loaders, 2, vim._load_package) end --- Tracks the time spent in a function diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua index 8508f2aa14..20d3a940b2 100644 --- a/test/functional/lua/loader_spec.lua +++ b/test/functional/lua/loader_spec.lua @@ -10,7 +10,17 @@ local eq = t.eq describe('vim.loader', function() before_each(clear) - it('can work in compatibility with --luamod-dev #27413', function() + it('can be disabled', function() + exec_lua(function() + local orig_loader = _G.loadfile + vim.loader.enable() + assert(orig_loader ~= _G.loadfile) + vim.loader.enable(false) + assert(orig_loader == _G.loadfile) + end) + end) + + it('works with --luamod-dev #27413', function() clear({ args = { '--luamod-dev' } }) exec_lua(function() vim.loader.enable() @@ -31,7 +41,7 @@ describe('vim.loader', function() end) end) - it('handles changing files (#23027)', function() + it('handles changing files #23027', function() exec_lua(function() vim.loader.enable() end) @@ -63,7 +73,7 @@ describe('vim.loader', function() ) end) - it('handles % signs in modpath (#24491)', function() + it('handles % signs in modpath #24491', function() exec_lua [[ vim.loader.enable() ]] @@ -82,7 +92,7 @@ describe('vim.loader', function() eq(2, exec_lua('return loadfile(...)()', tmp2)) end) - it('correct indent on error message (#29809)', function() + it('indents error message #29809', function() local errmsg = exec_lua [[ vim.loader.enable() local _, errmsg = pcall(require, 'non_existent_module') -- cgit From e8450ef2368bd2d80149bf98a34571bc92fb2aba Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 26 Nov 2024 22:55:05 +0800 Subject: vim-patch:9.1.0889: Possible unnecessary redraw after adding/deleting lines (#31356) Problem: Possible unnecessary redraw after adding/deleting lines. Solution: Check b_mod_set before using b_mod_xlines to avoid using stale b_mod_xlines value (zeertzjq). closes: vim/vim#16124 https://github.com/vim/vim/commit/9f25a3a237156889df3df78dbd8f12ee6059e332 --- src/nvim/drawscreen.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 19a8093a16..e645d1bbea 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2072,7 +2072,7 @@ static void win_update(win_T *wp) // match in fixed position might need redraw // if lines were inserted or deleted || (wp->w_match_head != NULL - && buf->b_mod_xlines != 0))))) + && buf->b_mod_set && buf->b_mod_xlines != 0))))) || lnum == wp->w_cursorline || lnum == wp->w_last_cursorline) { if (lnum == mod_top) { @@ -2291,7 +2291,8 @@ static void win_update(win_T *wp) // - 'number' is set and below inserted/deleted lines, or // - 'relativenumber' is set and cursor moved vertically, // the text doesn't need to be redrawn, but the number column does. - if ((wp->w_p_nu && mod_top != 0 && lnum >= mod_bot && buf->b_mod_xlines != 0) + if ((wp->w_p_nu && mod_top != 0 && lnum >= mod_bot + && buf->b_mod_set && buf->b_mod_xlines != 0) || (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum)) { foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum ? cursorline_fi : fold_info(wp, lnum); -- cgit From 99b5ffd688247f25295f3dd06e57c0d9ad85b072 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 26 Nov 2024 13:56:01 -0600 Subject: perf(vim.text): use lookup table implementation for hex encoding (#30080) Co-authored-by: glepnir --- runtime/lua/vim/text.lua | 24 +++++++++++++++--- test/benchmark/text_spec.lua | 52 +++++++++++++++++++++++++++++++++++++++ test/functional/lua/text_spec.lua | 16 ++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 test/benchmark/text_spec.lua diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua index d45c8021c6..f910ab3a1d 100644 --- a/runtime/lua/vim/text.lua +++ b/runtime/lua/vim/text.lua @@ -2,6 +2,18 @@ local M = {} +local alphabet = '0123456789ABCDEF' +local atoi = {} ---@type table +local itoa = {} ---@type table +do + for i = 1, #alphabet do + local char = alphabet:sub(i, i) + itoa[i - 1] = char + atoi[char] = i - 1 + atoi[char:lower()] = i - 1 + end +end + --- Hex encode a string. --- --- @param str string String to encode @@ -9,7 +21,9 @@ local M = {} function M.hexencode(str) local enc = {} ---@type string[] for i = 1, #str do - enc[i] = string.format('%02X', str:byte(i, i + 1)) + local byte = str:byte(i) + enc[2 * i - 1] = itoa[math.floor(byte / 16)] + enc[2 * i] = itoa[byte % 16] end return table.concat(enc) end @@ -26,8 +40,12 @@ function M.hexdecode(enc) local str = {} ---@type string[] for i = 1, #enc, 2 do - local n = assert(tonumber(enc:sub(i, i + 1), 16)) - str[#str + 1] = string.char(n) + local u = atoi[enc:sub(i, i)] + local l = atoi[enc:sub(i + 1, i + 1)] + if not u or not l then + return nil, 'string must contain only hex characters' + end + str[(i + 1) / 2] = string.char(u * 16 + l) end return table.concat(str), nil end diff --git a/test/benchmark/text_spec.lua b/test/benchmark/text_spec.lua new file mode 100644 index 0000000000..9cfeaf765b --- /dev/null +++ b/test/benchmark/text_spec.lua @@ -0,0 +1,52 @@ +describe('vim.text', function() + --- @param t number[] + local function mean(t) + assert(#t > 0) + local sum = 0 + for _, v in ipairs(t) do + sum = sum + v + end + return sum / #t + end + + --- @param t number[] + local function median(t) + local len = #t + if len % 2 == 0 then + return t[len / 2] + end + return t[(len + 1) / 2] + end + + --- @param f fun(t: number[]): table + local function measure(f, input, N) + local stats = {} ---@type number[] + for _ = 1, N do + local tic = vim.uv.hrtime() + f(input) + local toc = vim.uv.hrtime() + stats[#stats + 1] = (toc - tic) / 1000000 + end + table.sort(stats) + print( + string.format( + '\nN: %d, Min: %0.6f ms, Max: %0.6f ms, Median: %0.6f ms, Mean: %0.6f ms', + N, + math.min(unpack(stats)), + math.max(unpack(stats)), + median(stats), + mean(stats) + ) + ) + end + + local input, output = string.rep('😂', 2 ^ 16), string.rep('F09F9882', 2 ^ 16) + + it('hexencode', function() + measure(vim.text.hexencode, input, 100) + end) + + it('hexdecode', function() + measure(vim.text.hexdecode, output, 100) + end) +end) diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua index be471bfd62..dd08a6ec04 100644 --- a/test/functional/lua/text_spec.lua +++ b/test/functional/lua/text_spec.lua @@ -26,5 +26,21 @@ describe('vim.text', function() eq(output, vim.text.hexencode(input)) eq(input, vim.text.hexdecode(output)) end) + + it('errors on invalid input', function() + -- Odd number of hex characters + do + local res, err = vim.text.hexdecode('ABC') + eq(nil, res) + eq('string must have an even number of hex characters', err) + end + + -- Non-hexadecimal input + do + local res, err = vim.text.hexdecode('nothex') + eq(nil, res) + eq('string must contain only hex characters', err) + end + end) end) end) -- cgit From d460928263d0ff53283f301dfcb85f5b6e17d2ac Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 26 Nov 2024 14:22:01 -0600 Subject: feat(tui): update 'background' on theme change events (#31350) Enabling private DEC mode 2031 tells the terminal to notify Nvim whenever the OS theme changes (i.e. light mode to dark mode or vice versa) or the terminal emulator's palette changes. When we receive one of these notifications we query the terminal color's background color again to see if it has changed and update the value of 'background' if it has. We only do this though if the user has not explicitly set the value of 'bg' themselves. The help text is updated slightly to hint to users that they probably shouldn't set this value: on modern terminal emulators Nvim is able to completely determine this automatically. --- runtime/doc/news.txt | 2 ++ runtime/lua/vim/_defaults.lua | 49 ++++++++++++++++++++++++++----------------- src/nvim/tui/input.c | 39 ++++++++++++++++++++++++++++++++++ src/nvim/tui/input.h | 1 + src/nvim/tui/tui.c | 35 ++++++++++++++++++++++++------- src/nvim/tui/tui_defs.h | 1 + 6 files changed, 100 insertions(+), 27 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 9c69970400..98782bfd15 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -284,6 +284,8 @@ TUI :lua =vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan) • |log| messages written by the builtin UI client (TUI, |--remote-ui|) are now prefixed with "ui" instead of "?". +• The TUI will re-query the terminal's background color when a theme update + notification is received and Nvim will update 'background' accordingly. UI diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 2687f34302..6583cf48b3 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -546,8 +546,9 @@ do --- --- @param option string Option name --- @param value any Option value - local function setoption(option, value) - if vim.api.nvim_get_option_info2(option, {}).was_set then + --- @param force boolean? Always set the value, even if already set + local function setoption(option, value, force) + if not force and vim.api.nvim_get_option_info2(option, {}).was_set then -- Don't do anything if option is already set return end @@ -563,7 +564,7 @@ do once = true, nested = true, callback = function() - setoption(option, value) + setoption(option, value, force) end, }) end @@ -645,11 +646,15 @@ do return nil, nil, nil end - local timer = assert(vim.uv.new_timer()) - + -- This autocommand updates the value of 'background' anytime we receive + -- an OSC 11 response from the terminal emulator. If the user has set + -- 'background' explictly then we will delete this autocommand, + -- effectively disabling automatic background setting. + local force = false local id = vim.api.nvim_create_autocmd('TermResponse', { group = group, nested = true, + desc = "Update the value of 'background' automatically based on the terminal emulator's background color", callback = function(args) local resp = args.data ---@type string local r, g, b = parseosc11(resp) @@ -661,27 +666,33 @@ do if rr and gg and bb then local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) local bg = luminance < 0.5 and 'dark' or 'light' - setoption('background', bg) + setoption('background', bg, force) + + -- On the first query response, don't force setting the option in + -- case the user has already set it manually. If they have, then + -- this autocommand will be deleted. If they haven't, then we do + -- want to force setting the option to override the value set by + -- this autocommand. + if not force then + force = true + end end + end + end, + }) - return true + vim.api.nvim_create_autocmd('VimEnter', { + group = group, + nested = true, + once = true, + callback = function() + if vim.api.nvim_get_option_info2('background', {}).was_set then + vim.api.nvim_del_autocmd(id) end end, }) io.stdout:write('\027]11;?\007') - - timer:start(1000, 0, function() - -- Delete the autocommand if no response was received - vim.schedule(function() - -- Suppress error if autocommand has already been deleted - pcall(vim.api.nvim_del_autocmd, id) - end) - - if not timer:is_closing() then - timer:close() - end - end) end --- If the TUI (term_has_truecolor) was able to determine that the host diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 98dd7b4b45..744d306c06 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -160,12 +160,16 @@ void tinput_init(TermInput *input, Loop *loop) // initialize a timer handle for handling ESC with libtermkey uv_timer_init(&loop->uv, &input->timer_handle); input->timer_handle.data = input; + + uv_timer_init(&loop->uv, &input->bg_query_timer); + input->bg_query_timer.data = input; } void tinput_destroy(TermInput *input) { map_destroy(int, &kitty_key_map); uv_close((uv_handle_t *)&input->timer_handle, NULL); + uv_close((uv_handle_t *)&input->bg_query_timer, NULL); rstream_may_close(&input->read_stream); termkey_destroy(input->tk); } @@ -179,6 +183,7 @@ void tinput_stop(TermInput *input) { rstream_stop(&input->read_stream); uv_timer_stop(&input->timer_handle); + uv_timer_stop(&input->bg_query_timer); } static void tinput_done_event(void **argv) @@ -474,6 +479,13 @@ static void tinput_timer_cb(uv_timer_t *handle) tinput_flush(input); } +static void bg_query_timer_cb(uv_timer_t *handle) + FUNC_ATTR_NONNULL_ALL +{ + TermInput *input = handle->data; + tui_query_bg_color(input->tui_data); +} + /// Handle focus events. /// /// If the upcoming sequence of bytes in the input stream matches the termcode @@ -660,6 +672,33 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) } } break; + case 'n': + // Device Status Report (DSR) + if (nparams == 2) { + int args[2]; + for (size_t i = 0; i < ARRAY_SIZE(args); i++) { + if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) { + return; + } + } + + if (args[0] == 997) { + // Theme update notification + // https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md + // The second argument tells us whether the OS theme is set to light + // mode or dark mode, but all we care about is the background color of + // the terminal emulator. We query for that with OSC 11 and the response + // is handled by the autocommand created in _defaults.lua. The terminal + // may send us multiple notifications all at once so we use a timer to + // coalesce the queries. + if (uv_timer_get_due_in(&input->bg_query_timer) > 0) { + return; + } + + uv_timer_start(&input->bg_query_timer, bg_query_timer_cb, 100, 0); + } + } + break; default: break; } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 4c2baf908e..f6aaff30de 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -32,6 +32,7 @@ typedef struct { TermKey *tk; TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook uv_timer_t timer_handle; + uv_timer_t bg_query_timer; ///< timer used to batch background color queries Loop *loop; RStream read_stream; TUIData *tui_data; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 6e0012096b..603db5b891 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -241,16 +241,19 @@ void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state) tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync", "\x1b[?2026%?%p1%{1}%-%tl%eh%;"); break; - case kTermModeResizeEvents: - signal_watcher_stop(&tui->winch_handle); - tui_set_term_mode(tui, mode, true); - break; case kTermModeGraphemeClusters: if (!is_set) { tui_set_term_mode(tui, mode, true); tui->did_set_grapheme_cluster_mode = true; } break; + case kTermModeThemeUpdates: + tui_set_term_mode(tui, mode, true); + break; + case kTermModeResizeEvents: + signal_watcher_stop(&tui->winch_handle); + tui_set_term_mode(tui, mode, true); + break; } } } @@ -320,6 +323,18 @@ static void tui_reset_key_encoding(TUIData *tui) } } +/// Write the OSC 11 sequence to the terminal emulator to query the current +/// background color. +/// +/// The response will be handled by the TermResponse autocommand created in +/// _defaults.lua. +void tui_query_bg_color(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ + out(tui, S_LEN("\x1b]11;?\x07")); + flush_buf(tui); +} + /// Enable the alternate screen and emit other control sequences to start the TUI. /// /// This is also called when the TUI is resumed after being suspended. We reinitialize all state @@ -438,14 +453,13 @@ static void terminfo_start(TUIData *tui) // Enable bracketed paste unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste); - // Query support for mode 2026 (Synchronized Output). Some terminals also - // support an older DCS sequence for synchronized output, but we will only use - // mode 2026. + // Query support for private DEC modes that Nvim can take advantage of. // Some terminals (such as Terminal.app) do not support DECRQM, so skip the query. if (!nsterm) { tui_request_term_mode(tui, kTermModeSynchronizedOutput); - tui_request_term_mode(tui, kTermModeResizeEvents); tui_request_term_mode(tui, kTermModeGraphemeClusters); + tui_request_term_mode(tui, kTermModeThemeUpdates); + tui_request_term_mode(tui, kTermModeResizeEvents); } // Don't use DECRQSS in screen or tmux, as they behave strangely when receiving it. @@ -493,6 +507,10 @@ static void terminfo_start(TUIData *tui) /// Disable the alternate screen and prepare for the TUI to close. static void terminfo_stop(TUIData *tui) { + // Disable theme update notifications. We do this first to avoid getting any + // more notifications after we reset the cursor and any color palette changes. + tui_set_term_mode(tui, kTermModeThemeUpdates, false); + // Destroy output stuff tui_mode_change(tui, NULL_STRING, SHAPE_IDX_N); tui_mouse_off(tui); @@ -509,6 +527,7 @@ static void terminfo_stop(TUIData *tui) if (tui->did_set_grapheme_cluster_mode) { tui_set_term_mode(tui, kTermModeGraphemeClusters, false); } + // May restore old title before exiting alternate screen. tui_set_title(tui, NULL_STRING); if (ui_client_exit_status == 0) { diff --git a/src/nvim/tui/tui_defs.h b/src/nvim/tui/tui_defs.h index bd99d6b0ad..5d6f027bf7 100644 --- a/src/nvim/tui/tui_defs.h +++ b/src/nvim/tui/tui_defs.h @@ -5,6 +5,7 @@ typedef struct TUIData TUIData; typedef enum { kTermModeSynchronizedOutput = 2026, kTermModeGraphemeClusters = 2027, + kTermModeThemeUpdates = 2031, kTermModeResizeEvents = 2048, } TermMode; -- cgit From 078e8e57d3a0c806bffc0de70ee4a027a3af1580 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 26 Nov 2024 15:39:15 +0100 Subject: vim-patch:7e501f4: runtime(gzip): load undofile if there exists one fixes: vim/vim#16102 closes: vim/vim#16122 https://github.com/vim/vim/commit/7e501f4d9986866382888e67d8f11faf5767c8f4 Co-authored-by: Christian Brabandt --- runtime/autoload/gzip.vim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/autoload/gzip.vim b/runtime/autoload/gzip.vim index 26b1cda034..a6fbe2c336 100644 --- a/runtime/autoload/gzip.vim +++ b/runtime/autoload/gzip.vim @@ -1,6 +1,6 @@ " Vim autoload file for editing compressed files. " Maintainer: The Vim Project -" Last Change: 2023 Aug 10 +" Last Change: 2024 Nov 25 " Former Maintainer: Bram Moolenaar " These functions are used by the gzip plugin. @@ -148,6 +148,9 @@ fun gzip#read(cmd) else let fname = escape(expand("%:r"), " \t\n*?[{`$\\%#'\"|!<") endif + if filereadable(undofile(expand("%"))) + exe "sil rundo " . fnameescape(undofile(expand("%"))) + endif if &verbose >= 8 execute "doau BufReadPost " . fname else -- cgit From c24e6e66dd24a7920f48df7effe50c16527fe880 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Wed, 27 Nov 2024 12:39:51 +0100 Subject: ci: remove myself from lsp reviewers --- .github/scripts/reviewers_add.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/reviewers_add.js b/.github/scripts/reviewers_add.js index 08b4e85b74..73e1ece516 100644 --- a/.github/scripts/reviewers_add.js +++ b/.github/scripts/reviewers_add.js @@ -57,7 +57,6 @@ module.exports = async ({ github, context }) => { if (labels.includes("lsp")) { reviewers.add("MariaSolOs"); - reviewers.add("mfussenegger"); } if (labels.includes("netrw")) { -- cgit From 203e7a43d1f209168a3cdae1650a9ef01db9cd72 Mon Sep 17 00:00:00 2001 From: glepnir Date: Wed, 27 Nov 2024 21:44:29 +0800 Subject: fix(lsp): mention function name in warning #31301 Problem: The warning message is the same in different functions. It is not convenient to locate the specific function name Solution: add function name in warning message --- runtime/lua/vim/lsp/util.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3de76f93a6..ced8aa5745 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1894,7 +1894,7 @@ function M.make_position_params(window, position_encoding) local buf = api.nvim_win_get_buf(window) if position_encoding == nil then vim.notify_once( - 'warning: position_encoding is required, using the offset_encoding from the first client', + 'position_encoding param is required in vim.lsp.util.make_position_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) position_encoding = M._get_offset_encoding(buf) @@ -1950,7 +1950,7 @@ function M.make_range_params(window, position_encoding) local buf = api.nvim_win_get_buf(window or 0) if position_encoding == nil then vim.notify_once( - 'warning: position_encoding is required, using the offset_encoding from the first client', + 'position_encoding param is required in vim.lsp.util.make_range_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) position_encoding = M._get_offset_encoding(buf) @@ -1979,7 +1979,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding) bufnr = bufnr or api.nvim_get_current_buf() if position_encoding == nil then vim.notify_once( - 'warning: position_encoding is required, using the offset_encoding from the first client', + 'position_encoding param is required in vim.lsp.util.make_given_range_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) position_encoding = M._get_offset_encoding(bufnr) -- cgit From 76dcc7029b200e1d85024d7ba4a34c602e730dbe Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 27 Nov 2024 15:36:38 +0100 Subject: docs: add tag `vim.fs.exists()` and document suggested replacement --- runtime/doc/lua.txt | 11 +++++++++++ runtime/lua/vim/fs.lua | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index cf53825c68..c3dddaf888 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2926,6 +2926,17 @@ vim.keymap.set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* ============================================================================== Lua module: vim.fs *vim.fs* + + *vim.fs.exists()* +Use |uv.fs_stat()| to check a file's type, and whether it exists. + +Example: >lua + if vim.uv.fs_stat(file) then + vim.print("file exists") + end +< + + vim.fs.basename({file}) *vim.fs.basename()* Return the basename of the given path diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index d91eeaf02f..2f007d97c3 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,3 +1,15 @@ +--- @brief
help
+--- *vim.fs.exists()*
+--- Use |uv.fs_stat()| to check a file's type, and whether it exists.
+---
+--- Example:
+---
+--- >lua
+---   if vim.uv.fs_stat(file) then
+---     vim.print("file exists")
+---   end
+--- <
+
 local uv = vim.uv
 
 local M = {}
-- 
cgit 


From 5897994cb75b5b64a34017873119c87a75c42375 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Thu, 28 Nov 2024 09:02:10 +0800
Subject: vim-patch:9.1.0890: %! item not allowed for 'rulerformat' (#31369)

Problem:  %! item not allowed for 'rulerformat'
          (yatinlala)
Solution: also allow to use %! for rulerformat option
          (Yegappan Lakshmanan)

fixes: vim/vim#16091
closes: vim/vim#16118

https://github.com/vim/vim/commit/ac023e8baae65584537aa3c11494dad6f71770af

Co-authored-by: Yegappan Lakshmanan 
---
 runtime/doc/options.txt                 |  1 +
 runtime/lua/vim/_meta/options.lua       |  1 +
 src/nvim/options.lua                    |  1 +
 src/nvim/optionstr.c                    |  6 +++++-
 test/functional/legacy/cmdline_spec.lua | 16 ++++++++++++++++
 test/old/testdir/test_cmdline.vim       | 21 +++++++++++++++++++++
 6 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index ea56633c77..c5f21c64a2 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -5917,6 +5917,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 	All fields except the {item} are optional.  A single percent sign can
 	be given as "%%".
 
+							*stl-%!*
 	When the option starts with "%!" then it is used as an expression,
 	evaluated and the result is used as the option value.  Example: >vim
 		set statusline=%!MyStatusLine()
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index c635d9bd3b..247b464a70 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -6311,6 +6311,7 @@ vim.wo.stc = vim.wo.statuscolumn
 --- All fields except the {item} are optional.  A single percent sign can
 --- be given as "%%".
 ---
+--- 						*stl-%!*
 --- When the option starts with "%!" then it is used as an expression,
 --- evaluated and the result is used as the option value.  Example:
 ---
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index bcb05b107b..84c90e44a7 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -8407,6 +8407,7 @@ return {
         All fields except the {item} are optional.  A single percent sign can
         be given as "%%".
 
+        						*stl-%!*
         When the option starts with "%!" then it is used as an expression,
         evaluated and the result is used as the option value.  Example: >vim
         	set statusline=%!MyStatusLine()
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index b47517b1a2..918443db9f 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -2191,7 +2191,11 @@ static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerf
     if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) {
       ru_wid = wid;
     } else {
-      errmsg = check_stl_option(p_ruf);
+      // Validate the flags in 'rulerformat' only if it doesn't point to
+      // a custom function ("%!" flag).
+      if ((*varp)[1] != '!') {
+        errmsg = check_stl_option(p_ruf);
+      }
     }
   } else if (rulerformat || s[0] != '%' || s[1] != '!') {
     // check 'statusline', 'winbar', 'tabline' or 'statuscolumn'
diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua
index bf146e1322..3addcb957c 100644
--- a/test/functional/legacy/cmdline_spec.lua
+++ b/test/functional/legacy/cmdline_spec.lua
@@ -216,6 +216,22 @@ describe('cmdline', function()
                 longish   |
     ]]
   end)
+
+  -- oldtest: Test_rulerformat_function()
+  it("'rulerformat' can use %!", function()
+    local screen = Screen.new(40, 2)
+    exec([[
+      func TestRulerFn()
+        return '10,20%=30%%'
+      endfunc
+    ]])
+    api.nvim_set_option_value('ruler', true, {})
+    api.nvim_set_option_value('rulerformat', '%!TestRulerFn()', {})
+    screen:expect([[
+      ^                                        |
+                            10,20         30% |
+    ]])
+  end)
 end)
 
 describe('cmdwin', function()
diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
index 6fa3ee5250..2f34ecb414 100644
--- a/test/old/testdir/test_cmdline.vim
+++ b/test/old/testdir/test_cmdline.vim
@@ -4036,6 +4036,27 @@ func Test_rulerformat_position()
   call StopVimInTerminal(buf)
 endfunc
 
+" Test for using "%!" in 'rulerformat' to use a function
+func Test_rulerformat_function()
+  CheckScreendump
+
+  let lines =<< trim END
+    func TestRulerFn()
+      return '10,20%=30%%'
+    endfunc
+  END
+  call writefile(lines, 'Xrulerformat_function', 'D')
+
+  let buf = RunVimInTerminal('-S Xrulerformat_function', #{rows: 2, cols: 40})
+  call term_sendkeys(buf, ":set ruler rulerformat=%!TestRulerFn()\")
+  call term_sendkeys(buf, ":redraw!\")
+  call term_wait(buf)
+  call VerifyScreenDump(buf, 'Test_rulerformat_function', {})
+
+  " clean up
+  call StopVimInTerminal(buf)
+endfunc
+
 func Test_getcompletion_usercmd()
   command! -nargs=* -complete=command TestCompletion echo 
 
-- 
cgit 


From 344923fe9a4d4966a8faf48d2767bfed181925c5 Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Thu, 28 Nov 2024 13:54:59 +0800
Subject: vim-patch:9.1.0867: ins_compl_add() has too many args

Problem:  ins_compl_add() has too many args
Solution: refactor it and use an int array instead of 2 separate int
          args (glepnir)

closes: vim/vim#16062

https://github.com/vim/vim/commit/5c66e23c624717216d380d938d0bba5d34a004fe

Co-authored-by: glepnir 
---
 src/nvim/insexpand.c | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index d3517667fb..154a142c74 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -758,7 +758,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir
     flags |= CP_ICASE;
   }
 
-  int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, -1, -1);
+  int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, NULL);
   xfree(tofree);
   return res;
 }
@@ -798,7 +798,7 @@ static inline void free_cptext(char *const *const cptext)
 ///         returned in case of error.
 static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext,
                          const bool cptext_allocated, typval_T *user_data, const Direction cdir,
-                         int flags_arg, const bool adup, int user_abbr_hlattr, int user_kind_hlattr)
+                         int flags_arg, const bool adup, int *extra_hl)
   FUNC_ATTR_NONNULL_ARG(1)
 {
   compl_T *match;
@@ -864,8 +864,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
     match->cp_fname = NULL;
   }
   match->cp_flags = flags;
-  match->cp_user_abbr_hlattr = user_abbr_hlattr;
-  match->cp_user_kind_hlattr = user_kind_hlattr;
+  match->cp_user_abbr_hlattr = extra_hl ? extra_hl[0] : -1;
+  match->cp_user_kind_hlattr = extra_hl ? extra_hl[1] : -1;
 
   if (cptext != NULL) {
     int i;
@@ -999,7 +999,7 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase)
   for (int i = 0; i < num_matches && add_r != FAIL; i++) {
     if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
                                CP_FAST | (icase ? CP_ICASE : 0),
-                               false, -1, -1)) == OK) {
+                               false, NULL)) == OK) {
       // If dir was BACKWARD then honor it just once.
       dir = FORWARD;
     }
@@ -2573,9 +2573,8 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
   int flags = fast ? CP_FAST : 0;
   char *(cptext[CPT_COUNT]);
   char *user_abbr_hlname = NULL;
-  int user_abbr_hlattr = -1;
   char *user_kind_hlname = NULL;
-  int user_kind_hlattr = -1;
+  int extra_hl[2] = { -1, -1 };
   typval_T user_data;
 
   user_data.v_type = VAR_UNKNOWN;
@@ -2587,10 +2586,10 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
     cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true);
 
     user_abbr_hlname = tv_dict_get_string(tv->vval.v_dict, "abbr_hlgroup", false);
-    user_abbr_hlattr = get_user_highlight_attr(user_abbr_hlname);
+    extra_hl[0] = get_user_highlight_attr(user_abbr_hlname);
 
     user_kind_hlname = tv_dict_get_string(tv->vval.v_dict, "kind_hlgroup", false);
-    user_kind_hlattr = get_user_highlight_attr(user_kind_hlname);
+    extra_hl[1] = get_user_highlight_attr(user_kind_hlname);
 
     tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data);
 
@@ -2613,8 +2612,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
     return FAIL;
   }
   int status = ins_compl_add((char *)word, -1, NULL, cptext, true,
-                             &user_data, dir, flags, dup,
-                             user_abbr_hlattr, user_kind_hlattr);
+                             &user_data, dir, flags, dup, extra_hl);
   if (status != OK) {
     tv_clear(&user_data);
   }
@@ -2707,7 +2705,7 @@ static void set_completion(colnr_T startcol, list_T *list)
     flags |= CP_ICASE;
   }
   if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
-                    flags | CP_FAST, false, -1, -1) != OK) {
+                    flags | CP_FAST, false, NULL) != OK) {
     return;
   }
 
@@ -3452,7 +3450,7 @@ static void get_next_bufname_token(void)
       char *tail = path_tail(b->b_sfname);
       if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) {
         ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0,
-                      p_ic ? CP_ICASE : 0, false, -1, -1);
+                      p_ic ? CP_ICASE : 0, false, NULL);
       }
     }
   }
@@ -4490,7 +4488,7 @@ static int ins_compl_start(void)
     flags |= CP_ICASE;
   }
   if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
-                    flags, false, -1, -1) != OK) {
+                    flags, false, NULL) != OK) {
     XFREE_CLEAR(compl_pattern);
     compl_patternlen = 0;
     XFREE_CLEAR(compl_orig_text);
-- 
cgit 


From 864f25d6b08ccfe17e0cf3fbc30639005c0145e0 Mon Sep 17 00:00:00 2001
From: Riley Bruins 
Date: Mon, 25 Nov 2024 20:56:18 -0800
Subject: docs: more accurate typing for LSP references context

**Problem:** The `context` parameter for `references()` is just typed as
a table, which is unhelpful.

**Solution:** Properly type it as an `lsp.ReferenceContext`!
---
 runtime/doc/lsp.txt         | 2 +-
 runtime/lua/vim/lsp/buf.lua | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 350edc068f..38e9b046cd 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1448,7 +1448,7 @@ references({context}, {opts})                       *vim.lsp.buf.references()*
     window.
 
     Parameters: ~
-      • {context}  (`table?`) Context for the request
+      • {context}  (`lsp.ReferenceContext?`) Context for the request
       • {opts}     (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|.
 
     See also: ~
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index aca6bf27f4..e4b6b9ac1e 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -736,7 +736,7 @@ end
 
 --- Lists all the references to the symbol under the cursor in the quickfix window.
 ---
----@param context (table|nil) Context for the request
+---@param context lsp.ReferenceContext? Context for the request
 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
 ---@param opts? vim.lsp.ListOpts
 function M.references(context, opts)
-- 
cgit 


From 146b8300a145efa64e579527da8606546a36162b Mon Sep 17 00:00:00 2001
From: Yi Ming 
Date: Fri, 29 Nov 2024 00:08:27 +0800
Subject: docs(lsp): update example, optional parameters #31299

---
 runtime/doc/lsp.txt            | 9 +++++----
 runtime/lua/vim/lsp/buf.lua    | 2 +-
 runtime/lua/vim/lsp/client.lua | 4 ++--
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 38e9b046cd..1693ff5e4f 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -975,7 +975,7 @@ Lua module: vim.lsp.client                                        *lsp-client*
       • {dynamic_capabilities}  (`lsp.DynamicCapabilities`)
       • {request}               (`fun(self: vim.lsp.Client, method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer?`)
                                 See |Client:request()|.
-      • {request_sync}          (`fun(self: vim.lsp.Client, method: string, params: table, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError?, result:any}?, string?`)
+      • {request_sync}          (`fun(self: vim.lsp.Client, method: string, params: table, timeout_ms: integer?, bufnr: integer?): {err: lsp.ResponseError?, result:any}?, string?`)
                                 See |Client:request_sync()|.
       • {notify}                (`fun(self: vim.lsp.Client, method: string, params: table?): boolean`)
                                 See |Client:notify()|.
@@ -1179,7 +1179,7 @@ Client:request({method}, {params}, {handler}, {bufnr})
       • {method}   (`string`) LSP method name.
       • {params}   (`table?`) LSP request params.
       • {handler}  (`lsp.Handler?`) Response |lsp-handler| for this method.
-      • {bufnr}    (`integer?`) Buffer handle. 0 for current (default).
+      • {bufnr}    (`integer?`) (default: 0) Buffer handle, or 0 for current.
 
     Return (multiple): ~
         (`boolean`) status indicates whether the request was successful. If it
@@ -1201,7 +1201,8 @@ Client:request_sync({method}, {params}, {timeout_ms}, {bufnr})
       • {params}      (`table`) LSP request params.
       • {timeout_ms}  (`integer?`) Maximum time in milliseconds to wait for a
                       result. Defaults to 1000
-      • {bufnr}       (`integer`) Buffer handle (0 for current).
+      • {bufnr}       (`integer?`) (default: 0) Buffer handle, or 0 for
+                      current.
 
     Return (multiple): ~
         (`{err: lsp.ResponseError?, result:any}?`) `result` and `err` from the
@@ -1385,7 +1386,7 @@ format({opts})                                          *vim.lsp.buf.format()*
                   predicate are included. Example: >lua
                     -- Never request typescript-language-server for formatting
                     vim.lsp.buf.format {
-                      filter = function(client) return client.name ~= "tsserver" end
+                      filter = function(client) return client.name ~= "ts_ls" end
                     }
 <
                 • {async}? (`boolean`, default: false) If true the method
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index e4b6b9ac1e..10479807a2 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -487,7 +487,7 @@ end
 --- ```lua
 --- -- Never request typescript-language-server for formatting
 --- vim.lsp.buf.format {
----   filter = function(client) return client.name ~= "tsserver" end
+---   filter = function(client) return client.name ~= "ts_ls" end
 --- }
 --- ```
 --- @field filter? fun(client: vim.lsp.Client): boolean?
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index 7c2b7192f5..a14b6ccda6 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -639,7 +639,7 @@ end
 --- @param method string LSP method name.
 --- @param params? table LSP request params.
 --- @param handler? lsp.Handler Response |lsp-handler| for this method.
---- @param bufnr? integer Buffer handle. 0 for current (default).
+--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current.
 --- @return boolean status indicates whether the request was successful.
 ---     If it is `false`, then it will always be `false` (the client has shutdown).
 --- @return integer? request_id Can be used with |Client:cancel_request()|.
@@ -718,7 +718,7 @@ end
 --- @param params table LSP request params.
 --- @param timeout_ms integer? Maximum time in milliseconds to wait for
 ---                                a result. Defaults to 1000
---- @param bufnr integer Buffer handle (0 for current).
+--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current.
 --- @return {err: lsp.ResponseError?, result:any}? `result` and `err` from the |lsp-handler|.
 ---                 `nil` is the request was unsuccessful
 --- @return string? err On timeout, cancel or error, where `err` is a
-- 
cgit 


From 731f83ea4ae7f7118cf34c43dc14005503b950fd Mon Sep 17 00:00:00 2001
From: vanaigr 
Date: Sun, 20 Oct 2024 20:13:42 -0500
Subject: test: add decor benchmarks

---
 test/benchmark/decor_spec.lua | 102 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)
 create mode 100644 test/benchmark/decor_spec.lua

diff --git a/test/benchmark/decor_spec.lua b/test/benchmark/decor_spec.lua
new file mode 100644
index 0000000000..0994023c2d
--- /dev/null
+++ b/test/benchmark/decor_spec.lua
@@ -0,0 +1,102 @@
+local n = require('test.functional.testnvim')()
+local Screen = require('test.functional.ui.screen')
+local exec_lua = n.exec_lua
+
+describe('decor perf', function()
+  before_each(n.clear)
+
+  it('can handle long lines', function()
+    local screen = Screen.new(100, 101)
+    screen:attach()
+
+    local result = exec_lua [==[
+      local ephemeral_pattern = {
+        { 0, 4, 'Comment', 11 },
+        { 0, 3, 'Keyword', 12 },
+        { 1, 2, 'Label', 12 },
+        { 0, 1, 'String', 21 },
+        { 1, 3, 'Function', 21 },
+        { 2, 10, 'Label', 8 },
+      }
+
+      local regular_pattern = {
+        { 4, 5, 'String', 12 },
+        { 1, 4, 'Function', 2 },
+      }
+
+      for _, list in ipairs({ ephemeral_pattern, regular_pattern }) do
+        for _, p in ipairs(list) do
+          p[3] = vim.api.nvim_get_hl_id_by_name(p[3])
+        end
+      end
+
+      local text = ('abcdefghijklmnopqrstuvwxyz0123'):rep(333)
+      local line_len = #text
+      vim.api.nvim_buf_set_lines(0, 0, 0, false, { text })
+
+      local ns = vim.api.nvim_create_namespace('decor_spec.lua')
+      vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
+      vim.api.nvim_win_set_cursor(0, { 1, 0 })
+
+      local ps, pe
+      local function add_pattern(pattern, ephemeral)
+        ps = vim.uv.hrtime()
+        local i = 0
+        while i < line_len - 10 do
+          for _, p in ipairs(pattern) do
+            vim.api.nvim_buf_set_extmark(0, ns, 0, i + p[1], {
+              end_row = 0,
+              end_col = i + p[2],
+              hl_group = p[3],
+              priority = p[4],
+              ephemeral = ephemeral,
+            })
+          end
+          i = i + 5
+        end
+        pe = vim.uv.hrtime()
+      end
+
+      vim.api.nvim_set_decoration_provider(ns, {
+        on_win = function()
+          return true
+        end,
+        on_line = function()
+            add_pattern(ephemeral_pattern, true)
+        end,
+      })
+
+      add_pattern(regular_pattern, false)
+
+      local total = {}
+      local provider = {}
+      for i = 1, 100 do
+        local tic = vim.uv.hrtime()
+        vim.cmd'redraw!'
+        local toc = vim.uv.hrtime()
+        table.insert(total, toc - tic)
+        table.insert(provider, pe - ps)
+      end
+
+      return { total, provider }
+    ]==]
+
+    local total, provider = unpack(result)
+    table.sort(total)
+    table.sort(provider)
+
+    local ms = 1 / 1000000
+    local function fmt(stats)
+      return string.format(
+        'min, 25%%, median, 75%%, max:\n\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms',
+        stats[1] * ms,
+        stats[1 + math.floor(#stats * 0.25)] * ms,
+        stats[1 + math.floor(#stats * 0.5)] * ms,
+        stats[1 + math.floor(#stats * 0.75)] * ms,
+        stats[#stats] * ms
+      )
+    end
+
+    print('\nTotal ' .. fmt(total) .. '\nDecoration provider: ' .. fmt(provider))
+  end)
+end)
-- 
cgit 


From 8d7d225caa12e5a25f6853a54a2fd6d144342d3c Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 29 Nov 2024 08:54:47 +0800
Subject: vim-patch:65311c6: runtime(compiler): include spotbugs Java linter

closes: vim/vim#16001

https://github.com/vim/vim/commit/65311c6f472de67b368d83441ca5e93da86161f4

Co-authored-by: Konfekt 
Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
---
 runtime/autoload/spotbugs.vim | 250 ++++++++++++++++++++++++++++++++++++++++++
 runtime/compiler/javac.vim    |   8 +-
 runtime/compiler/maven.vim    |   2 +-
 runtime/compiler/spotbugs.vim | 189 +++++++++++++++++++++++++++++++
 runtime/doc/quickfix.txt      | 112 ++++++++++++++++++-
 runtime/ftplugin/java.vim     | 138 ++++++++++++++++++++++-
 6 files changed, 686 insertions(+), 13 deletions(-)
 create mode 100644 runtime/autoload/spotbugs.vim
 create mode 100644 runtime/compiler/spotbugs.vim

diff --git a/runtime/autoload/spotbugs.vim b/runtime/autoload/spotbugs.vim
new file mode 100644
index 0000000000..9161395794
--- /dev/null
+++ b/runtime/autoload/spotbugs.vim
@@ -0,0 +1,250 @@
+" Default pre- and post-compiler actions for SpotBugs
+" Maintainers:  @konfekt and @zzzyxwvut
+" Last Change:  2024 Nov 27
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+if v:version > 900
+
+  function! spotbugs#DeleteClassFiles() abort
+    if !exists('b:spotbugs_class_files')
+      return
+    endif
+
+    for pathname in b:spotbugs_class_files
+      let classname = pathname =~# "^'.\\+\\.class'$"
+          \ ? eval(pathname)
+          \ : pathname
+
+      if classname =~# '\.class$' && filereadable(classname)
+        " Since v9.0.0795.
+        let octad = readblob(classname, 0, 8)
+
+        " Test the magic number and the major version number (45 for v1.0).
+        " Since v9.0.2027.
+        if len(octad) == 8 && octad[0 : 3] == 0zcafe.babe &&
+              \ or((octad[6] << 8), octad[7]) >= 45
+          echomsg printf('Deleting %s: %d', classname, delete(classname))
+        endif
+      endif
+    endfor
+
+    let b:spotbugs_class_files = []
+  endfunction
+
+else
+
+  function! s:DeleteClassFilesWithNewLineCodes(classname) abort
+    " The distribution of "0a"s in class file versions 2560 and 2570:
+    "
+    " 0zca.fe.ba.be.00.00.0a.00    0zca.fe.ba.be.00.00.0a.0a
+    " 0zca.fe.ba.be.00.0a.0a.00    0zca.fe.ba.be.00.0a.0a.0a
+    " 0zca.fe.ba.be.0a.00.0a.00    0zca.fe.ba.be.0a.00.0a.0a
+    " 0zca.fe.ba.be.0a.0a.0a.00    0zca.fe.ba.be.0a.0a.0a.0a
+    let numbers = [0, 0, 0, 0, 0, 0, 0, 0]
+    let offset = 0
+    let lines = readfile(a:classname, 'b', 4)
+
+    " Track NL byte counts to handle files of less than 8 bytes.
+    let nl_cnt = len(lines)
+    " Track non-NL byte counts for "0zca.fe.ba.be.0a.0a.0a.0a".
+    let non_nl_cnt = 0
+
+    for line in lines
+      for idx in range(strlen(line))
+        " Remap NLs to Nuls.
+        let numbers[offset] = (line[idx] == "\n") ? 0 : char2nr(line[idx]) % 256
+        let non_nl_cnt += 1
+        let offset += 1
+
+        if offset > 7
+          break
+        endif
+      endfor
+
+      let nl_cnt -= 1
+
+      if offset > 7 || (nl_cnt < 1 && non_nl_cnt > 4)
+        break
+      endif
+
+      " Reclaim NLs.
+      let numbers[offset] = 10
+      let offset += 1
+
+      if offset > 7
+        break
+      endif
+    endfor
+
+    " Test the magic number and the major version number (45 for v1.0).
+    if offset > 7 && numbers[0] == 0xca && numbers[1] == 0xfe &&
+          \ numbers[2] == 0xba && numbers[3] == 0xbe &&
+          \ (numbers[6] * 256 + numbers[7]) >= 45
+      echomsg printf('Deleting %s: %d', a:classname, delete(a:classname))
+    endif
+  endfunction
+
+  function! spotbugs#DeleteClassFiles() abort
+    if !exists('b:spotbugs_class_files')
+      return
+    endif
+
+    let encoding = &encoding
+
+    try
+      set encoding=latin1
+
+      for pathname in b:spotbugs_class_files
+        let classname = pathname =~# "^'.\\+\\.class'$"
+            \ ? eval(pathname)
+            \ : pathname
+
+        if classname =~# '\.class$' && filereadable(classname)
+          let line = get(readfile(classname, 'b', 1), 0, '')
+          let length = strlen(line)
+
+          " Test the magic number and the major version number (45 for v1.0).
+          if length > 3 && line[0 : 3] == "\xca\xfe\xba\xbe"
+            if length > 7 && ((line[6] == "\n" ? 0 : char2nr(line[6]) % 256) * 256 +
+                    \ (line[7] == "\n" ? 0 : char2nr(line[7]) % 256)) >= 45
+              echomsg printf('Deleting %s: %d', classname, delete(classname))
+            else
+              call s:DeleteClassFilesWithNewLineCodes(classname)
+            endif
+          endif
+        endif
+      endfor
+    finally
+      let &encoding = encoding
+    endtry
+
+    let b:spotbugs_class_files = []
+  endfunction
+
+endif
+
+function! spotbugs#DefaultPostCompilerAction() abort
+  " Since v7.4.191.
+  make %:S
+endfunction
+
+" Look for "spotbugs#compiler" in "ftplugin/java.vim".
+let s:compiler = exists('spotbugs#compiler') ? spotbugs#compiler : ''
+let s:readable = filereadable($VIMRUNTIME . '/compiler/' . s:compiler . '.vim')
+
+if s:readable && s:compiler ==# 'maven' && executable('mvn')
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler maven
+    make compile
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler maven
+    make test-compile
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {
+        \ 'PreCompilerAction':
+            \ function('spotbugs#DefaultPreCompilerAction'),
+        \ 'PreCompilerTestAction':
+            \ function('spotbugs#DefaultPreCompilerTestAction'),
+        \ 'PostCompilerAction':
+            \ function('spotbugs#DefaultPostCompilerAction'),
+        \ 'sourceDirPath':      'src/main/java',
+        \ 'classDirPath':       'target/classes',
+        \ 'testSourceDirPath':  'src/test/java',
+        \ 'testClassDirPath':   'target/test-classes',
+        \ }
+  endfunction
+
+  unlet s:readable s:compiler
+elseif s:readable && s:compiler ==# 'ant' && executable('ant')
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler ant
+    make compile
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler ant
+    make compile-test
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {
+        \ 'PreCompilerAction':
+            \ function('spotbugs#DefaultPreCompilerAction'),
+        \ 'PreCompilerTestAction':
+            \ function('spotbugs#DefaultPreCompilerTestAction'),
+        \ 'PostCompilerAction':
+            \ function('spotbugs#DefaultPostCompilerAction'),
+        \ 'sourceDirPath':      'src',
+        \ 'classDirPath':       'build/classes',
+        \ 'testSourceDirPath':  'test',
+        \ 'testClassDirPath':   'build/test/classes',
+        \ }
+  endfunction
+
+  unlet s:readable s:compiler
+elseif s:readable && s:compiler ==# 'javac' && executable('javac')
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler javac
+
+    if get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')) =~ '\s@\S'
+      " Read options and filenames from @options [@sources ...].
+      make
+    else
+      " Let Javac figure out what files to compile.
+      execute 'make ' . join(map(filter(copy(v:argv),
+          \ "v:val =~# '\\.java\\=$'"),
+          \ 'shellescape(v:val)'), ' ')
+    endif
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DefaultPreCompilerAction()
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {
+        \ 'PreCompilerAction':
+            \ function('spotbugs#DefaultPreCompilerAction'),
+        \ 'PreCompilerTestAction':
+            \ function('spotbugs#DefaultPreCompilerTestAction'),
+        \ 'PostCompilerAction':
+            \ function('spotbugs#DefaultPostCompilerAction'),
+        \ }
+  endfunction
+
+  unlet s:readable s:compiler
+else
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    echomsg printf('Not supported: "%s"', s:compiler)
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DefaultPreCompilerAction()
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {}
+  endfunction
+
+  unlet s:readable
+endif
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
+
+" vim: set foldmethod=syntax shiftwidth=2 expandtab:
diff --git a/runtime/compiler/javac.vim b/runtime/compiler/javac.vim
index 9bd4cdf270..53cd772ed8 100644
--- a/runtime/compiler/javac.vim
+++ b/runtime/compiler/javac.vim
@@ -1,7 +1,7 @@
 " Vim compiler file
 " Compiler:	Java Development Kit Compiler
 " Maintainer:	Doug Kearns 
-" Last Change:	2024 Jun 14
+" Last Change:	2024 Nov 19 (enable local javac_makeprg_params)
 
 if exists("current_compiler")
   finish
@@ -11,11 +11,7 @@ let current_compiler = "javac"
 let s:cpo_save = &cpo
 set cpo&vim
 
-if exists("g:javac_makeprg_params")
-  execute $'CompilerSet makeprg=javac\ {escape(g:javac_makeprg_params, ' \|"')}'
-else
-  CompilerSet makeprg=javac
-endif
+execute $'CompilerSet makeprg=javac\ {escape(get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')), ' \|"')}'
 
 CompilerSet errorformat=%E%f:%l:\ error:\ %m,
 		       \%W%f:%l:\ warning:\ %m,
diff --git a/runtime/compiler/maven.vim b/runtime/compiler/maven.vim
index ef8d8a6fb2..72e74e301d 100644
--- a/runtime/compiler/maven.vim
+++ b/runtime/compiler/maven.vim
@@ -14,7 +14,7 @@ if exists("current_compiler")
 endif
 let current_compiler = "maven"
 
-CompilerSet makeprg=mvn\ --batch-mode
+execute $'CompilerSet makeprg=mvn\ --batch-mode\ {escape(get(b:, 'maven_makeprg_params', get(g:, 'maven_makeprg_params', '')), ' \|"')}'
 
 " Error message for POM
 CompilerSet errorformat=[FATAL]\ Non-parseable\ POM\ %f:\ %m%\\s%\\+@%.%#line\ %l\\,\ column\ %c%.%#,
diff --git a/runtime/compiler/spotbugs.vim b/runtime/compiler/spotbugs.vim
new file mode 100644
index 0000000000..72a5084976
--- /dev/null
+++ b/runtime/compiler/spotbugs.vim
@@ -0,0 +1,189 @@
+" Vim compiler file
+" Compiler:     Spotbugs (Java static checker; needs javac compiled classes)
+" Maintainer:   @konfekt and @zzzyxwvut
+" Last Change:  2024 Nov 27
+
+if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9
+  finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Unfortunately Spotbugs does not output absolute paths, so you need to
+" pass the directory of the files being checked as `-sourcepath` parameter.
+" The regex, auxpath and glob try to include all dependent classes of the
+" current buffer. See https://github.com/spotbugs/spotbugs/issues/856
+
+" FIXME: When "search()" is used with the "e" flag, it makes no _further_
+" progress after claiming an EOL match (i.e. "\_" or "\n", but not "$").
+" XXX: Omit anonymous class declarations
+let s:keywords = '\C\<\%(\.\@1'
+" Capture ";" for counting a class file directory (see s:package_dir_heads below)
+let s:package_names = '\C\ 0
+      let name_attr = synIDattr(synID(lnum, (col('.') - 1), 0), 'name')
+      if name_attr ==# 'javaClassDecl'
+        let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:type_names)
+        if !empty(tokens) | call add(type_names, tokens[1]) | endif
+      elseif name_attr ==# 'javaExternal'
+        let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:package_names)
+        if !empty(tokens) | let s:package = tokens[1] | endif
+      endif
+      let lnum = search(s:keywords, 'eW')
+    endwhile
+    return type_names
+  endfunction
+
+else
+  function! s:GetDeclaredTypeNames() abort
+    if bufname() =~# '\<\%(module\|package\)-info\.java\=$'
+      return [expand('%:t:r')]
+    endif
+    " Undo the unsetting of &hls, see below
+    if &hls
+      defer execute('set hls')
+    endif
+    " Possibly restore the current values for registers '"' and "y", see below
+    defer call('setreg', ['"', getreg('"'), getregtype('"')])
+    defer call('setreg', ['y', getreg('y'), getregtype('y')])
+    defer execute('silent bwipeout')
+    " Copy buffer contents for modification
+    silent %y y
+    new
+    " Apply ":help scratch-buffer" effects and match "$" in Java (generated)
+    " type names (see s:type_names)
+    setlocal iskeyword+=$ buftype=nofile bufhidden=hide noswapfile nohls
+    0put y
+    " Discard text blocks and strings
+    silent keeppatterns %s/\\\@ 0
+      let line = getline(lnum)
+      if line =~# '\'
+        let tokens = matchlist(line..getline(lnum + 1), s:package_names)
+        if !empty(tokens) | let s:package = tokens[1] | endif
+      else
+        let tokens = matchlist(line..getline(lnum + 1), s:type_names)
+        if !empty(tokens) | call add(type_names, tokens[1]) | endif
+      endif
+      let lnum = search(s:keywords, 'eW')
+    endwhile
+    return type_names
+  endfunction
+endif
+
+if has('win32')
+
+  function! s:GlobClassFiles(src_type_name) abort
+    return glob(a:src_type_name..'$*.class', 1, 1)
+  endfunction
+
+else
+  function! s:GlobClassFiles(src_type_name) abort
+    return glob(a:src_type_name..'\$*.class', 1, 1)
+  endfunction
+endif
+
+if exists('g:spotbugs_properties') &&
+    \ (has_key(g:spotbugs_properties, 'sourceDirPath') &&
+    \ has_key(g:spotbugs_properties, 'classDirPath')) ||
+    \ (has_key(g:spotbugs_properties, 'testSourceDirPath') &&
+    \ has_key(g:spotbugs_properties, 'testClassDirPath'))
+
+function! s:FindClassFiles(src_type_name) abort
+  let class_files = []
+  " Match pairwise the components of source and class pathnames
+  for [src_dir, bin_dir] in filter([
+            \ [get(g:spotbugs_properties, 'sourceDirPath', ''),
+                \ get(g:spotbugs_properties, 'classDirPath', '')],
+            \ [get(g:spotbugs_properties, 'testSourceDirPath', ''),
+                \ get(g:spotbugs_properties, 'testClassDirPath', '')]],
+        \ '!(empty(v:val[0]) || empty(v:val[1]))')
+    " Since only the rightmost "src" is sought, while there can be any number of
+    " such filenames, no "fnamemodify(a:src_type_name, ':p:s?src?bin?')" is used
+    let tail_idx = strridx(a:src_type_name, src_dir)
+    " No such directory or no such inner type (i.e. without "$")
+    if tail_idx < 0 | continue | endif
+    " Substitute "bin_dir" for the rightmost "src_dir"
+    let candidate_type_name = strpart(a:src_type_name, 0, tail_idx)..
+        \ bin_dir..
+        \ strpart(a:src_type_name, (tail_idx + strlen(src_dir)))
+    for candidate in insert(s:GlobClassFiles(candidate_type_name),
+            \ candidate_type_name..'.class')
+      if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
+    endfor
+    if !empty(class_files) | break | endif
+  endfor
+  return class_files
+endfunction
+
+else
+function! s:FindClassFiles(src_type_name) abort
+  let class_files = []
+  for candidate in insert(s:GlobClassFiles(a:src_type_name),
+          \ a:src_type_name..'.class')
+    if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
+  endfor
+  return class_files
+endfunction
+endif
+
+function! s:CollectClassFiles() abort
+  " Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'"
+  let pathname = expand('%:p')
+  let tail_idx = strridx(pathname, expand('%:t'))
+  let src_pathname = strpart(pathname, 0, tail_idx)
+  let all_class_files = []
+  " Get all type names in the current buffer and let the filename globbing
+  " discover inner type names from arbitrary type names
+  for type_name in s:GetDeclaredTypeNames()
+    call extend(all_class_files, s:FindClassFiles(src_pathname..type_name))
+  endfor
+  return all_class_files
+endfunction
+
+" Expose class files for removal etc.
+let b:spotbugs_class_files = s:CollectClassFiles()
+let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g'))))
+let g:current_compiler = 'spotbugs'
+" CompilerSet makeprg=spotbugs
+let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '..
+    \ get(b:, 'spotbugs_makeprg_params', get(g:, 'spotbugs_makeprg_params', '-workHard -experimental'))..
+    \ ' -textui -emacs -auxclasspath %:p'..s:package_dir_heads..':S -sourcepath %:p'..s:package_dir_heads..':S '..
+    \ join(b:spotbugs_class_files, ' ')
+" Emacs expects doubled line numbers
+setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m
+
+" " This compiler is meant to be used for a single buffer only
+" exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"')
+" exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"')
+
+delfunction s:CollectClassFiles
+delfunction s:FindClassFiles
+delfunction s:GlobClassFiles
+delfunction s:GetDeclaredTypeNames
+let &cpo = s:cpo_save
+unlet s:package_dir_heads s:package s:package_names s:type_names s:keywords s:cpo_save
+
+" vim: set foldmethod=syntax shiftwidth=2 expandtab:
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index b3399b2766..4811a51a87 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1317,9 +1317,117 @@ g:compiler_gcc_ignore_unmatched_lines
 JAVAC							*compiler-javac*
 
 Commonly used compiler options can be added to 'makeprg' by setting the
-g:javac_makeprg_params variable.  For example: >
+b/g:javac_makeprg_params variable.  For example: >
+
 	let g:javac_makeprg_params = "-Xlint:all -encoding utf-8"
-<
+
+MAVEN							*compiler-maven*
+
+Commonly used compiler options can be added to 'makeprg' by setting the
+b/g:maven_makeprg_params variable.  For example: >
+
+	let g:maven_makeprg_params = "-DskipTests -U -X"
+
+SPOTBUGS						*compiler-spotbugs*
+
+SpotBugs is a static analysis tool that can be used to find bugs in Java.
+It scans the Java bytecode of all classes in the currently open buffer.
+(Therefore, `:compiler! spotbugs` is not supported.)
+
+Commonly used compiler options can be added to 'makeprg' by setting the
+"b:" or "g:spotbugs_makeprg_params" variable.  For example: >
+
+	let b:spotbugs_makeprg_params = "-longBugCodes -effort:max -low"
+
+The global default is "-workHard -experimental".
+
+By default, the class files are searched in the directory where the source
+files are placed.  However, typical Java projects use distinct directories
+for source files and class files.  To make both known to SpotBugs, assign
+their paths (distinct and relative to their common root directory) to the
+following properties (using the example of a common Maven project): >
+
+	let g:spotbugs_properties = {
+		\ 'sourceDirPath':	'src/main/java',
+		\ 'classDirPath':	'target/classes',
+		\ 'testSourceDirPath':	'src/test/java',
+		\ 'testClassDirPath':	'target/test-classes',
+	\ }
+
+Note that values for the path keys describe only for SpotBugs where to look
+for files; refer to the documentation for particular compiler plugins for more
+information.
+
+The default pre- and post-compiler actions are provided for Ant, Maven, and
+Javac compiler plugins and can be selected by assigning the name of a compiler
+plugin to the "compiler" key: >
+
+	let g:spotbugs_properties = {
+		\ 'compiler':		'maven',
+	\ }
+
+This single setting is essentially equivalent to all the settings below, with
+the exception made for the "PreCompilerAction" and "PreCompilerTestAction"
+values: their listed |Funcref|s will obtain no-op implementations whereas the
+implicit Funcrefs of the "compiler" key will obtain the requested defaults if
+available. >
+
+	let g:spotbugs_properties = {
+		\ 'PreCompilerAction':
+			\ function('spotbugs#DefaultPreCompilerAction'),
+		\ 'PreCompilerTestAction':
+			\ function('spotbugs#DefaultPreCompilerTestAction'),
+		\ 'PostCompilerAction':
+			\ function('spotbugs#DefaultPostCompilerAction'),
+		\ 'sourceDirPath':	'src/main/java',
+		\ 'classDirPath':	'target/classes',
+		\ 'testSourceDirPath':	'src/test/java',
+		\ 'testClassDirPath':	'target/test-classes',
+	\ }
+
+With default actions, the compiler of choice will attempt to rebuild the class
+files for the buffer (and possibly for the whole project) as soon as a Java
+syntax file is loaded; then, `spotbugs` will attempt to analyze the quality of
+the compilation unit of the buffer.
+
+When default actions are not suited to a desired workflow, consider writing
+arbitrary functions yourself and matching their |Funcref|s to the supported
+keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction".
+
+The next example re-implements the default pre-compiler actions for a Maven
+project and requests other default Maven settings with the "compiler" entry: >
+
+	function! MavenPreCompilerAction() abort
+		call spotbugs#DeleteClassFiles()
+		compiler maven
+		make compile
+	endfunction
+
+	function! MavenPreCompilerTestAction() abort
+		call spotbugs#DeleteClassFiles()
+		compiler maven
+		make test-compile
+	endfunction
+
+	let g:spotbugs_properties = {
+		\ 'compiler':		'maven',
+		\ 'PreCompilerAction':
+			\ function('MavenPreCompilerAction'),
+		\ 'PreCompilerTestAction':
+			\ function('MavenPreCompilerTestAction'),
+	\ }
+
+Note that all entered custom settings will take precedence over the matching
+default settings in "g:spotbugs_properties".
+
+The "g:spotbugs_properties" variable is consulted by the Java filetype plugin
+(|ft-java-plugin|) to arrange for the described automation, and, therefore, it
+must be defined before |FileType| events can take place for the buffers loaded
+with Java source files.  It could, for example, be set in a project-local
+|vimrc| loaded by [0].
+
+[0] https://github.com/MarcWeber/vim-addon-local-vimrc/
+
 GNU MAKE						*compiler-make*
 
 Since the default make program is "make", the compiler plugin for make,
diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim
index 55b358374f..6e12fe2fe5 100644
--- a/runtime/ftplugin/java.vim
+++ b/runtime/ftplugin/java.vim
@@ -3,7 +3,7 @@
 " Maintainer:		Aliaksei Budavei <0x000c70 AT gmail DOT com>
 " Former Maintainer:	Dan Sharp
 " Repository:		https://github.com/zzzyxwvut/java-vim.git
-" Last Change:		2024 Sep 26
+" Last Change:		2024 Nov 24
 "			2024 Jan 14 by Vim Project (browsefilter)
 "			2024 May 23 by Riley Bruins  ('commentstring')
 
@@ -90,10 +90,127 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
     endif
 endif
 
+" The support for pre- and post-compiler actions for SpotBugs.
+if exists("g:spotbugs_properties") && has_key(g:spotbugs_properties, 'compiler')
+    try
+	let spotbugs#compiler = g:spotbugs_properties.compiler
+	let g:spotbugs_properties = extend(
+		\ spotbugs#DefaultProperties(),
+		\ g:spotbugs_properties,
+		\ 'force')
+    catch
+	echomsg v:errmsg
+    finally
+	call remove(g:spotbugs_properties, 'compiler')
+    endtry
+endif
+
+if exists("g:spotbugs_properties") &&
+	    \ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim')
+    let s:request = 0
+
+    if has_key(g:spotbugs_properties, 'PreCompilerAction')
+	let s:dispatcher = 'call g:spotbugs_properties.PreCompilerAction() | '
+	let s:request += 1
+    endif
+
+    if has_key(g:spotbugs_properties, 'PreCompilerTestAction')
+	let s:dispatcher = 'call g:spotbugs_properties.PreCompilerTestAction() | '
+	let s:request += 2
+    endif
+
+    if has_key(g:spotbugs_properties, 'PostCompilerAction')
+	let s:request += 4
+    endif
+
+    if (s:request == 3 || s:request == 7) &&
+	    \ has_key(g:spotbugs_properties, 'sourceDirPath') &&
+	    \ has_key(g:spotbugs_properties, 'testSourceDirPath')
+	function! s:DispatchAction(path_action_pairs) abort
+	    let name = expand('%:p')
+
+	    for [path, Action] in a:path_action_pairs
+		if name =~# (path . '.\{-}\.java\=$')
+		    call Action()
+		    break
+		endif
+	    endfor
+	endfunction
+
+	let s:dispatcher = printf('call s:DispatchAction(%s) | ',
+		\ string([[g:spotbugs_properties.sourceDirPath,
+			    \ g:spotbugs_properties.PreCompilerAction],
+			\ [g:spotbugs_properties.testSourceDirPath,
+			    \ g:spotbugs_properties.PreCompilerTestAction]]))
+    endif
+
+    if s:request
+	if exists("b:spotbugs_syntax_once")
+	    let s:actions = [{'event': 'BufWritePost'}]
+	else
+	    " XXX: Handle multiple FileType events when vimrc contains more
+	    " than one filetype setting for the language, e.g.:
+	    "	:filetype plugin indent on
+	    "	:autocmd BufRead,BufNewFile *.java setlocal filetype=java ...
+	    " XXX: DO NOT ADD b:spotbugs_syntax_once TO b:undo_ftplugin !
+	    let b:spotbugs_syntax_once = 1
+	    let s:actions = [{
+		    \ 'event': 'Syntax',
+		    \ 'once': 1,
+		    \ }, {
+		    \ 'event': 'BufWritePost',
+		    \ }]
+	endif
+
+	for s:idx in range(len(s:actions))
+	    if s:request == 7 || s:request == 6 || s:request == 5
+		let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs | ' .
+			\ 'call g:spotbugs_properties.PostCompilerAction()'
+	    elseif s:request == 4
+		let s:actions[s:idx].cmd = 'compiler spotbugs | ' .
+			\ 'call g:spotbugs_properties.PostCompilerAction()'
+	    elseif s:request == 3 || s:request == 2 || s:request == 1
+		let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs'
+	    else
+		let s:actions[s:idx].cmd = ''
+	    endif
+	endfor
+
+	if !exists("#java_spotbugs")
+	    augroup java_spotbugs
+	    augroup END
+	endif
+
+	" The events are defined in s:actions.
+	silent! autocmd! java_spotbugs BufWritePost 
+	silent! autocmd! java_spotbugs Syntax 
+
+	for s:action in s:actions
+	    execute printf('autocmd java_spotbugs %s  %s',
+		    \ s:action.event,
+		    \ s:action.cmd . (has_key(s:action, 'once')
+			    \ ? printf(' | autocmd! java_spotbugs %s ',
+				    \ s:action.event)
+			    \ : ''))
+	endfor
+
+	unlet! s:action s:actions s:idx s:dispatcher
+    endif
+
+    unlet s:request
+endif
+
+function! JavaFileTypeCleanUp() abort
+    setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr<
+    unlet! b:browsefilter
+
+    " The concatenated removals may be misparsed as a BufWritePost autocmd.
+    silent! autocmd! java_spotbugs BufWritePost 
+    silent! autocmd! java_spotbugs Syntax 
+endfunction
+
 " Undo the stuff we changed.
-let b:undo_ftplugin = "setlocal suffixes< suffixesadd<" .
-		\     " formatoptions< comments< commentstring< path< includeexpr<" .
-		\     " | unlet! b:browsefilter"
+let b:undo_ftplugin = 'call JavaFileTypeCleanUp() | delfunction JavaFileTypeCleanUp'
 
 " See ":help vim9-mix".
 if !has("vim9script")
@@ -114,6 +231,19 @@ if exists("s:zip_func_upgradable")
     setlocal suffixesadd<
 endif
 
+if exists("*s:DispatchAction")
+    def! s:DispatchAction(path_action_pairs: list>)
+	const name: string = expand('%:p')
+
+	for [path: string, Action: func: any] in path_action_pairs
+	    if name =~# (path .. '.\{-}\.java\=$')
+		Action()
+		break
+	    endif
+	endfor
+    enddef
+endif
+
 " Restore the saved compatibility options.
 let &cpo = s:save_cpo
 unlet s:save_cpo
-- 
cgit 


From cfa8418c21133af5562ab56f0b2e9d48ce8b2096 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 29 Nov 2024 08:58:08 +0800
Subject: vim-patch:9.1.0894: No test for what the spotbug compiler parses

Problem:  No test for what the spotbug compiler parses
          (after commit: 65311c6f472de67b368)
Solution: Test &makeprg for the SpotBugs compiler plugin
          (Aliaksei Budavei)

closes: vim/vim#16096

https://github.com/vim/vim/commit/60ddb1a14025dc061908a1b7a0a3a82b4bbed223

Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
---
 test/old/testdir/test_compiler.vim | 194 +++++++++++++++++++++++++++++++++++++
 1 file changed, 194 insertions(+)

diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim
index 07b57b76d9..6b195b5efa 100644
--- a/test/old/testdir/test_compiler.vim
+++ b/test/old/testdir/test_compiler.vim
@@ -78,3 +78,197 @@ func Test_compiler_error()
   call assert_fails('compiler! doesnotexist', 'E666:')
   unlet! g:current_compiler
 endfunc
+
+func s:SpotBugsParseFilterMakePrg(dirname, makeprg)
+  let result = {}
+  let result.sourcepath = ''
+  let result.classfiles = []
+
+  " Get the argument after the rightmost occurrence of "-sourcepath".
+  let offset = strridx(a:makeprg, '-sourcepath')
+  if offset < 0
+    return result
+  endif
+  let offset += 1 + strlen('-sourcepath')
+  let result.sourcepath = matchstr(strpart(a:makeprg, offset), '.\{-}\ze[ \t]')
+
+  " Get the class file arguments, dropping the pathname prefix.
+  let offset = stridx(a:makeprg, a:dirname, offset)
+  if offset < 0
+    return result
+  endif
+
+  while offset > -1
+    let candidate = matchstr(a:makeprg, '[^ \t]\{-}\.class\>', offset)
+    if empty(candidate)
+      break
+    endif
+    call add(result.classfiles, candidate)
+    let offset = stridx(a:makeprg, a:dirname, (1 + strlen(candidate) + offset))
+  endwhile
+
+  call sort(result.classfiles)
+  return result
+endfunc
+
+func Test_compiler_spotbugs_makeprg()
+  let save_shellslash = &shellslash
+  set shellslash
+
+  call assert_true(mkdir('Xspotbugs/src/tests/α/β/γ/δ', 'pR'))
+  call assert_true(mkdir('Xspotbugs/tests/α/β/γ/δ', 'pR'))
+
+  let lines =<< trim END
+      // EOL comment. /*
+      abstract class
+      𐌂1 /* Multiline comment. */ {
+          /* Multiline comment. */ // EOL comment. /*
+          static final String COMMENT_A_LIKE = "/*";
+          { new Object() {/* Try globbing. */}; }
+          static { interface 𐌉𐌉1 {} }
+          static class 𐌂11 { interface 𐌉𐌉2 {} }
+      }
+      /* Multiline comment. */ // EOL comment. /*
+      final class 𐌂2 {
+          public static void main(String... aa) {
+              record 𐌓() {}
+              enum 𐌄 {}
+          }
+      } // class
+  END
+
+  " THE EXPECTED RESULTS.
+  let results = {}
+  let results['Xspotbugs/src/tests/𐌂1.java'] = {
+      \ 'sourcepath': '%:p:h:S',
+      \ 'classfiles': sort([
+          \ 'Xspotbugs/tests/𐌂1$1.class',
+          \ 'Xspotbugs/tests/𐌂1$1𐌉𐌉1.class',
+          \ 'Xspotbugs/tests/𐌂1$𐌂11$𐌉𐌉2.class',
+          \ 'Xspotbugs/tests/𐌂1$𐌂11.class',
+          \ 'Xspotbugs/tests/𐌂1.class',
+          \ 'Xspotbugs/tests/𐌂2$1𐌄.class',
+          \ 'Xspotbugs/tests/𐌂2$1𐌓.class',
+          \ 'Xspotbugs/tests/𐌂2.class']),
+      \ }
+  " No class file for an empty source file even with "-Xpkginfo:always".
+  let results['Xspotbugs/src/tests/package-info.java'] = {
+      \ 'sourcepath': '',
+      \ 'classfiles': [],
+      \ }
+  let results['Xspotbugs/src/tests/α/𐌂1.java'] = {
+      \ 'sourcepath': '%:p:h:h:S',
+      \ 'classfiles': sort([
+          \ 'Xspotbugs/tests/α/𐌂1$1.class',
+          \ 'Xspotbugs/tests/α/𐌂1$1𐌉𐌉1.class',
+          \ 'Xspotbugs/tests/α/𐌂1$𐌂11$𐌉𐌉2.class',
+          \ 'Xspotbugs/tests/α/𐌂1$𐌂11.class',
+          \ 'Xspotbugs/tests/α/𐌂1.class',
+          \ 'Xspotbugs/tests/α/𐌂2$1𐌄.class',
+          \ 'Xspotbugs/tests/α/𐌂2$1𐌓.class',
+          \ 'Xspotbugs/tests/α/𐌂2.class']),
+      \ }
+  let results['Xspotbugs/src/tests/α/package-info.java'] = {
+      \ 'sourcepath': '%:p:h:S',
+      \ 'classfiles': ['Xspotbugs/tests/α/package-info.class'],
+      \ }
+  let results['Xspotbugs/src/tests/α/β/𐌂1.java'] = {
+      \ 'sourcepath': '%:p:h:h:h:S',
+      \ 'classfiles': sort([
+          \ 'Xspotbugs/tests/α/β/𐌂1$1.class',
+          \ 'Xspotbugs/tests/α/β/𐌂1$1𐌉𐌉1.class',
+          \ 'Xspotbugs/tests/α/β/𐌂1$𐌂11$𐌉𐌉2.class',
+          \ 'Xspotbugs/tests/α/β/𐌂1$𐌂11.class',
+          \ 'Xspotbugs/tests/α/β/𐌂1.class',
+          \ 'Xspotbugs/tests/α/β/𐌂2$1𐌄.class',
+          \ 'Xspotbugs/tests/α/β/𐌂2$1𐌓.class',
+          \ 'Xspotbugs/tests/α/β/𐌂2.class']),
+      \ }
+  let results['Xspotbugs/src/tests/α/β/package-info.java'] = {
+      \ 'sourcepath': '%:p:h:S',
+      \ 'classfiles': ['Xspotbugs/tests/α/β/package-info.class'],
+      \ }
+  let results['Xspotbugs/src/tests/α/β/γ/𐌂1.java'] = {
+      \ 'sourcepath': '%:p:h:h:h:h:S',
+      \ 'classfiles': sort([
+          \ 'Xspotbugs/tests/α/β/γ/𐌂1$1.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂1$1𐌉𐌉1.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂1$𐌂11$𐌉𐌉2.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂1$𐌂11.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂1.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂2$1𐌄.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂2$1𐌓.class',
+          \ 'Xspotbugs/tests/α/β/γ/𐌂2.class']),
+      \ }
+  let results['Xspotbugs/src/tests/α/β/γ/package-info.java'] = {
+      \ 'sourcepath': '%:p:h:S',
+      \ 'classfiles': ['Xspotbugs/tests/α/β/γ/package-info.class'],
+      \ }
+  let results['Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java'] = {
+      \ 'sourcepath': '%:p:h:h:h:h:h:S',
+      \ 'classfiles': sort([
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1𐌉𐌉1.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$𐌂11$𐌉𐌉2.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$𐌂11.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2$1𐌄.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2$1𐌓.class',
+          \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2.class']),
+      \ }
+  let results['Xspotbugs/src/tests/α/β/γ/δ/package-info.java'] = {
+      \ 'sourcepath': '%:p:h:S',
+      \ 'classfiles': ['Xspotbugs/tests/α/β/γ/δ/package-info.class'],
+      \ }
+
+  " MAKE CLASS FILES DISCOVERABLE!
+  let g:spotbugs_properties = {
+    \ 'sourceDirPath': 'src/tests',
+    \ 'classDirPath': 'tests',
+  \ }
+
+  call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'sourcepath'))
+  call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'classfiles'))
+
+  " Write 45 mock-up class files for 10 source files.
+  for [class_dir, src_dir, package] in [
+        \ ['Xspotbugs/tests/', 'Xspotbugs/src/tests/', ''],
+        \ ['Xspotbugs/tests/α/', 'Xspotbugs/src/tests/α/', 'package α;'],
+        \ ['Xspotbugs/tests/α/β/', 'Xspotbugs/src/tests/α/β/', 'package α.β;'],
+        \ ['Xspotbugs/tests/α/β/γ/', 'Xspotbugs/src/tests/α/β/γ/', 'package α.β.γ;'],
+        \ ['Xspotbugs/tests/α/β/γ/δ/', 'Xspotbugs/src/tests/α/β/γ/δ/', 'package α.β.γ.δ;']]
+    for class_file in ['𐌂1$1.class', '𐌂1$1𐌉𐌉1.class', '𐌂1$𐌂11$𐌉𐌉2.class',
+          \ '𐌂1$𐌂11.class', '𐌂1.class', '𐌂2$1𐌄.class', '𐌂2$1𐌓.class', '𐌂2.class']
+      call writefile(0zcafe.babe.0000.0041, class_dir .. class_file)
+    endfor
+    call writefile(0zcafe.babe.0000.0041, class_dir .. 'package-info.class')
+
+    " Write Java source files.
+    let type_file = src_dir .. '𐌂1.java'
+    call writefile(insert(copy(lines), package), type_file)
+    let package_file = src_dir .. 'package-info.java'
+    call writefile([package], src_dir .. 'package-info.java')
+
+    for s in ['on', 'off']
+      execute 'syntax ' .. s
+
+      execute 'edit ' .. type_file
+      compiler spotbugs
+      let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg)
+      call assert_equal(results[type_file].sourcepath, result.sourcepath)
+      call assert_equal(results[type_file].classfiles, result.classfiles)
+      bwipeout
+
+      execute 'edit ' .. package_file
+      compiler spotbugs
+      let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg)
+      call assert_equal(results[package_file].sourcepath, result.sourcepath)
+      call assert_equal(results[package_file].classfiles, result.classfiles)
+      bwipeout
+    endfor
+  endfor
+
+  let &shellslash = save_shellslash
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From 1536f79d86b9edac1100e58ad5fbc421d14bfaa1 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 29 Nov 2024 10:06:12 +0800
Subject: vim-patch:8.2.2993: 'fileencodings' default value should depend on
 'encoding' (#31379)

Problem:    'fileencodings' default value should depend on 'encoding'. (Gary
            Johnson)
Solution:   When 'encoding' is "utf-8" use a different default value for
            'fileencodings'.

https://github.com/vim/vim/commit/5ffefbb35aba2448099314a9e09714d2f3b2b1bd

Co-authored-by: Bram Moolenaar 
---
 test/old/testdir/test_options.vim | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
index b6bdb1be52..caafd9d820 100644
--- a/test/old/testdir/test_options.vim
+++ b/test/old/testdir/test_options.vim
@@ -2252,6 +2252,14 @@ func Test_opt_default()
   call assert_equal('vt', &formatoptions)
   set formatoptions&vim
   call assert_equal('tcq', &formatoptions)
+
+  call assert_equal('ucs-bom,utf-8,default,latin1', &fencs)
+  set fencs=latin1
+  set fencs&
+  call assert_equal('ucs-bom,utf-8,default,latin1', &fencs)
+  set fencs=latin1
+  set all&
+  call assert_equal('ucs-bom,utf-8,default,latin1', &fencs)
 endfunc
 
 " Test for the 'cmdheight' option
-- 
cgit 


From b1c907f21916dd3b16baeae9f68b1c3bcb3dcfd3 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 29 Nov 2024 10:12:30 +0800
Subject: vim-patch:9.1.0892: the max value of 'cmdheight' is limited by other
 tabpages (#31378)

Problem:  the max value of 'cmdheight' is limited by other tabpages
Solution: Limit the maximum value of 'cmdheight' to the current tabpage only.
          (Milly)

The Help says that cmdheight is local to the tab page, but says nothing
about the maximum value depending on the state of all tab pages. Users
may wonder why they can't increase cmdheight when there are still rows
available on the current tab page. This PR changes the behavior of
cmdheight so that its maximum value depends only on the state of the
current tab page.

Also, since magic numbers were embedded in various places with the
minimum value of cmdheight being 1, we defined a constant to make it
easier to understand.

closes: vim/vim#16131

https://github.com/vim/vim/commit/2cddf0e85a7f8304476397e1c51dcd0e41835ac3

Cherry-pick Test_cmdheight_not_changed() from patch 9.0.0187.

Co-authored-by: Milly 
---
 src/nvim/drawscreen.c                |  2 +-
 src/nvim/option.c                    |  7 ++++---
 src/nvim/window.c                    | 18 +++++++++++++++++-
 test/old/testdir/test_options.vim    | 35 ++++++++++++++++++++++++++++++++++-
 test/old/testdir/test_window_cmd.vim | 21 +++++++++++++++++++++
 5 files changed, 77 insertions(+), 6 deletions(-)

diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index e645d1bbea..835fdcf7d0 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -396,7 +396,7 @@ void check_screensize(void)
 {
   // Limit Rows and Columns to avoid an overflow in Rows * Columns.
   // need room for one window and command line
-  Rows = MIN(MAX(Rows, min_rows()), 1000);
+  Rows = MIN(MAX(Rows, min_rows_for_all_tabpages()), 1000);
   Columns = MIN(MAX(Columns, MIN_COLUMNS), 10000);
 }
 
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 27b80c0ac8..6da9635479 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2764,10 +2764,11 @@ static const char *check_num_option_bounds(OptIndex opt_idx, OptInt *newval, cha
 
   switch (opt_idx) {
   case kOptLines:
-    if (*newval < min_rows() && full_screen) {
-      vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows());
+    if (*newval < min_rows_for_all_tabpages() && full_screen) {
+      vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"),
+                   min_rows_for_all_tabpages());
       errmsg = errbuf;
-      *newval = min_rows();
+      *newval = min_rows_for_all_tabpages();
     }
     // True max size is defined by check_screensize().
     *newval = MIN(*newval, INT_MAX);
diff --git a/src/nvim/window.c b/src/nvim/window.c
index ac4c5a8e4a..d92b2ab601 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -6189,7 +6189,7 @@ const char *did_set_winminheight(optset_T *args FUNC_ATTR_UNUSED)
   // loop until there is a 'winminheight' that is possible
   while (p_wmh > 0) {
     const int room = Rows - (int)p_ch;
-    const int needed = min_rows();
+    const int needed = min_rows_for_all_tabpages();
     if (room >= needed) {
       break;
     }
@@ -7072,6 +7072,22 @@ int min_rows(void)
     return MIN_LINES;
   }
 
+  int total = frame_minheight(curtab->tp_topframe, NULL);
+  total += tabline_height() + global_stl_height();
+  if (p_ch > 0) {
+    total += 1;           // count the room for the command line
+  }
+  return total;
+}
+
+/// Return the minimal number of rows that is needed on the screen to display
+/// the current number of windows for all tab pages.
+int min_rows_for_all_tabpages(void)
+{
+  if (firstwin == NULL) {       // not initialized yet
+    return MIN_LINES;
+  }
+
   int total = 0;
   FOR_ALL_TABS(tp) {
     int n = frame_minheight(tp->tp_topframe, NULL);
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
index caafd9d820..c948846819 100644
--- a/test/old/testdir/test_options.vim
+++ b/test/old/testdir/test_options.vim
@@ -2263,13 +2263,46 @@ func Test_opt_default()
 endfunc
 
 " Test for the 'cmdheight' option
-func Test_cmdheight()
+func Test_opt_cmdheight()
   %bw!
   let ht = &lines
   set cmdheight=9999
   call assert_equal(1, winheight(0))
   call assert_equal(ht - 1, &cmdheight)
   set cmdheight&
+
+  " The status line should be taken into account.
+  set laststatus=2
+  set cmdheight=9999
+  call assert_equal(ht - 2, &cmdheight)
+  set cmdheight& laststatus=1  " Accommodate Nvim default
+
+  " The tabline should be taken into account only non-GUI.
+  set showtabline=2
+  set cmdheight=9999
+  if has('gui_running')
+    call assert_equal(ht - 1, &cmdheight)
+  else
+    call assert_equal(ht - 2, &cmdheight)
+  endif
+  set cmdheight& showtabline&
+
+  " The 'winminheight' should be taken into account.
+  set winheight=3 winminheight=3
+  split
+  set cmdheight=9999
+  call assert_equal(ht - 8, &cmdheight)
+  %bw!
+  set cmdheight& winminheight& winheight&
+
+  " Only the windows in the current tabpage are taken into account.
+  set winheight=3 winminheight=3 showtabline=0
+  split
+  tabnew
+  set cmdheight=9999
+  call assert_equal(ht - 3, &cmdheight)
+  %bw!
+  set cmdheight& winminheight& winheight& showtabline&
 endfunc
 
 " To specify a control character as an option value, '^' can be used
diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim
index 8048fa6ff8..e173aa1e73 100644
--- a/test/old/testdir/test_window_cmd.vim
+++ b/test/old/testdir/test_window_cmd.vim
@@ -55,6 +55,27 @@ func Test_window_cmd_cmdwin_with_vsp()
   set ls&vim
 endfunc
 
+func Test_cmdheight_not_changed()
+  throw 'Skipped: N/A'
+  set cmdheight=2
+  set winminheight=0
+  augroup Maximize
+    autocmd WinEnter * wincmd _
+  augroup END
+  split
+  tabnew
+  tabfirst
+  call assert_equal(2, &cmdheight)
+
+  tabonly!
+  only
+  set winminheight& cmdheight&
+  augroup Maximize
+    au!
+  augroup END
+  augroup! Maximize
+endfunc
+
 " Test for jumping to windows
 func Test_window_jump()
   new
-- 
cgit 


From e74316bf48280e1a5b9bfb8dd29beb9d6e6ee743 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Thu, 28 Nov 2024 19:00:21 +0100
Subject: vim-patch:511eb84: runtime(po): remove poDiffOld/New, add po-format
 flags to syntax file

fixes: vim/vim#16120
closes: vim/vim#16132

https://github.com/vim/vim/commit/511eb84c08ea28a0a1363a4e780ee4311818f459

Co-authored-by: Eisuke Kawashima 
---
 runtime/syntax/po.vim | 56 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 43 insertions(+), 13 deletions(-)

diff --git a/runtime/syntax/po.vim b/runtime/syntax/po.vim
index 08d6baec27..6da27f639d 100644
--- a/runtime/syntax/po.vim
+++ b/runtime/syntax/po.vim
@@ -1,10 +1,11 @@
 " Vim syntax file
 " Language:	po (gettext)
 " Maintainer:	Dwayne Bailey 
-" Last Change:	2015 Jun 07
+" Last Change:	2024 Nov 28
 " Contributors: Dwayne Bailey (Most advanced syntax highlighting)
 "               Leonardo Fontenelle (Spell checking)
 "               Nam SungHyun  (Original maintainer)
+"               Eisuke Kawashima (add format-flags: #16132)
 
 " quit when a syntax file was already loaded
 if exists("b:current_syntax")
@@ -32,9 +33,9 @@ syn region     poMsgCTxt	matchgroup=poStatementMsgCTxt start=+^msgctxt "+rs=e-1
 syn region     poMsgID	matchgroup=poStatementMsgid start=+^msgid "+rs=e-1 matchgroup=poStringID end=+^msgstr\(\|\[[\]0\[]\]\) "+me=s-1 contains=poStringID,poStatementMsgidplural,poStatementMsgid
 syn region     poMsgSTR	matchgroup=poStatementMsgstr start=+^msgstr\(\|\[[\]0\[]\]\) "+rs=e-1 matchgroup=poStringSTR end=+\n\n+me=s-1 contains=poStringSTR,poStatementMsgstr
 syn region poStringCTxt	start=+"+ skip=+\\\\\|\\"+ end=+"+
-syn region poStringID	start=+"+ skip=+\\\\\|\\"+ end=+"+ contained 
+syn region poStringID	start=+"+ skip=+\\\\\|\\"+ end=+"+ contained
                             \ contains=poSpecial,poFormat,poCommentKDE,poPluralKDE,poKDEdesktopFile,poHtml,poAcceleratorId,poHtmlNot,poVariable
-syn region poStringSTR	start=+"+ skip=+\\\\\|\\"+ end=+"+ contained 
+syn region poStringSTR	start=+"+ skip=+\\\\\|\\"+ end=+"+ contained
                             \ contains=@Spell,poSpecial,poFormat,poHeaderItem,poCommentKDEError,poHeaderUndefined,poPluralKDEError,poMsguniqError,poKDEdesktopFile,poHtml,poAcceleratorStr,poHtmlNot,poVariable
 
 " Header and Copyright
@@ -45,13 +46,43 @@ syn match     poCopyrightUnset "SOME DESCRIPTIVE TITLE\|FIRST AUTHOR / contained
+syn match     poFlagFormat /\<\%(no-\)\?boost-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?c++-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?c-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?csharp-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?elisp-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?gcc-internal-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?gfc-internal-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?java-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?java-printf-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?javascript-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?kde-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?librep-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?lisp-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?lua-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?objc-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?object-pascal-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?perl-brace-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?perl-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?php-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?python-brace-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?python-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?qt-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?qt-plural-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?ruby-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?scheme-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?sh-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?smalltalk-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?tcl-format\>/ contained
+syn match     poFlagFormat /\<\%(no-\)\?ycp-format\>/ contained
+
 syn match     poCommentTranslator "^# .*$" contains=poCopyrightUnset
-syn match     poCommentAutomatic "^#\..*$" 
+syn match     poCommentAutomatic "^#\..*$"
 syn match     poCommentSources	"^#:.*$"
-syn match     poCommentFlags "^#,.*$" contains=poFlagFuzzy
-syn match     poDiffOld '\(^#| "[^{]*+}\|{+[^}]*+}\|{+[^}]*\|"$\)' contained
-syn match     poDiffNew '\(^#| "[^{]*-}\|{-[^}]*-}\|{-[^}]*\|"$\)' contained
-syn match     poCommentDiff "^#|.*$" contains=poDiffOld,poDiffNew
+syn match     poCommentFlags "^#,.*$" contains=poFlagFuzzy,poFlagFormat
+syn match     poCommentPrevious "^#|.*$"
 
 " Translations (also includes header fields as they appear in a translation msgstr)
 syn region poCommentKDE	  start=+"_: +ms=s+1 end="\\n" end="\"\n^msgstr"me=s-1 contained
@@ -66,13 +97,13 @@ syn match  poFormat	"%%" contained
 syn region poMsguniqError matchgroup=poMsguniqErrorMarkers  start="#-#-#-#-#"  end='#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)\\n' contained
 
 " Obsolete messages
-syn match poObsolete "^#\~.*$" 
+syn match poObsolete "^#\~.*$"
 
 " KDE Name= handling
 syn match poKDEdesktopFile "\"\(Name\|Comment\|GenericName\|Description\|Keywords\|About\)="ms=s+1,me=e-1
 
 " Accelerator keys - this messes up if the preceding or following char is a multibyte unicode char
-syn match poAcceleratorId  contained "[^&_~][&_~]\(\a\|\d\)[^:]"ms=s+1,me=e-1 
+syn match poAcceleratorId  contained "[^&_~][&_~]\(\a\|\d\)[^:]"ms=s+1,me=e-1
 syn match poAcceleratorStr  contained "[^&_~][&_~]\(\a\|\d\)[^:]"ms=s+1,me=e-1 contains=@Spell
 
 " Variables simple
@@ -86,11 +117,10 @@ hi def link poComment	     Comment
 hi def link poCommentAutomatic  Comment
 hi def link poCommentTranslator Comment
 hi def link poCommentFlags      Special
-hi def link poCommentDiff       Comment
+hi def link poCommentPrevious   Comment
 hi def link poCopyrightUnset    Todo
 hi def link poFlagFuzzy         Todo
-hi def link poDiffOld           Todo
-hi def link poDiffNew          Special
+hi def link poFlagFormat        Todo
 hi def link poObsolete         Comment
 
 hi def link poStatementMsgid   Statement
-- 
cgit 


From c867a4f5e335a1e62e699c01056f9553c0ce151a Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 29 Nov 2024 08:26:46 +0100
Subject: build(deps): bump luajit to HEAD - 19878ec05

---
 cmake.deps/deps.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index c42790ed5d..f7827fc47a 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -1,8 +1,8 @@
 LIBUV_URL https://github.com/libuv/libuv/archive/v1.49.2.tar.gz
 LIBUV_SHA256 388ffcf3370d4cf7c4b3a3205504eea06c4be5f9e80d2ab32d19f8235accc1cf
 
-LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/fe71d0fb54ceadfb5b5f3b6baf29e486d97f6059.tar.gz
-LUAJIT_SHA256 92325f209b21aaf0a67b099bc73cf9bbac5789a9749bdc3898d4a990abb4f36e
+LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/19878ec05c239ccaf5f3d17af27670a963e25b8b.tar.gz
+LUAJIT_SHA256 e91acbe181cf6ffa3ef15870b8e620131002240ba24c5c779fd0131db021517f
 
 LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz
 LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333
-- 
cgit 


From a1e313ded6e4c46c58012639e5c0c6d0b009d52a Mon Sep 17 00:00:00 2001
From: Yi Ming 
Date: Fri, 29 Nov 2024 20:40:32 +0800
Subject: feat(lsp): support `textDocument/foldingRange` (#31311)

* refactor(shared): extract `vim._list_insert` and `vim._list_remove`

* feat(lsp): add `vim.lsp.foldexpr()`

* docs(lsp): add a todo for state management

* feat(lsp): add `vim.lsp.folding_range.foldclose()`

* feat(lsp): schedule `foldclose()` if the buffer is not up-to-date

* feat(lsp): add `vim.lsp.foldtext()`

* feat(lsp): support multiple folding range providers

* refactor(lsp): expose all folding related functions under `vim.lsp.*`

* perf(lsp): add `lsp.MultiHandler` for do `foldupdate()` only once
---
 runtime/doc/lsp.txt                               |  30 +
 runtime/doc/news.txt                              |   2 +
 runtime/lua/vim/lsp.lua                           |  34 ++
 runtime/lua/vim/lsp/_folding_range.lua            | 371 +++++++++++++
 runtime/lua/vim/lsp/protocol.lua                  |   7 +
 runtime/lua/vim/shared.lua                        |  45 ++
 runtime/lua/vim/treesitter/_fold.lua              |  53 +-
 scripts/gen_vimdoc.lua                            |   1 +
 test/functional/plugin/lsp/folding_range_spec.lua | 647 ++++++++++++++++++++++
 9 files changed, 1141 insertions(+), 49 deletions(-)
 create mode 100644 runtime/lua/vim/lsp/_folding_range.lua
 create mode 100644 test/functional/plugin/lsp/folding_range_spec.lua

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 1693ff5e4f..f7157df0f2 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -204,6 +204,7 @@ won't run if your server doesn't support them.
 - `'textDocument/diagnostic'`
 - `'textDocument/documentHighlight'`
 - `'textDocument/documentSymbol'`
+- `'textDocument/foldingRange'`
 - `'textDocument/formatting'`
 - `'textDocument/hover'`
 - `'textDocument/implementation'`
@@ -697,6 +698,35 @@ commands                                                    *vim.lsp.commands*
 
     The second argument is the `ctx` of |lsp-handler|
 
+foldclose({kind}, {winid})                               *vim.lsp.foldclose()*
+    Close all {kind} of folds in the the window with {winid}.
+
+    To automatically fold imports when opening a file, you can use an autocmd: >lua
+        vim.api.nvim_create_autocmd('LspNotify', {
+          callback = function(args)
+            if args.data.method == 'textDocument/didOpen' then
+              vim.lsp.foldclose('imports', vim.fn.bufwinid(args.buf))
+            end
+          end,
+        })
+<
+
+    Parameters: ~
+      • {kind}   (`lsp.FoldingRangeKind`) Kind to close, one of "comment",
+                 "imports" or "region".
+      • {winid}  (`integer?`) Defaults to the current window.
+
+foldexpr({lnum})                                          *vim.lsp.foldexpr()*
+    Provides an interface between the built-in client and a `foldexpr`
+    function.
+
+    Parameters: ~
+      • {lnum}  (`integer`) line number
+
+foldtext()                                                *vim.lsp.foldtext()*
+    Provides a `foldtext` function that shows the `collapsedText` retrieved,
+    defaults to the first folded line if `collapsedText` is not provided.
+
 formatexpr({opts})                                      *vim.lsp.formatexpr()*
     Provides an interface between the built-in client and a `formatexpr`
     function.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 98782bfd15..ad3f2c0a6a 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -230,6 +230,8 @@ LSP
 • |vim.lsp.buf.hover()| now highlights hover ranges using the
   |hl-LspReferenceTarget| highlight group.
 • Functions in |vim.lsp.Client| can now be called as methods.
+• Implemented LSP folding: |vim.lsp.foldexpr()|
+  https://microsoft.github.io/language-server-protocol/specification/#textDocument_foldingRange
 
 LUA
 
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 6d29c9e4df..a3791e15c3 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -3,6 +3,7 @@ local validate = vim.validate
 
 local lsp = vim._defer_require('vim.lsp', {
   _changetracking = ..., --- @module 'vim.lsp._changetracking'
+  _folding_range = ..., --- @module 'vim.lsp._folding_range'
   _snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
   _tagfunc = ..., --- @module 'vim.lsp._tagfunc'
   _watchfiles = ..., --- @module 'vim.lsp._watchfiles'
@@ -57,6 +58,7 @@ lsp._request_name_to_capability = {
   [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' },
   [ms.textDocument_documentLink] = { 'documentLinkProvider' },
   [ms.textDocument_documentSymbol] = { 'documentSymbolProvider' },
+  [ms.textDocument_foldingRange] = { 'foldingRangeProvider' },
   [ms.textDocument_formatting] = { 'documentFormattingProvider' },
   [ms.textDocument_hover] = { 'hoverProvider' },
   [ms.textDocument_implementation] = { 'implementationProvider' },
@@ -1094,6 +1096,38 @@ function lsp.tagfunc(pattern, flags)
   return vim.lsp._tagfunc(pattern, flags)
 end
 
+--- Provides an interface between the built-in client and a `foldexpr` function.
+---@param lnum integer line number
+function lsp.foldexpr(lnum)
+  return vim.lsp._folding_range.foldexpr(lnum)
+end
+
+--- Close all {kind} of folds in the the window with {winid}.
+---
+--- To automatically fold imports when opening a file, you can use an autocmd:
+---
+--- ```lua
+--- vim.api.nvim_create_autocmd('LspNotify', {
+---   callback = function(args)
+---     if args.data.method == 'textDocument/didOpen' then
+---       vim.lsp.foldclose('imports', vim.fn.bufwinid(args.buf))
+---     end
+---   end,
+--- })
+--- ```
+---
+---@param kind lsp.FoldingRangeKind Kind to close, one of "comment", "imports" or "region".
+---@param winid? integer Defaults to the current window.
+function lsp.foldclose(kind, winid)
+  return vim.lsp._folding_range.foldclose(kind, winid)
+end
+
+--- Provides a `foldtext` function that shows the `collapsedText` retrieved,
+--- defaults to the first folded line if `collapsedText` is not provided.
+function lsp.foldtext()
+  return vim.lsp._folding_range.foldtext()
+end
+
 ---Checks whether a client is stopped.
 ---
 ---@param client_id (integer)
diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua
new file mode 100644
index 0000000000..6a445017a3
--- /dev/null
+++ b/runtime/lua/vim/lsp/_folding_range.lua
@@ -0,0 +1,371 @@
+local util = require('vim.lsp.util')
+local log = require('vim.lsp.log')
+local ms = require('vim.lsp.protocol').Methods
+local api = vim.api
+
+local M = {}
+
+---@class (private) vim.lsp.folding_range.BufState
+---
+---@field version? integer
+---
+--- Never use this directly, `renew()` the cached foldinfo
+--- then use on demand via `row_*` fields.
+---
+--- Index In the form of client_id -> ranges
+---@field client_ranges table
+---
+--- Index in the form of row -> [foldlevel, mark]
+---@field row_level table" | "<"?]?>
+---
+--- Index in the form of start_row -> kinds
+---@field row_kinds table?>>
+---
+--- Index in the form of start_row -> collapsed_text
+---@field row_text table
+
+---@type table
+local bufstates = {}
+
+--- Renew the cached foldinfo in the buffer.
+---@param bufnr integer
+local function renew(bufnr)
+  local bufstate = assert(bufstates[bufnr])
+
+  ---@type table" | "<"?]?>
+  local row_level = {}
+  ---@type table?>>
+  local row_kinds = {}
+  ---@type table
+  local row_text = {}
+
+  for _, ranges in pairs(bufstate.client_ranges) do
+    for _, range in ipairs(ranges) do
+      local start_row = range.startLine
+      local end_row = range.endLine
+      -- Adding folds within a single line is not supported by Nvim.
+      if start_row ~= end_row then
+        row_text[start_row] = range.collapsedText
+
+        local kind = range.kind
+        if kind then
+          local kinds = row_kinds[start_row] or {}
+          kinds[kind] = true
+          row_kinds[start_row] = kinds
+        end
+
+        for row = start_row, end_row do
+          local level = row_level[row] or { 0 }
+          level[1] = level[1] + 1
+          row_level[row] = level
+        end
+        row_level[start_row][2] = '>'
+        row_level[end_row][2] = '<'
+      end
+    end
+  end
+
+  bufstate.row_level = row_level
+  bufstate.row_kinds = row_kinds
+  bufstate.row_text = row_text
+end
+
+--- Renew the cached foldinfo then force `foldexpr()` to be re-evaluated,
+--- without opening folds.
+---@param bufnr integer
+local function foldupdate(bufnr)
+  renew(bufnr)
+  for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do
+    local wininfo = vim.fn.getwininfo(winid)[1]
+    if wininfo and wininfo.tabnr == vim.fn.tabpagenr() then
+      if vim.wo[winid].foldmethod == 'expr' then
+        vim._foldupdate(winid, 0, api.nvim_buf_line_count(bufnr))
+      end
+    end
+  end
+end
+
+--- Whether `foldupdate()` is scheduled for the buffer with `bufnr`.
+---
+--- Index in the form of bufnr -> true?
+---@type table
+local scheduled_foldupdate = {}
+
+--- Schedule `foldupdate()` after leaving insert mode.
+---@param bufnr integer
+local function schedule_foldupdate(bufnr)
+  if not scheduled_foldupdate[bufnr] then
+    scheduled_foldupdate[bufnr] = true
+    api.nvim_create_autocmd('InsertLeave', {
+      buffer = bufnr,
+      once = true,
+      callback = function()
+        foldupdate(bufnr)
+        scheduled_foldupdate[bufnr] = nil
+      end,
+    })
+  end
+end
+
+---@param results table
+---@type lsp.MultiHandler
+local function multi_handler(results, ctx)
+  local bufnr = assert(ctx.bufnr)
+  -- Handling responses from outdated buffer only causes performance overhead.
+  if util.buf_versions[bufnr] ~= ctx.version then
+    return
+  end
+
+  local bufstate = assert(bufstates[bufnr])
+  for client_id, result in pairs(results) do
+    if result.err then
+      log.error(result.err)
+    else
+      bufstate.client_ranges[client_id] = result.result
+    end
+  end
+  bufstate.version = ctx.version
+
+  if api.nvim_get_mode().mode:match('^i') then
+    -- `foldUpdate()` is guarded in insert mode.
+    schedule_foldupdate(bufnr)
+  else
+    foldupdate(bufnr)
+  end
+end
+
+---@param result lsp.FoldingRange[]?
+---@type lsp.Handler
+local function handler(err, result, ctx)
+  multi_handler({ [ctx.client_id] = { err = err, result = result } }, ctx)
+end
+
+--- Request `textDocument/foldingRange` from the server.
+--- `foldupdate()` is scheduled once after the request is completed.
+---@param bufnr integer
+---@param client? vim.lsp.Client The client whose server supports `foldingRange`.
+local function request(bufnr, client)
+  ---@type lsp.FoldingRangeParams
+  local params = { textDocument = util.make_text_document_params(bufnr) }
+
+  if client then
+    client:request(ms.textDocument_foldingRange, params, handler, bufnr)
+    return
+  end
+
+  if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then
+    return
+  end
+
+  vim.lsp.buf_request_all(bufnr, ms.textDocument_foldingRange, params, multi_handler)
+end
+
+-- NOTE:
+-- `bufstate` and event hooks are interdependent:
+-- * `bufstate` needs event hooks for correctness.
+-- * event hooks require the previous `bufstate` for updates.
+-- Since they are manually created and destroyed,
+-- we ensure their lifecycles are always synchronized.
+--
+-- TODO(ofseed):
+-- 1. Implement clearing `bufstate` and event hooks
+--    when no clients in the buffer support the corresponding method.
+-- 2. Then generalize this state management to other LSP modules.
+local augroup_setup = api.nvim_create_augroup('vim_lsp_folding_range/setup', {})
+
+--- Initialize `bufstate` and event hooks, then request folding ranges.
+--- Manage their lifecycle within this function.
+---@param bufnr integer
+---@return vim.lsp.folding_range.BufState?
+local function setup(bufnr)
+  if not api.nvim_buf_is_loaded(bufnr) then
+    return
+  end
+
+  -- Register the new `bufstate`.
+  bufstates[bufnr] = {
+    client_ranges = {},
+    row_level = {},
+    row_kinds = {},
+    row_text = {},
+  }
+
+  -- Event hooks from `buf_attach` can't be removed externally.
+  -- Hooks and `bufstate` share the same lifecycle;
+  -- they should self-destroy if `bufstate == nil`.
+  api.nvim_buf_attach(bufnr, false, {
+    -- `on_detach` also runs on buffer reload (`:e`).
+    -- Ensure `bufstate` and hooks are cleared to avoid duplication or leftover states.
+    on_detach = function()
+      bufstates[bufnr] = nil
+      api.nvim_clear_autocmds({ buffer = bufnr, group = augroup_setup })
+    end,
+    -- Reset `bufstate` and request folding ranges.
+    on_reload = function()
+      bufstates[bufnr] = {
+        client_ranges = {},
+        row_level = {},
+        row_kinds = {},
+        row_text = {},
+      }
+      request(bufnr)
+    end,
+    --- Sync changed rows with their previous foldlevels before applying new ones.
+    on_bytes = function(_, _, _, start_row, _, _, old_row, _, _, new_row, _, _)
+      if bufstates[bufnr] == nil then
+        return true
+      end
+      local row_level = bufstates[bufnr].row_level
+      if next(row_level) == nil then
+        return
+      end
+      local row = new_row - old_row
+      if row > 0 then
+        vim._list_insert(row_level, start_row, start_row + math.abs(row) - 1, { -1 })
+        -- If the previous row ends a fold,
+        -- Nvim treats the first row after consecutive `-1`s as a new fold start,
+        -- which is not the desired behavior.
+        local prev_level = row_level[start_row - 1]
+        if prev_level and prev_level[2] == '<' then
+          row_level[start_row] = { prev_level[1] - 1 }
+        end
+      elseif row < 0 then
+        vim._list_remove(row_level, start_row, start_row + math.abs(row) - 1)
+      end
+    end,
+  })
+  api.nvim_create_autocmd('LspDetach', {
+    group = augroup_setup,
+    buffer = bufnr,
+    callback = function(args)
+      if not api.nvim_buf_is_loaded(bufnr) then
+        return
+      end
+
+      ---@type integer
+      local client_id = args.data.client_id
+      bufstates[bufnr].client_ranges[client_id] = nil
+
+      ---@type vim.lsp.Client[]
+      local clients = vim
+        .iter(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange }))
+        ---@param client vim.lsp.Client
+        :filter(function(client)
+          return client.id ~= client_id
+        end)
+        :totable()
+      if #clients == 0 then
+        bufstates[bufnr] = {
+          client_ranges = {},
+          row_level = {},
+          row_kinds = {},
+          row_text = {},
+        }
+      end
+
+      foldupdate(bufnr)
+    end,
+  })
+  api.nvim_create_autocmd('LspAttach', {
+    group = augroup_setup,
+    buffer = bufnr,
+    callback = function(args)
+      local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
+      request(bufnr, client)
+    end,
+  })
+  api.nvim_create_autocmd('LspNotify', {
+    group = augroup_setup,
+    buffer = bufnr,
+    callback = function(args)
+      local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
+      if
+        client:supports_method(ms.textDocument_foldingRange, bufnr)
+        and (
+          args.data.method == ms.textDocument_didChange
+          or args.data.method == ms.textDocument_didOpen
+        )
+      then
+        request(bufnr, client)
+      end
+    end,
+  })
+
+  request(bufnr)
+
+  return bufstates[bufnr]
+end
+
+---@param kind lsp.FoldingRangeKind
+---@param winid integer
+local function foldclose(kind, winid)
+  vim._with({ win = winid }, function()
+    local bufnr = api.nvim_win_get_buf(winid)
+    local row_kinds = bufstates[bufnr].row_kinds
+    -- Reverse traverse to ensure that the smallest ranges are closed first.
+    for row = api.nvim_buf_line_count(bufnr) - 1, 0, -1 do
+      local kinds = row_kinds[row]
+      if kinds and kinds[kind] then
+        vim.cmd(row + 1 .. 'foldclose')
+      end
+    end
+  end)
+end
+
+---@param kind lsp.FoldingRangeKind
+---@param winid? integer
+function M.foldclose(kind, winid)
+  vim.validate('kind', kind, 'string')
+  vim.validate('winid', winid, 'number', true)
+
+  winid = winid or api.nvim_get_current_win()
+  local bufnr = api.nvim_win_get_buf(winid)
+  local bufstate = bufstates[bufnr]
+  if not bufstate then
+    return
+  end
+
+  if bufstate.version == util.buf_versions[bufnr] then
+    foldclose(kind, winid)
+    return
+  end
+  -- Schedule `foldclose()` if the buffer is not up-to-date.
+
+  if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then
+    return
+  end
+  ---@type lsp.FoldingRangeParams
+  local params = { textDocument = util.make_text_document_params(bufnr) }
+  vim.lsp.buf_request_all(bufnr, ms.textDocument_foldingRange, params, function(...)
+    multi_handler(...)
+    foldclose(kind, winid)
+  end)
+end
+
+---@return string
+function M.foldtext()
+  local bufnr = api.nvim_get_current_buf()
+  local lnum = vim.v.foldstart
+  local row = lnum - 1
+  local bufstate = bufstates[bufnr]
+  if bufstate and bufstate.row_text[row] then
+    return bufstate.row_text[row]
+  end
+  return vim.fn.getline(lnum)
+end
+
+---@param lnum? integer
+---@return string level
+function M.foldexpr(lnum)
+  local bufnr = api.nvim_get_current_buf()
+  local bufstate = bufstates[bufnr] or setup(bufnr)
+  if not bufstate then
+    return '0'
+  end
+
+  local row = (lnum or vim.v.lnum) - 1
+  local level = bufstate.row_level[row]
+  return level and (level[2] or '') .. (level[1] or '0') or '0'
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 3d29dad90a..cfd47d8f7c 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -440,6 +440,13 @@ function protocol.make_client_capabilities()
           properties = { 'command' },
         },
       },
+      foldingRange = {
+        dynamicRegistration = false,
+        lineFoldingOnly = true,
+        foldingRange = {
+          collapsedText = true,
+        },
+      },
       formatting = {
         dynamicRegistration = true,
       },
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 4f2373b182..2e8edea22a 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -737,6 +737,51 @@ function vim.list_slice(list, start, finish)
   return new_list
 end
 
+--- Efficiently insert items into the middle of a list.
+---
+--- Calling table.insert() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index  the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+---@param v any
+function vim._list_insert(t, first, last, v)
+  local n = #t
+
+  -- Shift table forward
+  for i = n - first, 0, -1 do
+    t[last + 1 + i] = t[first + i]
+  end
+
+  -- Fill in new values
+  for i = first, last do
+    t[i] = v
+  end
+end
+
+--- Efficiently remove items from middle of a list.
+---
+--- Calling table.remove() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index  the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+function vim._list_remove(t, first, last)
+  local n = #t
+  for i = 0, n - first do
+    t[first + i] = t[last + 1 + i]
+    t[last + 1 + i] = nil
+  end
+end
+
 --- Trim whitespace (Lua pattern "%s") from both sides of a string.
 ---
 ---@see |lua-patterns|
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 7237d2e7d4..0cb5b497c7 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -30,65 +30,20 @@ function FoldInfo.new()
   }, FoldInfo)
 end
 
---- Efficiently remove items from middle of a list a list.
----
---- Calling table.remove() in a loop will re-index the tail of the table on
---- every iteration, instead this function will re-index  the table exactly
---- once.
----
---- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
----
----@param t any[]
----@param first integer
----@param last integer
-local function list_remove(t, first, last)
-  local n = #t
-  for i = 0, n - first do
-    t[first + i] = t[last + 1 + i]
-    t[last + 1 + i] = nil
-  end
-end
-
 ---@package
 ---@param srow integer
 ---@param erow integer 0-indexed, exclusive
 function FoldInfo:remove_range(srow, erow)
-  list_remove(self.levels, srow + 1, erow)
-  list_remove(self.levels0, srow + 1, erow)
-end
-
---- Efficiently insert items into the middle of a list.
----
---- Calling table.insert() in a loop will re-index the tail of the table on
---- every iteration, instead this function will re-index  the table exactly
---- once.
----
---- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
----
----@param t any[]
----@param first integer
----@param last integer
----@param v any
-local function list_insert(t, first, last, v)
-  local n = #t
-
-  -- Shift table forward
-  for i = n - first, 0, -1 do
-    t[last + 1 + i] = t[first + i]
-  end
-
-  -- Fill in new values
-  for i = first, last do
-    t[i] = v
-  end
+  vim._list_remove(self.levels, srow + 1, erow)
+  vim._list_remove(self.levels0, srow + 1, erow)
 end
 
 ---@package
 ---@param srow integer
 ---@param erow integer 0-indexed, exclusive
 function FoldInfo:add_range(srow, erow)
-  list_insert(self.levels, srow + 1, erow, -1)
-  list_insert(self.levels0, srow + 1, erow, -1)
+  vim._list_insert(self.levels, srow + 1, erow, -1)
+  vim._list_insert(self.levels0, srow + 1, erow, -1)
 end
 
 ---@param range Range2
diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua
index 1125021bdc..3f870c561f 100755
--- a/scripts/gen_vimdoc.lua
+++ b/scripts/gen_vimdoc.lua
@@ -274,6 +274,7 @@ local config = {
       'diagnostic.lua',
       'codelens.lua',
       'completion.lua',
+      'folding_range.lua',
       'inlay_hint.lua',
       'tagfunc.lua',
       'semantic_tokens.lua',
diff --git a/test/functional/plugin/lsp/folding_range_spec.lua b/test/functional/plugin/lsp/folding_range_spec.lua
new file mode 100644
index 0000000000..7e68a598d2
--- /dev/null
+++ b/test/functional/plugin/lsp/folding_range_spec.lua
@@ -0,0 +1,647 @@
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
+local Screen = require('test.functional.ui.screen')
+local t_lsp = require('test.functional.plugin.lsp.testutil')
+
+local eq = t.eq
+local tempname = t.tmpname
+
+local clear_notrace = t_lsp.clear_notrace
+local create_server_definition = t_lsp.create_server_definition
+
+local api = n.api
+local exec_lua = n.exec_lua
+local insert = n.insert
+local command = n.command
+local feed = n.feed
+
+describe('vim.lsp.folding_range', function()
+  local text = [[// foldLevel() {{{2
+/// @return  fold level at line number "lnum" in the current window.
+static int foldLevel(linenr_T lnum)
+{
+  // While updating the folds lines between invalid_top and invalid_bot have
+  // an undefined fold level.  Otherwise update the folds first.
+  if (invalid_top == 0) {
+    checkupdate(curwin);
+  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {
+    return prev_lnum_lvl;
+  } else if (lnum >= invalid_top && lnum <= invalid_bot) {
+    return -1;
+  }
+
+  // Return quickly when there is no folding at all in this window.
+  if (!hasAnyFolding(curwin)) {
+    return 0;
+  }
+
+  return foldLevelWin(curwin, lnum);
+}]]
+
+  local result = {
+    {
+      endLine = 19,
+      kind = 'region',
+      startCharacter = 1,
+      startLine = 3,
+    },
+    {
+      endCharacter = 2,
+      endLine = 7,
+      kind = 'region',
+      startCharacter = 25,
+      startLine = 6,
+    },
+    {
+      endCharacter = 2,
+      endLine = 9,
+      kind = 'region',
+      startCharacter = 55,
+      startLine = 8,
+    },
+    {
+      endCharacter = 2,
+      endLine = 11,
+      kind = 'region',
+      startCharacter = 58,
+      startLine = 10,
+    },
+    {
+      endCharacter = 2,
+      endLine = 16,
+      kind = 'region',
+      startCharacter = 31,
+      startLine = 15,
+    },
+    {
+      endCharacter = 68,
+      endLine = 1,
+      kind = 'comment',
+      startCharacter = 2,
+      startLine = 0,
+    },
+    {
+      endCharacter = 64,
+      endLine = 5,
+      kind = 'comment',
+      startCharacter = 4,
+      startLine = 4,
+    },
+  }
+
+  local bufnr ---@type integer
+  local client_id ---@type integer
+
+  clear_notrace()
+  before_each(function()
+    clear_notrace()
+
+    exec_lua(create_server_definition)
+    bufnr = n.api.nvim_get_current_buf()
+    client_id = exec_lua(function()
+      _G.server = _G._create_server({
+        capabilities = {
+          foldingRangeProvider = true,
+        },
+        handlers = {
+          ['textDocument/foldingRange'] = function(_, _, callback)
+            callback(nil, result)
+          end,
+        },
+      })
+
+      vim.api.nvim_win_set_buf(0, bufnr)
+
+      return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+    end)
+    command('set foldmethod=expr foldcolumn=1 foldlevel=999')
+    insert(text)
+  end)
+  after_each(function()
+    api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
+  end)
+
+  describe('setup()', function()
+    ---@type integer
+    local bufnr_set_expr
+    ---@type integer
+    local bufnr_never_set_expr
+
+    local function buf_autocmd_num(bufnr_to_check)
+      return exec_lua(function()
+        return #vim.api.nvim_get_autocmds({ buffer = bufnr_to_check, event = 'LspNotify' })
+      end)
+    end
+
+    before_each(function()
+      command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]])
+      exec_lua(function()
+        bufnr_set_expr = vim.api.nvim_create_buf(true, false)
+        vim.api.nvim_set_current_buf(bufnr_set_expr)
+      end)
+      insert(text)
+      command('write ' .. tempname(false))
+      command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]])
+      exec_lua(function()
+        bufnr_never_set_expr = vim.api.nvim_create_buf(true, false)
+        vim.api.nvim_set_current_buf(bufnr_never_set_expr)
+      end)
+      insert(text)
+      api.nvim_win_set_buf(0, bufnr_set_expr)
+    end)
+
+    it('only create event hooks where foldexpr has been set', function()
+      eq(1, buf_autocmd_num(bufnr))
+      eq(1, buf_autocmd_num(bufnr_set_expr))
+      eq(0, buf_autocmd_num(bufnr_never_set_expr))
+    end)
+
+    it('does not create duplicate event hooks after reloaded', function()
+      command('edit')
+      eq(1, buf_autocmd_num(bufnr_set_expr))
+    end)
+
+    it('cleans up event hooks when buffer is unloaded', function()
+      command('bdelete')
+      eq(0, buf_autocmd_num(bufnr_set_expr))
+    end)
+  end)
+
+  describe('expr()', function()
+    --- @type test.functional.ui.screen
+    local screen
+    before_each(function()
+      screen = Screen.new(80, 45)
+      screen:set_default_attr_ids({
+        [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue },
+        [2] = { bold = true, foreground = Screen.colors.Blue1 },
+        [3] = { bold = true, reverse = true },
+        [4] = { reverse = true },
+      })
+      command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
+      command([[split]])
+    end)
+
+    it('can compute fold levels', function()
+      ---@type table
+      local foldlevels = {}
+      for i = 1, 21 do
+        foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')')
+      end
+      eq({
+        [1] = '>1',
+        [2] = '<1',
+        [3] = '0',
+        [4] = '>1',
+        [5] = '>2',
+        [6] = '<2',
+        [7] = '>2',
+        [8] = '<2',
+        [9] = '>2',
+        [10] = '<2',
+        [11] = '>2',
+        [12] = '<2',
+        [13] = '1',
+        [14] = '1',
+        [15] = '1',
+        [16] = '>2',
+        [17] = '<2',
+        [18] = '1',
+        [19] = '1',
+        [20] = '<1',
+        [21] = '0',
+      }, foldlevels)
+    end)
+
+    it('updates folds in all windows', function()
+      screen:expect({
+        grid = [[
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:[No Name] [+]                                                                   }|
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }}                                                                              |
+{4:[No Name] [+]                                                                   }|
+                                                                                |
+  ]],
+      })
+    end)
+
+    it('persists wherever foldexpr is set', function()
+      command([[setlocal foldexpr=]])
+      feed('zx')
+      screen:expect({
+        grid = [[
+{1: }// foldLevel() {{{2                                                            |
+{1: }/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1: }{                                                                              |
+{1: }  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1: }  // an undefined fold level.  Otherwise update the folds first.               |
+{1: }  if (invalid_top == 0) {                                                      |
+{1: }    checkupdate(curwin);                                                       |
+{1: }  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1: }    return prev_lnum_lvl;                                                      |
+{1: }  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1: }    return -1;                                                                 |
+{1: }  }                                                                            |
+{1: }                                                                               |
+{1: }  // Return quickly when there is no folding at all in this window.            |
+{1: }  if (!hasAnyFolding(curwin)) {                                                |
+{1: }    return 0;                                                                  |
+{1: }  }                                                                            |
+{1: }                                                                               |
+{1: }  return foldLevelWin(curwin, lnum);                                           |
+{1: }}                                                                              |
+{4:[No Name] [+]                                                                   }|
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:[No Name] [+]                                                                   }|
+                                                                                |
+  ]],
+      })
+    end)
+
+    it('synchronizes changed rows with their previous foldlevels', function()
+      command('1,2d')
+      screen:expect({
+        grid = [[
+{1: }^static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }}                                                                              |
+{2:~                                                                               }|*2
+{3:[No Name] [+]                                                                   }|
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }}                                                                              |
+{2:~                                                                               }|*2
+{4:[No Name] [+]                                                                   }|
+                                                                                |
+]],
+      })
+    end)
+
+    it('clears folds when sole client detaches', function()
+      exec_lua(function()
+        vim.lsp.buf_detach_client(bufnr, client_id)
+      end)
+      screen:expect({
+        grid = [[
+{1: }// foldLevel() {{{2                                                            |
+{1: }/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1: }{                                                                              |
+{1: }  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1: }  // an undefined fold level.  Otherwise update the folds first.               |
+{1: }  if (invalid_top == 0) {                                                      |
+{1: }    checkupdate(curwin);                                                       |
+{1: }  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1: }    return prev_lnum_lvl;                                                      |
+{1: }  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1: }    return -1;                                                                 |
+{1: }  }                                                                            |
+{1: }                                                                               |
+{1: }  // Return quickly when there is no folding at all in this window.            |
+{1: }  if (!hasAnyFolding(curwin)) {                                                |
+{1: }    return 0;                                                                  |
+{1: }  }                                                                            |
+{1: }                                                                               |
+{1: }  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:[No Name] [+]                                                                   }|
+{1: }// foldLevel() {{{2                                                            |
+{1: }/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1: }{                                                                              |
+{1: }  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1: }  // an undefined fold level.  Otherwise update the folds first.               |
+{1: }  if (invalid_top == 0) {                                                      |
+{1: }    checkupdate(curwin);                                                       |
+{1: }  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1: }    return prev_lnum_lvl;                                                      |
+{1: }  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1: }    return -1;                                                                 |
+{1: }  }                                                                            |
+{1: }                                                                               |
+{1: }  // Return quickly when there is no folding at all in this window.            |
+{1: }  if (!hasAnyFolding(curwin)) {                                                |
+{1: }    return 0;                                                                  |
+{1: }  }                                                                            |
+{1: }                                                                               |
+{1: }  return foldLevelWin(curwin, lnum);                                           |
+{1: }}                                                                              |
+{4:[No Name] [+]                                                                   }|
+                                                                                |
+  ]],
+      })
+    end)
+
+    it('remains valid after the client re-attaches.', function()
+      exec_lua(function()
+        vim.lsp.buf_detach_client(bufnr, client_id)
+        vim.lsp.buf_attach_client(bufnr, client_id)
+      end)
+      screen:expect({
+        grid = [[
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:[No Name] [+]                                                                   }|
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }}                                                                              |
+{4:[No Name] [+]                                                                   }|
+                                                                                |
+  ]],
+      })
+    end)
+  end)
+
+  describe('foldtext()', function()
+    --- @type test.functional.ui.screen
+    local screen
+    before_each(function()
+      screen = Screen.new(80, 23)
+      screen:set_default_attr_ids({
+        [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue },
+        [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey },
+        [3] = { bold = true, foreground = Screen.colors.Blue1 },
+        [4] = { bold = true, reverse = true },
+        [5] = { reverse = true },
+      })
+      command(
+        [[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]]
+      )
+    end)
+
+    it('shows the first folded line if `collapsedText` does not exist', function()
+      screen:expect({
+        grid = [[
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:+}{2:  // While updating the folds lines between invalid_top and invalid_bot have···}|
+{1:+}{2:  if (invalid_top == 0) {······················································}|
+{1:+}{2:  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}|
+{1:+}{2:  } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}|
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:+}{2:  if (!hasAnyFolding(curwin)) {················································}|
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:~                                                                               }|*6
+                                                                                |
+  ]],
+      })
+    end)
+  end)
+
+  describe('foldclose()', function()
+    --- @type test.functional.ui.screen
+    local screen
+    before_each(function()
+      screen = Screen.new(80, 23)
+      screen:set_default_attr_ids({
+        [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue },
+        [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey },
+        [3] = { bold = true, foreground = Screen.colors.Blue1 },
+        [4] = { bold = true, reverse = true },
+        [5] = { reverse = true },
+      })
+      command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
+    end)
+
+    it('closes all folds of one kind immediately', function()
+      exec_lua(function()
+        vim.lsp.foldclose('comment')
+      end)
+      screen:expect({
+        grid = [[
+{1:+}{2:+--  2 lines: foldLevel()······················································}|
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:+}{2:+---  2 lines: While updating the folds lines between invalid_top and invalid_b}|
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:~                                                                               }|*3
+                                                                                |
+  ]],
+      })
+    end)
+
+    it('closes the smallest fold first', function()
+      exec_lua(function()
+        vim.lsp.foldclose('region')
+      end)
+      screen:expect({
+        grid = [[
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:+}{2:+-- 17 lines: {································································}|
+{1: }^}                                                                              |
+{3:~                                                                               }|*17
+                                                                                |
+  ]],
+      })
+      command('4foldopen')
+      screen:expect({
+        grid = [[
+{1:-}// foldLevel() {{{2                                                            |
+{1:│}/// @return  fold level at line number "lnum" in the current window.           |
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:-}  // While updating the folds lines between invalid_top and invalid_bot have   |
+{1:2}  // an undefined fold level.  Otherwise update the folds first.               |
+{1:+}{2:+---  2 lines: if (invalid_top == 0) {·········································}|
+{1:+}{2:+---  2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}|
+{1:+}{2:+---  2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}|
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:+}{2:+---  2 lines: if (!hasAnyFolding(curwin)) {···································}|
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:~                                                                               }|*5
+                                                                                |
+  ]],
+      })
+    end)
+
+    it('is defered when the buffer is not up-to-date', function()
+      exec_lua(function()
+        vim.lsp.foldclose('comment')
+        vim.lsp.util.buf_versions[bufnr] = 0
+      end)
+      screen:expect({
+        grid = [[
+{1:+}{2:+--  2 lines: foldLevel()······················································}|
+{1: }static int foldLevel(linenr_T lnum)                                            |
+{1:-}{                                                                              |
+{1:+}{2:+---  2 lines: While updating the folds lines between invalid_top and invalid_b}|
+{1:-}  if (invalid_top == 0) {                                                      |
+{1:2}    checkupdate(curwin);                                                       |
+{1:-}  } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {                        |
+{1:2}    return prev_lnum_lvl;                                                      |
+{1:-}  } else if (lnum >= invalid_top && lnum <= invalid_bot) {                     |
+{1:2}    return -1;                                                                 |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  // Return quickly when there is no folding at all in this window.            |
+{1:-}  if (!hasAnyFolding(curwin)) {                                                |
+{1:2}    return 0;                                                                  |
+{1:│}  }                                                                            |
+{1:│}                                                                               |
+{1:│}  return foldLevelWin(curwin, lnum);                                           |
+{1: }^}                                                                              |
+{3:~                                                                               }|*3
+                                                                                |
+  ]],
+      })
+    end)
+  end)
+end)
-- 
cgit 


From 3056115785a435f89d11551e1c8bb4c89c90f610 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Tue, 26 Nov 2024 12:26:46 +0000
Subject: refactor: gen_declarations.lua

Problem:
gen_declarations.lua is complex and has duplicate logic with
c_grammar.lua

Solution:
Move all lpeg logic to c_grammar.lua and refactor gen_declarations.lua.
---
 src/nvim/CMakeLists.txt                   |   2 +-
 src/nvim/generators/c_grammar.lua         | 308 +++++++++++++++-----
 src/nvim/generators/gen_api_ui_events.lua |   2 +-
 src/nvim/generators/gen_declarations.lua  | 468 ++++++++++--------------------
 4 files changed, 386 insertions(+), 394 deletions(-)

diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index e2b1036d95..cffdcf0415 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -556,7 +556,7 @@ foreach(sfile ${NVIM_SOURCES}
     set(PREPROC_OUTPUT -w -E -o ${gf_i})
   endif()
 
-  set(depends "${HEADER_GENERATOR}" "${sfile}" "${LUA_GEN_DEPS}")
+  set(depends "${HEADER_GENERATOR}" "${sfile}" "${LUA_GEN_DEPS}" "${GENERATOR_C_GRAMMAR}")
   if("${f}" STREQUAL "version.c")
     # Ensure auto/versiondef_git.h exists after "make clean".
     list(APPEND depends update_version_stamp "${NVIM_VERSION_GIT_H}" "${NVIM_VERSION_DEF_H}")
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index a0a9c86789..9b1c284c1e 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -1,10 +1,37 @@
-local lpeg = vim.lpeg
-
 -- lpeg grammar for building api metadata from a set of header files. It
 -- ignores comments and preprocessor commands and parses a very small subset
 -- of C prototypes with a limited set of types
+
+--- @class nvim.c_grammar.Proto
+--- @field [1] 'proto'
+--- @field pos integer
+--- @field endpos integer
+--- @field fast boolean
+--- @field name string
+--- @field return_type string
+--- @field parameters [string, string][]
+--- @field static true?
+--- @field inline true?
+
+--- @class nvim.c_grammar.Preproc
+--- @field [1] 'preproc'
+--- @field content string
+
+--- @class nvim.c_grammar.Empty
+--- @field [1] 'empty'
+
+--- @alias nvim.c_grammar.result
+--- | nvim.c_grammar.Proto
+--- | nvim.c_grammar.Preproc
+--- | nvim.c_grammar.Empty
+
+--- @class nvim.c_grammar
+--- @field match fun(self, input: string): nvim.c_grammar.result[]
+
+local lpeg = vim.lpeg
+
 local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V
-local C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg
+local C, Ct, Cc, Cg, Cp = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cp
 
 --- @param pat vim.lpeg.Pattern
 local function rep(pat)
@@ -21,125 +48,248 @@ local function opt(pat)
   return pat ^ -1
 end
 
-local any = P(1) -- (consume one character)
+local any = P(1)
 local letter = R('az', 'AZ') + S('_$')
 local num = R('09')
 local alpha = letter + num
 local nl = P('\r\n') + P('\n')
-local not_nl = any - nl
 local space = S(' \t')
+local str = P('"') * rep((P('\\') * any) + (1 - P('"'))) * P('"')
+local char = P("'") * (any - P("'")) * P("'")
 local ws = space + nl
-local fill = rep(ws)
-local c_comment = P('//') * rep(not_nl)
-local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(not_nl), 'comment')))
-local c_preproc = P('#') * rep(not_nl)
-local dllexport = P('DLLEXPORT') * rep1(ws)
+local wb = #-alpha -- word boundary
+local id = letter * rep(alpha)
+
+local comment_inline = P('/*') * rep(1 - P('*/')) * P('*/')
+local comment = P('//') * rep(1 - nl) * nl
+local preproc = Ct(Cc('preproc') * P('#') * Cg(rep(1 - nl) * nl, 'content'))
+
+local fill = rep(ws + comment_inline + comment + preproc)
+
+--- @param s string
+--- @return vim.lpeg.Pattern
+local function word(s)
+  return fill * P(s) * wb * fill
+end
 
 --- @param x vim.lpeg.Pattern
 local function comma1(x)
   return x * rep(fill * P(',') * fill * x)
 end
 
--- Define the grammar
-
-local basic_id = letter * rep(alpha)
-
-local str = P('"') * rep(any - P('"')) * P('"')
+--- @param v string
+local function Pf(v)
+  return fill * P(v) * fill
+end
 
 --- @param x vim.lpeg.Pattern
 local function paren(x)
   return P('(') * fill * x * fill * P(')')
 end
 
+local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(1 - nl), 'comment')))
+
+local braces = P({
+  'S',
+  A = comment_inline + comment + preproc + str + char + (any - S('{}')),
+  S = P('{') * rep(V('A')) * rep(V('S') + V('A')) * P('}'),
+})
+
 -- stylua: ignore start
 local typed_container = P({
   'S',
-  ID = V('S') + basic_id,
   S = (
     (P('Union') * paren(comma1(V('ID'))))
-    + (P('ArrayOf') * paren(basic_id * opt(P(',') * fill * rep1(num))))
-    + (P('DictOf') * paren(basic_id))
+    + (P('ArrayOf') * paren(id * opt(P(',') * fill * rep1(num))))
+    + (P('DictOf') * paren(id))
     + (P('LuaRefOf') * paren(
-      paren(comma1((V('ID') + str) * rep1(ws) * opt(P('*')) * basic_id))
+      paren(comma1((V('ID') + str) * rep1(ws) * opt(P('*')) * id))
       * opt(P(',') * fill * opt(P('*')) * V('ID'))
     ))
-    + (P('Dict') * paren(basic_id))),
+    + (P('Dict') * paren(id))),
+  ID = V('S') + id,
 })
 -- stylua: ignore end
 
-local c_id = typed_container + basic_id
-local c_void = P('void')
+local ptr_mod = word('restrict') + word('__restrict') + word('const')
+local opt_ptr = rep(Pf('*') * opt(ptr_mod))
+
+--- @param name string
+--- @param var string
+--- @return vim.lpeg.Pattern
+local function attr(name, var)
+  return Cg((P(name) * Cc(true)), var)
+end
+
+--- @param name string
+--- @param var string
+--- @return vim.lpeg.Pattern
+local function attr_num(name, var)
+  return Cg((P(name) * paren(C(rep1(num)))), var)
+end
+
+local fattr = (
+  attr_num('FUNC_API_SINCE', 'since')
+  + attr_num('FUNC_API_DEPRECATED_SINCE', 'deprecated_since')
+  + attr('FUNC_API_FAST', 'fast')
+  + attr('FUNC_API_RET_ALLOC', 'ret_alloc')
+  + attr('FUNC_API_NOEXPORT', 'noexport')
+  + attr('FUNC_API_REMOTE_ONLY', 'remote_only')
+  + attr('FUNC_API_LUA_ONLY', 'lua_only')
+  + attr('FUNC_API_TEXTLOCK_ALLOW_CMDWIN', 'textlock_allow_cmdwin')
+  + attr('FUNC_API_TEXTLOCK', 'textlock')
+  + attr('FUNC_API_REMOTE_IMPL', 'remote_impl')
+  + attr('FUNC_API_COMPOSITOR_IMPL', 'compositor_impl')
+  + attr('FUNC_API_CLIENT_IMPL', 'client_impl')
+  + attr('FUNC_API_CLIENT_IGNORE', 'client_ignore')
+  + (P('FUNC_') * rep(alpha) * opt(fill * paren(rep(1 - P(')') * any))))
+)
+
+local void = P('void') * wb
 
-local c_param_type = (
-  ((P('Error') * fill * P('*') * fill) * Cc('error'))
-  + ((P('Arena') * fill * P('*') * fill) * Cc('arena'))
-  + ((P('lua_State') * fill * P('*') * fill) * Cc('lstate'))
-  + C(opt(P('const ')) * c_id * rep1(ws) * rep1(P('*')))
-  + (C(c_id) * rep1(ws))
+local api_param_type = (
+  (word('Error') * opt_ptr * Cc('error'))
+  + (word('Arena') * opt_ptr * Cc('arena'))
+  + (word('lua_State') * opt_ptr * Cc('lstate'))
 )
 
-local c_type = (C(c_void) * (ws ^ 1)) + c_param_type
-local c_param = Ct(c_param_type * C(c_id))
-local c_param_list = c_param * (fill * (P(',') * fill * c_param) ^ 0)
-local c_params = Ct(c_void + c_param_list)
+local ctype = C(
+  opt(word('const'))
+    * (
+      typed_container
+      -- 'unsigned' is a type modifier, and a type itself
+      + (word('unsigned char') + word('unsigned'))
+      + (word('struct') * fill * id)
+      + id
+    )
+    * opt(word('const'))
+    * opt_ptr
+)
 
-local impl_line = (any - P('}')) * opt(rep(not_nl)) * nl
+local return_type = (C(void) * fill) + ctype
 
-local ignore_line = rep1(not_nl) * nl
+-- stylua: ignore start
+local params = Ct(
+  (void * #P(')'))
+  + comma1(Ct(
+      (api_param_type + ctype)
+      * fill
+      * C(id)
+      * rep(Pf('[') * rep(alpha) * Pf(']'))
+      * rep(fill * fattr)
+    ))
+    * opt(Pf(',') * P('...'))
+)
+-- stylua: ignore end
 
+local ignore_line = rep1(1 - nl) * nl
 local empty_line = Ct(Cc('empty') * nl * nl)
 
-local c_proto = Ct(
-  Cc('proto')
-    * opt(dllexport)
-    * opt(Cg(P('static') * fill * Cc(true), 'static'))
-    * Cg(c_type, 'return_type')
-    * Cg(c_id, 'name')
-    * fill
-    * (P('(') * fill * Cg(c_params, 'parameters') * fill * P(')'))
-    * Cg(Cc(false), 'fast')
-    * (fill * Cg((P('FUNC_API_SINCE(') * C(rep1(num))) * P(')'), 'since') ^ -1)
-    * (fill * Cg((P('FUNC_API_DEPRECATED_SINCE(') * C(rep1(num))) * P(')'), 'deprecated_since') ^ -1)
-    * (fill * Cg((P('FUNC_API_FAST') * Cc(true)), 'fast') ^ -1)
-    * (fill * Cg((P('FUNC_API_RET_ALLOC') * Cc(true)), 'ret_alloc') ^ -1)
-    * (fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1)
-    * (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1)
-    * (fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1)
-    * (fill * (Cg(P('FUNC_API_TEXTLOCK_ALLOW_CMDWIN') * Cc(true), 'textlock_allow_cmdwin') + Cg(
-      P('FUNC_API_TEXTLOCK') * Cc(true),
-      'textlock'
-    )) ^ -1)
-    * (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1)
-    * (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1)
-    * (fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1)
-    * (fill * Cg((P('FUNC_API_CLIENT_IGNORE') * Cc(true)), 'client_ignore') ^ -1)
-    * fill
-    * (P(';') + (P('{') * nl + (impl_line ^ 0) * P('}')))
+local proto_name = opt_ptr * fill * id
+
+-- __inline is used in MSVC
+local decl_mod = (
+  Cg(word('static') * Cc(true), 'static')
+  + Cg((word('inline') + word('__inline')) * Cc(true), 'inline')
 )
 
-local dict_key = P('DictKey(') * Cg(rep1(any - P(')')), 'dict_key') * P(')')
-local keyset_field =
-  Ct(Cg(c_id, 'type') * ws * Cg(c_id, 'name') * fill * (dict_key ^ -1) * fill * P(';') * fill)
-local c_keyset = Ct(
-  P('typedef')
-    * ws
-    * P('struct')
+local proto = Ct(
+  Cg(Cp(), 'pos')
+    * Cc('proto')
+    * -#P('typedef')
+    * #alpha
+    * opt(P('DLLEXPORT') * rep1(ws))
+    * rep(decl_mod)
+    * Cg(return_type, 'return_type')
     * fill
-    * P('{')
+    * Cg(proto_name, 'name')
     * fill
-    * Cg(Ct(keyset_field ^ 1), 'fields')
-    * P('}')
-    * fill
-    * P('Dict')
+    * paren(Cg(params, 'parameters'))
+    * Cg(Cc(false), 'fast')
+    * rep(fill * fattr)
+    * Cg(Cp(), 'endpos')
+    * (fill * (S(';') + braces))
+)
+
+local keyset_field = Ct(
+  Cg(ctype, 'type')
     * fill
-    * P('(')
-    * Cg(c_id, 'keyset_name')
+    * Cg(id, 'name')
     * fill
-    * P(')')
-    * P(';')
+    * opt(P('DictKey') * paren(Cg(rep1(1 - P(')')), 'dict_key')))
+    * Pf(';')
 )
 
-local grammar = Ct(
-  rep1(empty_line + c_proto + cdoc_comment + c_comment + c_preproc + ws + c_keyset + ignore_line)
+local keyset = Ct(
+  P('typedef')
+    * word('struct')
+    * Pf('{')
+    * Cg(Ct(rep1(keyset_field)), 'fields')
+    * Pf('}')
+    * P('Dict')
+    * paren(Cg(id, 'keyset_name'))
+    * Pf(';')
 )
-return { grammar = grammar, typed_container = typed_container }
+
+local grammar =
+  Ct(rep1(empty_line + proto + cdoc_comment + comment + preproc + ws + keyset + ignore_line))
+
+if arg[1] == '--test' then
+  for i, t in ipairs({
+    'void multiqueue_put_event(MultiQueue *self, Event event) {} ',
+    'void *xmalloc(size_t size) {} ',
+    {
+      'struct tm *os_localtime_r(const time_t *restrict clock,',
+      '                          struct tm *restrict result) FUNC_ATTR_NONNULL_ALL {}',
+    },
+    {
+      '_Bool',
+      '# 163 "src/nvim/event/multiqueue.c"',
+      '    multiqueue_empty(MultiQueue *self)',
+      '{}',
+    },
+    'const char *find_option_end(const char *arg, OptIndex *opt_idxp) {}',
+    'bool semsg(const char *const fmt, ...) {}',
+    'int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len) {}',
+    'void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) {}',
+    'static TermKeySym register_c0(TermKey *tk, TermKeySym sym, unsigned char ctrl, const char *name) {}',
+    'unsigned get_bkc_flags(buf_T *buf) {}',
+    'char *xstpcpy(char *restrict dst, const char *restrict src) {}',
+    'bool try_leave(const TryState *const tstate, Error *const err) {}',
+    'void api_set_error(ErrorType errType) {}',
+
+    -- Do not consume leading preproc statements
+    {
+      '#line 1 "D:/a/neovim/neovim/src\\nvim/mark.h"',
+      'static __inline int mark_global_index(const char name)',
+      '  FUNC_ATTR_CONST',
+      '{}',
+    },
+    {
+      '',
+      '#line 1 "D:/a/neovim/neovim/src\\nvim/mark.h"',
+      'static __inline int mark_global_index(const char name)',
+      '{}',
+    },
+    {
+      'size_t xstrlcpy(char *__restrict dst, const char *__restrict src, size_t dsize)',
+      ' FUNC_ATTR_NONNULL_ALL',
+      ' {}',
+    },
+  }) do
+    if type(t) == 'table' then
+      t = table.concat(t, '\n') .. '\n'
+    end
+    t = t:gsub(' +', ' ')
+    local r = grammar:match(t)
+    if not r then
+      print('Test ' .. i .. ' failed')
+      print('    |' .. table.concat(vim.split(t, '\n'), '\n    |'))
+    end
+  end
+end
+
+return {
+  grammar = grammar --[[@as nvim.c_grammar]],
+  typed_container = typed_container,
+}
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index 30a83330eb..a3bb76cb91 100644
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -98,7 +98,7 @@ local function call_ui_event_method(output, ev)
 end
 
 events = vim.tbl_filter(function(ev)
-  return ev[1] ~= 'empty'
+  return ev[1] ~= 'empty' and ev[1] ~= 'preproc'
 end, events)
 
 for i = 1, #events do
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index 2ec0e9ab68..6e1ea92572 100644
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -1,136 +1,105 @@
-local fname = arg[1]
-local static_fname = arg[2]
-local non_static_fname = arg[3]
-local preproc_fname = arg[4]
-local static_basename = arg[5]
-
-local lpeg = vim.lpeg
-
-local fold = function(func, ...)
-  local result = nil
-  for _, v in ipairs({ ... }) do
-    if result == nil then
-      result = v
-    else
-      result = func(result, v)
+local grammar = require('generators.c_grammar').grammar
+
+--- @param fname string
+--- @return string?
+local function read_file(fname)
+  local f = io.open(fname, 'r')
+  if not f then
+    return
+  end
+  local contents = f:read('*a')
+  f:close()
+  return contents
+end
+
+--- @param fname string
+--- @param contents string[]
+local function write_file(fname, contents)
+  local contents_s = table.concat(contents, '\n') .. '\n'
+  local fcontents = read_file(fname)
+  if fcontents == contents_s then
+    return
+  end
+  local f = assert(io.open(fname, 'w'))
+  f:write(contents_s)
+  f:close()
+end
+
+--- @param fname string
+--- @param non_static_fname string
+--- @return string? non_static
+local function add_iwyu_non_static(fname, non_static_fname)
+  if fname:find('.*/src/nvim/.*%.c$') then
+    -- Add an IWYU pragma comment if the corresponding .h file exists.
+    local header_fname = fname:sub(1, -3) .. '.h'
+    local header_f = io.open(header_fname, 'r')
+    if header_f then
+      header_f:close()
+      return (header_fname:gsub('.*/src/nvim/', 'nvim/'))
     end
+  elseif non_static_fname:find('/include/api/private/dispatch_wrappers%.h%.generated%.h$') then
+    return 'nvim/api/private/dispatch.h'
+  elseif non_static_fname:find('/include/ui_events_call%.h%.generated%.h$') then
+    return 'nvim/ui.h'
+  elseif non_static_fname:find('/include/ui_events_client%.h%.generated%.h$') then
+    return 'nvim/ui_client.h'
+  elseif non_static_fname:find('/include/ui_events_remote%.h%.generated%.h$') then
+    return 'nvim/api/ui.h'
   end
-  return result
 end
 
-local folder = function(func)
-  return function(...)
-    return fold(func, ...)
+--- @param d string
+local function process_decl(d)
+  -- Comments are really handled by preprocessor, so the following is not
+  -- needed
+  d = d:gsub('/%*.-%*/', '')
+  d = d:gsub('//.-\n', '\n')
+  d = d:gsub('# .-\n', '')
+  d = d:gsub('\n', ' ')
+  d = d:gsub('%s+', ' ')
+  d = d:gsub(' ?%( ?', '(')
+  d = d:gsub(' ?, ?', ', ')
+  d = d:gsub(' ?(%*+) ?', ' %1')
+  d = d:gsub(' ?(FUNC_ATTR_)', ' %1')
+  d = d:gsub(' $', '')
+  d = d:gsub('^ ', '')
+  return d .. ';'
+end
+
+--- @param fname string
+--- @param text string
+--- @return string[] static
+--- @return string[] non_static
+--- @return boolean any_static
+local function gen_declarations(fname, text)
+  local non_static = {} --- @type string[]
+  local static = {} --- @type string[]
+
+  local neededfile = fname:match('[^/]+$')
+  local curfile = nil
+  local any_static = false
+  for _, node in ipairs(grammar:match(text)) do
+    if node[1] == 'preproc' then
+      curfile = node.content:match('^%a* %d+ "[^"]-/?([^"/]+)"') or curfile
+    elseif node[1] == 'proto' and curfile == neededfile then
+      local node_text = text:sub(node.pos, node.endpos - 1)
+      local declaration = process_decl(node_text)
+
+      if node.static then
+        if not any_static and declaration:find('FUNC_ATTR_') then
+          any_static = true
+        end
+        static[#static + 1] = declaration
+      else
+        non_static[#non_static + 1] = 'DLLEXPORT ' .. declaration
+      end
+    end
   end
-end
 
-local lit = lpeg.P
-local set = function(...)
-  return lpeg.S(fold(function(a, b)
-    return a .. b
-  end, ...))
-end
-local any_character = lpeg.P(1)
-local rng = function(s, e)
-  return lpeg.R(s .. e)
-end
-local concat = folder(function(a, b)
-  return a * b
-end)
-local branch = folder(function(a, b)
-  return a + b
-end)
-local one_or_more = function(v)
-  return v ^ 1
-end
-local two_or_more = function(v)
-  return v ^ 2
-end
-local any_amount = function(v)
-  return v ^ 0
-end
-local one_or_no = function(v)
-  return v ^ -1
-end
-local look_behind = lpeg.B
-local look_ahead = function(v)
-  return #v
-end
-local neg_look_ahead = function(v)
-  return -v
-end
-local neg_look_behind = function(v)
-  return -look_behind(v)
+  return static, non_static, any_static
 end
 
-local w = branch(rng('a', 'z'), rng('A', 'Z'), lit('_'))
-local aw = branch(w, rng('0', '9'))
-local s = set(' ', '\n', '\t')
-local raw_word = concat(w, any_amount(aw))
-local right_word = concat(raw_word, neg_look_ahead(aw))
-local word = branch(
-  concat(
-    branch(lit('ArrayOf('), lit('DictOf('), lit('Dict(')), -- typed container macro
-    one_or_more(any_character - lit(')')),
-    lit(')')
-  ),
-  concat(neg_look_behind(aw), right_word)
-)
-local inline_comment =
-  concat(lit('/*'), any_amount(concat(neg_look_ahead(lit('*/')), any_character)), lit('*/'))
-local spaces = any_amount(branch(
-  s,
-  -- Comments are really handled by preprocessor, so the following is not needed
-  inline_comment,
-  concat(lit('//'), any_amount(concat(neg_look_ahead(lit('\n')), any_character)), lit('\n')),
-  -- Linemarker inserted by preprocessor
-  concat(lit('# '), any_amount(concat(neg_look_ahead(lit('\n')), any_character)), lit('\n'))
-))
-local typ_part = concat(word, any_amount(concat(spaces, lit('*'))), spaces)
-
-local typ_id = two_or_more(typ_part)
-local arg = typ_id -- argument name is swallowed by typ
-local pattern = concat(
-  any_amount(branch(set(' ', '\t'), inline_comment)),
-  typ_id, -- return type with function name
-  spaces,
-  lit('('),
-  spaces,
-  one_or_no(branch( -- function arguments
-    concat(
-      arg, -- first argument, does not require comma
-      any_amount(concat( -- following arguments, start with a comma
-        spaces,
-        lit(','),
-        spaces,
-        arg,
-        any_amount(concat(lit('['), spaces, any_amount(aw), spaces, lit(']')))
-      )),
-      one_or_no(concat(spaces, lit(','), spaces, lit('...')))
-    ),
-    lit('void') -- also accepts just void
-  )),
-  spaces,
-  lit(')'),
-  any_amount(concat( -- optional attributes
-    spaces,
-    lit('FUNC_'),
-    any_amount(aw),
-    one_or_no(concat( -- attribute argument
-      spaces,
-      lit('('),
-      any_amount(concat(neg_look_ahead(lit(')')), any_character)),
-      lit(')')
-    ))
-  )),
-  look_ahead(concat( -- definition must be followed by "{"
-    spaces,
-    lit('{')
-  ))
-)
-
-if fname == '--help' then
-  print([[
+local usage = [[
 Usage:
 
     gen_declarations.lua definitions.c static.h non-static.h definitions.i
@@ -141,204 +110,77 @@ non-static.h. File `definitions.i' should contain an already preprocessed
 version of definitions.c and it is the only one which is actually parsed,
 definitions.c is needed only to determine functions from which file out of all
 functions found in definitions.i are needed and to generate an IWYU comment.
-
-Additionally uses the following environment variables:
-
-    NVIM_GEN_DECLARATIONS_LINE_NUMBERS:
-        If set to 1 then all generated declarations receive a comment with file
-        name and line number after the declaration. This may be useful for
-        debugging gen_declarations script, but not much beyond that with
-        configured development environment (i.e. with with clang/etc).
-
-        WARNING: setting this to 1 will cause extensive rebuilds: declarations
-                 generator script will not regenerate non-static.h file if its
-                 contents did not change, but including line numbers will make
-                 contents actually change.
-
-                 With contents changed timestamp of the file is regenerated even
-                 when no real changes were made (e.g. a few lines were added to
-                 a function which is not at the bottom of the file).
-
-                 With changed timestamp build system will assume that header
-                 changed, triggering rebuilds of all C files which depend on the
-                 "changed" header.
-]])
-  os.exit()
-end
-
-local preproc_f = io.open(preproc_fname)
-local text = preproc_f:read('*all')
-preproc_f:close()
-
-local non_static = [[
-#define DEFINE_FUNC_ATTRIBUTES
-#include "nvim/func_attr.h"
-#undef DEFINE_FUNC_ATTRIBUTES
-#ifndef DLLEXPORT
-#  ifdef MSWIN
-#    define DLLEXPORT __declspec(dllexport)
-#  else
-#    define DLLEXPORT
-#  endif
-#endif
-]]
-
-local static = [[
-#define DEFINE_FUNC_ATTRIBUTES
-#include "nvim/func_attr.h"
-#undef DEFINE_FUNC_ATTRIBUTES
 ]]
 
-local non_static_footer = [[
-#include "nvim/func_attr.h"
-]]
+local function main()
+  local fname = arg[1]
+  local static_fname = arg[2]
+  local non_static_fname = arg[3]
+  local preproc_fname = arg[4]
+  local static_basename = arg[5]
 
-local static_footer = [[
-#define DEFINE_EMPTY_ATTRIBUTES
-#include "nvim/func_attr.h"  // IWYU pragma: export
-]]
-
-if fname:find('.*/src/nvim/.*%.c$') then
-  -- Add an IWYU pragma comment if the corresponding .h file exists.
-  local header_fname = fname:sub(1, -3) .. '.h'
-  local header_f = io.open(header_fname, 'r')
-  if header_f ~= nil then
-    header_f:close()
-    non_static = ([[
-// IWYU pragma: private, include "%s"
-]]):format(header_fname:gsub('.*/src/nvim/', 'nvim/')) .. non_static
+  if fname == '--help' or #arg < 5 then
+    print(usage)
+    os.exit()
   end
-elseif fname:find('.*/src/nvim/.*%.h$') then
-  static = ([[
-// IWYU pragma: private, include "%s"
-]]):format(fname:gsub('.*/src/nvim/', 'nvim/')) .. static
-elseif non_static_fname:find('/include/api/private/dispatch_wrappers%.h%.generated%.h$') then
-  non_static = [[
-// IWYU pragma: private, include "nvim/api/private/dispatch.h"
-]] .. non_static
-elseif non_static_fname:find('/include/ui_events_call%.h%.generated%.h$') then
-  non_static = [[
-// IWYU pragma: private, include "nvim/ui.h"
-]] .. non_static
-elseif non_static_fname:find('/include/ui_events_client%.h%.generated%.h$') then
-  non_static = [[
-// IWYU pragma: private, include "nvim/ui_client.h"
-]] .. non_static
-elseif non_static_fname:find('/include/ui_events_remote%.h%.generated%.h$') then
-  non_static = [[
-// IWYU pragma: private, include "nvim/api/ui.h"
-]] .. non_static
-end
 
-local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"'
+  local text = assert(read_file(preproc_fname))
 
-local init = 1
-local curfile = nil
-local neededfile = fname:match('[^/]+$')
-local declline = 0
-local declendpos = 0
-local curdir = nil
-local is_needed_file = false
-local init_is_nl = true
-local any_static = false
-while init ~= nil do
-  if init_is_nl and text:sub(init, init) == '#' then
-    local line, dir, file = text:match(filepattern, init)
-    if file ~= nil then
-      curfile = file
-      is_needed_file = (curfile == neededfile)
-      declline = tonumber(line) - 1
-      curdir = dir:gsub('.*/src/nvim/', '')
-    else
-      declline = declline - 1
-    end
-  elseif init < declendpos then -- luacheck: ignore 542
-    -- Skipping over declaration
-  elseif is_needed_file then
-    s = init
-    local e = pattern:match(text, init)
-    if e ~= nil then
-      local declaration = text:sub(s, e - 1)
-      -- Comments are really handled by preprocessor, so the following is not
-      -- needed
-      declaration = declaration:gsub('/%*.-%*/', '')
-      declaration = declaration:gsub('//.-\n', '\n')
-
-      declaration = declaration:gsub('# .-\n', '')
+  local static_decls, non_static_decls, any_static = gen_declarations(fname, text)
 
-      declaration = declaration:gsub('\n', ' ')
-      declaration = declaration:gsub('%s+', ' ')
-      declaration = declaration:gsub(' ?%( ?', '(')
-      -- declaration = declaration:gsub(' ?%) ?', ')')
-      declaration = declaration:gsub(' ?, ?', ', ')
-      declaration = declaration:gsub(' ?(%*+) ?', ' %1')
-      declaration = declaration:gsub(' ?(FUNC_ATTR_)', ' %1')
-      declaration = declaration:gsub(' $', '')
-      declaration = declaration:gsub('^ ', '')
-      declaration = declaration .. ';'
-
-      if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then
-        declaration = declaration .. ('  // %s/%s:%u'):format(curdir, curfile, declline)
-      end
-      declaration = declaration .. '\n'
-      if declaration:sub(1, 6) == 'static' then
-        if declaration:find('FUNC_ATTR_') then
-          any_static = true
-        end
-        static = static .. declaration
-      else
-        declaration = 'DLLEXPORT ' .. declaration
-        non_static = non_static .. declaration
-      end
-      declendpos = e
-    end
-  end
-  init = text:find('[\n;}]', init)
-  if init == nil then
-    break
-  end
-  init_is_nl = text:sub(init, init) == '\n'
-  init = init + 1
-  if init_is_nl and is_needed_file then
-    declline = declline + 1
+  local static = {} --- @type string[]
+  if fname:find('.*/src/nvim/.*%.h$') then
+    static[#static + 1] = ('// IWYU pragma: private, include "%s"'):format(
+      fname:gsub('.*/src/nvim/', 'nvim/')
+    )
   end
-end
-
-non_static = non_static .. non_static_footer
-static = static .. static_footer
-
-local F
-F = io.open(static_fname, 'w')
-F:write(static)
-F:close()
-
-if any_static then
-  F = io.open(fname, 'r')
-  local orig_text = F:read('*a')
-  local pat = '\n#%s?include%s+"' .. static_basename .. '"\n'
-  local pat_comment = '\n#%s?include%s+"' .. static_basename .. '"%s*//'
-  if not string.find(orig_text, pat) and not string.find(orig_text, pat_comment) then
-    error('fail: missing include for ' .. static_basename .. ' in ' .. fname)
+  vim.list_extend(static, {
+    '#define DEFINE_FUNC_ATTRIBUTES',
+    '#include "nvim/func_attr.h"',
+    '#undef DEFINE_FUNC_ATTRIBUTES',
+  })
+  vim.list_extend(static, static_decls)
+  vim.list_extend(static, {
+    '#define DEFINE_EMPTY_ATTRIBUTES',
+    '#include "nvim/func_attr.h"  // IWYU pragma: export',
+    '',
+  })
+
+  write_file(static_fname, static)
+
+  if any_static then
+    local orig_text = assert(read_file(fname))
+    local pat = '\n#%s?include%s+"' .. static_basename .. '"\n'
+    local pat_comment = '\n#%s?include%s+"' .. static_basename .. '"%s*//'
+    if not orig_text:find(pat) and not orig_text:find(pat_comment) then
+      error(('fail: missing include for %s in %s'):format(static_basename, fname))
+    end
   end
-  F:close()
-end
 
-if non_static_fname == 'SKIP' then
-  return -- only want static declarations
-end
-
--- Before generating the non-static headers, check if the current file (if
--- exists) is different from the new one. If they are the same, we won't touch
--- the current version to avoid triggering an unnecessary rebuilds of modules
--- that depend on this one
-F = io.open(non_static_fname, 'r')
-if F ~= nil then
-  if F:read('*a') == non_static then
-    os.exit(0)
+  if non_static_fname ~= 'SKIP' then
+    local non_static = {} --- @type string[]
+    local iwyu_non_static = add_iwyu_non_static(fname, non_static_fname)
+    if iwyu_non_static then
+      non_static[#non_static + 1] = ('// IWYU pragma: private, include "%s"'):format(
+        iwyu_non_static
+      )
+    end
+    vim.list_extend(non_static, {
+      '#define DEFINE_FUNC_ATTRIBUTES',
+      '#include "nvim/func_attr.h"',
+      '#undef DEFINE_FUNC_ATTRIBUTES',
+      '#ifndef DLLEXPORT',
+      '#  ifdef MSWIN',
+      '#    define DLLEXPORT __declspec(dllexport)',
+      '#  else',
+      '#    define DLLEXPORT',
+      '#  endif',
+      '#endif',
+    })
+    vim.list_extend(non_static, non_static_decls)
+    non_static[#non_static + 1] = '#include "nvim/func_attr.h"'
+    write_file(non_static_fname, non_static)
   end
-  F:close()
 end
 
-F = io.open(non_static_fname, 'w')
-F:write(non_static)
-F:close()
+return main()
-- 
cgit 


From 2833925cfc688786759d6a980a1ad62b62d20570 Mon Sep 17 00:00:00 2001
From: Yochem van Rosmalen 
Date: Fri, 29 Nov 2024 19:07:08 +0100
Subject: docs(diagnostics): location list / quickfix example #31371

---
 runtime/doc/diagnostic.txt | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt
index 9ccc3102b6..eaa3681caa 100644
--- a/runtime/doc/diagnostic.txt
+++ b/runtime/doc/diagnostic.txt
@@ -163,6 +163,33 @@ show a sign for the highest severity diagnostic on a given line: >lua
     }
 <
 
+                                          *diagnostic-loclist-example*
+Whenever the |location-list| is opened, the following `show` handler will show
+the most recent diagnostics: >lua
+
+  vim.diagnostic.handlers.loclist = {
+    show = function(_, _, _, opts)
+      -- Generally don't want it to open on every update
+      opts.loclist.open = opts.loclist.open or false
+      local winid = vim.api.nvim_get_current_win()
+      vim.diagnostic.setloclist(opts.loclist)
+      vim.api.nvim_set_current_win(winid)
+    end
+  }
+<
+
+The handler accepts the same options as |vim.diagnostic.setloclist()| and can be
+configured using |vim.diagnostic.config()|: >lua
+
+  -- Open the location list on every diagnostic change (warnings/errors only).
+  vim.diagnostic.config({
+    loclist = {
+      open = true,
+      severity = { min = vim.diagnostic.severity.WARN },
+    }
+  })
+<
+
 ==============================================================================
 HIGHLIGHTS                                              *diagnostic-highlights*
 
-- 
cgit 


From fd865fbd9229ddd4856d8913b0e452deb0bba872 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sat, 30 Nov 2024 11:12:38 +0100
Subject: vim-patch:dd21c89: runtime(compiler): update eslint compiler

compact formatter is no longer distributed with eslint, so:

- switch to '--format stylish' in makeprg
- update 'errorformat' for the 'stylish' format output

fixes: vim/vim#16126
closes: vim/vim#16137

https://github.com/vim/vim/commit/dd21c8962680ba726ac1bf78ae106a4b6071450f

Co-authored-by: Romain Lafourcade 
---
 runtime/compiler/eslint.vim | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/runtime/compiler/eslint.vim b/runtime/compiler/eslint.vim
index db7a665991..0414817900 100644
--- a/runtime/compiler/eslint.vim
+++ b/runtime/compiler/eslint.vim
@@ -1,13 +1,12 @@
 " Vim compiler file
 " Compiler:    ESLint for JavaScript
 " Maintainer:  Romain Lafourcade 
-" Last Change: 2020 August 20
-"	       2024 Apr 03 by The Vim Project (removed :CompilerSet definition)
+" Last Change: 2024 Nov 30
 
 if exists("current_compiler")
   finish
 endif
 let current_compiler = "eslint"
 
-CompilerSet makeprg=npx\ eslint\ --format\ compact
-CompilerSet errorformat=%f:\ line\ %l\\,\ col\ %c\\,\ %m,%-G%.%#
+CompilerSet makeprg=npx\ eslint\ --format\ stylish
+CompilerSet errorformat=%-P%f,\%\\s%#%l:%c\ %#\ %trror\ \ %m,\%\\s%#%l:%c\ %#\ %tarning\ \ %m,\%-Q,\%-G%.%#,
-- 
cgit 


From 4426a326e2441326e280a0478f83128e09305806 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sat, 30 Nov 2024 11:17:59 +0100
Subject: vim-patch:9.1.0897: filetype: pyrex files are not detected

Problem:  filetype: pyrex files are not detected
Solution: detect '*.pxi' and '*.pyx+' as pyrex filetype
          (user202729)

References:
https://cython.readthedocs.io/en/latest/src/userguide/language_basics.html#cython-file-types
https://www.csse.canterbury.ac.nz/greg.ewing/python/Pyrex/version/Doc/Manual/using_with_c++.html

closes: vim/vim#16136

https://github.com/vim/vim/commit/aa16b30552f0c6a00dcc761e3954cd5266bf106a

Co-authored-by: user202729 <25191436+user202729@users.noreply.github.com>
---
 runtime/lua/vim/filetype.lua       | 2 ++
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index aa566973b6..5d771c30e9 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -962,7 +962,9 @@ local extension = {
   purs = 'purescript',
   arr = 'pyret',
   pxd = 'pyrex',
+  pxi = 'pyrex',
   pyx = 'pyrex',
+  ['pyx+'] = 'pyrex',
   pyw = 'python',
   py = 'python',
   pyi = 'python',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 58064ea412..06ac59de20 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -610,7 +610,7 @@ func s:GetFilenameChecks() abort
     \ 'purescript': ['file.purs'],
     \ 'pymanifest': ['MANIFEST.in'],
     \ 'pyret': ['file.arr'],
-    \ 'pyrex': ['file.pyx', 'file.pxd'],
+    \ 'pyrex': ['file.pyx', 'file.pxd', 'file.pxi', 'file.pyx+'],
     \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', '.python_history', '.jline-jython.history', 'file.ptl', 'file.pyi', 'SConstruct'],
     \ 'ql': ['file.ql', 'file.qll'],
     \ 'qml': ['file.qml', 'file.qbs'],
-- 
cgit 


From d512479115296a2df246ef7afbfa55f057fecae2 Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Thu, 28 Nov 2024 14:34:38 +0800
Subject: vim-patch:9.1.0891: building the completion list array is inefficient

Problem:  building the completion list array is inefficient
Solution: refactor and improve ins_compl_build_pum() func
          (glepnir)

current time complexity is O(n^2). I guess garray is not used here to save memory and avoid efficiency
is caused by heap memory allocation. A simple way is to add an extra pointer as a single linked list
to store the matching compl_T, and traverse this single linked list to generate compl_match_array.
The time complexity is O(n x m). The worst case is m=n, but we can still get a little improvement.
Because the if condition does not need to be run at one time. This should be a good solution for now.
Later we may be able to complete it in O(lgn) time. But this requires more reconstruction. So this is
the first step.

closes: #16125

https://github.com/vim/vim/commit/80b662009c0fe8f1728a3f3a2c8013b7eebf6745
---
 src/nvim/insexpand.c | 145 ++++++++++++++++++++++++++-------------------------
 1 file changed, 75 insertions(+), 70 deletions(-)

diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 154a142c74..6a45a06fb9 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -156,6 +156,7 @@ typedef struct compl_S compl_T;
 struct compl_S {
   compl_T *cp_next;
   compl_T *cp_prev;
+  compl_T *cp_match_next;        ///< matched next compl_T
   char *cp_str;                  ///< matched text
   char *(cp_text[CPT_COUNT]);    ///< text for the menu
   typval_T cp_user_data;
@@ -789,6 +790,7 @@ static inline void free_cptext(char *const *const cptext)
 /// @param[in]  cdir       match direction. If 0, use "compl_direction".
 /// @param[in]  flags_arg  match flags (cp_flags)
 /// @param[in]  adup       accept this match even if it is already present.
+/// @param[in]  user_hl    list of extra highlight attributes for abbr kind.
 ///
 /// If "cdir" is FORWARD, then the match is added after the current match.
 /// Otherwise, it is added before the current match.
@@ -798,7 +800,7 @@ static inline void free_cptext(char *const *const cptext)
 ///         returned in case of error.
 static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext,
                          const bool cptext_allocated, typval_T *user_data, const Direction cdir,
-                         int flags_arg, const bool adup, int *extra_hl)
+                         int flags_arg, const bool adup, const int *user_hl)
   FUNC_ATTR_NONNULL_ARG(1)
 {
   compl_T *match;
@@ -864,8 +866,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
     match->cp_fname = NULL;
   }
   match->cp_flags = flags;
-  match->cp_user_abbr_hlattr = extra_hl ? extra_hl[0] : -1;
-  match->cp_user_kind_hlattr = extra_hl ? extra_hl[1] : -1;
+  match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1;
+  match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1;
 
   if (cptext != NULL) {
     int i;
@@ -1173,7 +1175,7 @@ static int ins_compl_build_pum(void)
   unsigned cur_cot_flags = get_cot_flags();
   bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0;
   bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
-
+  compl_T *match_head = NULL, *match_tail = NULL;
   do {
     // When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
     // set the cp_score for later comparisons.
@@ -1186,6 +1188,12 @@ static int ins_compl_build_pum(void)
             || ins_compl_equal(comp, compl_leader, (size_t)lead_len)
             || (compl_fuzzy_match && comp->cp_score > 0))) {
       compl_match_arraysize++;
+      if (match_head == NULL) {
+        match_head = comp;
+      } else {
+        match_tail->cp_match_next = comp;
+      }
+      match_tail = comp;
     }
     comp = comp->cp_next;
   } while (comp != NULL && !is_first_match(comp));
@@ -1206,75 +1214,70 @@ static int ins_compl_build_pum(void)
   }
 
   compl_T *shown_compl = NULL;
-  bool did_find_shown_match = false;
+  bool did_find_shown_match = match_head == match_tail ? true : false;
   int cur = -1;
   int i = 0;
-  comp = compl_first_match;
-  do {
-    if (!match_at_original_text(comp)
-        && (compl_leader == NULL
-            || ins_compl_equal(comp, compl_leader, (size_t)lead_len)
-            || (compl_fuzzy_match && comp->cp_score > 0))) {
-      if (!shown_match_ok && !compl_fuzzy_match) {
-        if (comp == compl_shown_match || did_find_shown_match) {
-          // This item is the shown match or this is the
-          // first displayed item after the shown match.
+  comp = match_head;
+  while (comp != NULL) {
+    if (!shown_match_ok && !compl_fuzzy_match) {
+      if (comp == compl_shown_match || did_find_shown_match) {
+        // This item is the shown match or this is the
+        // first displayed item after the shown match.
+        compl_shown_match = comp;
+        did_find_shown_match = true;
+        shown_match_ok = true;
+      } else {
+        // Remember this displayed match for when the
+        // shown match is just below it.
+        shown_compl = comp;
+      }
+      cur = i;
+    } else if (compl_fuzzy_match) {
+      if (i == 0) {
+        shown_compl = comp;
+      }
+      // Update the maximum fuzzy score and the shown match
+      // if the current item's score is higher
+      if (comp->cp_score > max_fuzzy_score) {
+        did_find_shown_match = true;
+        max_fuzzy_score = comp->cp_score;
+        if (!compl_no_select) {
           compl_shown_match = comp;
-          did_find_shown_match = true;
-          shown_match_ok = true;
-        } else {
-          // Remember this displayed match for when the
-          // shown match is just below it.
-          shown_compl = comp;
-        }
-        cur = i;
-      } else if (compl_fuzzy_match) {
-        if (i == 0) {
-          shown_compl = comp;
-        }
-        // Update the maximum fuzzy score and the shown match
-        // if the current item's score is higher
-        if (comp->cp_score > max_fuzzy_score) {
-          did_find_shown_match = true;
-          max_fuzzy_score = comp->cp_score;
-          if (!compl_no_select) {
-            compl_shown_match = comp;
-          }
         }
+      }
 
-        if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) {
-          cur = i;
-          shown_match_ok = true;
-        }
+      if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) {
+        cur = i;
+        shown_match_ok = true;
+      }
 
-        // If there is no "no select" condition and the max fuzzy
-        // score is positive, or there is no completion leader or the
-        // leader length is zero, mark the shown match as valid and
-        // reset the current index.
-        if (!compl_no_select
-            && (max_fuzzy_score > 0
-                || (compl_leader == NULL || lead_len == 0))) {
-          if (match_at_original_text(compl_shown_match)) {
-            compl_shown_match = shown_compl;
-          }
+      // If there is no "no select" condition and the max fuzzy
+      // score is positive, or there is no completion leader or the
+      // leader length is zero, mark the shown match as valid and
+      // reset the current index.
+      if (!compl_no_select
+          && (max_fuzzy_score > 0
+              || (compl_leader == NULL || lead_len == 0))) {
+        if (match_at_original_text(compl_shown_match)) {
+          compl_shown_match = shown_compl;
         }
       }
+    }
 
-      if (comp->cp_text[CPT_ABBR] != NULL) {
-        compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR];
-      } else {
-        compl_match_array[i].pum_text = comp->cp_str;
-      }
-      compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND];
-      compl_match_array[i].pum_info = comp->cp_text[CPT_INFO];
-      compl_match_array[i].pum_score = comp->cp_score;
-      compl_match_array[i].pum_user_abbr_hlattr = comp->cp_user_abbr_hlattr;
-      compl_match_array[i].pum_user_kind_hlattr = comp->cp_user_kind_hlattr;
-      if (comp->cp_text[CPT_MENU] != NULL) {
-        compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU];
-      } else {
-        compl_match_array[i++].pum_extra = comp->cp_fname;
-      }
+    if (comp->cp_text[CPT_ABBR] != NULL) {
+      compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR];
+    } else {
+      compl_match_array[i].pum_text = comp->cp_str;
+    }
+    compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND];
+    compl_match_array[i].pum_info = comp->cp_text[CPT_INFO];
+    compl_match_array[i].pum_score = comp->cp_score;
+    compl_match_array[i].pum_user_abbr_hlattr = comp->cp_user_abbr_hlattr;
+    compl_match_array[i].pum_user_kind_hlattr = comp->cp_user_kind_hlattr;
+    if (comp->cp_text[CPT_MENU] != NULL) {
+      compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU];
+    } else {
+      compl_match_array[i++].pum_extra = comp->cp_fname;
     }
 
     if (comp == compl_shown_match && !compl_fuzzy_match) {
@@ -1293,8 +1296,10 @@ static int ins_compl_build_pum(void)
         shown_match_ok = true;
       }
     }
-    comp = comp->cp_next;
-  } while (comp != NULL && !is_first_match(comp));
+    compl_T *match_next = comp->cp_match_next;
+    comp->cp_match_next = NULL;
+    comp = match_next;
+  }
 
   if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) {
     for (i = 0; i < compl_match_arraysize; i++) {
@@ -2574,7 +2579,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
   char *(cptext[CPT_COUNT]);
   char *user_abbr_hlname = NULL;
   char *user_kind_hlname = NULL;
-  int extra_hl[2] = { -1, -1 };
+  int user_hl[2] = { -1, -1 };
   typval_T user_data;
 
   user_data.v_type = VAR_UNKNOWN;
@@ -2586,10 +2591,10 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
     cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true);
 
     user_abbr_hlname = tv_dict_get_string(tv->vval.v_dict, "abbr_hlgroup", false);
-    extra_hl[0] = get_user_highlight_attr(user_abbr_hlname);
+    user_hl[0] = get_user_highlight_attr(user_abbr_hlname);
 
     user_kind_hlname = tv_dict_get_string(tv->vval.v_dict, "kind_hlgroup", false);
-    extra_hl[1] = get_user_highlight_attr(user_kind_hlname);
+    user_hl[1] = get_user_highlight_attr(user_kind_hlname);
 
     tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data);
 
@@ -2612,7 +2617,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
     return FAIL;
   }
   int status = ins_compl_add((char *)word, -1, NULL, cptext, true,
-                             &user_data, dir, flags, dup, extra_hl);
+                             &user_data, dir, flags, dup, user_hl);
   if (status != OK) {
     tv_clear(&user_data);
   }
-- 
cgit 


From 1af1e918d30c062dfcb7d4ce2f4cfbca1d11e2ab Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Sat, 30 Nov 2024 18:24:31 +0800
Subject: vim-patch:9.1.0896: completion list wrong after v9.1.0891

Problem:  completion list wrong after v9.1.0891
Solution: update compl_mach_array after leader change
          (glepnir)

compl_shown_match update not correct after refactoring in v9.1.0891
Unfortunately, this regressed what item is selected after leader change.

So generate compl_match_array before updating compl_shown_match range,
and split generate compl_match_array into range match_head

fixes: https://github.com/vim/vim/issues/16128
closes: https://github.com/vim/vim/pull/16129

https://github.com/vim/vim/commit/a49c077a883b2566882df9069385ed1e1277ca64
---
 src/nvim/insexpand.c            | 136 ++++++++++++++++++----------------------
 test/old/testdir/test_popup.vim |  25 ++++++++
 2 files changed, 87 insertions(+), 74 deletions(-)

diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 6a45a06fb9..820647df97 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1176,6 +1176,20 @@ static int ins_compl_build_pum(void)
   bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0;
   bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
   compl_T *match_head = NULL, *match_tail = NULL;
+
+  // If the current match is the original text don't find the first
+  // match after it, don't highlight anything.
+  bool shown_match_ok = match_at_original_text(compl_shown_match);
+
+  if (strequal(compl_leader, compl_orig_text) && !shown_match_ok) {
+    compl_shown_match = compl_no_select ? compl_first_match : compl_first_match->cp_next;
+  }
+
+  bool did_find_shown_match = false;
+  compl_T *shown_compl = NULL;
+  int i = 0;
+  int cur = -1;
+
   do {
     // When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
     // set the cp_score for later comparisons.
@@ -1194,6 +1208,53 @@ static int ins_compl_build_pum(void)
         match_tail->cp_match_next = comp;
       }
       match_tail = comp;
+      if (!shown_match_ok && !compl_fuzzy_match) {
+        if (comp == compl_shown_match || did_find_shown_match) {
+          // This item is the shown match or this is the
+          // first displayed item after the shown match.
+          compl_shown_match = comp;
+          did_find_shown_match = true;
+          shown_match_ok = true;
+        } else {
+          // Remember this displayed match for when the
+          // shown match is just below it.
+          shown_compl = comp;
+        }
+        cur = i;
+      } else if (compl_fuzzy_match) {
+        if (i == 0) {
+          shown_compl = comp;
+        }
+        // Update the maximum fuzzy score and the shown match
+        // if the current item's score is higher
+        if (comp->cp_score > max_fuzzy_score) {
+          did_find_shown_match = true;
+          max_fuzzy_score = comp->cp_score;
+          if (!compl_no_select) {
+            compl_shown_match = comp;
+          }
+        }
+        if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) {
+          cur = i;
+          shown_match_ok = true;
+        }
+      }
+      i++;
+    }
+
+    if (comp == compl_shown_match && !compl_fuzzy_match) {
+      did_find_shown_match = true;
+      // When the original text is the shown match don't set
+      // compl_shown_match.
+      if (match_at_original_text(comp)) {
+        shown_match_ok = true;
+      }
+      if (!shown_match_ok && shown_compl != NULL) {
+        // The shown match isn't displayed, set it to the
+        // previously displayed match.
+        compl_shown_match = shown_compl;
+        shown_match_ok = true;
+      }
     }
     comp = comp->cp_next;
   } while (comp != NULL && !is_first_match(comp));
@@ -1205,65 +1266,9 @@ static int ins_compl_build_pum(void)
   assert(compl_match_arraysize >= 0);
   compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T));
 
-  // If the current match is the original text don't find the first
-  // match after it, don't highlight anything.
-  bool shown_match_ok = match_at_original_text(compl_shown_match);
-
-  if (strequal(compl_leader, compl_orig_text) && !shown_match_ok) {
-    compl_shown_match = compl_no_select ? compl_first_match : compl_first_match->cp_next;
-  }
-
-  compl_T *shown_compl = NULL;
-  bool did_find_shown_match = match_head == match_tail ? true : false;
-  int cur = -1;
-  int i = 0;
+  i = 0;
   comp = match_head;
   while (comp != NULL) {
-    if (!shown_match_ok && !compl_fuzzy_match) {
-      if (comp == compl_shown_match || did_find_shown_match) {
-        // This item is the shown match or this is the
-        // first displayed item after the shown match.
-        compl_shown_match = comp;
-        did_find_shown_match = true;
-        shown_match_ok = true;
-      } else {
-        // Remember this displayed match for when the
-        // shown match is just below it.
-        shown_compl = comp;
-      }
-      cur = i;
-    } else if (compl_fuzzy_match) {
-      if (i == 0) {
-        shown_compl = comp;
-      }
-      // Update the maximum fuzzy score and the shown match
-      // if the current item's score is higher
-      if (comp->cp_score > max_fuzzy_score) {
-        did_find_shown_match = true;
-        max_fuzzy_score = comp->cp_score;
-        if (!compl_no_select) {
-          compl_shown_match = comp;
-        }
-      }
-
-      if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) {
-        cur = i;
-        shown_match_ok = true;
-      }
-
-      // If there is no "no select" condition and the max fuzzy
-      // score is positive, or there is no completion leader or the
-      // leader length is zero, mark the shown match as valid and
-      // reset the current index.
-      if (!compl_no_select
-          && (max_fuzzy_score > 0
-              || (compl_leader == NULL || lead_len == 0))) {
-        if (match_at_original_text(compl_shown_match)) {
-          compl_shown_match = shown_compl;
-        }
-      }
-    }
-
     if (comp->cp_text[CPT_ABBR] != NULL) {
       compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR];
     } else {
@@ -1279,23 +1284,6 @@ static int ins_compl_build_pum(void)
     } else {
       compl_match_array[i++].pum_extra = comp->cp_fname;
     }
-
-    if (comp == compl_shown_match && !compl_fuzzy_match) {
-      did_find_shown_match = true;
-
-      // When the original text is the shown match don't set
-      // compl_shown_match.
-      if (match_at_original_text(comp)) {
-        shown_match_ok = true;
-      }
-
-      if (!shown_match_ok && shown_compl != NULL) {
-        // The shown match isn't displayed, set it to the
-        // previously displayed match.
-        compl_shown_match = shown_compl;
-        shown_match_ok = true;
-      }
-    }
     compl_T *match_next = comp->cp_match_next;
     comp->cp_match_next = NULL;
     comp = match_next;
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index 601ba6c688..33e86678c8 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1675,4 +1675,29 @@ func Test_pum_completeitemalign()
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_pum_keep_select()
+  CheckScreendump
+  let lines =<< trim END
+    set completeopt=menu,menuone,noinsert
+  END
+  call writefile(lines, 'Xscript', 'D')
+  let buf = RunVimInTerminal('-S Xscript', {})
+  call TermWait(buf)
+
+  call term_sendkeys(buf, "ggSFab\Five\find\film\\\")
+  call TermWait(buf, 50)
+  call VerifyScreenDump(buf, 'Test_pum_keep_select_01', {})
+  call term_sendkeys(buf, "\\")
+  call TermWait(buf, 50)
+
+  call term_sendkeys(buf, "S\\")
+  call TermWait(buf, 50)
+  call term_sendkeys(buf, "F")
+  call VerifyScreenDump(buf, 'Test_pum_keep_select_02', {})
+  call term_sendkeys(buf, "\\")
+
+  call TermWait(buf, 50)
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From 9d174a7dace3004a069f474ed9c8ba4f3b97c5d8 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sat, 30 Nov 2024 12:08:46 +0100
Subject: vim-patch:9.1.0898: runtime(compiler): pytest compiler not included

Problem:  runtime(compiler): pytest compiler not included
Solution: include pytest compiler, update the compiler completion test
          (Konfekt)

closes: vim/vim#16130

https://github.com/vim/vim/commit/3c2596a9e967910143d41fbb9615614ab36d43a7

Co-authored-by: Konfekt 
---
 runtime/compiler/pytest.vim        | 103 +++++++++++++++++++++++++++++++++++++
 runtime/doc/quickfix.txt           |   7 +++
 runtime/ftplugin/python.vim        |  11 +++-
 test/old/testdir/test_compiler.vim |   4 +-
 4 files changed, 121 insertions(+), 4 deletions(-)
 create mode 100644 runtime/compiler/pytest.vim

diff --git a/runtime/compiler/pytest.vim b/runtime/compiler/pytest.vim
new file mode 100644
index 0000000000..7fc189932c
--- /dev/null
+++ b/runtime/compiler/pytest.vim
@@ -0,0 +1,103 @@
+" Vim compiler file
+" Compiler:     Pytest (Python testing framework)
+" Maintainer:   @Konfekt and @mgedmin
+" Last Change:  2024 Nov 28
+
+if exists("current_compiler") | finish | endif
+let current_compiler = "pytest"
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" CompilerSet makeprg=pytest
+if has('unix')
+  execute $'CompilerSet makeprg=/usr/bin/env\ PYTHONWARNINGS=ignore\ pytest\ {escape(get(b:, 'pytest_makeprg_params', get(g:, 'pytest_makeprg_params', '--tb=short --quiet')), ' \|"')}'
+elseif has('win32')
+  execute $'CompilerSet makeprg=set\ PYTHONWARNINGS=ignore\ &&\ pytest\ {escape(get(b:, 'pytest_makeprg_params', get(g:, 'pytest_makeprg_params', '--tb=short --quiet')), ' \|"')}'
+else
+  CompilerSet makeprg=pytest\ --tb=short\ --quiet
+  execute $'CompilerSet makeprg=pytest\ {escape(get(b:, 'pytest_makeprg_params', get(g:, 'pytest_makeprg_params', '--tb=short --quiet')), ' \|"')}'
+endif
+
+" Pytest syntax errors                                          {{{2
+
+" Reset error format so that sourcing .vimrc again and again doesn't grow it
+" without bounds
+setlocal errorformat&
+
+" For the record, the default errorformat is this:
+"
+"   %*[^"]"%f"%*\D%l: %m
+"   "%f"%*\D%l: %m
+"   %-G%f:%l: (Each undeclared identifier is reported only once
+"   %-G%f:%l: for each function it appears in.)
+"   %-GIn file included from %f:%l:%c:
+"   %-GIn file included from %f:%l:%c\,
+"   %-GIn file included from %f:%l:%c
+"   %-GIn file included from %f:%l
+"   %-G%*[ ]from %f:%l:%c
+"   %-G%*[ ]from %f:%l:
+"   %-G%*[ ]from %f:%l\,
+"   %-G%*[ ]from %f:%l
+"   %f:%l:%c:%m
+"   %f(%l):%m
+"   %f:%l:%m
+"   "%f"\, line %l%*\D%c%*[^ ] %m
+"   %D%*\a[%*\d]: Entering directory %*[`']%f'
+"   %X%*\a[%*\d]: Leaving directory %*[`']%f'
+"   %D%*\a: Entering directory %*[`']%f'
+"   %X%*\a: Leaving directory %*[`']%f'
+"   %DMaking %*\a in %f
+"   %f|%l| %m
+"
+" and sometimes it misfires, so let's fix it up a bit
+" (TBH I don't even know what compiler produces filename(lineno) so why even
+" have it?)
+setlocal errorformat-=%f(%l):%m
+
+" Sometimes Vim gets confused about ISO-8601 timestamps and thinks they're
+" filenames; this is a big hammer that ignores anything filename-like on lines
+" that start with at least two spaces, possibly preceded by a number and
+" optional punctuation
+setlocal errorformat^=%+G%\\d%#%.%\\=\ \ %.%#
+
+" Similar, but when the entire line starts with a date
+setlocal errorformat^=%+G\\d\\d\\d\\d-\\d\\d-\\d\\d\ \\d\\d:\\d\\d%.%#
+
+" make: *** [Makefile:14: target] Error 1
+setlocal errorformat^=%+Gmake:\ ***\ %.%#
+
+" FAILED tests.py::test_with_params[YYYY-MM-DD:HH:MM:SS] - Exception: bla bla
+setlocal errorformat^=%+GFAILED\ %.%#
+
+" AssertionError: assert ...YYYY-MM-DD:HH:MM:SS...
+setlocal errorformat^=%+GAssertionError:\ %.%#
+
+" --- /path/to/file:before  YYYY-MM-DD HH:MM:SS.ssssss
+setlocal errorformat^=---%f:%m
+
+" +++ /path/to/file:before  YYYY-MM-DD HH:MM:SS.ssssss
+setlocal errorformat^=+++%f:%m
+
+" Sometimes pytest prepends an 'E' marker at the beginning of a traceback line
+setlocal errorformat+=E\ %#File\ \"%f\"\\,\ line\ %l%.%#
+
+" Python tracebacks (unittest + doctest output)                 {{{2
+
+" This collapses the entire traceback into just the last file+lineno,
+" which is convenient when you want to jump to the line that failed (and not
+" the top-level entry point), but it makes it impossible to see the full
+" traceback, which sucks.
+""setlocal errorformat+=
+""            \File\ \"%f\"\\,\ line\ %l%.%#,
+""            \%C\ %.%#,
+""            \%-A\ \ File\ \"unittest%.py\"\\,\ line\ %.%#,
+""            \%-A\ \ File\ \"%f\"\\,\ line\ 0%.%#,
+""            \%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,
+""            \%Z%[%^\ ]%\\@=%m
+setlocal errorformat+=File\ \"%f\"\\,\ line\ %l\\,%#%m
+
+exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"')
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index 4811a51a87..a291c0277d 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1517,6 +1517,13 @@ Useful values for the 'makeprg' options therefore are:
  setlocal makeprg=./alltests.py " Run a testsuite
  setlocal makeprg=python\ %:S   " Run a single testcase
 
+PYTEST COMPILER						*compiler-pytest*
+Commonly used compiler options can be added to 'makeprg' by setting the
+b/g:pytest_makeprg_params variable.  For example: >
+
+  let b:pytest_makeprg_params = "--verbose --no-summary --disable-warnings"
+
+The global default is "--tb=short --quiet"; Python warnings are suppressed.
 
 TEX COMPILER						*compiler-tex*
 
diff --git a/runtime/ftplugin/python.vim b/runtime/ftplugin/python.vim
index c000296726..6f20468896 100644
--- a/runtime/ftplugin/python.vim
+++ b/runtime/ftplugin/python.vim
@@ -3,8 +3,9 @@
 " Maintainer:	Tom Picton 
 " Previous Maintainer: James Sully 
 " Previous Maintainer: Johannes Zellner 
-" Last Change:	2024/05/13
-" https://github.com/tpict/vim-ftplugin-python
+" Repository: https://github.com/tpict/vim-ftplugin-python
+" Last Change: 2024/05/13
+"              2024 Nov 30 use pytest compiler (#16130)
 
 if exists("b:did_ftplugin") | finish | endif
 let b:did_ftplugin = 1
@@ -134,6 +135,11 @@ elseif executable('python')
   setlocal keywordprg=python\ -m\ pydoc
 endif
 
+if expand('%:t') =~# '\v^test_.*\.py$|_test\.py$' && executable('pytest')
+  compiler pytest
+  let &l:makeprg .= ' %:S'
+endif
+
 " Script for filetype switching to undo the local stuff we may have changed
 let b:undo_ftplugin = 'setlocal cinkeys<'
       \ . '|setlocal comments<'
@@ -148,6 +154,7 @@ let b:undo_ftplugin = 'setlocal cinkeys<'
       \ . '|setlocal softtabstop<'
       \ . '|setlocal suffixesadd<'
       \ . '|setlocal tabstop<'
+      \ . '|setlocal makeprg<'
       \ . '|silent! nunmap  [M'
       \ . '|silent! nunmap  [['
       \ . '|silent! nunmap  []'
diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim
index 6b195b5efa..3ad6b365de 100644
--- a/test/old/testdir/test_compiler.vim
+++ b/test/old/testdir/test_compiler.vim
@@ -65,10 +65,10 @@ func Test_compiler_completion()
   call assert_match('^"compiler ' .. clist .. '$', @:)
 
   call feedkeys(":compiler p\\\"\", 'tx')
-  call assert_match('"compiler pandoc pbx perl\( p[a-z_]\+\)\+ pylint pyunit', @:)
+  call assert_match('"compiler pandoc pbx perl\( p[a-z_]\+\)\+ pyunit', @:)
 
   call feedkeys(":compiler! p\\\"\", 'tx')
-  call assert_match('"compiler! pandoc pbx perl\( p[a-z_]\+\)\+ pylint pyunit', @:)
+  call assert_match('"compiler! pandoc pbx perl\( p[a-z_]\+\)\+ pyunit', @:)
 endfunc
 
 func Test_compiler_error()
-- 
cgit 


From feb62d5429680278c1353c565db6bb3ecb3b7c24 Mon Sep 17 00:00:00 2001
From: "C.D. MacEachern" 
Date: Sun, 1 Dec 2024 16:58:28 -0500
Subject: docs: example keybind for :Inspect #31391

---
 runtime/doc/lua.txt            | 5 +++++
 runtime/lua/vim/_inspector.lua | 7 +++++++
 2 files changed, 12 insertions(+)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index c3dddaf888..047e263768 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1911,6 +1911,11 @@ vim.show_pos({bufnr}, {row}, {col}, {filter})                 *vim.show_pos()*
 
     Can also be shown with `:Inspect`.                              *:Inspect*
 
+    Example: To bind this function to the vim-scriptease inspired `zS` in
+    Normal mode: >lua
+        vim.keymap.set('n', 'zS', vim.show_pos)
+<
+
     Attributes: ~
         Since: 0.9.0
 
diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index fccf4b8dbe..f4e41a31e5 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -146,6 +146,13 @@ end
 ---
 ---Can also be shown with `:Inspect`. [:Inspect]()
 ---
+---Example: To bind this function to the vim-scriptease
+---inspired `zS` in Normal mode:
+---
+---```lua
+---vim.keymap.set('n', 'zS', vim.show_pos)
+---```
+---
 ---@since 11
 ---@param bufnr? integer defaults to the current buffer
 ---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
-- 
cgit 


From fb689d7ebd680c4921f4ec617fe5e01cd09ba96c Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 2 Dec 2024 08:06:57 +0800
Subject: vim-patch:9.1.0899: default for 'backspace' can be set in C code
 (#31416)

Problem:  default for 'backspace' can be set in C code
Solution: promote the default for 'backspace' from defaults.vim to the C
          code (Luca Saccarola)

closes: vim/vim#16143

https://github.com/vim/vim/commit/959ef61430bdd8fb982b38bd3347d90251255cfc

N/A patches:
vim-patch:9.1.0895: default history value is too small
vim-patch:075aeea: runtime(doc): document changed default value for 'history'

Co-authored-by: Luca Saccarola 
---
 runtime/doc/vim_diff.txt               | 1 -
 test/old/testdir/setup.vim             | 1 -
 test/old/testdir/test_autocmd.vim      | 4 ++--
 test/old/testdir/test_digraph.vim      | 3 +++
 test/old/testdir/test_ins_complete.vim | 2 +-
 test/old/testdir/test_options.vim      | 2 +-
 6 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index adc866af6b..6e1a9adb83 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -39,7 +39,6 @@ Defaults                                                    *nvim-defaults*
 - 'autoindent' is enabled
 - 'autoread' is enabled (works in all UIs, including terminal)
 - 'background' defaults to "dark" (unless set automatically by the terminal/UI)
-- 'backspace' defaults to "indent,eol,start"
 - 'backupdir' defaults to .,~/.local/state/nvim/backup// (|xdg|), auto-created
 - 'belloff' defaults to "all"
 - 'comments' includes "fb:•"
diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim
index e7b4bb1a88..b104d733f0 100644
--- a/test/old/testdir/setup.vim
+++ b/test/old/testdir/setup.vim
@@ -1,6 +1,5 @@
 if exists('s:did_load')
   " Align Nvim defaults to Vim.
-  set backspace=
   set commentstring=/*\ %s\ */
   set complete=.,w,b,u,t,i
   set define=^\\s*#\\s*define
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
index 64599c869a..40c09e61ac 100644
--- a/test/old/testdir/test_autocmd.vim
+++ b/test/old/testdir/test_autocmd.vim
@@ -1198,8 +1198,8 @@ func Test_OptionSet()
   call assert_equal(g:opt[0], g:opt[1])
 
   " 14: Setting option backspace through :let"
-  let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']]
-  let &bs = "eol,indent,start"
+  let g:options = [['backspace', 'indent,eol,start', 'indent,eol,start', 'indent,eol,start', '', 'global', 'set']]
+  let &bs = ''
   call assert_equal([], g:options)
   call assert_equal(g:opt[0], g:opt[1])
 
diff --git a/test/old/testdir/test_digraph.vim b/test/old/testdir/test_digraph.vim
index 8fbcd4d8ca..ce5e1b2055 100644
--- a/test/old/testdir/test_digraph.vim
+++ b/test/old/testdir/test_digraph.vim
@@ -250,9 +250,12 @@ func Test_digraphs_option()
   call Put_Dig_BS("P","=")
   call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.')))
   " Not a digraph: this is different from !
+  let _bs = &bs
+  set bs=
   call Put_Dig_BS("a","\")
   call Put_Dig_BS("\","a")
   call assert_equal(['','a'], getline(line('.')-1,line('.')))
+  let &bs = _bs
   " Grave
   call Put_Dig_BS("a","!")
   call Put_Dig_BS("!","e")
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
index c02aa1db62..6cc894c28c 100644
--- a/test/old/testdir/test_ins_complete.vim
+++ b/test/old/testdir/test_ins_complete.vim
@@ -1509,7 +1509,7 @@ func Test_complete_item_refresh_always()
   set completefunc=Tcomplete
   exe "normal! iup\\\\\\\"
   call assert_equal('up', getline(1))
-  call assert_equal(2, g:CallCount)
+  call assert_equal(6, g:CallCount)
   set completeopt&
   set completefunc&
   bw!
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
index c948846819..3773775564 100644
--- a/test/old/testdir/test_options.vim
+++ b/test/old/testdir/test_options.vim
@@ -496,7 +496,7 @@ func Test_set_completion_string_values()
   " but don't exhaustively validate their results.
   call assert_equal('single', getcompletion('set ambw=', 'cmdline')[0])
   call assert_match('light\|dark', getcompletion('set bg=', 'cmdline')[1])
-  call assert_equal('indent', getcompletion('set backspace=', 'cmdline')[0])
+  call assert_equal('indent,eol,start', getcompletion('set backspace=', 'cmdline')[0])
   call assert_equal('yes', getcompletion('set backupcopy=', 'cmdline')[1])
   call assert_equal('backspace', getcompletion('set belloff=', 'cmdline')[1])
   call assert_equal('min:', getcompletion('set briopt=', 'cmdline')[1])
-- 
cgit 


From 8de1dc6923396b46c327a31daa8a1562a196a255 Mon Sep 17 00:00:00 2001
From: Evgeni Chasnovski 
Date: Mon, 2 Dec 2024 02:24:58 +0200
Subject: fix(api): make `nvim_set_hl()` respect all `cterm` attributes
 (#31390)

---
 src/nvim/highlight.c                   | 3 +++
 test/functional/api/highlight_spec.lua | 9 +++++++++
 2 files changed, 12 insertions(+)

diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index b063a7708d..1a4b211f9c 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -1100,6 +1100,9 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
     CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC);
     CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE);
     CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL);
+    CHECK_FLAG(cterm, cterm_mask, underdouble, , HL_UNDERDOUBLE);
+    CHECK_FLAG(cterm, cterm_mask, underdotted, , HL_UNDERDOTTED);
+    CHECK_FLAG(cterm, cterm_mask, underdashed, , HL_UNDERDASHED);
     CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT);
     CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH);
     CHECK_FLAG(cterm, cterm_mask, altfont, , HL_ALTFONT);
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 1bbfe203b6..8f98ae3650 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -309,6 +309,15 @@ describe('API: set highlight', function()
     eq({ underdotted = true }, api.nvim_get_hl_by_name('Test_hl', true))
   end)
 
+  it('can set all underline cterm attributes #31385', function()
+    local ns = get_ns()
+    local attrs = { 'underline', 'undercurl', 'underdouble', 'underdotted', 'underdashed' }
+    for _, attr in ipairs(attrs) do
+      api.nvim_set_hl(ns, 'Test_' .. attr, { cterm = { [attr] = true } })
+      eq({ [attr] = true }, api.nvim_get_hl_by_name('Test_' .. attr, false))
+    end
+  end)
+
   it('can set a highlight in the global namespace', function()
     api.nvim_set_hl(0, 'Test_hl', highlight2_config)
     eq(
-- 
cgit 


From 6cdcac4492cc33b4360dfbb93fa2b04d9f771494 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 2 Dec 2024 10:05:49 +0800
Subject: fix(ui): clamp 'cmdheight' for other tabpages on screen resize
 (#31419)

---
 src/nvim/drawscreen.c                    | 20 ++++++++++--
 src/nvim/option.c                        |  4 +--
 src/nvim/window.c                        | 16 +++++-----
 test/functional/ui/screen_basic_spec.lua | 53 ++++++++++++++++++++++++++++++++
 4 files changed, 80 insertions(+), 13 deletions(-)

diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 835fdcf7d0..e03cffd1ca 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -290,9 +290,23 @@ void screen_resize(int width, int height)
   Rows = height;
   Columns = width;
   check_screensize();
-  int max_p_ch = Rows - min_rows() + 1;
-  if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) {
-    p_ch = max_p_ch ? max_p_ch : 1;
+  if (!ui_has(kUIMessages)) {
+    // clamp 'cmdheight'
+    int max_p_ch = Rows - min_rows(curtab) + 1;
+    if (p_ch > 0 && p_ch > max_p_ch) {
+      p_ch = MAX(max_p_ch, 1);
+      curtab->tp_ch_used = p_ch;
+    }
+    // clamp 'cmdheight' for other tab pages
+    FOR_ALL_TABS(tp) {
+      if (tp == curtab) {
+        continue;  // already set above
+      }
+      int max_tp_ch = Rows - min_rows(tp) + 1;
+      if (tp->tp_ch_used > 0 && tp->tp_ch_used > max_tp_ch) {
+        tp->tp_ch_used = MAX(max_tp_ch, 1);
+      }
+    }
   }
   height = Rows;
   width = Columns;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 6da9635479..fd0ac375a6 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1977,8 +1977,8 @@ static const char *did_set_cmdheight(optset_T *args)
 {
   OptInt old_value = args->os_oldval.number;
 
-  if (p_ch > Rows - min_rows() + 1) {
-    p_ch = Rows - min_rows() + 1;
+  if (p_ch > Rows - min_rows(curtab) + 1) {
+    p_ch = Rows - min_rows(curtab) + 1;
   }
 
   // if p_ch changed value, change the command line height
diff --git a/src/nvim/window.c b/src/nvim/window.c
index d92b2ab601..79c3ce9304 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -7065,17 +7065,17 @@ int last_stl_height(bool morewin)
 }
 
 /// Return the minimal number of rows that is needed on the screen to display
-/// the current number of windows.
-int min_rows(void)
+/// the current number of windows for the given tab page.
+int min_rows(tabpage_T *tp) FUNC_ATTR_NONNULL_ALL
 {
   if (firstwin == NULL) {       // not initialized yet
     return MIN_LINES;
   }
 
-  int total = frame_minheight(curtab->tp_topframe, NULL);
+  int total = frame_minheight(tp->tp_topframe, NULL);
   total += tabline_height() + global_stl_height();
-  if (p_ch > 0) {
-    total += 1;           // count the room for the command line
+  if ((tp == curtab ? p_ch : tp->tp_ch_used) > 0) {
+    total++;  // count the room for the command line
   }
   return total;
 }
@@ -7091,12 +7091,12 @@ int min_rows_for_all_tabpages(void)
   int total = 0;
   FOR_ALL_TABS(tp) {
     int n = frame_minheight(tp->tp_topframe, NULL);
+    if ((tp == curtab ? p_ch : tp->tp_ch_used) > 0) {
+      n++;  // count the room for the command line
+    }
     total = MAX(total, n);
   }
   total += tabline_height() + global_stl_height();
-  if (p_ch > 0) {
-    total += 1;           // count the room for the command line
-  }
   return total;
 }
 
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index f39e9ecc33..666d98c3b2 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -645,6 +645,59 @@ local function screen_tests(linegrid)
                             |
       ]])
     end)
+
+    it('clamps &cmdheight for current tabpage', function()
+      command('set cmdheight=10 laststatus=2')
+      screen:expect([[
+        ^                                                     |
+        {0:~                                                    }|*2
+        {1:[No Name]                                            }|
+                                                             |*10
+      ]])
+      screen:try_resize(53, 8)
+      screen:expect([[
+        ^                                                     |
+        {1:[No Name]                                            }|
+                                                             |*6
+      ]])
+      eq(6, api.nvim_get_option_value('cmdheight', {}))
+    end)
+
+    it('clamps &cmdheight for another tabpage #31380', function()
+      command('tabnew')
+      command('set cmdheight=9 laststatus=2')
+      screen:expect([[
+        {4: [No Name] }{2: [No Name] }{3:                              }{4:X}|
+        ^                                                     |
+        {0:~                                                    }|*2
+        {1:[No Name]                                            }|
+                                                             |*9
+      ]])
+      command('tabprev')
+      screen:expect([[
+        {2: [No Name] }{4: [No Name] }{3:                              }{4:X}|
+        ^                                                     |
+        {0:~                                                    }|*10
+        {1:[No Name]                                            }|
+                                                             |
+      ]])
+      screen:try_resize(53, 8)
+      screen:expect([[
+        {2: [No Name] }{4: [No Name] }{3:                              }{4:X}|
+        ^                                                     |
+        {0:~                                                    }|*4
+        {1:[No Name]                                            }|
+                                                             |
+      ]])
+      command('tabnext')
+      screen:expect([[
+        {4: [No Name] }{2: [No Name] }{3:                              }{4:X}|
+        ^                                                     |
+        {1:[No Name]                                            }|
+                                                             |*5
+      ]])
+      eq(5, api.nvim_get_option_value('cmdheight', {}))
+    end)
   end)
 
   describe('press enter', function()
-- 
cgit 


From 36b714e9b7fa2ab9e15d7a5ff029997363fedce6 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 1 Dec 2024 22:12:20 +0100
Subject: vim-patch:9a39483: runtime(typst): provide a formatlistpat in
 ftplugin

closes: vim/vim#16134

https://github.com/vim/vim/commit/9a39483adb418e37c672000a58792c0f0e8aa662

Co-authored-by: Luca Saccarola 
---
 runtime/ftplugin/typst.vim | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/runtime/ftplugin/typst.vim b/runtime/ftplugin/typst.vim
index 3841e427f3..09b65d0e52 100644
--- a/runtime/ftplugin/typst.vim
+++ b/runtime/ftplugin/typst.vim
@@ -1,7 +1,7 @@
 " Vim filetype plugin file
 " Language:    Typst
 " Maintainer:  Gregory Anders
-" Last Change: 2024 Oct 21
+" Last Change: 2024 Dev 01
 " Based on:    https://github.com/kaarmu/typst.vim
 
 if exists('b:did_ftplugin')
@@ -11,10 +11,12 @@ let b:did_ftplugin = 1
 
 setlocal commentstring=//\ %s
 setlocal comments=s1:/*,mb:*,ex:*/,://
-setlocal formatoptions+=croq
+setlocal formatoptions+=croqn
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal formatlistpat+=\\\|^\\s*[-+\]\\s\\+
 setlocal suffixesadd=.typ
 
-let b:undo_ftplugin = 'setl cms< com< fo< sua<'
+let b:undo_ftplugin = 'setl cms< com< fo< flp< sua<'
 
 if get(g:, 'typst_conceal', 0)
   setlocal conceallevel=2
-- 
cgit 


From c1378413c142e1762b80132c41b10c16c4aa7c13 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Sun, 1 Dec 2024 20:30:39 +0100
Subject: vim-patch:768728b: runtime(doc): Update documentation for "noselect"
 in 'completeopt'

In particular, make the distinction and interaction between "noinsert"
and "noselect" clearer as it was very confusing before.

closes: vim/vim#16148

https://github.com/vim/vim/commit/768728b48751c5e937409d12d98bfa1fb4c37266

Co-authored-by: dundargoc 
---
 runtime/doc/options.txt           | 6 +++---
 runtime/lua/vim/_meta/options.lua | 6 +++---
 src/nvim/options.lua              | 6 +++---
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index c5f21c64a2..9782846be5 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1559,9 +1559,9 @@ A jump table for the options with a short description can be found at |Q_op|.
 		    a match from the menu. Only works in combination with
 		    "menu" or "menuone". No effect if "longest" is present.
 
-	   noselect Do not select a match in the menu, force the user to
-		    select one from the menu. Only works in combination with
-		    "menu" or "menuone".
+	   noselect Same as "noinsert", except that no menu item is
+		    pre-selected. If both "noinsert" and "noselect" are present,
+		    "noselect" has precedence.
 
 	   fuzzy    Enable |fuzzy-matching| for completion candidates. This
 		    allows for more flexible and intuitive matching, where
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 247b464a70..01d5235d94 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -1086,9 +1086,9 @@ vim.go.cia = vim.go.completeitemalign
 --- 	    a match from the menu. Only works in combination with
 --- 	    "menu" or "menuone". No effect if "longest" is present.
 ---
----    noselect Do not select a match in the menu, force the user to
---- 	    select one from the menu. Only works in combination with
---- 	    "menu" or "menuone".
+---    noselect Same as "noinsert", except that no menu item is
+--- 	    pre-selected. If both "noinsert" and "noselect" are present,
+--- 	    "noselect" has precedence.
 ---
 ---    fuzzy    Enable `fuzzy-matching` for completion candidates. This
 --- 	    allows for more flexible and intuitive matching, where
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 84c90e44a7..97fe09f376 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1530,9 +1530,9 @@ return {
         	    a match from the menu. Only works in combination with
         	    "menu" or "menuone". No effect if "longest" is present.
 
-           noselect Do not select a match in the menu, force the user to
-        	    select one from the menu. Only works in combination with
-        	    "menu" or "menuone".
+           noselect Same as "noinsert", except that no menu item is
+        	    pre-selected. If both "noinsert" and "noselect" are present,
+        	    "noselect" has precedence.
 
            fuzzy    Enable |fuzzy-matching| for completion candidates. This
         	    allows for more flexible and intuitive matching, where
-- 
cgit 


From 716adbcc4563f5b4d1b7bc0301530296c538a33c Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Mon, 2 Dec 2024 04:16:44 -0800
Subject: fix(api): deprecate nvim_subscribe, nvim_unsubscribe #30456

Problem:
- nvim_subscribe, nvim_unsubscribe were deprecated in
  aec4938a21a02d279d13a9eb64ef3b7cc592c374 but this wasn't set in the
  API metadata.
- The function annotations
  ```
  FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY FUNC_API_DEPRECATED_SINCE(13)
  ```
  cause this test to fail:
   ```
   RUN T3 api metadata functions are compatible with old metadata or have new level: 3.00 ms ERR
   test/functional/api/version_spec.lua:135: function vim_subscribe was removed but exists in level 0 which nvim should be compatible with
   stack traceback:
     test/functional/api/version_spec.lua:135: in function 
   ```

Solution:
- Set the API metadata.
- Rearrange the annotations so that FUNC_API_DEPRECATED_SINCE is 2nd:
  ```
  FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY
  ```
---
 src/nvim/api/deprecated.c                |  6 ++++--
 src/nvim/generators/c_grammar.lua        |  5 +++++
 src/nvim/generators/gen_api_dispatch.lua | 16 ++++++++++------
 test/functional/api/version_spec.lua     | 16 ++++++++--------
 4 files changed, 27 insertions(+), 16 deletions(-)

diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 493856905f..51ed6eaa41 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -798,7 +798,8 @@ theend:
 /// @param channel_id Channel id (passed automatically by the dispatcher)
 /// @param event      Event type string
 void nvim_subscribe(uint64_t channel_id, String event)
-  FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
+// XXX: c_grammar.lua is order-sensitive.
+  FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY
 {
   // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions".
 }
@@ -808,7 +809,8 @@ void nvim_subscribe(uint64_t channel_id, String event)
 /// @param channel_id Channel id (passed automatically by the dispatcher)
 /// @param event      Event type string
 void nvim_unsubscribe(uint64_t channel_id, String event)
-  FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
+// XXX: c_grammar.lua is order-sensitive.
+  FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY
 {
   // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions".
 }
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index 9b1c284c1e..890c260843 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -257,6 +257,11 @@ if arg[1] == '--test' then
     'char *xstpcpy(char *restrict dst, const char *restrict src) {}',
     'bool try_leave(const TryState *const tstate, Error *const err) {}',
     'void api_set_error(ErrorType errType) {}',
+    {
+      'void nvim_subscribe(uint64_t channel_id, String event)',
+      'FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY',
+      '{}',
+    },
 
     -- Do not consume leading preproc statements
     {
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 402382acd2..c987037324 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -1,3 +1,10 @@
+-- Example (manual) invocation:
+--
+--    make
+--    cp build/nvim_version.lua src/nvim/
+--    cd src/nvim
+--    nvim -l generators/gen_api_dispatch.lua "../../build/src/nvim/auto/api/private/dispatch_wrappers.generated.h" "../../build/src/nvim/auto/api/private/api_metadata.generated.h" "../../build/funcs_metadata.mpack" "../../build/src/nvim/auto/lua_api_c_bindings.generated.h" "../../build/src/nvim/auto/keysets_defs.generated.h" "../../build/ui_metadata.mpack" "../../build/cmake.config/auto/versiondef_git.h" "./api/autocmd.h" "./api/buffer.h" "./api/command.h" "./api/deprecated.h" "./api/extmark.h" "./api/keysets_defs.h" "./api/options.h" "./api/tabpage.h" "./api/ui.h" "./api/vim.h" "./api/vimscript.h" "./api/win_config.h" "./api/window.h" "../../build/include/api/autocmd.h.generated.h" "../../build/include/api/buffer.h.generated.h" "../../build/include/api/command.h.generated.h" "../../build/include/api/deprecated.h.generated.h" "../../build/include/api/extmark.h.generated.h" "../../build/include/api/options.h.generated.h" "../../build/include/api/tabpage.h.generated.h" "../../build/include/api/ui.h.generated.h" "../../build/include/api/vim.h.generated.h" "../../build/include/api/vimscript.h.generated.h" "../../build/include/api/win_config.h.generated.h" "../../build/include/api/window.h.generated.h"
+
 local mpack = vim.mpack
 
 local hashy = require 'generators.hashy'
@@ -8,7 +15,7 @@ assert(#arg >= pre_args)
 local dispatch_outputf = arg[1]
 -- output h file with packed metadata (api_metadata.generated.h)
 local api_metadata_outputf = arg[2]
--- output metadata mpack file, for use by other build scripts (api_metadata.mpack)
+-- output metadata mpack file, for use by other build scripts (funcs_metadata.mpack)
 local mpack_outputf = arg[3]
 local lua_c_bindings_outputf = arg[4] -- lua_api_c_bindings.generated.c
 local keysets_outputf = arg[5] -- keysets_defs.generated.h
@@ -235,7 +242,7 @@ for x in string.gmatch(ui_options_text, '"([a-z][a-z_]+)"') do
   table.insert(ui_options, x)
 end
 
-local version = require 'nvim_version'
+local version = require 'nvim_version' -- `build/nvim_version.lua` file.
 local git_version = io.open(git_version_inputf):read '*a'
 local version_build = string.match(git_version, '#define NVIM_VERSION_BUILD "([^"]+)"') or vim.NIL
 
@@ -266,10 +273,7 @@ fixdict(1 + #version)
 for _, item in ipairs(version) do
   -- NB: all items are mandatory. But any error will be less confusing
   -- with placeholder vim.NIL (than invalid mpack data)
-  local val = item[2]
-  if val == nil then
-    val = vim.NIL
-  end
+  local val = item[2] == nil and vim.NIL or item[2]
   put(item[1], val)
 end
 put('build', version_build)
diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua
index 617786eb2d..72b5e307ee 100644
--- a/test/functional/api/version_spec.lua
+++ b/test/functional/api/version_spec.lua
@@ -93,28 +93,28 @@ describe('api metadata', function()
   local function clean_level_0(metadata)
     for _, f in ipairs(metadata.functions) do
       f.can_fail = nil
-      f.async = nil
+      f.async = nil -- XXX: renamed to "fast".
       f.receives_channel_id = nil
       f.since = 0
     end
   end
 
-  local api_info, compat, stable, api_level
+  local api_info --[[@type table]]
+  local compat --[[@type integer]]
+  local stable --[[@type integer]]
+  local api_level --[[@type integer]]
   local old_api = {}
   setup(function()
     clear() -- Ensure a session before requesting api_info.
+    --[[@type { version: {api_compatible: integer, api_level: integer, api_prerelease: boolean} }]]
     api_info = api.nvim_get_api_info()[2]
     compat = api_info.version.api_compatible
     api_level = api_info.version.api_level
-    if api_info.version.api_prerelease then
-      stable = api_level - 1
-    else
-      stable = api_level
-    end
+    stable = api_info.version.api_prerelease and api_level - 1 or api_level
 
     for level = compat, stable do
       local path = ('test/functional/fixtures/api_level_' .. tostring(level) .. '.mpack')
-      old_api[level] = read_mpack_file(path)
+      old_api[level] = read_mpack_file(path) --[[@type table]]
       if old_api[level] == nil then
         local errstr = 'missing metadata fixture for stable level ' .. level .. '. '
         if level == api_level and not api_info.version.api_prerelease then
-- 
cgit 


From 9d0117fd305d4a52552eca407dacb429132df82c Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Mon, 2 Dec 2024 15:08:26 +0100
Subject: test(treesitter): global highlight definitions and fold test #31407

Add test for foldtext= highlighting. Change file to global highlight
definitions while at it.
---
 test/functional/treesitter/highlight_spec.lua | 814 +++++++++++++-------------
 1 file changed, 401 insertions(+), 413 deletions(-)

diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index 60b1510603..f2563d9bad 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -65,40 +65,40 @@ static int nlua_schedule(lua_State *const lstate)
 }]]
 
 local hl_grid_legacy_c = [[
-  {2:^/// Schedule Lua callback on main loop's event queue}             |
-  {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate)                |
+  {18:^/// Schedule Lua callback on main loop's event queue}             |
+  {6:static} {6:int} nlua_schedule(lua_State *{6:const} lstate)                |
   {                                                                |
-    {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION                       |
+    {15:if} (lua_type(lstate, {26:1}) != LUA_TFUNCTION                       |
         || lstate != lstate) {                                     |
-      lua_pushliteral(lstate, {5:"vim.schedule: expected function"});  |
-      {4:return} lua_error(lstate);                                    |
+      lua_pushliteral(lstate, {26:"vim.schedule: expected function"});  |
+      {15:return} lua_error(lstate);                                    |
     }                                                              |
                                                                    |
-    LuaRef cb = nlua_ref(lstate, {5:1});                               |
+    LuaRef cb = nlua_ref(lstate, {26:1});                               |
                                                                    |
     multiqueue_put(main_loop.events, nlua_schedule_event,          |
-                   {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-    {4:return} {5:0};                                                      |
+                   {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+    {15:return} {26:0};                                                      |
   }                                                                |
   {1:~                                                                }|*2
                                                                    |
 ]]
 
 local hl_grid_ts_c = [[
-  {2:^/// Schedule Lua callback on main loop's event queue}             |
-  {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
+  {18:^/// Schedule Lua callback on main loop's event queue}             |
+  {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
   {                                                                |
-    {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION}                       |
-        || {6:lstate} != {6:lstate}) {                                     |
-      {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"});  |
-      {4:return} {11:lua_error}(lstate);                                    |
+    {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION}                       |
+        || {19:lstate} != {19:lstate}) {                                     |
+      {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"});  |
+      {15:return} {25:lua_error}(lstate);                                    |
     }                                                              |
                                                                    |
-    {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
+    {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
                                                                    |
-    multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                   {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-    {4:return} {5:0};                                                      |
+    multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                   {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+    {15:return} {26:0};                                                      |
   }                                                                |
   {1:~                                                                }|*2
                                                                    |
@@ -145,10 +145,10 @@ local injection_grid_c = [[
 ]]
 
 local injection_grid_expected_c = [[
-  {3:int} x = {5:INT_MAX};                                                 |
-  #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y))  |
-  #define foo {3:void} main() { \                                      |
-                {4:return} {5:42};  \                                      |
+  {6:int} x = {26:INT_MAX};                                                 |
+  #define {26:READ_STRING}(x, y) ({6:char} *)read_string((x), ({6:size_t})(y))  |
+  #define foo {6:void} main() { \                                      |
+                {15:return} {26:42};  \                                      |
               }                                                    |
   ^                                                                 |
   {1:~                                                                }|*11
@@ -161,20 +161,6 @@ describe('treesitter highlighting (C)', function()
   before_each(function()
     clear()
     screen = Screen.new(65, 18)
-    screen:set_default_attr_ids {
-      [1] = { bold = true, foreground = Screen.colors.Blue1 },
-      [2] = { foreground = Screen.colors.Blue1 },
-      [3] = { bold = true, foreground = Screen.colors.SeaGreen4 },
-      [4] = { bold = true, foreground = Screen.colors.Brown },
-      [5] = { foreground = Screen.colors.Magenta },
-      [6] = { foreground = Screen.colors.Red },
-      [7] = { bold = true, foreground = Screen.colors.SlateBlue },
-      [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
-      [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red },
-      [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red },
-      [11] = { foreground = Screen.colors.Cyan4 },
-    }
-
     command [[ hi link @error ErrorMsg ]]
     command [[ hi link @warning WarningMsg ]]
   end)
@@ -246,124 +232,124 @@ describe('treesitter highlighting (C)', function()
 
     feed('5Gocdd')
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:/// Schedule Lua callback on main loop's event queue}             |
-      {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
-      {                                                                |
-        {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION}                       |
-            || {6:lstate} != {6:lstate}) {                                     |
-          {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"});  |
-          {4:return} {11:lua_error}(lstate);                                    |
-        }                                                              |
-                                                                       |
-        {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
-                                                                       |
-        multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                       {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-        {4:return} {5:0};                                                      |
-      }                                                                |
-      {1:~                                                                }|*2
-                                                                       |
-    ]],
-    }
+        {18:/// Schedule Lua callback on main loop's event queue}             |
+        {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
+        {                                                                |
+          {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION}                       |
+              || {19:lstate} != {19:lstate}) {                                     |
+            {25:^lua_pushliteral}(lstate, {26:"vim.schedule: expected function"});  |
+            {15:return} {25:lua_error}(lstate);                                    |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        }                                                                |
+        {1:~                                                                }|*2
+                                                                         |
+      ]],
+    })
 
     feed('7Go*/')
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:/// Schedule Lua callback on main loop's event queue}             |
-      {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
-      {                                                                |
-        {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION}                       |
-            || {6:lstate} != {6:lstate}) {                                     |
-          {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"});  |
-          {4:return} {11:lua_error}(lstate);                                    |
-      {8:*^/}                                                               |
-        }                                                              |
-                                                                       |
-        {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
-                                                                       |
-        multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                       {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-        {4:return} {5:0};                                                      |
-      }                                                                |
-      {1:~                                                                }|
-                                                                       |
-    ]],
-    }
+        {18:/// Schedule Lua callback on main loop's event queue}             |
+        {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
+        {                                                                |
+          {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION}                       |
+              || {19:lstate} != {19:lstate}) {                                     |
+            {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"});  |
+            {15:return} {25:lua_error}(lstate);                                    |
+        {9:*^/}                                                               |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        }                                                                |
+        {1:~                                                                }|
+                                                                         |
+      ]],
+    })
 
     feed('3Go/*')
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:/// Schedule Lua callback on main loop's event queue}             |
-      {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
-      {                                                                |
-      {2:/^*}                                                               |
-      {2:  if (lua_type(lstate, 1) != LUA_TFUNCTION}                       |
-      {2:      || lstate != lstate) {}                                     |
-      {2:    lua_pushliteral(lstate, "vim.schedule: expected function");}  |
-      {2:    return lua_error(lstate);}                                    |
-      {2:*/}                                                               |
-        }                                                              |
-                                                                       |
-        {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
-                                                                       |
-        multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                       {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-        {4:return} {5:0};                                                      |
-      {8:}}                                                                |
-                                                                       |
-    ]],
-    }
+        {18:/// Schedule Lua callback on main loop's event queue}             |
+        {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
+        {                                                                |
+        {18:/^*}                                                               |
+        {18:  if (lua_type(lstate, 1) != LUA_TFUNCTION}                       |
+        {18:      || lstate != lstate) {}                                     |
+        {18:    lua_pushliteral(lstate, "vim.schedule: expected function");}  |
+        {18:    return lua_error(lstate);}                                    |
+        {18:*/}                                                               |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        {9:}}                                                                |
+                                                                         |
+      ]],
+    })
 
     feed('gg$')
     feed('~')
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:/// Schedule Lua callback on main loop's event queu^E}             |
-      {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
-      {                                                                |
-      {2:/*}                                                               |
-      {2:  if (lua_type(lstate, 1) != LUA_TFUNCTION}                       |
-      {2:      || lstate != lstate) {}                                     |
-      {2:    lua_pushliteral(lstate, "vim.schedule: expected function");}  |
-      {2:    return lua_error(lstate);}                                    |
-      {2:*/}                                                               |
-        }                                                              |
-                                                                       |
-        {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
-                                                                       |
-        multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                       {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-        {4:return} {5:0};                                                      |
-      {8:}}                                                                |
-                                                                       |
-    ]],
-    }
+        {18:/// Schedule Lua callback on main loop's event queu^E}             |
+        {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
+        {                                                                |
+        {18:/*}                                                               |
+        {18:  if (lua_type(lstate, 1) != LUA_TFUNCTION}                       |
+        {18:      || lstate != lstate) {}                                     |
+        {18:    lua_pushliteral(lstate, "vim.schedule: expected function");}  |
+        {18:    return lua_error(lstate);}                                    |
+        {18:*/}                                                               |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        {9:}}                                                                |
+                                                                         |
+      ]],
+    })
 
     feed('re')
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:/// Schedule Lua callback on main loop's event queu^e}             |
-      {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
-      {                                                                |
-      {2:/*}                                                               |
-      {2:  if (lua_type(lstate, 1) != LUA_TFUNCTION}                       |
-      {2:      || lstate != lstate) {}                                     |
-      {2:    lua_pushliteral(lstate, "vim.schedule: expected function");}  |
-      {2:    return lua_error(lstate);}                                    |
-      {2:*/}                                                               |
-        }                                                              |
-                                                                       |
-        {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
-                                                                       |
-        multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                       {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-        {4:return} {5:0};                                                      |
-      {8:}}                                                                |
-                                                                       |
-    ]],
-    }
+        {18:/// Schedule Lua callback on main loop's event queu^e}             |
+        {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
+        {                                                                |
+        {18:/*}                                                               |
+        {18:  if (lua_type(lstate, 1) != LUA_TFUNCTION}                       |
+        {18:      || lstate != lstate) {}                                     |
+        {18:    lua_pushliteral(lstate, "vim.schedule: expected function");}  |
+        {18:    return lua_error(lstate);}                                    |
+        {18:*/}                                                               |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        {9:}}                                                                |
+                                                                         |
+      ]],
+    })
   end)
 
   it('is updated with :sort', function()
@@ -372,83 +358,79 @@ describe('treesitter highlighting (C)', function()
       local parser = vim.treesitter.get_parser(0, 'c')
       vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } })
     end)
-    screen:expect {
+    screen:expect({
       grid = [[
-        {3:int} width = {5:INT_MAX}, height = {5:INT_MAX};                         |
-        {3:bool} ext_widgets[kUIExtCount];                                 |
-        {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) {           |
-          ext_widgets[i] = true;                                       |
-        }                                                              |
-                                                                       |
-        {3:bool} inclusive = ui_override();                                |
-        {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) {                        |
-          {3:UI} *ui = uis[i];                                             |
-          width = {5:MIN}(ui->width, width);                               |
-          height = {5:MIN}(ui->height, height);                            |
-          foo = {5:BAR}(ui->bazaar, bazaar);                               |
-          {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) {         |
-            ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
-          }                                                            |
-        }                                                              |
-      ^}                                                                |
-                                                                       |
-    ]],
-    }
+          {6:int} width = {26:INT_MAX}, height = {26:INT_MAX};                         |
+          {6:bool} ext_widgets[kUIExtCount];                                 |
+          {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) {           |
+            ext_widgets[i] = true;                                       |
+          }                                                              |
+                                                                         |
+          {6:bool} inclusive = ui_override();                                |
+          {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) {                        |
+            {6:UI} *ui = uis[i];                                             |
+            width = {26:MIN}(ui->width, width);                               |
+            height = {26:MIN}(ui->height, height);                            |
+            foo = {26:BAR}(ui->bazaar, bazaar);                               |
+            {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) {         |
+              ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
+            }                                                            |
+          }                                                              |
+        ^}                                                                |
+                                                                         |
+      ]],
+    })
 
     feed ':sort'
-    screen:expect {
+    screen:expect({
       grid = [[
-      ^                                                                 |
-            ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
-          {3:UI} *ui = uis[i];                                             |
-          ext_widgets[i] = true;                                       |
-          foo = {5:BAR}(ui->bazaar, bazaar);                               |
-          {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) {         |
-          height = {5:MIN}(ui->height, height);                            |
-          width = {5:MIN}(ui->width, width);                               |
-          }                                                            |
-        {3:bool} ext_widgets[kUIExtCount];                                 |
-        {3:bool} inclusive = ui_override();                                |
-        {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) {           |
-        {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) {                        |
-        {3:int} width = {5:INT_MAX}, height = {5:INT_MAX};                         |
-        }                                                              |*2
-      {3:void} ui_refresh({3:void})                                            |
-      :sort                                                            |
-    ]],
-    }
+        ^                                                                 |
+              ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
+            {6:UI} *ui = uis[i];                                             |
+            ext_widgets[i] = true;                                       |
+            foo = {26:BAR}(ui->bazaar, bazaar);                               |
+            {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) {         |
+            height = {26:MIN}(ui->height, height);                            |
+            width = {26:MIN}(ui->width, width);                               |
+            }                                                            |
+          {6:bool} ext_widgets[kUIExtCount];                                 |
+          {6:bool} inclusive = ui_override();                                |
+          {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) {           |
+          {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) {                        |
+          {6:int} width = {26:INT_MAX}, height = {26:INT_MAX};                         |
+          }                                                              |*2
+        {6:void} ui_refresh({6:void})                                            |
+        :sort                                                            |
+      ]],
+    })
 
     feed 'u'
 
-    screen:expect {
+    screen:expect({
       grid = [[
-        {3:int} width = {5:INT_MAX}, height = {5:INT_MAX};                         |
-        {3:bool} ext_widgets[kUIExtCount];                                 |
-        {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) {           |
-          ext_widgets[i] = true;                                       |
-        }                                                              |
-                                                                       |
-        {3:bool} inclusive = ui_override();                                |
-        {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) {                        |
-          {3:UI} *ui = uis[i];                                             |
-          width = {5:MIN}(ui->width, width);                               |
-          height = {5:MIN}(ui->height, height);                            |
-          foo = {5:BAR}(ui->bazaar, bazaar);                               |
-          {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) {         |
-            ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
-          }                                                            |
-        }                                                              |
-      ^}                                                                |
-      19 changes; before #2  {MATCH:.*}|
-    ]],
-    }
+          {6:int} width = {26:INT_MAX}, height = {26:INT_MAX};                         |
+          {6:bool} ext_widgets[kUIExtCount];                                 |
+          {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) {           |
+            ext_widgets[i] = true;                                       |
+          }                                                              |
+                                                                         |
+          {6:bool} inclusive = ui_override();                                |
+          {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) {                        |
+            {6:UI} *ui = uis[i];                                             |
+            width = {26:MIN}(ui->width, width);                               |
+            height = {26:MIN}(ui->height, height);                            |
+            foo = {26:BAR}(ui->bazaar, bazaar);                               |
+            {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) {         |
+              ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
+            }                                                            |
+          }                                                              |
+        ^}                                                                |
+        19 changes; before #2  0 seconds ago                             |
+      ]],
+    })
   end)
 
   it('supports with custom parser', function()
-    screen:set_default_attr_ids {
-      [1] = { bold = true, foreground = Screen.colors.SeaGreen4 },
-    }
-
     insert(test_text_c)
 
     screen:expect {
@@ -488,28 +470,28 @@ describe('treesitter highlighting (C)', function()
       vim.treesitter.highlighter.new(parser, { queries = { c = '(identifier) @type' } })
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX};                         |
-      bool {1:ext_widgets}[{1:kUIExtCount}];                                 |
-      for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) {           |
-        ext_widgets[i] = true;                                       |
-      }                                                              |
-                                                                     |
-      bool {1:inclusive} = {1:ui_override}();                                |
-      for (size_t {1:i} = 0; i < ui_count; i++) {                        |
-        UI *{1:ui} = {1:uis}[{1:i}];                                             |
-        width = MIN(ui->width, width);                               |
-        height = MIN(ui->height, height);                            |
-        foo = BAR(ui->bazaar, bazaar);                               |
-        for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) {         |
-          ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
-        }                                                            |
-      }                                                              |
-    ^}                                                                |
-                                                                     |
-    ]],
-    }
+          int {6:width} = {6:INT_MAX}, {6:height} = {6:INT_MAX};                         |
+          bool {6:ext_widgets}[{6:kUIExtCount}];                                 |
+          for (UIExtension {6:i} = 0; (int)i < kUIExtCount; i++) {           |
+            ext_widgets[i] = true;                                       |
+          }                                                              |
+                                                                         |
+          bool {6:inclusive} = {6:ui_override}();                                |
+          for (size_t {6:i} = 0; i < ui_count; i++) {                        |
+            UI *{6:ui} = {6:uis}[{6:i}];                                             |
+            width = MIN(ui->width, width);                               |
+            height = MIN(ui->height, height);                            |
+            foo = BAR(ui->bazaar, bazaar);                               |
+            for (UIExtension {6:j} = 0; (int)j < kUIExtCount; j++) {         |
+              ext_widgets[j] &= (ui->ui_ext[j] || inclusive);            |
+            }                                                            |
+          }                                                              |
+        ^}                                                                |
+                                                                         |
+      ]],
+    })
   end)
 
   it('supports injected languages', function()
@@ -567,18 +549,18 @@ describe('treesitter highlighting (C)', function()
       vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {3:int} x = {5:INT_MAX};                                                 |
-      #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y))  |
-      #define foo {3:void} main() { \                                      |
-                    {4:return} {5:42};  \                                      |
-                  }                                                    |
-      ^                                                                 |
-      {1:~                                                                }|*11
-                                                                       |
-    ]],
-    }
+        {6:int} x = {26:INT_MAX};                                                 |
+        #define {26:READ_STRING}(x, y) ({6:char} *)read_string((x), ({6:size_t})(y))  |
+        #define foo {6:void} main() { \                                      |
+                      {15:return} {26:42};  \                                      |
+                    }                                                    |
+        ^                                                                 |
+        {1:~                                                                }|*11
+                                                                         |
+      ]],
+    })
   end)
 
   it('supports highlighting with custom highlight groups', function()
@@ -595,27 +577,27 @@ describe('treesitter highlighting (C)', function()
     -- This will change ONLY the literal strings to look like comments
     -- The only literal string is the "vim.schedule: expected function" in this test.
     exec_lua [[vim.cmd("highlight link @string.nonexistent_specializer comment")]]
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:^/// Schedule Lua callback on main loop's event queue}             |
-      {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate)                |
-      {                                                                |
-        {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION}                       |
-            || {6:lstate} != {6:lstate}) {                                     |
-          {11:lua_pushliteral}(lstate, {2:"vim.schedule: expected function"});  |
-          {4:return} {11:lua_error}(lstate);                                    |
-        }                                                              |
-                                                                       |
-        {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1});                               |
-                                                                       |
-        multiqueue_put(main_loop.events, {11:nlua_schedule_event},          |
-                       {5:1}, ({3:void} *)({3:ptrdiff_t})cb);                      |
-        {4:return} {5:0};                                                      |
-      }                                                                |
-      {1:~                                                                }|*2
-                                                                       |
-    ]],
-    }
+        {18:^/// Schedule Lua callback on main loop's event queue}             |
+        {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate)                |
+        {                                                                |
+          {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION}                       |
+              || {19:lstate} != {19:lstate}) {                                     |
+            {25:lua_pushliteral}(lstate, {18:"vim.schedule: expected function"});  |
+            {15:return} {25:lua_error}(lstate);                                    |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        }                                                                |
+        {1:~                                                                }|*2
+                                                                         |
+      ]],
+    })
     screen:expect { unchanged = true }
   end)
 
@@ -691,25 +673,25 @@ describe('treesitter highlighting (C)', function()
         )
       end)
 
-      screen:expect {
+      screen:expect({
         grid = [[
-      {3:char}* x = {5:"Will somebody ever read this?"};                       |
-      ^                                                                 |
-      {1:~                                                                }|*15
-                                                                       |
-    ]],
-      }
+          {6:char}* x = {26:"Will somebody ever read this?"};                       |
+          ^                                                                 |
+          {1:~                                                                }|*15
+                                                                           |
+        ]],
+      })
 
       -- clearing specialization reactivates fallback
       command [[ hi clear @foo.bar ]]
-      screen:expect {
+      screen:expect({
         grid = [[
-      {5:char}* x = {5:"Will somebody ever read this?"};                       |
-      ^                                                                 |
-      {1:~                                                                }|*15
-                                                                       |
-    ]],
-      }
+          {26:char}* x = {26:"Will somebody ever read this?"};                       |
+          ^                                                                 |
+          {1:~                                                                }|*15
+                                                                           |
+        ]],
+      })
     end
   )
 
@@ -740,27 +722,27 @@ describe('treesitter highlighting (C)', function()
       })
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      /// Schedule Lua callback on main loop's event queue             |
-      {4:R} int nlua_schedule(lua_State *const )                           |
-      {                                                                |
-        if (lua_type(, 1) != LUA_TFUNCTION                             |
-            ||  != ) {                                                 |
-          lua_pushliteral(, "vim.schedule: expected function");        |
-          return lua_error();                                          |
-        }                                                              |
-                                                                       |
-        LuaRef cb = nlua_ref(, 1);                                     |
-                                                                       |
-        {11:V}(main_loop.events, nlua_schedule_event,                       |
-                       1, (void *)(ptrdiff_t)cb);                      |
-        return 0;                                                      |
-      ^}                                                                |
-      {1:~                                                                }|*2
-                                                                       |
-    ]],
-    }
+        /// Schedule Lua callback on main loop's event queue             |
+        {15:R} int nlua_schedule(lua_State *const )                           |
+        {                                                                |
+          if (lua_type(, 1) != LUA_TFUNCTION                             |
+              ||  != ) {                                                 |
+            lua_pushliteral(, "vim.schedule: expected function");        |
+            return lua_error();                                          |
+          }                                                              |
+                                                                         |
+          LuaRef cb = nlua_ref(, 1);                                     |
+                                                                         |
+          {25:V}(main_loop.events, nlua_schedule_event,                       |
+                         1, (void *)(ptrdiff_t)cb);                      |
+          return 0;                                                      |
+        ^}                                                                |
+        {1:~                                                                }|*2
+                                                                         |
+      ]],
+    })
   end)
 
   it('@foo.bar groups has the correct fallback behavior', function()
@@ -801,16 +783,16 @@ describe('treesitter highlighting (C)', function()
       vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-        {5:int x = 4;}                                                     |
-        {5:int y = 5;}                                                     |
-        {5:int z = 6;}                                                     |
-      ^                                                                 |
-      {1:~                                                                }|*13
-                                                                       |
-    ]],
-    }
+          {26:int x = 4;}                                                     |
+          {26:int y = 5;}                                                     |
+          {26:int z = 6;}                                                     |
+        ^                                                                 |
+        {1:~                                                                }|*13
+                                                                         |
+      ]],
+    })
   end)
 
   it('gives higher priority to more specific captures #27895', function()
@@ -830,14 +812,52 @@ describe('treesitter highlighting (C)', function()
       vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-        void foo(int {4:*}{11:bar});                                            |
-      ^                                                                 |
-      {1:~                                                                }|*15
-                                                                       |
-    ]],
-    }
+          void foo(int {15:*}{25:bar});                                            |
+        ^                                                                 |
+        {1:~                                                                }|*15
+                                                                         |
+      ]],
+    })
+  end)
+
+  it('highlights applied to first line of closed fold', function()
+    insert(hl_text_c)
+    exec_lua(function()
+      vim.treesitter.query.set('c', 'highlights', hl_query_c)
+      vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
+    end)
+    feed('ggjzfj')
+    command('set foldtext=')
+    screen:add_extra_attr_ids({
+      [100] = {
+        bold = true,
+        background = Screen.colors.LightGray,
+        foreground = Screen.colors.SeaGreen4,
+      },
+      [101] = { background = Screen.colors.LightGray, foreground = Screen.colors.DarkCyan },
+    })
+    screen:expect({
+      grid = [[
+        {18:/// Schedule Lua callback on main loop's event queue}             |
+        {100:^static}{13: }{100:int}{13: }{101:nlua_schedule}{13:(}{100:lua_State}{13: *}{100:const}{13: lstate)················}|
+          {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION}                       |
+              || {19:lstate} != {19:lstate}) {                                     |
+            {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"});  |
+            {15:return} {25:lua_error}(lstate);                                    |
+          }                                                              |
+                                                                         |
+          {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1});                               |
+                                                                         |
+          multiqueue_put(main_loop.events, {25:nlua_schedule_event},          |
+                         {26:1}, ({6:void} *)({6:ptrdiff_t})cb);                      |
+          {15:return} {26:0};                                                      |
+        }                                                                |
+        {1:~                                                                }|*3
+                                                                         |
+      ]],
+    })
   end)
 end)
 
@@ -847,13 +867,6 @@ describe('treesitter highlighting (lua)', function()
   before_each(function()
     clear()
     screen = Screen.new(65, 18)
-    screen:set_default_attr_ids {
-      [1] = { bold = true, foreground = Screen.colors.Blue },
-      [2] = { foreground = Screen.colors.DarkCyan },
-      [3] = { foreground = Screen.colors.Magenta },
-      [4] = { foreground = Screen.colors.SlateBlue },
-      [5] = { bold = true, foreground = Screen.colors.Brown },
-    }
   end)
 
   it('supports language injections', function()
@@ -867,15 +880,15 @@ describe('treesitter highlighting (lua)', function()
       vim.treesitter.start()
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-        {5:local} {2:ffi} {5:=} {4:require(}{3:'ffi'}{4:)}                                     |
-        {2:ffi}{4:.}{2:cdef}{4:(}{3:"}{4:int}{3: }{4:(}{5:*}{3:fun}{4:)(int,}{3: }{4:char}{3: }{5:*}{4:);}{3:"}{4:)}                           |
-      ^                                                                 |
-      {1:~                                                                }|*14
-                                                                       |
-    ]],
-    }
+          {15:local} {25:ffi} {15:=} {16:require(}{26:'ffi'}{16:)}                                     |
+          {25:ffi}{16:.}{25:cdef}{16:(}{26:"}{16:int}{26: }{16:(}{15:*}{26:fun}{16:)(int,}{26: }{16:char}{26: }{15:*}{16:);}{26:"}{16:)}                           |
+        ^                                                                 |
+        {1:~                                                                }|*14
+                                                                         |
+      ]],
+    })
   end)
 end)
 
@@ -885,16 +898,6 @@ describe('treesitter highlighting (help)', function()
   before_each(function()
     clear()
     screen = Screen.new(40, 6)
-    screen:set_default_attr_ids {
-      [1] = { foreground = Screen.colors.Blue1 },
-      [2] = { bold = true, foreground = Screen.colors.Blue1 },
-      [3] = { bold = true, foreground = Screen.colors.Brown },
-      [4] = { foreground = Screen.colors.Cyan4 },
-      [5] = { foreground = Screen.colors.Magenta1 },
-      title = { bold = true, foreground = Screen.colors.Magenta1 },
-      h1_delim = { nocombine = true, underdouble = true },
-      h2_delim = { nocombine = true, underline = true },
-    }
   end)
 
   it('defaults in vimdoc/highlights.scm', function()
@@ -918,13 +921,18 @@ describe('treesitter highlighting (help)', function()
       vim.treesitter.start()
     end)
 
+    screen:add_extra_attr_ids({
+      [100] = { nocombine = true, underdouble = true },
+      [101] = { foreground = Screen.colors.Fuchsia, bold = true },
+      [102] = { underline = true, nocombine = true },
+    })
     screen:expect({
       grid = [[
-        {h1_delim:^========================================}|
-        {title:NVIM DOCUMENTATION}                      |
+        {100:^========================================}|
+        {101:NVIM DOCUMENTATION}                      |
                                                 |
-        {h2_delim:----------------------------------------}|
-        {title:ABOUT NVIM}                              |
+        {102:----------------------------------------}|
+        {101:ABOUT NVIM}                              |
                                                 |
       ]],
     })
@@ -943,42 +951,42 @@ describe('treesitter highlighting (help)', function()
       vim.treesitter.start()
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {1:>}{3:ruby}                                   |
-      {1:  -- comment}                            |
-      {1:  local this_is = 'actually_lua'}        |
-      {1:<}                                       |
-      ^                                        |
-                                              |
-    ]],
-    }
+        {18:>}{15:ruby}                                   |
+        {18:  -- comment}                            |
+        {18:  local this_is = 'actually_lua'}        |
+        {18:<}                                       |
+        ^                                        |
+                                                |
+      ]],
+    })
 
     n.api.nvim_buf_set_text(0, 0, 1, 0, 5, { 'lua' })
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {1:>}{3:lua}                                    |
-      {1:  -- comment}                            |
-      {1:  }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'}        |
-      {1:<}                                       |
-      ^                                        |
-                                              |
-    ]],
-    }
+        {18:>}{15:lua}                                    |
+        {18:  -- comment}                            |
+        {18:  }{15:local}{18: }{25:this_is}{18: }{15:=}{18: }{26:'actually_lua'}        |
+        {18:<}                                       |
+        ^                                        |
+                                                |
+      ]],
+    })
 
     n.api.nvim_buf_set_text(0, 0, 1, 0, 4, { 'ruby' })
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {1:>}{3:ruby}                                   |
-      {1:  -- comment}                            |
-      {1:  local this_is = 'actually_lua'}        |
-      {1:<}                                       |
-      ^                                        |
-                                              |
-    ]],
-    }
+        {18:>}{15:ruby}                                   |
+        {18:  -- comment}                            |
+        {18:  local this_is = 'actually_lua'}        |
+        {18:<}                                       |
+        ^                                        |
+                                                |
+      ]],
+    })
   end)
 
   it('correctly redraws injections subpriorities', function()
@@ -1003,16 +1011,16 @@ describe('treesitter highlighting (help)', function()
       vim.treesitter.highlighter.new(parser)
     end)
 
-    screen:expect {
+    screen:expect({
       grid = [=[
-      {3:local} {4:s} {3:=} {5:[[}                            |
-      {5:  }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua}                      |
-      {5:]]}                                      |
-      ^                                        |
-      {2:~                                       }|
-                                              |
-    ]=],
-    }
+        {15:local} {25:s} {15:=} {26:[[}                            |
+        {26:  }{15:local}{26: }{25:also}{26: }{15:=}{26: }{25:lua}                      |
+        {26:]]}                                      |
+        ^                                        |
+        {1:~                                       }|
+                                                |
+      ]=],
+    })
   end)
 end)
 
@@ -1022,12 +1030,6 @@ describe('treesitter highlighting (nested injections)', function()
   before_each(function()
     clear()
     screen = Screen.new(80, 7)
-    screen:set_default_attr_ids {
-      [1] = { foreground = Screen.colors.SlateBlue },
-      [2] = { bold = true, foreground = Screen.colors.Brown },
-      [3] = { foreground = Screen.colors.Cyan4 },
-      [4] = { foreground = Screen.colors.Fuchsia },
-    }
   end)
 
   it('correctly redraws nested injections (GitHub #25252)', function()
@@ -1054,32 +1056,32 @@ vim.cmd([[
     -- invalidate the language tree
     feed('ggi--[[04x')
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end}                                                |
-                                                                                      |
-      {2:local} {3:lorem} {2:=} {1:{}                                                                 |
-          {3:ipsum} {2:=} {1:{},}                                                                 |
-          {3:bar} {2:=} {1:{},}                                                                   |
-      {1:}}                                                                               |
-                                                                                      |
-    ]],
-    }
+        {15:^function} {25:foo}{16:()} {16:print(}{26:"Lua!"}{16:)} {15:end}                                                |
+                                                                                        |
+        {15:local} {25:lorem} {15:=} {16:{}                                                                 |
+            {25:ipsum} {15:=} {16:{},}                                                                 |
+            {25:bar} {15:=} {16:{},}                                                                   |
+        {16:}}                                                                               |
+                                                                                        |
+      ]],
+    })
 
     -- spam newline insert/delete to invalidate Lua > Vim > Lua region
     feed('3joddkoddkoddkoddk0')
 
-    screen:expect {
+    screen:expect({
       grid = [[
-      {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end}                                                |
-                                                                                      |
-      {2:local} {3:lorem} {2:=} {1:{}                                                                 |
-      ^    {3:ipsum} {2:=} {1:{},}                                                                 |
-          {3:bar} {2:=} {1:{},}                                                                   |
-      {1:}}                                                                               |
-                                                                                      |
-    ]],
-    }
+        {15:function} {25:foo}{16:()} {16:print(}{26:"Lua!"}{16:)} {15:end}                                                |
+                                                                                        |
+        {15:local} {25:lorem} {15:=} {16:{}                                                                 |
+        ^    {25:ipsum} {15:=} {16:{},}                                                                 |
+            {25:bar} {15:=} {16:{},}                                                                   |
+        {16:}}                                                                               |
+                                                                                        |
+      ]],
+    })
   end)
 end)
 
@@ -1156,20 +1158,6 @@ it('starting and stopping treesitter highlight in init.lua works #29541', functi
   eq('', api.nvim_get_vvar('errmsg'))
 
   local screen = Screen.new(65, 18)
-  screen:set_default_attr_ids {
-    [1] = { bold = true, foreground = Screen.colors.Blue1 },
-    [2] = { foreground = Screen.colors.Blue1 },
-    [3] = { bold = true, foreground = Screen.colors.SeaGreen4 },
-    [4] = { bold = true, foreground = Screen.colors.Brown },
-    [5] = { foreground = Screen.colors.Magenta },
-    [6] = { foreground = Screen.colors.Red },
-    [7] = { bold = true, foreground = Screen.colors.SlateBlue },
-    [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
-    [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red },
-    [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red },
-    [11] = { foreground = Screen.colors.Cyan4 },
-  }
-
   fn.setreg('r', hl_text_c)
   feed('irgg')
   -- legacy syntax highlighting is used
-- 
cgit 


From c7ec010ade0832e43c7a319ea69fae642771479d Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Mon, 2 Dec 2024 15:11:38 +0100
Subject: fix(extmark): builtin completion can still affect nearby extmarks
 #31387

Problem:
Built-in completion can still affect nearby extmarks. #31384

Solution:
Restore extmarks when completion leader changes.
---
 src/nvim/insexpand.c                       |  1 +
 test/functional/editor/completion_spec.lua | 10 ++++++++++
 2 files changed, 11 insertions(+)

diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 820647df97..a1cebb407e 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1805,6 +1805,7 @@ static void ins_compl_new_leader(void)
   ins_compl_del_pum();
   ins_compl_delete();
   ins_bytes(compl_leader + get_compl_len());
+  restore_orig_extmarks();
   compl_used_match = false;
 
   if (compl_started) {
diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
index 030181764d..da4d7a6640 100644
--- a/test/functional/editor/completion_spec.lua
+++ b/test/functional/editor/completion_spec.lua
@@ -1302,5 +1302,15 @@ describe('completion', function()
       aaaaa                                                       |
       {5:-- INSERT --}                                                |
     ]])
+    -- Also when completion leader is changed #31384
+    feed('hia')
+    screen:expect({
+      grid = [[
+        {9:aa}a^aa                                                       |
+        {4:aaaa           }                                             |
+        {4:aaaaa          }                                             |
+        {5:-- Keyword completion (^N^P) }{19:Back at original}               |
+      ]],
+    })
   end)
 end)
-- 
cgit 


From 49d6cd1da86cab49c7a5a8c79e59d48d016975fa Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Mon, 2 Dec 2024 12:13:09 -0600
Subject: docs: provide example for configuring LSP foldexpr (#31411)

Using the "supports_method" function with a client capability inside of
an LspAttach autocommand is the preferred method to do this, so we
should be showing users how to do it.
---
 runtime/doc/lsp.txt     | 13 +++++++++++++
 runtime/lua/vim/lsp.lua | 16 ++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index f7157df0f2..e311831bd3 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -720,6 +720,19 @@ foldexpr({lnum})                                          *vim.lsp.foldexpr()*
     Provides an interface between the built-in client and a `foldexpr`
     function.
 
+    To use, check for the "textDocument/foldingRange" capability in an
+    |LspAttach| autocommand. Example: >lua
+        vim.api.nvim_create_autocommand('LspAttach', {
+          callback = function(args)
+            local client = vim.lsp.get_client_by_id(args.data.client_id)
+            if client:supports_method('textDocument/foldingRange') then
+              vim.wo.foldmethod = 'expr'
+              vim.wo.foldexpr = 'v:lua.vim.lsp.foldexpr()'
+            end
+          end,
+        })
+<
+
     Parameters: ~
       • {lnum}  (`integer`) line number
 
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index a3791e15c3..b1a3316e3e 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1097,6 +1097,22 @@ function lsp.tagfunc(pattern, flags)
 end
 
 --- Provides an interface between the built-in client and a `foldexpr` function.
+---
+--- To use, check for the "textDocument/foldingRange" capability in an
+--- |LspAttach| autocommand. Example:
+---
+--- ```lua
+--- vim.api.nvim_create_autocommand('LspAttach', {
+---   callback = function(args)
+---     local client = vim.lsp.get_client_by_id(args.data.client_id)
+---     if client:supports_method('textDocument/foldingRange') then
+---       vim.wo.foldmethod = 'expr'
+---       vim.wo.foldexpr = 'v:lua.vim.lsp.foldexpr()'
+---     end
+---   end,
+--- })
+--- ```
+---
 ---@param lnum integer line number
 function lsp.foldexpr(lnum)
   return vim.lsp._folding_range.foldexpr(lnum)
-- 
cgit 


From 3d3a99e69cda365cae9ad65831712301807a772b Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 3 Dec 2024 08:32:23 +0800
Subject: vim-patch:9.1.0900: Vim9: digraph_getlist() does not accept bool arg
 (#31431)

Problem:  Vim9: digraph_getlist() does not accept bool argument
          (Maxim Kim)
Solution: accept boolean as first argument (Yegappan Lakshmanan)

fixes: vim/vim#16154
closes: vim/vim#16159

https://github.com/vim/vim/commit/198ada3d9f48c6556d20c4115ec500555b118aad

Co-authored-by: Yegappan Lakshmanan 
---
 src/nvim/digraph.c                | 10 +++++-----
 test/old/testdir/test_digraph.vim |  4 +++-
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index ea0d1ba708..4d40455507 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -1954,16 +1954,16 @@ void f_digraph_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 /// "digraph_getlist()" function
 void f_digraph_getlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 {
+  if (tv_check_for_opt_bool_arg(argvars, 0) == FAIL) {
+    return;
+  }
+
   bool flag_list_all;
 
   if (argvars[0].v_type == VAR_UNKNOWN) {
     flag_list_all = false;
   } else {
-    bool error = false;
-    varnumber_T flag = tv_get_number_chk(&argvars[0], &error);
-    if (error) {
-      return;
-    }
+    varnumber_T flag = tv_get_bool(&argvars[0]);
     flag_list_all = flag != 0;
   }
 
diff --git a/test/old/testdir/test_digraph.vim b/test/old/testdir/test_digraph.vim
index ce5e1b2055..9c32b85f61 100644
--- a/test/old/testdir/test_digraph.vim
+++ b/test/old/testdir/test_digraph.vim
@@ -607,8 +607,10 @@ func Test_digraph_getlist_function()
   " of digraphs returned.
   call assert_equal(digraph_getlist()->len(), digraph_getlist(0)->len())
   call assert_notequal(digraph_getlist()->len(), digraph_getlist(1)->len())
+  call assert_equal(digraph_getlist()->len(), digraph_getlist(v:false)->len())
+  call assert_notequal(digraph_getlist()->len(), digraph_getlist(v:true)->len())
 
-  call assert_fails('call digraph_getlist(0z12)', 'E974: Using a Blob as a Number')
+  call assert_fails('call digraph_getlist(0z12)', 'E1212: Bool required for argument 1')
 endfunc
 
 
-- 
cgit 


From 7a367c6967d8bd1e386e391216a41b15bde5b28a Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Wed, 27 Nov 2024 11:17:42 -0500
Subject: test(vterm): move test functions into vterm_test fixture

In order to run unittests with a release build, we need the test
functions to be accessible when NDEBUG is defined. Moving the functions
into the test fixture ensures they are available and only available for
use by the unit tests.
---
 src/vterm/vterm.c               | 506 ----------------------------------------
 src/vterm/vterm.h               |  34 ---
 test/unit/fixtures/vterm_test.c | 504 +++++++++++++++++++++++++++++++++++++++
 test/unit/fixtures/vterm_test.h |  37 +++
 test/unit/vterm_spec.lua        |   6 +-
 5 files changed, 546 insertions(+), 541 deletions(-)
 create mode 100644 test/unit/fixtures/vterm_test.c
 create mode 100644 test/unit/fixtures/vterm_test.h

diff --git a/src/vterm/vterm.c b/src/vterm/vterm.c
index e8c87929e2..530c513755 100644
--- a/src/vterm/vterm.c
+++ b/src/vterm/vterm.c
@@ -430,509 +430,3 @@ void vterm_check_version(int major, int minor)
 
   // Happy
 }
-
-// For unit tests.
-#ifndef NDEBUG
-
-int parser_text(const char bytes[], size_t len, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "text ");
-  int i;
-  for(i = 0; i < len; i++) {
-    unsigned char b = bytes[i];
-    if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) {
-      break;
-    }
-    fprintf(f, i ? ",%x" : "%x", b);
-  }
-  fprintf(f, "\n");
-  fclose(f);
-
-  return i;
-}
-
-static void printchars(const char *s, size_t len, FILE *f)
-{
-  while(len--) {
-    fprintf(f, "%c", (s++)[0]);
-  }
-}
-
-int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "csi %02x", command);
-
-  if(leader && leader[0]) {
-    fprintf(f, " L=");
-    for(int i = 0; leader[i]; i++) {
-      fprintf(f, "%02x", leader[i]);
-    }
-  }
-
-  for(int i = 0; i < argcount; i++) {
-    char sep = i ? ',' : ' ';
-
-    if(args[i] == CSI_ARG_MISSING) {
-      fprintf(f, "%c*", sep);
-    } else {
-      fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
-    }
-  }
-
-  if(intermed && intermed[0]) {
-    fprintf(f, " I=");
-    for(int i = 0; intermed[i]; i++) {
-      fprintf(f, "%02x", intermed[i]);
-    }
-  }
-
-  fprintf(f, "\n");
-
-  fclose(f);
-
-  return 1;
-}
-
-int parser_osc(int command, VTermStringFragment frag, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "osc ");
-
-  if(frag.initial) {
-    if(command == -1) {
-      fprintf(f, "[");
-    } else {
-      fprintf(f, "[%d;", command);
-    }
-  }
-
-  printchars(frag.str, frag.len, f);
-
-  if(frag.final) {
-    fprintf(f, "]");
-  }
-
-  fprintf(f, "\n");
-  fclose(f);
-
-  return 1;
-}
-
-int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "dcs ");
-
-  if(frag.initial) {
-    fprintf(f, "[");
-    for(int i = 0; i < commandlen; i++) {
-      fprintf(f, "%c", command[i]);
-    }
-  }
-
-  printchars(frag.str, frag.len,f);
-
-  if(frag.final) {
-    fprintf(f, "]");
-  }
-
-  fprintf(f, "\n");
-  fclose(f);
-
-  return 1;
-}
-
-int parser_apc(VTermStringFragment frag, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "apc ");
-
-  if(frag.initial) {
-    fprintf(f, "[");
-  }
-
-  printchars(frag.str, frag.len, f);
-
-  if(frag.final) {
-    fprintf(f, "]");
-  }
-
-  fprintf(f, "\n");
-  fclose(f);
-
-  return 1;
-}
-
-int parser_pm(VTermStringFragment frag, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "pm ");
-
-  if(frag.initial) {
-    fprintf(f, "[");
-  }
-
-  printchars(frag.str, frag.len,f);
-
-  if(frag.final) {
-    fprintf(f, "]");
-  }
-
-  fprintf(f, "\n");
-  fclose(f);
-
-  return 1;
-}
-
-int parser_sos(VTermStringFragment frag, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "sos ");
-
-  if(frag.initial) {
-    fprintf(f, "[");
-  }
-
-  printchars(frag.str, frag.len,f);
-
-  if(frag.final) {
-    fprintf(f, "]");
-  }
-
-  fprintf(f, "\n");
-  fclose(f);
-
-  return 1;
-}
-
-int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "selection-set mask=%04X ", mask);
-  if(frag.initial) {
-    fprintf(f, "[");
-}
-  printchars(frag.str, frag.len, f);
-  if(frag.final) {
-    fprintf(f, "]");
-}
-  fprintf(f,"\n");
-
-  fclose(f);
-  return 1;
-}
-
-int selection_query(VTermSelectionMask mask, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"selection-query mask=%04X\n", mask);
-
-  fclose(f);
-  return 1;
-}
-
-bool want_state_putglyph;
-int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
-{
-  if(!want_state_putglyph) {
-    return 1;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "putglyph ");
-  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
-    fprintf(f, i ? ",%x" : "%x", info->chars[i]);
-  }
-  fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
-  if(info->protected_cell) {
-    fprintf(f, " prot");
-  }
-  if(info->dwl) {
-    fprintf(f, " dwl");
-  }
-  if(info->dhl) {
-    fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" );
-  }
-  fprintf(f, "\n");
-
-  fclose(f);
-
-  return 1;
-}
-
-bool want_state_movecursor;
-VTermPos state_pos;
-int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  state_pos = pos;
-
-  if(want_state_movecursor) {
-    fprintf(f,"movecursor %d,%d\n", pos.row, pos.col);
-  }
-
-  fclose(f);
-  return 1;
-}
-
-bool want_state_scrollrect;
-int state_scrollrect(VTermRect rect, int downward, int rightward, void *user)
-{
-  if(!want_state_scrollrect) {
-    return 0;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-
-  fprintf(f,"scrollrect %d..%d,%d..%d => %+d,%+d\n",
-      rect.start_row, rect.end_row, rect.start_col, rect.end_col,
-      downward, rightward);
-
-  fclose(f);
-  return 1;
-}
-
-bool want_state_moverect;
-int state_moverect(VTermRect dest, VTermRect src, void *user)
-{
-  if(!want_state_moverect) {
-    return 0;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
-      src.start_row,  src.end_row,  src.start_col,  src.end_col,
-      dest.start_row, dest.end_row, dest.start_col, dest.end_col);
-
-  fclose(f);
-  return 1;
-}
-
-void print_color(const VTermColor *col)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  if (VTERM_COLOR_IS_RGB(col)) {
-    fprintf(f,"rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue);
-  }
-  else if (VTERM_COLOR_IS_INDEXED(col)) {
-    fprintf(f,"idx(%d", col->indexed.idx);
-  }
-  else {
-    fprintf(f,"invalid(%d", col->type);
-  }
-  if (VTERM_COLOR_IS_DEFAULT_FG(col)) {
-    fprintf(f,",is_default_fg");
-  }
-  if (VTERM_COLOR_IS_DEFAULT_BG(col)) {
-    fprintf(f,",is_default_bg");
-  }
-  fprintf(f,")");
-  fclose(f);
-}
-
-bool want_state_settermprop;
-int state_settermprop(VTermProp prop, VTermValue *val, void *user)
-{
-  if(!want_state_settermprop) {
-    return 1;
-  }
-
-  int errcode = 0;
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-
-  VTermValueType type = vterm_get_prop_type(prop);
-  switch(type) {
-    case VTERM_VALUETYPE_BOOL:
-      fprintf(f,"settermprop %d %s\n", prop, val->boolean ? "true" : "false");
-      errcode = 1;
-      goto end;
-    case VTERM_VALUETYPE_INT:
-      fprintf(f,"settermprop %d %d\n", prop, val->number);
-      errcode = 1;
-      goto end;
-    case VTERM_VALUETYPE_STRING:
-      fprintf(f,"settermprop %d %s\"%.*s\"%s\n", prop,
-          val->string.initial ? "[" : "", (int)val->string.len, val->string.str, val->string.final ? "]" : "");
-      errcode=0;
-      goto end;
-    case VTERM_VALUETYPE_COLOR:
-      fprintf(f,"settermprop %d ", prop);
-      print_color(&val->color);
-      fprintf(f,"\n");
-      errcode=1;
-      goto end;
-    case VTERM_N_VALUETYPES:
-      goto end;
-  }
-
-end:
-  fclose(f);
-  return errcode;
-}
-
-bool want_state_erase;
-int state_erase(VTermRect rect, int selective, void *user)
-{
-  if(!want_state_erase) {
-    return 1;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-
-  fprintf(f,"erase %d..%d,%d..%d%s\n",
-      rect.start_row, rect.end_row, rect.start_col, rect.end_col,
-      selective ? " selective" : "");
-
-  fclose(f);
-  return 1;
-}
-
-struct {
-  int bold;
-  int underline;
-  int italic;
-  int blink;
-  int reverse;
-  int conceal;
-  int strike;
-  int font;
-  int small;
-  int baseline;
-  VTermColor foreground;
-  VTermColor background;
-} state_pen;
-
-int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
-{
-  switch(attr) {
-  case VTERM_ATTR_BOLD:
-    state_pen.bold = val->boolean;
-    break;
-  case VTERM_ATTR_UNDERLINE:
-    state_pen.underline = val->number;
-    break;
-  case VTERM_ATTR_ITALIC:
-    state_pen.italic = val->boolean;
-    break;
-  case VTERM_ATTR_BLINK:
-    state_pen.blink = val->boolean;
-    break;
-  case VTERM_ATTR_REVERSE:
-    state_pen.reverse = val->boolean;
-    break;
-  case VTERM_ATTR_CONCEAL:
-    state_pen.conceal = val->boolean;
-    break;
-  case VTERM_ATTR_STRIKE:
-    state_pen.strike = val->boolean;
-    break;
-  case VTERM_ATTR_FONT:
-    state_pen.font = val->number;
-    break;
-  case VTERM_ATTR_SMALL:
-    state_pen.small = val->boolean;
-    break;
-  case VTERM_ATTR_BASELINE:
-    state_pen.baseline = val->number;
-    break;
-  case VTERM_ATTR_FOREGROUND:
-    state_pen.foreground = val->color;
-    break;
-  case VTERM_ATTR_BACKGROUND:
-    state_pen.background = val->color;
-    break;
-
-  case VTERM_N_ATTRS:
-    return 0;
-  default:
-    break;
-  }
-
-  return 1;
-}
-
-bool want_state_scrollback;
-int state_sb_clear(void *user) {
-  if(!want_state_scrollback) {
-    return 1;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"sb_clear\n");
-  fclose(f);
-
-  return 0;
-}
-
-bool want_screen_scrollback;
-int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
-{
-  if(!want_screen_scrollback) {
-    return 1;
-  }
-
-  int eol = cols;
-  while(eol && !cells[eol-1].chars[0]) {
-    eol--;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "sb_pushline %d =", cols);
-  for(int c = 0; c < eol; c++) {
-    fprintf(f, " %02X", cells[c].chars[0]);
-  }
-  fprintf(f, "\n");
-
-  fclose(f);
-
-  return 1;
-}
-
-int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
-{
-  if(!want_screen_scrollback) {
-    return 0;
-  }
-
-  // All lines of scrollback contain "ABCDE"
-  for(int col = 0; col < cols; col++) {
-    if(col < 5) {
-      cells[col].chars[0] = 'A' + col;
-    } else {
-      cells[col].chars[0] = 0;
-    }
-
-    cells[col].width = 1;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"sb_popline %d\n", cols);
-  fclose(f);
-  return 1;
-}
-
-int screen_sb_clear(void *user)
-{
-  if(!want_screen_scrollback) {
-    return 1;
-  }
-
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "sb_clear\n");
-  fclose(f);
-  return 0;
-}
-
-void term_output(const char *s, size_t len, void *user)
-{
-  FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f, "output ");
-  for(int i = 0; i < len; i++) {
-    fprintf(f, "%x%s", (unsigned char)s[i], i < len-1 ? "," : "\n");
-  }
-  fclose(f);
-}
-
-#endif
diff --git a/src/vterm/vterm.h b/src/vterm/vterm.h
index df6878f744..89fe2a58bb 100644
--- a/src/vterm/vterm.h
+++ b/src/vterm/vterm.h
@@ -635,40 +635,6 @@ void vterm_copy_cells(VTermRect dest,
                       void (*copycell)(VTermPos dest, VTermPos src, void *user),
                       void *user);
 
-#ifndef NDEBUG
-int parser_text(const char bytes[], size_t len, void *user);
-int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
-int parser_osc(int command, VTermStringFragment frag, void *user);
-int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
-int parser_apc(VTermStringFragment frag, void *user);
-int parser_pm(VTermStringFragment frag, void *user);
-int parser_sos(VTermStringFragment frag, void *user);
-int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user);
-int selection_query(VTermSelectionMask mask, void *user);
-int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user);
-int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
-int state_scrollrect(VTermRect rect, int downward, int rightward, void *user);
-int state_moverect(VTermRect dest, VTermRect src, void *user);
-int state_settermprop(VTermProp prop, VTermValue *val, void *user);
-int state_erase(VTermRect rect, int selective, void *user);
-int state_setpenattr(VTermAttr attr, VTermValue *val, void *user);
-int state_sb_clear(void *user);
-void print_color(const VTermColor *col);
-int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user);
-int screen_sb_popline(int cols, VTermScreenCell *cells, void *user);
-int screen_sb_clear(void *user);
-void term_output(const char *s, size_t len, void *user);
-EXTERN VTermPos state_pos;
-EXTERN bool want_state_putglyph INIT (=false);
-EXTERN bool want_state_movecursor INIT(= false);
-EXTERN bool want_state_erase INIT(= false);
-EXTERN bool want_state_scrollrect INIT(= false);
-EXTERN bool want_state_moverect INIT(= false);
-EXTERN bool want_state_settermprop INIT(= false);
-EXTERN bool want_state_scrollback INIT(= false);
-EXTERN bool want_screen_scrollback INIT(= false);
-#endif
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c
new file mode 100644
index 0000000000..47aa071f9b
--- /dev/null
+++ b/test/unit/fixtures/vterm_test.c
@@ -0,0 +1,504 @@
+#include "vterm_test.h"
+
+#include 
+
+int parser_text(const char bytes[], size_t len, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "text ");
+  size_t i;
+  for(i = 0; i < len; i++) {
+    unsigned char b = (unsigned char)bytes[i];
+    if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) {
+      break;
+    }
+    fprintf(f, i ? ",%x" : "%x", b);
+  }
+  fprintf(f, "\n");
+  fclose(f);
+
+  return (int)i;
+}
+
+static void printchars(const char *s, size_t len, FILE *f)
+{
+  while(len--) {
+    fprintf(f, "%c", (s++)[0]);
+  }
+}
+
+int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "csi %02x", command);
+
+  if(leader && leader[0]) {
+    fprintf(f, " L=");
+    for(int i = 0; leader[i]; i++) {
+      fprintf(f, "%02x", leader[i]);
+    }
+  }
+
+  for(int i = 0; i < argcount; i++) {
+    char sep = i ? ',' : ' ';
+
+    if(args[i] == CSI_ARG_MISSING) {
+      fprintf(f, "%c*", sep);
+    } else {
+      fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
+    }
+  }
+
+  if(intermed && intermed[0]) {
+    fprintf(f, " I=");
+    for(int i = 0; intermed[i]; i++) {
+      fprintf(f, "%02x", intermed[i]);
+    }
+  }
+
+  fprintf(f, "\n");
+
+  fclose(f);
+
+  return 1;
+}
+
+int parser_osc(int command, VTermStringFragment frag, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "osc ");
+
+  if(frag.initial) {
+    if(command == -1) {
+      fprintf(f, "[");
+    } else {
+      fprintf(f, "[%d;", command);
+    }
+  }
+
+  printchars(frag.str, frag.len, f);
+
+  if(frag.final) {
+    fprintf(f, "]");
+  }
+
+  fprintf(f, "\n");
+  fclose(f);
+
+  return 1;
+}
+
+int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "dcs ");
+
+  if(frag.initial) {
+    fprintf(f, "[");
+    for(size_t i = 0; i < commandlen; i++) {
+      fprintf(f, "%c", command[i]);
+    }
+  }
+
+  printchars(frag.str, frag.len,f);
+
+  if(frag.final) {
+    fprintf(f, "]");
+  }
+
+  fprintf(f, "\n");
+  fclose(f);
+
+  return 1;
+}
+
+int parser_apc(VTermStringFragment frag, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "apc ");
+
+  if(frag.initial) {
+    fprintf(f, "[");
+  }
+
+  printchars(frag.str, frag.len, f);
+
+  if(frag.final) {
+    fprintf(f, "]");
+  }
+
+  fprintf(f, "\n");
+  fclose(f);
+
+  return 1;
+}
+
+int parser_pm(VTermStringFragment frag, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "pm ");
+
+  if(frag.initial) {
+    fprintf(f, "[");
+  }
+
+  printchars(frag.str, frag.len,f);
+
+  if(frag.final) {
+    fprintf(f, "]");
+  }
+
+  fprintf(f, "\n");
+  fclose(f);
+
+  return 1;
+}
+
+int parser_sos(VTermStringFragment frag, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "sos ");
+
+  if(frag.initial) {
+    fprintf(f, "[");
+  }
+
+  printchars(frag.str, frag.len,f);
+
+  if(frag.final) {
+    fprintf(f, "]");
+  }
+
+  fprintf(f, "\n");
+  fclose(f);
+
+  return 1;
+}
+
+int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "selection-set mask=%04X ", mask);
+  if(frag.initial) {
+    fprintf(f, "[");
+}
+  printchars(frag.str, frag.len, f);
+  if(frag.final) {
+    fprintf(f, "]");
+}
+  fprintf(f,"\n");
+
+  fclose(f);
+  return 1;
+}
+
+int selection_query(VTermSelectionMask mask, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f,"selection-query mask=%04X\n", mask);
+
+  fclose(f);
+  return 1;
+}
+
+bool want_state_putglyph;
+int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
+{
+  if(!want_state_putglyph) {
+    return 1;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "putglyph ");
+  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
+    fprintf(f, i ? ",%x" : "%x", info->chars[i]);
+  }
+  fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
+  if(info->protected_cell) {
+    fprintf(f, " prot");
+  }
+  if(info->dwl) {
+    fprintf(f, " dwl");
+  }
+  if(info->dhl) {
+    fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" );
+  }
+  fprintf(f, "\n");
+
+  fclose(f);
+
+  return 1;
+}
+
+bool want_state_movecursor;
+VTermPos state_pos;
+int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  state_pos = pos;
+
+  if(want_state_movecursor) {
+    fprintf(f,"movecursor %d,%d\n", pos.row, pos.col);
+  }
+
+  fclose(f);
+  return 1;
+}
+
+bool want_state_scrollrect;
+int state_scrollrect(VTermRect rect, int downward, int rightward, void *user)
+{
+  if(!want_state_scrollrect) {
+    return 0;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+
+  fprintf(f,"scrollrect %d..%d,%d..%d => %+d,%+d\n",
+      rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+      downward, rightward);
+
+  fclose(f);
+  return 1;
+}
+
+bool want_state_moverect;
+int state_moverect(VTermRect dest, VTermRect src, void *user)
+{
+  if(!want_state_moverect) {
+    return 0;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f,"moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
+      src.start_row,  src.end_row,  src.start_col,  src.end_col,
+      dest.start_row, dest.end_row, dest.start_col, dest.end_col);
+
+  fclose(f);
+  return 1;
+}
+
+void print_color(const VTermColor *col)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  if (VTERM_COLOR_IS_RGB(col)) {
+    fprintf(f,"rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue);
+  }
+  else if (VTERM_COLOR_IS_INDEXED(col)) {
+    fprintf(f,"idx(%d", col->indexed.idx);
+  }
+  else {
+    fprintf(f,"invalid(%d", col->type);
+  }
+  if (VTERM_COLOR_IS_DEFAULT_FG(col)) {
+    fprintf(f,",is_default_fg");
+  }
+  if (VTERM_COLOR_IS_DEFAULT_BG(col)) {
+    fprintf(f,",is_default_bg");
+  }
+  fprintf(f,")");
+  fclose(f);
+}
+
+bool want_state_settermprop;
+int state_settermprop(VTermProp prop, VTermValue *val, void *user)
+{
+  if(!want_state_settermprop) {
+    return 1;
+  }
+
+  int errcode = 0;
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+
+  VTermValueType type = vterm_get_prop_type(prop);
+  switch(type) {
+    case VTERM_VALUETYPE_BOOL:
+      fprintf(f,"settermprop %d %s\n", prop, val->boolean ? "true" : "false");
+      errcode = 1;
+      goto end;
+    case VTERM_VALUETYPE_INT:
+      fprintf(f,"settermprop %d %d\n", prop, val->number);
+      errcode = 1;
+      goto end;
+    case VTERM_VALUETYPE_STRING:
+      fprintf(f,"settermprop %d %s\"%.*s\"%s\n", prop,
+          val->string.initial ? "[" : "", (int)val->string.len, val->string.str, val->string.final ? "]" : "");
+      errcode=0;
+      goto end;
+    case VTERM_VALUETYPE_COLOR:
+      fprintf(f,"settermprop %d ", prop);
+      print_color(&val->color);
+      fprintf(f,"\n");
+      errcode=1;
+      goto end;
+    case VTERM_N_VALUETYPES:
+      goto end;
+  }
+
+end:
+  fclose(f);
+  return errcode;
+}
+
+bool want_state_erase;
+int state_erase(VTermRect rect, int selective, void *user)
+{
+  if(!want_state_erase) {
+    return 1;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+
+  fprintf(f,"erase %d..%d,%d..%d%s\n",
+      rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+      selective ? " selective" : "");
+
+  fclose(f);
+  return 1;
+}
+
+struct {
+  int bold;
+  int underline;
+  int italic;
+  int blink;
+  int reverse;
+  int conceal;
+  int strike;
+  int font;
+  int small;
+  int baseline;
+  VTermColor foreground;
+  VTermColor background;
+} state_pen;
+
+int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
+{
+  switch(attr) {
+  case VTERM_ATTR_BOLD:
+    state_pen.bold = val->boolean;
+    break;
+  case VTERM_ATTR_UNDERLINE:
+    state_pen.underline = val->number;
+    break;
+  case VTERM_ATTR_ITALIC:
+    state_pen.italic = val->boolean;
+    break;
+  case VTERM_ATTR_BLINK:
+    state_pen.blink = val->boolean;
+    break;
+  case VTERM_ATTR_REVERSE:
+    state_pen.reverse = val->boolean;
+    break;
+  case VTERM_ATTR_CONCEAL:
+    state_pen.conceal = val->boolean;
+    break;
+  case VTERM_ATTR_STRIKE:
+    state_pen.strike = val->boolean;
+    break;
+  case VTERM_ATTR_FONT:
+    state_pen.font = val->number;
+    break;
+  case VTERM_ATTR_SMALL:
+    state_pen.small = val->boolean;
+    break;
+  case VTERM_ATTR_BASELINE:
+    state_pen.baseline = val->number;
+    break;
+  case VTERM_ATTR_FOREGROUND:
+    state_pen.foreground = val->color;
+    break;
+  case VTERM_ATTR_BACKGROUND:
+    state_pen.background = val->color;
+    break;
+
+  case VTERM_N_ATTRS:
+    return 0;
+  default:
+    break;
+  }
+
+  return 1;
+}
+
+bool want_state_scrollback;
+int state_sb_clear(void *user) {
+  if(!want_state_scrollback) {
+    return 1;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f,"sb_clear\n");
+  fclose(f);
+
+  return 0;
+}
+
+bool want_screen_scrollback;
+int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
+{
+  if(!want_screen_scrollback) {
+    return 1;
+  }
+
+  int eol = cols;
+  while(eol && !cells[eol-1].chars[0]) {
+    eol--;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "sb_pushline %d =", cols);
+  for(int c = 0; c < eol; c++) {
+    fprintf(f, " %02X", cells[c].chars[0]);
+  }
+  fprintf(f, "\n");
+
+  fclose(f);
+
+  return 1;
+}
+
+int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
+{
+  if(!want_screen_scrollback) {
+    return 0;
+  }
+
+  // All lines of scrollback contain "ABCDE"
+  for(int col = 0; col < cols; col++) {
+    if(col < 5) {
+      cells[col].chars[0] = (uint32_t)('A' + col);
+    } else {
+      cells[col].chars[0] = 0;
+    }
+
+    cells[col].width = 1;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f,"sb_popline %d\n", cols);
+  fclose(f);
+  return 1;
+}
+
+int screen_sb_clear(void *user)
+{
+  if(!want_screen_scrollback) {
+    return 1;
+  }
+
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "sb_clear\n");
+  fclose(f);
+  return 0;
+}
+
+void term_output(const char *s, size_t len, void *user)
+{
+  FILE *f = fopen(VTERM_TEST_FILE, "a");
+  fprintf(f, "output ");
+  for(size_t i = 0; i < len; i++) {
+    fprintf(f, "%x%s", (unsigned char)s[i], i < len-1 ? "," : "\n");
+  }
+  fclose(f);
+}
diff --git a/test/unit/fixtures/vterm_test.h b/test/unit/fixtures/vterm_test.h
new file mode 100644
index 0000000000..924c6c1633
--- /dev/null
+++ b/test/unit/fixtures/vterm_test.h
@@ -0,0 +1,37 @@
+#include 
+#include 
+
+#include "nvim/macros_defs.h"
+#include "vterm/vterm.h"
+
+int parser_text(const char bytes[], size_t len, void *user);
+int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
+int parser_osc(int command, VTermStringFragment frag, void *user);
+int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+int parser_apc(VTermStringFragment frag, void *user);
+int parser_pm(VTermStringFragment frag, void *user);
+int parser_sos(VTermStringFragment frag, void *user);
+int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user);
+int selection_query(VTermSelectionMask mask, void *user);
+int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user);
+int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
+int state_scrollrect(VTermRect rect, int downward, int rightward, void *user);
+int state_moverect(VTermRect dest, VTermRect src, void *user);
+int state_settermprop(VTermProp prop, VTermValue *val, void *user);
+int state_erase(VTermRect rect, int selective, void *user);
+int state_setpenattr(VTermAttr attr, VTermValue *val, void *user);
+int state_sb_clear(void *user);
+void print_color(const VTermColor *col);
+int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user);
+int screen_sb_popline(int cols, VTermScreenCell *cells, void *user);
+int screen_sb_clear(void *user);
+void term_output(const char *s, size_t len, void *user);
+EXTERN VTermPos state_pos;
+EXTERN bool want_state_putglyph INIT (=false);
+EXTERN bool want_state_movecursor INIT(= false);
+EXTERN bool want_state_erase INIT(= false);
+EXTERN bool want_state_scrollrect INIT(= false);
+EXTERN bool want_state_moverect INIT(= false);
+EXTERN bool want_state_settermprop INIT(= false);
+EXTERN bool want_state_scrollback INIT(= false);
+EXTERN bool want_screen_scrollback INIT(= false);
diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua
index 4ea5d9c29a..a05579b4ff 100644
--- a/test/unit/vterm_spec.lua
+++ b/test/unit/vterm_spec.lua
@@ -79,7 +79,11 @@ local bit = require('bit')
 --- @field vterm_state_set_callbacks function
 --- @field vterm_state_set_selection_callbacks function
 --- @field vterm_state_set_unrecognised_fallbacks function
-local vterm = t.cimport('./src/vterm/vterm.h', './src/vterm/vterm_internal.h')
+local vterm = t.cimport(
+  './src/vterm/vterm.h',
+  './src/vterm/vterm_internal.h',
+  './test/unit/fixtures/vterm_test.h'
+)
 
 --- @return string
 local function read_rm()
-- 
cgit 


From 747e84a256036927585ce07d1162ef7ab73d4fd2 Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Tue, 26 Nov 2024 15:20:36 -0500
Subject: ci: run one set of tests with a release build

This ensures that no tests fail due to differences between release and debug builds.

The release build-type check is now unnecessary, too, so remove it.
---
 .github/workflows/test.yml | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0885efddd5..7320c4ad81 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -106,7 +106,7 @@ jobs:
           [
             { runner: ubuntu-24.04, os: ubuntu, flavor: asan, cc: clang, flags: -D ENABLE_ASAN_UBSAN=ON },
             { runner: ubuntu-24.04, os: ubuntu, flavor: tsan, cc: clang, flags: -D ENABLE_TSAN=ON },
-            { runner: ubuntu-24.04, os: ubuntu, cc: gcc },
+            { runner: ubuntu-24.04, os: ubuntu, flavor: release, cc: gcc, flags: -D CMAKE_BUILD_TYPE=Release },
             { runner: macos-13, os: macos, flavor: intel, cc: clang, flags: -D CMAKE_FIND_FRAMEWORK=NEVER, deps_flags: -D CMAKE_FIND_FRAMEWORK=NEVER },
             { runner: macos-15, os: macos, flavor: arm, cc: clang, flags: -D CMAKE_FIND_FRAMEWORK=NEVER, deps_flags: -D CMAKE_FIND_FRAMEWORK=NEVER },
             { runner: ubuntu-24.04, os: ubuntu, flavor: puc-lua, cc: gcc, deps_flags: -D USE_BUNDLED_LUAJIT=OFF -D USE_BUNDLED_LUA=ON, flags: -D PREFER_LUA=ON },
@@ -194,7 +194,7 @@ jobs:
     uses: ./.github/workflows/test_windows.yml
 
   # This job tests the following things:
-  # - Check if Release, MinSizeRel and RelWithDebInfo compiles correctly.
+  # - Check if MinSizeRel and RelWithDebInfo compiles correctly.
   # - Test the above build types with the GCC compiler specifically.
   #   Empirically the difference in warning levels between GCC and other
   #   compilers is particularly big.
@@ -218,9 +218,6 @@ jobs:
       - name: Configure
         run: cmake --preset ci -G "Ninja Multi-Config"
 
-      - name: Release
-        run: cmake --build build --config Release
-
       - name: RelWithDebInfo
         run: cmake --build build --config RelWithDebInfo
 
-- 
cgit 


From 05dd41f3e911b18b9e48180715a37abbb02266d0 Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Tue, 26 Nov 2024 15:27:52 -0500
Subject: ci(test): remove the .git directory for Linux

Tests should not rely on being run inside a git clone, so the Linux
builds cover this use case. The macOS builds will continue running with
the .git directory so there's still unix-ish coverage within a git
clone.
---
 .github/workflows/test.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7320c4ad81..106f77ea00 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -145,6 +145,10 @@ jobs:
           sudo cpanm -n Neovim::Ext || cat "$HOME/.cpanm/build.log"
           perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'
 
+      - name: Remove .git directory
+        if: ${{ matrix.build.os == 'ubuntu' }}
+        run: cmake -E rm -rf -- .git
+
       - name: Build third-party deps
         run: |
           cmake -S cmake.deps --preset ci -D CMAKE_BUILD_TYPE=Debug ${{ matrix.build.deps_flags }}
-- 
cgit 


From e5d96a69fd969ab15f866eba1bf4c141728b4f2e Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Tue, 26 Nov 2024 21:04:50 -0500
Subject: test(main_spec): use CMakePresets.json instead of .git for root
 marker

---
 test/functional/lua/fs_spec.lua | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index f0d49205e7..89f6ad6a0e 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -273,14 +273,14 @@ describe('vim.fs', function()
     end)
 
     it('works with a single marker', function()
-      eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]]))
+      eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
     end)
 
     it('works with multiple markers', function()
       local bufnr = api.nvim_get_current_buf()
       eq(
         vim.fs.joinpath(test_source_path, 'test/functional/fixtures'),
-        exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', '.git'})]], bufnr)
+        exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', 'CMakePresets.json'})]], bufnr)
       )
     end)
 
@@ -295,26 +295,26 @@ describe('vim.fs', function()
     end)
 
     it('works with a filename argument', function()
-      eq(test_source_path, exec_lua([[return vim.fs.root(..., '.git')]], nvim_prog))
+      eq(test_source_path, exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], nvim_prog))
     end)
 
     it('works with a relative path', function()
       eq(
         test_source_path,
-        exec_lua([[return vim.fs.root(..., '.git')]], vim.fs.basename(nvim_prog))
+        exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], vim.fs.basename(nvim_prog))
       )
     end)
 
     it('uses cwd for unnamed buffers', function()
       command('new')
-      eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]]))
+      eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
     end)
 
     it("uses cwd for buffers with non-empty 'buftype'", function()
       command('new')
       command('set buftype=nofile')
       command('file lua://')
-      eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]]))
+      eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
     end)
   end)
 
-- 
cgit 


From 25bd2782a5f7cd6cec32d5d592a814951045a71b Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Tue, 26 Nov 2024 21:07:28 -0500
Subject: test(version_spec): expect vim.NIL, not nil, for "build" if not in a
 git clone

---
 test/functional/api/version_spec.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua
index 72b5e307ee..68c4ef7503 100644
--- a/test/functional/api/version_spec.lua
+++ b/test/functional/api/version_spec.lua
@@ -43,7 +43,7 @@ describe("api_info()['version']", function()
     eq(0, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. (patch + 1)))
     eq(0, fn.has('nvim-' .. major .. '.' .. (minor + 1) .. '.' .. patch))
     eq(0, fn.has('nvim-' .. (major + 1) .. '.' .. minor .. '.' .. patch))
-    assert(build == nil or type(build) == 'string')
+    assert(build == vim.NIL or type(build) == 'string')
   end)
 end)
 
-- 
cgit 


From 9123bc0f3ff1607494dc859fa4df8eba3ec15540 Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Tue, 26 Nov 2024 22:11:20 -0500
Subject: test(main_spec): make "nvim -v" test agnostic to build type

In release builds, the Compilation: line is omitted so the build is reproducible. Since the "fall-back for $VIM" line is always present, check for that instead.
---
 test/functional/core/main_spec.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index a445423efc..3b7cefb89f 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -188,9 +188,9 @@ describe('command-line option', function()
 
   it('nvim -v, :version', function()
     matches('Run ":verbose version"', fn.execute(':version'))
-    matches('Compilation: .*Run :checkhealth', fn.execute(':verbose version'))
+    matches('fall%-back for %$VIM: .*Run :checkhealth', fn.execute(':verbose version'))
     matches('Run "nvim %-V1 %-v"', fn.system({ nvim_prog_abs(), '-v' }))
-    matches('Compilation: .*Run :checkhealth', fn.system({ nvim_prog_abs(), '-V1', '-v' }))
+    matches('fall%-back for %$VIM: .*Run :checkhealth', fn.system({ nvim_prog_abs(), '-V1', '-v' }))
   end)
 
   if is_os('win') then
-- 
cgit 


From c410375d4d7df80577c66d1241069d7f9f62983a Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Wed, 27 Nov 2024 07:55:55 -0500
Subject: ci: run tests directly rather than via the Makefile

Since the Makefile is not used to build, running the tests via the Makefile causes cmake to reconfigure and revert the release build back to debug.
---
 .github/workflows/test.yml | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 106f77ea00..c7802d2210 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -159,9 +159,15 @@ jobs:
           cmake --preset ci -D CMAKE_BUILD_TYPE=Debug -D CMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX ${{ matrix.build.flags }}
           cmake --build build
 
-      - name: ${{ matrix.test }}
+      - if: ${{ matrix.test == 'oldtest' }}
+        name: ${{ matrix.test }}
         timeout-minutes: 20
-        run: make ${{ matrix.test }}
+        run: make -C test/old/testdir NVIM_PRG=$(realpath build)/bin/nvim
+
+      - if: ${{ matrix.test != 'oldtest' }}
+        name: ${{ matrix.test }}
+        timeout-minutes: 20
+        run: cmake --build build --target ${{ matrix.test }}
 
       - name: Install
         run: |
-- 
cgit 


From 48bdbf12d0db441bb565c306385a88d05b3903b2 Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Tue, 3 Dec 2024 06:34:07 -0500
Subject: test(marktree): expose test functions in release builds

In order to run the marktree unit test in release mode, the test functions need to be available even when NDEBUG is defined.

Keep the body of marktree_check a nop during release builds, which limits the usefulness of the testing, but at least lets the tests run.
---
 src/nvim/marktree.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 555fef5bbd..5ccd4fd45d 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -2330,7 +2330,6 @@ void marktree_check(MarkTree *b)
 #endif
 }
 
-#ifndef NDEBUG
 size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right,
                            const uint32_t *meta_node_ref)
 {
@@ -2485,8 +2484,6 @@ bool mt_recurse_nodes_compare(MTNode *x, PMap(ptr_t) *checked)
   return true;
 }
 
-#endif
-
 // TODO(bfredl): kv_print
 #define GA_PUT(x) ga_concat(ga, (char *)(x))
 #define GA_PRINT(fmt, ...) snprintf(buf, sizeof(buf), fmt, __VA_ARGS__); \
-- 
cgit 


From 0e299ebf75591b262d659704e84e3d5e7fc2aed9 Mon Sep 17 00:00:00 2001
From: JD <46619169+rudiejd@users.noreply.github.com>
Date: Tue, 3 Dec 2024 06:41:37 -0500
Subject: fix(decorator): noisy errors from decoration provider #31418

Problem:
A broken decoration provider can cause endless errors. #27235

Solution:
Don't show decorator errors when they exceed `DP_MAX_ERROR`.
---
 src/nvim/decoration_provider.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c
index 74f444d8e8..805e9877b6 100644
--- a/src/nvim/decoration_provider.c
+++ b/src/nvim/decoration_provider.c
@@ -55,14 +55,13 @@ static bool decor_provider_invoke(int provider_idx, const char *name, LuaRef ref
   // We get the provider here via an index in case the above call to nlua_call_ref causes
   // decor_providers to be reallocated.
   DecorProvider *provider = &kv_A(decor_providers, provider_idx);
-
   if (!ERROR_SET(&err)
       && api_object_to_bool(ret, "provider %s retval", default_true, &err)) {
     provider->error_count = 0;
     return true;
   }
 
-  if (ERROR_SET(&err)) {
+  if (ERROR_SET(&err) && provider->error_count < DP_MAX_ERROR) {
     decor_provider_error(provider, name, err.msg);
     provider->error_count++;
 
-- 
cgit 


From 2495e7e22a0d56911d3677a17de3ff946b68a9a1 Mon Sep 17 00:00:00 2001
From: wzy <32936898+Freed-Wu@users.noreply.github.com>
Date: Tue, 3 Dec 2024 21:06:54 +0800
Subject: fix(clipboard): tmux clipboard depends on $TMUX #31268

Problem:
tmux clipboard not used when tmux is a daemon and $TMUX is empty.

Solution:
If `tmux list-buffers` succeeds, use tmux clipboard.
---
 runtime/autoload/provider/clipboard.vim | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index 58d3d4550f..848fa401f1 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -158,7 +158,7 @@ function! provider#clipboard#Executable() abort
     let s:copy['*'] = s:copy['+']
     let s:paste['*'] = s:paste['+']
     return 'termux-clipboard'
-  elseif !empty($TMUX) && executable('tmux')
+  elseif executable('tmux') && (!empty($TMUX) || 0 == jobwait([jobstart(['tmux', 'list-buffers'])], 2000)[0])
     let tmux_v = v:lua.vim.version.parse(system(['tmux', '-V']))
     if !empty(tmux_v) && !v:lua.vim.version.lt(tmux_v, [3,2,0])
       let s:copy['+'] = ['tmux', 'load-buffer', '-w', '-']
-- 
cgit 


From ae93c7f369a174f3d738ab55030de2c9dfc10c57 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Tue, 3 Dec 2024 09:44:28 -0800
Subject: docs: misc, help tags for neovim.io searches #31428

Problem:
Various keywords are commonly searched-for on https://neovim.io, but
don't have help tags.

Solution:
Add help tags.

fix #31327
---
 runtime/doc/api.txt             | 13 ++++++++++++-
 runtime/doc/builtin.txt         |  9 +++++----
 runtime/doc/dev_arch.txt        | 10 +++++-----
 runtime/doc/develop.txt         |  8 +++++---
 runtime/doc/gui.txt             |  3 ++-
 runtime/doc/intro.txt           | 24 +++++++++++++-----------
 runtime/doc/lua.txt             |  6 +++---
 runtime/doc/map.txt             |  2 +-
 runtime/doc/nvim.txt            | 36 +++++++++++++++++++++++++++---------
 runtime/doc/ui.txt              |  2 +-
 runtime/doc/various.txt         |  3 ++-
 runtime/doc/vim_diff.txt        |  2 +-
 runtime/lua/vim/_meta/api.lua   | 14 +++++++++++++-
 runtime/lua/vim/_meta/vimfn.lua | 13 +++++++------
 src/nvim/api/vim.c              | 14 +++++++++++++-
 src/nvim/eval.lua               |  8 +++++---
 16 files changed, 115 insertions(+), 52 deletions(-)

diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index c5dabeb551..9cb8f72348 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1114,7 +1114,7 @@ nvim_open_term({buffer}, {opts})                            *nvim_open_term()*
     Open a terminal instance in a buffer
 
     By default (and currently the only option) the terminal will not be
-    connected to an external process. Instead, input send on the channel will
+    connected to an external process. Instead, input sent on the channel will
     be echoed directly by the terminal. This is useful to display ANSI
     terminal sequences returned as part of a rpc message, or similar.
 
@@ -1125,6 +1125,17 @@ nvim_open_term({buffer}, {opts})                            *nvim_open_term()*
     |nvim_chan_send()| can be called immediately to process sequences in a
     virtual terminal having the intended size.
 
+    Example: this `TermHl` command can be used to display and highlight raw
+    ANSI termcodes, so you can use Nvim as a "scrollback pager" (for terminals
+    like kitty):                                   *terminal-scrollback-pager* >lua
+        vim.api.nvim_create_user_command('TermHl', function()
+          local b = vim.api.nvim_create_buf(false, true)
+          local chan = vim.api.nvim_open_term(b, {})
+          vim.api.nvim_chan_send(chan, table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n'))
+          vim.api.nvim_win_set_buf(0, b)
+        end, { desc = 'Highlights ANSI termcodes in curbuf' })
+<
+
     Attributes: ~
         not allowed when |textlock| is active
 
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 585db21a0b..304e63fd95 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -5443,7 +5443,7 @@ jobwait({jobs} [, {timeout}])                                        *jobwait()*
                   • {timeout} (`integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer[]`)
 
 join({list} [, {sep}])                                                  *join()*
 		Join the items in {list} together into one String.
@@ -7986,7 +7986,7 @@ rpcnotify({channel}, {event} [, {args}...])                        *rpcnotify()*
                 Parameters: ~
                   • {channel} (`integer`)
                   • {event} (`string`)
-                  • {args} (`any?`)
+                  • {...} (`any`)
 
                 Return: ~
                   (`any`)
@@ -8001,7 +8001,7 @@ rpcrequest({channel}, {method} [, {args}...])                     *rpcrequest()*
                 Parameters: ~
                   • {channel} (`integer`)
                   • {method} (`string`)
-                  • {args} (`any?`)
+                  • {...} (`any`)
 
                 Return: ~
                   (`any`)
@@ -10234,6 +10234,7 @@ str2list({string} [, {utf8}])                                       *str2list()*
 		and exists only for backwards-compatibility.
 		With UTF-8 composing characters are handled properly: >vim
 			echo str2list("á")		" returns [97, 769]
+<
 
                 Parameters: ~
                   • {string} (`string`)
@@ -11991,7 +11992,7 @@ winlayout([{tabnr}])                                               *winlayout()*
                   • {tabnr} (`integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`any[]`)
 
 winline()                                                            *winline()*
 		The result is a Number, which is the screen line of the cursor
diff --git a/runtime/doc/dev_arch.txt b/runtime/doc/dev_arch.txt
index 1cb3b9ad67..2be6221117 100644
--- a/runtime/doc/dev_arch.txt
+++ b/runtime/doc/dev_arch.txt
@@ -46,11 +46,11 @@ Remember to bump NVIM_API_LEVEL if it wasn't already during this development
 cycle.
 
 Other references:
-* |msgpack-rpc|
-* |ui|
-* https://github.com/neovim/neovim/pull/3246
-* https://github.com/neovim/neovim/pull/18375
-* https://github.com/neovim/neovim/pull/21605
+- |msgpack-rpc|
+- |ui|
+- https://github.com/neovim/neovim/pull/3246
+- https://github.com/neovim/neovim/pull/18375
+- https://github.com/neovim/neovim/pull/21605
 
 
 
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index a61c569a67..da64475465 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -300,7 +300,7 @@ vim.paste in runtime/lua/vim/_editor.lua like this: >
     --- @returns false if client should cancel the paste.
 
 
-LUA STDLIB DESIGN GUIDELINES                            *dev-lua*
+STDLIB DESIGN GUIDELINES                                             *dev-lua*
 
 See also |dev-naming|.
 
@@ -337,7 +337,7 @@ preference):
     way. Advantage is that propagation happens for free and it's harder to
     accidentally swallow errors. (E.g. using
     `uv_handle/pipe:write()` without checking return values is common.)
-4. `on_error` parameter
+4. `on_error` callback
   - For async and "visitors" traversing a graph, where many errors may be
     collected while work continues.
 5. `vim.notify` (sometimes with optional `opts.silent` (async, visitors ^))
@@ -434,7 +434,9 @@ Use existing common {verb} names (actions) if possible:
     - eval:         Evaluates an expression
     - exec:         Executes code, may return a result
     - fmt:          Formats
-    - get:          Gets things (often by a query)
+    - get:          Gets things. Two variants (overloads):
+                    1. `get(id: int): T` returns one item.
+                    2. `get(filter: dict): T[]` returns a list.
     - inspect:      Presents a high-level, often interactive, view
     - is_enabled:   Checks if functionality is enabled.
     - open:         Opens something (a buffer, window, …)
diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt
index 21f1ba8241..85a0df03f9 100644
--- a/runtime/doc/gui.txt
+++ b/runtime/doc/gui.txt
@@ -20,8 +20,9 @@ features, whereas help tags with the "ui-" prefix refer to the |ui-protocol|.
 Nvim provides a default, builtin UI (the |TUI|), but there are many other
 (third-party) GUIs that you can use instead:
 
-- Firenvim (Nvim in your web browser!) https://github.com/glacambre/firenvim
+							*vscode*
 - vscode-neovim (Nvim in VSCode!) https://github.com/vscode-neovim/vscode-neovim
+- Firenvim (Nvim in your web browser!) https://github.com/glacambre/firenvim
 - Neovide https://neovide.dev/
 - Goneovim https://github.com/akiyosi/goneovim
 - Nvy https://github.com/RMichelsen/Nvy
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index d099c29bdb..85169fcc17 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -49,17 +49,19 @@ For more information try one of these:
 ==============================================================================
 Nvim on the interwebs					*internet*
 
-			*www* *distribution* *download*
-
-	Nvim home page:	  https://neovim.io/
-	Downloads:	  https://github.com/neovim/neovim/releases
-	Vim FAQ:	  https://vimhelp.org/vim_faq.txt.html
-
-
-			*bugs* *bug-report*
-Report bugs and request features here:
-https://github.com/neovim/neovim/issues
-
+			*www* *distribution*
+- Nvim home page: https://neovim.io/
+- Vim FAQ: https://vimhelp.org/vim_faq.txt.html
+
+			*download* *upgrade* *ubuntu*
+To install or upgrade Nvim, you can...
+- Download a pre-built archive:
+  https://github.com/neovim/neovim/releases
+- Use your system package manager:
+  https://github.com/neovim/neovim/blob/master/INSTALL.md#install-from-package
+
+			*bugs* *bug-report* *feature-request*
+Report bugs and request features here: https://github.com/neovim/neovim/issues
 Be brief, yet complete.  Always give a reproducible example and try to find
 out which settings or other things trigger the bug.
 
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 047e263768..48fa595a53 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -17,9 +17,9 @@ get an idea of what lurks beneath: >vim
     :lua vim.print(package.loaded)
 
 Nvim includes a "standard library" |lua-stdlib| for Lua.  It complements the
-"editor stdlib" (|builtin-functions| and |Ex-commands|) and the |API|, all of
-which can be used from Lua code (|lua-vimscript| |vim.api|). Together these
-"namespaces" form the Nvim programming interface.
+"editor stdlib" (|vimscript-functions| + |Ex-commands|) and the |API|, all of
+which can be used from Lua code (|lua-vimscript| |vim.api|). These three
+namespaces form the Nvim programming interface.
 
 Lua plugins and user config are automatically discovered and loaded, just like
 Vimscript. See |lua-guide| for practical guidance.
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 11048aee30..34cf309576 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -12,7 +12,7 @@ manual.
                                       Type |gO| to see the table of contents.
 
 ==============================================================================
-1. Key mapping				*key-mapping* *mapping* *macro*
+1. Key mapping			      *keybind* *key-mapping* *mapping* *macro*
 
 Key mapping is used to change the meaning of typed keys.  The most common use
 is to define a sequence of commands for a function key.  Example: >
diff --git a/runtime/doc/nvim.txt b/runtime/doc/nvim.txt
index 86e344c654..8593511dbc 100644
--- a/runtime/doc/nvim.txt
+++ b/runtime/doc/nvim.txt
@@ -6,21 +6,23 @@
 
 Nvim                                              *nvim* *neovim* *nvim-intro*
 
-Nvim is based on Vim by Bram Moolenaar.
+Nvim is based on Vim by Bram Moolenaar. Nvim is emphatically a fork of Vim,
+not a clone: compatibility with Vim (especially editor and Vimscript features,
+except |Vim9script|) is maintained where possible. See |vim-differences| for
+the complete reference.
 
-If you already use Vim see |nvim-from-vim| for a quickstart.
-If you are new to Vim, try the 30-minute tutorial: >vim
+If you already use Vim, see |nvim-from-vim| for a quickstart. If you just
+installed Nvim and have never used it before, watch this 10-minute
+video: https://youtu.be/TQn2hJeHQbM .
 
-    :Tutor
-
-Nvim is emphatically a fork of Vim, not a clone: compatibility with Vim
-(especially editor and Vimscript features) is maintained where possible. See
-|vim-differences| for the complete reference of differences from Vim.
+To learn how to use Vim in 30 minutes, try the tutorial: >vim
 
+    :Tutor
+<
                                       Type |gO| to see the table of contents.
 
 ==============================================================================
-Transitioning from Vim                          *nvim-from-vim*
+Transitioning from Vim                                         *nvim-from-vim*
 
 1. To start the transition, create your |init.vim| (user config) file: >vim
 
@@ -70,5 +72,21 @@ the same Nvim configuration on all of your machines, by creating
 ~/AppData/Local/nvim/init.vim containing just this line: >vim
     source ~/.config/nvim/init.vim
 
+==============================================================================
+What next?                                                   *nvim-quickstart*
+
+If you are just trying out Nvim for a few minutes, and want to see the
+extremes of what it can do, try one of these popular "extension packs" or
+"distributions" (Note: Nvim is not affiliated with these projects, and does
+not support them):
+
+- *kickstart* https://github.com/nvim-lua/kickstart.nvim
+- *lazyvim* https://www.lazyvim.org/
+- *nvchad* https://nvchad.com/
+
+However, in general, we recommend (eventually) taking time to learn Nvim from
+its stock configuration, and incrementally setting options and adding plugins
+to your |config| as you find an explicit need to do so.
+
 ==============================================================================
  vim:tw=78:ts=8:et:ft=help:norl:
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 55eba484bc..77eddfd8e1 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -788,7 +788,7 @@ must handle.
 
 	kind
 	    Name indicating the message kind:
-		"" (empty)	Unknown (consider a feature-request: |bugs|)
+		"" (empty)	Unknown (consider a |feature-request|)
 		"confirm"	|confirm()| or |:confirm| dialog
 		"confirm_sub"	|:substitute| confirm dialog |:s_c|
 		"emsg"		Error (|errors|, internal error, |:throw|, …)
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index d967e7c75b..5049439337 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -534,7 +534,8 @@ gO			Show a filetype-specific, navigable "outline" of the
 			current buffer. For example, in a |help| buffer this
 			shows the table of contents.
 
-			Currently works in |help| and |:Man| buffers.
+			Works in |help| and |:Man| buffers, or any buffer with
+			an active |LSP| client (|lsp-defaults|).
 
 [N]gs							*gs* *:sl* *:sleep*
 :[N]sl[eep] [N][m]	Do nothing for [N] seconds, or [N] milliseconds if [m]
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 6e1a9adb83..8e19df0160 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -686,7 +686,7 @@ Cscope:
   https://github.com/dhananjaylatkar/cscope_maps.nvim
 
 Eval:
-- Vim9script
+- *Vim9script* (the Vim 9+ flavor of Vimscript) is not supported.
 - *cscope_connection()*
 - *err_teapot()*
 - *js_encode()*
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index acd12b353d..d74ee11b46 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -1654,7 +1654,7 @@ function vim.api.nvim_notify(msg, log_level, opts) end
 --- Open a terminal instance in a buffer
 ---
 --- By default (and currently the only option) the terminal will not be
---- connected to an external process. Instead, input send on the channel
+--- connected to an external process. Instead, input sent on the channel
 --- will be echoed directly by the terminal. This is useful to display
 --- ANSI terminal sequences returned as part of a rpc message, or similar.
 ---
@@ -1665,6 +1665,18 @@ function vim.api.nvim_notify(msg, log_level, opts) end
 --- Then `nvim_chan_send()` can be called immediately to process sequences
 --- in a virtual terminal having the intended size.
 ---
+--- Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you
+--- can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]()
+---
+--- ```lua
+--- vim.api.nvim_create_user_command('TermHl', function()
+---   local b = vim.api.nvim_create_buf(false, true)
+---   local chan = vim.api.nvim_open_term(b, {})
+---   vim.api.nvim_chan_send(chan, table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n'))
+---   vim.api.nvim_win_set_buf(0, b)
+--- end, { desc = 'Highlights ANSI termcodes in curbuf' })
+--- ```
+---
 --- @param buffer integer the buffer to use (expected to be empty)
 --- @param opts vim.api.keyset.open_term Optional parameters.
 --- - on_input: Lua callback for input sent, i e keypresses in terminal
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index f9b5d93a4b..e207f641de 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -4928,7 +4928,7 @@ function vim.fn.jobstop(id) end
 ---
 --- @param jobs integer[]
 --- @param timeout? integer
---- @return any
+--- @return integer[]
 function vim.fn.jobwait(jobs, timeout) end
 
 --- Join the items in {list} together into one String.
@@ -7244,9 +7244,9 @@ function vim.fn.round(expr) end
 ---
 --- @param channel integer
 --- @param event string
---- @param args? any
+--- @param ... any
 --- @return any
-function vim.fn.rpcnotify(channel, event, args) end
+function vim.fn.rpcnotify(channel, event, ...) end
 
 --- Sends a request to {channel} to invoke {method} via
 --- |RPC| and blocks until a response is received.
@@ -7256,9 +7256,9 @@ function vim.fn.rpcnotify(channel, event, args) end
 ---
 --- @param channel integer
 --- @param method string
---- @param args? any
+--- @param ... any
 --- @return any
-function vim.fn.rpcrequest(channel, method, args) end
+function vim.fn.rpcrequest(channel, method, ...) end
 
 --- @deprecated
 --- Deprecated. Replace  >vim
@@ -9328,6 +9328,7 @@ function vim.fn.str2float(string, quoted) end
 --- and exists only for backwards-compatibility.
 --- With UTF-8 composing characters are handled properly: >vim
 ---   echo str2list("á")    " returns [97, 769]
+--- <
 ---
 --- @param string string
 --- @param utf8? boolean
@@ -10870,7 +10871,7 @@ function vim.fn.winheight(nr) end
 --- <
 ---
 --- @param tabnr? integer
---- @return any
+--- @return any[]
 function vim.fn.winlayout(tabnr) end
 
 --- The result is a Number, which is the screen line of the cursor
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index ab52612f9f..1262af5e40 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1040,7 +1040,7 @@ fail:
 /// Open a terminal instance in a buffer
 ///
 /// By default (and currently the only option) the terminal will not be
-/// connected to an external process. Instead, input send on the channel
+/// connected to an external process. Instead, input sent on the channel
 /// will be echoed directly by the terminal. This is useful to display
 /// ANSI terminal sequences returned as part of a rpc message, or similar.
 ///
@@ -1051,6 +1051,18 @@ fail:
 /// Then |nvim_chan_send()| can be called immediately to process sequences
 /// in a virtual terminal having the intended size.
 ///
+/// Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you
+/// can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]()
+///
+/// ```lua
+/// vim.api.nvim_create_user_command('TermHl', function()
+///   local b = vim.api.nvim_create_buf(false, true)
+///   local chan = vim.api.nvim_open_term(b, {})
+///   vim.api.nvim_chan_send(chan, table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n'))
+///   vim.api.nvim_win_set_buf(0, b)
+/// end, { desc = 'Highlights ANSI termcodes in curbuf' })
+/// ```
+///
 /// @param buffer the buffer to use (expected to be empty)
 /// @param opts   Optional parameters.
 ///          - on_input: Lua callback for input sent, i e keypresses in terminal
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index cd3ccf543e..08c9cd3991 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -6049,6 +6049,7 @@ M.funcs = {
     ]=],
     name = 'jobwait',
     params = { { 'jobs', 'integer[]' }, { 'timeout', 'integer' } },
+    returns = 'integer[]',
     signature = 'jobwait({jobs} [, {timeout}])',
   },
   join = {
@@ -8776,7 +8777,7 @@ M.funcs = {
       <
     ]=],
     name = 'rpcnotify',
-    params = { { 'channel', 'integer' }, { 'event', 'string' }, { 'args', 'any' } },
+    params = { { 'channel', 'integer' }, { 'event', 'string' }, { '...', 'any' } },
     signature = 'rpcnotify({channel}, {event} [, {args}...])',
   },
   rpcrequest = {
@@ -8789,7 +8790,7 @@ M.funcs = {
       <
     ]=],
     name = 'rpcrequest',
-    params = { { 'channel', 'integer' }, { 'method', 'string' }, { 'args', 'any' } },
+    params = { { 'channel', 'integer' }, { 'method', 'string' }, { '...', 'any' } },
     signature = 'rpcrequest({channel}, {method} [, {args}...])',
   },
   rpcstart = {
@@ -11155,7 +11156,7 @@ M.funcs = {
       and exists only for backwards-compatibility.
       With UTF-8 composing characters are handled properly: >vim
       	echo str2list("á")		" returns [97, 769]
-
+      <
     ]=],
     name = 'str2list',
     params = { { 'string', 'string' }, { 'utf8', 'boolean' } },
@@ -13091,6 +13092,7 @@ M.funcs = {
     ]=],
     name = 'winlayout',
     params = { { 'tabnr', 'integer' } },
+    returns = 'any[]',
     signature = 'winlayout([{tabnr}])',
   },
   winline = {
-- 
cgit 


From b079a9d2e76062ee7275e35a4623108550e836a5 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 4 Dec 2024 17:49:12 +0800
Subject: vim-patch:9.1.0902: filetype: Conda configuration files are not
 recognized (#31445)

Problem:  filetype: Conda configuration files are not recognized
Solution: detect '.condarc' and 'condarc' files as yaml filetype.
          (zeertzjq)

closes: vim/vim#16162

https://github.com/vim/vim/commit/876de275cb3affa5910664cc52a5177c214313e8
---
 runtime/lua/vim/filetype.lua       | 2 ++
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 5d771c30e9..b4c37dd160 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1873,6 +1873,8 @@ local filename = {
   ['.clang-tidy'] = 'yaml',
   ['yarn.lock'] = 'yaml',
   matplotlibrc = 'yaml',
+  ['.condarc'] = 'yaml',
+  condarc = 'yaml',
   zathurarc = 'zathurarc',
   ['/etc/zprofile'] = 'zsh',
   ['.zlogin'] = 'zsh',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 06ac59de20..5a38f26902 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -886,7 +886,7 @@ func s:GetFilenameChecks() abort
     \ 'xslt': ['file.xsl', 'file.xslt'],
     \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
     \ 'yaml': ['file.yaml', 'file.yml', 'file.eyaml', 'any/.bundle/config', '.clangd', '.clang-format', '.clang-tidy', 'file.mplstyle', 'matplotlibrc', 'yarn.lock',
-    \          '/home/user/.kube/config'],
+    \          '/home/user/.kube/config', '.condarc', 'condarc'],
     \ 'yang': ['file.yang'],
     \ 'yuck': ['file.yuck'],
     \ 'z8a': ['file.z8a'],
-- 
cgit 


From e56437cd48f7df87ccdfb79812ee56241c0da0cb Mon Sep 17 00:00:00 2001
From: Maria José Solano 
Date: Wed, 4 Dec 2024 05:14:47 -0800
Subject: feat(lsp): deprecate vim.lsp.start_client #31341

Problem:
LSP module has multiple "start" interfaces.

Solution:
- Enhance vim.lsp.start
- Deprecate vim.lsp.start_client
---
 runtime/doc/deprecated.txt                         |   1 +
 runtime/doc/lsp.txt                                |  36 ++--
 runtime/lua/vim/lsp.lua                            | 208 ++++++++++++---------
 runtime/lua/vim/lsp/client.lua                     |   8 +-
 runtime/lua/vim/lsp/rpc.lua                        |   2 +-
 test/functional/plugin/lsp/diagnostic_spec.lua     |   4 +-
 .../functional/plugin/lsp/semantic_tokens_spec.lua |   2 +-
 test/functional/plugin/lsp_spec.lua                |   4 +-
 8 files changed, 139 insertions(+), 126 deletions(-)

diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index c6ca5e5ce9..ab9c0b2ce8 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -64,6 +64,7 @@ LSP
 • `client.is_stopped()`			Use |Client:is_stopped()| instead.
 • `client.supports_method()`		Use |Client:supports_method()| instead.
 • `client.on_attach()`			Use |Client:on_attach()| instead.
+• `vim.lsp.start_client()`		Use |vim.lsp.start()| instead.
 
 ------------------------------------------------------------------------------
 DEPRECATED IN 0.10					*deprecated-0.10*
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index e311831bd3..2654d7f14f 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -841,12 +841,11 @@ start({config}, {opts})                                      *vim.lsp.start()*
         })
 <
 
-    See |vim.lsp.start_client()| for all available options. The most important
+    See |vim.lsp.ClientConfig| for all available options. The most important
     are:
     • `name` arbitrary name for the LSP client. Should be unique per language
       server.
-    • `cmd` command string[] or function, described at
-      |vim.lsp.start_client()|.
+    • `cmd` command string[] or function.
     • `root_dir` path to the project root. By default this is used to decide
       if an existing client should be re-used. The example above uses
       |vim.fs.root()| to detect the root by traversing the file system upwards
@@ -868,7 +867,7 @@ start({config}, {opts})                                      *vim.lsp.start()*
     Parameters: ~
       • {config}  (`vim.lsp.ClientConfig`) Configuration for the server. See
                   |vim.lsp.ClientConfig|.
-      • {opts}    (`table?`) Optional keyword arguments
+      • {opts}    (`table?`) Optional keyword arguments.
                   • {reuse_client}?
                     (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`)
                     Predicate used to decide if a client should be re-used.
@@ -876,25 +875,15 @@ start({config}, {opts})                                      *vim.lsp.start()*
                     re-uses a client if name and root_dir matches.
                   • {bufnr}? (`integer`) Buffer handle to attach to if
                     starting or re-using a client (0 for current).
+                  • {attach}? (`boolean`) Whether to attach the client to a
+                    buffer (default true). If set to `false`, `reuse_client`
+                    and `bufnr` will be ignored.
                   • {silent}? (`boolean`) Suppress error reporting if the LSP
                     server fails to start (default false).
 
     Return: ~
         (`integer?`) client_id
 
-start_client({config})                                *vim.lsp.start_client()*
-    Starts and initializes a client with the given configuration.
-
-    Parameters: ~
-      • {config}  (`vim.lsp.ClientConfig`) Configuration for the server. See
-                  |vim.lsp.ClientConfig|.
-
-    Return (multiple): ~
-        (`integer?`) client_id |vim.lsp.get_client_by_id()| Note: client may
-        not be fully initialized. Use `on_init` to do any actions once the
-        client has been initialized.
-        (`string?`) Error message, if any
-
 status()                                                    *vim.lsp.status()*
     Consumes the latest progress messages from all clients and formats them as
     a string. Empty if there are no clients or if no new messages
@@ -968,8 +957,7 @@ Lua module: vim.lsp.client                                        *lsp-client*
                                 server.
       • {config}                (`vim.lsp.ClientConfig`) copy of the table
                                 that was passed by the user to
-                                |vim.lsp.start_client()|. See
-                                |vim.lsp.ClientConfig|.
+                                |vim.lsp.start()|. See |vim.lsp.ClientConfig|.
       • {server_capabilities}   (`lsp.ServerCapabilities?`) Response from the
                                 server sent on `initialize` describing the
                                 server's capabilities.
@@ -1093,7 +1081,7 @@ Lua module: vim.lsp.client                                        *lsp-client*
       • {commands}?           (`table`)
                               Table that maps string of clientside commands to
                               user-defined functions. Commands passed to
-                              start_client take precedence over the global
+                              `start()` take precedence over the global
                               command registry. Each key must be a unique
                               command name, and the value is a function which
                               is called if any LSP action (code action, code
@@ -1122,9 +1110,9 @@ Lua module: vim.lsp.client                                        *lsp-client*
                               Callback invoked before the LSP "initialize"
                               phase, where `params` contains the parameters
                               being sent to the server and `config` is the
-                              config that was passed to
-                              |vim.lsp.start_client()|. You can use this to
-                              modify parameters before they are sent.
+                              config that was passed to |vim.lsp.start()|. You
+                              can use this to modify parameters before they
+                              are sent.
       • {on_init}?            (`elem_or_list`)
                               Callback invoked after LSP "initialize", where
                               `result` is a table of `capabilities` and
@@ -2296,7 +2284,7 @@ connect({host_or_path}, {port})                        *vim.lsp.rpc.connect()*
     • a host and port via TCP
 
     Return a function that can be passed to the `cmd` field for
-    |vim.lsp.start_client()| or |vim.lsp.start()|.
+    |vim.lsp.start()|.
 
     Parameters: ~
       • {host_or_path}  (`string`) host to connect to or path to a pipe/domain
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index b1a3316e3e..4717d7995a 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -211,17 +211,117 @@ local function reuse_client_default(client, config)
   return false
 end
 
+--- Reset defaults set by `set_defaults`.
+--- Must only be called if the last client attached to a buffer exits.
+local function reset_defaults(bufnr)
+  if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
+    vim.bo[bufnr].tagfunc = nil
+  end
+  if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
+    vim.bo[bufnr].omnifunc = nil
+  end
+  if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
+    vim.bo[bufnr].formatexpr = nil
+  end
+  vim._with({ buf = bufnr }, function()
+    local keymap = vim.fn.maparg('K', 'n', false, true)
+    if keymap and keymap.callback == vim.lsp.buf.hover and keymap.buffer == 1 then
+      vim.keymap.del('n', 'K', { buffer = bufnr })
+    end
+  end)
+end
+
+--- @param code integer
+--- @param signal integer
+--- @param client_id integer
+local function on_client_exit(code, signal, client_id)
+  local client = all_clients[client_id]
+
+  vim.schedule(function()
+    for bufnr in pairs(client.attached_buffers) do
+      if client and client.attached_buffers[bufnr] and api.nvim_buf_is_valid(bufnr) then
+        api.nvim_exec_autocmds('LspDetach', {
+          buffer = bufnr,
+          modeline = false,
+          data = { client_id = client_id },
+        })
+      end
+
+      client.attached_buffers[bufnr] = nil
+
+      if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then
+        reset_defaults(bufnr)
+      end
+    end
+
+    local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+    vim.diagnostic.reset(namespace)
+  end)
+
+  local name = client.name or 'unknown'
+
+  -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
+  -- autocommands
+  vim.schedule(function()
+    all_clients[client_id] = nil
+
+    -- Client can be absent if executable starts, but initialize fails
+    -- init/attach won't have happened
+    if client then
+      changetracking.reset(client)
+    end
+    if code ~= 0 or (signal ~= 0 and signal ~= 15) then
+      local msg = string.format(
+        'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
+        name,
+        code,
+        signal,
+        lsp.get_log_path()
+      )
+      vim.notify(msg, vim.log.levels.WARN)
+    end
+  end)
+end
+
+--- Creates and initializes a client with the given configuration.
+--- @param config vim.lsp.ClientConfig Configuration for the server.
+--- @return integer? client_id |vim.lsp.get_client_by_id()| Note: client may not be
+---         fully initialized. Use `on_init` to do any actions once
+---         the client has been initialized.
+--- @return string? # Error message, if any
+local function create_and_initialize_client(config)
+  local ok, res = pcall(require('vim.lsp.client').create, config)
+  if not ok then
+    return nil, res --[[@as string]]
+  end
+
+  local client = assert(res)
+
+  --- @diagnostic disable-next-line: invisible
+  table.insert(client._on_exit_cbs, on_client_exit)
+
+  all_clients[client.id] = client
+
+  client:initialize()
+
+  return client.id, nil
+end
+
 --- @class vim.lsp.start.Opts
 --- @inlinedoc
 ---
 --- Predicate used to decide if a client should be re-used. Used on all
 --- running clients. The default implementation re-uses a client if name and
 --- root_dir matches.
---- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean
+--- @field reuse_client? (fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean)
 ---
 --- Buffer handle to attach to if starting or re-using a client (0 for current).
 --- @field bufnr? integer
 ---
+--- Whether to attach the client to a buffer (default true).
+--- If set to `false`, `reuse_client` and `bufnr` will be ignored.
+--- @field attach? boolean
+---
 --- Suppress error reporting if the LSP server fails to start (default false).
 --- @field silent? boolean
 
@@ -239,10 +339,10 @@ end
 --- })
 --- ```
 ---
---- See |vim.lsp.start_client()| for all available options. The most important are:
+--- See |vim.lsp.ClientConfig| for all available options. The most important are:
 ---
 --- - `name` arbitrary name for the LSP client. Should be unique per language server.
---- - `cmd` command string[] or function, described at |vim.lsp.start_client()|.
+--- - `cmd` command string[] or function.
 --- - `root_dir` path to the project root. By default this is used to decide if an existing client
 ---   should be re-used. The example above uses |vim.fs.root()| to detect the root by traversing
 ---   the file system upwards starting from the current directory until either a `pyproject.toml`
@@ -262,7 +362,7 @@ end
 --- `ftplugin/.lua` (See |ftplugin-name|)
 ---
 --- @param config vim.lsp.ClientConfig Configuration for the server.
---- @param opts vim.lsp.start.Opts? Optional keyword arguments
+--- @param opts vim.lsp.start.Opts? Optional keyword arguments.
 --- @return integer? client_id
 function lsp.start(config, opts)
   opts = opts or {}
@@ -271,6 +371,10 @@ function lsp.start(config, opts)
 
   for _, client in pairs(all_clients) do
     if reuse_client(client, config) then
+      if opts.attach == false then
+        return client.id
+      end
+
       if lsp.buf_attach_client(bufnr, client.id) then
         return client.id
       else
@@ -279,7 +383,7 @@ function lsp.start(config, opts)
     end
   end
 
-  local client_id, err = lsp.start_client(config)
+  local client_id, err = create_and_initialize_client(config)
   if err then
     if not opts.silent then
       vim.notify(err, vim.log.levels.WARN)
@@ -287,6 +391,10 @@ function lsp.start(config, opts)
     return nil
   end
 
+  if opts.attach == false then
+    return client_id
+  end
+
   if client_id and lsp.buf_attach_client(bufnr, client_id) then
     return client_id
   end
@@ -383,78 +491,7 @@ function lsp._set_defaults(client, bufnr)
   end
 end
 
---- Reset defaults set by `set_defaults`.
---- Must only be called if the last client attached to a buffer exits.
-local function reset_defaults(bufnr)
-  if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
-    vim.bo[bufnr].tagfunc = nil
-  end
-  if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
-    vim.bo[bufnr].omnifunc = nil
-  end
-  if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
-    vim.bo[bufnr].formatexpr = nil
-  end
-  vim._with({ buf = bufnr }, function()
-    local keymap = vim.fn.maparg('K', 'n', false, true)
-    if keymap and keymap.callback == vim.lsp.buf.hover and keymap.buffer == 1 then
-      vim.keymap.del('n', 'K', { buffer = bufnr })
-    end
-  end)
-end
-
---- @param code integer
---- @param signal integer
---- @param client_id integer
-local function on_client_exit(code, signal, client_id)
-  local client = all_clients[client_id]
-
-  vim.schedule(function()
-    for bufnr in pairs(client.attached_buffers) do
-      if client and client.attached_buffers[bufnr] and api.nvim_buf_is_valid(bufnr) then
-        api.nvim_exec_autocmds('LspDetach', {
-          buffer = bufnr,
-          modeline = false,
-          data = { client_id = client_id },
-        })
-      end
-
-      client.attached_buffers[bufnr] = nil
-
-      if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then
-        reset_defaults(bufnr)
-      end
-    end
-
-    local namespace = vim.lsp.diagnostic.get_namespace(client_id)
-    vim.diagnostic.reset(namespace)
-  end)
-
-  local name = client.name or 'unknown'
-
-  -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
-  -- autocommands
-  vim.schedule(function()
-    all_clients[client_id] = nil
-
-    -- Client can be absent if executable starts, but initialize fails
-    -- init/attach won't have happened
-    if client then
-      changetracking.reset(client)
-    end
-    if code ~= 0 or (signal ~= 0 and signal ~= 15) then
-      local msg = string.format(
-        'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
-        name,
-        code,
-        signal,
-        lsp.get_log_path()
-      )
-      vim.notify(msg, vim.log.levels.WARN)
-    end
-  end)
-end
-
+--- @deprecated
 --- Starts and initializes a client with the given configuration.
 --- @param config vim.lsp.ClientConfig Configuration for the server.
 --- @return integer? client_id |vim.lsp.get_client_by_id()| Note: client may not be
@@ -462,21 +499,8 @@ end
 ---         the client has been initialized.
 --- @return string? # Error message, if any
 function lsp.start_client(config)
-  local ok, res = pcall(require('vim.lsp.client').create, config)
-  if not ok then
-    return nil, res --[[@as string]]
-  end
-
-  local client = assert(res)
-
-  --- @diagnostic disable-next-line: invisible
-  table.insert(client._on_exit_cbs, on_client_exit)
-
-  all_clients[client.id] = client
-
-  client:initialize()
-
-  return client.id, nil
+  vim.deprecate('vim.lsp.start_client()', 'vim.lsp.start()', '0.13')
+  return create_and_initialize_client(config)
 end
 
 ---Buffer lifecycle handler for textDocument/didSave
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index a14b6ccda6..a83d75bf75 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -78,7 +78,7 @@ local validate = vim.validate
 --- @field settings? table
 ---
 --- Table that maps string of clientside commands to user-defined functions.
---- Commands passed to start_client take precedence over the global command registry. Each key
+--- Commands passed to `start()` take precedence over the global command registry. Each key
 --- must be a unique command name, and the value is a function which is called if any LSP action
 --- (code action, code lenses, ...) triggers the command.
 --- @field commands? table
@@ -104,7 +104,7 @@ local validate = vim.validate
 --- @field on_error? fun(code: integer, err: string)
 ---
 --- Callback invoked before the LSP "initialize" phase, where `params` contains the parameters
---- being sent to the server and `config` is the config that was passed to |vim.lsp.start_client()|.
+--- being sent to the server and `config` is the config that was passed to |vim.lsp.start()|.
 --- You can use this to modify parameters before they are sent.
 --- @field before_init? fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
 ---
@@ -167,7 +167,7 @@ local validate = vim.validate
 --- @field requests table
 ---
 --- copy of the table that was passed by the user
---- to |vim.lsp.start_client()|.
+--- to |vim.lsp.start()|.
 --- @field config vim.lsp.ClientConfig
 ---
 --- Response from the server sent on `initialize` describing the server's
@@ -307,7 +307,7 @@ local function default_get_language_id(_bufnr, filetype)
   return filetype
 end
 
---- Validates a client configuration as given to |vim.lsp.start_client()|.
+--- Validates a client configuration as given to |vim.lsp.start()|.
 --- @param config vim.lsp.ClientConfig
 local function validate_config(config)
   validate('config', config, 'table')
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 6c8564845f..2327a37ab1 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -617,7 +617,7 @@ end
 ---  - a host and port via TCP
 ---
 --- Return a function that can be passed to the `cmd` field for
---- |vim.lsp.start_client()| or |vim.lsp.start()|.
+--- |vim.lsp.start()|.
 ---
 ---@param host_or_path string host to connect to or path to a pipe/domain socket
 ---@param port integer? TCP port to connect to. If absent the first argument must be a pipe
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
index ca9196562c..4ecb056d01 100644
--- a/test/functional/plugin/lsp/diagnostic_spec.lua
+++ b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -89,7 +89,7 @@ describe('vim.lsp.diagnostic', function()
         return extmarks
       end
 
-      client_id = assert(vim.lsp.start_client {
+      client_id = assert(vim.lsp.start({
         cmd_env = {
           NVIM_LUA_NOTRACK = '1',
         },
@@ -101,7 +101,7 @@ describe('vim.lsp.diagnostic', function()
           '--headless',
         },
         offset_encoding = 'utf-16',
-      })
+      }, { attach = false }))
     end)
 
     fake_uri = 'file:///fake/uri'
diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua
index 280bd27207..9912bf2063 100644
--- a/test/functional/plugin/lsp/semantic_tokens_spec.lua
+++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua
@@ -456,7 +456,7 @@ describe('semantic token highlighting', function()
         vim.notify = function(...)
           table.insert(_G.notifications, 1, { ... })
         end
-        return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd })
+        return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }, { attach = false })
       end)
       eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id))
 
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index e30d1ba411..e735e20ff5 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -95,7 +95,7 @@ describe('LSP', function()
       exec_lua(function()
         _G.lsp = require('vim.lsp')
         function _G.test__start_client()
-          return vim.lsp.start_client {
+          return vim.lsp.start({
             cmd_env = {
               NVIM_LOG_FILE = fake_lsp_logfile,
               NVIM_APPNAME = 'nvim_lsp_test',
@@ -112,7 +112,7 @@ describe('LSP', function()
                 name = 'test_folder',
               },
             },
-          }
+          }, { attach = false })
         end
         _G.TEST_CLIENT1 = _G.test__start_client()
       end)
-- 
cgit 


From 3cb1e825e679587f5c1c0e911fff4337ba0926a9 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Wed, 4 Dec 2024 14:20:24 +0100
Subject: fix(column): check if signcolumn changed in all windows #31439

---
 src/nvim/drawscreen.c            | 10 +++---
 test/functional/ui/sign_spec.lua | 73 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index e03cffd1ca..a939038603 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -1516,10 +1516,12 @@ static void win_update(win_T *wp)
 
   decor_providers_invoke_win(wp);
 
-  if (win_redraw_signcols(wp)) {
-    wp->w_lines_valid = 0;
-    wp->w_redr_type = UPD_NOT_VALID;
-    changed_line_abv_curs_win(wp);
+  FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
+    if (win->w_buffer == wp->w_buffer && win_redraw_signcols(win)) {
+      win->w_lines_valid = 0;
+      changed_line_abv_curs_win(win);
+      redraw_later(win, UPD_NOT_VALID);
+    }
   }
 
   init_search_hl(wp, &screen_search_hl);
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index 7874c04c39..b7a2429ada 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -607,4 +607,77 @@ describe('Signs', function()
     eq(6, infos[1].textoff)
     eq(6, infos[2].textoff)
   end)
+
+  it('auto width updated in all windows after sign placed in on_win #31438', function()
+    exec_lua([[
+      vim.cmd.call('setline(1, range(1, 500))')
+      vim.cmd('wincmd s | wincmd v | wincmd j | wincmd v')
+
+      _G.log, _G.needs_clear = {}, false
+      local ns_id, mark_id = vim.api.nvim_create_namespace('test'), nil
+
+      -- Add decoration which possibly clears all extmarks and adds one on line 499
+      local on_win = function(_, winid, bufnr, toprow, botrow)
+        if _G.needs_clear then
+          vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)
+          _G.needs_clear = false
+        end
+
+        if toprow < 499 and 499 <= botrow then
+          mark_id = vim.api.nvim_buf_set_extmark(bufnr, ns_id, 499, 0, { id = mark_id, sign_text = '!', invalidate = true })
+        end
+      end
+      vim.api.nvim_set_decoration_provider(ns_id, { on_win = on_win })
+    ]])
+    screen:expect([[
+      1                         │1                         |
+      2                         │2                         |
+      3                         │3                         |
+      4                         │4                         |
+      5                         │5                         |
+      6                         │6                         |
+      {2:[No Name] [+]              [No Name] [+]             }|
+      ^1                         │1                         |
+      2                         │2                         |
+      3                         │3                         |
+      4                         │4                         |
+      5                         │5                         |
+      {3:[No Name] [+]              }{2:[No Name] [+]             }|
+                                                           |
+    ]])
+    feed('G')
+    screen:expect([[
+      {7:  }1                       │{7:  }1                       |
+      {7:  }2                       │{7:  }2                       |
+      {7:  }3                       │{7:  }3                       |
+      {7:  }4                       │{7:  }4                       |
+      {7:  }5                       │{7:  }5                       |
+      {7:  }6                       │{7:  }6                       |
+      {2:[No Name] [+]              [No Name] [+]             }|
+      {7:  }496                     │{7:  }1                       |
+      {7:  }497                     │{7:  }2                       |
+      {7:  }498                     │{7:  }3                       |
+      {7:  }499                     │{7:  }4                       |
+      ! ^500                     │{7:  }5                       |
+      {3:[No Name] [+]              }{2:[No Name] [+]             }|
+                                                           |
+    ]])
+    feed(':lua log, needs_clear = {}, true')
+    screen:expect([[
+      {7:  }1                       │{7:  }1                       |
+      {7:  }2                       │{7:  }2                       |
+      {7:  }3                       │{7:  }3                       |
+      {7:  }4                       │{7:  }4                       |
+      {7:  }5                       │{7:  }5                       |
+      {7:  }6                       │{7:  }6                       |
+      {2:[No Name] [+]              [No Name] [+]             }|
+      {7:  }496                     │{7:  }1                       |
+      {7:  }497                     │{7:  }2                       |
+      {7:  }498                     │{7:  }3                       |
+      {7:  }499                     │{7:  }4                       |
+      ! ^500                     │{7:  }5                       |
+      {3:[No Name] [+]              }{2:[No Name] [+]             }|
+      :lua log, needs_clear = {}, true                     |
+    ]])
+  end)
 end)
-- 
cgit 


From 56d11b494b54862a9b55ca1efcaa35f26c56b3ff Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Wed, 4 Dec 2024 08:40:01 -0600
Subject: defaults: disable 'number', 'relativenumber', and 'signcolumn' in
 terminal buffers (#31443)

---
 runtime/doc/vim_diff.txt                 | 3 +++
 runtime/lua/vim/_defaults.lua            | 3 +++
 test/functional/terminal/cursor_spec.lua | 2 +-
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 8e19df0160..8d2d672ce1 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -186,6 +186,9 @@ nvim_terminal:
     - 'textwidth' set to 0
     - 'nowrap'
     - 'nolist'
+    - 'nonumber'
+    - 'norelativenumber'
+    - 'signcolumn' set to "no"
     - 'winhighlight' uses |hl-StatusLineTerm| and |hl-StatusLineTermNC| in
       place of |hl-StatusLine| and |hl-StatusLineNC|
 
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index 6583cf48b3..ef83a3ccc3 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -492,6 +492,9 @@ do
       vim.bo.textwidth = 0
       vim.wo[0][0].wrap = false
       vim.wo[0][0].list = false
+      vim.wo[0][0].number = false
+      vim.wo[0][0].relativenumber = false
+      vim.wo[0][0].signcolumn = 'no'
 
       -- This is gross. Proper list options support when?
       local winhl = vim.o.winhighlight
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index f223cdd417..4d25fe62ad 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -511,7 +511,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
 
   before_each(function()
     clear()
-    command('set number')
+    command('au TermOpen * set number')
   end)
 
   describe('in a line with no multibyte chars or trailing spaces,', function()
-- 
cgit 


From 734dba04d13bc7a6714134af322d49f333bfdc4c Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Tue, 3 Dec 2024 18:06:58 +0000
Subject: fix(vim.system): close pipe handles after process handle

Fixes #30846
---
 runtime/lua/vim/_system.lua         | 126 +++++++++++++++++++-----------------
 test/functional/lua/system_spec.lua |  45 ++++++++++---
 2 files changed, 103 insertions(+), 68 deletions(-)

diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua
index ce5dbffeaa..c0a0570e13 100644
--- a/runtime/lua/vim/_system.lua
+++ b/runtime/lua/vim/_system.lua
@@ -47,15 +47,6 @@ local function close_handle(handle)
   end
 end
 
----@param state vim.SystemState
-local function close_handles(state)
-  close_handle(state.handle)
-  close_handle(state.stdin)
-  close_handle(state.stdout)
-  close_handle(state.stderr)
-  close_handle(state.timer)
-end
-
 --- @class vim.SystemObj
 --- @field cmd string[]
 --- @field pid integer
@@ -132,9 +123,7 @@ function SystemObj:write(data)
     -- (https://github.com/neovim/neovim/pull/17620#discussion_r820775616)
     stdin:write('', function()
       stdin:shutdown(function()
-        if stdin then
-          stdin:close()
-        end
+        close_handle(stdin)
       end)
     end)
   end
@@ -146,25 +135,52 @@ function SystemObj:is_closing()
   return handle == nil or handle:is_closing() or false
 end
 
----@param output fun(err:string?, data: string?)|false
----@return uv.uv_stream_t?
----@return fun(err:string?, data: string?)? Handler
-local function setup_output(output)
-  if output == nil then
-    return assert(uv.new_pipe(false)), nil
+--- @param output? uv.read_start.callback|false
+--- @param text? boolean
+--- @return uv.uv_stream_t? pipe
+--- @return uv.read_start.callback? handler
+--- @return string[]? data
+local function setup_output(output, text)
+  if output == false then
+    return
   end
 
+  local bucket --- @type string[]?
+  local handler --- @type uv.read_start.callback
+
   if type(output) == 'function' then
-    return assert(uv.new_pipe(false)), output
+    handler = output
+  else
+    bucket = {}
+    handler = function(err, data)
+      if err then
+        error(err)
+      end
+      if text and data then
+        bucket[#bucket + 1] = data:gsub('\r\n', '\n')
+      else
+        bucket[#bucket + 1] = data
+      end
+    end
   end
 
-  assert(output == false)
-  return nil, nil
+  local pipe = assert(uv.new_pipe(false))
+
+  --- @type uv.read_start.callback
+  local function handler_with_close(err, data)
+    handler(err, data)
+    if data == nil then
+      pipe:read_stop()
+      pipe:close()
+    end
+  end
+
+  return pipe, handler_with_close, bucket
 end
 
----@param input string|string[]|true|nil
----@return uv.uv_stream_t?
----@return string|string[]?
+--- @param input? string|string[]|boolean
+--- @return uv.uv_stream_t?
+--- @return string|string[]?
 local function setup_input(input)
   if not input then
     return
@@ -208,28 +224,6 @@ local function setup_env(env, clear_env)
   return renv
 end
 
---- @param stream uv.uv_stream_t
---- @param text? boolean
---- @param bucket string[]
---- @return fun(err: string?, data: string?)
-local function default_handler(stream, text, bucket)
-  return function(err, data)
-    if err then
-      error(err)
-    end
-    if data ~= nil then
-      if text then
-        bucket[#bucket + 1] = data:gsub('\r\n', '\n')
-      else
-        bucket[#bucket + 1] = data
-      end
-    else
-      stream:read_stop()
-      stream:close()
-    end
-  end
-end
-
 local is_win = vim.fn.has('win32') == 1
 
 local M = {}
@@ -255,9 +249,9 @@ local function spawn(cmd, opts, on_exit, on_error)
   return handle, pid_or_err --[[@as integer]]
 end
 
----@param timeout integer
----@param cb fun()
----@return uv.uv_timer_t
+--- @param timeout integer
+--- @param cb fun()
+--- @return uv.uv_timer_t
 local function timer_oneshot(timeout, cb)
   local timer = assert(uv.new_timer())
   timer:start(timeout, 0, function()
@@ -273,7 +267,12 @@ end
 --- @param signal integer
 --- @param on_exit fun(result: vim.SystemCompleted)?
 local function _on_exit(state, code, signal, on_exit)
-  close_handles(state)
+  close_handle(state.handle)
+  close_handle(state.stdin)
+  close_handle(state.timer)
+
+  -- #30846: Do not close stdout/stderr here, as they may still have data to
+  -- read. They will be closed in uv.read_start on EOF.
 
   local check = assert(uv.new_check())
   check:start(function()
@@ -311,6 +310,15 @@ local function _on_exit(state, code, signal, on_exit)
   end)
 end
 
+--- @param state vim.SystemState
+local function _on_error(state)
+  close_handle(state.handle)
+  close_handle(state.stdin)
+  close_handle(state.stdout)
+  close_handle(state.stderr)
+  close_handle(state.timer)
+end
+
 --- Run a system command
 ---
 --- @param cmd string[]
@@ -324,8 +332,8 @@ function M.run(cmd, opts, on_exit)
 
   opts = opts or {}
 
-  local stdout, stdout_handler = setup_output(opts.stdout)
-  local stderr, stderr_handler = setup_output(opts.stderr)
+  local stdout, stdout_handler, stdout_data = setup_output(opts.stdout, opts.text)
+  local stderr, stderr_handler, stderr_data = setup_output(opts.stderr, opts.text)
   local stdin, towrite = setup_input(opts.stdin)
 
   --- @type vim.SystemState
@@ -335,7 +343,9 @@ function M.run(cmd, opts, on_exit)
     timeout = opts.timeout,
     stdin = stdin,
     stdout = stdout,
+    stdout_data = stdout_data,
     stderr = stderr,
+    stderr_data = stderr_data,
   }
 
   --- @diagnostic disable-next-line:missing-fields
@@ -350,17 +360,15 @@ function M.run(cmd, opts, on_exit)
   }, function(code, signal)
     _on_exit(state, code, signal, on_exit)
   end, function()
-    close_handles(state)
+    _on_error(state)
   end)
 
-  if stdout then
-    state.stdout_data = {}
-    stdout:read_start(stdout_handler or default_handler(stdout, opts.text, state.stdout_data))
+  if stdout and stdout_handler then
+    stdout:read_start(stdout_handler)
   end
 
-  if stderr then
-    state.stderr_data = {}
-    stderr:read_start(stderr_handler or default_handler(stderr, opts.text, state.stderr_data))
+  if stderr and stderr_handler then
+    stderr:read_start(stderr_handler)
   end
 
   local obj = new_systemobj(state)
diff --git a/test/functional/lua/system_spec.lua b/test/functional/lua/system_spec.lua
index 3f847ca3be..79a70c9c72 100644
--- a/test/functional/lua/system_spec.lua
+++ b/test/functional/lua/system_spec.lua
@@ -18,8 +18,7 @@ local function system_sync(cmd, opts)
     local res = obj:wait()
 
     -- Check the process is no longer running
-    local proc = vim.api.nvim_get_proc(obj.pid)
-    assert(not proc, 'process still exists')
+    assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists')
 
     return res
   end)
@@ -27,23 +26,23 @@ end
 
 local function system_async(cmd, opts)
   return exec_lua(function()
-    _G.done = false
+    local done = false
+    local res --- @type vim.SystemCompleted?
     local obj = vim.system(cmd, opts, function(obj)
-      _G.done = true
-      _G.ret = obj
+      done = true
+      res = obj
     end)
 
     local ok = vim.wait(10000, function()
-      return _G.done
+      return done
     end)
 
     assert(ok, 'process did not exit')
 
     -- Check the process is no longer running
-    local proc = vim.api.nvim_get_proc(obj.pid)
-    assert(not proc, 'process still exists')
+    assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists')
 
-    return _G.ret
+    return res
   end)
 end
 
@@ -120,4 +119,32 @@ describe('vim.system', function()
       system_sync({ './test' })
     end)
   end
+
+  it('always captures all content of stdout/stderr #30846', function()
+    t.skip(n.fn.executable('git') == 0, 'missing "git" command')
+    eq(
+      0,
+      exec_lua(function()
+        local done = 0
+        local fail = 0
+        for _ = 1, 200 do
+          vim.system(
+            { 'git', 'show', ':0:test/functional/plugin/lsp_spec.lua' },
+            { text = true },
+            function(o)
+              if o.code ~= 0 or #o.stdout == 0 then
+                fail = fail + 1
+              end
+              done = done + 1
+            end
+          )
+        end
+
+        local ok = vim.wait(10000, function()
+          return done == 200
+        end, 200)
+        return fail + (ok and 0 or 1)
+      end)
+    )
+  end)
 end)
-- 
cgit 


From e2a91876ac61b4265e62a73ea9aed37597976b85 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Wed, 4 Dec 2024 16:31:08 +0100
Subject: test(screen): adjust screen state per stylua #31441

Before:
screen:expect({        | screen:expect({
  grid = [[            |   grid = [[
    {10:>!}a        |  |     line ^1                   |
    {7:  }b        |   |     {1:~                        }|*4
    {10:>>}c        |  |   ]], messages={ {
    {7:  }^         |  |     content = { { "\ntest\n[O]k: ", 6, 11 } },
    {1:~          }|*9 |     kind = "confirm"
               |       |   } }
  ]]                   | })
})

After:
screen:expect([[         | screen:expect({
  {10:>!}a            |  |   grid = [[
  {7:  }b            |   |     line ^1                   |
  {10:>>}c            |  |     {1:~                        }|*4
  {7:  }^             |  |   ]],
  {1:~              }|*9 |   messages = { {
                 |       |     content = { { "\ntest\n[O]k: ", 6, 11 } },
]])                      |     kind = "confirm"
                         |   } },
                         | })
---
 test/functional/treesitter/highlight_spec.lua |  4 ++--
 test/functional/ui/screen.lua                 | 25 ++++++++++++++-----------
 2 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index f2563d9bad..028c01f14a 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -404,7 +404,7 @@ describe('treesitter highlighting (C)', function()
       ]],
     })
 
-    feed 'u'
+    feed 'u:'
 
     screen:expect({
       grid = [[
@@ -425,7 +425,7 @@ describe('treesitter highlighting (C)', function()
             }                                                            |
           }                                                              |
         ^}                                                                |
-        19 changes; before #2  0 seconds ago                             |
+                                                                         |
       ]],
     })
   end)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 8e15e6c35f..f5cb914299 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -37,10 +37,10 @@
 -- Tests will often share a group of extra attribute sets to expect(). Those can be
 -- defined at the beginning of a test:
 --
---    screen:add_extra_attr_ids {
+--    screen:add_extra_attr_ids({
 --      [100] = { background = Screen.colors.Plum1, underline = true },
 --      [101] = { background = Screen.colors.Red1, bold = true, underline = true },
---    }
+--    })
 --
 -- To help write screen tests, see Screen:snapshot_util().
 -- To debug screen tests, see Screen:redraw_debug().
@@ -454,7 +454,7 @@ end
 ---    screen:expect(grid, [attr_ids])
 ---    screen:expect(condition)
 --- or keyword args (supports more options):
----    screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end}
+---    screen:expect({ grid=[[...]], cmdline={...}, condition=function() ... end })
 ---
 --- @param expected string|function|test.function.ui.screen.Expect
 --- @param attr_ids? table>
@@ -1713,21 +1713,24 @@ function Screen:_print_snapshot()
       end
     end
     local fn_name = modify_attrs and 'add_extra_attr_ids' or 'set_default_attr_ids'
-    attrstr = ('screen:' .. fn_name .. ' {\n' .. table.concat(attrstrs, '\n') .. '\n}\n\n')
+    attrstr = ('screen:' .. fn_name .. '({\n' .. table.concat(attrstrs, '\n') .. '\n})\n\n')
   end
 
-  local result = ('%sscreen:expect({\n  grid = [[\n  %s\n  ]]'):format(
-    attrstr,
-    kwargs.grid:gsub('\n', '\n  ')
-  )
+  local extstr = ''
   for _, k in ipairs(ext_keys) do
     if ext_state[k] ~= nil and not (k == 'win_viewport' and not self.options.ext_multigrid) then
-      result = result .. ', ' .. k .. '=' .. fmt_ext_state(k, ext_state[k])
+      extstr = extstr .. '\n  ' .. k .. ' = ' .. fmt_ext_state(k, ext_state[k]) .. ','
     end
   end
-  result = result .. '\n})'
 
-  return result
+  return ('%sscreen:expect(%s%s%s%s%s'):format(
+    attrstr,
+    #extstr > 0 and '{\n  grid = [[\n  ' or '[[\n',
+    #extstr > 0 and kwargs.grid:gsub('\n', '\n  ') or kwargs.grid,
+    #extstr > 0 and '\n  ]],' or '\n]]',
+    extstr,
+    #extstr > 0 and '\n})' or ')'
+  )
 end
 
 function Screen:print_snapshot()
-- 
cgit 


From 6586645d78711b1e40909136de64ee69fd4e274e Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Tue, 3 Dec 2024 19:17:15 +0100
Subject: docs: help tags for neovim.io searches

---
 runtime/doc/intro.txt    | 22 ++++++++++++++++++----
 runtime/doc/provider.txt |  2 +-
 runtime/doc/sign.txt     |  2 +-
 3 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 85169fcc17..73de54010e 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -53,19 +53,33 @@ Nvim on the interwebs					*internet*
 - Nvim home page: https://neovim.io/
 - Vim FAQ: https://vimhelp.org/vim_faq.txt.html
 
-			*download* *upgrade* *ubuntu*
+			*download* *upgrade* *install* *ubuntu*
 To install or upgrade Nvim, you can...
 - Download a pre-built archive:
   https://github.com/neovim/neovim/releases
 - Use your system package manager:
   https://github.com/neovim/neovim/blob/master/INSTALL.md#install-from-package
+- Build from source:
+  https://github.com/neovim/neovim/blob/master/INSTALL.md#install-from-source
 
 			*bugs* *bug-report* *feature-request*
 Report bugs and request features here: https://github.com/neovim/neovim/issues
 Be brief, yet complete.  Always give a reproducible example and try to find
-out which settings or other things trigger the bug.
-
-If Nvim crashes, try to get a backtrace.  See |debug.txt|.
+out which settings or other things trigger the bug. If Nvim crashed, try to
+get a backtrace (see |dev-tools-backtrace|).
+
+			*uninstall*
+To un-install Nvim:
+- If you downloaded a pre-built archive or built Nvim from source (e.g. `make
+  install`), just delete its files, typically located in: >
+  /usr/local/bin/nvim
+  /usr/local/share/nvim
+<
+- If you installed via package manager, read your package manager's
+  documentation. Common examples:
+  - APT (Debian, Ubuntu, …): `apt-get remove neovim`
+  - Homebrew (macOS): `brew install neovim`
+  - Scoop (Windows): `scoop install neovim`
 
 ==============================================================================
 Sponsor Vim/Nvim development				*sponsor* *register*
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index f1b0daee76..c54a4df3d8 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -193,7 +193,7 @@ registers. Nvim looks for these clipboard tools, in order of priority:
   - xclip (if $DISPLAY is set)
   - lemonade (for SSH) https://github.com/pocke/lemonade
   - doitclient (for SSH) https://www.chiark.greenend.org.uk/~sgtatham/doit/
-  - win32yank (Windows)
+  - *win32yank* (Windows)
   - putclip, getclip (Windows) https://cygwin.com/packages/summary/cygutils.html
   - clip, powershell (Windows) https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/clip
   - termux (via termux-clipboard-set, termux-clipboard-set)
diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt
index 9d74f1f376..9895b606fd 100644
--- a/runtime/doc/sign.txt
+++ b/runtime/doc/sign.txt
@@ -10,7 +10,7 @@ Sign Support Features				*sign-support*
                                       Type |gO| to see the table of contents.
 
 ==============================================================================
-1. Introduction					*sign-intro* *signs*
+1. Introduction					*sign-intro* *signs* *gutter*
 
 When a debugger or other IDE tool is driving an editor it needs to be able
 to give specific highlights which quickly tell the user useful information
-- 
cgit 


From 7579af3c514c44581fe33b5c03660cdfda7d658e Mon Sep 17 00:00:00 2001
From: Jeremy Fleischman 
Date: Wed, 4 Dec 2024 07:49:57 -0800
Subject: feat(diagnostic): vim.diagnostic.setqflist improvements #30868

1. Use the new "u" action to update the quickfix list so we don't lose
   our position in the quickfix list when updating it.
2. Rather than creating a new quickfix list each time, update the
   exiting one if we've already created one.
---
 runtime/doc/news.txt           |  2 ++
 runtime/lua/vim/diagnostic.lua | 21 +++++++++++++++++++--
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index ad3f2c0a6a..c309d85297 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -303,6 +303,8 @@ UI
   |hl-PmenuSel| and |hl-PmenuMatch| both inherit from |hl-Pmenu|, and
   |hl-PmenuMatchSel| inherits highlights from both |hl-PmenuSel| and
   |hl-PmenuMatch|.
+• |vim.diagnostic.setqflist()| updates existing diagnostics quickfix list if one
+  exists.
 
 • |ui-messages| content chunks now also contain the highlight group ID.
 
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 2de996feeb..dbf4f56032 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -2,6 +2,8 @@ local api, if_nil = vim.api, vim.F.if_nil
 
 local M = {}
 
+local _qf_id = nil
+
 --- [diagnostic-structure]()
 ---
 --- Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based
@@ -848,9 +850,24 @@ local function set_list(loclist, opts)
   local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], false)
   local items = M.toqflist(diagnostics)
   if loclist then
-    vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items })
+    vim.fn.setloclist(winnr, {}, 'u', { title = title, items = items })
   else
-    vim.fn.setqflist({}, ' ', { title = title, items = items })
+    -- Check if the diagnostics quickfix list no longer exists.
+    if _qf_id and vim.fn.getqflist({ id = _qf_id }).id == 0 then
+      _qf_id = nil
+    end
+
+    -- If we already have a diagnostics quickfix, update it rather than creating a new one.
+    -- This avoids polluting the finite set of quickfix lists, and preserves the currently selected
+    -- entry.
+    vim.fn.setqflist({}, _qf_id and 'u' or ' ', {
+      title = title,
+      items = items,
+      id = _qf_id,
+    })
+
+    -- Get the id of the newly created quickfix list.
+    _qf_id = vim.fn.getqflist({ id = 0 }).id
   end
   if open then
     api.nvim_command(loclist and 'lwindow' or 'botright cwindow')
-- 
cgit 


From 52481eecf0dfc596a4d8df389c901f46cd3b6661 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Wed, 4 Dec 2024 10:17:19 -0600
Subject: docs: mention new terminal defaults (#31449)

---
 runtime/doc/news.txt     | 2 ++
 runtime/doc/terminal.txt | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index c309d85297..58fe2e02e8 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -58,6 +58,8 @@ DEFAULTS
 • |]d-default| and |[d-default| accept a count.
 • |[D-default| and |]D-default| jump to the first and last diagnostic in the
   current buffer, respectively.
+• 'number', 'relativenumber', and 'signcolumn' are disabled in |terminal|
+  buffers. See |terminal-config| for an example of changing these defaults.
 
 DIAGNOSTICS
 
diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt
index ed9659d6e7..27586c38a7 100644
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -108,6 +108,9 @@ global configuration.
 
 - 'list' is disabled
 - 'wrap' is disabled
+- 'number' is disabled
+- 'relativenumber' is disabled
+- 'signcolumn' is set to "no"
 
 You can change the defaults with a TermOpen autocommand: >vim
     au TermOpen * setlocal list
-- 
cgit 


From 0a15cd250035b770e771baaabfb071827778347d Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Wed, 4 Dec 2024 10:17:46 -0600
Subject: misc: keep deprecated vim.loader.disable stub (#31450)

Transitional stub to minimize breaking change pain, to be removed after
0.11 release.
---
 runtime/lua/vim/loader.lua | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
index 71d0188128..c7158673fe 100644
--- a/runtime/lua/vim/loader.lua
+++ b/runtime/lua/vim/loader.lua
@@ -446,6 +446,12 @@ function M.enable(enable)
   end
 end
 
+--- @deprecated
+function M.disable()
+  vim.deprecate('vim.loader.disable', 'vim.loader.enable(false)', '0.12')
+  vim.loader.enable(false)
+end
+
 --- Tracks the time spent in a function
 --- @generic F: function
 --- @param f F
-- 
cgit 


From 8f84167c30692555d3332565605e8a625aebc43c Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Tue, 3 Dec 2024 15:31:36 +0000
Subject: refactor(runtime.c): misc

---
 src/nvim/runtime.c | 84 +++++++++++++++++++++---------------------------------
 1 file changed, 33 insertions(+), 51 deletions(-)

diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 3f00b74e61..ff7471025f 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -159,10 +159,7 @@ char *estack_sfile(estack_arg_T which)
 {
   const estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1;
   if (which == ESTACK_SFILE && entry->es_type != ETYPE_UFUNC) {
-    if (entry->es_name == NULL) {
-      return NULL;
-    }
-    return xstrdup(entry->es_name);
+    return entry->es_name != NULL ? xstrdup(entry->es_name) : NULL;
   }
 
   // If evaluated in a function or autocommand, return the path of the script
@@ -499,7 +496,6 @@ static void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
 /// return FAIL when no file could be sourced, OK otherwise.
 static int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie)
 {
-  char *tail;
   bool did_one = false;
 
   char buf[MAXPATHL];
@@ -533,7 +529,7 @@ static int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback,
     } else if (buflen + strlen(name) + 2 < MAXPATHL) {
       STRCPY(buf, item.path);
       add_pathsep(buf);
-      tail = buf + strlen(buf);
+      char *tail = buf + strlen(buf);
 
       // Loop over all patterns in "name"
       char *np = name;
@@ -935,7 +931,6 @@ static int gen_expand_wildcards_and_cb(int num_pat, char **pats, int flags, bool
 /// @param is_pack whether the added dir is a "pack/*/start/*/" style package
 static int add_pack_dir_to_rtp(char *fname, bool is_pack)
 {
-  char *p;
   char *afterdir = NULL;
   int retval = FAIL;
 
@@ -943,7 +938,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
   char *p2 = p1;
   char *p3 = p1;
   char *p4 = p1;
-  for (p = p1; *p; MB_PTR_ADV(p)) {
+  for (char *p = p1; *p; MB_PTR_ADV(p)) {
     if (vim_ispathsep_nocolon(*p)) {
       p4 = p3;
       p3 = p2;
@@ -970,21 +965,21 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
   // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences
   // Also stop at the first "after" directory
   size_t fname_len = strlen(ffname);
-  char *buf = try_malloc(MAXPATHL);
-  if (buf == NULL) {
-    goto theend;
-  }
+  char buf[MAXPATHL];
   const char *insp = NULL;
   const char *after_insp = NULL;
-  for (const char *entry = p_rtp; *entry != NUL;) {
+  const char *entry = p_rtp;
+  while (*entry != NUL) {
     const char *cur_entry = entry;
-
     copy_option_part((char **)&entry, buf, MAXPATHL, ",");
 
-    if ((p = strstr(buf, "after")) != NULL
-        && p > buf
-        && vim_ispathsep(p[-1])
-        && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) {
+    char *p = strstr(buf, "after");
+    bool is_after = p != NULL
+                    && p > buf
+                    && vim_ispathsep(p[-1])
+                    && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',');
+
+    if (is_after) {
       if (insp == NULL) {
         // Did not find "ffname" before the first "after" directory,
         // insert it before this entry.
@@ -1000,12 +995,11 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
       if (rtp_ffname == NULL) {
         goto theend;
       }
-      bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0;
-      xfree(rtp_ffname);
-      if (match) {
+      if (path_fnamencmp(rtp_ffname, ffname, fname_len) == 0) {
         // Insert "ffname" after this entry (and comma).
         insp = entry;
       }
+      xfree(rtp_ffname);
     }
   }
 
@@ -1075,7 +1069,6 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
   retval = OK;
 
 theend:
-  xfree(buf);
   xfree(ffname);
   xfree(afterdir);
   return retval;
@@ -1123,7 +1116,7 @@ static void add_pack_plugins(bool opt, int num_fnames, char **fnames, bool all,
   bool did_one = false;
 
   if (cookie != &APP_LOAD) {
-    char *buf = xmalloc(MAXPATHL);
+    char buf[MAXPATHL];
     for (int i = 0; i < num_fnames; i++) {
       bool found = false;
 
@@ -1138,7 +1131,6 @@ static void add_pack_plugins(bool opt, int num_fnames, char **fnames, bool all,
       if (!found) {
         // directory is not yet in 'runtimepath', add it
         if (add_pack_dir_to_rtp(fnames[i], false) == FAIL) {
-          xfree(buf);
           return;
         }
       }
@@ -1147,7 +1139,6 @@ static void add_pack_plugins(bool opt, int num_fnames, char **fnames, bool all,
         break;
       }
     }
-    xfree(buf);
   }
 
   if (!all && did_one) {
@@ -1276,25 +1267,23 @@ void ex_packadd(exarg_T *eap)
   static const char plugpat[] = "pack/*/%s/%s";  // NOLINT
   int res = OK;
 
-  // Round 1: use "start", round 2: use "opt".
-  for (int round = 1; round <= 2; round++) {
-    // Only look under "start" when loading packages wasn't done yet.
-    if (round == 1 && did_source_packages) {
-      continue;
-    }
+  const size_t len = sizeof(plugpat) + strlen(eap->arg) + 5;
+  char *pat = xmallocz(len);
+  void *cookie = eap->forceit ? &APP_ADD_DIR : &APP_BOTH;
 
-    const size_t len = sizeof(plugpat) + strlen(eap->arg) + 5;
-    char *pat = xmallocz(len);
-    vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg);
-    // The first round don't give a "not found" error, in the second round
-    // only when nothing was found in the first round.
-    res =
-      do_in_path(p_pp, "", pat,
-                 DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0),
-                 round == 1 ? add_start_pack_plugins : add_opt_pack_plugins,
-                 eap->forceit ? &APP_ADD_DIR : &APP_BOTH);
-    xfree(pat);
+  // Only look under "start" when loading packages wasn't done yet.
+  if (!did_source_packages) {
+    vim_snprintf(pat, len, plugpat, "start", eap->arg);
+    res = do_in_path(p_pp, "", pat, DIP_ALL + DIP_DIR,
+                     add_start_pack_plugins, cookie);
   }
+
+  // Give a "not found" error if nothing was found in 'start' or 'opt'.
+  vim_snprintf(pat, len, plugpat, "opt", eap->arg);
+  do_in_path(p_pp, "", pat, DIP_ALL + DIP_DIR + (res == FAIL ? DIP_ERR : 0),
+             add_opt_pack_plugins, cookie);
+
+  xfree(pat);
 }
 
 static void ExpandRTDir_int(char *pat, size_t pat_len, int flags, bool keep_ext, garray_T *gap,
@@ -2726,22 +2715,15 @@ retry:
 /// Without the multi-byte feature it's simply ignored.
 void ex_scriptencoding(exarg_T *eap)
 {
-  source_cookie_T *sp;
-  char *name;
-
   if (!getline_equal(eap->ea_getline, eap->cookie, getsourceline)) {
     emsg(_("E167: :scriptencoding used outside of a sourced file"));
     return;
   }
 
-  if (*eap->arg != NUL) {
-    name = enc_canonize(eap->arg);
-  } else {
-    name = eap->arg;
-  }
+  char *name = (*eap->arg != NUL) ? enc_canonize(eap->arg) : eap->arg;
 
   // Setup for conversion from the specified encoding to 'encoding'.
-  sp = (source_cookie_T *)getline_cookie(eap->ea_getline, eap->cookie);
+  source_cookie_T *sp = (source_cookie_T *)getline_cookie(eap->ea_getline, eap->cookie);
   convert_setup(&sp->conv, name, p_enc);
 
   if (name != eap->arg) {
-- 
cgit 


From 5413c6475e46be31c00fd71ef1672592ed3f4ecf Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Thu, 5 Dec 2024 01:38:44 +0100
Subject: docs: graduate gui.txt to "flow layout"

---
 runtime/doc/gui.txt       | 464 +++++++++++++++++++++++-----------------------
 scripts/gen_help_html.lua |   1 +
 2 files changed, 234 insertions(+), 231 deletions(-)

diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt
index 85a0df03f9..380fa71537 100644
--- a/runtime/doc/gui.txt
+++ b/runtime/doc/gui.txt
@@ -1,10 +1,10 @@
 *gui.txt*       Nvim
 
 
-		  VIM REFERENCE MANUAL    by Bram Moolenaar
+                  VIM REFERENCE MANUAL    by Bram Moolenaar
 
 
-Nvim Graphical User Interface				*gui* *GUI*
+Nvim Graphical User Interface                           *gui* *GUI*
 
 Any client that supports the Nvim |ui-protocol| can be used as a UI for Nvim.
 And multiple UIs can connect to the same Nvim instance! The terms "UI" and
@@ -20,7 +20,7 @@ features, whereas help tags with the "ui-" prefix refer to the |ui-protocol|.
 Nvim provides a default, builtin UI (the |TUI|), but there are many other
 (third-party) GUIs that you can use instead:
 
-							*vscode*
+                                                        *vscode*
 - vscode-neovim (Nvim in VSCode!) https://github.com/vscode-neovim/vscode-neovim
 - Firenvim (Nvim in your web browser!) https://github.com/glacambre/firenvim
 - Neovide https://neovide.dev/
@@ -33,71 +33,71 @@ Nvim provides a default, builtin UI (the |TUI|), but there are many other
                                       Type |gO| to see the table of contents.
 
 ==============================================================================
-Starting the GUI				*gui-config* *gui-start*
+Starting the GUI                                *gui-config* *gui-start*
 
-				*ginit.vim* *gui-init* *gvimrc* *$MYGVIMRC*
+                                *ginit.vim* *gui-init* *gvimrc* *$MYGVIMRC*
 For GUI-specific configuration Nvim provides the |UIEnter| event.  This
 happens after other |initialization|s, or whenever a UI attaches (multiple UIs
 can connect to any Nvim instance).
 
 Example: this sets "g:gui" to the value of the UI's "rgb" field:    >
-	:autocmd UIEnter * let g:gui = filter(nvim_list_uis(),{k,v-> v.chan==v:event.chan})[0].rgb
+        :autocmd UIEnter * let g:gui = filter(nvim_list_uis(),{k,v-> v.chan==v:event.chan})[0].rgb
 <
 
-						*:winp* *:winpos* *E188*
+                                                *:winp* *:winpos* *E188*
 :winp[os]
-		Display current position of the top left corner of the GUI vim
-		window in pixels.  Does not work in all versions.
-		Also see |getwinpos()|, |getwinposx()| and |getwinposy()|.
-
-:winp[os] {X} {Y}							*E466*
-		Put the GUI vim window at the given {X} and {Y} coordinates.
-		The coordinates should specify the position in pixels of the
-		top left corner of the window.
-		When the GUI window has not been opened yet, the values are
-		remembered until the window is opened.  The position is
-		adjusted to make the window fit on the screen (if possible).
-
-					    *:wi* *:win* *:winsize* *E465*
+                Display current position of the top left corner of the GUI vim
+                window in pixels.  Does not work in all versions.
+                Also see |getwinpos()|, |getwinposx()| and |getwinposy()|.
+
+:winp[os] {X} {Y}                                                       *E466*
+                Put the GUI vim window at the given {X} and {Y} coordinates.
+                The coordinates should specify the position in pixels of the
+                top left corner of the window.
+                When the GUI window has not been opened yet, the values are
+                remembered until the window is opened.  The position is
+                adjusted to make the window fit on the screen (if possible).
+
+                                            *:wi* *:win* *:winsize* *E465*
 :win[size] {width} {height}
-		Set the window height to {width} by {height} characters.
-		Obsolete, use ":set lines=11 columns=22".
+                Set the window height to {width} by {height} characters.
+                Obsolete, use ":set lines=11 columns=22".
 
 ==============================================================================
-Scrollbars						*gui-scrollbars*
+Scrollbars                                              *gui-scrollbars*
 
 There are vertical scrollbars and a horizontal scrollbar.  You may
 configure which ones appear with the 'guioptions' option.
 
 The interface looks like this (with `:set guioptions=mlrb`):
 >
-		       +------------------------------+ `
-		       | File  Edit		 Help | <- Menu bar (m) `
-		       +-+--------------------------+-+ `
-		       |^|			    |^| `
-		       |#| Text area.		    |#| `
-		       | |			    | | `
-		       |v|__________________________|v| `
- Normal status line -> |-+ File.c	       5,2  +-| `
+                       +------------------------------+ `
+                       | File  Edit              Help | <- Menu bar (m) `
+                       +-+--------------------------+-+ `
+                       |^|                          |^| `
+                       |#| Text area.               |#| `
+                       | |                          | | `
+                       |v|__________________________|v| `
+ Normal status line -> |-+ File.c              5,2  +-| `
  between Vim windows   |^|""""""""""""""""""""""""""|^| `
-		       | |			    | | `
-		       | | Another file buffer.     | | `
-		       | |			    | | `
-		       |#|			    |#| `
- Left scrollbar (l) -> |#|			    |#| <- Right `
-		       |#|			    |#|    scrollbar (r) `
-		       | |			    | | `
-		       |v|			    |v| `
-		       +-+--------------------------+-+ `
-		       | |< ####		   >| | <- Bottom `
-		       +-+--------------------------+-+    scrollbar (b) `
+                       | |                          | | `
+                       | | Another file buffer.     | | `
+                       | |                          | | `
+                       |#|                          |#| `
+ Left scrollbar (l) -> |#|                          |#| <- Right `
+                       |#|                          |#|    scrollbar (r) `
+                       | |                          | | `
+                       |v|                          |v| `
+                       +-+--------------------------+-+ `
+                       | |< ####                   >| | <- Bottom `
+                       +-+--------------------------+-+    scrollbar (b) `
 <
 Any of the scrollbar or menu components may be turned off by not putting the
 appropriate letter in the 'guioptions' string.  The bottom scrollbar is
 only useful when 'nowrap' is set.
 
 
-VERTICAL SCROLLBARS					*gui-vert-scroll*
+VERTICAL SCROLLBARS                                     *gui-vert-scroll*
 
 Each Vim window has a scrollbar next to it which may be scrolled up and down
 to move through the text in that buffer.  The size of the scrollbar-thumb
@@ -116,7 +116,7 @@ is on the left half, the right scrollbar column will contain scrollbars for
 the rightmost windows.  The same happens on the other side.
 
 
-HORIZONTAL SCROLLBARS					*gui-horiz-scroll*
+HORIZONTAL SCROLLBARS                                   *gui-horiz-scroll*
 
 The horizontal scrollbar (at the bottom of the Vim GUI) may be used to
 scroll text sideways when the 'wrap' option is turned off.  The
@@ -132,7 +132,7 @@ include the 'h' flag in 'guioptions'.  Then the scrolling is limited by the
 text of the current cursor line.
 
 ==============================================================================
-Drag and drop						*drag-n-drop*
+Drag and drop                                           *drag-n-drop*
 
 You can drag and drop one or more files into the Vim window, where they will
 be opened as if a |:drop| command was used.
@@ -151,12 +151,12 @@ names with any Ex command.  Special characters (space, tab, double quote and
 "|"; backslash on non-MS-Windows systems) will be escaped.
 
 ==============================================================================
-Menus							*menus*
+Menus                                                   *menus*
 
 For an introduction see |usr_42.txt| in the user manual.
 
 
-Using Menus						*using-menus*
+Using Menus                                             *using-menus*
 
 Basically, menus can be used just like mappings.  You can define your own
 menus, as many as you like.
@@ -166,45 +166,45 @@ what the key sequence was.
 
 For creating menus in a different language, see |:menutrans|.
 
-							*menu.vim*
+                                                        *menu.vim*
 The default menus are read from the file "$VIMRUNTIME/menu.vim".  See
 |$VIMRUNTIME| for where the path comes from.  You can set up your own menus.
 Starting off with the default set is a good idea.  You can add more items, or,
 if you don't like the defaults at all, start with removing all menus
 |:unmenu-all|.  You can also avoid the default menus being loaded by adding
 this line to your vimrc file (NOT your gvimrc file!): >
-	:let did_install_default_menus = 1
+        :let did_install_default_menus = 1
 If you also want to avoid the Syntax menu: >
-	:let did_install_syntax_menu = 1
+        :let did_install_syntax_menu = 1
 The first item in the Syntax menu can be used to show all available filetypes
 in the menu (which can take a bit of time to load).  If you want to have all
 filetypes already present at startup, add: >
-	:let do_syntax_sel_menu = 1
+        :let do_syntax_sel_menu = 1
 
 Note that the menu.vim is sourced when `:syntax on` or `:filetype on` is
 executed or after your .vimrc file is sourced.  This means that the 'encoding'
 option and the language of messages (`:language messages`) must be set before
 that (if you want to change them).
 
-							*console-menus*
+                                                        *console-menus*
 Although this documentation is in the GUI section, you can actually use menus
 in console mode too.  You will have to load |menu.vim| explicitly then, it is
 not done by default.  You can use the |:emenu| command and command-line
 completion with 'wildmenu' to access the menu entries almost like a real menu
 system.  To do this, put these commands in your vimrc file: >
-	:source $VIMRUNTIME/menu.vim
-	:set wildmenu
-	:set cpo-=<
-	:set wcm=
-	:map  :emenu 
+        :source $VIMRUNTIME/menu.vim
+        :set wildmenu
+        :set cpo-=<
+        :set wcm=
+        :map  :emenu 
 Pressing  will start the menu.  You can now use the cursor keys to select
 a menu entry.  Hit  to execute it.  Hit  if you want to cancel.
 
-Creating New Menus					*creating-menus*
+Creating New Menus                                      *creating-menus*
 
-				*:me*  *:menu*  *:noreme*  *:noremenu*
-				*E330* *E327* *E331* *E336* *E333*
-				*E328* *E329* *E337* *E792*
+                                *:me*  *:menu*  *:noreme*  *:noremenu*
+                                *E330* *E327* *E331* *E336* *E333*
+                                *E328* *E329* *E337* *E792*
 To create a new menu item, use the ":menu" commands.  They are mostly like
 the ":map" set of commands (see |map-modes|), but the first argument is a menu
 item name, given as a path of menus and submenus with a '.' between them,
@@ -225,15 +225,16 @@ tooltips for menus. See |terminal-input|.
 
 Special characters in a menu name:
 
-							*menu-shortcut*
-	&	The next character is the shortcut key.  Make sure each
-		shortcut key is only used once in a (sub)menu.  If you want to
-		insert a literal "&" in the menu name use "&&".
-							*menu-text*
-		Separates the menu name from right-aligned text.  This can be
-		used to show the equivalent typed command.  The text ""
-		can be used here for convenience.  If you are using a real
-		tab, don't forget to put a backslash before it!
+                                                        *menu-shortcut*
+- & The next character is the shortcut key.  Make sure each shortcut key is
+  only used once in a (sub)menu.  If you want to insert a literal "&" in the
+  menu name use "&&".
+                                                        *menu-text*
+-  Separates the menu name from right-aligned text.  This can be used to
+  show the equivalent typed command.  The text "" can be used here for
+  convenience.  If you are using a real tab, don't forget to put a backslash
+  before it!
+
 Example: >
 
    :amenu &File.&Open:e  :browse e
@@ -243,99 +244,99 @@ With the shortcut "F" (while keeping the  key pressed), and then "O",
 this menu can be used.  The second part is shown as "Open     :e".  The ":e"
 is right aligned, and the "O" is underlined, to indicate it is the shortcut.
 
-					*:am*  *:amenu*  *:an*      *:anoremenu*
+                                        *:am*  *:amenu*  *:an*      *:anoremenu*
 The ":amenu" command can be used to define menu entries for all modes at once,
 except for Terminal mode.  To make the command work correctly, a character is
-automatically inserted for some modes:
-	mode		inserted	appended	~
-	Normal		nothing		nothing
-	Visual				
-	Insert		
-	Cmdline				
-	Op-pending			
-
+automatically inserted for some modes: >
+    mode            inserted        appended
+    Normal          nothing         nothing
+    Visual                     
+    Insert          
+    Cmdline                    
+    Op-pending                 
+<
 Example: >
 
-   :amenu File.Next	:next^M
+    :amenu File.Next     :next^M
 
 is equal to: >
 
-   :nmenu File.Next	:next^M
-   :vmenu File.Next	^C:next^M^\^G
-   :imenu File.Next	^\^O:next^M
-   :cmenu File.Next	^C:next^M^\^G
-   :omenu File.Next	^C:next^M^\^G
+    :nmenu File.Next     :next^M
+    :vmenu File.Next     ^C:next^M^\^G
+    :imenu File.Next     ^\^O:next^M
+    :cmenu File.Next     ^C:next^M^\^G
+    :omenu File.Next     ^C:next^M^\^G
 
 Careful: In Insert mode this only works for a SINGLE Normal mode command,
 because of the CTRL-O.  If you have two or more commands, you will need to use
 the ":imenu" command.  For inserting text in any mode, you can use the
 expression register: >
 
-   :amenu Insert.foobar   "='foobar'P
+    :amenu Insert.foobar   "='foobar'P
 
 The special text  begins a "command menu", it executes the command
 directly without changing modes.  Where you might use ":..." you can
 instead use "...".  See || for more info.  Example: >
-	anoremenu File.Next next
+    anoremenu File.Next next
 
 Note that  in Cmdline mode executes the command, like in a mapping.  This
 is Vi compatible.  Use CTRL-C to quit Cmdline mode.
 
-		*:nme* *:nmenu*  *:nnoreme* *:nnoremenu* *:nunme* *:nunmenu*
+                *:nme* *:nmenu*  *:nnoreme* *:nnoremenu* *:nunme* *:nunmenu*
 Menu commands starting with "n" work in Normal mode. |mapmode-n|
 
-		*:ome* *:omenu*  *:onoreme* *:onoremenu* *:ounme* *:ounmenu*
+                *:ome* *:omenu*  *:onoreme* *:onoremenu* *:ounme* *:ounmenu*
 Menu commands starting with "o" work in Operator-pending mode. |mapmode-o|
 
-		*:vme* *:vmenu*  *:vnoreme* *:vnoremenu* *:vunme* *:vunmenu*
+                *:vme* *:vmenu*  *:vnoreme* *:vnoremenu* *:vunme* *:vunmenu*
 Menu commands starting with "v" work in Visual mode. |mapmode-v|
 
-		*:xme* *:xmenu*  *:xnoreme* *:xnoremenu* *:xunme* *:xunmenu*
+                *:xme* *:xmenu*  *:xnoreme* *:xnoremenu* *:xunme* *:xunmenu*
 Menu commands starting with "x" work in Visual and Select mode. |mapmode-x|
 
-		*:sme* *:smenu*  *:snoreme* *:snoremenu* *:sunme* *:sunmenu*
+                *:sme* *:smenu*  *:snoreme* *:snoremenu* *:sunme* *:sunmenu*
 Menu commands starting with "s" work in Select mode. |mapmode-s|
 
-		*:ime* *:imenu*  *:inoreme* *:inoremenu* *:iunme* *:iunmenu*
+                *:ime* *:imenu*  *:inoreme* *:inoremenu* *:iunme* *:iunmenu*
 Menu commands starting with "i" work in Insert mode. |mapmode-i|
 
-		*:cme* *:cmenu*  *:cnoreme* *:cnoremenu* *:cunme* *:cunmenu*
+                *:cme* *:cmenu*  *:cnoreme* *:cnoremenu* *:cunme* *:cunmenu*
 Menu commands starting with "c" work in Cmdline mode. |mapmode-c|
 
-		*:tlm* *:tlmenu* *:tln*     *:tlnoremenu* *:tlu*   *:tlunmenu*
+                *:tlm* *:tlmenu* *:tln*     *:tlnoremenu* *:tlu*   *:tlunmenu*
 Menu commands starting with "tl" work in Terminal mode. |mapmode-t|
 
-						*:menu-* *:menu-silent*
+                                                *:menu-* *:menu-silent*
 To define a menu which will not be echoed on the command line, add
 "" as the first argument.  Example: >
-	:menu  Settings.Ignore\ case  :set ic
+        :menu  Settings.Ignore\ case  :set ic
 The ":set ic" will not be echoed when using this menu.  Messages from the
 executed command are still given though.  To shut them up too, add a ":silent"
 in the executed command: >
-	:menu  Search.Header :exe ":silent normal /Header\r"
+        :menu  Search.Header :exe ":silent normal /Header\r"
 "" may also appear just after "` closing tag); in the context of
Nvim this is rarely useful.

Solution:
- Add a `escape_slash` flag to `vim.json.encode`.
- Defaults to `false`. (This is a "breaking" change, but more like
  a bug fix.)
---
 runtime/doc/lua.txt               |   7 ++-
 runtime/doc/news.txt              |   2 +
 runtime/lua/vim/_meta/json.lua    |   5 +-
 src/cjson/lua_cjson.c             | 121 +++++++++++++++++++++++++++-----------
 test/functional/lua/json_spec.lua |  39 ++++++++++++
 5 files changed, 136 insertions(+), 38 deletions(-)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 48fa595a53..80808abd51 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -811,11 +811,14 @@ vim.json.decode({str}, {opts})                             *vim.json.decode()*
     Return: ~
         (`any`)
 
-vim.json.encode({obj})                                     *vim.json.encode()*
+vim.json.encode({obj}, {opts})                             *vim.json.encode()*
     Encodes (or "packs") Lua object {obj} as JSON in a Lua string.
 
     Parameters: ~
-      • {obj}  (`any`)
+      • {obj}   (`any`)
+      • {opts}  (`table?`) Options table with keys:
+                • escape_slash: (boolean) (default false) When true, escapes
+                  `/` character in JSON strings
 
     Return: ~
         (`string`)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 8b4b396a26..3a3e1a8d64 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -60,6 +60,7 @@ DEFAULTS
   current buffer, respectively.
 • 'number', 'relativenumber', 'signcolumn', and 'foldcolumn' are disabled in
   |terminal| buffers. See |terminal-config| for an example of changing these defaults.
+• |vim.json.encode()| no longer escapes the forward slash symbol by default
 
 DIAGNOSTICS
 
@@ -169,6 +170,7 @@ The following new features were added.
 API
 
 • |nvim__ns_set()| can set properties for a namespace
+• |vim.json.encode()| has an option to enable forward slash escaping
 
 DEFAULTS
 
diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua
index 07d89aafc8..1a7e87db9c 100644
--- a/runtime/lua/vim/_meta/json.lua
+++ b/runtime/lua/vim/_meta/json.lua
@@ -35,5 +35,8 @@ function vim.json.decode(str, opts) end
 
 --- Encodes (or "packs") Lua object {obj} as JSON in a Lua string.
 ---@param obj any
+---@param opts? table Options table with keys:
+---                                 - escape_slash: (boolean) (default false) When true, escapes `/`
+---                                                           character in JSON strings
 ---@return string
-function vim.json.encode(obj) end
+function vim.json.encode(obj, opts) end
diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c
index 254355e5a2..e4bd0bcf6b 100644
--- a/src/cjson/lua_cjson.c
+++ b/src/cjson/lua_cjson.c
@@ -172,6 +172,16 @@ typedef struct {
     int decode_array_with_array_mt;
 } json_config_t;
 
+typedef struct {
+    const char **char2escape[256];
+} json_encode_options_t;
+
+typedef struct {
+    json_config_t *cfg;
+    json_encode_options_t *options;
+    strbuf_t *json;
+} json_encode_t;
+
 typedef struct {
     /* convert null in json objects to lua nil instead of vim.NIL */
     bool luanil_object;
@@ -209,7 +219,7 @@ static const char *char2escape[256] = {
     "\\u0018", "\\u0019", "\\u001a", "\\u001b",
     "\\u001c", "\\u001d", "\\u001e", "\\u001f",
     NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL,
-    NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/",
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
@@ -555,11 +565,11 @@ static void json_create_config(lua_State *l)
 
 /* ===== ENCODING ===== */
 
-static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex,
+static void json_encode_exception(lua_State *l, json_encode_t *ctx, int lindex,
                                   const char *reason)
 {
-    if (!cfg->encode_keep_buffer)
-        strbuf_free(json);
+    if (!ctx->cfg->encode_keep_buffer)
+        strbuf_free(ctx->json);
     luaL_error(l, "Cannot serialise %s: %s",
                   lua_typename(l, lua_type(l, lindex)), reason);
 }
@@ -570,12 +580,13 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js
  * - String (Lua stack index)
  *
  * Returns nothing. Doesn't remove string from Lua stack */
-static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
+static void json_append_string(lua_State *l, json_encode_t *ctx, int lindex)
 {
     const char *escstr;
     unsigned i;
     const char *str;
     size_t len;
+    strbuf_t *json = ctx->json;
 
     str = lua_tolstring(l, lindex, &len);
 
@@ -587,7 +598,7 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
 
     strbuf_append_char_unsafe(json, '\"');
     for (i = 0; i < len; i++) {
-        escstr = char2escape[(unsigned char)str[i]];
+        escstr = (*ctx->options->char2escape)[(unsigned char)str[i]];
         if (escstr)
             strbuf_append_string(json, escstr);
         else
@@ -600,11 +611,12 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
  * -1   object (not a pure array)
  * >=0  elements in array
  */
-static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
+static int lua_array_length(lua_State *l, json_encode_t *ctx)
 {
     double k;
     int max;
     int items;
+    json_config_t *cfg = ctx->cfg;
 
     max = 0;
     items = 0;
@@ -635,7 +647,7 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
         max > items * cfg->encode_sparse_ratio &&
         max > cfg->encode_sparse_safe) {
         if (!cfg->encode_sparse_convert)
-            json_encode_exception(l, cfg, json, -1, "excessively sparse array");
+            json_encode_exception(l, ctx, -1, "excessively sparse array");
 
         return -1;
     }
@@ -666,17 +678,18 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
                current_depth);
 }
 
-static void json_append_data(lua_State *l, json_config_t *cfg,
-                             int current_depth, strbuf_t *json);
+static void json_append_data(lua_State *l, json_encode_t *cfg,
+                             int current_depth);
 
 /* json_append_array args:
  * - lua_State
  * - JSON strbuf
  * - Size of passwd Lua array (top of stack) */
-static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
-                              strbuf_t *json, int array_length)
+static void json_append_array(lua_State *l, json_encode_t *ctx, int current_depth,
+                              int array_length)
 {
     int comma, i;
+    strbuf_t *json = ctx->json;
 
     strbuf_append_char(json, '[');
 
@@ -688,23 +701,25 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
             comma = 1;
 
         lua_rawgeti(l, -1, i);
-        json_append_data(l, cfg, current_depth, json);
+        json_append_data(l, ctx, current_depth);
         lua_pop(l, 1);
     }
 
     strbuf_append_char(json, ']');
 }
 
-static void json_append_number(lua_State *l, json_config_t *cfg,
-                               strbuf_t *json, int lindex)
+static void json_append_number(lua_State *l, json_encode_t *ctx,
+                               int lindex)
 {
     double num = lua_tonumber(l, lindex);
     int len;
+    json_config_t *cfg = ctx->cfg;
+    strbuf_t *json = ctx->json;
 
     if (cfg->encode_invalid_numbers == 0) {
         /* Prevent encoding invalid numbers */
         if (isinf(num) || isnan(num))
-            json_encode_exception(l, cfg, json, lindex,
+            json_encode_exception(l, ctx, lindex,
                                   "must not be NaN or Infinity");
     } else if (cfg->encode_invalid_numbers == 1) {
         /* Encode NaN/Infinity separately to ensure Javascript compatible
@@ -733,10 +748,11 @@ static void json_append_number(lua_State *l, json_config_t *cfg,
     strbuf_extend_length(json, len);
 }
 
-static void json_append_object(lua_State *l, json_config_t *cfg,
-                               int current_depth, strbuf_t *json)
+static void json_append_object(lua_State *l, json_encode_t *ctx,
+                               int current_depth)
 {
     int comma, keytype;
+    strbuf_t *json = ctx->json;
 
     /* Object */
     strbuf_append_char(json, '{');
@@ -754,19 +770,19 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
         keytype = lua_type(l, -2);
         if (keytype == LUA_TNUMBER) {
             strbuf_append_char(json, '"');
-            json_append_number(l, cfg, json, -2);
+            json_append_number(l, ctx, -2);
             strbuf_append_mem(json, "\":", 2);
         } else if (keytype == LUA_TSTRING) {
-            json_append_string(l, json, -2);
+            json_append_string(l, ctx, -2);
             strbuf_append_char(json, ':');
         } else {
-            json_encode_exception(l, cfg, json, -2,
+            json_encode_exception(l, ctx, -2,
                                   "table key must be a number or string");
             /* never returns */
         }
 
         /* table, key, value */
-        json_append_data(l, cfg, current_depth, json);
+        json_append_data(l, ctx, current_depth);
         lua_pop(l, 1);
         /* table, key */
     }
@@ -775,20 +791,22 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
 }
 
 /* Serialise Lua data into JSON string. */
-static void json_append_data(lua_State *l, json_config_t *cfg,
-                             int current_depth, strbuf_t *json)
+static void json_append_data(lua_State *l, json_encode_t *ctx,
+                             int current_depth)
 {
     int len;
     int as_array = 0;
     int as_empty_dict = 0;
     int has_metatable;
+    json_config_t *cfg = ctx->cfg;
+    strbuf_t *json = ctx->json;
 
     switch (lua_type(l, -1)) {
     case LUA_TSTRING:
-        json_append_string(l, json, -1);
+        json_append_string(l, ctx, -1);
         break;
     case LUA_TNUMBER:
-        json_append_number(l, cfg, json, -1);
+        json_append_number(l, ctx, -1);
         break;
     case LUA_TBOOLEAN:
         if (lua_toboolean(l, -1))
@@ -818,12 +836,12 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
 
         if (as_array) {
             len = lua_objlen(l, -1);
-            json_append_array(l, cfg, current_depth, json, len);
+            json_append_array(l, ctx, current_depth, len);
         } else {
-            len = lua_array_length(l, cfg, json);
+            len = lua_array_length(l, ctx);
 
             if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object && !as_empty_dict)) {
-                json_append_array(l, cfg, current_depth, json, len);
+                json_append_array(l, ctx, current_depth, len);
             } else {
                 if (has_metatable) {
                     lua_getmetatable(l, -1);
@@ -833,11 +851,11 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
                     as_array = lua_rawequal(l, -1, -2);
                     lua_pop(l, 2); /* pop pointer + metatable */
                     if (as_array) {
-                        json_append_array(l, cfg, current_depth, json, 0);
+                        json_append_array(l, ctx, current_depth, 0);
                         break;
                     }
                 }
-                json_append_object(l, cfg, current_depth, json);
+                json_append_object(l, ctx, current_depth);
             }
         }
         break;
@@ -846,7 +864,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
         break;
     case LUA_TLIGHTUSERDATA:
         if (lua_touserdata(l, -1) == &json_array) {
-            json_append_array(l, cfg, current_depth, json, 0);
+            json_append_array(l, ctx, current_depth, 0);
         }
         break;
     case LUA_TUSERDATA:
@@ -862,7 +880,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
     default:
         /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
          * and LUA_TLIGHTUSERDATA) cannot be serialised */
-        json_encode_exception(l, cfg, json, -1, "type not supported");
+        json_encode_exception(l, ctx, -1, "type not supported");
         /* never returns */
     }
 }
@@ -870,12 +888,44 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
 static int json_encode(lua_State *l)
 {
     json_config_t *cfg = json_fetch_config(l);
+    json_encode_options_t options = { .char2escape = { char2escape } };
+    json_encode_t ctx = { .options = &options, .cfg = cfg };
     strbuf_t local_encode_buf;
     strbuf_t *encode_buf;
     char *json;
     int len;
+    const char *customChar2escape[256];
 
-    luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
+    switch (lua_gettop(l)) {
+    case 1:
+        break;
+    case 2:
+        luaL_checktype(l, 2, LUA_TTABLE);
+        lua_getfield(l, 2, "escape_slash");
+
+        /* We only handle the escape_slash option for now */
+        if (lua_isnil(l, -1)) {
+            lua_pop(l, 2);
+            break;
+        }
+
+        luaL_checktype(l, -1, LUA_TBOOLEAN);
+
+        int escape_slash = lua_toboolean(l, -1);
+
+        if (escape_slash) {
+            /* This can be optimised by adding a new hard-coded escape table for this case,
+             * but this path will rarely if ever be used, so let's just memcpy.*/ 
+            memcpy(customChar2escape, char2escape, sizeof(char2escape));
+            customChar2escape['/'] = "\\/";
+            *ctx.options->char2escape = customChar2escape;
+        }
+
+        lua_pop(l, 2);
+        break;
+    default:
+        return luaL_error (l, "expected 1 or 2 arguments");
+    }
 
     if (!cfg->encode_keep_buffer) {
         /* Use private buffer */
@@ -887,7 +937,8 @@ static int json_encode(lua_State *l)
         strbuf_reset(encode_buf);
     }
 
-    json_append_data(l, cfg, 0, encode_buf);
+    ctx.json = encode_buf;
+    json_append_data(l, &ctx, 0);
     json = strbuf_string(encode_buf, &len);
 
     lua_pushlstring(l, json, len);
diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua
index a6e814d739..8df42fcaa1 100644
--- a/test/functional/lua/json_spec.lua
+++ b/test/functional/lua/json_spec.lua
@@ -152,6 +152,45 @@ describe('vim.json.encode()', function()
     clear()
   end)
 
+  it('dumps strings with & without escaped slash', function()
+    -- With slash
+    eq('"Test\\/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = true })]]))
+    eq(
+      'Test/',
+      exec_lua([[return vim.json.decode(vim.json.encode('Test/', { escape_slash = true }))]])
+    )
+
+    -- Without slash
+    eq('"Test/"', exec_lua([[return vim.json.encode('Test/')]]))
+    eq('"Test/"', exec_lua([[return vim.json.encode('Test/', {})]]))
+    eq('"Test/"', exec_lua([[return vim.json.encode('Test/', { _invalid = true })]]))
+    eq('"Test/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = false })]]))
+    eq(
+      '"Test/"',
+      exec_lua([[return vim.json.encode('Test/', { _invalid = true, escape_slash = false })]])
+    )
+    eq(
+      'Test/',
+      exec_lua([[return vim.json.decode(vim.json.encode('Test/', { escape_slash = false }))]])
+    )
+
+    -- Checks for for global side-effects
+    eq(
+      '"Test/"',
+      exec_lua([[
+        vim.json.encode('Test/', { escape_slash = true })
+        return vim.json.encode('Test/')
+      ]])
+    )
+    eq(
+      '"Test\\/"',
+      exec_lua([[
+        vim.json.encode('Test/', { escape_slash = false })
+        return vim.json.encode('Test/', { escape_slash = true })
+      ]])
+    )
+  end)
+
   it('dumps strings', function()
     eq('"Test"', exec_lua([[return vim.json.encode('Test')]]))
     eq('""', exec_lua([[return vim.json.encode('')]]))
-- 
cgit 


From ec94c2704f5059794923777ed51412d80bd26b5b Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sat, 7 Dec 2024 10:17:36 +0800
Subject: vim-patch:9.1.0908: not possible to configure :messages (#31492)

Problem:  not possible to configure :messages
Solution: add the 'messagesopt' option (Shougo Matsushita)

closes: vim/vim#16068

https://github.com/vim/vim/commit/51d4d84d6a7159c6ce9e04b36f8edc105ca3794b

Co-authored-by: Shougo Matsushita 
Co-authored-by: h_east 
---
 runtime/doc/message.txt                  |   3 +-
 runtime/doc/news.txt                     |   3 +-
 runtime/doc/options.txt                  |  28 ++--
 runtime/lua/vim/_meta/options.lua        |  34 +++--
 runtime/optwin.vim                       |   4 +-
 src/nvim/ex_docmd.c                      |  14 +-
 src/nvim/generators/gen_options.lua      |   6 +-
 src/nvim/message.c                       | 221 ++++++++++++++++++++-----------
 src/nvim/normal.c                        |   4 +-
 src/nvim/option.c                        |  14 --
 src/nvim/option_vars.h                   |   2 +-
 src/nvim/options.lua                     |  49 ++++---
 src/nvim/optionstr.c                     |  18 +++
 test/functional/legacy/messages_spec.lua |  39 ++++++
 test/old/testdir/gen_opt_test.vim        |   6 +-
 test/old/testdir/test_cmdline.vim        |  26 ----
 test/old/testdir/test_messages.vim       |  47 +++++++
 test/old/testdir/test_options.vim        |   7 +-
 18 files changed, 361 insertions(+), 164 deletions(-)

diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index ac151811c2..d6f5aeb354 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -27,7 +27,7 @@ depends on the 'shortmess' option.
 				Clear messages, keeping only the {count} most
 				recent ones.
 
-The number of remembered messages is fixed at 200.
+The number of remembered messages is determined by the 'messagesopt' option.
 
 								*g<*
 The "g<" command can be used to see the last page of previous command output.
@@ -789,6 +789,7 @@ If you accidentally hit  or  and you want to see the displayed
 text then use |g<|.  This only works when 'more' is set.
 
 To reduce the number of hit-enter prompts:
+- Set 'messagesopt'.
 - Set 'cmdheight' to 2 or higher.
 - Add flags to 'shortmess'.
 - Reset 'showcmd' and/or 'ruler'.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 3a3e1a8d64..3ce73135ba 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -25,6 +25,7 @@ EXPERIMENTS
 OPTIONS
 
 • 'jumpoptions' flag "unload" has been renamed to "clean".
+• The `msghistory` option has been removed in favor of 'messagesopt'.
 
 ==============================================================================
 BREAKING CHANGES                                                *news-breaking*
@@ -248,7 +249,7 @@ LUA
 OPTIONS
 
 • 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|.
-• 'msghistory' controls maximum number of messages to remember.
+• 'messagesopt' configures |:messages| and |hit-enter| prompt.
 • 'tabclose' controls which tab page to focus when closing a tab page.
 
 PERFORMANCE
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 9782846be5..0db7b0f03c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3197,7 +3197,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 			global
 	A history of ":" commands, and a history of previous search patterns
 	is remembered.  This option decides how many entries may be stored in
-	each of these histories (see |cmdline-editing| and 'msghistory' for
+	each of these histories (see |cmdline-editing| and 'messagesopt' for
 	the number of messages to remember).
 	The maximum value is 10000.
 
@@ -4045,6 +4045,25 @@ A jump table for the options with a short description can be found at |Q_op|.
 	generated from a list of items, e.g., the Buffers menu.  Changing this
 	option has no direct effect, the menu must be refreshed first.
 
+						*'messagesopt'* *'mopt'*
+'messagesopt' 'mopt'	string	(default "hit-enter,history:500")
+			global
+	Option settings when outputting messages.  It can consist of the
+	following items.  Items must be separated by a comma.
+
+	hit-enter	Use |hit-enter| prompt when the message is longer than
+			'cmdheight' size.
+
+	wait:{n}	Ignored when "hit-enter" is present.  Instead of using
+			|hit-enter| prompt, will simply wait for {n}
+			milliseconds so the user has a chance to read the
+			message, use 0 to disable sleep (but then the user may
+			miss an important message).
+
+	history:{n}	Determines how many entries are remembered in the
+			|:messages| history.  The maximum value is 10000.
+			Setting it to zero clears the message history.
+
 						*'mkspellmem'* *'msm'*
 'mkspellmem' 'msm'	string	(default "460000,2000,500")
 			global
@@ -4290,13 +4309,6 @@ A jump table for the options with a short description can be found at |Q_op|.
 	Defines the maximum time in msec between two mouse clicks for the
 	second click to be recognized as a multi click.
 
-						*'msghistory'* *'mhi'*
-'msghistory' 'mhi'	number	(default 500)
-			global
-	Determines how many entries are remembered in the |:messages| history.
-	The maximum value is 10000.
-	Setting it to zero clears the message history.
-
 						*'nrformats'* *'nf'*
 'nrformats' 'nf'	string	(default "bin,hex")
 			local to buffer
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 01d5235d94..59d270e640 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -3016,7 +3016,7 @@ vim.go.hid = vim.go.hidden
 
 --- A history of ":" commands, and a history of previous search patterns
 --- is remembered.  This option decides how many entries may be stored in
---- each of these histories (see `cmdline-editing` and 'msghistory' for
+--- each of these histories (see `cmdline-editing` and 'messagesopt' for
 --- the number of messages to remember).
 --- The maximum value is 10000.
 ---
@@ -4084,6 +4084,28 @@ vim.o.mis = vim.o.menuitems
 vim.go.menuitems = vim.o.menuitems
 vim.go.mis = vim.go.menuitems
 
+--- Option settings when outputting messages.  It can consist of the
+--- following items.  Items must be separated by a comma.
+---
+--- hit-enter	Use `hit-enter` prompt when the message is longer than
+--- 		'cmdheight' size.
+---
+--- wait:{n}	Ignored when "hit-enter" is present.  Instead of using
+--- 		`hit-enter` prompt, will simply wait for {n}
+--- 		milliseconds so the user has a chance to read the
+--- 		message, use 0 to disable sleep (but then the user may
+--- 		miss an important message).
+---
+--- history:{n}	Determines how many entries are remembered in the
+--- 		`:messages` history.  The maximum value is 10000.
+--- 		Setting it to zero clears the message history.
+---
+--- @type string
+vim.o.messagesopt = "hit-enter,history:500"
+vim.o.mopt = vim.o.messagesopt
+vim.go.messagesopt = vim.o.messagesopt
+vim.go.mopt = vim.go.messagesopt
+
 --- Parameters for `:mkspell`.  This tunes when to start compressing the
 --- word tree.  Compression can be slow when there are many words, but
 --- it's needed to avoid running out of memory.  The amount of memory used
@@ -4379,16 +4401,6 @@ vim.o.mouset = vim.o.mousetime
 vim.go.mousetime = vim.o.mousetime
 vim.go.mouset = vim.go.mousetime
 
---- Determines how many entries are remembered in the `:messages` history.
---- The maximum value is 10000.
---- Setting it to zero clears the message history.
----
---- @type integer
-vim.o.msghistory = 500
-vim.o.mhi = vim.o.msghistory
-vim.go.msghistory = vim.o.msghistory
-vim.go.mhi = vim.go.msghistory
-
 --- This defines what bases Vim will consider for numbers when using the
 --- CTRL-A and CTRL-X commands for adding to and subtracting from a number
 --- respectively; see `CTRL-A` for more info on these commands.
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 48a4bd2816..923f55f8fa 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -626,8 +626,8 @@ call AddOption("terse", gettext("add 's' flag in 'shortmess' (don't show se
 call BinOptionG("terse", &terse)
 call AddOption("shortmess", gettext("list of flags to make messages shorter"))
 call OptionG("shm", &shm)
-call AddOption("msghistory", gettext("how many messages are remembered"))
-call append("$", " \tset mhi=" . &mhi)
+call AddOption("messagesopt", gettext("Option settings when outputting messages"))
+call OptionG("mopt", &mopt)
 call AddOption("showcmd", gettext("show (partial) command keys in location given by 'showcmdloc'"))
 let &sc = s:old_sc
 call BinOptionG("sc", &sc)
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 9968f32de1..b7c83ea1ac 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -6101,12 +6101,18 @@ static void ex_sleep(exarg_T *eap)
   default:
     semsg(_(e_invarg2), eap->arg); return;
   }
-  do_sleep(len);
+  do_sleep(len, false);
 }
 
 /// Sleep for "msec" milliseconds, but return early on CTRL-C.
-void do_sleep(int64_t msec)
+///
+/// @param hide_cursor  hide the cursor if true
+void do_sleep(int64_t msec, bool hide_cursor)
 {
+  if (hide_cursor) {
+    ui_busy_start();
+  }
+
   ui_flush();  // flush before waiting
   LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int);
 
@@ -6115,6 +6121,10 @@ void do_sleep(int64_t msec)
   if (got_int) {
     vpeekc();
   }
+
+  if (hide_cursor) {
+    ui_busy_stop();
+  }
 }
 
 /// ":winsize" command (obsolete).
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index 779b31e7a0..c79683dc00 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -47,7 +47,9 @@ end
 --- @param s string
 --- @return string
 local lowercase_to_titlecase = function(s)
-  return s:sub(1, 1):upper() .. s:sub(2)
+  return table.concat(vim.tbl_map(function(word) --- @param word string
+    return word:sub(1, 1):upper() .. word:sub(2)
+  end, vim.split(s, '[-_]')))
 end
 
 -- Generate options enum file
@@ -177,7 +179,7 @@ for _, option in ipairs(options_meta) do
       vars_w(
         ('  kOpt%sFlag%s = 0x%02x,'):format(
           opt_name,
-          lowercase_to_titlecase(flag_name),
+          lowercase_to_titlecase(flag_name:gsub(':$', '')),
           enum_values[flag_name]
         )
       )
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 0b1156a6bd..a32a06edca 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -26,6 +26,7 @@
 #include "nvim/event/loop.h"
 #include "nvim/event/multiqueue.h"
 #include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_docmd.h"
 #include "nvim/ex_eval.h"
 #include "nvim/fileio.h"
 #include "nvim/garray.h"
@@ -94,6 +95,16 @@ static char *confirm_msg_tail;              // tail of confirm_msg
 MessageHistoryEntry *first_msg_hist = NULL;
 MessageHistoryEntry *last_msg_hist = NULL;
 static int msg_hist_len = 0;
+static int msg_hist_max = 500;  // The default max value is 500
+
+// args in 'messagesopt' option
+#define MESSAGES_OPT_HIT_ENTER "hit-enter"
+#define MESSAGES_OPT_WAIT "wait:"
+#define MESSAGES_OPT_HISTORY "history:"
+
+// The default is "hit-enter,history:500"
+static int msg_flags = kOptMoptFlagHitEnter | kOptMoptFlagHistory;
+static int msg_wait = 0;
 
 static FILE *verbose_fd = NULL;
 static bool verbose_did_open = false;
@@ -1038,14 +1049,69 @@ int delete_first_msg(void)
   return OK;
 }
 
-void check_msg_hist(void)
+static void check_msg_hist(void)
 {
   // Don't let the message history get too big
-  while (msg_hist_len > 0 && msg_hist_len > p_mhi) {
+  while (msg_hist_len > 0 && msg_hist_len > msg_hist_max) {
     (void)delete_first_msg();
   }
 }
 
+int messagesopt_changed(void)
+{
+  int messages_flags_new = 0;
+  int messages_wait_new = 0;
+  int messages_history_new = 0;
+
+  char *p = p_meo;
+  while (*p != NUL) {
+    if (strnequal(p, S_LEN(MESSAGES_OPT_HIT_ENTER))) {
+      p += STRLEN_LITERAL(MESSAGES_OPT_HIT_ENTER);
+      messages_flags_new |= kOptMoptFlagHitEnter;
+    } else if (strnequal(p, S_LEN(MESSAGES_OPT_WAIT))
+               && ascii_isdigit(p[STRLEN_LITERAL(MESSAGES_OPT_WAIT)])) {
+      p += STRLEN_LITERAL(MESSAGES_OPT_WAIT);
+      messages_wait_new = getdigits_int(&p, false, INT_MAX);
+      messages_flags_new |= kOptMoptFlagWait;
+    } else if (strnequal(p, S_LEN(MESSAGES_OPT_HISTORY))
+               && ascii_isdigit(p[STRLEN_LITERAL(MESSAGES_OPT_HISTORY)])) {
+      p += STRLEN_LITERAL(MESSAGES_OPT_HISTORY);
+      messages_history_new = getdigits_int(&p, false, INT_MAX);
+      messages_flags_new |= kOptMoptFlagHistory;
+    }
+
+    if (*p != ',' && *p != NUL) {
+      return FAIL;
+    }
+    if (*p == ',') {
+      p++;
+    }
+  }
+
+  // Either "wait" or "hit-enter" is required
+  if (!(messages_flags_new & (kOptMoptFlagHitEnter | kOptMoptFlagWait))) {
+    return FAIL;
+  }
+
+  // "history" must be set
+  if (!(messages_flags_new & kOptMoptFlagHistory)) {
+    return FAIL;
+  }
+
+  // "history" must be <= 10000
+  if (messages_history_new > 10000) {
+    return FAIL;
+  }
+
+  msg_flags = messages_flags_new;
+  msg_wait = messages_wait_new;
+
+  msg_hist_max = messages_history_new;
+  check_msg_hist();
+
+  return OK;
+}
+
 /// :messages command implementation
 void ex_messages(exarg_T *eap)
   FUNC_ATTR_NONNULL_ALL
@@ -1209,83 +1275,88 @@ void wait_return(int redraw)
       cmdline_row = Rows - 1;
     }
 
-    hit_return_msg(true);
-
-    do {
-      // Remember "got_int", if it is set vgetc() probably returns a
-      // CTRL-C, but we need to loop then.
-      had_got_int = got_int;
-
-      // Don't do mappings here, we put the character back in the
-      // typeahead buffer.
-      no_mapping++;
-      allow_keys++;
-
-      // Temporarily disable Recording. If Recording is active, the
-      // character will be recorded later, since it will be added to the
-      // typebuf after the loop
-      const int save_reg_recording = reg_recording;
-      save_scriptout = scriptout;
-      reg_recording = 0;
-      scriptout = NULL;
-      c = safe_vgetc();
-      if (had_got_int && !global_busy) {
-        got_int = false;
-      }
-      no_mapping--;
-      allow_keys--;
-      reg_recording = save_reg_recording;
-      scriptout = save_scriptout;
-
-      // Allow scrolling back in the messages.
-      // Also accept scroll-down commands when messages fill the screen,
-      // to avoid that typing one 'j' too many makes the messages
-      // disappear.
-      if (p_more) {
-        if (c == 'b' || c == 'k' || c == 'u' || c == 'g'
-            || c == K_UP || c == K_PAGEUP) {
-          if (msg_scrolled > Rows) {
-            // scroll back to show older messages
-            do_more_prompt(c);
-          } else {
-            msg_didout = false;
-            c = K_IGNORE;
-            msg_col = 0;
-          }
-          if (quit_more) {
-            c = CAR;                            // just pretend CR was hit
-            quit_more = false;
-            got_int = false;
-          } else if (c != K_IGNORE) {
+    if (msg_flags & kOptMoptFlagHitEnter) {
+      hit_return_msg(true);
+
+      do {
+        // Remember "got_int", if it is set vgetc() probably returns a
+        // CTRL-C, but we need to loop then.
+        had_got_int = got_int;
+
+        // Don't do mappings here, we put the character back in the
+        // typeahead buffer.
+        no_mapping++;
+        allow_keys++;
+
+        // Temporarily disable Recording. If Recording is active, the
+        // character will be recorded later, since it will be added to the
+        // typebuf after the loop
+        const int save_reg_recording = reg_recording;
+        save_scriptout = scriptout;
+        reg_recording = 0;
+        scriptout = NULL;
+        c = safe_vgetc();
+        if (had_got_int && !global_busy) {
+          got_int = false;
+        }
+        no_mapping--;
+        allow_keys--;
+        reg_recording = save_reg_recording;
+        scriptout = save_scriptout;
+
+        // Allow scrolling back in the messages.
+        // Also accept scroll-down commands when messages fill the screen,
+        // to avoid that typing one 'j' too many makes the messages
+        // disappear.
+        if (p_more) {
+          if (c == 'b' || c == 'k' || c == 'u' || c == 'g'
+              || c == K_UP || c == K_PAGEUP) {
+            if (msg_scrolled > Rows) {
+              // scroll back to show older messages
+              do_more_prompt(c);
+            } else {
+              msg_didout = false;
+              c = K_IGNORE;
+              msg_col = 0;
+            }
+            if (quit_more) {
+              c = CAR;  // just pretend CR was hit
+              quit_more = false;
+              got_int = false;
+            } else if (c != K_IGNORE) {
+              c = K_IGNORE;
+              hit_return_msg(false);
+            }
+          } else if (msg_scrolled > Rows - 2
+                     && (c == 'j' || c == 'd' || c == 'f'
+                         || c == K_DOWN || c == K_PAGEDOWN)) {
             c = K_IGNORE;
-            hit_return_msg(false);
           }
-        } else if (msg_scrolled > Rows - 2
-                   && (c == 'j' || c == 'd' || c == 'f'
-                       || c == K_DOWN || c == K_PAGEDOWN)) {
-          c = K_IGNORE;
         }
+      } while ((had_got_int && c == Ctrl_C)
+               || c == K_IGNORE
+               || c == K_LEFTDRAG || c == K_LEFTRELEASE
+               || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE
+               || c == K_RIGHTDRAG || c == K_RIGHTRELEASE
+               || c == K_MOUSELEFT || c == K_MOUSERIGHT
+               || c == K_MOUSEDOWN || c == K_MOUSEUP
+               || c == K_MOUSEMOVE);
+      os_breakcheck();
+
+      // Avoid that the mouse-up event causes visual mode to start.
+      if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE
+          || c == K_X1MOUSE || c == K_X2MOUSE) {
+        jump_to_mouse(MOUSE_SETPOS, NULL, 0);
+      } else if (vim_strchr("\r\n ", c) == NULL && c != Ctrl_C) {
+        // Put the character back in the typeahead buffer.  Don't use the
+        // stuff buffer, because lmaps wouldn't work.
+        ins_char_typebuf(vgetc_char, vgetc_mod_mask, true);
+        do_redraw = true;  // need a redraw even though there is typeahead
       }
-    } while ((had_got_int && c == Ctrl_C)
-             || c == K_IGNORE
-             || c == K_LEFTDRAG || c == K_LEFTRELEASE
-             || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE
-             || c == K_RIGHTDRAG || c == K_RIGHTRELEASE
-             || c == K_MOUSELEFT || c == K_MOUSERIGHT
-             || c == K_MOUSEDOWN || c == K_MOUSEUP
-             || c == K_MOUSEMOVE);
-    os_breakcheck();
-
-    // Avoid that the mouse-up event causes visual mode to start.
-    if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE
-        || c == K_X1MOUSE || c == K_X2MOUSE) {
-      jump_to_mouse(MOUSE_SETPOS, NULL, 0);
-    } else if (vim_strchr("\r\n ", c) == NULL && c != Ctrl_C) {
-      // Put the character back in the typeahead buffer.  Don't use the
-      // stuff buffer, because lmaps wouldn't work.
-      ins_char_typebuf(vgetc_char, vgetc_mod_mask, true);
-      do_redraw = true;             // need a redraw even though there is
-                                    // typeahead
+    } else {
+      c = CAR;
+      // Wait to allow the user to verify the output.
+      do_sleep(msg_wait, true);
     }
   }
   redir_off = false;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index ba84380529..92a6068c5a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -812,7 +812,7 @@ static void normal_get_additional_char(NormalState *s)
       // There is a busy wait here when typing "f" and then
       // something different from CTRL-N.  Can't be avoided.
       while ((s->c = vpeekc()) <= 0 && towait > 0) {
-        do_sleep(towait > 50 ? 50 : towait);
+        do_sleep(towait > 50 ? 50 : towait, false);
         towait -= 50;
       }
       if (s->c > 0) {
@@ -5561,7 +5561,7 @@ static void nv_g_cmd(cmdarg_T *cap)
 
   // "gs": Goto sleep.
   case 's':
-    do_sleep(cap->count1 * 1000);
+    do_sleep(cap->count1 * 1000, false);
     break;
 
   // "ga": Display the ascii value of the character under the
diff --git a/src/nvim/option.c b/src/nvim/option.c
index fd0ac375a6..e261f06b42 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2199,13 +2199,6 @@ static const char *did_set_modified(optset_T *args)
   return NULL;
 }
 
-/// Process the updated 'msghistory' option value.
-static const char *did_set_msghistory(optset_T *args FUNC_ATTR_UNUSED)
-{
-  check_msg_hist();
-  return NULL;
-}
-
 /// Process the updated 'number' or 'relativenumber' option value.
 static const char *did_set_number_relativenumber(optset_T *args)
 {
@@ -2886,13 +2879,6 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e
       return e_invarg;
     }
     break;
-  case kOptMsghistory:
-    if (value < 0) {
-      return e_positive;
-    } else if (value > 10000) {
-      return e_invarg;
-    }
-    break;
   case kOptPyxversion:
     if (value == 0) {
       *newval = 3;
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index 3bb2035e7c..aca876bddb 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -448,6 +448,7 @@ EXTERN OptInt p_mfd;            ///< 'maxfuncdepth'
 EXTERN OptInt p_mmd;            ///< 'maxmapdepth'
 EXTERN OptInt p_mmp;            ///< 'maxmempattern'
 EXTERN OptInt p_mis;            ///< 'menuitems'
+EXTERN char *p_meo;             ///< 'messagesopt'
 EXTERN char *p_msm;             ///< 'mkspellmem'
 EXTERN int p_ml;                ///< 'modeline'
 EXTERN int p_mle;               ///< 'modelineexpr'
@@ -464,7 +465,6 @@ EXTERN OptInt p_mousescroll_vert INIT( = MOUSESCROLL_VERT_DFLT);
 EXTERN OptInt p_mousescroll_hor INIT( = MOUSESCROLL_HOR_DFLT);
 EXTERN OptInt p_mouset;         ///< 'mousetime'
 EXTERN int p_more;              ///< 'more'
-EXTERN OptInt p_mhi;            ///< 'msghistory'
 EXTERN char *p_nf;              ///< 'nrformats'
 EXTERN char *p_opfunc;          ///< 'operatorfunc'
 EXTERN char *p_para;            ///< 'paragraphs'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 97fe09f376..a5d5a45b59 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -4094,7 +4094,7 @@ return {
       desc = [=[
         A history of ":" commands, and a history of previous search patterns
         is remembered.  This option decides how many entries may be stored in
-        each of these histories (see |cmdline-editing| and 'msghistory' for
+        each of these histories (see |cmdline-editing| and 'messagesopt' for
         the number of messages to remember).
         The maximum value is 10000.
       ]=],
@@ -5448,6 +5448,38 @@ return {
       type = 'number',
       varname = 'p_mis',
     },
+    {
+      abbreviation = 'mopt',
+      cb = 'did_set_messagesopt',
+      defaults = { if_true = 'hit-enter,history:500' },
+      values = { 'hit-enter', 'wait:', 'history:' },
+      flags = true,
+      deny_duplicates = true,
+      desc = [=[
+        Option settings when outputting messages.  It can consist of the
+        following items.  Items must be separated by a comma.
+
+        hit-enter	Use |hit-enter| prompt when the message is longer than
+        		'cmdheight' size.
+
+        wait:{n}	Ignored when "hit-enter" is present.  Instead of using
+        		|hit-enter| prompt, will simply wait for {n}
+        		milliseconds so the user has a chance to read the
+        		message, use 0 to disable sleep (but then the user may
+        		miss an important message).
+
+        history:{n}	Determines how many entries are remembered in the
+        		|:messages| history.  The maximum value is 10000.
+        		Setting it to zero clears the message history.
+      ]=],
+      expand_cb = 'expand_set_messagesopt',
+      full_name = 'messagesopt',
+      list = 'onecommacolon',
+      scope = { 'global' },
+      short_desc = N_('options for outputting messages'),
+      type = 'string',
+      varname = 'p_meo',
+    },
     {
       abbreviation = 'msm',
       cb = 'did_set_mkspellmem',
@@ -5892,21 +5924,6 @@ return {
       type = 'number',
       varname = 'p_mouset',
     },
-    {
-      abbreviation = 'mhi',
-      cb = 'did_set_msghistory',
-      defaults = { if_true = 500 },
-      desc = [=[
-        Determines how many entries are remembered in the |:messages| history.
-        The maximum value is 10000.
-        Setting it to zero clears the message history.
-      ]=],
-      full_name = 'msghistory',
-      scope = { 'global' },
-      short_desc = N_('how many messages are remembered'),
-      type = 'number',
-      varname = 'p_mhi',
-    },
     {
       abbreviation = 'nf',
       cb = 'did_set_nrformats',
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 918443db9f..75b6585553 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -1682,6 +1682,24 @@ const char *did_set_matchpairs(optset_T *args)
   return NULL;
 }
 
+/// Process the updated 'messagesopt' option value.
+const char *did_set_messagesopt(optset_T *args FUNC_ATTR_UNUSED)
+{
+  if (messagesopt_changed() == FAIL) {
+    return e_invarg;
+  }
+  return NULL;
+}
+
+int expand_set_messagesopt(optexpand_T *args, int *numMatches, char ***matches)
+{
+  return expand_set_opt_string(args,
+                               opt_mopt_values,
+                               ARRAY_SIZE(opt_mopt_values) - 1,
+                               numMatches,
+                               matches);
+}
+
 /// The 'mkspellmem' option is changed.
 const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED)
 {
diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua
index bc58551a34..063bd05618 100644
--- a/test/functional/legacy/messages_spec.lua
+++ b/test/functional/legacy/messages_spec.lua
@@ -689,4 +689,43 @@ describe('messages', function()
     ]])
     os.remove('b.txt')
   end)
+
+  -- oldtest: Test_messagesopt_wait()
+  it('&messagesopt "wait"', function()
+    screen = Screen.new(45, 6)
+    command('set cmdheight=1')
+
+    -- Check hit-enter prompt
+    command('set messagesopt=hit-enter,history:500')
+    feed(":echo 'foo' | echo 'bar' | echo 'baz'\n")
+    screen:expect([[
+                                                   |
+      {3:                                             }|
+      foo                                          |
+      bar                                          |
+      baz                                          |
+      {6:Press ENTER or type command to continue}^      |
+    ]])
+    feed('')
+
+    -- Check no hit-enter prompt when "wait:" is set
+    command('set messagesopt=wait:100,history:500')
+    feed(":echo 'foo' | echo 'bar' | echo 'baz'\n")
+    screen:expect({
+      grid = [[
+                                                   |
+      {1:~                                            }|
+      {3:                                             }|
+      foo                                          |
+      bar                                          |
+      baz                                          |
+    ]],
+      timeout = 100,
+    })
+    screen:expect([[
+      ^                                             |
+      {1:~                                            }|*4
+                                                   |
+    ]])
+  end)
 end)
diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim
index be5a7e6ee4..74aea93ee9 100644
--- a/test/old/testdir/gen_opt_test.vim
+++ b/test/old/testdir/gen_opt_test.vim
@@ -117,7 +117,6 @@ let test_values = {
       "\ 'imstyle': [[0, 1], [-1, 2, 999]],
       \ 'lines': [[2, 24, 1000], [-1, 0, 1]],
       \ 'linespace': [[-1, 0, 2, 4, 999], ['']],
-      \ 'msghistory': [[0, 1, 100, 10000], [-1, 10001]],
       \ 'numberwidth': [[1, 4, 8, 10, 11, 20], [-1, 0, 21]],
       \ 'regexpengine': [[0, 1, 2], [-1, 3, 999]],
       \ 'report': [[0, 1, 2, 9999], [-1]],
@@ -264,6 +263,11 @@ let test_values = {
       \		'eol:\\u21b5', 'eol:\\U000021b5', 'eol:x,space:y'],
       \		['xxx', 'eol:']],
       \ 'matchpairs': [['', '(:)', '(:),<:>'], ['xxx']],
+      \ 'messagesopt': [['hit-enter,history:1', 'hit-enter,history:10000',
+      \		'history:100,wait:100', 'history:0,wait:0',
+      \		'hit-enter,history:1,wait:1'],
+      \		['xxx', 'history:500', 'hit-enter,history:-1',
+      \		'hit-enter,history:10001', 'hit-enter']],
       \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']],
       \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'],
       \		['xxx', 'n,v,i']],
diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
index 2f34ecb414..cbce0e908d 100644
--- a/test/old/testdir/test_cmdline.vim
+++ b/test/old/testdir/test_cmdline.vim
@@ -4181,30 +4181,4 @@ func Test_cd_bslash_completion_windows()
   let &shellslash = save_shellslash
 endfunc
 
-func Test_msghistory()
-  " After setting 'msghistory' to 2 and outputting a message 4 times with
-  " :echomsg, is the number of output lines of :messages 2?
-  set msghistory=2
-  echomsg 'foo'
-  echomsg 'bar'
-  echomsg 'baz'
-  echomsg 'foobar'
-  call assert_equal(['baz', 'foobar'], GetMessages())
-
-  " When the number of messages is 10 and 'msghistory' is changed to 5, is the
-  " number of output lines of :messages 5?
-  set msghistory=10
-  for num in range(1, 10)
-    echomsg num
-  endfor
-  set msghistory=5
-  call assert_equal(5, len(GetMessages()))
-
-  " Check empty list
-  set msghistory=0
-  call assert_true(empty(GetMessages()))
-
-  set msghistory&
-endfunc
-
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_messages.vim b/test/old/testdir/test_messages.vim
index ac5184645f..d79bc0fae7 100644
--- a/test/old/testdir/test_messages.vim
+++ b/test/old/testdir/test_messages.vim
@@ -216,6 +216,7 @@ endfunc
 " Test more-prompt (see :help more-prompt).
 func Test_message_more()
   CheckRunVimInTerminal
+
   let buf = RunVimInTerminal('', {'rows': 6})
   call term_sendkeys(buf, ":call setline(1, range(1, 100))\n")
 
@@ -611,4 +612,50 @@ func Test_cmdheight_zero()
   tabonly
 endfunc
 
+func Test_messagesopt_history()
+  " After setting 'messagesopt' "history" to 2 and outputting a message 4 times
+  " with :echomsg, is the number of output lines of :messages 2?
+  set messagesopt=hit-enter,history:2
+  echomsg 'foo'
+  echomsg 'bar'
+  echomsg 'baz'
+  echomsg 'foobar'
+  call assert_equal(['baz', 'foobar'], GetMessages())
+
+  " When the number of messages is 10 and 'messagesopt' "history" is changed to
+  " 5, is the number of output lines of :messages 5?
+  set messagesopt=hit-enter,history:10
+  for num in range(1, 10)
+    echomsg num
+  endfor
+  set messagesopt=hit-enter,history:5
+  call assert_equal(5, len(GetMessages()))
+
+  " Check empty list
+  set messagesopt=hit-enter,history:0
+  call assert_true(empty(GetMessages()))
+
+  set messagesopt&
+endfunc
+
+func Test_messagesopt_wait()
+  CheckRunVimInTerminal
+
+  let buf = RunVimInTerminal('', {'rows': 6, 'cols': 45})
+  call term_sendkeys(buf, ":set cmdheight=1\n")
+
+  " Check hit-enter prompt
+  call term_sendkeys(buf, ":set messagesopt=hit-enter,history:500\n")
+  call term_sendkeys(buf, ":echo 'foo' | echo 'bar' echo 'baz'\n")
+  call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
+
+  " Check no hit-enter prompt when "wait:" is set
+  call term_sendkeys(buf, ":set messagesopt=wait:100,history:500\n")
+  call term_sendkeys(buf, ":echo 'foo' | echo 'bar' echo 'baz'\n")
+  call WaitForAssert({-> assert_equal('                           0,0-1         All', term_getline(buf, 6))})
+
+  " clean up
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
index 3773775564..d090186b62 100644
--- a/test/old/testdir/test_options.vim
+++ b/test/old/testdir/test_options.vim
@@ -644,6 +644,10 @@ func Test_set_completion_string_values()
   " call feedkeys(":set hl=8b i\\\\\"\", 'xt')
   " call assert_equal("\"set hl=8bi i", @:)
 
+  " messagesopt
+  call assert_equal(['history:', 'hit-enter', 'wait:'],
+        \ getcompletion('set messagesopt+=', 'cmdline')->sort())
+
   "
   " Test flag lists
   "
@@ -743,7 +747,6 @@ func Test_set_option_errors()
   call assert_fails('set backupcopy=', 'E474:')
   call assert_fails('set regexpengine=3', 'E474:')
   call assert_fails('set history=10001', 'E474:')
-  call assert_fails('set msghistory=10001', 'E474:')
   call assert_fails('set numberwidth=21', 'E474:')
   call assert_fails('set colorcolumn=-a', 'E474:')
   call assert_fails('set colorcolumn=a', 'E474:')
@@ -757,7 +760,6 @@ func Test_set_option_errors()
   endif
   call assert_fails('set helpheight=-1', 'E487:')
   call assert_fails('set history=-1', 'E487:')
-  call assert_fails('set msghistory=-1', 'E487:')
   call assert_fails('set report=-1', 'E487:')
   call assert_fails('set shiftwidth=-1', 'E487:')
   call assert_fails('set sidescroll=-1', 'E487:')
@@ -2517,6 +2519,7 @@ func Test_string_option_revert_on_failure()
         \ ['lispoptions', 'expr:1', 'a123'],
         \ ['listchars', 'tab:->', 'tab:'],
         \ ['matchpairs', '<:>', '<:'],
+        \ ['messagesopt', 'hit-enter,history:100', 'a123'],
         \ ['mkspellmem', '100000,1000,100', '100000'],
         \ ['mouse', 'nvi', 'z'],
         \ ['mousemodel', 'extend', 'a123'],
-- 
cgit 


From 9c278af7cc96e5b7f07cd9fdec27951651063a76 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 6 Dec 2024 19:18:03 +0100
Subject: fix(inspect): show priority for treesitter highlights

Problem: `:Inspect` does not show priority for treesitter highlights,
leading to confusion why sometimes earlier highlights override later
highlights.

Solution: Also print priority metadata if set.
---
 runtime/lua/vim/_inspector.lua | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index f4e41a31e5..520c437906 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -191,7 +191,12 @@ function vim.show_pos(bufnr, row, col, filter)
     append('Treesitter', 'Title')
     nl()
     for _, capture in ipairs(items.treesitter) do
-      item(capture, capture.lang)
+      item(
+        capture,
+        capture.metadata.priority
+            and string.format('%s priority: %d', capture.lang, capture.metadata.priority)
+          or capture.lang
+      )
     end
     nl()
   end
-- 
cgit 


From c2bf09ddff4994f901350dd02412087a8abfde0a Mon Sep 17 00:00:00 2001
From: Maria José Solano 
Date: Sun, 24 Nov 2024 13:43:27 -0800
Subject: fix(lsp): check for configuration workspace folders when reusing
 clients

---
 runtime/doc/lsp.txt            |  4 +++-
 runtime/lua/vim/lsp.lua        | 46 +++++++++++++++++++++++++++++++-----------
 runtime/lua/vim/lsp/client.lua | 21 +++----------------
 3 files changed, 40 insertions(+), 31 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 2654d7f14f..1607f3492c 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -872,7 +872,9 @@ start({config}, {opts})                                      *vim.lsp.start()*
                     (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`)
                     Predicate used to decide if a client should be re-used.
                     Used on all running clients. The default implementation
-                    re-uses a client if name and root_dir matches.
+                    re-uses a client if it has the same name and if the given
+                    workspace folders (or root_dir) are all included in the
+                    client's workspace folders.
                   • {bufnr}? (`integer`) Buffer handle to attach to if
                     starting or re-using a client (0 for current).
                   • {attach}? (`boolean`) Whether to attach the client to a
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 4717d7995a..b67b2d6988 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -114,6 +114,22 @@ function lsp._unsupported_method(method)
   return msg
 end
 
+---@private
+---@param workspace_folders string|lsp.WorkspaceFolder[]?
+---@return lsp.WorkspaceFolder[]?
+function lsp._get_workspace_folders(workspace_folders)
+  if type(workspace_folders) == 'table' then
+    return workspace_folders
+  elseif type(workspace_folders) == 'string' then
+    return {
+      {
+        uri = vim.uri_from_fname(workspace_folders),
+        name = workspace_folders,
+      },
+    }
+  end
+end
+
 local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
 
 local format_line_ending = {
@@ -196,19 +212,24 @@ local function reuse_client_default(client, config)
     return false
   end
 
-  if config.root_dir then
-    local root = vim.uri_from_fname(config.root_dir)
-    for _, dir in ipairs(client.workspace_folders or {}) do
-      -- note: do not need to check client.root_dir since that should be client.workspace_folders[1]
-      if root == dir.uri then
-        return true
+  local config_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir)
+    or {}
+  local config_folders_included = 0
+
+  if not next(config_folders) then
+    return false
+  end
+
+  for _, config_folder in ipairs(config_folders) do
+    for _, client_folder in ipairs(client.workspace_folders) do
+      if config_folder.uri == client_folder.uri then
+        config_folders_included = config_folders_included + 1
+        break
       end
     end
   end
 
-  -- TODO(lewis6991): also check config.workspace_folders
-
-  return false
+  return config_folders_included == #config_folders
 end
 
 --- Reset defaults set by `set_defaults`.
@@ -311,9 +332,10 @@ end
 --- @inlinedoc
 ---
 --- Predicate used to decide if a client should be re-used. Used on all
---- running clients. The default implementation re-uses a client if name and
---- root_dir matches.
---- @field reuse_client? (fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean)
+--- running clients. The default implementation re-uses a client if it has the
+--- same name and if the given workspace folders (or root_dir) are all included
+--- in the client's workspace folders.
+--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean
 ---
 --- Buffer handle to attach to if starting or re-using a client (0 for current).
 --- @field bufnr? integer
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index a83d75bf75..7eb023da39 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -365,21 +365,6 @@ local function get_name(id, config)
   return tostring(id)
 end
 
---- @param workspace_folders string|lsp.WorkspaceFolder[]?
---- @return lsp.WorkspaceFolder[]?
-local function get_workspace_folders(workspace_folders)
-  if type(workspace_folders) == 'table' then
-    return workspace_folders
-  elseif type(workspace_folders) == 'string' then
-    return {
-      {
-        uri = vim.uri_from_fname(workspace_folders),
-        name = workspace_folders,
-      },
-    }
-  end
-end
-
 --- @generic T
 --- @param x elem_or_list?
 --- @return T[]
@@ -417,7 +402,7 @@ function Client.create(config)
     flags = config.flags or {},
     get_language_id = config.get_language_id or default_get_language_id,
     capabilities = config.capabilities or lsp.protocol.make_client_capabilities(),
-    workspace_folders = get_workspace_folders(config.workspace_folders or config.root_dir),
+    workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir),
     root_dir = config.root_dir,
     _before_init_cb = config.before_init,
     _on_init_cbs = ensure_list(config.on_init),
@@ -1174,7 +1159,7 @@ function Client:_add_workspace_folder(dir)
     end
   end
 
-  local wf = assert(get_workspace_folders(dir))
+  local wf = assert(lsp._get_workspace_folders(dir))
 
   self:notify(ms.workspace_didChangeWorkspaceFolders, {
     event = { added = wf, removed = {} },
@@ -1189,7 +1174,7 @@ end
 --- Remove a directory to the workspace folders.
 --- @param dir string?
 function Client:_remove_workspace_folder(dir)
-  local wf = assert(get_workspace_folders(dir))
+  local wf = assert(lsp._get_workspace_folders(dir))
 
   self:notify(ms.workspace_didChangeWorkspaceFolders, {
     event = { added = {}, removed = wf },
-- 
cgit 


From bdfba8598b41b891e1fcc8b96163f442baf509b4 Mon Sep 17 00:00:00 2001
From: tris203 
Date: Fri, 6 Dec 2024 16:25:13 +0000
Subject: fix(lsp): cancel pending requests before refreshing

Problem:
Diagnostics and inlay hints can be expensive to calculate, and we
shouldn't stack them as this can cause noticeable lag.

Solution:
Check for duplicate inflight requests and cancel them before issuing a new one.
This ensures that only the latest request is processed, improving
performance and preventing potential conflicts.
---
 runtime/lua/vim/lsp/util.lua | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index ced8aa5745..5bf436f2ca 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -2162,6 +2162,11 @@ function M._refresh(method, opts)
         local first = vim.fn.line('w0', window)
         local last = vim.fn.line('w$', window)
         for _, client in ipairs(clients) do
+          for rid, req in pairs(client.requests) do
+            if req.method == method and req.type == 'pending' and req.bufnr == bufnr then
+              client:cancel_request(rid)
+            end
+          end
           client:request(method, {
             textDocument = textDocument,
             range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding),
-- 
cgit 


From 5c245ec3e95570e515c1665a2ec694828706ac52 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 6 Dec 2024 17:09:49 +0000
Subject: fix: remove vim.lsp._with_extend

Not used anywhere.
---
 runtime/lua/vim/lsp.lua                     | 38 --------------------------
 test/functional/plugin/lsp/handler_spec.lua | 42 -----------------------------
 2 files changed, 80 deletions(-)
 delete mode 100644 test/functional/plugin/lsp/handler_spec.lua

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index b67b2d6988..e1946816da 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1279,44 +1279,6 @@ function lsp.with(handler, override_config)
   end
 end
 
---- Helper function to use when implementing a handler.
---- This will check that all of the keys in the user configuration
---- are valid keys and make sense to include for this handler.
----
---- Will error on invalid keys (i.e. keys that do not exist in the options)
---- @param name string
---- @param options table
---- @param user_config table
-function lsp._with_extend(name, options, user_config)
-  user_config = user_config or {}
-
-  local resulting_config = {} --- @type table
-  for k, v in pairs(user_config) do
-    if options[k] == nil then
-      error(
-        debug.traceback(
-          string.format(
-            'Invalid option for `%s`: %s. Valid options are:\n%s',
-            name,
-            k,
-            vim.inspect(vim.tbl_keys(options))
-          )
-        )
-      )
-    end
-
-    resulting_config[k] = v
-  end
-
-  for k, v in pairs(options) do
-    if resulting_config[k] == nil then
-      resulting_config[k] = v
-    end
-  end
-
-  return resulting_config
-end
-
 --- Registry for client side commands.
 --- This is an extension point for plugins to handle custom commands which are
 --- not part of the core language server protocol specification.
diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua
deleted file mode 100644
index 4b05b676a8..0000000000
--- a/test/functional/plugin/lsp/handler_spec.lua
+++ /dev/null
@@ -1,42 +0,0 @@
-local t = require('test.testutil')
-local n = require('test.functional.testnvim')()
-
-local eq = t.eq
-local exec_lua = n.exec_lua
-local pcall_err = t.pcall_err
-local matches = t.matches
-
-describe('lsp-handlers', function()
-  describe('vim.lsp._with_extend', function()
-    it('should return a table with the default keys', function()
-      eq(
-        { hello = 'world' },
-        exec_lua(function()
-          return vim.lsp._with_extend('test', { hello = 'world' })
-        end)
-      )
-    end)
-
-    it('should override with config keys', function()
-      eq(
-        { hello = 'universe', other = true },
-        exec_lua(function()
-          return vim.lsp._with_extend(
-            'test',
-            { other = true, hello = 'world' },
-            { hello = 'universe' }
-          )
-        end)
-      )
-    end)
-
-    it('should not allow invalid keys', function()
-      matches(
-        '.*Invalid option for `test`.*',
-        pcall_err(exec_lua, function()
-          return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true })
-        end)
-      )
-    end)
-  end)
-end)
-- 
cgit 


From c63e49cce2d20cee129a6312319dde8dcea6e3f6 Mon Sep 17 00:00:00 2001
From: Riley Bruins 
Date: Sat, 7 Dec 2024 03:01:59 -0800
Subject: fix(treesitter): #trim! range for nodes ending at col 0 #31488

Problem:
char-wise folding for `#trim!` ranges are improperly calculated for nodes that
end at column 0, due to the way `get_node_text` works.

Solution:
Add the blank line that `get_node_text` removes for for nodes ending at column
0. Also properly set column positions when performing linewise trims.
---
 runtime/lua/vim/treesitter/query.lua       |  9 ++++++++
 test/functional/treesitter/parser_spec.lua | 35 ++++++++++++++++++++++++++----
 2 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 3c7bc2eb89..dbe3d54c2f 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -593,6 +593,11 @@ local directive_handlers = {
     local start_row, start_col, end_row, end_col = node:range()
 
     local node_text = vim.split(vim.treesitter.get_node_text(node, bufnr), '\n')
+    if end_col == 0 then
+      -- get_node_text() will ignore the last line if the node ends at column 0
+      node_text[#node_text + 1] = ''
+    end
+
     local end_idx = #node_text
     local start_idx = 1
 
@@ -600,6 +605,9 @@ local directive_handlers = {
       while end_idx > 0 and node_text[end_idx]:find('^%s*$') do
         end_idx = end_idx - 1
         end_row = end_row - 1
+        -- set the end position to the last column of the next line, or 0 if we just trimmed the
+        -- last line
+        end_col = end_idx > 0 and #node_text[end_idx] or 0
       end
     end
     if trim_end_cols then
@@ -616,6 +624,7 @@ local directive_handlers = {
       while start_idx <= end_idx and node_text[start_idx]:find('^%s*$') do
         start_idx = start_idx + 1
         start_row = start_row + 1
+        start_col = 0
       end
     end
     if trim_start_cols and node_text[start_idx] then
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index 8a552c7d34..2f80cee226 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -696,14 +696,14 @@ print()
     end)
 
     it('trims only empty lines by default (backwards compatible)', function()
-      insert [[
+      insert(dedent [[
       ## Heading
 
       With some text
 
       ## And another
 
-      With some more]]
+      With some more here]])
 
       local query_text = [[
         ; query
@@ -716,8 +716,35 @@ print()
       end)
 
       eq({
-        { 'fold', { 0, 0, 3, 0 } },
-        { 'fold', { 4, 0, 7, 0 } },
+        { 'fold', { 0, 0, 2, 14 } },
+        { 'fold', { 4, 0, 6, 19 } },
+      }, run_query('markdown', query_text))
+    end)
+
+    it('can trim lines', function()
+      insert(dedent [[
+      - Fold list
+        - Fold list
+          - Fold list
+          - Fold list
+        - Fold list
+      - Fold list
+      ]])
+
+      local query_text = [[
+        ; query
+        ((list_item
+          (list)) @fold
+          (#trim! @fold 1 1 1 1))
+      ]]
+
+      exec_lua(function()
+        vim.treesitter.start(0, 'markdown')
+      end)
+
+      eq({
+        { 'fold', { 0, 0, 4, 13 } },
+        { 'fold', { 1, 2, 3, 15 } },
       }, run_query('markdown', query_text))
     end)
   end)
-- 
cgit 


From 4817547ec4630c2af2c94ade07bbf220992ad0d7 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sat, 7 Dec 2024 21:42:44 +0800
Subject: feat(ex_cmds): :sleep! hides the cursor while sleeping (#31493)

Problem:  :sleep! not hiding the cursor is an arbitrary difference from
          Vim without obvious justification, and Vim's behavior isn't
          easily achievable in Nvim.
Solution: Make :sleep! hide the cursor while sleeping.

Ref:
https://github.com/neovim/neovim/commit/6a01b3fcc361960e559db459e1524418bc76dd66
https://github.com/neovim/neovim/commit/b5c0ade43790cf18f6a54858ddad30d8d9667cf9
---
 runtime/doc/index.txt              |  2 ++
 runtime/doc/various.txt            |  3 +--
 runtime/doc/vim_diff.txt           |  1 -
 src/nvim/ex_docmd.c                |  4 +++-
 test/functional/ui/cursor_spec.lua | 34 ++++++++++++++++++++++++++++++++++
 5 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 9ee75ea950..d81d59c56f 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1569,6 +1569,8 @@ tag		command		action ~
 |:sign|		:sig[n]		manipulate signs
 |:silent|	:sil[ent]	run a command silently
 |:sleep|	:sl[eep]	do nothing for a few seconds
+|:sleep!|	:sl[eep]!	do nothing for a few seconds, without the
+				cursor visible
 |:slast|	:sla[st]	split window and go to last file in the
 				argument list
 |:smagic|	:sm[agic]	:substitute with 'magic'
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 5049439337..6074931565 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -552,8 +552,7 @@ gO			Show a filetype-specific, navigable "outline" of the
 			Queued messages are processed during the sleep.
 
 							*:sl!* *:sleep!*
-:[N]sl[eep]! [N][m]	Same as above. Unlike Vim, it does not hide the
-			cursor. |vim-differences|
+:[N]sl[eep]! [N][m]	Same as above, but hide the cursor.
 
 ==============================================================================
 2. Using Vim like less or more					*less*
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 7021e1d3e2..bfef5d5d4a 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -670,7 +670,6 @@ Commands:
 - :promptrepl
 - :scriptversion (always version 1)
 - :shell
-- :sleep! (does not hide the cursor; same as :sleep)
 - :smile
 - :tearoff
 - :cstag
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index b7c83ea1ac..b32a8b867f 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -6101,7 +6101,9 @@ static void ex_sleep(exarg_T *eap)
   default:
     semsg(_(e_invarg2), eap->arg); return;
   }
-  do_sleep(len, false);
+
+  // Hide the cursor if invoked with !
+  do_sleep(len, eap->forceit);
 }
 
 /// Sleep for "msec" milliseconds, but return early on CTRL-C.
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index d7c0657820..edf826a1d9 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -361,4 +361,38 @@ describe('ui/cursor', function()
       end
     end)
   end)
+
+  it(':sleep does not hide cursor when sleeping', function()
+    n.feed(':sleep 100m | echo 42\n')
+    screen:expect({
+      grid = [[
+      ^                         |
+      {1:~                        }|*3
+      :sleep 100m | echo 42    |
+    ]],
+      timeout = 100,
+    })
+    screen:expect([[
+      ^                         |
+      {1:~                        }|*3
+      42                       |
+    ]])
+  end)
+
+  it(':sleep! hides cursor when sleeping', function()
+    n.feed(':sleep! 100m | echo 42\n')
+    screen:expect({
+      grid = [[
+                               |
+      {1:~                        }|*3
+      :sleep! 100m | echo 42   |
+    ]],
+      timeout = 100,
+    })
+    screen:expect([[
+      ^                         |
+      {1:~                        }|*3
+      42                       |
+    ]])
+  end)
 end)
-- 
cgit 


From b52ffd0a59df3b79f2f3f2338485f235da94478d Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sat, 7 Dec 2024 10:57:09 +0100
Subject: fix(inspect): always show priority

Problem: It is not obvious if a treesitter highlight priority shown in
`:Inspect` is higher or lower than the default.

Solution: Also print default priority (`vim.hl.priorities.treesitter`).
Add padding for better readability.
---
 runtime/lua/vim/_inspector.lua | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index 520c437906..a09dd55a47 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -178,7 +178,7 @@ function vim.show_pos(bufnr, row, col, filter)
     if data.hl_group ~= data.hl_group_link then
       append('links to ', 'MoreMsg')
       append(data.hl_group_link, data.hl_group_link)
-      append(' ')
+      append('   ')
     end
     if comment then
       append(comment, 'Comment')
@@ -193,9 +193,11 @@ function vim.show_pos(bufnr, row, col, filter)
     for _, capture in ipairs(items.treesitter) do
       item(
         capture,
-        capture.metadata.priority
-            and string.format('%s priority: %d', capture.lang, capture.metadata.priority)
-          or capture.lang
+        string.format(
+          'priority: %d   language: %s',
+          capture.metadata.priority or vim.hl.priorities.treesitter,
+          capture.lang
+        )
       )
     end
     nl()
-- 
cgit 


From 668d2569b4109e7e83c45578c506c1c64dbd5e87 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Sat, 7 Dec 2024 13:05:05 +0000
Subject: refactor: add vim._resolve_bufnr

---
 runtime/lua/vim/_inspector.lua          |  2 +-
 runtime/lua/vim/diagnostic.lua          | 33 +++++++++++++--------------------
 runtime/lua/vim/lsp.lua                 | 28 ++++++++--------------------
 runtime/lua/vim/lsp/buf.lua             |  4 ++--
 runtime/lua/vim/lsp/client.lua          | 18 +++---------------
 runtime/lua/vim/lsp/codelens.lua        |  8 ++------
 runtime/lua/vim/lsp/completion.lua      |  2 +-
 runtime/lua/vim/lsp/diagnostic.lua      |  4 +---
 runtime/lua/vim/lsp/inlay_hint.lua      | 21 ++++++---------------
 runtime/lua/vim/lsp/semantic_tokens.lua | 19 +++++--------------
 runtime/lua/vim/lsp/util.lua            | 11 +++--------
 runtime/lua/vim/shared.lua              | 10 ++++++++++
 runtime/lua/vim/treesitter.lua          | 22 ++++++----------------
 13 files changed, 61 insertions(+), 121 deletions(-)

diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index a09dd55a47..15b0733671 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -53,7 +53,7 @@ function vim.inspect_pos(bufnr, row, col, filter)
     local cursor = vim.api.nvim_win_get_cursor(win)
     row, col = cursor[1] - 1, cursor[2]
   end
-  bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr
+  bufnr = vim._resolve_bufnr(bufnr)
 
   local results = {
     treesitter = {}, --- @type table[]
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index b8ffb26377..340bca4f6b 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -573,13 +573,6 @@ local underline_highlight_map = make_highlight_map('Underline')
 local floating_highlight_map = make_highlight_map('Floating')
 local sign_highlight_map = make_highlight_map('Sign')
 
-local function get_bufnr(bufnr)
-  if not bufnr or bufnr == 0 then
-    return api.nvim_get_current_buf()
-  end
-  return bufnr
-end
-
 --- @param diagnostics vim.Diagnostic[]
 --- @return table
 local function diagnostic_lines(diagnostics)
@@ -642,7 +635,7 @@ end
 --- @param namespace integer
 --- @param bufnr? integer
 local function save_extmarks(namespace, bufnr)
-  bufnr = get_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
   if not diagnostic_attached_buffers[bufnr] then
     api.nvim_buf_attach(bufnr, false, {
       on_lines = function(_, _, _, _, _, last)
@@ -814,7 +807,7 @@ local function get_diagnostics(bufnr, opts, clamp)
       end
     end
   elseif namespace == nil then
-    bufnr = get_bufnr(bufnr)
+    bufnr = vim._resolve_bufnr(bufnr)
     for iter_namespace in pairs(diagnostic_cache[bufnr]) do
       add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace])
     end
@@ -825,7 +818,7 @@ local function get_diagnostics(bufnr, opts, clamp)
       end
     end
   else
-    bufnr = get_bufnr(bufnr)
+    bufnr = vim._resolve_bufnr(bufnr)
     for _, iter_namespace in ipairs(namespace) do
       add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace] or {})
     end
@@ -1100,7 +1093,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
   vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
   vim.validate('opts', opts, 'table', true)
 
-  bufnr = get_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
 
   if vim.tbl_isempty(diagnostics) then
     diagnostic_cache[bufnr][namespace] = nil
@@ -1380,7 +1373,7 @@ M.handlers.signs = {
     vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
     vim.validate('opts', opts, 'table', true)
 
-    bufnr = get_bufnr(bufnr)
+    bufnr = vim._resolve_bufnr(bufnr)
     opts = opts or {}
 
     if not api.nvim_buf_is_loaded(bufnr) then
@@ -1486,7 +1479,7 @@ M.handlers.underline = {
     vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
     vim.validate('opts', opts, 'table', true)
 
-    bufnr = get_bufnr(bufnr)
+    bufnr = vim._resolve_bufnr(bufnr)
     opts = opts or {}
 
     if not vim.api.nvim_buf_is_loaded(bufnr) then
@@ -1550,7 +1543,7 @@ M.handlers.virtual_text = {
     vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
     vim.validate('opts', opts, 'table', true)
 
-    bufnr = get_bufnr(bufnr)
+    bufnr = vim._resolve_bufnr(bufnr)
     opts = opts or {}
 
     if not vim.api.nvim_buf_is_loaded(bufnr) then
@@ -1675,7 +1668,7 @@ function M.hide(namespace, bufnr)
   vim.validate('namespace', namespace, 'number', true)
   vim.validate('bufnr', bufnr, 'number', true)
 
-  local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
+  local buffers = bufnr and { vim._resolve_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
   for _, iter_bufnr in ipairs(buffers) do
     local namespaces = namespace and { namespace } or vim.tbl_keys(diagnostic_cache[iter_bufnr])
     for _, iter_namespace in ipairs(namespaces) do
@@ -1702,7 +1695,7 @@ function M.is_enabled(filter)
     return vim.tbl_isempty(diagnostic_disabled) and not diagnostic_disabled[1]
   end
 
-  local bufnr = get_bufnr(filter.bufnr)
+  local bufnr = vim._resolve_bufnr(filter.bufnr)
   if type(diagnostic_disabled[bufnr]) == 'table' then
     return not diagnostic_disabled[bufnr][filter.ns_id]
   end
@@ -1743,7 +1736,7 @@ function M.show(namespace, bufnr, diagnostics, opts)
       end
     else
       -- namespace is nil
-      bufnr = get_bufnr(bufnr)
+      bufnr = vim._resolve_bufnr(bufnr)
       for iter_namespace in pairs(diagnostic_cache[bufnr]) do
         M.show(iter_namespace, bufnr, nil, opts)
       end
@@ -1810,7 +1803,7 @@ function M.open_float(opts, ...)
   end
 
   opts = opts or {}
-  bufnr = get_bufnr(bufnr or opts.bufnr)
+  bufnr = vim._resolve_bufnr(bufnr or opts.bufnr)
 
   do
     -- Resolve options with user settings from vim.diagnostic.config
@@ -2013,7 +2006,7 @@ function M.reset(namespace, bufnr)
   vim.validate('namespace', namespace, 'number', true)
   vim.validate('bufnr', bufnr, 'number', true)
 
-  local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
+  local buffers = bufnr and { vim._resolve_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
   for _, iter_bufnr in ipairs(buffers) do
     local namespaces = namespace and { namespace } or vim.tbl_keys(diagnostic_cache[iter_bufnr])
     for _, iter_namespace in ipairs(namespaces) do
@@ -2151,7 +2144,7 @@ function M.enable(enable, filter)
       ns.disabled = not enable
     end
   else
-    bufnr = get_bufnr(bufnr)
+    bufnr = vim._resolve_bufnr(bufnr)
     if not ns_id then
       diagnostic_disabled[bufnr] = (not enable) and true or nil
     else
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index e1946816da..ebdc050405 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -89,18 +89,6 @@ lsp._request_name_to_capability = {
 
 -- TODO improve handling of scratch buffers with LSP attached.
 
---- Returns the buffer number for the given {bufnr}.
----
----@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
----@return integer bufnr
-local function resolve_bufnr(bufnr)
-  validate('bufnr', bufnr, 'number', true)
-  if bufnr == nil or bufnr == 0 then
-    return api.nvim_get_current_buf()
-  end
-  return bufnr
-end
-
 ---@private
 --- Called by the client when trying to call a method that's not
 --- supported in any of the servers registered for the current buffer.
@@ -389,7 +377,7 @@ end
 function lsp.start(config, opts)
   opts = opts or {}
   local reuse_client = opts.reuse_client or reuse_client_default
-  local bufnr = resolve_bufnr(opts.bufnr)
+  local bufnr = vim._resolve_bufnr(opts.bufnr)
 
   for _, client in pairs(all_clients) do
     if reuse_client(client, config) then
@@ -528,7 +516,7 @@ end
 ---Buffer lifecycle handler for textDocument/didSave
 --- @param bufnr integer
 local function text_document_did_save_handler(bufnr)
-  bufnr = resolve_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
   local uri = vim.uri_from_bufnr(bufnr)
   local text = once(lsp._buf_get_full_text)
   for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
@@ -689,7 +677,7 @@ end
 function lsp.buf_attach_client(bufnr, client_id)
   validate('bufnr', bufnr, 'number', true)
   validate('client_id', client_id, 'number')
-  bufnr = resolve_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
   if not api.nvim_buf_is_loaded(bufnr) then
     log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
     return false
@@ -726,7 +714,7 @@ end
 function lsp.buf_detach_client(bufnr, client_id)
   validate('bufnr', bufnr, 'number', true)
   validate('client_id', client_id, 'number')
-  bufnr = resolve_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
 
   local client = all_clients[client_id]
   if not client or not client.attached_buffers[bufnr] then
@@ -832,7 +820,7 @@ function lsp.get_clients(filter)
 
   local clients = {} --- @type vim.lsp.Client[]
 
-  local bufnr = filter.bufnr and resolve_bufnr(filter.bufnr)
+  local bufnr = filter.bufnr and vim._resolve_bufnr(filter.bufnr)
 
   for _, client in pairs(all_clients) do
     if
@@ -928,7 +916,7 @@ function lsp.buf_request(bufnr, method, params, handler, on_unsupported)
   validate('handler', handler, 'function', true)
   validate('on_unsupported', on_unsupported, 'function', true)
 
-  bufnr = resolve_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
   local method_supported = false
   local clients = lsp.get_clients({ bufnr = bufnr })
   local client_request_ids = {} --- @type table
@@ -1208,7 +1196,7 @@ end
 function lsp.buf_get_clients(bufnr)
   vim.deprecate('vim.lsp.buf_get_clients()', 'vim.lsp.get_clients()', '0.12')
   local result = {} --- @type table
-  for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do
+  for _, client in ipairs(lsp.get_clients({ bufnr = vim._resolve_bufnr(bufnr) })) do
     result[client.id] = client
   end
   return result
@@ -1262,7 +1250,7 @@ function lsp.for_each_buffer_client(bufnr, fn)
     'lsp.get_clients({ bufnr = bufnr }) with regular loop',
     '0.12'
   )
-  bufnr = resolve_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
 
   for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
     fn(client, client.id, bufnr)
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 10479807a2..1926a0228d 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -519,7 +519,7 @@ end
 --- @param opts? vim.lsp.buf.format.Opts
 function M.format(opts)
   opts = opts or {}
-  local bufnr = opts.bufnr or api.nvim_get_current_buf()
+  local bufnr = vim._resolve_bufnr(opts.bufnr)
   local mode = api.nvim_get_mode().mode
   local range = opts.range
   -- Try to use visual selection if no range is given
@@ -617,7 +617,7 @@ end
 ---@param opts? vim.lsp.buf.rename.Opts Additional options:
 function M.rename(new_name, opts)
   opts = opts or {}
-  local bufnr = opts.bufnr or api.nvim_get_current_buf()
+  local bufnr = vim._resolve_bufnr(opts.bufnr)
   local clients = lsp.get_clients({
     bufnr = bufnr,
     name = opts.name,
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index 7eb023da39..e03fb2415d 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -604,18 +604,6 @@ function Client:_resolve_handler(method)
   return self.handlers[method] or lsp.handlers[method]
 end
 
---- Returns the buffer number for the given {bufnr}.
----
---- @param bufnr integer? Buffer number to resolve. Defaults to current buffer
---- @return integer bufnr
-local function resolve_bufnr(bufnr)
-  validate('bufnr', bufnr, 'number', true)
-  if bufnr == nil or bufnr == 0 then
-    return api.nvim_get_current_buf()
-  end
-  return bufnr
-end
-
 --- Sends a request to the server.
 ---
 --- This is a thin wrapper around {client.rpc.request} with some additional
@@ -640,7 +628,7 @@ function Client:request(method, params, handler, bufnr)
   end
   -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
   changetracking.flush(self, bufnr)
-  bufnr = resolve_bufnr(bufnr)
+  bufnr = vim._resolve_bufnr(bufnr)
   local version = lsp.util.buf_versions[bufnr]
   log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr)
   local success, request_id = self.rpc.request(method, params, function(err, result)
@@ -891,7 +879,7 @@ end
 --- @param bufnr? integer
 --- @return lsp.Registration?
 function Client:_get_registration(method, bufnr)
-  bufnr = bufnr or vim.api.nvim_get_current_buf()
+  bufnr = vim._resolve_bufnr(bufnr)
   for _, reg in ipairs(self.registrations[method] or {}) do
     if not reg.registerOptions or not reg.registerOptions.documentSelector then
       return reg
@@ -928,7 +916,7 @@ end
 --- @param handler? lsp.Handler only called if a server command
 function Client:exec_cmd(command, context, handler)
   context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]]
-  context.bufnr = context.bufnr or api.nvim_get_current_buf()
+  context.bufnr = vim._resolve_bufnr(context.bufnr)
   context.client_id = self.id
   local cmdname = command.command
   local fn = self.commands[cmdname] or lsp.commands[cmdname]
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index a11f84d6c6..3ccd165d0b 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -104,16 +104,12 @@ function M.run()
   end
 end
 
-local function resolve_bufnr(bufnr)
-  return bufnr == 0 and api.nvim_get_current_buf() or bufnr
-end
-
 --- Clear the lenses
 ---
 ---@param client_id integer|nil filter by client_id. All clients if nil
 ---@param bufnr integer|nil filter by buffer. All buffers if nil, 0 for current buffer
 function M.clear(client_id, bufnr)
-  bufnr = bufnr and resolve_bufnr(bufnr)
+  bufnr = bufnr and vim._resolve_bufnr(bufnr)
   local buffers = bufnr and { bufnr }
     or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
   for _, iter_bufnr in pairs(buffers) do
@@ -296,7 +292,7 @@ end
 --- @param opts? vim.lsp.codelens.refresh.Opts Optional fields
 function M.refresh(opts)
   opts = opts or {}
-  local bufnr = opts.bufnr and resolve_bufnr(opts.bufnr)
+  local bufnr = opts.bufnr and vim._resolve_bufnr(opts.bufnr)
   local buffers = bufnr and { bufnr }
     or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
 
diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua
index 1e950f625e..dbf0a62eeb 100644
--- a/runtime/lua/vim/lsp/completion.lua
+++ b/runtime/lua/vim/lsp/completion.lua
@@ -716,7 +716,7 @@ end
 --- @param bufnr integer Buffer handle, or 0 for the current buffer
 --- @param opts? vim.lsp.completion.BufferOpts
 function M.enable(enable, client_id, bufnr, opts)
-  bufnr = (bufnr == 0 and api.nvim_get_current_buf()) or bufnr
+  bufnr = vim._resolve_bufnr(bufnr)
 
   if enable then
     enable_completions(client_id, bufnr, opts or {})
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 3f135d84f3..9a879d9f38 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -356,9 +356,7 @@ end
 ---@param bufnr (integer) Buffer handle, or 0 for current
 ---@private
 function M._enable(bufnr)
-  if bufnr == nil or bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   if not bufstates[bufnr] then
     bufstates[bufnr] = { enabled = true }
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
index f1a58de621..50cf9f5f29 100644
--- a/runtime/lua/vim/lsp/inlay_hint.lua
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -149,8 +149,8 @@ function M.get(filter)
       vim.list_extend(hints, M.get(vim.tbl_extend('keep', { bufnr = buf }, filter)))
     end, vim.api.nvim_list_bufs())
     return hints
-  elseif bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
+  else
+    bufnr = vim._resolve_bufnr(bufnr)
   end
 
   local bufstate = bufstates[bufnr]
@@ -203,9 +203,7 @@ end
 --- Clear inlay hints
 ---@param bufnr (integer) Buffer handle, or 0 for current
 local function clear(bufnr)
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
   local bufstate = bufstates[bufnr]
   local client_lens = (bufstate or {}).client_hints or {}
   local client_ids = vim.tbl_keys(client_lens) --- @type integer[]
@@ -221,9 +219,7 @@ end
 --- Disable inlay hints for a buffer
 ---@param bufnr (integer) Buffer handle, or 0 for current
 local function _disable(bufnr)
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
   clear(bufnr)
   bufstates[bufnr] = nil
   bufstates[bufnr].enabled = false
@@ -242,9 +238,7 @@ end
 --- Enable inlay hints for a buffer
 ---@param bufnr (integer) Buffer handle, or 0 for current
 local function _enable(bufnr)
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
   bufstates[bufnr] = nil
   bufstates[bufnr].enabled = true
   _refresh(bufnr)
@@ -371,13 +365,10 @@ function M.is_enabled(filter)
   filter = filter or {}
   local bufnr = filter.bufnr
 
-  vim.validate('bufnr', bufnr, 'number', true)
   if bufnr == nil then
     return globalstate.enabled
-  elseif bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
   end
-  return bufstates[bufnr].enabled
+  return bufstates[vim._resolve_bufnr(bufnr)].enabled
 end
 
 --- Optional filters |kwargs|, or `nil` for all.
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 01421fea29..7cc3f95aed 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -600,9 +600,7 @@ function M.start(bufnr, client_id, opts)
   vim.validate('bufnr', bufnr, 'number')
   vim.validate('client_id', client_id, 'number')
 
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   opts = opts or {}
   assert(
@@ -655,9 +653,7 @@ function M.stop(bufnr, client_id)
   vim.validate('bufnr', bufnr, 'number')
   vim.validate('client_id', client_id, 'number')
 
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   local highlighter = STHighlighter.active[bufnr]
   if not highlighter then
@@ -691,9 +687,7 @@ end
 ---        - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true }
 ---        - client_id (integer)
 function M.get_at_pos(bufnr, row, col)
-  if bufnr == nil or bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   local highlighter = STHighlighter.active[bufnr]
   if not highlighter then
@@ -739,8 +733,7 @@ function M.force_refresh(bufnr)
   vim.validate('bufnr', bufnr, 'number', true)
 
   local buffers = bufnr == nil and vim.tbl_keys(STHighlighter.active)
-    or bufnr == 0 and { api.nvim_get_current_buf() }
-    or { bufnr }
+    or { vim._resolve_bufnr(bufnr) }
 
   for _, buffer in ipairs(buffers) do
     local highlighter = STHighlighter.active[buffer]
@@ -770,9 +763,7 @@ end
 ---@param hl_group (string) Highlight group name
 ---@param opts? vim.lsp.semantic_tokens.highlight_token.Opts  Optional parameters:
 function M.highlight_token(token, bufnr, client_id, hl_group, opts)
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
   local highlighter = STHighlighter.active[bufnr]
   if not highlighter then
     return
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 5bf436f2ca..6bee5bc31f 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -192,9 +192,7 @@ local function get_lines(bufnr, rows)
   rows = type(rows) == 'table' and rows or { rows }
 
   -- This is needed for bufload and bufloaded
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   local function buf_lines()
     local lines = {} --- @type table
@@ -1976,7 +1974,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding)
   validate('start_pos', start_pos, 'table', true)
   validate('end_pos', end_pos, 'table', true)
   validate('position_encoding', position_encoding, 'string', true)
-  bufnr = bufnr or api.nvim_get_current_buf()
+  bufnr = vim._resolve_bufnr(bufnr)
   if position_encoding == nil then
     vim.notify_once(
       'position_encoding param is required in vim.lsp.util.make_given_range_params. Defaulting to position encoding of the first client.',
@@ -2143,10 +2141,7 @@ end
 ---@param opts? vim.lsp.util._refresh.Opts Options table
 function M._refresh(method, opts)
   opts = opts or {}
-  local bufnr = opts.bufnr
-  if bufnr == nil or bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  local bufnr = vim._resolve_bufnr(opts.bufnr)
 
   local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method, id = opts.client_id })
 
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 2e8edea22a..9314d34314 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -1399,4 +1399,14 @@ function vim._with(context, f)
   return vim._with_c(context, callback)
 end
 
+--- @param bufnr? integer
+--- @return integer
+function vim._resolve_bufnr(bufnr)
+  if bufnr == nil or bufnr == 0 then
+    return vim.api.nvim_get_current_buf()
+  end
+  vim.validate('bufnr', bufnr, 'number')
+  return bufnr
+end
+
 return vim
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index dca89f413c..5f4e796413 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -32,9 +32,7 @@ M.minimum_language_version = vim._ts_get_minimum_language_version()
 ---
 ---@return vim.treesitter.LanguageTree object to use for parsing
 function M._create_parser(bufnr, lang, opts)
-  if bufnr == 0 then
-    bufnr = vim.api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   vim.fn.bufload(bufnr)
 
@@ -90,9 +88,7 @@ function M.get_parser(bufnr, lang, opts)
   opts = opts or {}
   local should_error = opts.error == nil or opts.error
 
-  if bufnr == nil or bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
 
   if not valid_lang(lang) then
     lang = M.language.get_lang(vim.bo[bufnr].filetype)
@@ -258,9 +254,7 @@ end
 ---
 ---@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata}[]
 function M.get_captures_at_pos(bufnr, row, col)
-  if bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  bufnr = vim._resolve_bufnr(bufnr)
   local buf_highlighter = M.highlighter.active[bufnr]
 
   if not buf_highlighter then
@@ -361,11 +355,7 @@ end
 function M.get_node(opts)
   opts = opts or {}
 
-  local bufnr = opts.bufnr
-
-  if not bufnr or bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
+  local bufnr = vim._resolve_bufnr(opts.bufnr)
 
   local row, col --- @type integer, integer
   if opts.pos then
@@ -417,7 +407,7 @@ end
 ---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer)
 ---@param lang (string|nil) Language of the parser (default: from buffer filetype)
 function M.start(bufnr, lang)
-  bufnr = bufnr or api.nvim_get_current_buf()
+  bufnr = vim._resolve_bufnr(bufnr)
   local parser = assert(M.get_parser(bufnr, lang, { error = false }))
   M.highlighter.new(parser)
 end
@@ -426,7 +416,7 @@ end
 ---
 ---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer)
 function M.stop(bufnr)
-  bufnr = (bufnr and bufnr ~= 0) and bufnr or api.nvim_get_current_buf()
+  bufnr = vim._resolve_bufnr(bufnr)
 
   if M.highlighter.active[bufnr] then
     M.highlighter.active[bufnr]:destroy()
-- 
cgit 


From f3fa6507f2473d66a4c6172c82dec43bf55f8df6 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 8 Dec 2024 07:00:17 +0800
Subject: vim-patch:9.1.0910: 'messagesopt' does not check max wait time

Problem:  'messagesopt' does not check max wait time
          (after v9.1.0908)
Solution: Check for max wait value
          (Shougo Matsushita)

closes: vim/vim#16183

https://github.com/vim/vim/commit/d9e9f89e0ffd6e7ce5e2a7f8f1ace5471e37c210

Co-authored-by: Shougo Matsushita 
---
 runtime/doc/options.txt           | 1 +
 runtime/lua/vim/_meta/options.lua | 1 +
 src/nvim/message.c                | 5 +++++
 src/nvim/options.lua              | 1 +
 test/old/testdir/gen_opt_test.vim | 3 ++-
 5 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 0db7b0f03c..7899f40e24 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4059,6 +4059,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 			milliseconds so the user has a chance to read the
 			message, use 0 to disable sleep (but then the user may
 			miss an important message).
+			The maximum value is 10000.
 
 	history:{n}	Determines how many entries are remembered in the
 			|:messages| history.  The maximum value is 10000.
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 59d270e640..e38fa4a89f 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -4095,6 +4095,7 @@ vim.go.mis = vim.go.menuitems
 --- 		milliseconds so the user has a chance to read the
 --- 		message, use 0 to disable sleep (but then the user may
 --- 		miss an important message).
+--- 		The maximum value is 10000.
 ---
 --- history:{n}	Determines how many entries are remembered in the
 --- 		`:messages` history.  The maximum value is 10000.
diff --git a/src/nvim/message.c b/src/nvim/message.c
index a32a06edca..f6424c4644 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1103,6 +1103,11 @@ int messagesopt_changed(void)
     return FAIL;
   }
 
+  // "wait" must be <= 10000
+  if (messages_wait_new > 10000) {
+    return FAIL;
+  }
+
   msg_flags = messages_flags_new;
   msg_wait = messages_wait_new;
 
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index a5d5a45b59..e08f3d1410 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -5467,6 +5467,7 @@ return {
         		milliseconds so the user has a chance to read the
         		message, use 0 to disable sleep (but then the user may
         		miss an important message).
+        		The maximum value is 10000.
 
         history:{n}	Determines how many entries are remembered in the
         		|:messages| history.  The maximum value is 10000.
diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim
index 74aea93ee9..22ffde5269 100644
--- a/test/old/testdir/gen_opt_test.vim
+++ b/test/old/testdir/gen_opt_test.vim
@@ -267,7 +267,8 @@ let test_values = {
       \		'history:100,wait:100', 'history:0,wait:0',
       \		'hit-enter,history:1,wait:1'],
       \		['xxx', 'history:500', 'hit-enter,history:-1',
-      \		'hit-enter,history:10001', 'hit-enter']],
+      \		'hit-enter,history:10001', 'history:0,wait:10001',
+      \		'hit-enter']],
       \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']],
       \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'],
       \		['xxx', 'n,v,i']],
-- 
cgit 


From 96329910b011414758380e3c27e32c0ae7f43e1e Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 8 Dec 2024 07:01:59 +0800
Subject: vim-patch:9.1.0911: Variable name for 'messagesopt' doesn't match
 short name

Problem:  Variable name for 'messagesopt' doesn't match short name
          (after v9.1.0908)
Solution: Change p_meo to p_mopt.  Add more details to docs.

closes: vim/vim#16182

https://github.com/vim/vim/commit/8cc43daee1f485c9abf1de3c638cce7835b9f861
---
 runtime/doc/options.txt            | 18 ++++++++++--------
 runtime/lua/vim/_meta/options.lua  | 18 ++++++++++--------
 runtime/optwin.vim                 |  4 ++--
 src/nvim/message.c                 |  2 +-
 src/nvim/option_vars.h             |  2 +-
 src/nvim/options.lua               | 20 +++++++++++---------
 test/old/testdir/test_messages.vim |  4 ++--
 7 files changed, 37 insertions(+), 31 deletions(-)

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 7899f40e24..5763d16cad 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4048,22 +4048,24 @@ A jump table for the options with a short description can be found at |Q_op|.
 						*'messagesopt'* *'mopt'*
 'messagesopt' 'mopt'	string	(default "hit-enter,history:500")
 			global
-	Option settings when outputting messages.  It can consist of the
+	Option settings for outputting messages.  It can consist of the
 	following items.  Items must be separated by a comma.
 
-	hit-enter	Use |hit-enter| prompt when the message is longer than
+	hit-enter	Use a |hit-enter| prompt when the message is longer than
 			'cmdheight' size.
 
-	wait:{n}	Ignored when "hit-enter" is present.  Instead of using
-			|hit-enter| prompt, will simply wait for {n}
-			milliseconds so the user has a chance to read the
-			message, use 0 to disable sleep (but then the user may
-			miss an important message).
-			The maximum value is 10000.
+	wait:{n}	Instead of using a |hit-enter| prompt, simply wait for
+			{n} milliseconds so that the user has a chance to read
+			the message.  The maximum value of {n} is 10000.  Use
+			0 to disable the wait (but then the user may miss an
+			important message).
+			This item is ignored when "hit-enter" is present, but
+			required when "hit-enter" is not present.
 
 	history:{n}	Determines how many entries are remembered in the
 			|:messages| history.  The maximum value is 10000.
 			Setting it to zero clears the message history.
+			This item must always be present.
 
 						*'mkspellmem'* *'msm'*
 'mkspellmem' 'msm'	string	(default "460000,2000,500")
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index e38fa4a89f..7a8c3a6c29 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -4084,22 +4084,24 @@ vim.o.mis = vim.o.menuitems
 vim.go.menuitems = vim.o.menuitems
 vim.go.mis = vim.go.menuitems
 
---- Option settings when outputting messages.  It can consist of the
+--- Option settings for outputting messages.  It can consist of the
 --- following items.  Items must be separated by a comma.
 ---
---- hit-enter	Use `hit-enter` prompt when the message is longer than
+--- hit-enter	Use a `hit-enter` prompt when the message is longer than
 --- 		'cmdheight' size.
 ---
---- wait:{n}	Ignored when "hit-enter" is present.  Instead of using
---- 		`hit-enter` prompt, will simply wait for {n}
---- 		milliseconds so the user has a chance to read the
---- 		message, use 0 to disable sleep (but then the user may
---- 		miss an important message).
---- 		The maximum value is 10000.
+--- wait:{n}	Instead of using a `hit-enter` prompt, simply wait for
+--- 		{n} milliseconds so that the user has a chance to read
+--- 		the message.  The maximum value of {n} is 10000.  Use
+--- 		0 to disable the wait (but then the user may miss an
+--- 		important message).
+--- 		This item is ignored when "hit-enter" is present, but
+--- 		required when "hit-enter" is not present.
 ---
 --- history:{n}	Determines how many entries are remembered in the
 --- 		`:messages` history.  The maximum value is 10000.
 --- 		Setting it to zero clears the message history.
+--- 		This item must always be present.
 ---
 --- @type string
 vim.o.messagesopt = "hit-enter,history:500"
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 923f55f8fa..6866d46d51 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -1,7 +1,7 @@
 " These commands create the option window.
 "
 " Maintainer:	The Vim Project 
-" Last Change:	2024 Jul 12
+" Last Change:	2024 Dec 07
 " Former Maintainer:	Bram Moolenaar 
 
 " If there already is an option window, jump to that one.
@@ -626,7 +626,7 @@ call AddOption("terse", gettext("add 's' flag in 'shortmess' (don't show se
 call BinOptionG("terse", &terse)
 call AddOption("shortmess", gettext("list of flags to make messages shorter"))
 call OptionG("shm", &shm)
-call AddOption("messagesopt", gettext("Option settings when outputting messages"))
+call AddOption("messagesopt", gettext("options for outputting messages"))
 call OptionG("mopt", &mopt)
 call AddOption("showcmd", gettext("show (partial) command keys in location given by 'showcmdloc'"))
 let &sc = s:old_sc
diff --git a/src/nvim/message.c b/src/nvim/message.c
index f6424c4644..d8e6fd3001 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1063,7 +1063,7 @@ int messagesopt_changed(void)
   int messages_wait_new = 0;
   int messages_history_new = 0;
 
-  char *p = p_meo;
+  char *p = p_mopt;
   while (*p != NUL) {
     if (strnequal(p, S_LEN(MESSAGES_OPT_HIT_ENTER))) {
       p += STRLEN_LITERAL(MESSAGES_OPT_HIT_ENTER);
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index aca876bddb..97455380cc 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -448,7 +448,7 @@ EXTERN OptInt p_mfd;            ///< 'maxfuncdepth'
 EXTERN OptInt p_mmd;            ///< 'maxmapdepth'
 EXTERN OptInt p_mmp;            ///< 'maxmempattern'
 EXTERN OptInt p_mis;            ///< 'menuitems'
-EXTERN char *p_meo;             ///< 'messagesopt'
+EXTERN char *p_mopt;            ///< 'messagesopt'
 EXTERN char *p_msm;             ///< 'mkspellmem'
 EXTERN int p_ml;                ///< 'modeline'
 EXTERN int p_mle;               ///< 'modelineexpr'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index e08f3d1410..afce4a918b 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -5456,22 +5456,24 @@ return {
       flags = true,
       deny_duplicates = true,
       desc = [=[
-        Option settings when outputting messages.  It can consist of the
+        Option settings for outputting messages.  It can consist of the
         following items.  Items must be separated by a comma.
 
-        hit-enter	Use |hit-enter| prompt when the message is longer than
+        hit-enter	Use a |hit-enter| prompt when the message is longer than
         		'cmdheight' size.
 
-        wait:{n}	Ignored when "hit-enter" is present.  Instead of using
-        		|hit-enter| prompt, will simply wait for {n}
-        		milliseconds so the user has a chance to read the
-        		message, use 0 to disable sleep (but then the user may
-        		miss an important message).
-        		The maximum value is 10000.
+        wait:{n}	Instead of using a |hit-enter| prompt, simply wait for
+        		{n} milliseconds so that the user has a chance to read
+        		the message.  The maximum value of {n} is 10000.  Use
+        		0 to disable the wait (but then the user may miss an
+        		important message).
+        		This item is ignored when "hit-enter" is present, but
+        		required when "hit-enter" is not present.
 
         history:{n}	Determines how many entries are remembered in the
         		|:messages| history.  The maximum value is 10000.
         		Setting it to zero clears the message history.
+        		This item must always be present.
       ]=],
       expand_cb = 'expand_set_messagesopt',
       full_name = 'messagesopt',
@@ -5479,7 +5481,7 @@ return {
       scope = { 'global' },
       short_desc = N_('options for outputting messages'),
       type = 'string',
-      varname = 'p_meo',
+      varname = 'p_mopt',
     },
     {
       abbreviation = 'msm',
diff --git a/test/old/testdir/test_messages.vim b/test/old/testdir/test_messages.vim
index d79bc0fae7..bfead20142 100644
--- a/test/old/testdir/test_messages.vim
+++ b/test/old/testdir/test_messages.vim
@@ -646,12 +646,12 @@ func Test_messagesopt_wait()
 
   " Check hit-enter prompt
   call term_sendkeys(buf, ":set messagesopt=hit-enter,history:500\n")
-  call term_sendkeys(buf, ":echo 'foo' | echo 'bar' echo 'baz'\n")
+  call term_sendkeys(buf, ":echo 'foo' | echo 'bar' | echo 'baz'\n")
   call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
 
   " Check no hit-enter prompt when "wait:" is set
   call term_sendkeys(buf, ":set messagesopt=wait:100,history:500\n")
-  call term_sendkeys(buf, ":echo 'foo' | echo 'bar' echo 'baz'\n")
+  call term_sendkeys(buf, ":echo 'foo' | echo 'bar' | echo 'baz'\n")
   call WaitForAssert({-> assert_equal('                           0,0-1         All', term_getline(buf, 6))})
 
   " clean up
-- 
cgit 


From 5549115beeaec67b58ea67e8dca316e0a8782d4c Mon Sep 17 00:00:00 2001
From: brianhuster 
Date: Thu, 5 Dec 2024 22:49:07 +0700
Subject: vim-patch:189e24b: runtime(doc): include vietnamese.txt

Since Vietnamese keymaps in Vim is quite differences from the
corresponding input methods, let's document the Vietnamese specifics in
vietnames.txt

related: vim/vim#16144

https://github.com/vim/vim/commit/189e24bb1441abe87387a4e21c4387bbf2eac718
---
 runtime/doc/vietnamese.txt | 77 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 runtime/doc/vietnamese.txt

diff --git a/runtime/doc/vietnamese.txt b/runtime/doc/vietnamese.txt
new file mode 100644
index 0000000000..4d46d1fa60
--- /dev/null
+++ b/runtime/doc/vietnamese.txt
@@ -0,0 +1,77 @@
+*vietnamese.txt*   Nvim
+
+
+		  VIM REFERENCE MANUAL    by Phạm Bình An
+
+                                      Type |gO| to see the table of contents.
+
+===============================================================================
+1. Introduction
+							*vietnamese-intro*
+
+Vim supports Vietnamese language in the following ways:
+
+- Built-in |vietnamese-keymap|, which allows you to type Vietnamese characters
+  in |Insert-mode| and |search-commands| using US keyboard layout.
+- Localization in Vietnamese. See |vietnamese-l18n|
+
+===============================================================================
+2. Vietnamese keymaps
+							*vietnamese-keymap*
+
+To switch between languages you can use your system native keyboard switcher,
+or use one of the Vietnamese keymaps included in the Vim distribution, like
+below >
+    :set keymap=vietnamese-telex_utf-8
+<
+See |'keymap'| for more information.
+
+In the latter case, you can type Vietnamese even if you do not have a
+Vietnamese input method engine (IME) or you want Vim to be independent from a
+system-wide keyboard settings (when |'imdisable'| is set). You can also |:map|
+a key to switch between keyboards.
+
+Vim comes with the following Vietnamese keymaps:
+- *vietnamese-telex_utf-8*	Telex input method, |UTF-8| encoding.
+- *vietnamese-viqr_utf-8*	VIQR input method, |UTF-8| encoding.
+- *vietnamese-vni_utf-8*	VNI input method, |UTF-8| encoding.
+
+                                                   *vietnamese-ime_diff*
+
+Since these keymaps were designed to be minimalistic, they do not support all
+features of the corresponding input methods. The differences are described
+below:
+
+- You can only type each character individually, entering the base letter first
+  and then the diacritics later.  For example, to type the word `nến` using
+  |vietnamese-vni_utf-8|, you must type `ne61n`, not `nen61` or `ne6n1`
+- For characters with more than 1 diacritic, you need to type vowel mark before
+  tone mark. For example, to type `ồ` using |vietnamese-telex_utf-8|, you need
+  to type `oof`, not `ofo`.
+- With |vietnamese-telex_utf-8|, you need to type all uppercase letters to
+  produce uppercase characters with diacritics. For example, `Ừ` must be typed
+  as `UWF`.
+- With |vietnamese-telex_utf-8|, the escape character `\` from VNI is added,
+  hence the confusing `ooo` input to type `oo` is removed, which could lead to
+  ambiguities.  For example, to type the word `Đoòng`, you would type
+  `DDo\ofng`.
+- Simple Telex (both v1 and v2), including the `w[]{}` style, is not
+  supported.
+- Removing diacritics using `z` in Telex or `0` in VNI and VIQR is not supported.
+
+===============================================================================
+3. Localization
+							*vietnamese-l18n*
+
+Vim |messages| are also available in Vietnamese.  If you wish to see messages in
+Vietnamese, you can run the command |:language| with an argument being the name
+of the Vietnamese locale.  For example, >
+	:language vi_VN
+< or >
+	:language vi_VN.utf-8
+<
+Note that the name of the Vietnamese locale may vary depending on your system.
+See |mbyte-first| for details.
+
+===============================================================================
+vim:tw=78:ts=8:noet:ft=help:norl:
-- 
cgit 


From 99a24d511ff81138f25e6c57cc7fcc8e45d35bb8 Mon Sep 17 00:00:00 2001
From: brianhuster 
Date: Sat, 7 Dec 2024 22:28:18 +0700
Subject: vim-patch:8a52587: runtime(doc): fix wrong syntax and style of
 vietnamese.txt

https://github.com/vim/vim/commit/8a52587ee05a360cad4d42322200775fc74a4430

vim-patch:72212c9: runtime(doc): update wrong Vietnamese localization tag

https://github.com/vim/vim/commit/72212c9bea77f14f1e6be703de3c10d70eb2984c

Co-authored-by: Christian Brabandt 
---
 runtime/doc/vietnamese.txt | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/runtime/doc/vietnamese.txt b/runtime/doc/vietnamese.txt
index 4d46d1fa60..ed3fe9bc68 100644
--- a/runtime/doc/vietnamese.txt
+++ b/runtime/doc/vietnamese.txt
@@ -8,17 +8,15 @@
 ===============================================================================
 1. Introduction
 							*vietnamese-intro*
-
 Vim supports Vietnamese language in the following ways:
 
 - Built-in |vietnamese-keymap|, which allows you to type Vietnamese characters
   in |Insert-mode| and |search-commands| using US keyboard layout.
-- Localization in Vietnamese. See |vietnamese-l18n|
+- Localization in Vietnamese. See |vietnamese-l10n|
 
 ===============================================================================
 2. Vietnamese keymaps
 							*vietnamese-keymap*
-
 To switch between languages you can use your system native keyboard switcher,
 or use one of the Vietnamese keymaps included in the Vim distribution, like
 below >
@@ -37,7 +35,6 @@ Vim comes with the following Vietnamese keymaps:
 - *vietnamese-vni_utf-8*	VNI input method, |UTF-8| encoding.
 
                                                    *vietnamese-ime_diff*
-
 Since these keymaps were designed to be minimalistic, they do not support all
 features of the corresponding input methods. The differences are described
 below:
@@ -61,11 +58,10 @@ below:
 
 ===============================================================================
 3. Localization
-							*vietnamese-l18n*
-
-Vim |messages| are also available in Vietnamese.  If you wish to see messages in
-Vietnamese, you can run the command |:language| with an argument being the name
-of the Vietnamese locale.  For example, >
+							*vietnamese-l10n*
+Vim |messages| are also available in Vietnamese.  If you wish to see messages
+in Vietnamese, you can run the command |:language| with an argument being the
+name of the Vietnamese locale.  For example, >
 	:language vi_VN
 < or >
 	:language vi_VN.utf-8
-- 
cgit 


From bf7e534c8831334d091a536101cbdd6322991c59 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 8 Dec 2024 08:01:12 +0800
Subject: vim-patch:41afa30: runtime(doc): Add vietnamese.txt to helps main TOC

closes: vim/vim#16177

https://github.com/vim/vim/commit/41afa308d6f420504c47567b494e97a6721afe71

Co-authored-by: h-east 
---
 runtime/doc/help.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt
index fd8bfd644f..914dc64c0a 100644
--- a/runtime/doc/help.txt
+++ b/runtime/doc/help.txt
@@ -150,6 +150,7 @@ LANGUAGE SUPPORT
 |arabic.txt|		Arabic language support and editing
 |hebrew.txt|		Hebrew language support and editing
 |russian.txt|		Russian language support and editing
+|vietnamese.txt|	Vietnamese language support and editing
 
 ------------------------------------------------------------------------------
 INTEROP
-- 
cgit 


From 7008487b8351309a48d9f97cefd41ba15f9af6e7 Mon Sep 17 00:00:00 2001
From: brianhuster 
Date: Thu, 5 Dec 2024 22:49:07 +0700
Subject: vim-patch:336fb22: translation(vi): Update Vietnamese translation

closes: vim/vim#16144

https://github.com/vim/vim/commit/336fb22eaef7977741712d0c4735fc6d65428a4f
---
 src/nvim/po/vi.po | 7158 +++++++++++++++++------------------------------------
 1 file changed, 2276 insertions(+), 4882 deletions(-)

diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po
index 3e88ac446c..bcc1e0af8e 100644
--- a/src/nvim/po/vi.po
+++ b/src/nvim/po/vi.po
@@ -4,219 +4,147 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Vim 6.3 \n"
+"Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-05-26 14:21+0200\n"
-"PO-Revision-Date: 2005-02-30 21:37+0400\n"
-"Last-Translator: Phan Vinh Thinh \n"
+"POT-Creation-Date: 2024-12-03 22:06+0100\n"
+"PO-Revision-Date: 2024-12-03 22:07+0100\n"
+"Last-Translator: Phạm Bình An <111893501+brianhuster@users.noreply.github.com>\n"
 "Language-Team: Phan Vinh Thinh \n"
-"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: ../api/private/helpers.c:201
-#, fuzzy
-msgid "Unable to get option value"
-msgstr "E258: Không thể trả lời cho máy con"
-
-#: ../api/private/helpers.c:204
-msgid "internal error: unknown option type"
-msgstr ""
-
-#: ../buffer.c:92
-msgid "[Location List]"
-msgstr ""
-
-#: ../buffer.c:93
-msgid "[Quickfix List]"
-msgstr ""
-
-#: ../buffer.c:94
-msgid "E855: Autocommands caused command to abort"
-msgstr ""
-
-#: ../buffer.c:135
 msgid "E82: Cannot allocate any buffer, exiting..."
 msgstr "E82: Không thể phân chia bộ nhớ thậm chí cho một bộ đệm, thoát..."
 
-#: ../buffer.c:138
 msgid "E83: Cannot allocate buffer, using other one..."
 msgstr "E83: Không thể phân chia bộ nhớ cho bộ đệm, sử dụng bộ đệm khác..."
 
-#: ../buffer.c:763
 msgid "E515: No buffers were unloaded"
 msgstr "E515: Không có bộ đệm nào được bỏ nạp từ bộ nhớ"
 
-#: ../buffer.c:765
 msgid "E516: No buffers were deleted"
 msgstr "E516: Không có bộ đệm nào bị xóa"
 
-#: ../buffer.c:767
 msgid "E517: No buffers were wiped out"
 msgstr "E517: Không có bộ đệm nào được làm sạch"
 
-#: ../buffer.c:772
 msgid "1 buffer unloaded"
 msgstr "1 bộ đệm được bỏ nạp từ bộ nhớ"
 
-#: ../buffer.c:774
 #, c-format
 msgid "%d buffers unloaded"
 msgstr "%d bộ đệm được bỏ nạp từ bộ nhớ"
 
-#: ../buffer.c:777
 msgid "1 buffer deleted"
 msgstr "1 bộ đệm bị xóa"
 
-#: ../buffer.c:779
 #, c-format
 msgid "%d buffers deleted"
 msgstr "%d bộ đệm được bỏ nạp"
 
-#: ../buffer.c:782
 msgid "1 buffer wiped out"
 msgstr "1 bộ đệm được làm sạch"
 
-#: ../buffer.c:784
 #, c-format
 msgid "%d buffers wiped out"
 msgstr "%d bộ đệm được làm sạch"
 
-#: ../buffer.c:806
-msgid "E90: Cannot unload last buffer"
-msgstr "E90: Không thể bỏ nạp từ bộ nhớ bộ đệm cuối cùng"
-
-#: ../buffer.c:874
 msgid "E84: No modified buffer found"
 msgstr "E84: Không tìm thấy bộ đệm có thay đổi"
 
-#. back where we started, didn't find anything.
-#: ../buffer.c:903
 msgid "E85: There is no listed buffer"
 msgstr "E85: Không có bộ đệm được liệt kê"
 
-#: ../buffer.c:913
 #, c-format
-msgid "E86: Buffer % does not exist"
-msgstr "E86: Bộ đệm % không tồn tại"
+msgid "E86: Buffer %ld does not exist"
+msgstr "E86: Bộ đệm %ld không tồn tại"
 
-#: ../buffer.c:915
 msgid "E87: Cannot go beyond last buffer"
 msgstr "E87: Đây là bộ đệm cuối cùng"
 
-#: ../buffer.c:917
 msgid "E88: Cannot go before first buffer"
 msgstr "E88: Đây là bộ đệm đầu tiên"
 
-#: ../buffer.c:945
 #, c-format
-msgid ""
-"E89: No write since last change for buffer % (add ! to override)"
+msgid "E89: No write since last change for buffer %ld (add ! to override)"
 msgstr ""
-"E89: Thay đổi trong bộ đệm % chưa được ghi lại (thêm ! để thoát ra "
-"bằng mọi giá)"
+"E89: Thay đổi trong bộ đệm %ld chưa được ghi lại (thêm ! để thoát ra bằng "
+"mọi giá)"
+
+msgid "E90: Cannot unload last buffer"
+msgstr "E90: Không thể bỏ nạp từ bộ nhớ bộ đệm cuối cùng"
 
-#. wrap around (may cause duplicates)
-#: ../buffer.c:1423
 msgid "W14: Warning: List of file names overflow"
 msgstr "W14: Cảnh báo: Danh sách tên tập tin quá đầy"
 
-#: ../buffer.c:1555 ../quickfix.c:3361
 #, c-format
-msgid "E92: Buffer % not found"
-msgstr "E92: Bộ đệm % không được tìm thấy"
+msgid "E92: Buffer %ld not found"
+msgstr "E92: Bộ đệm %ld không được tìm thấy"
 
-#: ../buffer.c:1798
 #, c-format
 msgid "E93: More than one match for %s"
 msgstr "E93: Tìm thấy vài tương ứng với %s"
 
-#: ../buffer.c:1800
 #, c-format
 msgid "E94: No matching buffer for %s"
 msgstr "E94: Không có bộ đệm tương ứng với %s"
 
-#: ../buffer.c:2161
 #, c-format
-msgid "line %"
-msgstr "dòng %"
+msgid "line %ld"
+msgstr "dòng %ld"
 
-#: ../buffer.c:2233
 msgid "E95: Buffer with this name already exists"
 msgstr "E95: Đã có bộ đệm với tên như vậy"
 
-#: ../buffer.c:2498
 msgid " [Modified]"
 msgstr " [Đã thay đổi]"
 
-#: ../buffer.c:2501
 msgid "[Not edited]"
 msgstr "[Chưa soạn thảo]"
 
-#: ../buffer.c:2504
 msgid "[New file]"
 msgstr "[Tập tin mới]"
 
-#: ../buffer.c:2505
 msgid "[Read errors]"
 msgstr "[Lỗi đọc]"
 
-#: ../buffer.c:2506 ../buffer.c:3217 ../fileio.c:1807 ../screen.c:4895
-msgid "[RO]"
-msgstr "[Chỉ đọc]"
-
-#: ../buffer.c:2507 ../fileio.c:1807
 msgid "[readonly]"
 msgstr "[chỉ đọc]"
 
-#: ../buffer.c:2524
 #, c-format
 msgid "1 line --%d%%--"
 msgstr "1 dòng --%d%%--"
 
-#: ../buffer.c:2526
 #, c-format
-msgid "% lines --%d%%--"
-msgstr "% dòng --%d%%--"
+msgid "%ld lines --%d%%--"
+msgstr "%ld dòng --%d%%--"
 
-#: ../buffer.c:2530
 #, c-format
-msgid "line % of % --%d%%-- col "
-msgstr "dòng % của % --%d%%-- cột "
+msgid "line %ld of %ld --%d%%-- col "
+msgstr "dòng %ld của %ld --%d%%-- cột "
 
-#: ../buffer.c:2632 ../buffer.c:4292 ../memline.c:1554
-#, fuzzy
-msgid "[No Name]"
+msgid "[No file]"
 msgstr "[Không có tập tin]"
 
-#. must be a help buffer
-#: ../buffer.c:2667
 msgid "help"
 msgstr "trợ giúp"
 
-#: ../buffer.c:3225 ../screen.c:4883
-#, fuzzy
-msgid "[Help]"
+msgid "[help]"
 msgstr "[trợ giúp]"
 
-#: ../buffer.c:3254 ../screen.c:4887
 msgid "[Preview]"
 msgstr "[Xem trước]"
 
-#: ../buffer.c:3528
 msgid "All"
 msgstr "Tất cả"
 
-#: ../buffer.c:3528
 msgid "Bot"
 msgstr "Cuối"
 
-#: ../buffer.c:3531
 msgid "Top"
 msgstr "Đầu"
 
-#: ../buffer.c:4244
 msgid ""
 "\n"
 "# Buffer list:\n"
@@ -224,11 +152,12 @@ msgstr ""
 "\n"
 "# Danh sách bộ đệm:\n"
 
-#: ../buffer.c:4289
-msgid "[Scratch]"
-msgstr ""
+msgid "[Error List]"
+msgstr "[Danh sách lỗi]"
+
+msgid "[No File]"
+msgstr "[Không có tập tin]"
 
-#: ../buffer.c:4529
 msgid ""
 "\n"
 "--- Signs ---"
@@ -236,921 +165,561 @@ msgstr ""
 "\n"
 "--- Ký hiệu ---"
 
-#: ../buffer.c:4538
 #, c-format
 msgid "Signs for %s:"
 msgstr "Ký hiệu cho %s:"
 
-#: ../buffer.c:4543
 #, c-format
-msgid "    line=%  id=%d  name=%s"
-msgstr "    dòng=%  id=%d  tên=%s"
-
-#: ../cursor_shape.c:68
-msgid "E545: Missing colon"
-msgstr "E545: Thiếu dấu hai chấm"
-
-#: ../cursor_shape.c:70 ../cursor_shape.c:94
-msgid "E546: Illegal mode"
-msgstr "E546: Chế độ không cho phép"
-
-#: ../cursor_shape.c:134
-msgid "E548: digit expected"
-msgstr "E548: yêu cầu một số"
-
-#: ../cursor_shape.c:138
-msgid "E549: Illegal percentage"
-msgstr "E549: Tỷ lệ phần trăm không cho phép"
+msgid "    line=%ld  id=%d  name=%s"
+msgstr "    dòng=%ld  id=%d  tên=%s"
 
-#: ../diff.c:146
 #, c-format
-msgid "E96: Can not diff more than % buffers"
-msgstr ""
-"E96: Chỉ có thể theo dõi sự khác nhau trong nhiều nhất % bộ đệm"
-
-#: ../diff.c:753
-#, fuzzy
-msgid "E810: Cannot read or write temp files"
-msgstr "E557: Không thể mở tập tin termcap"
+msgid "E96: Can not diff more than %ld buffers"
+msgstr "E96: Chỉ có thể theo dõi sự khác nhau trong nhiều nhất %ld bộ đệm"
 
-#: ../diff.c:755
 msgid "E97: Cannot create diffs"
 msgstr "E97: Không thể tạo tập tin khác biệt (diff)"
 
-#: ../diff.c:966
-#, fuzzy
-msgid "E816: Cannot read patch output"
-msgstr "E98: Không thể đọc dữ liệu ra của lệnh diff"
+msgid "Patch file"
+msgstr "Tập tin vá lỗi (patch)"
 
-#: ../diff.c:1220
 msgid "E98: Cannot read diff output"
 msgstr "E98: Không thể đọc dữ liệu ra của lệnh diff"
 
-#: ../diff.c:2081
 msgid "E99: Current buffer is not in diff mode"
 msgstr "E99: Bộ đệm hiện thời không nằm trong chế độ khác biệt (diff)"
 
-#: ../diff.c:2100
-#, fuzzy
-msgid "E793: No other buffer in diff mode is modifiable"
-msgstr "E100: Không còn bộ đệm trong chế độ khác biệt (diff) nào nữa"
-
-#: ../diff.c:2102
 msgid "E100: No other buffer in diff mode"
 msgstr "E100: Không còn bộ đệm trong chế độ khác biệt (diff) nào nữa"
 
-#: ../diff.c:2112
 msgid "E101: More than two buffers in diff mode, don't know which one to use"
-msgstr ""
-"E101: Có nhiều hơn hai bộ đệm trong chế độ khác biệt (diff), không biết chọn"
+msgstr "E101: Có nhiều hơn hai bộ đệm trong chế độ khác biệt (diff), không biết nên chọn cái nào"
 
-#: ../diff.c:2141
 #, c-format
 msgid "E102: Can't find buffer \"%s\""
 msgstr "E102: Không tìm thấy bộ đệm \"%s\""
 
-#: ../diff.c:2152
 #, c-format
 msgid "E103: Buffer \"%s\" is not in diff mode"
 msgstr "E103: Bộ đệm \"%s\" không nằm trong chế độ khác biệt (diff)"
 
-#: ../diff.c:2193
-msgid "E787: Buffer changed unexpectedly"
-msgstr ""
-
-#: ../digraph.c:1598
 msgid "E104: Escape not allowed in digraph"
 msgstr "E104: Không cho phép dùng ký tự thoát Escape trong chữ ghép"
 
-#: ../digraph.c:1760
 msgid "E544: Keymap file not found"
 msgstr "E544: Không tìm thấy tập tin sơ đồ bàn phím"
 
-#: ../digraph.c:1785
 msgid "E105: Using :loadkeymap not in a sourced file"
 msgstr "E105: Câu lệnh :loadkeymap được sử dụng ngoài tập tin script"
 
-#: ../digraph.c:1821
-msgid "E791: Empty keymap entry"
-msgstr ""
-
-#: ../edit.c:82
 msgid " Keyword completion (^N^P)"
 msgstr " Tự động kết thúc cho từ khóa (^N^P)"
 
-#. ctrl_x_mode == 0, ^P/^N compl.
-#: ../edit.c:83
-#, fuzzy
-msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
+msgid " ^X mode (^E^Y^L^]^F^I^K^D^V^N^P)"
 msgstr " Chế độ ^X (^E^Y^L^]^F^I^K^D^V^N^P)"
 
-#: ../edit.c:85
+msgid " Keyword Local completion (^N^P)"
+msgstr " Tự động kết thúc nội bộ cho từ khóa (^N^P)"
+
 msgid " Whole line completion (^L^N^P)"
 msgstr " Tự động kết thúc cho cả dòng (^L^N^P)"
 
-#: ../edit.c:86
 msgid " File name completion (^F^N^P)"
 msgstr " Tự động kết thúc tên tập tin (^F^N^P)"
 
-#: ../edit.c:87
 msgid " Tag completion (^]^N^P)"
 msgstr " Tự động kết thúc thẻ đánh dấu (^]^N^P)"
 
-#: ../edit.c:88
 msgid " Path pattern completion (^N^P)"
 msgstr " Tự động kết thúc mẫu đường dẫn (^N^P)"
 
-#: ../edit.c:89
 msgid " Definition completion (^D^N^P)"
 msgstr " Tự động kết thúc định nghĩa (^D^N^P)"
 
-#: ../edit.c:91
 msgid " Dictionary completion (^K^N^P)"
 msgstr " Tự động kết thúc theo từ điển (^K^N^P)"
 
-#: ../edit.c:92
 msgid " Thesaurus completion (^T^N^P)"
 msgstr " Tự động kết thúc từ đồng âm (^T^N^P)"
 
-#: ../edit.c:93
 msgid " Command-line completion (^V^N^P)"
 msgstr " Tự động kết thúc dòng lệnh (^V^N^P)"
 
-#: ../edit.c:94
-#, fuzzy
-msgid " User defined completion (^U^N^P)"
-msgstr " Tự động kết thúc cho cả dòng (^L^N^P)"
-
-#: ../edit.c:95
-#, fuzzy
-msgid " Omni completion (^O^N^P)"
-msgstr " Tự động kết thúc thẻ đánh dấu (^]^N^P)"
-
-#: ../edit.c:96
-#, fuzzy
-msgid " Spelling suggestion (^S^N^P)"
-msgstr " Tự động kết thúc cho cả dòng (^L^N^P)"
-
-#: ../edit.c:97
-msgid " Keyword Local completion (^N^P)"
-msgstr " Tự động kết thúc nội bộ cho từ khóa (^N^P)"
-
-#: ../edit.c:100
 msgid "Hit end of paragraph"
 msgstr "Kết thúc của đoạn văn"
 
-#: ../edit.c:101
-msgid "E839: Completion function changed window"
-msgstr ""
-
-#: ../edit.c:102
-msgid "E840: Completion function deleted text"
-msgstr ""
+msgid "'thesaurus' option is empty"
+msgstr "Không đưa ra giá trị của tùy chọn 'thesaurus'"
 
-#: ../edit.c:1847
 msgid "'dictionary' option is empty"
 msgstr "Không đưa ra giá trị của tùy chọn 'dictionary'"
 
-#: ../edit.c:1848
-msgid "'thesaurus' option is empty"
-msgstr "Không đưa ra giá trị của tùy chọn 'thesaurus'"
-
-#: ../edit.c:2655
 #, c-format
 msgid "Scanning dictionary: %s"
 msgstr "Quét từ điển: %s"
 
-#: ../edit.c:3079
 msgid " (insert) Scroll (^E/^Y)"
 msgstr " (chèn) Cuộn (^E/^Y)"
 
-#: ../edit.c:3081
 msgid " (replace) Scroll (^E/^Y)"
 msgstr " (thay thế) Cuộn (^E/^Y)"
 
-#: ../edit.c:3587
 #, c-format
 msgid "Scanning: %s"
 msgstr "Quét: %s"
 
-#: ../edit.c:3614
 msgid "Scanning tags."
 msgstr "Tìm kiếm trong số thẻ đánh dấu."
 
-#: ../edit.c:4519
 msgid " Adding"
 msgstr " Thêm"
 
-#. showmode might reset the internal line pointers, so it must
-#. * be called before line = ml_get(), or when this address is no
-#. * longer needed.  -- Acevedo.
-#.
-#: ../edit.c:4562
 msgid "-- Searching..."
 msgstr "-- Tìm kiếm..."
 
-#: ../edit.c:4618
 msgid "Back at original"
 msgstr "Từ ban đầu"
 
-#: ../edit.c:4621
 msgid "Word from other line"
 msgstr "Từ của dòng khác"
 
-#: ../edit.c:4624
 msgid "The only match"
 msgstr "Tương ứng duy nhất"
 
-#: ../edit.c:4680
 #, c-format
 msgid "match %d of %d"
 msgstr "Tương ứng %d của %d"
 
-#: ../edit.c:4684
 #, c-format
 msgid "match %d"
 msgstr "Tương ứng %d"
 
-#: ../eval.c:137
-#, fuzzy
-msgid "E18: Unexpected characters in :let"
-msgstr "E18: Ở trước '=' có các ký tự không mong đợi"
-
-#: ../eval.c:138
-#, fuzzy, c-format
-msgid "E684: list index out of range: %"
-msgstr "E322: số thứ tự dòng vượt quá giới hạn : %"
-
-#: ../eval.c:139
-#, c-format
-msgid "E121: Undefined variable: %s"
-msgstr "E121: Biến không xác định: %s"
-
-#: ../eval.c:140
-msgid "E111: Missing ']'"
-msgstr "E111: Thiếu ']'"
-
-#: ../eval.c:141
-#, fuzzy, c-format
-msgid "E686: Argument of %s must be a List"
-msgstr "E487: Tham số phải là một số dương"
-
-#: ../eval.c:143
-#, fuzzy, c-format
-msgid "E712: Argument of %s must be a List or Dictionary"
-msgstr "E487: Tham số phải là một số dương"
-
-#: ../eval.c:144
-#, fuzzy
-msgid "E713: Cannot use empty key for Dictionary"
-msgstr "E214: Không tìm thấy tập tin tạm thời (temp) để ghi nhớ"
-
-#: ../eval.c:145
-#, fuzzy
-msgid "E714: List required"
-msgstr "E471: Cần chỉ ra tham số"
-
-#: ../eval.c:146
-#, fuzzy
-msgid "E715: Dictionary required"
-msgstr "E129: Cần tên hàm số"
-
-#: ../eval.c:147
-#, c-format
-msgid "E118: Too many arguments for function: %s"
-msgstr "E118: Quá nhiều tham số cho hàm: %s"
-
-#: ../eval.c:148
-#, c-format
-msgid "E716: Key not present in Dictionary: %s"
-msgstr ""
-
-#: ../eval.c:150
-#, c-format
-msgid "E122: Function %s already exists, add ! to replace it"
-msgstr "E122: Hàm số %s đã có, hãy thêm ! để thay thế nó."
-
-#: ../eval.c:151
-#, fuzzy
-msgid "E717: Dictionary entry already exists"
-msgstr "E95: Đã có bộ đệm với tên như vậy"
-
-#: ../eval.c:152
-#, fuzzy
-msgid "E718: Funcref required"
-msgstr "E129: Cần tên hàm số"
-
-#: ../eval.c:153
-#, fuzzy
-msgid "E719: Cannot use [:] with a Dictionary"
-msgstr "E360: Không chạy được shell với tùy chọn -f"
-
-#: ../eval.c:154
-#, c-format
-msgid "E734: Wrong variable type for %s="
-msgstr ""
-
-#: ../eval.c:155
-#, fuzzy, c-format
-msgid "E130: Unknown function: %s"
-msgstr "E117: Hàm số không biết: %s"
-
-#: ../eval.c:156
 #, c-format
-msgid "E461: Illegal variable name: %s"
-msgstr "E461: Tên biến không cho phép: %s"
-
-#: ../eval.c:157
-msgid "E806: using Float as a String"
-msgstr ""
-
-#: ../eval.c:1830
-msgid "E687: Less targets than List items"
-msgstr ""
-
-#: ../eval.c:1834
-msgid "E688: More targets than List items"
-msgstr ""
-
-#: ../eval.c:1906
-msgid "Double ; in list of variables"
-msgstr ""
-
-#: ../eval.c:2078
-#, fuzzy, c-format
-msgid "E738: Can't list variables for %s"
-msgstr "E138: Không thể ghi tập tin viminfo %s!"
-
-#: ../eval.c:2391
-msgid "E689: Can only index a List or Dictionary"
-msgstr ""
-
-#: ../eval.c:2396
-msgid "E708: [:] must come last"
-msgstr ""
-
-#: ../eval.c:2439
-msgid "E709: [:] requires a List value"
-msgstr ""
-
-#: ../eval.c:2674
-msgid "E710: List value has more items than target"
-msgstr ""
-
-#: ../eval.c:2678
-msgid "E711: List value has not enough items"
-msgstr ""
-
-#: ../eval.c:2867
-#, fuzzy
-msgid "E690: Missing \"in\" after :for"
-msgstr "E69: Thiếu ] sau %s%%["
+msgid "E106: Unknown variable: \"%s\""
+msgstr "E106: Biến không biết: \"%s\""
 
-#: ../eval.c:3063
 #, c-format
 msgid "E107: Missing parentheses: %s"
 msgstr "E107: Thiếu dấu ngoặc: %s"
 
-#: ../eval.c:3263
 #, c-format
 msgid "E108: No such variable: \"%s\""
 msgstr "E108: Không có biến như vậy: \"%s\""
 
-#: ../eval.c:3333
-msgid "E743: variable nested too deep for (un)lock"
-msgstr ""
-
-#: ../eval.c:3630
 msgid "E109: Missing ':' after '?'"
 msgstr "E109: Thiếu ':' sau '?'"
 
-#: ../eval.c:3893
-msgid "E691: Can only compare List with List"
-msgstr ""
-
-#: ../eval.c:3895
-#, fuzzy
-msgid "E692: Invalid operation for Lists"
-msgstr "E449: Nhận được một biểu thức không cho phép"
-
-#: ../eval.c:3915
-msgid "E735: Can only compare Dictionary with Dictionary"
-msgstr ""
-
-#: ../eval.c:3917
-#, fuzzy
-msgid "E736: Invalid operation for Dictionary"
-msgstr "E116: Tham số cho hàm %s đưa ra không đúng"
-
-#: ../eval.c:3932
-msgid "E693: Can only compare Funcref with Funcref"
-msgstr ""
-
-#: ../eval.c:3934
-#, fuzzy
-msgid "E694: Invalid operation for Funcrefs"
-msgstr "E116: Tham số cho hàm %s đưa ra không đúng"
-
-#: ../eval.c:4277
-#, fuzzy
-msgid "E804: Cannot use '%' with Float"
-msgstr "E360: Không chạy được shell với tùy chọn -f"
-
-#: ../eval.c:4478
 msgid "E110: Missing ')'"
 msgstr "E110: Thiếu ')'"
 
-#: ../eval.c:4609
-#, fuzzy
-msgid "E695: Cannot index a Funcref"
-msgstr "E90: Không thể bỏ nạp từ bộ nhớ bộ đệm cuối cùng"
+msgid "E111: Missing ']'"
+msgstr "E111: Thiếu ']'"
 
-#: ../eval.c:4839
 #, c-format
 msgid "E112: Option name missing: %s"
 msgstr "E112: Không đưa ra tên tùy chọn: %s"
 
-#: ../eval.c:4855
 #, c-format
 msgid "E113: Unknown option: %s"
 msgstr "E113: Tùy chọn không biết: %s"
 
-#: ../eval.c:4904
 #, c-format
 msgid "E114: Missing quote: %s"
 msgstr "E114: Thiếu ngoặc kép: %s"
 
-#: ../eval.c:5020
 #, c-format
 msgid "E115: Missing quote: %s"
 msgstr "E115: Thiếu ngoặc kép: %s"
 
-#: ../eval.c:5084
-#, fuzzy, c-format
-msgid "E696: Missing comma in List: %s"
-msgstr "E405: Thiếu dấu bằng: %s"
-
-#: ../eval.c:5091
-#, fuzzy, c-format
-msgid "E697: Missing end of List ']': %s"
-msgstr "E398: Thiếu '=': %s"
-
-#: ../eval.c:6475
-#, fuzzy, c-format
-msgid "E720: Missing colon in Dictionary: %s"
-msgstr "E405: Thiếu dấu bằng: %s"
-
-#: ../eval.c:6499
-#, c-format
-msgid "E721: Duplicate key in Dictionary: \"%s\""
-msgstr ""
-
-#: ../eval.c:6517
-#, fuzzy, c-format
-msgid "E722: Missing comma in Dictionary: %s"
-msgstr "E527: Thiếu dấu phẩy"
-
-#: ../eval.c:6524
-#, fuzzy, c-format
-msgid "E723: Missing end of Dictionary '}': %s"
-msgstr "E126: Thiếu lệnh :endfunction"
-
-#: ../eval.c:6555
-#, fuzzy
-msgid "E724: variable nested too deep for displaying"
-msgstr "E22: Các script lồng vào nhau quá sâu"
-
-#: ../eval.c:7188
-#, fuzzy, c-format
-msgid "E740: Too many arguments for function %s"
-msgstr "E118: Quá nhiều tham số cho hàm: %s"
-
-#: ../eval.c:7190
 #, c-format
 msgid "E116: Invalid arguments for function %s"
 msgstr "E116: Tham số cho hàm %s đưa ra không đúng"
 
-#: ../eval.c:7377
 #, c-format
 msgid "E117: Unknown function: %s"
 msgstr "E117: Hàm số không biết: %s"
 
-#: ../eval.c:7383
+#, c-format
+msgid "E118: Too many arguments for function: %s"
+msgstr "E118: Quá nhiều tham số cho hàm: %s"
+
 #, c-format
 msgid "E119: Not enough arguments for function: %s"
 msgstr "E119: Không đủ tham số cho hàm: %s"
 
-#: ../eval.c:7387
 #, c-format
 msgid "E120: Using  not in a script context: %s"
 msgstr "E120: Sử dụng  ngoài script: %s"
 
-#: ../eval.c:7391
-#, c-format
-msgid "E725: Calling dict function without Dictionary: %s"
-msgstr ""
-
-#: ../eval.c:7453
-#, fuzzy
-msgid "E808: Number or Float required"
-msgstr "E521: Sau dấu = cần đưa ra một số"
-
-#: ../eval.c:7503
-#, fuzzy
-msgid "add() argument"
-msgstr "Tham số không được phép cho"
-
-#: ../eval.c:7907
-#, fuzzy
-msgid "E699: Too many arguments"
-msgstr "Có quá nhiều tham số soạn thảo"
-
-#: ../eval.c:8073
-#, fuzzy
-msgid "E785: complete() can only be used in Insert mode"
-msgstr "E328: Trình đơn chỉ có trong chế độ khác"
-
-#: ../eval.c:8156
 msgid "&Ok"
 msgstr "&Ok"
 
-#: ../eval.c:8676
-#, fuzzy, c-format
-msgid "E737: Key already exists: %s"
-msgstr "E227: đã có ánh xạ cho %s"
-
-#: ../eval.c:8692
-msgid "extend() argument"
-msgstr ""
-
-#: ../eval.c:8915
-#, fuzzy
-msgid "map() argument"
-msgstr " vim [các tham số] "
-
-#: ../eval.c:8916
-msgid "filter() argument"
-msgstr ""
-
-#: ../eval.c:9229
 #, c-format
 msgid "+-%s%3ld lines: "
 msgstr "+-%s%3ld dòng: "
 
-#: ../eval.c:9291
-#, fuzzy, c-format
-msgid "E700: Unknown function: %s"
-msgstr "E117: Hàm số không biết: %s"
+msgid ""
+"&OK\n"
+"&Cancel"
+msgstr ""
+"&OK\n"
+"&Hủy bỏ"
 
-#: ../eval.c:10729
 msgid "called inputrestore() more often than inputsave()"
 msgstr "Hàm số inputrestore() được gọi nhiều hơn hàm inputsave()"
 
-#: ../eval.c:10771
-#, fuzzy
-msgid "insert() argument"
-msgstr "Có quá nhiều tham số soạn thảo"
-
-#: ../eval.c:10841
-#, fuzzy
-msgid "E786: Range not allowed"
-msgstr "E481: Không cho phép sử dụng phạm vi"
-
-#: ../eval.c:11140
-#, fuzzy
-msgid "E701: Invalid type for len()"
-msgstr "E596: Phông chữ không đúng"
-
-#: ../eval.c:11980
-msgid "E726: Stride is zero"
-msgstr ""
-
-#: ../eval.c:11982
-msgid "E727: Start past end"
-msgstr ""
-
-#: ../eval.c:12024 ../eval.c:15297
-msgid ""
-msgstr ""
-
-#: ../eval.c:12282
-msgid "remove() argument"
-msgstr ""
-
-#: ../eval.c:12466
 msgid "E655: Too many symbolic links (cycle?)"
 msgstr "E655: Quá nhiều liên kết tượng trưng (vòng lặp?)"
 
-#: ../eval.c:12593
-msgid "reverse() argument"
-msgstr ""
-
-#: ../eval.c:13721
-msgid "sort() argument"
-msgstr ""
+msgid "E240: No connection to Vim server"
+msgstr "E240: Không có kết nối với máy chủ Vim"
 
-#: ../eval.c:13721
-#, fuzzy
-msgid "uniq() argument"
-msgstr "Tham số không được phép cho"
+msgid "E277: Unable to read a server reply"
+msgstr "E277: Máy chủ không trả lời"
 
-#: ../eval.c:13776
-#, fuzzy
-msgid "E702: Sort compare function failed"
-msgstr "E237: Chọn máy in không thành công"
+msgid "E258: Unable to send to client"
+msgstr "E258: Không thể trả lời cho máy con"
 
-#: ../eval.c:13806
-msgid "E882: Uniq compare function failed"
-msgstr ""
+#, c-format
+msgid "E241: Unable to send to %s"
+msgstr "E241: Không thể gửi tin nhắn tới %s"
 
-#: ../eval.c:14085
 msgid "(Invalid)"
 msgstr "(Không đúng)"
 
-#: ../eval.c:14590
-#, fuzzy
-msgid "E677: Error writing temp file"
-msgstr "E208: Lỗi ghi nhớ vào \"%s\""
-
-#: ../eval.c:16159
-msgid "E805: Using a Float as a Number"
-msgstr ""
+#, c-format
+msgid "E121: Undefined variable: %s"
+msgstr "E121: Biến không xác định: %s"
 
-#: ../eval.c:16162
-msgid "E703: Using a Funcref as a Number"
-msgstr ""
+#, c-format
+msgid "E461: Illegal variable name: %s"
+msgstr "E461: Tên biến không cho phép: %s"
 
-#: ../eval.c:16170
-msgid "E745: Using a List as a Number"
-msgstr ""
+#, c-format
+msgid "E122: Function %s already exists, add ! to replace it"
+msgstr "E122: Hàm số %s đã có, hãy thêm ! để thay thế nó."
 
-#: ../eval.c:16173
-msgid "E728: Using a Dictionary as a Number"
-msgstr ""
+#, c-format
+msgid "E123: Undefined function: %s"
+msgstr "E123: Hàm số không xác định: %s"
 
-#: ../eval.c:16259
-msgid "E729: using Funcref as a String"
-msgstr ""
+#, c-format
+msgid "E124: Missing '(': %s"
+msgstr "E124: Thiếu '(': %s"
 
-#: ../eval.c:16262
-#, fuzzy
-msgid "E730: using List as a String"
-msgstr "E374: Thiếu ] trong chuỗi định dạng"
+#, c-format
+msgid "E125: Illegal argument: %s"
+msgstr "E125: Tham số không cho phép: %s"
 
-#: ../eval.c:16265
-msgid "E731: using Dictionary as a String"
-msgstr ""
+msgid "E126: Missing :endfunction"
+msgstr "E126: Thiếu lệnh :endfunction"
 
-#: ../eval.c:16619
-#, fuzzy, c-format
-msgid "E706: Variable type mismatch for: %s"
-msgstr "E93: Tìm thấy vài tương ứng với %s"
+#, c-format
+msgid "E127: Cannot redefine function %s: It is in use"
+msgstr "E127: Không thể định nghĩa lại hàm số %s: hàm đang được sử dụng"
 
-#: ../eval.c:16705
-#, fuzzy, c-format
-msgid "E795: Cannot delete variable %s"
-msgstr "E46: Không thay đổi được biến chỉ đọc \"%s\""
+msgid "E129: Function name required"
+msgstr "E129: Cần tên hàm số"
 
-#: ../eval.c:16724
-#, fuzzy, c-format
-msgid "E704: Funcref variable name must start with a capital: %s"
+#, c-format
+msgid "E128: Function name must start with a capital: %s"
 msgstr "E128: Tên hàm số phải bắt đầu với một chữ cái hoa: %s"
 
-#: ../eval.c:16732
 #, c-format
-msgid "E705: Variable name conflicts with existing function: %s"
-msgstr ""
+msgid "E130: Undefined function: %s"
+msgstr "E130: Hàm số %s chưa xác định"
 
-#: ../eval.c:16763
 #, c-format
-msgid "E741: Value is locked: %s"
+msgid "E131: Cannot delete function %s: It is in use"
+msgstr "E131: Không thể xóa hàm số %s: Hàm đang được sử dụng"
+
+msgid "E132: Function call depth is higher than 'maxfuncdepth'"
+msgstr "E132: Độ sâu của lời gọi hàm số lớn hơn giá trị 'maxfuncdepth'"
+
+#, c-format
+msgid "calling %s"
+msgstr "lời gọi %s"
+
+#, c-format
+msgid "%s aborted"
+msgstr "%s dừng"
+
+#, c-format
+msgid "%s returning #%ld"
+msgstr "%s trả lại #%ld"
+
+#, c-format
+msgid "%s returning \"%s\""
+msgstr "%s trả lại \"%s\""
+
+#, c-format
+msgid "continuing in %s"
+msgstr "tiếp tục trong %s"
+
+msgid "E133: :return not inside a function"
+msgstr "E133: lệnh :return ở ngoài một hàm"
+
+msgid ""
+"\n"
+"# global variables:\n"
 msgstr ""
+"\n"
+"# biến toàn cầu:\n"
 
-#: ../eval.c:16764 ../eval.c:16769 ../message.c:1839
-msgid "Unknown"
-msgstr "Không rõ"
+msgid "Entering Debug mode.  Type \"cont\" to continue."
+msgstr "Bật chế độ sửa lỗi (Debug). Gõ \"cont\" để tiếp tục."
 
-#: ../eval.c:16768
-#, fuzzy, c-format
-msgid "E742: Cannot change value of %s"
-msgstr "E284: Không đặt được giá trị nội dung nhập vào (IC)"
+#, c-format
+msgid "line %ld: %s"
+msgstr "dòng %ld: %s"
+
+#, c-format
+msgid "cmd: %s"
+msgstr "câu lệnh: %s"
+
+#, c-format
+msgid "Breakpoint in \"%s%s\" line %ld"
+msgstr "Điểm dừng trên \"%s%s\" dòng %ld"
+
+#, c-format
+msgid "E161: Breakpoint not found: %s"
+msgstr "E161: Không tìm thấy điểm dừng: %s"
+
+msgid "No breakpoints defined"
+msgstr "Điểm dừng không được xác định"
+
+#, c-format
+msgid "%3d  %s %s  line %ld"
+msgstr "%3d  %s %s dòng %ld"
+
+msgid "Save As"
+msgstr "Ghi nhớ như"
+
+#, c-format
+msgid "Save changes to \"%.*s\"?"
+msgstr "Ghi nhớ thay đổi vào \"%.*s\"?"
 
-#: ../eval.c:16838
-msgid "E698: variable nested too deep for making a copy"
+msgid "Untitled"
+msgstr "Chưa đặt tên"
+
+#, c-format
+msgid "E162: No write since last change for buffer \"%s\""
+msgstr "E162: Thay đổi chưa được ghi nhớ trong bộ đệm \"%s\""
+
+msgid "Warning: Entered other buffer unexpectedly (check autocommands)"
 msgstr ""
+"Cảnh báo: Chuyển tới bộ đệm khác không theo ý muốn (hãy kiểm tra câu lệnh tự "
+"động)"
+
+msgid "E163: There is only one file to edit"
+msgstr "E163: Chỉ có một tập tin để soạn thảo"
+
+msgid "E164: Cannot go before first file"
+msgstr "E164: Đây là tập tin đầu tiên"
+
+msgid "E165: Cannot go beyond last file"
+msgstr "E165: Đây là tập tin cuối cùng"
+
+# TODO: Capitalise first word of message?
+msgid "E666: Compiler not supported: %s"
+msgstr "E666: trình biên dịch không được hỗ trợ: %s"
 
-#: ../eval.c:17249
 #, c-format
-msgid "E123: Undefined function: %s"
-msgstr "E123: Hàm số không xác định: %s"
+msgid "Searching for \"%s\" in \"%s\""
+msgstr "Tìm kiếm \"%s\" trong \"%s\""
 
-#: ../eval.c:17260
 #, c-format
-msgid "E124: Missing '(': %s"
-msgstr "E124: Thiếu '(': %s"
+msgid "Searching for \"%s\""
+msgstr "Tìm kiếm \"%s\""
 
-#: ../eval.c:17293
-#, fuzzy
-msgid "E862: Cannot use g: here"
-msgstr "E284: Không đặt được giá trị nội dung nhập vào (IC)"
+#, c-format
+msgid "not found in 'runtimepath': \"%s\""
+msgstr "không tìm thấy trong 'runtimepath': \"%s\""
+
+msgid "Source Vim script"
+msgstr "Thực hiện script của Vim"
 
-#: ../eval.c:17312
 #, c-format
-msgid "E125: Illegal argument: %s"
-msgstr "E125: Tham số không cho phép: %s"
+msgid "Cannot source a directory: \"%s\""
+msgstr "Không thể thực hiện một thư mục: \"%s\""
 
-#: ../eval.c:17323
-#, fuzzy, c-format
-msgid "E853: Duplicate argument name: %s"
-msgstr "E154: Thẻ ghi lặp lại \"%s\" trong tập tin %s"
+#, c-format
+msgid "could not source \"%s\""
+msgstr "không thực hiện được \"%s\""
 
-#: ../eval.c:17416
-msgid "E126: Missing :endfunction"
-msgstr "E126: Thiếu lệnh :endfunction"
+#, c-format
+msgid "line %ld: could not source \"%s\""
+msgstr "dòng %ld: không thực hiện được \"%s\""
 
-#: ../eval.c:17537
-#, fuzzy, c-format
-msgid "E707: Function name conflicts with variable: %s"
-msgstr "E128: Tên hàm số phải bắt đầu với một chữ cái hoa: %s"
+#, c-format
+msgid "sourcing \"%s\""
+msgstr "thực hiện \"%s\""
 
-#: ../eval.c:17549
 #, c-format
-msgid "E127: Cannot redefine function %s: It is in use"
-msgstr "E127: Không thể định nghĩa lại hàm số %s: hàm đang được sử dụng"
+msgid "line %ld: sourcing \"%s\""
+msgstr "dòng %ld: thực hiện \"%s\""
 
-#: ../eval.c:17604
-#, fuzzy, c-format
-msgid "E746: Function name does not match script file name: %s"
-msgstr "E128: Tên hàm số phải bắt đầu với một chữ cái hoa: %s"
+#, c-format
+msgid "finished sourcing %s"
+msgstr "thực hiện xong %s"
 
-#: ../eval.c:17716
-msgid "E129: Function name required"
-msgstr "E129: Cần tên hàm số"
+msgid "W15: Warning: Wrong line separator, ^M may be missing"
+msgstr "W15: Cảnh báo: Ký tự phân cách dòng không đúng. Rất có thể thiếu ^M"
 
-#: ../eval.c:17824
-#, fuzzy, c-format
-msgid "E128: Function name must start with a capital or \"s:\": %s"
-msgstr "E128: Tên hàm số phải bắt đầu với một chữ cái hoa: %s"
+msgid "E167: :scriptencoding used outside of a sourced file"
+msgstr "E167: Lệnh :scriptencoding sử dụng ngoài tập tin script"
 
-#: ../eval.c:17833
-#, fuzzy, c-format
-msgid "E884: Function name cannot contain a colon: %s"
-msgstr "E128: Tên hàm số phải bắt đầu với một chữ cái hoa: %s"
+msgid "E168: :finish used outside of a sourced file"
+msgstr "E168: Lệnh :finish sử dụng ngoài tập tin script"
 
-#: ../eval.c:18336
 #, c-format
-msgid "E131: Cannot delete function %s: It is in use"
-msgstr "E131: Không thể xóa hàm số %s: Hàm đang được sử dụng"
+msgid "Page %d"
+msgstr "Trang %d"
 
-#: ../eval.c:18441
-msgid "E132: Function call depth is higher than 'maxfuncdepth'"
-msgstr "E132: Độ sâu của lời gọi hàm số lớn hơn giá trị 'maxfuncdepth'"
+msgid "No text to be printed"
+msgstr "Không có gì để in"
 
-#: ../eval.c:18568
 #, c-format
-msgid "calling %s"
-msgstr "lời gọi %s"
+msgid "Printing page %d (%d%%)"
+msgstr "In trang %d (%d%%)"
 
-#: ../eval.c:18651
 #, c-format
-msgid "%s aborted"
-msgstr "%s dừng"
+msgid " Copy %d of %d"
+msgstr " Sao chép %d của %d"
 
-#: ../eval.c:18653
 #, c-format
-msgid "%s returning #%"
-msgstr "%s trả lại #%"
+msgid "Printed: %s"
+msgstr "Đã in: %s"
 
-#: ../eval.c:18670
-#, fuzzy, c-format
-msgid "%s returning %s"
-msgstr "%s trả lại \"%s\""
+msgid "Printing aborted"
+msgstr "In bị dừng"
+
+msgid "E455: Error writing to PostScript output file"
+msgstr "E455: Lỗi ghi nhớ vào tập tin PostScript"
 
-#: ../eval.c:18691 ../ex_cmds2.c:2695
 #, c-format
-msgid "continuing in %s"
-msgstr "tiếp tục trong %s"
+msgid "E624: Can't open file \"%s\""
+msgstr "E624: Không thể mở tập tin \"%s\""
 
-#: ../eval.c:18795
-msgid "E133: :return not inside a function"
-msgstr "E133: lệnh :return ở ngoài một hàm"
+#, c-format
+msgid "E457: Can't read PostScript resource file \"%s\""
+msgstr "E457: Không thể đọc tập tin tài nguyên PostScript \"%s\""
 
-#: ../eval.c:19159
-msgid ""
-"\n"
-"# global variables:\n"
-msgstr ""
-"\n"
-"# biến toàn cầu:\n"
+# TODO: Capitalise first word of message?
+msgid "E618: File \"%s\" is not a PostScript resource file"
+msgstr "E618: \"%s\" không phải là tập tin tài nguyên PostScript"
 
-#: ../eval.c:19254
-msgid ""
-"\n"
-"\tLast set from "
-msgstr ""
-"\n"
-"\tLần cuối cùng tùy chọn thay đổi vào "
+# TODO: Capitalise first word of message?
+msgid "E619: File \"%s\" is not a supported PostScript resource file"
+msgstr "E619: \"%s\" không phải là tập tin tài nguyên PostScript được hỗ trợ"
 
-#: ../eval.c:19272
-#, fuzzy
-msgid "No old files"
-msgstr "Không có tập tin được tính đến"
+#, c-format
+msgid "E621: \"%s\" resource file has wrong version"
+msgstr "E621: tập tin tài nguyên \"%s\" có phiên bản không đúng"
+
+msgid "E324: Can't open PostScript output file"
+msgstr "E324: Không thể mở tập tin PostScript"
+
+#, c-format
+msgid "E456: Can't open file \"%s\""
+msgstr "E456: Không thể mở tập tin \"%s\""
+
+msgid "E456: Can't find PostScript resource file \"prolog.ps\""
+msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"prolog.ps\""
+
+#, c-format
+msgid "E456: Can't find PostScript resource file \"%s.ps\""
+msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"%s.ps\""
+
+#, c-format
+msgid "E620: Unable to convert from multi-byte to \"%s\" encoding"
+msgstr "E620: Không thể chuyển từ các ký tự nhiều byte thành bảng mã \"%s\""
+
+msgid "Sending to printer..."
+msgstr "Gửi tới máy in..."
+
+msgid "E365: Failed to print PostScript file"
+msgstr "E365: In tập tin PostScript không thành công"
+
+msgid "Print job sent."
+msgstr "Đã gửi công việc in."
+
+#, c-format
+msgid "Current %slanguage: \"%s\""
+msgstr "Ngôn ngữ %shiện thời: \"%s\""
+
+#, c-format
+msgid "E197: Cannot set language to \"%s\""
+msgstr "E197: Không thể thay đổi ngôn ngữ thành \"%s\""
 
-#: ../ex_cmds.c:122
 #, c-format
 msgid "<%s>%s%s  %d,  Hex %02x,  Octal %03o"
 msgstr "<%s>%s%s  %d,  Hex %02x,  Octal %03o"
 
-#: ../ex_cmds.c:145
 #, c-format
 msgid "> %d, Hex %04x, Octal %o"
 msgstr "> %d, Hex %04x, Octal %o"
 
-#: ../ex_cmds.c:146
 #, c-format
 msgid "> %d, Hex %08x, Octal %o"
 msgstr "> %d, Hex %08x, Octal %o"
 
-#: ../ex_cmds.c:684
 msgid "E134: Move lines into themselves"
 msgstr "E134: Di chuyển các dòng lên chính chúng"
 
-#: ../ex_cmds.c:747
 msgid "1 line moved"
 msgstr "Đã di chuyển 1 dòng"
 
-#: ../ex_cmds.c:749
 #, c-format
-msgid "% lines moved"
-msgstr "Đã di chuyển % dòng"
+msgid "%ld lines moved"
+msgstr "Đã di chuyển %ld dòng"
 
-#: ../ex_cmds.c:1175
 #, c-format
-msgid "% lines filtered"
-msgstr "Đã lọc % dòng"
+msgid "%ld lines filtered"
+msgstr "Đã lọc %ld dòng"
 
-#: ../ex_cmds.c:1194
 msgid "E135: *Filter* Autocommands must not change current buffer"
 msgstr "E135: Các lệnh tự động *Filter* không được thay đổi bộ đệm hiện thời"
 
-#: ../ex_cmds.c:1244
 msgid "[No write since last change]\n"
 msgstr "[Thay đổi chưa được ghi nhớ]\n"
 
-#: ../ex_cmds.c:1424
 #, c-format
 msgid "%sviminfo: %s in line: "
 msgstr "%sviminfo: %s trên dòng: "
 
-#: ../ex_cmds.c:1431
 msgid "E136: viminfo: Too many errors, skipping rest of file"
 msgstr "E136: viminfo: Quá nhiều lỗi, phần còn lại của tập tin sẽ được bỏ qua"
 
-#: ../ex_cmds.c:1458
 #, c-format
 msgid "Reading viminfo file \"%s\"%s%s%s"
 msgstr "Đọc tập tin viminfo \"%s\"%s%s%s"
 
-#: ../ex_cmds.c:1460
 msgid " info"
 msgstr " thông tin"
 
-#: ../ex_cmds.c:1461
 msgid " marks"
 msgstr " dấu hiệu"
 
-#: ../ex_cmds.c:1462
-#, fuzzy
-msgid " oldfiles"
-msgstr "Không có tập tin được tính đến"
-
-#: ../ex_cmds.c:1463
 msgid " FAILED"
 msgstr " KHÔNG THÀNH CÔNG"
 
-#. avoid a wait_return for this message, it's annoying
-#: ../ex_cmds.c:1541
 #, c-format
 msgid "E137: Viminfo file is not writable: %s"
 msgstr "E137: Thiếu quyền ghi lên tập tin viminfo: %s"
 
-#: ../ex_cmds.c:1626
 #, c-format
 msgid "E138: Can't write viminfo file %s!"
 msgstr "E138: Không thể ghi tập tin viminfo %s!"
 
-#: ../ex_cmds.c:1635
 #, c-format
 msgid "Writing viminfo file \"%s\""
 msgstr "Ghi tập tin viminfo \"%s\""
 
-#. Write the info:
-#: ../ex_cmds.c:1720
 #, c-format
 msgid "# This viminfo file was generated by Vim %s.\n"
 msgstr "# Tập tin viminfo này được tự động tạo bởi Vim %s.\n"
 
-#: ../ex_cmds.c:1722
 msgid ""
 "# You may edit it if you're careful!\n"
 "\n"
@@ -1158,137 +727,88 @@ msgstr ""
 "# Bạn có thể sửa tập tin này, nhưng hãy thận trọng!\n"
 "\n"
 
-#: ../ex_cmds.c:1723
 msgid "# Value of 'encoding' when this file was written\n"
 msgstr "# Giá trị của tùy chọn 'encoding' vào thời điểm ghi tập tin\n"
 
-#: ../ex_cmds.c:1800
 msgid "Illegal starting char"
 msgstr "Ký tự đầu tiên không cho phép"
 
-#: ../ex_cmds.c:2162
+msgid "E139: File is loaded in another buffer"
+msgstr "E139: Tập tin được nạp trong bộ đệm khác"
+
 msgid "Write partial file?"
 msgstr "Ghi nhớ một phần tập tin?"
 
-#: ../ex_cmds.c:2166
 msgid "E140: Use ! to write partial buffer"
 msgstr "E140: Sử dụng ! để ghi nhớ một phần bộ đệm"
 
-#: ../ex_cmds.c:2281
-#, fuzzy, c-format
-msgid "Overwrite existing file \"%s\"?"
-msgstr "Ghi đè lên tập tin đã có \"%.*s\"?"
-
-#: ../ex_cmds.c:2317
 #, c-format
-msgid "Swap file \"%s\" exists, overwrite anyway?"
-msgstr ""
-
-#: ../ex_cmds.c:2326
-#, fuzzy, c-format
-msgid "E768: Swap file exists: %s (:silent! overrides)"
-msgstr "E13: Tập tin đã tồn tại (thêm ! để ghi chèn)"
+msgid "Overwrite existing file \"%.*s\"?"
+msgstr "Ghi đè lên tập tin đã có \"%.*s\"?"
 
-#: ../ex_cmds.c:2381
 #, c-format
-msgid "E141: No file name for buffer %"
-msgstr "E141: Không có tên tập tin cho bộ đệm %"
+msgid "E141: No file name for buffer %ld"
+msgstr "E141: Không có tên tập tin cho bộ đệm %ld"
 
-#: ../ex_cmds.c:2412
 msgid "E142: File not written: Writing is disabled by 'write' option"
 msgstr "E142: Tập tin chưa được ghi nhớ: Ghi nhớ bị tắt bởi tùy chọn 'write'"
 
-#: ../ex_cmds.c:2434
-#, fuzzy, c-format
+#, c-format
 msgid ""
-"'readonly' option is set for \"%s\".\n"
+"'readonly' option is set for \"%.*s\".\n"
 "Do you wish to write anyway?"
 msgstr ""
 "Tùy chọn 'readonly' được đặt cho \"%.*s\".\n"
 "Ghi nhớ bằng mọi giá?"
 
-#: ../ex_cmds.c:2439
-#, c-format
-msgid ""
-"File permissions of \"%s\" are read-only.\n"
-"It may still be possible to write it.\n"
-"Do you wish to try?"
-msgstr ""
-
-#: ../ex_cmds.c:2451
-#, fuzzy, c-format
-msgid "E505: \"%s\" is read-only (add ! to override)"
-msgstr "là tập tin chỉ đọc (thêm ! để ghi nhớ bằng mọi giá)"
+msgid "Edit File"
+msgstr "Soạn thảo tập tin"
 
-#: ../ex_cmds.c:3120
 #, c-format
 msgid "E143: Autocommands unexpectedly deleted new buffer %s"
 msgstr "E143: Các lệnh tự động xóa bộ đệm mới ngoài ý muốn %s"
 
-#: ../ex_cmds.c:3313
-msgid "E144: non-numeric argument to :z"
+# TODO: Capitalise first word of message?
+msgid "E144: Non-numeric argument to :z"
 msgstr "E144: Tham số của lệnh :z phải là số"
 
-#: ../ex_cmds.c:3498
+msgid "E145: Shell commands not allowed in rvim"
+msgstr "E145: Không cho phép sử dụng lệnh shell trong rvim."
+
 msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Không thể phân cách biểu thức chính quy bằng chữ cái"
 
-#: ../ex_cmds.c:3964
 #, c-format
 msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
 msgstr "thay thế bằng %s? (y/n/a/q/l/^E/^Y)"
 
-#: ../ex_cmds.c:4379
 msgid "(Interrupted) "
 msgstr "(bị dừng)"
 
-#: ../ex_cmds.c:4384
-#, fuzzy
-msgid "1 match"
-msgstr "; tương ứng "
-
-#: ../ex_cmds.c:4384
 msgid "1 substitution"
 msgstr "1 thay thế"
 
-#: ../ex_cmds.c:4387
-#, fuzzy, c-format
-msgid "% matches"
-msgstr "% thay đổi"
-
-#: ../ex_cmds.c:4388
 #, c-format
-msgid "% substitutions"
-msgstr "% thay thế"
+msgid "%ld substitutions"
+msgstr "%ld thay thế"
 
-#: ../ex_cmds.c:4392
 msgid " on 1 line"
 msgstr " trên 1 dòng"
 
-#: ../ex_cmds.c:4395
 #, c-format
-msgid " on % lines"
-msgstr " trên % dòng"
+msgid " on %ld lines"
+msgstr " trên %ld dòng"
 
-#: ../ex_cmds.c:4438
 msgid "E147: Cannot do :global recursive"
 msgstr "E147: Không thực hiện được lệnh :global đệ qui"
 
-#: ../ex_cmds.c:4467
 msgid "E148: Regular expression missing from global"
 msgstr "E148: Thiếu biểu thức chính quy trong lệnh :global"
 
-#: ../ex_cmds.c:4508
 #, c-format
 msgid "Pattern found in every line: %s"
 msgstr "Tìm thấy tương ứng trên mọi dòng: %s"
 
-#: ../ex_cmds.c:4510
-#, fuzzy, c-format
-msgid "Pattern not found: %s"
-msgstr "Không tìm thấy mẫu (pattern)"
-
-#: ../ex_cmds.c:4587
 msgid ""
 "\n"
 "# Last Substitute String:\n"
@@ -1298,342 +818,138 @@ msgstr ""
 "# Chuỗi thay thế cuối cùng:\n"
 "$"
 
-#: ../ex_cmds.c:4679
 msgid "E478: Don't panic!"
 msgstr "E478: Hãy bình tĩnh, đừng hoảng hốt!"
 
-#: ../ex_cmds.c:4717
 #, c-format
 msgid "E661: Sorry, no '%s' help for %s"
 msgstr "E661: Rất tiếc, không có trợ giúp '%s' cho %s"
 
-#: ../ex_cmds.c:4719
 #, c-format
 msgid "E149: Sorry, no help for %s"
 msgstr "E149: Rất tiếc không có trợ giúp cho %s"
 
-#: ../ex_cmds.c:4751
 #, c-format
 msgid "Sorry, help file \"%s\" not found"
 msgstr "Xin lỗi, không tìm thấy tập tin trợ giúp \"%s\""
 
-#: ../ex_cmds.c:5323
 #, c-format
 msgid "E150: Not a directory: %s"
 msgstr "E150: %s không phải là một thư mục"
 
-#: ../ex_cmds.c:5446
 #, c-format
 msgid "E152: Cannot open %s for writing"
 msgstr "E152: Không thể mở %s để ghi"
 
-#: ../ex_cmds.c:5471
 #, c-format
 msgid "E153: Unable to open %s for reading"
 msgstr "E153: Không thể mở %s để đọc"
 
-#: ../ex_cmds.c:5500
 #, c-format
 msgid "E670: Mix of help file encodings within a language: %s"
 msgstr ""
 "E670: Tập tin trợ giúp sử dụng nhiều bảng mã khác nhau cho một ngôn ngữ: %s"
 
-#: ../ex_cmds.c:5565
-#, fuzzy, c-format
-msgid "E154: Duplicate tag \"%s\" in file %s/%s"
+#, c-format
+msgid "E154: Duplicate tag \"%s\" in file %s"
 msgstr "E154: Thẻ ghi lặp lại \"%s\" trong tập tin %s"
 
-#: ../ex_cmds.c:5687
 #, c-format
 msgid "E160: Unknown sign command: %s"
 msgstr "E160: Câu lệnh ký hiệu không biết: %s"
 
-#: ../ex_cmds.c:5704
 msgid "E156: Missing sign name"
 msgstr "E156: Thiếu tên ký hiệu"
 
-#: ../ex_cmds.c:5746
 msgid "E612: Too many signs defined"
 msgstr "E612: Định nghĩa quá nhiều ký hiệu"
 
-#: ../ex_cmds.c:5813
 #, c-format
 msgid "E239: Invalid sign text: %s"
 msgstr "E239: Văn bản ký hiệu không thích hợp: %s"
 
-#: ../ex_cmds.c:5844 ../ex_cmds.c:6035
 #, c-format
 msgid "E155: Unknown sign: %s"
 msgstr "E155: Ký hiệu không biết: %s"
 
-#: ../ex_cmds.c:5877
 msgid "E159: Missing sign number"
 msgstr "E159: Thiếu số của ký hiệu"
 
-#: ../ex_cmds.c:5971
 #, c-format
 msgid "E158: Invalid buffer name: %s"
 msgstr "E158: Tên bộ đệm không đúng: %s"
 
-#: ../ex_cmds.c:6008
 #, c-format
-msgid "E157: Invalid sign ID: %"
-msgstr "E157: ID của ký hiệu không đúng: %"
+msgid "E157: Invalid sign ID: %ld"
+msgstr "E157: ID của ký hiệu không đúng: %ld"
+
+msgid " (NOT FOUND)"
+msgstr " (KHÔNG TÌM THẤY)"
 
-#: ../ex_cmds.c:6066
 msgid " (not supported)"
 msgstr " (không được hỗ trợ)"
 
-#: ../ex_cmds.c:6169
 msgid "[Deleted]"
 msgstr "[bị xóa]"
 
-#: ../ex_cmds2.c:139
-msgid "Entering Debug mode.  Type \"cont\" to continue."
-msgstr "Bật chế độ sửa lỗi (Debug). Gõ \"cont\" để tiếp tục."
-
-#: ../ex_cmds2.c:143 ../ex_docmd.c:759
-#, c-format
-msgid "line %: %s"
-msgstr "dòng %: %s"
+msgid "Entering Ex mode.  Type \"visual\" to go to Normal mode."
+msgstr ""
+"Chuyển vào chế độ Ex. Để chuyển về chế độ Thông thường hãy gõ \"visual\""
 
-#: ../ex_cmds2.c:145
-#, c-format
-msgid "cmd: %s"
-msgstr "câu lệnh: %s"
+msgid "E501: At end-of-file"
+msgstr "E501: Ở cuối tập tin"
 
-#: ../ex_cmds2.c:322
-#, c-format
-msgid "Breakpoint in \"%s%s\" line %"
-msgstr "Điểm dừng trên \"%s%s\" dòng %"
+msgid "E169: Command too recursive"
+msgstr "E169: Câu lệnh quá đệ quy"
 
-#: ../ex_cmds2.c:581
 #, c-format
-msgid "E161: Breakpoint not found: %s"
-msgstr "E161: Không tìm thấy điểm dừng: %s"
+msgid "E605: Exception not caught: %s"
+msgstr "E605: Trường hợp đặc biệt không được xử lý: %s"
 
-#: ../ex_cmds2.c:611
-msgid "No breakpoints defined"
-msgstr "Điểm dừng không được xác định"
+msgid "End of sourced file"
+msgstr "Kết thúc tập tin script"
 
-#: ../ex_cmds2.c:617
-#, c-format
-msgid "%3d  %s %s  line %"
-msgstr "%3d  %s %s dòng %"
+msgid "End of function"
+msgstr "Kết thúc của hàm số"
 
-#: ../ex_cmds2.c:942
-msgid "E750: First use \":profile start {fname}\""
-msgstr ""
+msgid "E464: Ambiguous use of user-defined command"
+msgstr "E464: Sự sử dụng không rõ ràng câu lệnh do người dùng định nghĩa"
 
-#: ../ex_cmds2.c:1269
-#, fuzzy, c-format
-msgid "Save changes to \"%s\"?"
-msgstr "Ghi nhớ thay đổi vào \"%.*s\"?"
+msgid "E492: Not an editor command"
+msgstr "E492: Không phải là câu lệnh của trình soạn thảo"
 
-#: ../ex_cmds2.c:1271 ../ex_docmd.c:8851
-msgid "Untitled"
-msgstr "Chưa đặt tên"
+msgid "E493: Backwards range given"
+msgstr "E493: Đưa ra phạm vi ngược lại"
 
-#: ../ex_cmds2.c:1421
-#, c-format
-msgid "E162: No write since last change for buffer \"%s\""
-msgstr "E162: Thay đổi chưa được ghi nhớ trong bộ đệm \"%s\""
+msgid "Backwards range given, OK to swap"
+msgstr "Đưa ra phạm vi ngược lại, thay đổi vị trí hai giới hạn"
 
-#: ../ex_cmds2.c:1480
-msgid "Warning: Entered other buffer unexpectedly (check autocommands)"
-msgstr ""
-"Cảnh báo: Chuyển tới bộ đệm khác không theo ý muốn (hãy kiểm tra câu lệnh tự "
-"động)"
+msgid "E494: Use w or w>>"
+msgstr "E494: Hãy sử dụng w hoặc w>>"
 
-#: ../ex_cmds2.c:1826
-msgid "E163: There is only one file to edit"
-msgstr "E163: Chỉ có một tập tin để soạn thảo"
+msgid "E319: Sorry, the command is not available in this version"
+msgstr "E319: Xin lỗi, câu lệnh này không có trong phiên bản này"
 
-#: ../ex_cmds2.c:1828
-msgid "E164: Cannot go before first file"
-msgstr "E164: Đây là tập tin đầu tiên"
+msgid "E172: Only one file name allowed"
+msgstr "E172: Chỉ cho phép sử dụng một tên tập tin"
 
-#: ../ex_cmds2.c:1830
-msgid "E165: Cannot go beyond last file"
-msgstr "E165: Đây là tập tin cuối cùng"
+msgid "1 more file to edit.  Quit anyway?"
+msgstr "Còn 1 tập tin nữa cần soạn thảo. Thoát?"
 
-#: ../ex_cmds2.c:2175
-#, c-format
-msgid "E666: compiler not supported: %s"
-msgstr "E666: trình biên dịch không được hỗ trợ: %s"
-
-#: ../ex_cmds2.c:2257
-#, c-format
-msgid "Searching for \"%s\" in \"%s\""
-msgstr "Tìm kiếm \"%s\" trong \"%s\""
-
-#: ../ex_cmds2.c:2284
-#, c-format
-msgid "Searching for \"%s\""
-msgstr "Tìm kiếm \"%s\""
-
-#: ../ex_cmds2.c:2307
-#, c-format
-msgid "not found in 'runtimepath': \"%s\""
-msgstr "không tìm thấy trong 'runtimepath': \"%s\""
-
-#: ../ex_cmds2.c:2472
-#, c-format
-msgid "Cannot source a directory: \"%s\""
-msgstr "Không thể thực hiện một thư mục: \"%s\""
-
-#: ../ex_cmds2.c:2518
-#, c-format
-msgid "could not source \"%s\""
-msgstr "không thực hiện được \"%s\""
-
-#: ../ex_cmds2.c:2520
-#, c-format
-msgid "line %: could not source \"%s\""
-msgstr "dòng %: không thực hiện được \"%s\""
-
-#: ../ex_cmds2.c:2535
-#, c-format
-msgid "sourcing \"%s\""
-msgstr "thực hiện \"%s\""
-
-#: ../ex_cmds2.c:2537
-#, c-format
-msgid "line %: sourcing \"%s\""
-msgstr "dòng %: thực hiện \"%s\""
-
-#: ../ex_cmds2.c:2693
-#, c-format
-msgid "finished sourcing %s"
-msgstr "thực hiện xong %s"
-
-#: ../ex_cmds2.c:2765
-#, fuzzy
-msgid "modeline"
-msgstr "Thêm 1 dòng"
-
-#: ../ex_cmds2.c:2767
-#, fuzzy
-msgid "--cmd argument"
-msgstr " vim [các tham số] "
-
-#: ../ex_cmds2.c:2769
-#, fuzzy
-msgid "-c argument"
-msgstr " vim [các tham số] "
-
-#: ../ex_cmds2.c:2771
-msgid "environment variable"
-msgstr ""
-
-#: ../ex_cmds2.c:2773
-#, fuzzy
-msgid "error handler"
-msgstr "Lỗi và sự gián đoạn"
-
-#: ../ex_cmds2.c:3020
-msgid "W15: Warning: Wrong line separator, ^M may be missing"
-msgstr "W15: Cảnh báo: Ký tự phân cách dòng không đúng. Rất có thể thiếu ^M"
-
-#: ../ex_cmds2.c:3139
-msgid "E167: :scriptencoding used outside of a sourced file"
-msgstr "E167: Lệnh :scriptencoding sử dụng ngoài tập tin script"
-
-#: ../ex_cmds2.c:3166
-msgid "E168: :finish used outside of a sourced file"
-msgstr "E168: Lệnh :finish sử dụng ngoài tập tin script"
-
-#: ../ex_cmds2.c:3389
-#, c-format
-msgid "Current %slanguage: \"%s\""
-msgstr "Ngôn ngữ %shiện thời: \"%s\""
-
-#: ../ex_cmds2.c:3404
-#, c-format
-msgid "E197: Cannot set language to \"%s\""
-msgstr "E197: Không thể thay đổi ngôn ngữ thành \"%s\""
-
-#. don't redisplay the window
-#. don't wait for return
-#: ../ex_docmd.c:387
-msgid "Entering Ex mode.  Type \"visual\" to go to Normal mode."
-msgstr ""
-"Chuyển vào chế độ Ex. Để chuyển về chế độ Thông thường hãy gõ \"visual\""
-
-#: ../ex_docmd.c:428
-msgid "E501: At end-of-file"
-msgstr "E501: Ở cuối tập tin"
-
-#: ../ex_docmd.c:513
-msgid "E169: Command too recursive"
-msgstr "E169: Câu lệnh quá đệ quy"
-
-#: ../ex_docmd.c:1006
-#, c-format
-msgid "E605: Exception not caught: %s"
-msgstr "E605: Trường hợp đặc biệt không được xử lý: %s"
-
-#: ../ex_docmd.c:1085
-msgid "End of sourced file"
-msgstr "Kết thúc tập tin script"
-
-#: ../ex_docmd.c:1086
-msgid "End of function"
-msgstr "Kết thúc của hàm số"
-
-#: ../ex_docmd.c:1628
-msgid "E464: Ambiguous use of user-defined command"
-msgstr "E464: Sự sử dụng không rõ ràng câu lệnh do người dùng định nghĩa"
-
-#: ../ex_docmd.c:1638
-msgid "E492: Not an editor command"
-msgstr "E492: Không phải là câu lệnh của trình soạn thảo"
-
-#: ../ex_docmd.c:1729
-msgid "E493: Backwards range given"
-msgstr "E493: Đưa ra phạm vi ngược lại"
-
-#: ../ex_docmd.c:1733
-msgid "Backwards range given, OK to swap"
-msgstr "Đưa ra phạm vi ngược lại, thay đổi vị trí hai giới hạn"
-
-#. append
-#. typed wrong
-#: ../ex_docmd.c:1787
-msgid "E494: Use w or w>>"
-msgstr "E494: Hãy sử dụng w hoặc w>>"
-
-#: ../ex_docmd.c:3454
-msgid "E319: The command is not available in this version"
-msgstr "E319: Xin lỗi, câu lệnh này không có trong phiên bản này"
-
-#: ../ex_docmd.c:3752
-msgid "E172: Only one file name allowed"
-msgstr "E172: Chỉ cho phép sử dụng một tên tập tin"
-
-#: ../ex_docmd.c:4238
-msgid "1 more file to edit.  Quit anyway?"
-msgstr "Còn 1 tập tin nữa cần soạn thảo. Thoát?"
-
-#: ../ex_docmd.c:4242
 #, c-format
 msgid "%d more files to edit.  Quit anyway?"
 msgstr "Còn %d tập tin nữa chưa soạn thảo. Thoát?"
 
-#: ../ex_docmd.c:4248
 msgid "E173: 1 more file to edit"
 msgstr "E173: 1 tập tin nữa chờ soạn thảo."
 
-#: ../ex_docmd.c:4250
 #, c-format
-msgid "E173: % more files to edit"
-msgstr "E173: % tập tin nữa chưa soạn thảo."
+msgid "E173: %ld more files to edit"
+msgstr "E173: %ld tập tin nữa chưa soạn thảo."
 
-#: ../ex_docmd.c:4320
 msgid "E174: Command already exists: add ! to replace it"
 msgstr "E174: Đã có câu lệnh: Thêm ! để thay thế"
 
-#: ../ex_docmd.c:4432
 msgid ""
 "\n"
 "    Name        Args Range Complete  Definition"
@@ -1641,352 +957,252 @@ msgstr ""
 "\n"
 "    Tên\t\tTham_số Phạm_vi Phần_phụ Định_nghĩa"
 
-#: ../ex_docmd.c:4516
 msgid "No user-defined commands found"
 msgstr "Không tìm thấy câu lệnh do người dùng định nghĩa"
 
-#: ../ex_docmd.c:4538
 msgid "E175: No attribute specified"
 msgstr "E175: Không có tham số được chỉ ra"
 
-#: ../ex_docmd.c:4583
 msgid "E176: Invalid number of arguments"
 msgstr "E176: Số lượng tham số không đúng"
 
-#: ../ex_docmd.c:4594
 msgid "E177: Count cannot be specified twice"
 msgstr "E177: Số đếm không thể được chỉ ra hai lần"
 
-#: ../ex_docmd.c:4603
 msgid "E178: Invalid default value for count"
 msgstr "E178: Giá trị của số đếm theo mặc định không đúng"
 
-#: ../ex_docmd.c:4625
-#, fuzzy
-msgid "E179: argument required for -complete"
+# TODO: Capitalise first word of message?
+msgid "E179: Argument required for complete"
 msgstr "E179: yêu cầu đưa ra tham số để kết thúc"
 
-#: ../ex_docmd.c:4635
+#, c-format
+msgid "E180: Invalid complete value: %s"
+msgstr "E180: Giá trị phần phụ không đúng: %s"
+
+msgid "E468: Completion argument only allowed for custom completion"
+msgstr ""
+"E468: Tham số tự động kết thúc chỉ cho phép sử dụng với phần phụ đặc biệt"
+
+msgid "E467: Custom completion requires a function argument"
+msgstr "E467: Phần phục đặc biệt yêu cầu một tham số của hàm"
+
 #, c-format
 msgid "E181: Invalid attribute: %s"
 msgstr "E181: Thuộc tính không đúng: %s"
 
-#: ../ex_docmd.c:4678
 msgid "E182: Invalid command name"
 msgstr "E182: Tên câu lệnh không đúng"
 
-#: ../ex_docmd.c:4691
 msgid "E183: User defined commands must start with an uppercase letter"
 msgstr "E183: Câu lệnh người dùng định nghĩa phải bắt đầu với một ký tự hoa"
 
-#: ../ex_docmd.c:4696
-#, fuzzy
-msgid "E841: Reserved name, cannot be used for user defined command"
-msgstr "E464: Sự sử dụng không rõ ràng câu lệnh do người dùng định nghĩa"
-
-#: ../ex_docmd.c:4751
 #, c-format
 msgid "E184: No such user-defined command: %s"
 msgstr "E184: Không có câu lệnh người dùng định nghĩa như vậy: %s"
 
-#: ../ex_docmd.c:5219
 #, c-format
-msgid "E180: Invalid complete value: %s"
-msgstr "E180: Giá trị phần phụ không đúng: %s"
-
-#: ../ex_docmd.c:5225
-msgid "E468: Completion argument only allowed for custom completion"
-msgstr ""
-"E468: Tham số tự động kết thúc chỉ cho phép sử dụng với phần phụ đặc biệt"
-
-#: ../ex_docmd.c:5231
-msgid "E467: Custom completion requires a function argument"
-msgstr "E467: Phần phục đặc biệt yêu cầu một tham số của hàm"
-
-#: ../ex_docmd.c:5257
-#, fuzzy, c-format
-msgid "E185: Cannot find color scheme '%s'"
+msgid "E185: Cannot find color scheme %s"
 msgstr "E185: Không tin thấy sơ đồ màu sắc %s"
 
-#: ../ex_docmd.c:5263
 msgid "Greetings, Vim user!"
 msgstr "Xin chào người dùng Vim!"
 
-#: ../ex_docmd.c:5431
-#, fuzzy
-msgid "E784: Cannot close last tab page"
-msgstr "E444: Không được đóng cửa sổ cuối cùng"
-
-#: ../ex_docmd.c:5462
-#, fuzzy
-msgid "Already only one tab page"
-msgstr "Chỉ có một cửa sổ"
-
-#: ../ex_docmd.c:6004
-#, fuzzy, c-format
-msgid "Tab page %d"
-msgstr "Trang %d"
+msgid "Edit File in new window"
+msgstr "Soạn thảo tập tin trong cửa sổ mới"
 
-#: ../ex_docmd.c:6295
 msgid "No swap file"
 msgstr "Không có tập tin swap"
 
-#: ../ex_docmd.c:6478
-#, fuzzy
-msgid "E747: Cannot change directory, buffer is modified (add ! to override)"
-msgstr ""
-"E509: Không tạo được tập tin lưu trữ (thêm ! để bỏ qua việc kiểm tra lại)"
+msgid "Append File"
+msgstr "Thêm tập tin"
 
-#: ../ex_docmd.c:6485
 msgid "E186: No previous directory"
 msgstr "E186: Không có thư mục trước"
 
-#: ../ex_docmd.c:6530
 msgid "E187: Unknown"
 msgstr "E187: Không rõ"
 
-#: ../ex_docmd.c:6610
 msgid "E465: :winsize requires two number arguments"
 msgstr "E465: câu lệnh :winsize yêu cầu hai tham số bằng số"
 
-#: ../ex_docmd.c:6655
+#, c-format
+msgid "Window position: X %d, Y %d"
+msgstr "Vị trí cửa sổ: X %d, Y %d"
+
 msgid "E188: Obtaining window position not implemented for this platform"
 msgstr "E188: Trên hệ thống này việc xác định vị trí cửa sổ không làm việc"
 
-#: ../ex_docmd.c:6662
 msgid "E466: :winpos requires two number arguments"
 msgstr "E466: câu lệnh :winpos yêu câu hai tham số bằng số"
 
-#: ../ex_docmd.c:7241
-#, fuzzy, c-format
-msgid "E739: Cannot create directory: %s"
-msgstr "Không thể thực hiện một thư mục: \"%s\""
+msgid "Save Redirection"
+msgstr "Chuyển hướng ghi nhớ"
+
+msgid "Save View"
+msgstr "Ghi nhớ vẻ ngoài"
+
+msgid "Save Session"
+msgstr "Ghi nhớ buổi làm việc"
+
+msgid "Save Setup"
+msgstr "Ghi nhớ cấu hình"
 
-#: ../ex_docmd.c:7268
 #, c-format
 msgid "E189: \"%s\" exists (add ! to override)"
 msgstr "E189: \"%s\" đã có (thêm !, để ghi đè)"
 
-#: ../ex_docmd.c:7273
 #, c-format
 msgid "E190: Cannot open \"%s\" for writing"
 msgstr "E190: Không mở được \"%s\" để ghi nhớ"
 
-#. set mark
-#: ../ex_docmd.c:7294
 msgid "E191: Argument must be a letter or forward/backward quote"
 msgstr "E191: Tham số phải là một chữ cái hoặc dấu ngoặc thẳng/ngược"
 
-#: ../ex_docmd.c:7333
 msgid "E192: Recursive use of :normal too deep"
 msgstr "E192: Sử dụng đệ quy lệnh :normal quá sâu"
 
-#: ../ex_docmd.c:7807
 msgid "E194: No alternate file name to substitute for '#'"
 msgstr "E194: Không có tên tập tin tương đương để thay thế '#'"
 
-#: ../ex_docmd.c:7841
-msgid "E495: no autocommand file name to substitute for \"\""
+# TODO: Capitalise first word of message?
+msgid "E495: No autocommand file name to substitute for \"\""
 msgstr "E495: Không có tên tập tin câu lệnh tự động để thay thế \"\""
 
-#: ../ex_docmd.c:7850
-msgid "E496: no autocommand buffer number to substitute for \"\""
+# TODO: Capitalise first word of message?
+msgid "E496: No autocommand buffer number to substitute for \"\""
 msgstr ""
 "E496: Không có số thứ tự bộ đệm câu lệnh tự động để thay thế \"\""
 
-#: ../ex_docmd.c:7861
-msgid "E497: no autocommand match name to substitute for \"\""
+# TODO: Capitalise first word of message?
+msgid "E497: No autocommand match name to substitute for \"\""
 msgstr "E497: Không có tên tương ứng câu lệnh tự động để thay thế \"\""
 
-#: ../ex_docmd.c:7870
-msgid "E498: no :source file name to substitute for \"\""
-msgstr "E498: không có tên tập tin :source để thay thế \"\""
-
-#: ../ex_docmd.c:7876
-#, fuzzy
-msgid "E842: no line number to use for \"\""
+# TODO: Capitalise first word of message?
+msgid "E498: No :source file name to substitute for \"\""
 msgstr "E498: không có tên tập tin :source để thay thế \"\""
 
-#: ../ex_docmd.c:7903
-#, fuzzy, c-format
+#, no-c-format
 msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
 msgstr "E499: Tên tập tin rỗng cho '%' hoặc '#', chỉ làm việc với \":p:h\""
 
-#: ../ex_docmd.c:7905
 msgid "E500: Evaluates to an empty string"
 msgstr "E500: Kết quả của biểu thức là một chuỗi rỗng"
 
-#: ../ex_docmd.c:8838
 msgid "E195: Cannot open viminfo file for reading"
 msgstr "E195: Không thể mở tập tin viminfo để đọc"
 
-#: ../ex_eval.c:464
+msgid "E196: No digraphs in this version"
+msgstr "E196: Trong phiên bản này chữ ghép không được hỗ trợ"
+
 msgid "E608: Cannot :throw exceptions with 'Vim' prefix"
 msgstr ""
 "E608: Không thể thực hiện lệnh :throw cho những ngoại lệ với tiền tố 'Vim'"
 
-#. always scroll up, don't overwrite
-#: ../ex_eval.c:496
 #, c-format
 msgid "Exception thrown: %s"
 msgstr "Trường hợp ngoại lệ: %s"
 
-#: ../ex_eval.c:545
 #, c-format
 msgid "Exception finished: %s"
 msgstr "Kết thúc việc xử lý trường hợp ngoại lệ: %s"
 
-#: ../ex_eval.c:546
 #, c-format
 msgid "Exception discarded: %s"
 msgstr "Trường hợp ngoại lệ bị bỏ qua: %s"
 
-#: ../ex_eval.c:588 ../ex_eval.c:634
 #, c-format
-msgid "%s, line %"
-msgstr "%s, dòng %"
+msgid "%s, line %ld"
+msgstr "%s, dòng %ld"
 
-#. always scroll up, don't overwrite
-#: ../ex_eval.c:608
 #, c-format
 msgid "Exception caught: %s"
 msgstr "Xử lý trường hợp ngoại lệ: %s"
 
-#: ../ex_eval.c:676
 #, c-format
 msgid "%s made pending"
 msgstr "%s thực hiện việc chờ đợi"
 
-#: ../ex_eval.c:679
 #, c-format
 msgid "%s resumed"
 msgstr "%s được phục hồi lại"
 
-#: ../ex_eval.c:683
 #, c-format
 msgid "%s discarded"
 msgstr "%s bị bỏ qua"
 
-#: ../ex_eval.c:708
 msgid "Exception"
 msgstr "Trường hợp ngoại lệ"
 
-#: ../ex_eval.c:713
 msgid "Error and interrupt"
 msgstr "Lỗi và sự gián đoạn"
 
-#: ../ex_eval.c:715
 msgid "Error"
 msgstr "Lỗi"
 
-#. if (pending & CSTP_INTERRUPT)
-#: ../ex_eval.c:717
 msgid "Interrupt"
 msgstr "Sự gián đoạn"
 
-#: ../ex_eval.c:795
+# TODO: Capitalise first word of message?
 msgid "E579: :if nesting too deep"
 msgstr "E579: :if xếp lồng vào nhau quá sâu"
 
-#: ../ex_eval.c:830
 msgid "E580: :endif without :if"
 msgstr "E580: :endif không có :if"
 
-#: ../ex_eval.c:873
 msgid "E581: :else without :if"
 msgstr "E581: :else không có :if"
 
-#: ../ex_eval.c:876
 msgid "E582: :elseif without :if"
 msgstr "E582: :elseif không có :if"
 
-#: ../ex_eval.c:880
-msgid "E583: multiple :else"
+# TODO: Capitalise first word of message?
+msgid "E583: Multiple :else"
 msgstr "E583: phát hiện vài :else"
 
-#: ../ex_eval.c:883
 msgid "E584: :elseif after :else"
 msgstr "E584: :elseif sau :else"
 
-#: ../ex_eval.c:941
-#, fuzzy
-msgid "E585: :while/:for nesting too deep"
+msgid "E585: :while nesting too deep"
 msgstr "E585: :while xếp lồng vào nhau quá sâu"
 
-#: ../ex_eval.c:1028
-#, fuzzy
-msgid "E586: :continue without :while or :for"
+msgid "E586: :continue without :while"
 msgstr "E586: :continue không có :while"
 
-#: ../ex_eval.c:1061
-#, fuzzy
-msgid "E587: :break without :while or :for"
+msgid "E587: :break without :while"
 msgstr "E587: :break không có :while"
 
-#: ../ex_eval.c:1102
-#, fuzzy
-msgid "E732: Using :endfor with :while"
-msgstr "E170: Thiếu câu lệnh :endwhile"
-
-#: ../ex_eval.c:1104
-#, fuzzy
-msgid "E733: Using :endwhile with :for"
-msgstr "E170: Thiếu câu lệnh :endwhile"
-
-#: ../ex_eval.c:1247
 msgid "E601: :try nesting too deep"
 msgstr "E601: :try xếp lồng vào nhau quá sâu"
 
-#: ../ex_eval.c:1317
 msgid "E603: :catch without :try"
 msgstr "E603: :catch không có :try"
 
-#. Give up for a ":catch" after ":finally" and ignore it.
-#. * Just parse.
-#: ../ex_eval.c:1332
 msgid "E604: :catch after :finally"
 msgstr "E604: :catch đứng sau :finally"
 
-#: ../ex_eval.c:1451
 msgid "E606: :finally without :try"
 msgstr "E606: :finally không có :try"
 
-#. Give up for a multiple ":finally" and ignore it.
-#: ../ex_eval.c:1467
-msgid "E607: multiple :finally"
+# TODO: Capitalise first word of message?
+msgid "E607: Multiple :finally"
 msgstr "E607: phát hiện vài :finally"
 
-#: ../ex_eval.c:1571
 msgid "E602: :endtry without :try"
 msgstr "E602: :endtry không có :try"
 
-#: ../ex_eval.c:2026
 msgid "E193: :endfunction not inside a function"
 msgstr "E193: lệnh :endfunction chỉ được sử dụng trong một hàm số"
 
-#: ../ex_getln.c:1643
-#, fuzzy
-msgid "E788: Not allowed to edit another buffer now"
-msgstr "E48: Không cho phép trong hộp cát (sandbox)"
-
-#: ../ex_getln.c:1656
-#, fuzzy
-msgid "E811: Not allowed to change buffer information now"
-msgstr "E94: Không có bộ đệm tương ứng với %s"
-
-#: ../ex_getln.c:3178
 msgid "tagname"
 msgstr "tên thẻ ghi"
 
-#: ../ex_getln.c:3181
 msgid " kind file\n"
 msgstr " loại tập tin\n"
 
-#: ../ex_getln.c:4799
 msgid "'history' option is zero"
 msgstr "giá trị của tùy chọn 'history' bằng không"
 
-#: ../ex_getln.c:5046
 #, c-format
 msgid ""
 "\n"
@@ -1995,312 +1211,201 @@ msgstr ""
 "\n"
 "# %s, Lịch sử (bắt đầu từ mới nhất tới cũ nhất):\n"
 
-#: ../ex_getln.c:5047
 msgid "Command Line"
 msgstr "Dòng lệnh"
 
-#: ../ex_getln.c:5048
 msgid "Search String"
 msgstr "Chuỗi tìm kiếm"
 
-#: ../ex_getln.c:5049
 msgid "Expression"
 msgstr "Biểu thức"
 
-#: ../ex_getln.c:5050
 msgid "Input Line"
 msgstr "Dòng nhập"
 
-#: ../ex_getln.c:5117
 msgid "E198: cmd_pchar beyond the command length"
 msgstr "E198: cmd_pchar lớn hơn chiều dài câu lệnh"
 
-#: ../ex_getln.c:5279
 msgid "E199: Active window or buffer deleted"
 msgstr "E199: Cửa sổ hoặc bộ đệm hoạt động bị xóa"
 
-#: ../file_search.c:203
-msgid "E854: path too long for completion"
-msgstr ""
-
-#: ../file_search.c:446
-#, c-format
-msgid ""
-"E343: Invalid path: '**[number]' must be at the end of the path or be "
-"followed by '%s'."
-msgstr ""
-"E343: Đường dẫn đưa ra không đúng: '**[số]' phải ở cuối đường dẫn hoặc theo "
-"sau bởi '%s'"
-
-#: ../file_search.c:1505
-#, c-format
-msgid "E344: Can't find directory \"%s\" in cdpath"
-msgstr "E344: Không tìm thấy thư mục \"%s\" để chuyển thư mục"
-
-#: ../file_search.c:1508
-#, c-format
-msgid "E345: Can't find file \"%s\" in path"
-msgstr "E345: Không tìm thấy tập tin \"%s\" trong đường dẫn"
-
-#: ../file_search.c:1512
-#, c-format
-msgid "E346: No more directory \"%s\" found in cdpath"
-msgstr "E346: Trong đường dẫn thay đổi thư mục không còn có thư mục \"%s\" nữa"
-
-#: ../file_search.c:1515
-#, c-format
-msgid "E347: No more file \"%s\" found in path"
-msgstr "E347: Trong đường dẫn path không còn có tập tin \"%s\" nữa"
-
-#: ../fileio.c:137
-#, fuzzy
-msgid "E812: Autocommands changed buffer or buffer name"
-msgstr "E135: Các lệnh tự động *Filter* không được thay đổi bộ đệm hiện thời"
-
-#: ../fileio.c:368
 msgid "Illegal file name"
 msgstr "Tên tập tin không cho phép"
 
-#: ../fileio.c:395 ../fileio.c:476 ../fileio.c:2543 ../fileio.c:2578
 msgid "is a directory"
 msgstr "là một thư mục"
 
-#: ../fileio.c:397
 msgid "is not a file"
 msgstr "không phải là một tập tin"
 
-#: ../fileio.c:508 ../fileio.c:3522
 msgid "[New File]"
 msgstr "[Tập tin mới]"
 
-#: ../fileio.c:511
-msgid "[New DIRECTORY]"
-msgstr ""
-
-#: ../fileio.c:529 ../fileio.c:532
-msgid "[File too big]"
-msgstr ""
-
-#: ../fileio.c:534
 msgid "[Permission Denied]"
 msgstr "[Truy cập bị từ chối]"
 
-#: ../fileio.c:653
 msgid "E200: *ReadPre autocommands made the file unreadable"
 msgstr ""
 "E200: Câu lệnh tự động *ReadPre làm cho tập tin trở thành không thể đọc"
 
-#: ../fileio.c:655
 msgid "E201: *ReadPre autocommands must not change current buffer"
 msgstr "E201: Câu lệnh tự động *ReadPre không được thay đổi bộ đệm hoạt động"
 
-#: ../fileio.c:672
-msgid "Nvim: Reading from stdin...\n"
+msgid "Vim: Reading from stdin...\n"
 msgstr "Vim: Đọc từ đầu vào tiêu chuẩn stdin...\n"
 
-#. Re-opening the original file failed!
-#: ../fileio.c:909
+msgid "Reading from stdin..."
+msgstr "Đọc từ đầu vào tiêu chuẩn stdin..."
+
 msgid "E202: Conversion made file unreadable!"
 msgstr "E202: Sự biến đổi làm cho tập tin trở thành không thể đọc!"
 
-#. fifo or socket
-#: ../fileio.c:1782
 msgid "[fifo/socket]"
 msgstr "[fifo/socket]"
 
-#. fifo
-#: ../fileio.c:1788
 msgid "[fifo]"
 msgstr "[fifo]"
 
-#. or socket
-#: ../fileio.c:1794
 msgid "[socket]"
 msgstr "[socket]"
 
-#. or character special
-#: ../fileio.c:1801
-#, fuzzy
-msgid "[character special]"
-msgstr "1 ký tự"
+msgid "[RO]"
+msgstr "[Chỉ đọc]"
 
-#: ../fileio.c:1815
 msgid "[CR missing]"
 msgstr "[thiếu ký tự CR]"
 
-#: ../fileio.c:1819
+msgid "[NL found]"
+msgstr "[tìm thấy ký tự NL]"
+
 msgid "[long lines split]"
 msgstr "[dòng dài được chia nhỏ]"
 
-#: ../fileio.c:1823 ../fileio.c:3512
 msgid "[NOT converted]"
 msgstr "[KHÔNG được chuyển đổi]"
 
-#: ../fileio.c:1826 ../fileio.c:3515
 msgid "[converted]"
 msgstr "[đã chuyển bảng mã]"
 
-#: ../fileio.c:1831
-#, fuzzy, c-format
-msgid "[CONVERSION ERROR in line %]"
-msgstr "[BYTE KHÔNG CHO PHÉP trên dòng %]"
+msgid "[crypted]"
+msgstr "[đã mã hóa]"
+
+msgid "[CONVERSION ERROR]"
+msgstr "[LỖI CHUYỂN BẢNG MÃ]"
 
-#: ../fileio.c:1835
 #, c-format
-msgid "[ILLEGAL BYTE in line %]"
-msgstr "[BYTE KHÔNG CHO PHÉP trên dòng %]"
+msgid "[ILLEGAL BYTE in line %ld]"
+msgstr "[BYTE KHÔNG CHO PHÉP trên dòng %ld]"
 
-#: ../fileio.c:1838
 msgid "[READ ERRORS]"
 msgstr "[LỖI ĐỌC]"
 
-#: ../fileio.c:2104
 msgid "Can't find temp file for conversion"
 msgstr "Không tìm thấy tập tin tạm thời (temp) để chuyển bảng mã"
 
-#: ../fileio.c:2110
 msgid "Conversion with 'charconvert' failed"
 msgstr "Chuyển đổi nhờ 'charconvert' không được thực hiện"
 
-#: ../fileio.c:2113
 msgid "can't read output of 'charconvert'"
 msgstr "không đọc được đầu ra của 'charconvert'"
 
-#: ../fileio.c:2437
-#, fuzzy
-msgid "E676: No matching autocommands for acwrite buffer"
-msgstr "Không có câu lệnh tự động tương ứng"
-
-#: ../fileio.c:2466
 msgid "E203: Autocommands deleted or unloaded buffer to be written"
 msgstr "E203: Câu lệnh tự động đã xóa hoặc bỏ nạp bộ đệm cần ghi nhớ"
 
-#: ../fileio.c:2486
 msgid "E204: Autocommand changed number of lines in unexpected way"
 msgstr "E204: Câu lệnh tự động đã thay đổ số dòng theo cách không mong muốn"
 
-#: ../fileio.c:2548 ../fileio.c:2565
+msgid "NetBeans disallows writes of unmodified buffers"
+msgstr "NetBeans không cho phép ghi nhớ bộ đệm chưa có thay đổi nào"
+
+msgid "Partial writes disallowed for NetBeans buffers"
+msgstr "Ghi nhớ một phần bộ đệm NetBeans không được cho phép"
+
 msgid "is not a file or writable device"
 msgstr "không phải là một tập tin thay một thiết bị có thể ghi nhớ"
 
-#: ../fileio.c:2601
 msgid "is read-only (add ! to override)"
 msgstr "là tập tin chỉ đọc (thêm ! để ghi nhớ bằng mọi giá)"
 
-#: ../fileio.c:2886
 msgid "E506: Can't write to backup file (add ! to override)"
 msgstr ""
 "E506: Không thể ghi nhớ vào tập tin lưu trữ (thêm ! để ghi nhớ bằng mọi giá"
 
-#: ../fileio.c:2898
 msgid "E507: Close error for backup file (add ! to override)"
 msgstr "E507: Lỗi đóng tập tin lưu trữ (thêm ! để bỏ qua việc kiểm tra lại)"
 
-#: ../fileio.c:2901
 msgid "E508: Can't read file for backup (add ! to override)"
 msgstr ""
 "E508: Không đọc được tập tin lưu trữ (thêm ! để bỏ qua việc kiểm tra lại)"
 
-#: ../fileio.c:2923
 msgid "E509: Cannot create backup file (add ! to override)"
 msgstr ""
 "E509: Không tạo được tập tin lưu trữ (thêm ! để bỏ qua việc kiểm tra lại)"
 
-#: ../fileio.c:3008
 msgid "E510: Can't make backup file (add ! to override)"
 msgstr ""
 "E510: Không tạo được tập tin lưu trữ (thêm ! để bỏ qua việc kiểm tra lại)"
 
-#. Can't write without a tempfile!
-#: ../fileio.c:3121
 msgid "E214: Can't find temp file for writing"
 msgstr "E214: Không tìm thấy tập tin tạm thời (temp) để ghi nhớ"
 
-#: ../fileio.c:3134
 msgid "E213: Cannot convert (add ! to write without conversion)"
 msgstr ""
 "E213: Không thể chuyển đổi bảng mã (thêm ! để ghi nhớ mà không chuyển đổi)"
 
-#: ../fileio.c:3169
 msgid "E166: Can't open linked file for writing"
 msgstr "E166: Không thể mở tập tin liên kết để ghi nhớ"
 
-#: ../fileio.c:3173
 msgid "E212: Can't open file for writing"
 msgstr "E212: Không thể mở tập tin để ghi nhớ"
 
-#: ../fileio.c:3363
 msgid "E667: Fsync failed"
 msgstr "E667: Không thực hiện thành công hàm số fsync()"
 
-#: ../fileio.c:3398
 msgid "E512: Close failed"
 msgstr "E512: Thao tác đóng không thành công"
 
-#: ../fileio.c:3436
-#, fuzzy
-msgid "E513: write error, conversion failed (make 'fenc' empty to override)"
+# TODO: Capitalise first word of message?
+msgid "E513: Write error, conversion failed"
 msgstr "E513: Lỗi ghi nhớ, biến đổi không thành công"
 
-#: ../fileio.c:3441
-#, c-format
-msgid ""
-"E513: write error, conversion failed in line % (make 'fenc' empty to "
-"override)"
-msgstr ""
-
-#: ../fileio.c:3448
-msgid "E514: write error (file system full?)"
+# TODO: Capitalise first word of message?
+msgid "E514: Write error (file system full?)"
 msgstr "E514: lỗi ghi nhớ (không còn chỗ trống?)"
 
-#: ../fileio.c:3506
 msgid " CONVERSION ERROR"
 msgstr " LỖI BIẾN ĐỔI"
 
-#: ../fileio.c:3509
-#, fuzzy, c-format
-msgid " in line %;"
-msgstr "dòng %"
-
-#: ../fileio.c:3519
 msgid "[Device]"
 msgstr "[Thiết bị]"
 
-#: ../fileio.c:3522
 msgid "[New]"
 msgstr "[Mới]"
 
-#: ../fileio.c:3535
 msgid " [a]"
 msgstr " [a]"
 
-#: ../fileio.c:3535
 msgid " appended"
 msgstr " đã thêm"
 
-#: ../fileio.c:3537
 msgid " [w]"
 msgstr " [w]"
 
-#: ../fileio.c:3537
 msgid " written"
 msgstr " đã ghi"
 
-#: ../fileio.c:3579
 msgid "E205: Patchmode: can't save original file"
 msgstr "E205: Chế độ vá lỗi (patch): không thể ghi nhớ tập tin gốc"
 
-#: ../fileio.c:3602
-msgid "E206: patchmode: can't touch empty original file"
+# TODO: Capitalise first word of message?
+msgid "E206: Patchmode: can't touch empty original file"
 msgstr ""
 "E206: Chế độ vá lỗi (patch): không thể thay đổi tham số của tập tin gốc "
 "trống rỗng"
 
-#: ../fileio.c:3616
 msgid "E207: Can't delete backup file"
 msgstr "E207: Không thể xóa tập tin lưu trữ (backup)"
 
-#: ../fileio.c:3672
 msgid ""
 "\n"
 "WARNING: Original file may be lost or damaged\n"
@@ -2308,97 +1413,73 @@ msgstr ""
 "\n"
 "CẢNH BÁO: Tập tin gốc có thể bị mất hoặc bị hỏng\n"
 
-#: ../fileio.c:3675
 msgid "don't quit the editor until the file is successfully written!"
 msgstr ""
 "đừng thoát khởi trình soạn thảo, khi tập tin còn chưa được ghi nhớ thành cồng"
 
-#: ../fileio.c:3795
 msgid "[dos]"
 msgstr "[dos]"
 
-#: ../fileio.c:3795
 msgid "[dos format]"
 msgstr "[định dạng dos]"
 
-#: ../fileio.c:3801
 msgid "[mac]"
 msgstr "[mac]"
 
-#: ../fileio.c:3801
 msgid "[mac format]"
 msgstr "[định dạng mac]"
 
-#: ../fileio.c:3807
 msgid "[unix]"
 msgstr "[unix]"
 
-#: ../fileio.c:3807
 msgid "[unix format]"
 msgstr "[định dạng unix]"
 
-#: ../fileio.c:3831
 msgid "1 line, "
 msgstr "1 dòng, "
 
-#: ../fileio.c:3833
 #, c-format
-msgid "% lines, "
-msgstr "% dòng, "
+msgid "%ld lines, "
+msgstr "%ld dòng, "
 
-#: ../fileio.c:3836
 msgid "1 character"
 msgstr "1 ký tự"
 
-#: ../fileio.c:3838
 #, c-format
-msgid "% characters"
-msgstr "% ký tự"
+msgid "%ld characters"
+msgstr "%ld ký tự"
 
-#: ../fileio.c:3849
 msgid "[noeol]"
 msgstr "[noeol]"
 
-#: ../fileio.c:3849
 msgid "[Incomplete last line]"
 msgstr "[Dòng cuối cùng không đầy đủ]"
 
-#. don't overwrite messages here
-#. must give this prompt
-#. don't use emsg() here, don't want to flush the buffers
-#: ../fileio.c:3865
 msgid "WARNING: The file has been changed since reading it!!!"
 msgstr "CẢNH BÁO: Tập tin đã thay đổi so với thời điểm đọc!!!"
 
-#: ../fileio.c:3867
 msgid "Do you really want to write to it"
 msgstr "Bạn có chắc muốn ghi nhớ vào tập tin này"
 
-#: ../fileio.c:4648
 #, c-format
 msgid "E208: Error writing to \"%s\""
 msgstr "E208: Lỗi ghi nhớ vào \"%s\""
 
-#: ../fileio.c:4655
 #, c-format
 msgid "E209: Error closing \"%s\""
 msgstr "E209: Lỗi đóng \"%s\""
 
-#: ../fileio.c:4657
 #, c-format
 msgid "E210: Error reading \"%s\""
 msgstr "E210: Lỗi đọc \"%s\""
 
-#: ../fileio.c:4883
 msgid "E246: FileChangedShell autocommand deleted buffer"
 msgstr "E246: Bộ đệm bị xóa khi thực hiện câu lệnh tự động FileChangedShell"
 
-#: ../fileio.c:4894
-#, fuzzy, c-format
-msgid "E211: File \"%s\" no longer available"
+#, c-format
+msgid "E211: Warning: File \"%s\" no longer available"
 msgstr "E211: Cảnh báo: Tập tin \"%s\" không còn truy cập được nữa"
 
-#: ../fileio.c:4906
 #, c-format
 msgid ""
 "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as "
@@ -2407,44 +1488,28 @@ msgstr ""
 "W12: Cảnh báo: Tập tin \"%s\" và bộ đệm Vim đã thay đổi không phụ thuộc vào "
 "nhau"
 
-#: ../fileio.c:4907
-#, fuzzy
-msgid "See \":help W12\" for more info."
-msgstr "Hãy xem thông tin chi tiết trong \":help W11\"."
-
-#: ../fileio.c:4910
 #, c-format
 msgid "W11: Warning: File \"%s\" has changed since editing started"
 msgstr ""
 "W11: Cảnh báo: Tập tin \"%s\" đã thay đổi sau khi việc soạn thảo bắt đầu"
 
-#: ../fileio.c:4911
-msgid "See \":help W11\" for more info."
-msgstr "Hãy xem thông tin chi tiết trong \":help W11\"."
-
-#: ../fileio.c:4914
 #, c-format
 msgid "W16: Warning: Mode of file \"%s\" has changed since editing started"
 msgstr ""
 "W16: Cảnh báo: chế độ truy cập tới tập tin \"%s\" đã thay đổi sau khi bắt "
 "đầu soạn thảo"
 
-#: ../fileio.c:4915
-#, fuzzy
-msgid "See \":help W16\" for more info."
-msgstr "Hãy xem thông tin chi tiết trong \":help W11\"."
-
-#: ../fileio.c:4927
 #, c-format
 msgid "W13: Warning: File \"%s\" has been created after editing started"
 msgstr ""
 "W13: Cảnh báo: tập tin \"%s\" được tạo ra sau khi việc soạn thảo bắt đầu"
 
-#: ../fileio.c:4947
+msgid "See \":help W11\" for more info."
+msgstr "Hãy xem thông tin chi tiết trong \":help W11\"."
+
 msgid "Warning"
 msgstr "Cảnh báo"
 
-#: ../fileio.c:4948
 msgid ""
 "&OK\n"
 "&Load File"
@@ -2452,48 +1517,33 @@ msgstr ""
 "&OK\n"
 "&Nạp tập tin"
 
-#: ../fileio.c:5065
 #, c-format
 msgid "E462: Could not prepare for reloading \"%s\""
 msgstr "E462: Không thể chuẩn bị để nạp lại \"%s\""
 
-#: ../fileio.c:5078
 #, c-format
 msgid "E321: Could not reload \"%s\""
 msgstr "E321: Không thể nạp lại \"%s\""
 
-#: ../fileio.c:5601
 msgid "--Deleted--"
 msgstr "--Bị xóa--"
 
-#: ../fileio.c:5732
-#, c-format
-msgid "auto-removing autocommand: %s "
-msgstr ""
-
-#. the group doesn't exist
-#: ../fileio.c:5772
 #, c-format
 msgid "E367: No such group: \"%s\""
 msgstr "E367: Nhóm \"%s\" không tồn tại"
 
-#: ../fileio.c:5897
 #, c-format
 msgid "E215: Illegal character after *: %s"
 msgstr "E215: Ký tự không cho phép sau *: %s"
 
-#: ../fileio.c:5905
 #, c-format
 msgid "E216: No such event: %s"
 msgstr "E216: Sự kiện không có thật: %s"
 
-#: ../fileio.c:5907
 #, c-format
 msgid "E216: No such group or event: %s"
 msgstr "E216: Nhóm hoặc sự kiện không có thật: %s"
 
-#. Highlight title
-#: ../fileio.c:6090
 msgid ""
 "\n"
 "--- Autocommands ---"
@@ -2501,681 +1551,400 @@ msgstr ""
 "\n"
 "--- Câu lệnh tự động ---"
 
-#: ../fileio.c:6293
-#, fuzzy, c-format
-msgid "E680: : invalid buffer number "
-msgstr "số của bộ đệm không đúng"
-
-#: ../fileio.c:6370
 msgid "E217: Can't execute autocommands for ALL events"
 msgstr "E217: Không thể thực hiện câu lệnh tự động cho MỌI sự kiện"
 
-#: ../fileio.c:6393
 msgid "No matching autocommands"
 msgstr "Không có câu lệnh tự động tương ứng"
 
-#: ../fileio.c:6831
-msgid "E218: autocommand nesting too deep"
+# TODO: Capitalise first word of message?
+msgid "E218: Autocommand nesting too deep"
 msgstr "E218: câu lệnh tự động xếp lồng vào nhau quá xâu"
 
-#: ../fileio.c:7143
 #, c-format
 msgid "%s Autocommands for \"%s\""
 msgstr "%s câu lệnh tự động cho \"%s\""
 
-#: ../fileio.c:7149
 #, c-format
 msgid "Executing %s"
 msgstr "Thực hiện %s"
 
-#: ../fileio.c:7211
 #, c-format
 msgid "autocommand %s"
 msgstr "câu lệnh tự động %s"
 
-#: ../fileio.c:7795
 msgid "E219: Missing {."
 msgstr "E219: Thiếu {."
 
-#: ../fileio.c:7797
 msgid "E220: Missing }."
 msgstr "E220: Thiếu }."
 
-#: ../fold.c:93
 msgid "E490: No fold found"
 msgstr "E490: Không tìm thấy nếp gấp"
 
-#: ../fold.c:544
 msgid "E350: Cannot create fold with current 'foldmethod'"
 msgstr ""
 "E350: Không thể tạo nếp gấp với giá trị hiện thời của tùy chọn 'foldmethod'"
 
-#: ../fold.c:546
 msgid "E351: Cannot delete fold with current 'foldmethod'"
 msgstr ""
 "E351: Không thể xóa nếp gấp với giá trị hiện thời của tùy chọn 'foldmethod'"
 
-#: ../fold.c:1784
-#, c-format
-msgid "+--%3ld lines folded "
-msgstr "+--%3ld dòng được gấp"
-
-#. buffer has already been read
-#: ../getchar.c:273
 msgid "E222: Add to read buffer"
 msgstr "E222: Thêm vào bộ đệm đang đọc"
 
-#: ../getchar.c:2040
-msgid "E223: recursive mapping"
+# TODO: Capitalise first word of message?
+msgid "E223: Recursive mapping"
 msgstr "E223: ánh xạ đệ quy"
 
-#: ../getchar.c:2849
-#, c-format
-msgid "E224: global abbreviation already exists for %s"
+# TODO: Capitalise first word of message?
+msgid "E224: Global abbreviation already exists for %s"
 msgstr "E224: đã có sự viết tắt toàn cầu cho %s"
 
-#: ../getchar.c:2852
-#, c-format
-msgid "E225: global mapping already exists for %s"
+# TODO: Capitalise first word of message?
+msgid "E225: Global mapping already exists for %s"
 msgstr "E225: đã có ánh xạ toàn cầu cho %s"
 
-#: ../getchar.c:2952
-#, c-format
-msgid "E226: abbreviation already exists for %s"
+# TODO: Capitalise first word of message?
+msgid "E226: Abbreviation already exists for %s"
 msgstr "E226: đã có sự viết tắt cho %s"
 
-#: ../getchar.c:2955
-#, c-format
-msgid "E227: mapping already exists for %s"
+# TODO: Capitalise first word of message?
+msgid "E227: Mapping already exists for %s"
 msgstr "E227: đã có ánh xạ cho %s"
 
-#: ../getchar.c:3008
 msgid "No abbreviation found"
 msgstr "Không tìm thấy viết tắt"
 
-#: ../getchar.c:3010
 msgid "No mapping found"
 msgstr "Không tìm thấy ánh xạ"
 
-#: ../getchar.c:3974
 msgid "E228: makemap: Illegal mode"
 msgstr "E228: makemap: Chế độ không cho phép"
 
-#. key value of 'cedit' option
-#. type of cmdline window or 0
-#. result of cmdline window or 0
-#: ../globals.h:924
-msgid "--No lines in buffer--"
-msgstr "-- Không có dòng nào trong bộ đệm --"
+msgid " "
+msgstr " "
 
-#.
-#. * The error messages that can be shared are included here.
-#. * Excluded are errors that are only used once and debugging messages.
-#.
-#: ../globals.h:996
-msgid "E470: Command aborted"
-msgstr "E470: Câu lệnh bị dừng"
+#, c-format
+msgid "E616: vim_SelFile: can't get font %s"
+msgstr "E616: vim_SelFile: không tìm thấy phông chữ %s"
 
-#: ../globals.h:997
-msgid "E471: Argument required"
-msgstr "E471: Cần chỉ ra tham số"
+msgid "E614: vim_SelFile: can't return to current directory"
+msgstr "E614: vim_SelFile: không trở lại được thư mục hiện thời"
 
-#: ../globals.h:998
-msgid "E10: \\ should be followed by /, ? or &"
-msgstr "E10: Sau \\ phải là các ký tự /, ? hoặc &"
+msgid "Pathname:"
+msgstr "Đường dẫn tới tập tin:"
 
-#: ../globals.h:1000
-msgid "E11: Invalid in command-line window;  executes, CTRL-C quits"
-msgstr "E11: Lỗi trong cửa sổ dòng lệnh;  thực hiện, CTRL-C thoát"
+msgid "E615: vim_SelFile: can't get current directory"
+msgstr "E615: vim_SelFile: không tìm thấy thư mục hiện thời"
 
-#: ../globals.h:1002
-msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search"
-msgstr ""
-"E12: Câu lệnh không cho phép từ exrc/vimrc trong thư mục hiện thời hoặc "
-"trong tìm kiếm thẻ ghi"
+msgid "OK"
+msgstr "Đồng ý"
 
-#: ../globals.h:1003
-msgid "E171: Missing :endif"
-msgstr "E171: Thiếu câu lệnh :endif"
+msgid "Cancel"
+msgstr "Hủy bỏ"
 
-#: ../globals.h:1004
-msgid "E600: Missing :endtry"
-msgstr "E600: Thiếu câu lệnh :endtry"
+msgid "Vim dialog"
+msgstr "Hộp thoại Vim"
 
-#: ../globals.h:1005
-msgid "E170: Missing :endwhile"
-msgstr "E170: Thiếu câu lệnh :endwhile"
+msgid "Scrollbar Widget: Could not get geometry of thumb pixmap."
+msgstr "Thanh cuộn: Không thể xác định hình học của thanh cuộn."
 
-#: ../globals.h:1006
-#, fuzzy
-msgid "E170: Missing :endfor"
-msgstr "E171: Thiếu câu lệnh :endif"
+msgid "E232: Cannot create BalloonEval with both message and callback"
+msgstr "E232: Không tạo được BalloonEval với cả thông báo và lời gọi ngược lại"
 
-#: ../globals.h:1007
-msgid "E588: :endwhile without :while"
-msgstr "E588: Câu lệnh :endwhile không có lệnh :while (1 cặp)"
+msgid "E229: Cannot start the GUI"
+msgstr "E229: Không chạy được giao diện đồ họa GUI"
 
-#: ../globals.h:1008
-#, fuzzy
-msgid "E588: :endfor without :for"
-msgstr "E580: :endif không có :if"
+#, c-format
+msgid "E230: Cannot read from \"%s\""
+msgstr "E230: Không đọc được từ \"%s\""
 
-#: ../globals.h:1009
-msgid "E13: File exists (add ! to override)"
-msgstr "E13: Tập tin đã tồn tại (thêm ! để ghi chèn)"
+msgid "E665: Cannot start GUI, no valid font found"
+msgstr ""
+"E665: Không chạy được giao diện đồ họa GUI, đưa ra phông chữ không đúng"
 
-#: ../globals.h:1010
-msgid "E472: Command failed"
-msgstr "E472: Không thực hiện thành công câu lệnh"
+msgid "E231: 'guifontwide' invalid"
+msgstr "E231: 'guifontwide' có giá trị không đúng"
 
-#: ../globals.h:1011
-msgid "E473: Internal error"
-msgstr "E473: Lỗi nội bộ"
+msgid "E599: Value of 'imactivatekey' is invalid"
+msgstr "E599: Giá trị của 'imactivatekey' không đúng"
 
-#: ../globals.h:1012
-msgid "Interrupted"
-msgstr "Bị gián đoạn"
-
-#: ../globals.h:1013
-msgid "E14: Invalid address"
-msgstr "E14: Địa chỉ không cho phép"
-
-#: ../globals.h:1014
-msgid "E474: Invalid argument"
-msgstr "E474: Tham số không cho phép"
-
-#: ../globals.h:1015
-#, c-format
-msgid "E475: Invalid argument: %s"
-msgstr "E475: Tham số không cho phép: %s"
-
-#: ../globals.h:1016
-#, c-format
-msgid "E15: Invalid expression: %s"
-msgstr "E15: Biểu thức không cho phép: %s"
-
-#: ../globals.h:1017
-msgid "E16: Invalid range"
-msgstr "E16: Vùng không cho phép"
-
-#: ../globals.h:1018
-msgid "E476: Invalid command"
-msgstr "E476: Câu lệnh không cho phép"
-
-#: ../globals.h:1019
 #, c-format
-msgid "E17: \"%s\" is a directory"
-msgstr "E17: \"%s\" là mộ thư mục"
+msgid "E254: Cannot allocate color %s"
+msgstr "E254: Không chỉ định được màu %s"
 
-#: ../globals.h:1020
-#, fuzzy
-msgid "E900: Invalid job id"
-msgstr "E49: Kích thước thanh cuộn không cho phép"
+msgid "Vim dialog..."
+msgstr "Hộp thoại Vim..."
 
-#: ../globals.h:1021
-msgid "E901: Job table is full"
+msgid ""
+"&Yes\n"
+"&No\n"
+"&Cancel"
 msgstr ""
+"&Có\n"
+"&Không\n"
+"&Dừng"
 
-#: ../globals.h:1024
-#, c-format
-msgid "E364: Library call failed for \"%s()\""
-msgstr "E364: Gọi hàm số \"%s()\" của thư viện không thành công"
+msgid "Input _Methods"
+msgstr "Phương pháp _nhập liệu"
 
-#: ../globals.h:1026
-msgid "E19: Mark has invalid line number"
-msgstr "E19: Dấu hiệu chỉ đến một số thứ tự dòng không đúng"
+msgid "VIM - Search and Replace..."
+msgstr "VIM - Tìm kiếm và thay thế..."
 
-#: ../globals.h:1027
-msgid "E20: Mark not set"
-msgstr "E20: Dấu hiệu không được xác định"
+msgid "VIM - Search..."
+msgstr "VIM - Tìm kiếm..."
 
-#: ../globals.h:1029
-msgid "E21: Cannot make changes, 'modifiable' is off"
-msgstr "E21: Không thể thay đổi, vì tùy chọn 'modifiable' bị tắt"
+msgid "Find what:"
+msgstr "Tìm kiếm gì:"
 
-#: ../globals.h:1030
-msgid "E22: Scripts nested too deep"
-msgstr "E22: Các script lồng vào nhau quá sâu"
+msgid "Replace with:"
+msgstr "Thay thế bởi:"
 
-#: ../globals.h:1031
-msgid "E23: No alternate file"
-msgstr "E23: Không có tập tin xen kẽ"
+msgid "Match whole word only"
+msgstr "Chỉ tìm tương ứng hoàn toàn với từ"
 
-#: ../globals.h:1032
-msgid "E24: No such abbreviation"
-msgstr "E24: Không có chữ viết tắt như vậy"
+msgid "Match case"
+msgstr "Có tính kiểu chữ"
 
-#: ../globals.h:1033
-msgid "E477: No ! allowed"
-msgstr "E477: Không cho phép !"
+msgid "Direction"
+msgstr "Hướng"
 
-#: ../globals.h:1035
-msgid "E25: Nvim does not have a built-in GUI"
-msgstr "E25: Không sử dụng được giao diện đồ họa vì không chọn khi biên dịch"
+msgid "Up"
+msgstr "Lên"
 
-#: ../globals.h:1036
-#, c-format
-msgid "E28: No such highlight group name: %s"
-msgstr "E28: Nhóm chiếu sáng cú pháp %s không tồn tại"
+msgid "Down"
+msgstr "Xuống"
 
-#: ../globals.h:1037
-msgid "E29: No inserted text yet"
-msgstr "E29: Tạm thời chưa có văn bản được chèn"
+msgid "Find Next"
+msgstr "Tìm tiếp"
 
-#: ../globals.h:1038
-msgid "E30: No previous command line"
-msgstr "E30: Không có dòng lệnh trước"
+msgid "Replace"
+msgstr "Thay thế"
 
-#: ../globals.h:1039
-msgid "E31: No such mapping"
-msgstr "E31: Không có ánh xạ (mapping) như vậy"
+msgid "Replace All"
+msgstr "Thay thế tất cả"
 
-#: ../globals.h:1040
-msgid "E479: No match"
-msgstr "E479: Không có tương ứng"
+msgid "Vim: Received \"die\" request from session manager\n"
+msgstr "Vim: Nhận được yêu cầu \"chết\" (dừng) từ trình quản lý màn hình\n"
 
-#: ../globals.h:1041
-#, c-format
-msgid "E480: No match: %s"
-msgstr "E480: Không có tương ứng: %s"
+msgid "Vim: Main window unexpectedly destroyed\n"
+msgstr "Vim: Cửa sổ chính đã bị đóng đột ngột\n"
 
-#: ../globals.h:1042
-msgid "E32: No file name"
-msgstr "E32: Không có tên tập tin"
+msgid "Font Selection"
+msgstr "Chọn phông chữ"
 
-#: ../globals.h:1044
-msgid "E33: No previous substitute regular expression"
-msgstr "E33: Không có biểu thức chính quy trước để thay thế"
+msgid "Used CUT_BUFFER0 instead of empty selection"
+msgstr "Sử dụng CUT_BUFFER0 thay cho lựa chọn trống rỗng"
 
-#: ../globals.h:1045
-msgid "E34: No previous command"
-msgstr "E34: Không có câu lệnh trước"
+msgid "Filter"
+msgstr "Đầu lọc"
 
-#: ../globals.h:1046
-msgid "E35: No previous regular expression"
-msgstr "E35: Không có biểu thức chính quy trước"
+msgid "Directories"
+msgstr "Thư mục"
 
-#: ../globals.h:1047
-msgid "E481: No range allowed"
-msgstr "E481: Không cho phép sử dụng phạm vi"
+msgid "Help"
+msgstr "Trợ giúp"
 
-#: ../globals.h:1048
-msgid "E36: Not enough room"
-msgstr "E36: Không đủ chỗ trống"
+msgid "Files"
+msgstr "Tập tin"
 
-#: ../globals.h:1049
-#, c-format
-msgid "E482: Can't create file %s"
-msgstr "E482: Không tạo được tập tin %s"
+msgid "Selection"
+msgstr "Lựa chọn"
 
-#: ../globals.h:1050
-msgid "E483: Can't get temp file name"
-msgstr "E483: Không nhận được tên tập tin tạm thời (temp)"
+msgid "Undo"
+msgstr "Hủy thao tác"
 
-#: ../globals.h:1051
 #, c-format
-msgid "E484: Can't open file %s"
-msgstr "E484: Không mở được tập tin %s"
+msgid "E671: Cannot find window title \"%s\""
+msgstr "E671: Không tìm được tiêu đề cửa sổ \"%s\""
 
-#: ../globals.h:1052
 #, c-format
-msgid "E485: Can't read file %s"
-msgstr "E485: Không đọc được tập tin %s"
+msgid "E243: Argument not supported: \"-%s\"; Use the OLE version."
+msgstr "E243: Tham số không được hỗ trợ: \"-%s\"; Hãy sử dụng phiên bản OLE."
 
-#: ../globals.h:1054
-msgid "E37: No write since last change (add ! to override)"
-msgstr "E37: Thay đổi chưa được ghi nhớ (thêm ! để bỏ qua ghi nhớ)"
+msgid "E672: Unable to open window inside MDI application"
+msgstr "E672: Không mở được cửa sổ bên trong ứng dụng MDI"
 
-#: ../globals.h:1055
-#, fuzzy
-msgid "E37: No write since last change"
-msgstr "[Thay đổi chưa được ghi nhớ]\n"
+msgid "Find string (use '\\\\' to find  a '\\')"
+msgstr "Tìm kiếm chuỗi (hãy sử dụng '\\\\' để tìm kiếm dấu '\\')"
 
-#: ../globals.h:1056
-msgid "E38: Null argument"
-msgstr "E38: Tham sô bằng 0"
+msgid "Find & Replace (use '\\\\' to find  a '\\')"
+msgstr "Tìm kiếm và Thay thế (hãy sử dụng '\\\\' để tìm kiếm dấu '\\')"
 
-#: ../globals.h:1057
-msgid "E39: Number expected"
-msgstr "E39: Yêu cầu một số"
+msgid "Vim E458: Cannot allocate colormap entry, some colors may be incorrect"
+msgstr ""
+"Vim E458: Không chỉ định được bản ghi trong bảng màu, một vài màu có thể "
+"hiển thị không chính xác"
 
-#: ../globals.h:1058
 #, c-format
-msgid "E40: Can't open errorfile %s"
-msgstr "E40: Không mở được tập tin lỗi %s"
-
-#: ../globals.h:1059
-msgid "E41: Out of memory!"
-msgstr "E41: Không đủ bộ nhớ!"
+msgid "E250: Fonts for the following charsets are missing in fontset %s:"
+msgstr "E250: Trong bộ phông chữ %s thiếu phông cho các bảng mã sau:"
 
-#: ../globals.h:1060
-msgid "Pattern not found"
-msgstr "Không tìm thấy mẫu (pattern)"
+#, c-format
+msgid "E252: Fontset name: %s"
+msgstr "E252: Bộ phông chữ: %s"
 
-#: ../globals.h:1061
 #, c-format
-msgid "E486: Pattern not found: %s"
-msgstr "E486: Không tìm thấy mẫu (pattern): %s"
+msgid "Font '%s' is not fixed-width"
+msgstr "Phông chữ '%s' không phải là phông có độ rộng cố định (fixed-width)"
 
-#: ../globals.h:1062
-msgid "E487: Argument must be positive"
-msgstr "E487: Tham số phải là một số dương"
+#, c-format
+msgid "E253: Fontset name: %s\n"
+msgstr "E253: Bộ phông chữ: %s\n"
 
-#: ../globals.h:1064
-msgid "E459: Cannot go back to previous directory"
-msgstr "E459: Không quay lại được thư mục trước đó"
+#, c-format
+msgid "Font0: %s\n"
+msgstr "Font0: %s\n"
 
-#: ../globals.h:1066
-msgid "E42: No Errors"
-msgstr "E42: Không có lỗi"
+#, c-format
+msgid "Font1: %s\n"
+msgstr "Font1: %s\n"
 
-#: ../globals.h:1067
-msgid "E776: No location list"
+#, c-format
+msgid "Font%ld width is not twice that of font0\n"
 msgstr ""
+"Chiều rộng phông chữ font%ld phải lớn hơn hai lần so với chiều rộng font0\n"
 
-#: ../globals.h:1068
-msgid "E43: Damaged match string"
-msgstr "E43: Chuỗi tương ứng bị hỏng"
-
-#: ../globals.h:1069
-msgid "E44: Corrupted regexp program"
-msgstr "E44: Chương trình xử lý biểu thức chính quy bị hỏng"
-
-#: ../globals.h:1071
-msgid "E45: 'readonly' option is set (add ! to override)"
-msgstr "E45: Tùy chọn 'readonly' được bật (Hãy thêm ! để lờ đi)"
-
-#: ../globals.h:1073
-#, fuzzy, c-format
-msgid "E46: Cannot change read-only variable \"%s\""
-msgstr "E46: Không thay đổi được biến chỉ đọc \"%s\""
-
-#: ../globals.h:1075
-#, fuzzy, c-format
-msgid "E794: Cannot set variable in the sandbox: \"%s\""
-msgstr "E46: Không thay đổi được biến chỉ đọc \"%s\""
-
-#: ../globals.h:1076
-msgid "E47: Error while reading errorfile"
-msgstr "E47: Lỗi khi đọc tập tin lỗi"
-
-#: ../globals.h:1078
-msgid "E48: Not allowed in sandbox"
-msgstr "E48: Không cho phép trong hộp cát (sandbox)"
-
-#: ../globals.h:1080
-msgid "E523: Not allowed here"
-msgstr "E523: Không cho phép ở đây"
-
-#: ../globals.h:1082
-msgid "E359: Screen mode setting not supported"
-msgstr "E359: Chế độ màn hình không được hỗ trợ"
-
-#: ../globals.h:1083
-msgid "E49: Invalid scroll size"
-msgstr "E49: Kích thước thanh cuộn không cho phép"
-
-#: ../globals.h:1084
-msgid "E91: 'shell' option is empty"
-msgstr "E91: Tùy chọn 'shell' là một chuỗi rỗng"
-
-#: ../globals.h:1085
-msgid "E255: Couldn't read in sign data!"
-msgstr "E255: Không đọc được dữ liệu về ký tự!"
-
-#: ../globals.h:1086
-msgid "E72: Close error on swap file"
-msgstr "E72: Lỗi đóng tập tin trao đổi (swap)"
-
-#: ../globals.h:1087
-msgid "E73: tag stack empty"
-msgstr "E73: đống thẻ ghi rỗng"
-
-#: ../globals.h:1088
-msgid "E74: Command too complex"
-msgstr "E74: Câu lệnh quá phức tạp"
-
-#: ../globals.h:1089
-msgid "E75: Name too long"
-msgstr "E75: Tên quá dài"
-
-#: ../globals.h:1090
-msgid "E76: Too many ["
-msgstr "E76: Quá nhiều ký tự ["
-
-#: ../globals.h:1091
-msgid "E77: Too many file names"
-msgstr "E77: Quá nhiều tên tập tin"
-
-#: ../globals.h:1092
-msgid "E488: Trailing characters"
-msgstr "E488: Ký tự thừa ở đuôi"
-
-#: ../globals.h:1093
-msgid "E78: Unknown mark"
-msgstr "E78: Dấu hiệu không biết"
-
-#: ../globals.h:1094
-msgid "E79: Cannot expand wildcards"
-msgstr "E79: Không thực hiện được phép thế theo wildcard"
-
-#: ../globals.h:1096
-msgid "E591: 'winheight' cannot be smaller than 'winminheight'"
-msgstr "E591: giá trị của 'winheight' không thể nhỏ hơn 'winminheight'"
-
-#: ../globals.h:1098
-msgid "E592: 'winwidth' cannot be smaller than 'winminwidth'"
-msgstr "E592: giá trị của 'winwidth' không thể nhỏ hơn 'winminwidth'"
-
-#: ../globals.h:1099
-msgid "E80: Error while writing"
-msgstr "E80: Lỗi khi ghi nhớ"
-
-#: ../globals.h:1100
-msgid "Zero count"
-msgstr "Giá trị của bộ đếm bằng 0"
-
-#: ../globals.h:1101
-msgid "E81: Using  not in a script context"
-msgstr "E81: Sử dụng  ngoài phạm vi script"
-
-#: ../globals.h:1102
-#, fuzzy, c-format
-msgid "E685: Internal error: %s"
-msgstr "E473: Lỗi nội bộ"
+#, c-format
+msgid "Font0 width: %ld\n"
+msgstr "Chiều rộng font0: %ld\n"
 
-#: ../globals.h:1104
-msgid "E363: pattern uses more memory than 'maxmempattern'"
+#, c-format
+msgid ""
+"Font1 width: %ld\n"
+"\n"
 msgstr ""
+"Chiều rộng font1: %ld\n"
+"\n"
 
-#: ../globals.h:1105
-#, fuzzy
-msgid "E749: empty buffer"
-msgstr "E279: Đây không phải là bộ đệm SNiFF+"
-
-#: ../globals.h:1108
-#, fuzzy
-msgid "E682: Invalid search pattern or delimiter"
-msgstr "E383: Chuỗi tìm kiếm không đúng: %s"
-
-#: ../globals.h:1109
-msgid "E139: File is loaded in another buffer"
-msgstr "E139: Tập tin được nạp trong bộ đệm khác"
-
-#: ../globals.h:1110
-#, fuzzy, c-format
-msgid "E764: Option '%s' is not set"
-msgstr "E236: Phông chữ \"%s\" không có độ rộng cố định (fixed-width)"
-
-#: ../globals.h:1111
-#, fuzzy
-msgid "E850: Invalid register name"
-msgstr "E354: Tên sổ đăng ký không cho phép: '%s'"
-
-#: ../globals.h:1114
-msgid "search hit TOP, continuing at BOTTOM"
-msgstr "tìm kiếm sẽ được tiếp tục từ CUỐI tài liệu"
-
-#: ../globals.h:1115
-msgid "search hit BOTTOM, continuing at TOP"
-msgstr "tìm kiếm sẽ được tiếp tục từ ĐẦU tài liệu"
+msgid "E256: Hangul automata ERROR"
+msgstr "E256: LỖI máy tự động Hangual (tiếng Hàn)"
 
-#: ../if_cscope.c:85
 msgid "Add a new database"
 msgstr "Thêm một cơ sở dữ liệu mới"
 
-#: ../if_cscope.c:87
 msgid "Query for a pattern"
 msgstr "Yêu cầu theo một mẫu"
 
-#: ../if_cscope.c:89
 msgid "Show this message"
 msgstr "Hiển thị thông báo này"
 
-#: ../if_cscope.c:91
 msgid "Kill a connection"
 msgstr "Hủy kết nối"
 
-#: ../if_cscope.c:93
 msgid "Reinit all connections"
 msgstr "Khởi đầu lại tất cả các kết nối"
 
-#: ../if_cscope.c:95
 msgid "Show connections"
 msgstr "Hiển thị kết nối"
 
-#: ../if_cscope.c:101
 #, c-format
 msgid "E560: Usage: cs[cope] %s"
 msgstr "E560: Sử dụng: cs[cope] %s"
 
-#: ../if_cscope.c:225
 msgid "This cscope command does not support splitting the window.\n"
 msgstr "Câu lệnh cscope này không hỗ trợ việc chia (split) cửa sổ.\n"
 
-#: ../if_cscope.c:266
 msgid "E562: Usage: cstag "
 msgstr "E562: Sử dụng: cstag "
 
-#: ../if_cscope.c:313
-msgid "E257: cstag: tag not found"
+# TODO: Capitalise first word of message?
+msgid "E257: cstag: Tag not found"
 msgstr "E257: cstag: không tìm thấy thẻ ghi"
 
-#: ../if_cscope.c:461
 #, c-format
 msgid "E563: stat(%s) error: %d"
 msgstr "E563: lỗi stat(%s): %d"
 
-#: ../if_cscope.c:551
+msgid "E563: stat error"
+msgstr "E563: lỗi stat"
+
 #, c-format
 msgid "E564: %s is not a directory or a valid cscope database"
 msgstr ""
 "E564: %s không phải là một thư mục hoặc một cơ sở dữ liệu cscope thích hợp"
 
-#: ../if_cscope.c:566
 #, c-format
 msgid "Added cscope database %s"
 msgstr "Đã thêm cơ sở dữ liệu cscope %s"
 
-#: ../if_cscope.c:616
-#, c-format
-msgid "E262: error reading cscope connection %"
-msgstr "E262: lỗi lấy thông tin từ kết nối cscope %"
+# TODO: Capitalise first word of message?
+msgid "E262: Error reading cscope connection %ld"
+msgstr "E262: lỗi lấy thông tin từ kết nối cscope %ld"
 
-#: ../if_cscope.c:711
-msgid "E561: unknown cscope search type"
+# TODO: Capitalise first word of message?
+msgid "E561: Unknown cscope search type"
 msgstr "E561: không rõ loại tìm kiếm cscope"
 
-#: ../if_cscope.c:752 ../if_cscope.c:789
 msgid "E566: Could not create cscope pipes"
 msgstr "E566: Không tạo được đường ống (pipe) cho cscope"
 
-#: ../if_cscope.c:767
 msgid "E622: Could not fork for cscope"
 msgstr "E622: Không thực hiện được fork() cho cscope"
 
-#: ../if_cscope.c:849
-#, fuzzy
-msgid "cs_create_connection setpgid failed"
-msgstr "thực hiện cs_create_connection không thành công"
-
-#: ../if_cscope.c:853 ../if_cscope.c:889
 msgid "cs_create_connection exec failed"
 msgstr "thực hiện cs_create_connection không thành công"
 
-#: ../if_cscope.c:863 ../if_cscope.c:902
+msgid "E623: Could not spawn cscope process"
+msgstr "E623: Chạy tiến trình cscope không thành công"
+
 msgid "cs_create_connection: fdopen for to_fp failed"
 msgstr "cs_create_connection: thực hiện fdopen cho to_fp không thành công"
 
-#: ../if_cscope.c:865 ../if_cscope.c:906
 msgid "cs_create_connection: fdopen for fr_fp failed"
 msgstr "cs_create_connection: thực hiện fdopen cho fr_fp không thành công"
 
-#: ../if_cscope.c:890
-msgid "E623: Could not spawn cscope process"
-msgstr "E623: Chạy tiến trình cscope không thành công"
-
-#: ../if_cscope.c:932
-msgid "E567: no cscope connections"
+# TODO: Capitalise first word of message?
+msgid "E567: No cscope connections"
 msgstr "E567: không có kết nối với cscope"
 
-#: ../if_cscope.c:1009
-#, c-format
-msgid "E469: invalid cscopequickfix flag %c for %c"
-msgstr "E469: cờ cscopequickfix %c cho %c không chính xác"
-
-#: ../if_cscope.c:1058
-#, c-format
-msgid "E259: no matches found for cscope query %s of %s"
+# TODO: Capitalise first word of message?
+msgid "E259: No matches found for cscope query %s of %s"
 msgstr "E259: không tìm thấy tương ứng với yêu cầu cscope %s cho %s"
 
-#: ../if_cscope.c:1142
+# TODO: Capitalise first word of message?
+msgid "E469: Invalid cscopequickfix flag %c for %c"
+msgstr "E469: cờ cscopequickfix %c cho %c không chính xác"
+
 msgid "cscope commands:\n"
 msgstr "các lệnh cscope:\n"
 
-#: ../if_cscope.c:1150
-#, fuzzy, c-format
-msgid "%-5s: %s%*s (Usage: %s)"
+#, c-format
+msgid "%-5s: %-30s (Usage: %s)"
 msgstr "%-5s: %-30s (Sử dụng: %s)"
 
-#: ../if_cscope.c:1155
-msgid ""
-"\n"
-"       c: Find functions calling this function\n"
-"       d: Find functions called by this function\n"
-"       e: Find this egrep pattern\n"
-"       f: Find this file\n"
-"       g: Find this definition\n"
-"       i: Find files #including this file\n"
-"       s: Find this C symbol\n"
-"       t: Find this text string\n"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E625: Cannot open cscope database: %s"
+msgstr "E625: không mở được cơ sở dữ liệu cscope: %s"
+
+# TODO: Capitalise first word of message?
+msgid "E626: Cannot get cscope database information"
+msgstr "E626: không lấy được thông tin về cơ sở dữ liệu cscope"
 
-#: ../if_cscope.c:1226
-msgid "E568: duplicate cscope database not added"
+# TODO: Capitalise first word of message?
+msgid "E568: Duplicate cscope database not added"
 msgstr "E568: cơ sở dữ liệu này của cscope đã được gắn vào từ trước"
 
-#: ../if_cscope.c:1335
-#, c-format
-msgid "E261: cscope connection %s not found"
+msgid "E569: maximum number of cscope connections reached"
+msgstr "E569: đã đạt tới số kết nối lớn nhất cho phép với cscope"
+
+# TODO: Capitalise first word of message?
+msgid "E261: Cscope connection %s not found"
 msgstr "E261: kết nối với cscope %s không được tìm thấy"
 
-#: ../if_cscope.c:1364
 #, c-format
 msgid "cscope connection %s closed"
 msgstr "kết nối %s với cscope đã bị đóng"
 
-#. should not reach here
-#: ../if_cscope.c:1486
-msgid "E570: fatal error in cs_manage_matches"
+# TODO: Capitalise first word of message?
+msgid "E570: Fatal error in cs_manage_matches"
 msgstr "E570: lỗi nặng trong cs_manage_matches"
 
-#: ../if_cscope.c:1693
 #, c-format
 msgid "Cscope tag: %s"
 msgstr "Thẻ ghi cscope: %s"
 
-#: ../if_cscope.c:1711
 msgid ""
 "\n"
 "   #   line"
@@ -3183,90 +1952,305 @@ msgstr ""
 "\n"
 "   #   dòng"
 
-#: ../if_cscope.c:1713
 msgid "filename / context / line\n"
 msgstr "tên tập tin / nội dung / dòng\n"
 
-#: ../if_cscope.c:1809
 #, c-format
 msgid "E609: Cscope error: %s"
 msgstr "E609: Lỗi cscope: %s"
 
-#: ../if_cscope.c:2053
 msgid "All cscope databases reset"
 msgstr "Khởi động lại tất cả cơ sở dữ liệu cscope"
 
-#: ../if_cscope.c:2123
 msgid "no cscope connections\n"
 msgstr "không có kết nối với cscope\n"
 
-#: ../if_cscope.c:2126
 msgid " # pid    database name                       prepend path\n"
 msgstr " # pid    tên cơ sở dữ liệu                   đường dẫn ban đầu\n"
 
-#: ../main.c:144
-#, fuzzy
-msgid "Unknown option argument"
-msgstr "Tùy chọn không biết"
+msgid ""
+"E263: Sorry, this command is disabled, the Python library could not be "
+"loaded."
+msgstr ""
+"E263: Rất tiếc câu lệnh này không làm việc, vì thư viện Python chưa được nạp."
 
-#: ../main.c:146
-msgid "Too many edit arguments"
-msgstr "Có quá nhiều tham số soạn thảo"
+msgid "E659: Cannot invoke Python recursively"
+msgstr "E659: Không thể gọi Python một cách đệ quy"
 
-#: ../main.c:148
-msgid "Argument missing after"
-msgstr "Thiếu tham số sau"
+msgid "can't delete OutputObject attributes"
+msgstr "Không xóa được thuộc tính OutputObject"
 
-#: ../main.c:150
-#, fuzzy
-msgid "Garbage after option argument"
-msgstr "Rác sau tùy chọn"
+msgid "softspace must be an integer"
+msgstr "giá trị softspace phải là một số nguyên"
 
-#: ../main.c:152
-msgid "Too many \"+command\", \"-c command\" or \"--cmd command\" arguments"
-msgstr ""
-"Quá nhiều tham số \"+câu lệnh\", \"-c câu lệnh\" hoặc \"--cmd câu lệnh\""
+msgid "invalid attribute"
+msgstr "thuộc tính không đúng"
 
-#: ../main.c:154
-msgid "Invalid argument for"
-msgstr "Tham số không được phép cho"
+msgid "writelines() requires list of strings"
+msgstr "writelines() yêu cầu một danh sách các chuỗi"
 
-#: ../main.c:294
-#, c-format
-msgid "%d files to edit\n"
-msgstr "%d tập tin để soạn thảo\n"
+msgid "E264: Python: Error initialising I/O objects"
+msgstr "E264: Python: Lỗi khi bắt đầu sử dụng vật thể I/O"
 
-#: ../main.c:1342
-msgid "Attempt to open script file again: \""
-msgstr "Thử mở tập tin script một lần nữa: \""
+msgid "invalid expression"
+msgstr "biểu thức không đúng"
 
-#: ../main.c:1350
-msgid "Cannot open for reading: \""
-msgstr "Không mở để đọc được: \""
+msgid "expressions disabled at compile time"
+msgstr "biểu thức bị tắt khi biên dịch"
 
-#: ../main.c:1393
-msgid "Cannot open for script output: \""
-msgstr "Không mở cho đầu ra script được: \""
+msgid "attempt to refer to deleted buffer"
+msgstr "cố chỉ đến bộ đệm đã bị xóa"
 
-#: ../main.c:1622
-msgid "Vim: Warning: Output is not to a terminal\n"
-msgstr "Vim: Cảnh báo: Đầu ra không hướng tới một terminal\n"
+msgid "line number out of range"
+msgstr "số thứ tự của dòng vượt quá giới hạn"
+
+#, c-format
+msgid ""
+msgstr ""
+
+msgid "invalid mark name"
+msgstr "tên dấu hiệu không đúng"
+
+msgid "no such buffer"
+msgstr "không có bộ đệm như vậy"
+
+msgid "attempt to refer to deleted window"
+msgstr "cố chỉ đến cửa sổ đã bị đóng"
+
+msgid "readonly attribute"
+msgstr "thuộc tính chỉ đọc"
+
+msgid "cursor position outside buffer"
+msgstr "vị trí con trỏ nằm ngoài bộ đệm"
+
+#, c-format
+msgid ""
+msgstr ""
+
+#, c-format
+msgid ""
+msgstr ""
+
+#, c-format
+msgid ""
+msgstr ""
+
+msgid "no such window"
+msgstr "không có cửa sổ như vậy"
+
+msgid "cannot save undo information"
+msgstr "không ghi được thông tin về việc hủy thao tác"
+
+msgid "cannot delete line"
+msgstr "không xóa được dòng"
+
+msgid "cannot replace line"
+msgstr "không thay thế được dòng"
+
+msgid "cannot insert line"
+msgstr "không chèn được dòng"
+
+msgid "string cannot contain newlines"
+msgstr "chuỗi không thể chứa ký tự dòng mới"
+
+msgid ""
+"E266: Sorry, this command is disabled, the Ruby library could not be loaded."
+msgstr ""
+"E266: Rất tiếc câu lệnh này không làm việc, vì thư viện Ruby chưa đượcnạp."
+
+# TODO: Capitalise first word of message?
+msgid "E273: Unknown longjmp status %d"
+msgstr "E273: không rõ trạng thái của longjmp %d"
+
+msgid "Toggle implementation/definition"
+msgstr "Bật tắt giữa thi hành/định nghĩa"
+
+msgid "Show base class of"
+msgstr "Hiển thị hạng cơ bản của"
+
+msgid "Show overridden member function"
+msgstr "Hiển thị hàm số bị nạp đè lên"
+
+msgid "Retrieve from file"
+msgstr "Nhận từ tập tin"
+
+msgid "Retrieve from project"
+msgstr "Nhận từ dự án"
+
+msgid "Retrieve from all projects"
+msgstr "Nhận từ tất cả các dự án"
+
+msgid "Retrieve"
+msgstr "Nhận"
+
+msgid "Show source of"
+msgstr "Hiển thị mã nguồn"
+
+msgid "Find symbol"
+msgstr "Tìm ký hiệu"
+
+msgid "Browse class"
+msgstr "Duyệt hạng"
+
+msgid "Show class in hierarchy"
+msgstr "Hiển thị hạng trong hệ thống cấp bậc"
+
+msgid "Show class in restricted hierarchy"
+msgstr "Hiển thị hạng trong hệ thống cấp bậc giới hạn"
+
+msgid "Xref refers to"
+msgstr "Xref chỉ đến"
+
+msgid "Xref referred by"
+msgstr "Liên kết đến xref từ"
+
+msgid "Xref has a"
+msgstr "Xref có một"
+
+msgid "Xref used by"
+msgstr "Xref được sử dụng bởi"
+
+msgid "Show docu of"
+msgstr "Hiển thị docu của"
+
+msgid "Generate docu for"
+msgstr "Tạo docu cho"
+
+msgid "not "
+msgstr "không "
+
+msgid "connected"
+msgstr "được kết nối"
+
+
+msgid "invalid buffer number"
+msgstr "số của bộ đệm không đúng"
+
+msgid "not implemented yet"
+msgstr "tạm thời chưa được thực thi"
+
+msgid "unknown option"
+msgstr "tùy chọn không rõ"
+
+msgid "cannot set line(s)"
+msgstr "không thể đặt (các) dòng"
+
+msgid "mark not set"
+msgstr "dấu hiệu chưa được đặt"
+
+#, c-format
+msgid "row %d column %d"
+msgstr "hàng %d cột %d"
+
+msgid "cannot insert/append line"
+msgstr "không thể chèn hoặc thêm dòng"
+
+msgid "unknown flag: "
+msgstr "cờ không biết: "
+
+msgid "unknown vimOption"
+msgstr "không rõ tùy chọn vimOption"
+
+msgid "keyboard interrupt"
+msgstr "sự gián đoạn của bàn phím"
+
+msgid "Vim error"
+msgstr "lỗi của vim"
+
+msgid "cannot create buffer/window command: object is being deleted"
+msgstr "không tạo được câu lệnh của bộ đệm hay của cửa sổ: vật thể đang bị xóa"
+
+msgid ""
+"cannot register callback command: buffer/window is already being deleted"
+msgstr "không đăng ký được câu lệnh gọi ngược: bộ đệm hoặc cửa sổ đang bị xóa"
+
+msgid ""
+"E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-dev@vim."
+"org"
+msgstr ""
+"E280: LỖI NẶNG CỦA TCL: bị hỏng danh sách liên kết!? Hãy thông báo việc "
+"nàyđến danh sách thư (mailing list) vim-dev@vim.org"
+
+msgid "cannot register callback command: buffer/window reference not found"
+msgstr ""
+"không đăng ký được câu lệnh gọi ngược: không tìm thấy liên kết đến bộ đệm "
+"hoặc cửa sổ"
+
+msgid ""
+"E571: Sorry, this command is disabled: the Tcl library could not be loaded."
+msgstr ""
+"E571: Rất tiếc là câu lệnh này không làm việc, vì thư viện Tcl chưa được nạp"
+
+msgid ""
+"E281: TCL ERROR: exit code is not int!? Please report this to vim-dev@vim.org"
+msgstr ""
+"E281: LỖI TCL: mã thoát ra không phải là một số nguyên!? Hãy thông báo điều "
+"này đến danh sách thư (mailing list) vim-dev@vim.org"
+
+msgid "cannot get line"
+msgstr "không nhận được dòng"
+
+msgid "Unable to register a command server name"
+msgstr "Không đăng ký được một tên cho máy chủ câu lệnh"
+
+msgid "E248: Failed to send command to the destination program"
+msgstr "E248: Gửi câu lệnh vào chương trình khác không thành công"
+
+#, c-format
+msgid "E573: Invalid server id used: %s"
+msgstr "E573: Sử dụng id máy chủ không đúng: %s"
+
+msgid "E251: VIM instance registry property is badly formed.  Deleted!"
+msgstr "E251: Thuộc tính đăng ký của Vim được định dạng không đúng.  Xóa!"
+
+msgid "Unknown option"
+msgstr "Tùy chọn không biết"
+
+msgid "Too many edit arguments"
+msgstr "Có quá nhiều tham số soạn thảo"
+
+msgid "Argument missing after"
+msgstr "Thiếu tham số sau"
+
+msgid "Garbage after option"
+msgstr "Rác sau tùy chọn"
+
+msgid "Too many \"+command\", \"-c command\" or \"--cmd command\" arguments"
+msgstr ""
+"Quá nhiều tham số \"+câu lệnh\", \"-c câu lệnh\" hoặc \"--cmd câu lệnh\""
+
+msgid "Invalid argument for"
+msgstr "Tham số không được phép cho"
+
+msgid "This Vim was not compiled with the diff feature."
+msgstr "Vim không được biên dịch với tính năng hỗ trợ xem khác biệt (diff)."
+
+msgid "Attempt to open script file again: \""
+msgstr "Thử mở tập tin script một lần nữa: \""
+
+msgid "Cannot open for reading: \""
+msgstr "Không mở để đọc được: \""
+
+msgid "Cannot open for script output: \""
+msgstr "Không mở cho đầu ra script được: \""
+
+#, c-format
+msgid "%d files to edit\n"
+msgstr "%d tập tin để soạn thảo\n"
+
+msgid "Vim: Warning: Output is not to a terminal\n"
+msgstr "Vim: Cảnh báo: Đầu ra không hướng tới một terminal\n"
 
-#: ../main.c:1624
 msgid "Vim: Warning: Input is not from a terminal\n"
 msgstr "Vim: Cảnh báo: Đầu vào không phải đến từ một terminal\n"
 
-#. just in case..
-#: ../main.c:1891
 msgid "pre-vimrc command line"
 msgstr "dòng lệnh chạy trước khi thực hiện vimrc"
 
-#: ../main.c:1964
 #, c-format
 msgid "E282: Cannot read from \"%s\""
 msgstr "E282: Không đọc được từ \"%s\""
 
-#: ../main.c:2149
 msgid ""
 "\n"
 "More info with: \"vim -h\"\n"
@@ -3274,23 +2258,18 @@ msgstr ""
 "\n"
 "Xem thông tin chi tiết với: \"vim -h\"\n"
 
-#: ../main.c:2178
 msgid "[file ..]       edit specified file(s)"
 msgstr "[tập tin ..]     soạn thảo (các) tập tin chỉ ra"
 
-#: ../main.c:2179
 msgid "-               read text from stdin"
 msgstr "-                đọc văn bản từ đầu vào stdin"
 
-#: ../main.c:2180
 msgid "-t tag          edit file where tag is defined"
 msgstr "-t thẻ ghi      soạn thảo tập tin từ chỗ thẻ ghi chỉ ra"
 
-#: ../main.c:2181
 msgid "-q [errorfile]  edit file with first error"
 msgstr "-q [tập tin lỗi] soạn thảo tập tin với lỗi đầu tiên"
 
-#: ../main.c:2187
 msgid ""
 "\n"
 "\n"
@@ -3300,11 +2279,9 @@ msgstr ""
 "\n"
 "Sử dụng:"
 
-#: ../main.c:2189
 msgid " vim [arguments] "
 msgstr " vim [các tham số] "
 
-#: ../main.c:2193
 msgid ""
 "\n"
 "   or:"
@@ -3312,7 +2289,6 @@ msgstr ""
 "\n"
 "   hoặc:"
 
-#: ../main.c:2196
 msgid ""
 "\n"
 "\n"
@@ -3322,187 +2298,322 @@ msgstr ""
 "\n"
 "Tham số:\n"
 
-#: ../main.c:2197
 msgid "--\t\t\tOnly file names after this"
 msgstr "--\t\t\tSau tham số chỉ đưa ra tên tập tin"
 
-#: ../main.c:2199
 msgid "--literal\t\tDon't expand wildcards"
 msgstr "--literal\t\tKhông thực hiện việc mở rộng wildcard"
 
-#: ../main.c:2201
+msgid "-register\t\tRegister this gvim for OLE"
+msgstr "-register\t\tĐăng ký gvim này cho OLE"
+
+msgid "-unregister\t\tUnregister gvim for OLE"
+msgstr "-unregister\t\tBỏ đăng ký gvim này cho OLE"
+
+msgid "-g\t\t\tRun using GUI (like \"gvim\")"
+msgstr "-g\t\t\tSử dụng giao diện đồ họa GUI (giống \"gvim\")"
+
+msgid "-f  or  --nofork\tForeground: Don't fork when starting GUI"
+msgstr ""
+"-f  hoặc  --nofork\tTrong chương trình hoạt động: Không thực hiện fork khi "
+"chạy GUI"
+
 msgid "-v\t\t\tVi mode (like \"vi\")"
 msgstr "-v\t\t\tChế độ Vi (giống \"vi\")"
 
-#: ../main.c:2202
 msgid "-e\t\t\tEx mode (like \"ex\")"
 msgstr "-e\t\t\tChế độ Ex (giống \"ex\")"
 
-#: ../main.c:2203
-msgid "-E\t\t\tImproved Ex mode"
-msgstr ""
-
-#: ../main.c:2204
 msgid "-s\t\t\tSilent (batch) mode (only for \"ex\")"
 msgstr "-s\t\t\tChế độ ít đưa thông báo (gói) (chỉ dành cho \"ex\")"
 
-#: ../main.c:2205
 msgid "-d\t\t\tDiff mode (like \"vimdiff\")"
 msgstr "-d\t\t\tChế độ khác biệt, diff (giống \"vimdiff\")"
 
-#: ../main.c:2206
 msgid "-y\t\t\tEasy mode (like \"evim\", modeless)"
 msgstr "-y\t\t\tChế độ đơn giản (giống \"evim\", không có chế độ)"
 
-#: ../main.c:2207
 msgid "-R\t\t\tReadonly mode (like \"view\")"
 msgstr "-R\t\t\tChế độ chỉ đọc (giống \"view\")"
 
-#: ../main.c:2209
+msgid "-Z\t\t\tRestricted mode (like \"rvim\")"
+msgstr "-Z\t\t\tChế độ hạn chế (giống \"rvim\")"
+
 msgid "-m\t\t\tModifications (writing files) not allowed"
 msgstr "-m\t\t\tKhông có khả năng ghi nhớ thay đổi (ghi nhớ tập tin)"
 
-#: ../main.c:2210
 msgid "-M\t\t\tModifications in text not allowed"
 msgstr "-M\t\t\tKhông có khả năng thay đổi văn bản"
 
-#: ../main.c:2211
 msgid "-b\t\t\tBinary mode"
 msgstr "-b\t\t\tChế độ nhị phân (binary)"
 
-#: ../main.c:2212
 msgid "-l\t\t\tLisp mode"
 msgstr "-l\t\t\tChế độ Lisp"
 
-#: ../main.c:2213
 msgid "-C\t\t\tCompatible with Vi: 'compatible'"
 msgstr "-C\t\t\tChế độ tương thích với Vi: 'compatible'"
 
-#: ../main.c:2214
 msgid "-N\t\t\tNot fully Vi compatible: 'nocompatible'"
 msgstr "-N\t\t\tChế độ không tương thích hoàn toàn với Vi: 'nocompatible'"
 
-#: ../main.c:2215
-msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]"
-msgstr ""
+msgid "-V[N]\t\tVerbose level"
+msgstr "-V[N]\t\tMức độ chi tiết của thông báo"
 
-#: ../main.c:2216
 msgid "-D\t\t\tDebugging mode"
 msgstr "-D\t\t\tChế độ sửa lỗi (debug)"
 
-#: ../main.c:2217
 msgid "-n\t\t\tNo swap file, use memory only"
 msgstr "-n\t\t\tKhông sử dụng tập tin swap, chỉ sử dụng bộ nhớ"
 
-#: ../main.c:2218
 msgid "-r\t\t\tList swap files and exit"
 msgstr "-r\t\t\tLiệt kê các tập tin swap rồi thoát"
 
-#: ../main.c:2219
 msgid "-r (with file name)\tRecover crashed session"
 msgstr "-r (với tên tập tin)\tPhục hồi lần soạn thảo gặp sự cố"
 
-#: ../main.c:2220
 msgid "-L\t\t\tSame as -r"
 msgstr "-L\t\t\tGiống với -r"
 
-#: ../main.c:2221
-msgid "-A\t\t\tstart in Arabic mode"
+msgid "-f\t\t\tDon't use newcli to open window"
+msgstr "-f\t\t\tKhông sử dụng newcli để mở cửa sổ"
+
+msgid "-dev \t\tUse  for I/O"
+msgstr "-dev \t\tSử dụng  cho I/O"
+
+msgid "-A\t\t\tStart in Arabic mode"
 msgstr "-A\t\t\tKhởi động vào chế độ Ả Rập"
 
-#: ../main.c:2222
 msgid "-H\t\t\tStart in Hebrew mode"
 msgstr "-H\t\t\tKhởi động vào chế độ Do thái"
 
-#: ../main.c:2223
 msgid "-F\t\t\tStart in Farsi mode"
 msgstr "-F\t\t\tKhởi động vào chế độ Farsi"
 
-#: ../main.c:2224
 msgid "-T \tSet terminal type to "
 msgstr "-T \tĐặt loại terminal thành "
 
-#: ../main.c:2225
 msgid "-u \t\tUse  instead of any .vimrc"
 msgstr "-u \t\tSử dụng  thay thế cho mọi .vimrc"
 
-#: ../main.c:2226
+msgid "-U \t\tUse  instead of any .gvimrc"
+msgstr "-U \t\tSử dụng  thay thế cho mọi .gvimrc"
+
 msgid "--noplugin\t\tDon't load plugin scripts"
 msgstr "--noplugin\t\tKhông nạp bất kỳ script môđun nào"
 
-#: ../main.c:2227
-#, fuzzy
-msgid "-p[N]\t\tOpen N tab pages (default: one for each file)"
-msgstr "-o[N]\t\tMở N cửa sổ (theo mặc định: mỗi cửa sổ cho một tập tin)"
-
-#: ../main.c:2228
 msgid "-o[N]\t\tOpen N windows (default: one for each file)"
 msgstr "-o[N]\t\tMở N cửa sổ (theo mặc định: mỗi cửa sổ cho một tập tin)"
 
-#: ../main.c:2229
 msgid "-O[N]\t\tLike -o but split vertically"
 msgstr "-O[N]\t\tGiống với -o nhưng phân chia theo đường thẳng đứng"
 
-#: ../main.c:2230
 msgid "+\t\t\tStart at end of file"
 msgstr "+\t\t\tBắt đầu soạn thảo từ cuối tập tin"
 
-#: ../main.c:2231
 msgid "+\t\tStart at line "
 msgstr "+\t\tBắt đầu soạn thảo từ dòng thứ  (số thứ tự của dòng)"
 
-#: ../main.c:2232
 msgid "--cmd \tExecute  before loading any vimrc file"
 msgstr "--cmd \tThực hiện  trước khi nạp tập tin vimrc"
 
-#: ../main.c:2233
 msgid "-c \t\tExecute  after loading the first file"
 msgstr "-c \t\tThực hiện  sau khi nạp tập tin đầu tiên"
 
-#: ../main.c:2235
 msgid "-S \t\tSource file  after loading the first file"
 msgstr "-S \t\tThực hiện  sau khi nạp tập tin đầu tiên"
 
-#: ../main.c:2236
 msgid "-s \tRead Normal mode commands from file "
 msgstr ""
 "-s \tĐọc các lệnh của chế độ Thông thường từ tập tin "
 
-#: ../main.c:2237
 msgid "-w \tAppend all typed commands to file "
 msgstr "-w \tThêm tất cả các lệnh đã gõ vào tập tin "
 
-#: ../main.c:2238
 msgid "-W \tWrite all typed commands to file "
 msgstr "-W \tGhi nhớ tất cả các lệnh đã gõ vào tập tin "
 
-#: ../main.c:2240
-msgid "--startuptime \tWrite startup timing messages to "
+msgid "-x\t\t\tEdit encrypted files"
+msgstr "-x\t\t\tSoạn thảo tập tin đã mã hóa"
+
+msgid "-display \tConnect Vim to this particular X-server"
+msgstr "-display \tKết nối Vim tới máy chủ X đã chỉ ra"
+
+msgid "-X\t\t\tDo not connect to X server"
+msgstr "-X\t\t\tKhông thực hiện việc kết nối tới máy chủ X"
+
+msgid "--remote \tEdit  in a Vim server if possible"
+msgstr "--remote \tSoạn thảo  trên máy chủ Vim nếu có thể"
+
+msgid "--remote-silent   Same, don't complain if there is no server"
+msgstr ""
+"--remote-silent   Cũng vậy, nhưng không kêu ca dù không có máy chủ"
+
+msgid ""
+"--remote-wait   As --remote but wait for files to have been edited"
+msgstr "--remote-wait   Cũng như --remote, nhưng chờ sự kết thúc"
+
+msgid ""
+"--remote-wait-silent   Same, don't complain if there is no server"
+msgstr ""
+"--remote-wait-silent   Cũng vậy, nhưng không kêu ca dù không có máy "
+"chủ"
+
+msgid "--remote-send \tSend  to a Vim server and exit"
+msgstr "--remote-send \tGửi  lên máy chủ Vim và thoát"
+
+msgid "--remote-expr \tEvaluate  in a Vim server and print result"
 msgstr ""
+"--remote-expr \tTính  trên máy chủ Vim và in ra kết quả"
+
+msgid "--serverlist\t\tList available Vim server names and exit"
+msgstr "--serverlist\t\tHiển thị danh sách máy chủ Vim và thoát"
+
+msgid "--servername \tSend to/become the Vim server "
+msgstr "--servername \tGửi lên (hoặc trở thành) máy chủ Vim với "
 
-#: ../main.c:2242
 msgid "-i \t\tUse  instead of .viminfo"
 msgstr "-i \t\tSử dụng tập tin  thay cho .viminfo"
 
-#: ../main.c:2243
 msgid "-h  or  --help\tPrint Help (this message) and exit"
 msgstr "-h hoặc --help\tHiển thị Trợ giúp (thông tin này) và thoát"
 
-#: ../main.c:2244
 msgid "--version\t\tPrint version information and exit"
 msgstr "--version\t\tĐưa ra thông tin về phiên bản Vim và thoát"
 
-#: ../mark.c:676
+msgid ""
+"\n"
+"Arguments recognised by gvim (Motif version):\n"
+msgstr ""
+"\n"
+"Tham số cho gvim (phiên bản Motif):\n"
+
+msgid ""
+"\n"
+"Arguments recognised by gvim (neXtaw version):\n"
+msgstr ""
+"\n"
+"Tham số cho gvim (phiên bản neXtaw):\n"
+
+msgid ""
+"\n"
+"Arguments recognised by gvim (Athena version):\n"
+msgstr ""
+"\n"
+"Tham số cho gvim (phiên bản Athena):\n"
+
+msgid "-display \tRun Vim on "
+msgstr "-display \tChạy Vim trong  đã chỉ ra"
+
+msgid "-iconic\t\tStart Vim iconified"
+msgstr "-iconic\t\tChạy Vim ở dạng thu nhỏ"
+
+msgid "-name \t\tUse resource as if vim was "
+msgstr "-name \t\tSử dụng tài nguyên giống như khi vim có "
+
+msgid "\t\t\t  (Unimplemented)\n"
+msgstr "\t\t\t  (Chưa được thực thi)\n"
+
+msgid "-background \tUse  for the background (also: -bg)"
+msgstr "-background \tSử dụng  chỉ ra cho nền (cũng như: -bg)"
+
+msgid "-foreground \tUse  for normal text (also: -fg)"
+msgstr ""
+"-foreground \tSử dụng  cho văn bản thông thường (cũng như: -fg)"
+
+msgid "-font \t\tUse  for normal text (also: -fn)"
+msgstr ""
+"-font \t\tSử dụng  chữ cho văn bản thông thường (cũng như: -fn)"
+
+msgid "-boldfont \tUse  for bold text"
+msgstr "-boldfont \tSử dụng  chữ cho văn bản in đậm"
+
+msgid "-italicfont \tUse  for italic text"
+msgstr "-italicfont \tSử dụng  chữ cho văn bản in nghiêng"
+
+msgid "-geometry \tUse  for initial geometry (also: -geom)"
+msgstr "-geometry \tSử dụng  ban đầu (cũng như: -geom)"
+
+msgid "-borderwidth \tUse a border width of  (also: -bw)"
+msgstr ""
+"-borderwidth \tSử dụng đường viền có chiều  (cũng như: -bw)"
+
+msgid "-scrollbarwidth   Use a scrollbar width of  (also: -sw)"
+msgstr ""
+"-scrollbarwidth  Sử dụng thanh cuộn với chiều  (cũng như: -sw)"
+
+msgid "-menuheight \tUse a menu bar height of  (also: -mh)"
+msgstr ""
+"-menuheight \tSử dụng thanh trình đơn với chiều  (cũng như: -mh)"
+
+msgid "-reverse\t\tUse reverse video (also: -rv)"
+msgstr "-reverse\t\tSử dụng chế độ video đảo ngược (cũng như: -rv)"
+
+msgid "+reverse\t\tDon't use reverse video (also: +rv)"
+msgstr "+reverse\t\tKhông sử dụng chế độ video đảo ngược (cũng như: +rv)"
+
+msgid "-xrm \tSet the specified resource"
+msgstr "-xrm \tĐặt  chỉ ra"
+
+msgid ""
+"\n"
+"Arguments recognised by gvim (RISC OS version):\n"
+msgstr ""
+"\n"
+"Tham số cho gvim (phiên bản RISC OS):\n"
+
+msgid "--columns \tInitial width of window in columns"
+msgstr "--columns \tChiều rộng ban đầu của cửa sổ tính theo số cột"
+
+msgid "--rows \tInitial height of window in rows"
+msgstr "--rows \tChiều cao ban đầu của cửa sổ tính theo số dòng"
+
+msgid ""
+"\n"
+"Arguments recognised by gvim (GTK+ version):\n"
+msgstr ""
+"\n"
+"Tham số cho gvim (phiên bản GTK+):\n"
+
+msgid "-display \tRun Vim on  (also: --display)"
+msgstr ""
+"-display \tChạy Vim trên  chỉ ra (cũng như: --display)"
+
+msgid "--role \tSet a unique role to identify the main window"
+msgstr "--role \tĐặt  duy nhất để nhận diện cửa sổ chính"
+
+msgid "--socketid \tOpen Vim inside another GTK widget"
+msgstr "--socketid \tMở Vim bên trong thành phần GTK khác"
+
+msgid "-P \tOpen Vim inside parent application"
+msgstr "-P \tMở Vim bên trong ứng dụng mẹ"
+
+msgid "No display"
+msgstr "Không có màn hình"
+
+msgid ": Send failed.\n"
+msgstr ": Gửi không thành công.\n"
+
+msgid ": Send failed. Trying to execute locally\n"
+msgstr ": Gửi không thành công. Thử thực hiện nội bộ\n"
+
+#, c-format
+msgid "%d of %d edited"
+msgstr "đã soạn thảo %d từ %d"
+
+msgid "No display: Send expression failed.\n"
+msgstr "Không có màn hình: gửi biểu thức không thành công.\n"
+
+msgid ": Send expression failed.\n"
+msgstr ": Gửi biểu thức không thành công.\n"
+
 msgid "No marks set"
 msgstr "Không có dấu hiệu nào được đặt."
 
-#: ../mark.c:678
 #, c-format
 msgid "E283: No marks matching \"%s\""
 msgstr "E283: Không có dấu hiệu tương ứng với \"%s\""
 
-#. Highlight title
-#: ../mark.c:687
 msgid ""
 "\n"
 "mark line  col file/text"
@@ -3510,8 +2621,6 @@ msgstr ""
 "\n"
 "nhãn dòng  cột tập tin/văn bản"
 
-#. Highlight title
-#: ../mark.c:789
 msgid ""
 "\n"
 " jump line  col file/text"
@@ -3519,8 +2628,6 @@ msgstr ""
 "\n"
 " bước_nhảy dòng  cột tập tin/văn bản"
 
-#. Highlight title
-#: ../mark.c:831
 msgid ""
 "\n"
 "change line  col text"
@@ -3528,7 +2635,6 @@ msgstr ""
 "\n"
 "thay_đổi dòng  cột văn_bản"
 
-#: ../mark.c:1238
 msgid ""
 "\n"
 "# File marks:\n"
@@ -3536,8 +2642,6 @@ msgstr ""
 "\n"
 "# Nhãn của tập tin:\n"
 
-#. Write the jumplist with -'
-#: ../mark.c:1271
 msgid ""
 "\n"
 "# Jumplist (newest first):\n"
@@ -3545,7 +2649,6 @@ msgstr ""
 "\n"
 "# Danh sách bước nhảy (mới hơn đứng trước):\n"
 
-#: ../mark.c:1352
 msgid ""
 "\n"
 "# History of marks within files (newest to oldest):\n"
@@ -3553,88 +2656,94 @@ msgstr ""
 "\n"
 "# Lịch sử các nhãn trong tập tin (từ mới nhất đến cũ nhất):\n"
 
-#: ../mark.c:1431
 msgid "Missing '>'"
 msgstr "Thiếu '>'"
 
-#: ../memfile.c:426
-msgid "E293: block was not locked"
+msgid "E543: Not a valid codepage"
+msgstr "E543: Bảng mã không cho phép"
+
+msgid "E284: Cannot set IC values"
+msgstr "E284: Không đặt được giá trị nội dung nhập vào (IC)"
+
+msgid "E285: Failed to create input context"
+msgstr "E285: Không tạo được nội dung nhập vào"
+
+msgid "E286: Failed to open input method"
+msgstr "E286: Việc thử mở phương pháp nhập không thành công"
+
+msgid "E287: Warning: Could not set destroy callback to IM"
+msgstr ""
+"E287: Cảnh báo: không đặt được sự gọi ngược hủy diệt thành phương pháp nhập"
+
+# TODO: Capitalise first word of message?
+msgid "E288: Input method doesn't support any style"
+msgstr "E288: phương pháp nhập không hỗ trợ bất kỳ phong cách (style) nào"
+
+# TODO: Capitalise first word of message?
+msgid "E289: Input method doesn't support my preedit type"
+msgstr "E289: phương pháp nhập không hỗ trợ loại soạn thảo trước của Vim"
+
+msgid "E291: Your GTK+ is older than 1.2.3. Status area disabled"
+msgstr "E291: GTK+ cũ hơn 1.2.3. Vùng chỉ trạng thái không làm việc"
+
+# TODO: Capitalise first word of message?
+msgid "E293: Block was not locked"
 msgstr "E293: khối chưa bị khóa"
 
-#: ../memfile.c:799
 msgid "E294: Seek error in swap file read"
 msgstr "E294: Lỗi tìm kiếm khi đọc tập tin trao đổi (swap)"
 
-#: ../memfile.c:803
 msgid "E295: Read error in swap file"
 msgstr "E295: Lỗi đọc tập tin trao đổi (swap)"
 
-#: ../memfile.c:849
 msgid "E296: Seek error in swap file write"
 msgstr "E296: Lỗi tìm kiếm khi ghi nhớ tập tin trao đổi (swap)"
 
-#: ../memfile.c:865
 msgid "E297: Write error in swap file"
 msgstr "E297: Lỗi ghi nhớ tập tin trao đổi (swap)"
 
-#: ../memfile.c:1036
 msgid "E300: Swap file already exists (symlink attack?)"
 msgstr ""
 "E300: Tập tin trao đổi (swap) đã tồn tại (sử dụng liên kết mềm tấn công?)"
 
-#: ../memline.c:318
 msgid "E298: Didn't get block nr 0?"
 msgstr "E298: Chưa lấy khối số 0?"
 
-#: ../memline.c:361
 msgid "E298: Didn't get block nr 1?"
 msgstr "E298: Chưa lấy khối số 12?"
 
-#: ../memline.c:377
 msgid "E298: Didn't get block nr 2?"
 msgstr "E298: Chưa lấy khối số 2?"
 
-#. could not (re)open the swap file, what can we do????
-#: ../memline.c:465
 msgid "E301: Oops, lost the swap file!!!"
 msgstr "E301: Ối, mất tập tin trao đổi (swap)!!!"
 
-#: ../memline.c:477
 msgid "E302: Could not rename swap file"
 msgstr "E302: Không đổi được tên tập tin trao đổi (swap)"
 
-#: ../memline.c:554
 #, c-format
 msgid "E303: Unable to open swap file for \"%s\", recovery impossible"
 msgstr ""
 "E303: Không mở được tập tin trao đổi (swap) cho \"%s\", nên không thể phục "
 "hồi"
 
-#: ../memline.c:666
-#, fuzzy
-msgid "E304: ml_upd_block0(): Didn't get block 0??"
+msgid "E304: ml_timestamp: Didn't get block 0??"
 msgstr "E304: ml_timestamp: Chưa lấy khối số 0??"
 
-#. no swap files found
-#: ../memline.c:830
 #, c-format
 msgid "E305: No swap file found for %s"
 msgstr "E305: Không tìm thấy tập tin trao đổi (swap) cho %s"
 
-#: ../memline.c:839
 msgid "Enter number of swap file to use (0 to quit): "
 msgstr "Hãy nhập số của tập tin trao đổi (swap) muốn sử dụng (0 để thoát): "
 
-#: ../memline.c:879
 #, c-format
 msgid "E306: Cannot open %s"
 msgstr "E306: Không mở được %s"
 
-#: ../memline.c:897
 msgid "Unable to read block 0 from "
 msgstr "Không thể đọc khối số 0 từ "
 
-#: ../memline.c:900
 msgid ""
 "\n"
 "Maybe no changes were made or Vim did not update the swap file."
@@ -3642,28 +2751,22 @@ msgstr ""
 "\n"
 "Chưa có thay đổi nào hoặc Vim không thể cập nhật tập tin trao đổi (swap)"
 
-#: ../memline.c:909
 msgid " cannot be used with this version of Vim.\n"
 msgstr " không thể sử dụng trong phiên bản Vim này.\n"
 
-#: ../memline.c:911
 msgid "Use Vim version 3.0.\n"
 msgstr "Hãy sử dụng Vim phiên bản 3.0.\n"
 
-#: ../memline.c:916
 #, c-format
 msgid "E307: %s does not look like a Vim swap file"
 msgstr "E307: %s không phải là tập tin trao đổi (swap) của Vim"
 
-#: ../memline.c:922
 msgid " cannot be used on this computer.\n"
 msgstr " không thể sử dụng trên máy tính này.\n"
 
-#: ../memline.c:924
 msgid "The file was created on "
 msgstr "Tập tin đã được tạo trên "
 
-#: ../memline.c:928
 msgid ""
 ",\n"
 "or the file has been damaged."
@@ -3671,85 +2774,63 @@ msgstr ""
 ",\n"
 "hoặc tập tin đã bị hỏng."
 
-#: ../memline.c:945
-msgid " has been damaged (page size is smaller than minimum value).\n"
-msgstr ""
-
-#: ../memline.c:974
 #, c-format
 msgid "Using swap file \"%s\""
 msgstr "Đang sử dụng tập tin trao đổi (swap) \"%s\""
 
-#: ../memline.c:980
 #, c-format
 msgid "Original file \"%s\""
 msgstr "Tập tin gốc \"%s\""
 
-#: ../memline.c:995
 msgid "E308: Warning: Original file may have been changed"
 msgstr "E308: Cảnh báo: Tập tin gốc có thể đã bị thay đổi"
 
-#: ../memline.c:1061
 #, c-format
 msgid "E309: Unable to read block 1 from %s"
 msgstr "E309: Không đọc được khối số 1 từ %s"
 
-#: ../memline.c:1065
 msgid "???MANY LINES MISSING"
 msgstr "???THIẾU NHIỀU DÒNG"
 
-#: ../memline.c:1076
 msgid "???LINE COUNT WRONG"
 msgstr "???GIÁ TRỊ CỦA SỐ ĐẾM DÒNG BỊ SAI"
 
-#: ../memline.c:1082
 msgid "???EMPTY BLOCK"
 msgstr "???KHỐI RỖNG"
 
-#: ../memline.c:1103
 msgid "???LINES MISSING"
 msgstr "???THIẾU DÒNG"
 
-#: ../memline.c:1128
 #, c-format
 msgid "E310: Block 1 ID wrong (%s not a .swp file?)"
 msgstr "E310: Khối 1 ID sai (%s không phải là tập tin .swp?)"
 
-#: ../memline.c:1133
 msgid "???BLOCK MISSING"
 msgstr "???THIẾU KHỐI"
 
-#: ../memline.c:1147
 msgid "??? from here until ???END lines may be messed up"
 msgstr "??? từ đây tới ???CUỐI, các dòng có thể đã bị hỏng"
 
-#: ../memline.c:1164
 msgid "??? from here until ???END lines may have been inserted/deleted"
 msgstr "??? từ đây tới ???CUỐI, các dòng có thể đã bị chèn hoặc xóa"
 
-#: ../memline.c:1181
 msgid "???END"
 msgstr "???CUỐI"
 
-#: ../memline.c:1238
 msgid "E311: Recovery Interrupted"
 msgstr "E311: Việc phục hồi bị gián đoạn"
 
-#: ../memline.c:1243
 msgid ""
 "E312: Errors detected while recovering; look for lines starting with ???"
 msgstr ""
 "E312: Phát hiện ra lỗi trong khi phục hồi; hãy xem những dòng bắt đầu với ???"
 
-#: ../memline.c:1245
 msgid "See \":help E312\" for more information."
 msgstr "Hãy xem thông tin bổ sung trong trợ giúp \":help E312\""
 
-#: ../memline.c:1249
 msgid "Recovery completed. You should check if everything is OK."
 msgstr "Việc phục hồi đã hoàn thành. Nên kiểm tra xem mọi thứ có ổn không."
 
-#: ../memline.c:1251
 msgid ""
 "\n"
 "(You might want to write out this file under another name\n"
@@ -3757,71 +2838,49 @@ msgstr ""
 "\n"
 "(Có thể ghi nhớ tập tin với tên khác và so sánh với tập\n"
 
-#: ../memline.c:1252
-#, fuzzy
-msgid "and run diff with the original file to check for changes)"
+msgid "and run diff with the original file to check for changes)\n"
 msgstr "gốc bằng chương trình diff).\n"
 
-#: ../memline.c:1254
-msgid "Recovery completed. Buffer contents equals file contents."
-msgstr ""
-
-#: ../memline.c:1255
-#, fuzzy
 msgid ""
-"\n"
-"You may want to delete the .swp file now.\n"
+"Delete the .swp file afterwards.\n"
 "\n"
 msgstr ""
 "Sau đó hãy xóa tập tin .swp.\n"
 "\n"
 
-#. use msg() to start the scrolling properly
-#: ../memline.c:1327
 msgid "Swap files found:"
 msgstr "Tìm thấy tập tin trao đổi (swap):"
 
-#: ../memline.c:1446
 msgid "   In current directory:\n"
 msgstr "   Trong thư mục hiện thời:\n"
 
-#: ../memline.c:1448
 msgid "   Using specified name:\n"
 msgstr "   Với tên chỉ ra:\n"
 
-#: ../memline.c:1450
 msgid "   In directory "
 msgstr "   Trong thư mục   "
 
-#: ../memline.c:1465
 msgid "      -- none --\n"
 msgstr "      -- không --\n"
 
-#: ../memline.c:1527
 msgid "          owned by: "
 msgstr "          người sở hữu: "
 
-#: ../memline.c:1529
 msgid "   dated: "
 msgstr "    ngày: "
 
-#: ../memline.c:1532 ../memline.c:3231
 msgid "             dated: "
 msgstr "              ngày: "
 
-#: ../memline.c:1548
 msgid "         [from Vim version 3.0]"
 msgstr "         [từ Vim phiên bản 3.0]"
 
-#: ../memline.c:1550
 msgid "         [does not look like a Vim swap file]"
 msgstr "         [không phải là tập tin trao đổi (swap) của Vim]"
 
-#: ../memline.c:1552
 msgid "         file name: "
 msgstr "         tên tập tin: "
 
-#: ../memline.c:1558
 msgid ""
 "\n"
 "          modified: "
@@ -3829,15 +2888,12 @@ msgstr ""
 "\n"
 "           thay đổi: "
 
-#: ../memline.c:1559
 msgid "YES"
 msgstr "CÓ"
 
-#: ../memline.c:1559
 msgid "no"
 msgstr "không"
 
-#: ../memline.c:1562
 msgid ""
 "\n"
 "         user name: "
@@ -3845,11 +2901,9 @@ msgstr ""
 "\n"
 "      tên người dùng: "
 
-#: ../memline.c:1568
 msgid "   host name: "
 msgstr "    tên máy: "
 
-#: ../memline.c:1570
 msgid ""
 "\n"
 "         host name: "
@@ -3857,7 +2911,6 @@ msgstr ""
 "\n"
 "           tên máy: "
 
-#: ../memline.c:1575
 msgid ""
 "\n"
 "        process ID: "
@@ -3865,11 +2918,16 @@ msgstr ""
 "\n"
 "     ID tiến trình: "
 
-#: ../memline.c:1579
 msgid " (still running)"
 msgstr " (vẫn đang chạy)"
 
-#: ../memline.c:1586
+msgid ""
+"\n"
+"         [not usable with this version of Vim]"
+msgstr ""
+"\n"
+"         [không sử dụng được với phiên bản này của Vim]"
+
 msgid ""
 "\n"
 "         [not usable on this computer]"
@@ -3877,97 +2935,75 @@ msgstr ""
 "\n"
 "         [không sử dụng được trên máy tính này]"
 
-#: ../memline.c:1590
 msgid "         [cannot be read]"
 msgstr "         [không đọc được]"
 
-#: ../memline.c:1593
 msgid "         [cannot be opened]"
 msgstr "         [không mở được]"
 
-#: ../memline.c:1698
 msgid "E313: Cannot preserve, there is no swap file"
 msgstr "E313: Không cập nhật được tập tin trao đổi (swap) vì không tìm thấy nó"
 
-#: ../memline.c:1747
 msgid "File preserved"
 msgstr "Đã cập nhật tập tin trao đổi (swap)"
 
-#: ../memline.c:1749
 msgid "E314: Preserve failed"
 msgstr "E314: Cập nhật không thành công"
 
-#: ../memline.c:1819
-#, c-format
-msgid "E315: ml_get: invalid lnum: %"
-msgstr "E315: ml_get: giá trị lnum không đúng: %"
+# TODO: Capitalise first word of message?
+msgid "E315: ml_get: Invalid lnum: %ld"
+msgstr "E315: ml_get: giá trị lnum không đúng: %ld"
 
-#: ../memline.c:1851
-#, c-format
-msgid "E316: ml_get: cannot find line %"
-msgstr "E316: ml_get: không tìm được dòng %"
+# TODO: Capitalise first word of message?
+msgid "E316: ml_get: Cannot find line %ld"
+msgstr "E316: ml_get: không tìm được dòng %ld"
 
-#: ../memline.c:2236
-msgid "E317: pointer block id wrong 3"
+# TODO: Capitalise first word of message?
+msgid "E317: Pointer block id wrong 3"
 msgstr "E317: Giá trị của pointer khối số 3 không đúng"
 
-#: ../memline.c:2311
 msgid "stack_idx should be 0"
 msgstr "giá trị stack_idx phải bằng 0"
 
-#: ../memline.c:2369
 msgid "E318: Updated too many blocks?"
 msgstr "E318: Đã cập nhật quá nhiều khối?"
 
-#: ../memline.c:2511
-msgid "E317: pointer block id wrong 4"
+# TODO: Capitalise first word of message?
+msgid "E317: Pointer block id wrong 4"
 msgstr "E317: Giá trị của pointer khối số 4 không đúng"
 
-#: ../memline.c:2536
 msgid "deleted block 1?"
 msgstr "đã xóa khối số 1?"
 
-#: ../memline.c:2707
 #, c-format
-msgid "E320: Cannot find line %"
-msgstr "E320: Không tìm được dòng %"
+msgid "E320: Cannot find line %ld"
+msgstr "E320: Không tìm được dòng %ld"
 
-#: ../memline.c:2916
-msgid "E317: pointer block id wrong"
+# TODO: Capitalise first word of message?
+msgid "E317: Pointer block id wrong"
 msgstr "E317: giá trị của pointer khối không đúng"
 
-#: ../memline.c:2930
 msgid "pe_line_count is zero"
 msgstr "giá trị pe_line_count bằng không"
 
-#: ../memline.c:2955
-#, c-format
-msgid "E322: line number out of range: % past the end"
-msgstr "E322: số thứ tự dòng vượt quá giới hạn : %"
+# TODO: Capitalise first word of message?
+msgid "E322: Line number out of range: %ld past the end"
+msgstr "E322: số thứ tự dòng vượt quá giới hạn : %ld"
 
-#: ../memline.c:2959
-#, c-format
-msgid "E323: line count wrong in block %"
-msgstr "E323: giá trị đếm dòng không đúng trong khối %"
+# TODO: Capitalise first word of message?
+msgid "E323: Line count wrong in block %ld"
+msgstr "E323: giá trị đếm dòng không đúng trong khối %ld"
 
-#: ../memline.c:2999
 msgid "Stack size increases"
 msgstr "Kích thước của đống tăng lên"
 
-#: ../memline.c:3038
-msgid "E317: pointer block id wrong 2"
+# TODO: Capitalise first word of message?
+msgid "E317: Pointer block id wrong 2"
 msgstr "E317: Giá trị của cái chỉ (pointer) khối số 2 không đúng"
 
-#: ../memline.c:3070
-#, c-format
-msgid "E773: Symlink loop for \"%s\""
-msgstr ""
-
-#: ../memline.c:3221
 msgid "E325: ATTENTION"
 msgstr "E325: CHÚ Ý"
 
-#: ../memline.c:3222
 msgid ""
 "\n"
 "Found a swap file by the name \""
@@ -3975,45 +3011,37 @@ msgstr ""
 "\n"
 "Tìm thấy một tập tin trao đổi (swap) với tên \""
 
-#: ../memline.c:3226
 msgid "While opening file \""
 msgstr "Khi mở tập tin: \""
 
-#: ../memline.c:3239
 msgid "      NEWER than swap file!\n"
 msgstr "                    MỚI hơn so với tập tin trao đổi (swap)\n"
 
-#: ../memline.c:3244
-#, fuzzy
 msgid ""
 "\n"
-"(1) Another program may be editing the same file.  If this is the case,\n"
-"    be careful not to end up with two different instances of the same\n"
-"    file when making changes."
+"(1) Another program may be editing the same file.\n"
+"    If this is the case, be careful not to end up with two\n"
+"    different instances of the same file when making changes.\n"
 msgstr ""
 "\n"
 "(1) Rất có thể một chương trình khác đang soạn thảo tập tin.\n"
 "    Nếu như vậy, hãy cẩn thận khi thay đổi, làm sao để không thu\n"
 "    được hai phương án khác nhau của cùng một tập tin.\n"
 
-#: ../memline.c:3245
-#, fuzzy
-msgid "  Quit, or continue with caution.\n"
+msgid "    Quit, or continue with caution.\n"
 msgstr "    Thoát hoặc tiếp tục với sự cẩn thận.\n"
 
-#: ../memline.c:3246
-#, fuzzy
-msgid "(2) An edit session for this file crashed.\n"
+msgid ""
+"\n"
+"(2) An edit session for this file crashed.\n"
 msgstr ""
 "\n"
 "(2) Lần soạn thảo trước của tập tin này gặp sự cố.\n"
 
-#: ../memline.c:3247
-msgid "    If this is the case, use \":recover\" or \"nvim -r "
+msgid "    If this is the case, use \":recover\" or \"vim -r "
 msgstr ""
-"    Trong trường hợp này, hãy sử dụng câu lệnh \":recover\" hoặc \"nvim -r "
+"    Trong trường hợp này, hãy sử dụng câu lệnh \":recover\" hoặc \"vim -r "
 
-#: ../memline.c:3249
 msgid ""
 "\"\n"
 "    to recover the changes (see \":help recovery\").\n"
@@ -4021,12 +3049,10 @@ msgstr ""
 "\"\n"
 "    để phục hồi những thay đổi (hãy xem \":help recovery\").\n"
 
-#: ../memline.c:3250
 msgid "    If you did this already, delete the swap file \""
 msgstr ""
 "    Nếu đã thực hiện thao tác này rồi, thì hãy xóa tập tin trao đổi (swap) \""
 
-#: ../memline.c:3252
 msgid ""
 "\"\n"
 "    to avoid this message.\n"
@@ -4034,23 +3060,18 @@ msgstr ""
 "\"\n"
 "    để tránh sự xuất hiện của thông báo này trong tương lai.\n"
 
-#: ../memline.c:3450 ../memline.c:3452
 msgid "Swap file \""
 msgstr "Tập tin trao đổi (swap) \""
 
-#: ../memline.c:3451 ../memline.c:3455
 msgid "\" already exists!"
 msgstr "\" đã có rồi!"
 
-#: ../memline.c:3457
 msgid "VIM - ATTENTION"
 msgstr "VIM - CHÚ Ý"
 
-#: ../memline.c:3459
 msgid "Swap file already exists!"
 msgstr "Tập tin trao đổi (swap) đã rồi!"
 
-#: ../memline.c:3464
 msgid ""
 "&Open Read-Only\n"
 "&Edit anyway\n"
@@ -4064,75 +3085,44 @@ msgstr ""
 "&Q Thoát\n"
 "&A Gián đoạn"
 
-#: ../memline.c:3467
-#, fuzzy
 msgid ""
 "&Open Read-Only\n"
 "&Edit anyway\n"
 "&Recover\n"
-"&Delete it\n"
 "&Quit\n"
-"&Abort"
+"&Abort\n"
+"&Delete it"
 msgstr ""
 "&O Mở chỉ để đọc\n"
 "&E Vẫn soạn thảo\n"
 "&R Phục hồi\n"
 "&Q Thoát\n"
-"&A Gián đoạn"
+"&A Gián đoạn&D Xóa nó"
 
-#.
-#. * Change the ".swp" extension to find another file that can be used.
-#. * First decrement the last char: ".swo", ".swn", etc.
-#. * If that still isn't enough decrement the last but one char: ".svz"
-#. * Can happen when editing many "No Name" buffers.
-#.
-#. ".s?a"
-#. ".saa": tried enough, give up
-#: ../memline.c:3528
 msgid "E326: Too many swap files found"
 msgstr "E326: Tìm thấy quá nhiều tập tin trao đổi (swap)"
 
-#: ../memory.c:227
-#, c-format
-msgid "E342: Out of memory!  (allocating % bytes)"
-msgstr "E342: Không đủ bộ nhớ! (phân chia % byte)"
-
-#: ../menu.c:62
 msgid "E327: Part of menu-item path is not sub-menu"
 msgstr ""
 "E327: Một phần của đường dẫn tới phần tử của trình đơn không phải là trình "
 "đơn con"
 
-#: ../menu.c:63
 msgid "E328: Menu only exists in another mode"
 msgstr "E328: Trình đơn chỉ có trong chế độ khác"
 
-#: ../menu.c:64
-#, fuzzy, c-format
-msgid "E329: No menu \"%s\""
+msgid "E329: No menu of that name"
 msgstr "E329: Không có trình đơn với tên như vậy"
 
-#. Only a mnemonic or accelerator is not valid.
-#: ../menu.c:329
-msgid "E792: Empty menu name"
-msgstr ""
-
-#: ../menu.c:340
 msgid "E330: Menu path must not lead to a sub-menu"
 msgstr "E330: Đường dẫn tới trình đơn không được đưa tới trình đơn con"
 
-#: ../menu.c:365
 msgid "E331: Must not add menu items directly to menu bar"
 msgstr ""
 "E331: Các phần tử của trình đơn không thể thêm trực tiếp vào thanh trình đơn"
 
-#: ../menu.c:370
 msgid "E332: Separator cannot be part of a menu path"
 msgstr "E332: Cái phân chia không thể là một phần của đường dẫn tới trình đơn"
 
-#. Now we have found the matching menu, and we list the mappings
-#. Highlight title
-#: ../menu.c:762
 msgid ""
 "\n"
 "--- Menus ---"
@@ -4140,70 +3130,62 @@ msgstr ""
 "\n"
 "--- Trình đơn ---"
 
-#: ../menu.c:1313
+msgid "Tear off this menu"
+msgstr "Chia cắt trình đơn này"
+
 msgid "E333: Menu path must lead to a menu item"
 msgstr "E333: Đường dẫn tới trình đơn phải đưa tới một phần tử cuả trình đơn"
 
-#: ../menu.c:1330
 #, c-format
 msgid "E334: Menu not found: %s"
 msgstr "E334: Không tìm thấy trình đơn: %s"
 
-#: ../menu.c:1396
 #, c-format
 msgid "E335: Menu not defined for %s mode"
 msgstr "E335: Trình đơn không được định nghĩa cho chế độ %s"
 
-#: ../menu.c:1426
 msgid "E336: Menu path must lead to a sub-menu"
 msgstr "E336: Đường dẫn tới trình đơn phải đưa tới một trình đơn con"
 
-#: ../menu.c:1447
 msgid "E337: Menu not found - check menu names"
 msgstr "E337: Không tìm thấy trình đơn - hãy kiểm tra tên trình đơn"
 
-#: ../message.c:423
 #, c-format
 msgid "Error detected while processing %s:"
 msgstr "Phát hiện lỗi khi xử lý %s:"
 
-#: ../message.c:445
 #, c-format
 msgid "line %4ld:"
 msgstr "dòng %4ld:"
 
-#: ../message.c:617
-#, c-format
-msgid "E354: Invalid register name: '%s'"
-msgstr "E354: Tên sổ đăng ký không cho phép: '%s'"
+msgid "[string too long]"
+msgstr "[chuỗi quá dài]"
+
+msgid "Messages maintainer: The Vim Project"
+msgstr ""
+"Bản dịch các thông báo sang tiếng Việt: Phan Vĩnh Thịnh "
 
-#: ../message.c:986
 msgid "Interrupt: "
 msgstr "Gián đoạn: "
 
-#: ../message.c:988
-#, fuzzy
-msgid "Press ENTER or type command to continue"
-msgstr "Nhấn phím ENTER hoặc nhập câu lệnh để tiếp tục"
+msgid "Hit ENTER to continue"
+msgstr "Nhấn phím ENTER để tiếp tục"
 
-#: ../message.c:1843
-#, fuzzy, c-format
-msgid "%s line %"
-msgstr "%s, dòng %"
+msgid "Hit ENTER or type command to continue"
+msgstr "Nhấn phím ENTER hoặc nhập câu lệnh để tiếp tục"
 
-#: ../message.c:2392
 msgid "-- More --"
 msgstr "-- Còn nữa --"
 
-#: ../message.c:2398
-msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "
-msgstr ""
+msgid " (RET/BS: line, SPACE/b: page, d/u: half page, q: quit)"
+msgstr " (RET/BS: dòng, SPACE/b: trang, d/u: nửa trang, q: thoát)"
+
+msgid " (RET: line, SPACE: page, d: half page, q: quit)"
+msgstr " (RET: dòng, SPACE: trang, d: nửa trang, q: thoát)"
 
-#: ../message.c:3021 ../message.c:3031
 msgid "Question"
 msgstr "Câu hỏi"
 
-#: ../message.c:3023
 msgid ""
 "&Yes\n"
 "&No"
@@ -4211,17 +3193,6 @@ msgstr ""
 "&Có\n"
 "&Không"
 
-#: ../message.c:3033
-msgid ""
-"&Yes\n"
-"&No\n"
-"&Cancel"
-msgstr ""
-"&Có\n"
-"&Không\n"
-"&Dừng"
-
-#: ../message.c:3045
 msgid ""
 "&Yes\n"
 "&No\n"
@@ -4234,181 +3205,233 @@ msgstr ""
 "&Vứt bỏ tất cả\n"
 "&Dừng lại"
 
-#: ../message.c:3058
-#, fuzzy
-msgid "E766: Insufficient arguments for printf()"
-msgstr "E116: Tham số cho hàm %s đưa ra không đúng"
+msgid "Save File dialog"
+msgstr "Ghi nhớ tập tin"
+
+msgid "Open File dialog"
+msgstr "Mở tập tin"
 
-#: ../message.c:3119
-msgid "E807: Expected Float argument for printf()"
+msgid "E338: Sorry, no file browser in console mode"
 msgstr ""
+"E338: Xin lỗi nhưng không có trình duyệt tập tin trong chế độ kênh giao tác "
+"(console)"
 
-#: ../message.c:3873
-#, fuzzy
-msgid "E767: Too many arguments to printf()"
-msgstr "E118: Quá nhiều tham số cho hàm: %s"
-
-#: ../misc1.c:2256
 msgid "W10: Warning: Changing a readonly file"
 msgstr "W10: Cảnh báo: Thay đổi một tập tin chỉ có quyền đọc"
 
-#: ../misc1.c:2537
-msgid "Type number and  or click with mouse (empty cancels): "
-msgstr ""
-
-#: ../misc1.c:2539
-msgid "Type number and  (empty cancels): "
-msgstr ""
-
-#: ../misc1.c:2585
 msgid "1 more line"
 msgstr "Thêm 1 dòng"
 
-#: ../misc1.c:2588
 msgid "1 line less"
 msgstr "Bớt 1 dòng"
 
-#: ../misc1.c:2593
 #, c-format
-msgid "% more lines"
-msgstr "Thêm % dòng"
+msgid "%ld more lines"
+msgstr "Thêm %ld dòng"
 
-#: ../misc1.c:2596
 #, c-format
-msgid "% fewer lines"
-msgstr "Bớt % dòng"
+msgid "%ld fewer lines"
+msgstr "Bớt %ld dòng"
 
-#: ../misc1.c:2599
 msgid " (Interrupted)"
 msgstr " (Bị gián đoạn)"
 
-#: ../misc1.c:2635
-msgid "Beep!"
+msgid "Vim: preserving files...\n"
+msgstr "Vim: ghi nhớ các tập tin...\n"
+
+msgid "Vim: Finished.\n"
+msgstr "Vim: Đã xong.\n"
+
+msgid "ERROR: "
+msgstr "LỖI: "
+
+#, c-format
+msgid ""
+"\n"
+"[bytes] total alloc-freed %lu-%lu, in use %lu, peak use %lu\n"
+msgstr ""
+"\n"
+"[byte] tổng phân phối-còn trống %lu-%lu, sử dụng %lu, píc sử dụng %lu\n"
+
+#, c-format
+msgid ""
+"[calls] total re/malloc()'s %lu, total free()'s %lu\n"
+"\n"
 msgstr ""
+"[gọi] tổng re/malloc() %lu, tổng free() %lu\n"
+"\n"
+
+msgid "E340: Line is becoming too long"
+msgstr "E340: Dòng đang trở thành quá dài"
+
+#, c-format
+msgid "E341: Internal error: lalloc(%ld, )"
+msgstr "E341: Lỗi nội bộ: lalloc(%ld, )"
+
+#, c-format
+msgid "E342: Out of memory!  (allocating %lu bytes)"
+msgstr "E342: Không đủ bộ nhớ! (phân chia %lu byte)"
 
-#: ../misc2.c:738
 #, c-format
 msgid "Calling shell to execute: \"%s\""
 msgstr "Gọi shell để thực hiện: \"%s\""
 
-#: ../normal.c:183
-msgid "E349: No identifier under cursor"
-msgstr "E349: Không có tên ở vị trí con trỏ"
+msgid "E545: Missing colon"
+msgstr "E545: Thiếu dấu hai chấm"
 
-#: ../normal.c:1866
-#, fuzzy
-msgid "E774: 'operatorfunc' is empty"
-msgstr "E91: Tùy chọn 'shell' là một chuỗi rỗng"
+msgid "E546: Illegal mode"
+msgstr "E546: Chế độ không cho phép"
+
+msgid "E547: Illegal mouseshape"
+msgstr "E547: Dạng trỏ chuột không cho phép"
+
+# TODO: Capitalise first word of message?
+msgid "E548: Digit expected"
+msgstr "E548: yêu cầu một số"
+
+msgid "E549: Illegal percentage"
+msgstr "E549: Tỷ lệ phần trăm không cho phép"
+
+msgid "Enter encryption key: "
+msgstr "Nhập mật khẩu để mã hóa: "
+
+msgid "Enter same key again: "
+msgstr "      Nhập lại mật khẩu:"
+
+msgid "Keys don't match!"
+msgstr "Hai mật khẩu không trùng nhau!"
+
+#, c-format
+msgid ""
+"E343: Invalid path: '**[number]' must be at the end of the path or be "
+"followed by '%s'."
+msgstr ""
+"E343: Đường dẫn đưa ra không đúng: '**[số]' phải ở cuối đường dẫn hoặc theo "
+"sau bởi '%s'"
+
+#, c-format
+msgid "E344: Can't find directory \"%s\" in cdpath"
+msgstr "E344: Không tìm thấy thư mục \"%s\" để chuyển thư mục"
+
+#, c-format
+msgid "E345: Can't find file \"%s\" in path"
+msgstr "E345: Không tìm thấy tập tin \"%s\" trong đường dẫn"
+
+#, c-format
+msgid "E346: No more directory \"%s\" found in cdpath"
+msgstr "E346: Trong đường dẫn thay đổi thư mục không còn có thư mục \"%s\" nữa"
+
+#, c-format
+msgid "E347: No more file \"%s\" found in path"
+msgstr "E347: Trong đường dẫn path không còn có tập tin \"%s\" nữa"
+
+msgid "E550: Missing colon"
+msgstr "E550: Thiếu dấu hai chấm"
+
+msgid "E551: Illegal component"
+msgstr "E551: Thành phần không cho phép"
+
+# TODO: Capitalise first word of message?
+msgid "E552: Digit expected"
+msgstr "E552: Cần chỉ ra một số"
+
+msgid "Cannot connect to Netbeans #2"
+msgstr "Không kết nối được với Netbeans #2"
+
+msgid "Cannot connect to Netbeans"
+msgstr "Không kết nối được với NetBeans"
+
+#, c-format
+msgid "E668: Wrong access mode for NetBeans connection info file: \"%s\""
+msgstr ""
+"E668: Chế độ truy cập thông tin về liên kết với NetBeans không đúng: \"%s\""
+
+msgid "read from Netbeans socket"
+msgstr "đọc từ socket NetBeans"
+
+#, c-format
+msgid "E658: NetBeans connection lost for buffer %ld"
+msgstr "E658: Bị mất liên kết với NetBeans cho bộ đệm %ld"
 
-#: ../normal.c:2637
 msgid "Warning: terminal cannot highlight"
 msgstr "Cảnh báo: terminal không thực hiện được sự chiếu sáng"
 
-#: ../normal.c:2807
 msgid "E348: No string under cursor"
 msgstr "E348: Không có chuỗi ở vị trí con trỏ"
 
-#: ../normal.c:3937
+msgid "E349: No identifier under cursor"
+msgstr "E349: Không có tên ở vị trí con trỏ"
+
 msgid "E352: Cannot erase folds with current 'foldmethod'"
 msgstr ""
 "E352: Không thể tẩy xóa nếp gấp với giá trị hiện thời của tùy chọn "
 "'foldmethod'"
 
-#: ../normal.c:5897
-msgid "E664: changelist is empty"
+# TODO: Capitalise first word of message?
+msgid "E664: Changelist is empty"
 msgstr "E664: danh sách những thay đổi trống rỗng"
 
-#: ../normal.c:5899
 msgid "E662: At start of changelist"
 msgstr "E662: Ở đầu danh sách những thay đổi"
 
-#: ../normal.c:5901
 msgid "E663: At end of changelist"
 msgstr "E663: Ở cuối danh sách những thay đổi"
 
-#: ../normal.c:7053
-msgid "Type  :quit  to exit Nvim"
+msgid "Type  :quit  to exit Vim"
 msgstr "Gõ :quit  để thoát khỏi Vim"
 
-#: ../ops.c:248
 #, c-format
 msgid "1 line %sed 1 time"
 msgstr "Trên 1 dòng %s 1 lần"
 
-#: ../ops.c:250
 #, c-format
 msgid "1 line %sed %d times"
 msgstr "Trên 1 dòng %s %d lần"
 
-#: ../ops.c:253
 #, c-format
-msgid "% lines %sed 1 time"
-msgstr "Trên % dòng %s 1 lần"
+msgid "%ld lines %sed 1 time"
+msgstr "Trên %ld dòng %s 1 lần"
 
-#: ../ops.c:256
 #, c-format
-msgid "% lines %sed %d times"
-msgstr "Trên % dòng %s %d lần"
+msgid "%ld lines %sed %d times"
+msgstr "Trên %ld dòng %s %d lần"
 
-#: ../ops.c:592
 #, c-format
-msgid "% lines to indent... "
-msgstr "Thụt đầu % dòng..."
+msgid "%ld lines to indent... "
+msgstr "Thụt đầu %ld dòng..."
 
-#: ../ops.c:634
 msgid "1 line indented "
 msgstr "Đã thụt đầu 1 dòng"
 
-#: ../ops.c:636
 #, c-format
-msgid "% lines indented "
-msgstr "% dòng đã thụt đầu"
-
-#: ../ops.c:938
-#, fuzzy
-msgid "E748: No previously used register"
-msgstr "E186: Không có thư mục trước"
+msgid "%ld lines indented "
+msgstr "%ld dòng đã thụt đầu"
 
-#. must display the prompt
-#: ../ops.c:1433
 msgid "cannot yank; delete anyway"
 msgstr "sao chép không thành công; đã xóa"
 
-#: ../ops.c:1929
 msgid "1 line changed"
 msgstr "1 dòng đã thay đổi"
 
-#: ../ops.c:1931
 #, c-format
-msgid "% lines changed"
-msgstr "% đã thay đổi"
+msgid "%ld lines changed"
+msgstr "%ld đã thay đổi"
 
-#: ../ops.c:2521
-#, fuzzy
-msgid "block of 1 line yanked"
-msgstr "đã sao chép 1 dòng"
+#, c-format
+msgid "freeing %ld lines"
+msgstr "đã làm sạch %ld dòng"
 
-#: ../ops.c:2523
 msgid "1 line yanked"
 msgstr "đã sao chép 1 dòng"
 
-#: ../ops.c:2525
-#, fuzzy, c-format
-msgid "block of % lines yanked"
-msgstr "đã sao chép % dòng"
-
-#: ../ops.c:2528
 #, c-format
-msgid "% lines yanked"
-msgstr "đã sao chép % dòng"
+msgid "%ld lines yanked"
+msgstr "đã sao chép %ld dòng"
 
-#: ../ops.c:2710
 #, c-format
 msgid "E353: Nothing in register %s"
 msgstr "E353: Trong sổ đăng ký %s không có gì hết"
 
-#. Highlight title
-#: ../ops.c:3185
 msgid ""
 "\n"
 "--- Registers ---"
@@ -4416,11 +3439,9 @@ msgstr ""
 "\n"
 "--- Sổ đăng ký ---"
 
-#: ../ops.c:4455
 msgid "Illegal register name"
 msgstr "Tên sổ đăng ký không cho phép"
 
-#: ../ops.c:4533
 msgid ""
 "\n"
 "# Registers:\n"
@@ -4428,194 +3449,154 @@ msgstr ""
 "\n"
 "# Sổ đăng ký:\n"
 
-#: ../ops.c:4575
 #, c-format
 msgid "E574: Unknown register type %d"
 msgstr "E574: Loại sổ đăng ký không biết %d"
 
-#: ../ops.c:5089
 #, c-format
-msgid "% Cols; "
-msgstr "% Cột; "
+msgid "E354: Invalid register name: '%s'"
+msgstr "E354: Tên sổ đăng ký không cho phép: '%s'"
 
-#: ../ops.c:5097
 #, c-format
-msgid ""
-"Selected %s% of % Lines; % of % Words; "
-"% of % Bytes"
-msgstr ""
-"Chọn %s% của % Dòng; % của % Từ; % "
-"của % Byte"
-
-#: ../ops.c:5105
-#, fuzzy, c-format
-msgid ""
-"Selected %s% of % Lines; % of % Words; "
-"% of % Chars; % of % Bytes"
-msgstr ""
-"Chọn %s% của % Dòng; % của % Từ; % "
-"của % Byte"
+msgid "%ld Cols; "
+msgstr "%ld Cột; "
 
-#: ../ops.c:5123
 #, c-format
-msgid ""
-"Col %s of %s; Line % of %; Word % of %; Byte "
-"% of %"
-msgstr ""
-"Cột %s của %s;  Dòng % của %; Từ % của %; "
-"Byte % của %"
+msgid "Selected %s%ld of %ld Lines; %ld of %ld Words; %ld of %ld Bytes"
+msgstr "Chọn %s%ld của %ld Dòng; %ld của %ld Từ; %ld của %ld Byte"
 
-#: ../ops.c:5133
-#, fuzzy, c-format
-msgid ""
-"Col %s of %s; Line % of %; Word % of %; Char "
-"% of %; Byte % of %"
-msgstr ""
-"Cột %s của %s;  Dòng % của %; Từ % của %; "
-"Byte % của %"
+#, c-format
+msgid "Col %s of %s; Line %ld of %ld; Word %ld of %ld; Byte %ld of %ld"
+msgstr "Cột %s của %s;  Dòng %ld của %ld; Từ %ld của %ld; Byte %ld của %ld"
 
-#: ../ops.c:5146
 #, c-format
-msgid "(+% for BOM)"
-msgstr "(+% cho BOM)"
+msgid "(+%ld for BOM)"
+msgstr "(+%ld cho BOM)"
 
-#: ../option.c:1238
 msgid "%<%f%h%m%=Page %N"
 msgstr "%<%f%h%m%=Trang %N"
 
-#: ../option.c:1574
 msgid "Thanks for flying Vim"
 msgstr "Xin cảm ơn đã sử dụng Vim"
 
-#. found a mismatch: skip
-#: ../option.c:2698
 msgid "E518: Unknown option"
 msgstr "E518: Tùy chọn không biết"
 
-#: ../option.c:2709
 msgid "E519: Option not supported"
 msgstr "E519: Tùy chọn không được hỗ trợ"
 
-#: ../option.c:2740
 msgid "E520: Not allowed in a modeline"
 msgstr "E520: Không cho phép trên dòng chế độ (modeline)"
 
-#: ../option.c:2815
-msgid "E846: Key code not set"
+msgid ""
+"\n"
+"\tLast set from "
 msgstr ""
+"\n"
+"\tLần cuối cùng tùy chọn thay đổi vào "
 
-#: ../option.c:2924
 msgid "E521: Number required after ="
 msgstr "E521: Sau dấu = cần đưa ra một số"
 
-#: ../option.c:3226 ../option.c:3864
 msgid "E522: Not found in termcap"
 msgstr "E522: Không tìm thấy trong termcap"
 
-#: ../option.c:3335
 #, c-format
 msgid "E539: Illegal character <%s>"
 msgstr "E539: Ký tự không cho phép <%s>"
 
-#: ../option.c:3862
 msgid "E529: Cannot set 'term' to empty string"
 msgstr "E529: Giá trị của tùy chọn 'term' không thể là một chuỗi trống rỗng"
 
-#: ../option.c:3885
+msgid "E530: Cannot change term in GUI"
+msgstr "E530: Không thể thay đổi terminal trong giao diện đồ họa GUI"
+
+msgid "E531: Use \":gui\" to start the GUI"
+msgstr "E531: Hãy sử dụng \":gui\" để chạy giao diện đồ họa GUI"
+
 msgid "E589: 'backupext' and 'patchmode' are equal"
 msgstr "E589: giá trị của tùy chọn 'backupext' và 'patchmode' bằng nhau"
 
-#: ../option.c:3964
-msgid "E834: Conflicts with value of 'listchars'"
-msgstr ""
-
-#: ../option.c:3966
-msgid "E835: Conflicts with value of 'fillchars'"
-msgstr ""
+msgid "E617: Cannot be changed in the GTK+ 2 GUI"
+msgstr "E617: Không thể thay đổi trong giao diện đồ họa GTK+ 2"
 
-#: ../option.c:4163
 msgid "E524: Missing colon"
 msgstr "E524: Thiếu dấu hai chấm"
 
-#: ../option.c:4165
 msgid "E525: Zero length string"
 msgstr "E525: Chuỗi có độ dài bằng không"
 
-#: ../option.c:4220
 #, c-format
 msgid "E526: Missing number after <%s>"
 msgstr "E526: Thiếu một số sau <%s>"
 
-#: ../option.c:4232
 msgid "E527: Missing comma"
 msgstr "E527: Thiếu dấu phẩy"
 
-#: ../option.c:4239
 msgid "E528: Must specify a ' value"
 msgstr "E528: Cần đưa ra một giá trị cho '"
 
-#: ../option.c:4271
 msgid "E595: contains unprintable or wide character"
 msgstr "E595: chứa ký tự không in ra hoặc ký tự với chiều rộng gấp đôi"
 
-#: ../option.c:4469
+msgid "E596: Invalid font(s)"
+msgstr "E596: Phông chữ không đúng"
+
+# TODO: Capitalise first word of message?
+msgid "E597: Can't select fontset"
+msgstr "E597: không chọn được bộ phông chữ"
+
+msgid "E598: Invalid fontset"
+msgstr "E598: Bộ phông chữ không đúng"
+
+# TODO: Capitalise first word of message?
+msgid "E533: Can't select wide font"
+msgstr "E533: không chọn được phông chữ với các ký tự có chiều rộng gấp đôi"
+
+msgid "E534: Invalid wide font"
+msgstr "E534: Phông chữ, với ký tự có chiều rộng gấp đôi, không đúng"
+
 #, c-format
 msgid "E535: Illegal character after <%c>"
 msgstr "E535: Ký tự sau <%c> không chính xác"
 
-#: ../option.c:4534
-msgid "E536: comma required"
+# TODO: Capitalise first word of message?
+msgid "E536: Comma required"
 msgstr "E536: cầu có dấu phẩy"
 
-#: ../option.c:4543
 #, c-format
 msgid "E537: 'commentstring' must be empty or contain %s"
 msgstr "E537: Giá trị của tùy chọn 'commentstring' phải rỗng hoặc chứa %s"
 
-#: ../option.c:4928
+msgid "E538: No mouse support"
+msgstr "E538: Chuột không được hỗ trợ"
+
 msgid "E540: Unclosed expression sequence"
 msgstr "E540: Dãy các biểu thức không đóng"
 
-#: ../option.c:4932
-msgid "E541: too many items"
-msgstr "E541: quá nhiều phần tử"
 
-#: ../option.c:4934
-msgid "E542: unbalanced groups"
+# TODO: Capitalise first word of message?
+msgid "E542: Unbalanced groups"
 msgstr "E542: các nhóm không cân bằng"
 
-#: ../option.c:5148
 msgid "E590: A preview window already exists"
 msgstr "E590: Cửa sổ xem trước đã có"
 
-#: ../option.c:5311
 msgid "W17: Arabic requires UTF-8, do ':set encoding=utf-8'"
 msgstr "W17: Tiếng Ả Rập yêu cầu sử dụng UTF-8, hãy nhập ':set encoding=utf-8'"
 
-#: ../option.c:5623
 #, c-format
 msgid "E593: Need at least %d lines"
 msgstr "E593: Cần ít nhất %d dòng"
 
-#: ../option.c:5631
 #, c-format
 msgid "E594: Need at least %d columns"
 msgstr "E594: Cần ít nhất %d cột"
 
-#: ../option.c:6011
 #, c-format
 msgid "E355: Unknown option: %s"
 msgstr "E355: Tùy chọn không biết: %s"
 
-#. There's another character after zeros or the string
-#. * is empty.  In both cases, we are trying to set a
-#. * num option using a string.
-#: ../option.c:6037
-#, fuzzy, c-format
-msgid "E521: Number required: &%s = '%s'"
-msgstr "E521: Sau dấu = cần đưa ra một số"
-
-#: ../option.c:6149
 msgid ""
 "\n"
 "--- Terminal codes ---"
@@ -4623,7 +3604,6 @@ msgstr ""
 "\n"
 "--- Mã terminal ---"
 
-#: ../option.c:6151
 msgid ""
 "\n"
 "--- Global option values ---"
@@ -4631,7 +3611,6 @@ msgstr ""
 "\n"
 "--- Giá trị tùy chọn toàn cầu ---"
 
-#: ../option.c:6153
 msgid ""
 "\n"
 "--- Local option values ---"
@@ -4639,7 +3618,6 @@ msgstr ""
 "\n"
 "--- Giá trị tùy chọn nội bộ ---"
 
-#: ../option.c:6155
 msgid ""
 "\n"
 "--- Options ---"
@@ -4647,21 +3625,127 @@ msgstr ""
 "\n"
 "--- Tùy chọn ---"
 
-#: ../option.c:6816
 msgid "E356: get_varp ERROR"
 msgstr "E356: LỖI get_varp"
 
-#: ../option.c:7696
 #, c-format
 msgid "E357: 'langmap': Matching character missing for %s"
 msgstr "E357: 'langmap': Thiếu ký tự tương ứng cho %s"
 
-#: ../option.c:7715
 #, c-format
 msgid "E358: 'langmap': Extra characters after semicolon: %s"
 msgstr "E358: 'langmap': Thừa ký tự sau dấu chấm phẩy: %s"
 
-#: ../os/shell.c:194
+msgid "cannot open "
+msgstr "không mở được "
+
+msgid "VIM: Can't open window!\n"
+msgstr "VIM: Không mở được cửa sổ!\n"
+
+msgid "Need Amigados version 2.04 or later\n"
+msgstr "Cần Amigados phiên bản 2.04 hoặc mới hơn\n"
+
+#, c-format
+msgid "Need %s version %ld\n"
+msgstr "Cần %s phiên bản %ld\n"
+
+msgid "Cannot open NIL:\n"
+msgstr "Không mở được NIL:\n"
+
+msgid "Cannot create "
+msgstr "Không tạo được "
+
+#, c-format
+msgid "Vim exiting with %d\n"
+msgstr "Thoát Vim với mã %d\n"
+
+msgid "cannot change console mode ?!\n"
+msgstr "không thay đổi được chế độ kênh giao tác (console)?!\n"
+
+msgid "mch_get_shellsize: not a console??\n"
+msgstr "mch_get_shellsize: không phải là kênh giao tác (console)??\n"
+
+msgid "E360: Cannot execute shell with -f option"
+msgstr "E360: Không chạy được shell với tùy chọn -f"
+
+msgid "Cannot execute "
+msgstr "Không chạy được "
+
+msgid "shell "
+msgstr "shell "
+
+msgid " returned\n"
+msgstr " thoát\n"
+
+msgid "ANCHOR_BUF_SIZE too small."
+msgstr "Giá trị ANCHOR_BUF_SIZE quá nhỏ."
+
+msgid "I/O ERROR"
+msgstr "LỖI I/O (NHẬP/XUẤT)"
+
+msgid "...(truncated)"
+msgstr "...(bị cắt bớt)"
+
+msgid "'columns' is not 80, cannot execute external commands"
+msgstr "Tùy chọn 'columns' khác 80, chương trình ngoại trú không thể thực hiện"
+
+msgid "E237: Printer selection failed"
+msgstr "E237: Chọn máy in không thành công"
+
+#, c-format
+msgid "to %s on %s"
+msgstr "tới %s trên %s"
+
+#, c-format
+msgid "E613: Unknown printer font: %s"
+msgstr "E613: Không rõ phông chữ của máy in: %s"
+
+#, c-format
+msgid "E238: Print error: %s"
+msgstr "E238: Lỗi in: %s"
+
+msgid "Unknown"
+msgstr "Không rõ"
+
+#, c-format
+msgid "Printing '%s'"
+msgstr "Đang in '%s'"
+
+#, c-format
+msgid "E244: Illegal charset name \"%s\" in font name \"%s\""
+msgstr "E244: Tên bảng mã không cho phép \"%s\" trong tên phông chữ \"%s\""
+
+#, c-format
+msgid "E245: Illegal char '%c' in font name \"%s\""
+msgstr "E245: Ký tự không cho phép '%c' trong tên phông chữ \"%s\""
+
+msgid "Vim: Double signal, exiting\n"
+msgstr "Vim: Tín hiệu đôi, thoát\n"
+
+#, c-format
+msgid "Vim: Caught deadly signal %s\n"
+msgstr "Vim: Nhận được tín hiệu chết %s\n"
+
+msgid "Vim: Caught deadly signal\n"
+msgstr "Vim: Nhận được tín hiệu chết\n"
+
+#, c-format
+msgid "Opening the X display took %ld msec"
+msgstr "Mở màn hình X mất %ld mili giây"
+
+msgid ""
+"\n"
+"Vim: Got X error\n"
+msgstr ""
+"\n"
+"Vim: Lỗi X\n"
+
+msgid "Testing the X display failed"
+msgstr "Kiểm tra màn hình X không thành công"
+
+msgid "Opening the X display timed out"
+msgstr "Không mở được màn hình X trong thời gian cho phép (time out)"
+
 msgid ""
 "\n"
 "Cannot execute shell "
@@ -4669,7 +3753,13 @@ msgstr ""
 "\n"
 "Không chạy được shell "
 
-#: ../os/shell.c:439
+msgid ""
+"\n"
+"Cannot execute shell sh\n"
+msgstr ""
+"\n"
+"Không chạy được shell sh\n"
+
 msgid ""
 "\n"
 "shell returned "
@@ -4677,3060 +3767,1364 @@ msgstr ""
 "\n"
 "shell dừng làm việc "
 
-#: ../os_unix.c:465 ../os_unix.c:471
 msgid ""
 "\n"
-"Could not get security context for "
+"Cannot create pipes\n"
 msgstr ""
+"\n"
+"Không tạo được đường ống (pipe)\n"
 
-#: ../os_unix.c:479
 msgid ""
 "\n"
-"Could not set security context for "
+"Cannot fork\n"
 msgstr ""
+"\n"
+"Không thực hiện được fork()\n"
 
-#: ../os_unix.c:1558 ../os_unix.c:1647
-#, c-format
-msgid "dlerror = \"%s\""
+msgid ""
+"\n"
+"Command terminated\n"
 msgstr ""
+"\n"
+"Câu lệnh bị gián đoạn\n"
+
+msgid "XSMP lost ICE connection"
+msgstr "XSMP mất kết nối ICE"
+
+msgid "Opening the X display failed"
+msgstr "Mở màn hình X không thành công"
+
+msgid "XSMP handling save-yourself request"
+msgstr "XSMP xử lý yêu cầu tự động ghi nhớ"
+
+msgid "XSMP opening connection"
+msgstr "XSMP mở kết nối"
+
+msgid "XSMP ICE connection watch failed"
+msgstr "XSMP mất theo dõi kết nối ICE"
 
-#: ../path.c:1449
 #, c-format
-msgid "E447: Can't find file \"%s\" in path"
-msgstr "E447: Không tìm thấy tập tin \"%s\" trong đường dẫn"
+msgid "XSMP SmcOpenConnection failed: %s"
+msgstr "XSMP thực hiện SmcOpenConnection không thành công: %s"
+
+msgid "At line"
+msgstr "Tại dòng"
+
+msgid "Could not allocate memory for command line."
+msgstr "Không phân chia được bộ nhớ cho dòng lệnh."
+
+msgid "VIM Error"
+msgstr "Lỗi VIM"
+
+msgid "Could not load vim32.dll!"
+msgstr "Không nạp được vim32.dll!"
+
+msgid "Could not fix up function pointers to the DLL!"
+msgstr "Không sửa được cái chỉ (pointer) hàm số tới DLL!"
+
+#, c-format
+msgid "shell returned %d"
+msgstr "thoát shell với mã %d"
+
+#, c-format
+msgid "Vim: Caught %s event\n"
+msgstr "Vim: Nhận được sự kiện %s\n"
+
+msgid "close"
+msgstr "đóng"
+
+msgid "logoff"
+msgstr "thoát"
+
+msgid "shutdown"
+msgstr "tắt máy"
+
+msgid "E371: Command not found"
+msgstr "E371: Câu lệnh không tìm thấy"
+
+msgid ""
+"VIMRUN.EXE not found in your $PATH.\n"
+"External commands will not pause after completion.\n"
+"See  :help win32-vimrun  for more information."
+msgstr ""
+"Không tìm thấy VIMRUN.EXE trong $PATH.\n"
+"Lệnh ngoại trú sẽ không dừng lại sau khi hoàn thành.\n"
+"Thông tin chi tiết xem trong :help win32-vimrun"
+
+msgid "Vim Warning"
+msgstr "Cảnh báo Vim"
 
-#: ../quickfix.c:359
 #, c-format
 msgid "E372: Too many %%%c in format string"
 msgstr "E372: Quá nhiều %%%c trong chuỗi định dạng"
 
-#: ../quickfix.c:371
 #, c-format
 msgid "E373: Unexpected %%%c in format string"
 msgstr "E373: Không mong đợi %%%c trong chuỗi định dạng"
 
-#: ../quickfix.c:420
 msgid "E374: Missing ] in format string"
 msgstr "E374: Thiếu ] trong chuỗi định dạng"
 
-#: ../quickfix.c:431
 #, c-format
 msgid "E375: Unsupported %%%c in format string"
 msgstr "E375: %%%c không được hỗ trợ trong chuỗi định dạng"
 
-#: ../quickfix.c:448
 #, c-format
 msgid "E376: Invalid %%%c in format string prefix"
 msgstr "E376: Không cho phép %%%c trong tiền tố của chuỗi định dạng"
 
-#: ../quickfix.c:454
 #, c-format
 msgid "E377: Invalid %%%c in format string"
 msgstr "E377: Không cho phép %%%c trong chuỗi định dạng"
 
-#. nothing found
-#: ../quickfix.c:477
 msgid "E378: 'errorformat' contains no pattern"
 msgstr "E378: Trong giá trị 'errorformat' thiếu mẫu (pattern)"
 
-#: ../quickfix.c:695
 msgid "E379: Missing or empty directory name"
 msgstr "E379: Tên thư mục không được đưa ra hoặc bằng một chuỗi rỗng"
 
-#: ../quickfix.c:1305
 msgid "E553: No more items"
 msgstr "E553: Không còn phần tử nào nữa"
 
-#: ../quickfix.c:1674
 #, c-format
 msgid "(%d of %d)%s%s: "
 msgstr "(%d của %d)%s%s: "
 
-#: ../quickfix.c:1676
 msgid " (line deleted)"
 msgstr " (dòng bị xóa)"
 
-#: ../quickfix.c:1863
 msgid "E380: At bottom of quickfix stack"
 msgstr "E380: Ở dưới của đống sửa nhanh"
 
-#: ../quickfix.c:1869
 msgid "E381: At top of quickfix stack"
 msgstr "E381: Ở đầu của đống sửa nhanh"
 
-#: ../quickfix.c:1880
 #, c-format
 msgid "error list %d of %d; %d errors"
 msgstr "danh sách lỗi %d của %d; %d lỗi"
 
-#: ../quickfix.c:2427
 msgid "E382: Cannot write, 'buftype' option is set"
 msgstr "E382: Không ghi nhớ được, giá trị 'buftype' không phải là chuỗi rỗng"
 
-#: ../quickfix.c:2812
-msgid "E683: File name missing or invalid pattern"
-msgstr ""
-
-#: ../quickfix.c:2911
-#, fuzzy, c-format
-msgid "Cannot open file \"%s\""
-msgstr "E624: Không thể mở tập tin \"%s\""
+# TODO: Capitalise first word of message?
+msgid "E369: Invalid item in %s%%[]"
+msgstr "E369: phần tử không cho phép trong %s%%[]"
 
-#: ../quickfix.c:3429
-#, fuzzy
-msgid "E681: Buffer is not loaded"
-msgstr "1 bộ đệm được bỏ nạp từ bộ nhớ"
+msgid "E339: Pattern too long"
+msgstr "E339: Mẫu (pattern) quá dài"
 
-#: ../quickfix.c:3487
-#, fuzzy
-msgid "E777: String or List expected"
-msgstr "E548: yêu cầu một số"
+msgid "E50: Too many \\z("
+msgstr "E50: Quá nhiều \\z("
 
-#: ../regexp.c:359
 #, c-format
-msgid "E369: invalid item in %s%%[]"
-msgstr "E369: phần tử không cho phép trong %s%%[]"
+msgid "E51: Too many %s("
+msgstr "E51: Quá nhiều %s("
 
-#: ../regexp.c:374
-#, fuzzy, c-format
-msgid "E769: Missing ] after %s["
-msgstr "E69: Thiếu ] sau %s%%["
+msgid "E52: Unmatched \\z("
+msgstr "E52: Không có cặp cho \\z("
 
-#: ../regexp.c:375
 #, c-format
 msgid "E53: Unmatched %s%%("
 msgstr "E53: Không có cặp cho %s%%("
 
-#: ../regexp.c:376
 #, c-format
 msgid "E54: Unmatched %s("
 msgstr "E54: Không có cặp cho %s("
 
-#: ../regexp.c:377
 #, c-format
 msgid "E55: Unmatched %s)"
 msgstr "E55: Không có cặp cho %s)"
 
-#: ../regexp.c:378
-msgid "E66: \\z( not allowed here"
-msgstr "E66: \\z( không thể sử dụng ở đây"
-
-#: ../regexp.c:379
-msgid "E67: \\z1 et al. not allowed here"
-msgstr "E67: \\z1 và tương tự không được sử dụng ở đây"
-
-#: ../regexp.c:380
-#, c-format
-msgid "E69: Missing ] after %s%%["
-msgstr "E69: Thiếu ] sau %s%%["
-
-#: ../regexp.c:381
 #, c-format
-msgid "E70: Empty %s%%[]"
-msgstr "E70: %s%%[] rỗng"
-
-#: ../regexp.c:1209 ../regexp.c:1224
-msgid "E339: Pattern too long"
-msgstr "E339: Mẫu (pattern) quá dài"
-
-#: ../regexp.c:1371
-msgid "E50: Too many \\z("
-msgstr "E50: Quá nhiều \\z("
+msgid "E56: %s* operand could be empty"
+msgstr "E56: operand %s* không thể rỗng"
 
-#: ../regexp.c:1378
 #, c-format
-msgid "E51: Too many %s("
-msgstr "E51: Quá nhiều %s("
+msgid "E57: %s+ operand could be empty"
+msgstr "E57: operand %s+ không thể rỗng"
 
-#: ../regexp.c:1427
-msgid "E52: Unmatched \\z("
-msgstr "E52: Không có cặp cho \\z("
+# TODO: Capitalise first word of message?
+msgid "E59: Invalid character after %s@"
+msgstr "E59: ký tự không cho phép sau %s@"
 
-#: ../regexp.c:1637
 #, c-format
-msgid "E59: invalid character after %s@"
-msgstr "E59: ký tự không cho phép sau %s@"
+msgid "E58: %s{ operand could be empty"
+msgstr "E58: operand %s{ không thể rỗng"
 
-#: ../regexp.c:1672
 #, c-format
 msgid "E60: Too many complex %s{...}s"
 msgstr "E60: Quá nhiều cấu trúc phức tạp %s{...}"
 
-#: ../regexp.c:1687
 #, c-format
 msgid "E61: Nested %s*"
 msgstr "E61: %s* lồng vào"
 
-#: ../regexp.c:1690
 #, c-format
 msgid "E62: Nested %s%c"
 msgstr "E62: %s%c lồng vào"
 
-#: ../regexp.c:1800
-msgid "E63: invalid use of \\_"
+# TODO: Capitalise first word of message?
+msgid "E63: Invalid use of \\_"
 msgstr "E63: không cho phép sử dụng \\_"
 
-#: ../regexp.c:1850
 #, c-format
 msgid "E64: %s%c follows nothing"
 msgstr "E64: %s%c không theo sau gì cả"
 
-#: ../regexp.c:1902
 msgid "E65: Illegal back reference"
 msgstr "E65: Không cho phép liên kết ngược lại"
 
-#: ../regexp.c:1943
+msgid "E66: \\z( not allowed here"
+msgstr "E66: \\z( không thể sử dụng ở đây"
+
+msgid "E67: \\z1 - \\z9 not allowed here"
+msgstr "E67: \\z1 và tương tự không được sử dụng ở đây"
+
 msgid "E68: Invalid character after \\z"
 msgstr "E68: Ký tự không cho phép sau \\z"
 
-#: ../regexp.c:2049 ../regexp_nfa.c:1296
-#, fuzzy, c-format
-msgid "E678: Invalid character after %s%%[dxouU]"
-msgstr "E71: Ký tự không cho phép sau %s%%"
+#, c-format
+msgid "E69: Missing ] after %s%%["
+msgstr "E69: Thiếu ] sau %s%%["
+
+#, c-format
+msgid "E70: Empty %s%%[]"
+msgstr "E70: %s%%[] rỗng"
 
-#: ../regexp.c:2107
 #, c-format
 msgid "E71: Invalid character after %s%%"
 msgstr "E71: Ký tự không cho phép sau %s%%"
 
-#: ../regexp.c:3017
 #, c-format
 msgid "E554: Syntax error in %s{...}"
 msgstr "E554: Lỗi cú pháp trong %s{...}"
 
-#: ../regexp.c:3805
+msgid "E361: Crash intercepted; regexp too complex?"
+msgstr "E361: Sự cố được ngăn chặn; biểu thức chính quy quá phức tạp?"
+
+# TODO: Capitalise first word of message?
+msgid "E363: Pattern caused out-of-stack error"
+msgstr "E363: sử dụng mẫu (pattern) gây ra lỗi out-of-stack"
+
 msgid "External submatches:\n"
 msgstr "Sự tương ứng con ngoài:\n"
 
-#: ../regexp.c:7022
-msgid ""
-"E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be "
-"used "
-msgstr ""
-
-#: ../regexp_nfa.c:239
-msgid "E865: (NFA) Regexp end encountered prematurely"
-msgstr ""
-
-#: ../regexp_nfa.c:240
-#, c-format
-msgid "E866: (NFA regexp) Misplaced %c"
-msgstr ""
-
-#: ../regexp_nfa.c:242
-#, c-format
-msgid "E877: (NFA regexp) Invalid character class: %"
-msgstr ""
-
-#: ../regexp_nfa.c:1261
-#, c-format
-msgid "E867: (NFA) Unknown operator '\\z%c'"
-msgstr ""
-
-#: ../regexp_nfa.c:1387
-#, c-format
-msgid "E867: (NFA) Unknown operator '\\%%%c'"
-msgstr ""
-
-#: ../regexp_nfa.c:1802
-#, c-format
-msgid "E869: (NFA) Unknown operator '\\@%c'"
-msgstr ""
-
-#: ../regexp_nfa.c:1831
-msgid "E870: (NFA regexp) Error reading repetition limits"
-msgstr ""
-
-#. Can't have a multi follow a multi.
-#: ../regexp_nfa.c:1895
-msgid "E871: (NFA regexp) Can't have a multi follow a multi !"
-msgstr ""
-
-#. Too many `('
-#: ../regexp_nfa.c:2037
-msgid "E872: (NFA regexp) Too many '('"
-msgstr ""
-
-#: ../regexp_nfa.c:2042
-#, fuzzy
-msgid "E879: (NFA regexp) Too many \\z("
-msgstr "E50: Quá nhiều \\z("
-
-#: ../regexp_nfa.c:2066
-msgid "E873: (NFA regexp) proper termination error"
-msgstr ""
-
-#: ../regexp_nfa.c:2599
-msgid "E874: (NFA) Could not pop the stack !"
-msgstr ""
-
-#: ../regexp_nfa.c:3298
-msgid ""
-"E875: (NFA regexp) (While converting from postfix to NFA), too many states "
-"left on stack"
-msgstr ""
-
-#: ../regexp_nfa.c:3302
-msgid "E876: (NFA regexp) Not enough space to store the whole NFA "
-msgstr ""
-
-#: ../regexp_nfa.c:4571 ../regexp_nfa.c:4869
-msgid ""
-"Could not open temporary log file for writing, displaying on stderr ... "
-msgstr ""
-
-#: ../regexp_nfa.c:4840
 #, c-format
-msgid "(NFA) COULD NOT OPEN %s !"
-msgstr ""
-
-#: ../regexp_nfa.c:6049
-#, fuzzy
-msgid "Could not open temporary log file for writing "
-msgstr "E214: Không tìm thấy tập tin tạm thời (temp) để ghi nhớ"
+msgid "+--%3ld lines folded "
+msgstr "+--%3ld dòng được gấp"
 
-#: ../screen.c:7435
 msgid " VREPLACE"
 msgstr " THAY THẾ ẢO"
 
-#: ../screen.c:7437
 msgid " REPLACE"
 msgstr " THAY THẾ"
 
-#: ../screen.c:7440
 msgid " REVERSE"
 msgstr " NGƯỢC LẠI"
 
-#: ../screen.c:7441
 msgid " INSERT"
 msgstr " CHÈN"
 
-#: ../screen.c:7443
 msgid " (insert)"
 msgstr " (chèn)"
 
-#: ../screen.c:7445
 msgid " (replace)"
 msgstr " (thay thế)"
 
-#: ../screen.c:7447
 msgid " (vreplace)"
 msgstr " (thay thế ảo)"
 
-#: ../screen.c:7449
 msgid " Hebrew"
 msgstr " Do thái"
 
-#: ../screen.c:7454
 msgid " Arabic"
 msgstr " Ả rập"
 
-#: ../screen.c:7456
 msgid " (lang)"
 msgstr " (ngôn ngữ)"
 
-#: ../screen.c:7459
 msgid " (paste)"
 msgstr " (dán)"
 
-#: ../screen.c:7469
 msgid " VISUAL"
 msgstr " CHẾ ĐỘ VISUAL"
 
-#: ../screen.c:7470
 msgid " VISUAL LINE"
 msgstr " DÒNG VISUAL"
 
-#: ../screen.c:7471
 msgid " VISUAL BLOCK"
 msgstr " KHỐI VISUAL"
 
-#: ../screen.c:7472
 msgid " SELECT"
 msgstr " LỰA CHỌN"
 
-#: ../screen.c:7473
 msgid " SELECT LINE"
 msgstr " LỰA CHỌN DÒNG"
 
-#: ../screen.c:7474
 msgid " SELECT BLOCK"
 msgstr " LỰA CHỌN KHỐI"
 
-#: ../screen.c:7486 ../screen.c:7541
 msgid "recording"
 msgstr "đang ghi"
 
-#: ../search.c:487
+msgid "search hit TOP, continuing at BOTTOM"
+msgstr "tìm kiếm sẽ được tiếp tục từ CUỐI tài liệu"
+
+msgid "search hit BOTTOM, continuing at TOP"
+msgstr "tìm kiếm sẽ được tiếp tục từ ĐẦU tài liệu"
+
 #, c-format
 msgid "E383: Invalid search string: %s"
 msgstr "E383: Chuỗi tìm kiếm không đúng: %s"
 
-#: ../search.c:832
-#, c-format
-msgid "E384: search hit TOP without match for: %s"
+# TODO: Capitalise first word of message?
+msgid "E384: Search hit TOP without match for: %s"
 msgstr "E384: tìm kiếm kết thúc ở ĐẦU tập tin; không tìm thấy %s"
 
-#: ../search.c:835
-#, c-format
-msgid "E385: search hit BOTTOM without match for: %s"
+# TODO: Capitalise first word of message?
+msgid "E385: Search hit BOTTOM without match for: %s"
 msgstr "E385: tìm kiếm kết thúc ở CUỐI tập tin; không tìm thấy %s"
 
-#: ../search.c:1200
 msgid "E386: Expected '?' or '/'  after ';'"
 msgstr "E386: Mong đợi nhập '?' hoặc '/' sau ';'"
 
-#: ../search.c:4085
 msgid " (includes previously listed match)"
 msgstr " (gồm cả những tương ứng đã liệt kê trước đây)"
 
-#. cursor at status line
-#: ../search.c:4104
 msgid "--- Included files "
 msgstr "--- Tập tin tính đến "
 
-#: ../search.c:4106
 msgid "not found "
 msgstr "không tìm thấy "
 
-#: ../search.c:4107
 msgid "in path ---\n"
 msgstr "trong đường dẫn ---\n"
 
-#: ../search.c:4168
 msgid "  (Already listed)"
 msgstr " (Đã liệt kê)"
 
-#: ../search.c:4170
 msgid "  NOT FOUND"
 msgstr " KHÔNG TÌM THẤY"
 
-#: ../search.c:4211
 #, c-format
 msgid "Scanning included file: %s"
 msgstr "Quét trong tập tin được tính đến: %s"
 
-#: ../search.c:4216
-#, fuzzy, c-format
-msgid "Searching included file %s"
-msgstr "Quét trong tập tin được tính đến: %s"
-
-#: ../search.c:4405
 msgid "E387: Match is on current line"
 msgstr "E387: Tương ứng nằm trên dòng hiện tại"
 
-#: ../search.c:4517
 msgid "All included files were found"
 msgstr "Tìm thấy tất cả các tập tin được tính đến"
 
-#: ../search.c:4519
 msgid "No included files"
 msgstr "Không có tập tin được tính đến"
 
-#: ../search.c:4527
 msgid "E388: Couldn't find definition"
 msgstr "E388: Không tìm thấy định nghĩa"
 
-#: ../search.c:4529
 msgid "E389: Couldn't find pattern"
 msgstr "E389: Không tìm thấy mẫu (pattern)"
 
-#: ../search.c:4668
-#, fuzzy
-msgid "Substitute "
-msgstr "1 thay thế"
+#, c-format
+msgid "E390: Illegal argument: %s"
+msgstr "E390: Tham số không cho phép: %s"
 
-#: ../search.c:4681
 #, c-format
-msgid ""
-"\n"
-"# Last %sSearch Pattern:\n"
-"~"
-msgstr ""
+msgid "E391: No such syntax cluster: %s"
+msgstr "E391: Không có cụm cú pháp như vậy: %s"
 
-#: ../spell.c:951
-#, fuzzy
-msgid "E759: Format error in spell file"
-msgstr "E297: Lỗi ghi nhớ tập tin trao đổi (swap)"
+msgid "No Syntax items defined for this buffer"
+msgstr "Không có phần tử cú pháp nào được định nghĩa cho bộ đệm này"
 
-#: ../spell.c:952
-msgid "E758: Truncated spell file"
-msgstr ""
+msgid "syncing on C-style comments"
+msgstr "Đồng bộ hóa theo chú thích kiểu C"
 
-#: ../spell.c:953
-#, c-format
-msgid "Trailing text in %s line %d: %s"
-msgstr ""
+msgid "no syncing"
+msgstr "không đồng bộ hóa"
 
-#: ../spell.c:954
-#, c-format
-msgid "Affix name too long in %s line %d: %s"
-msgstr ""
+msgid "syncing starts "
+msgstr "đồng bộ hóa bắt đầu "
 
-#: ../spell.c:955
-#, fuzzy
-msgid "E761: Format error in affix file FOL, LOW or UPP"
-msgstr "E431: Lỗi định dạng trong tập tin thẻ ghi \"%s\""
+msgid " lines before top line"
+msgstr " dòng trước dòng đầu tiên"
 
-#: ../spell.c:957
-msgid "E762: Character in FOL, LOW or UPP is out of range"
+msgid ""
+"\n"
+"--- Syntax sync items ---"
 msgstr ""
+"\n"
+"--- Phần tử đồng bộ hóa cú pháp ---"
 
-#: ../spell.c:958
-msgid "Compressing word tree..."
+msgid ""
+"\n"
+"syncing on items"
 msgstr ""
+"\n"
+"đồng bộ hóa theo phần tử"
 
-#: ../spell.c:1951
-msgid "E756: Spell checking is not enabled"
+msgid ""
+"\n"
+"--- Syntax items ---"
 msgstr ""
+"\n"
+"--- Phần tử cú pháp ---"
 
-#: ../spell.c:2249
 #, c-format
-msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\""
-msgstr ""
-
-#: ../spell.c:2473
-#, fuzzy, c-format
-msgid "Reading spell file \"%s\""
-msgstr "Đang sử dụng tập tin trao đổi (swap) \"%s\""
-
-#: ../spell.c:2496
-#, fuzzy
-msgid "E757: This does not look like a spell file"
-msgstr "E307: %s không phải là tập tin trao đổi (swap) của Vim"
+msgid "E392: No such syntax cluster: %s"
+msgstr "E392: Không có cụm cú pháp như vậy: %s"
 
-#: ../spell.c:2501
-msgid "E771: Old spell file, needs to be updated"
-msgstr ""
+msgid "minimal "
+msgstr "nhỏ nhất "
 
-#: ../spell.c:2504
-msgid "E772: Spell file is for newer version of Vim"
-msgstr ""
+msgid "maximal "
+msgstr "lớn nhất "
 
-#: ../spell.c:2602
-#, fuzzy
-msgid "E770: Unsupported section in spell file"
-msgstr "E297: Lỗi ghi nhớ tập tin trao đổi (swap)"
+msgid "; match "
+msgstr "; tương ứng "
 
-#: ../spell.c:3762
-#, fuzzy, c-format
-msgid "Warning: region %s not supported"
-msgstr "E519: Tùy chọn không được hỗ trợ"
+msgid " line breaks"
+msgstr " chuyển dòng"
 
-#: ../spell.c:4550
-#, fuzzy, c-format
-msgid "Reading affix file %s ..."
-msgstr "Tìm kiếm tập tin thẻ ghi %s"
+msgid "E393: group[t]here not accepted here"
+msgstr "E393: không được sử dụng group[t]here ở đây"
 
-#: ../spell.c:4589 ../spell.c:5635 ../spell.c:6140
 #, c-format
-msgid "Conversion failure for word in %s line %d: %s"
-msgstr ""
+msgid "E394: Didn't find region item for %s"
+msgstr "E394: Phần tử vùng cho %s không tìm thấy"
 
-#: ../spell.c:4630 ../spell.c:6170
-#, c-format
-msgid "Conversion in %s not supported: from %s to %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E395: Contains argument not accepted here"
+msgstr "E395: không được sử dụng tham số contains ở đây"
 
-#: ../spell.c:4642
-#, c-format
-msgid "Invalid value for FLAG in %s line %d: %s"
-msgstr ""
+msgid "E396: containedin argument not accepted here"
+msgstr "E396: không được sử dụng tham số containedin ở đây"
 
-#: ../spell.c:4655
-#, c-format
-msgid "FLAG after using flags in %s line %d: %s"
-msgstr ""
+msgid "E397: Filename required"
+msgstr "E397: Yêu cầu tên tập tin"
 
-#: ../spell.c:4723
 #, c-format
-msgid ""
-"Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line "
-"%d"
-msgstr ""
+msgid "E398: Missing '=': %s"
+msgstr "E398: Thiếu '=': %s"
 
-#: ../spell.c:4731
 #, c-format
-msgid ""
-"Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line "
-"%d"
-msgstr ""
+msgid "E399: Not enough arguments: syntax region %s"
+msgstr "E399: Không đủ tham số: vùng cú pháp %s"
 
-#: ../spell.c:4747
-#, c-format
-msgid "Wrong COMPOUNDRULES value in %s line %d: %s"
-msgstr ""
+msgid "E400: No cluster specified"
+msgstr "E400: Chưa chỉ ra cụm"
 
-#: ../spell.c:4771
 #, c-format
-msgid "Wrong COMPOUNDWORDMAX value in %s line %d: %s"
-msgstr ""
+msgid "E401: Pattern delimiter not found: %s"
+msgstr "E401: Không tìm thấy ký tự phân chia mẫu (pattern): %s"
 
-#: ../spell.c:4777
 #, c-format
-msgid "Wrong COMPOUNDMIN value in %s line %d: %s"
-msgstr ""
+msgid "E402: Garbage after pattern: %s"
+msgstr "E402: Rác ở sau mẫu (pattern): %s"
 
-#: ../spell.c:4783
-#, c-format
-msgid "Wrong COMPOUNDSYLMAX value in %s line %d: %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E403: syntax sync: Line continuations pattern specified twice"
+msgstr "E403: đồng bộ hóa cú pháp: mẫu tiếp tục của dòng chỉ ra hai lần"
 
-#: ../spell.c:4795
 #, c-format
-msgid "Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s"
-msgstr ""
+msgid "E404: Illegal arguments: %s"
+msgstr "E404: Tham số không cho phép: %s"
 
-#: ../spell.c:4847
 #, c-format
-msgid "Different combining flag in continued affix block in %s line %d: %s"
-msgstr ""
-
-#: ../spell.c:4850
-#, fuzzy, c-format
-msgid "Duplicate affix in %s line %d: %s"
-msgstr "E154: Thẻ ghi lặp lại \"%s\" trong tập tin %s"
+msgid "E405: Missing equal sign: %s"
+msgstr "E405: Thiếu dấu bằng: %s"
 
-#: ../spell.c:4871
 #, c-format
-msgid ""
-"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s "
-"line %d: %s"
-msgstr ""
+msgid "E406: Empty argument: %s"
+msgstr "E406: Tham số trống rỗng: %s"
 
-#: ../spell.c:4893
 #, c-format
-msgid "Expected Y or N in %s line %d: %s"
-msgstr ""
+msgid "E407: %s not allowed here"
+msgstr "E407: %s không được cho phép ở đây"
 
-#: ../spell.c:4968
 #, c-format
-msgid "Broken condition in %s line %d: %s"
-msgstr ""
+msgid "E408: %s must be first in contains list"
+msgstr "E408: %s phải là đầu tiên trong danh sách contains"
 
-#: ../spell.c:5091
 #, c-format
-msgid "Expected REP(SAL) count in %s line %d"
-msgstr ""
+msgid "E409: Unknown group name: %s"
+msgstr "E409: Tên nhóm không biết: %s"
 
-#: ../spell.c:5120
 #, c-format
-msgid "Expected MAP count in %s line %d"
-msgstr ""
+msgid "E410: Invalid :syntax subcommand: %s"
+msgstr "E410: Câu lệnh con :syntax không đúng: %s"
 
-#: ../spell.c:5132
-#, c-format
-msgid "Duplicate character in MAP in %s line %d"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E411: Highlight group not found: %s"
+msgstr "E411: không tìm thấy nhóm chiếu sáng cú pháp: %s"
 
-#: ../spell.c:5176
 #, c-format
-msgid "Unrecognized or duplicate item in %s line %d: %s"
-msgstr ""
+msgid "E412: Not enough arguments: \":highlight link %s\""
+msgstr "E412: Không đủ tham số: \":highlight link %s\""
 
-#: ../spell.c:5197
 #, c-format
-msgid "Missing FOL/LOW/UPP line in %s"
-msgstr ""
-
-#: ../spell.c:5220
-msgid "COMPOUNDSYLMAX used without SYLLABLE"
-msgstr ""
+msgid "E413: Too many arguments: \":highlight link %s\""
+msgstr "E413: Quá nhiều tham số: \":highlight link %s\""
 
-#: ../spell.c:5236
-#, fuzzy
-msgid "Too many postponed prefixes"
-msgstr "Có quá nhiều tham số soạn thảo"
+# TODO: Capitalise first word of message?
+msgid "E414: Group has settings, highlight link ignored"
+msgstr "E414: nhóm có thiết lập riêng, chiếu sáng liên kết bị bỏ qua"
 
-#: ../spell.c:5238
-#, fuzzy
-msgid "Too many compound flags"
-msgstr "Có quá nhiều tham số soạn thảo"
+# TODO: Capitalise first word of message?
+msgid "E415: Unexpected equal sign: %s"
+msgstr "E415: dấu bằng không được mong đợi: %s"
 
-#: ../spell.c:5240
-msgid "Too many postponed prefixes and/or compound flags"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E416: Missing equal sign: %s"
+msgstr "E416: thiếu dấu bằng: %s"
 
-#: ../spell.c:5250
-#, c-format
-msgid "Missing SOFO%s line in %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E417: Missing argument: %s"
+msgstr "E417: thiếu tham số: %s"
 
-#: ../spell.c:5253
 #, c-format
-msgid "Both SAL and SOFO lines in %s"
-msgstr ""
+msgid "E418: Illegal value: %s"
+msgstr "E418: Giá trị không cho phép: %s"
 
-#: ../spell.c:5331
-#, c-format
-msgid "Flag is not a number in %s line %d: %s"
-msgstr ""
+msgid "E419: FG color unknown"
+msgstr "E419: Không rõ màu văn bản (FG)"
 
-#: ../spell.c:5334
-#, c-format
-msgid "Illegal flag in %s line %d: %s"
-msgstr ""
+msgid "E420: BG color unknown"
+msgstr "E420: Không rõ màu nền sau (BG)"
 
-#: ../spell.c:5493 ../spell.c:5501
 #, c-format
-msgid "%s value differs from what is used in another .aff file"
-msgstr ""
+msgid "E421: Color name or number not recognized: %s"
+msgstr "E421: Tên hoặc số của màu không được nhận ra: %s"
 
-#: ../spell.c:5602
-#, fuzzy, c-format
-msgid "Reading dictionary file %s ..."
-msgstr "Quét từ điển: %s"
+# TODO: Capitalise first word of message?
+msgid "E422: Terminal code too long: %s"
+msgstr "E422: mã terminal quá dài: %s"
 
-#: ../spell.c:5611
 #, c-format
-msgid "E760: No word count in %s"
-msgstr ""
+msgid "E423: Illegal argument: %s"
+msgstr "E423: Tham số không cho phép: %s"
 
-#: ../spell.c:5669
-#, c-format
-msgid "line %6d, word %6d - %s"
-msgstr ""
+msgid "E424: Too many different highlighting attributes in use"
+msgstr "E424: Sử dụng quá nhiều thuộc tính chiếu sáng cú pháp"
 
-#: ../spell.c:5691
-#, fuzzy, c-format
-msgid "Duplicate word in %s line %d: %s"
-msgstr "Tìm thấy tương ứng trên mọi dòng: %s"
+msgid "E669: Unprintable character in group name"
+msgstr "E669: Ký tự không thể tin ra trong tên nhóm"
 
-#: ../spell.c:5694
-#, c-format
-msgid "First duplicate word in %s line %d: %s"
-msgstr ""
+msgid "W18: Invalid character in group name"
+msgstr "W18: Ký tự không cho phép trong tên nhóm"
 
-#: ../spell.c:5746
-#, c-format
-msgid "%d duplicate word(s) in %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E555: At bottom of tag stack"
+msgstr "E555: ở cuối đống thẻ ghi"
 
-#: ../spell.c:5748
-#, c-format
-msgid "Ignored %d word(s) with non-ASCII characters in %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E556: At top of tag stack"
+msgstr "E556: ở đầu đống thẻ ghi"
 
-#: ../spell.c:6115
-#, fuzzy, c-format
-msgid "Reading word file %s ..."
-msgstr "Đọc từ đầu vào tiêu chuẩn stdin..."
+msgid "E425: Cannot go before first matching tag"
+msgstr "E425: Không chuyển được tới vị trí ở trước thẻ ghi tương ứng đầu tiên"
 
-#: ../spell.c:6155
-#, c-format
-msgid "Duplicate /encoding= line ignored in %s line %d: %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E426: Tag not found: %s"
+msgstr "E426: không tìm thấy thẻ ghi: %s"
 
-#: ../spell.c:6159
-#, c-format
-msgid "/encoding= line after word ignored in %s line %d: %s"
-msgstr ""
+msgid "  # pri kind tag"
+msgstr "  # pri loại thẻ ghi"
 
-#: ../spell.c:6180
-#, c-format
-msgid "Duplicate /regions= line ignored in %s line %d: %s"
-msgstr ""
+msgid "file\n"
+msgstr "tập tin\n"
 
-#: ../spell.c:6185
-#, c-format
-msgid "Too many regions in %s line %d: %s"
-msgstr ""
+msgid "Enter nr of choice ( to abort): "
+msgstr "Hãy chọn số cần thiết ( để dừng):"
 
-#: ../spell.c:6198
-#, c-format
-msgid "/ line ignored in %s line %d: %s"
-msgstr ""
+msgid "E427: There is only one matching tag"
+msgstr "E427: Chỉ có một thẻ ghi tương ứng"
 
-#: ../spell.c:6224
-#, fuzzy, c-format
-msgid "Invalid region nr in %s line %d: %s"
-msgstr "E573: Sử dụng id máy chủ không đúng: %s"
+msgid "E428: Cannot go beyond last matching tag"
+msgstr "E428: Không chuyển được tới vị trí ở sau thẻ ghi tương ứng cuối cùng"
 
-#: ../spell.c:6230
 #, c-format
-msgid "Unrecognized flags in %s line %d: %s"
-msgstr ""
+msgid "File \"%s\" does not exist"
+msgstr "Tập tin \"%s\" không tồn tại"
 
-#: ../spell.c:6257
 #, c-format
-msgid "Ignored %d words with non-ASCII characters"
-msgstr ""
+msgid "tag %d of %d%s"
+msgstr "thẻ ghi %d của %d%s"
 
-#: ../spell.c:6656
-#, c-format
-msgid "Compressed %d of %d nodes; %d (%d%%) remaining"
-msgstr ""
-
-#: ../spell.c:7340
-msgid "Reading back spell file..."
-msgstr ""
+msgid " or more"
+msgstr " và hơn nữa"
 
-#. Go through the trie of good words, soundfold each word and add it to
-#. the soundfold trie.
-#: ../spell.c:7357
-msgid "Performing soundfolding..."
-msgstr ""
+msgid "  Using tag with different case!"
+msgstr " Đang sử dụng thẻ ghi với kiểu chữ khác!"
 
-#: ../spell.c:7368
 #, c-format
-msgid "Number of words after soundfolding: %"
-msgstr ""
+msgid "E429: File \"%s\" does not exist"
+msgstr "E429: Tập tin \"%s\" không tồn tại"
 
-#: ../spell.c:7476
-#, c-format
-msgid "Total number of words: %d"
+msgid ""
+"\n"
+"  # TO tag         FROM line  in file/text"
 msgstr ""
+"\n"
+"  # TỚI thẻ ghi        TỪ   dòng  trong tập tin/văn bản"
 
-#: ../spell.c:7655
-#, fuzzy, c-format
-msgid "Writing suggestion file %s ..."
-msgstr "Ghi tập tin viminfo \"%s\""
-
-#: ../spell.c:7707 ../spell.c:7927
 #, c-format
-msgid "Estimated runtime memory use: %d bytes"
-msgstr ""
-
-#: ../spell.c:7820
-msgid "E751: Output file name must not have region name"
-msgstr ""
-
-#: ../spell.c:7822
-#, fuzzy
-msgid "E754: Only up to 8 regions supported"
-msgstr "E519: Tùy chọn không được hỗ trợ"
-
-#: ../spell.c:7846
-#, fuzzy, c-format
-msgid "E755: Invalid region in %s"
-msgstr "E15: Biểu thức không cho phép: %s"
-
-#: ../spell.c:7907
-msgid "Warning: both compounding and NOBREAK specified"
-msgstr ""
-
-#: ../spell.c:7920
-#, fuzzy, c-format
-msgid "Writing spell file %s ..."
-msgstr "Ghi tập tin viminfo \"%s\""
+msgid "Searching tags file %s"
+msgstr "Tìm kiếm tập tin thẻ ghi %s"
 
-#: ../spell.c:7925
-msgid "Done!"
-msgstr ""
+#, c-format
+msgid "E430: Tag file path truncated for %s\n"
+msgstr "E430: Đường dẫn tới tập tin thẻ ghi bị cắt bớt cho %s\n"
 
-#: ../spell.c:8034
 #, c-format
-msgid "E765: 'spellfile' does not have % entries"
-msgstr ""
+msgid "E431: Format error in tags file \"%s\""
+msgstr "E431: Lỗi định dạng trong tập tin thẻ ghi \"%s\""
 
-#: ../spell.c:8074
 #, c-format
-msgid "Word '%.*s' removed from %s"
-msgstr ""
+msgid "Before byte %ld"
+msgstr "Trước byte %ld"
 
-#: ../spell.c:8117
 #, c-format
-msgid "Word '%.*s' added to %s"
-msgstr ""
+msgid "E432: Tags file not sorted: %s"
+msgstr "E432: Tập tin thẻ ghi chưa được sắp xếp: %s"
 
-#: ../spell.c:8381
-msgid "E763: Word characters differ between spell files"
-msgstr ""
+msgid "E433: No tags file"
+msgstr "E433: Không có tập tin thẻ ghi"
 
-#: ../spell.c:8684
-msgid "Sorry, no suggestions"
-msgstr ""
+msgid "E434: Can't find tag pattern"
+msgstr "E434: Không tìm thấy mẫu thẻ ghi"
 
-#: ../spell.c:8687
-#, fuzzy, c-format
-msgid "Sorry, only % suggestions"
-msgstr " trên % dòng"
+msgid "E435: Couldn't find tag, just guessing!"
+msgstr "E435: Không tìm thấy thẻ ghi, đang thử đoán!"
 
-#. for when 'cmdheight' > 1
-#. avoid more prompt
-#: ../spell.c:8704
-#, fuzzy, c-format
-msgid "Change \"%.*s\" to:"
-msgstr "Ghi nhớ thay đổi vào \"%.*s\"?"
+msgid "' not known. Available builtin terminals are:"
+msgstr "' không rõ. Có các terminal gắn sẵn (builtin) sau:"
 
-#: ../spell.c:8737
-#, c-format
-msgid " < \"%.*s\""
-msgstr ""
+msgid "defaulting to '"
+msgstr "theo mặc định '"
 
-#: ../spell.c:8882
-#, fuzzy
-msgid "E752: No previous spell replacement"
-msgstr "E35: Không có biểu thức chính quy trước"
+msgid "E557: Cannot open termcap file"
+msgstr "E557: Không thể mở tập tin termcap"
 
-#: ../spell.c:8925
-#, fuzzy, c-format
-msgid "E753: Not found: %s"
-msgstr "E334: Không tìm thấy trình đơn: %s"
+msgid "E558: Terminal entry not found in terminfo"
+msgstr "E558: Trong terminfo không có bản ghi nào về terminal này"
 
-#: ../spell.c:9276
-#, fuzzy, c-format
-msgid "E778: This does not look like a .sug file: %s"
-msgstr "E307: %s không phải là tập tin trao đổi (swap) của Vim"
+msgid "E559: Terminal entry not found in termcap"
+msgstr "E559: Trong termcap không có bản ghi nào về terminal này"
 
-#: ../spell.c:9282
 #, c-format
-msgid "E779: Old .sug file, needs to be updated: %s"
-msgstr ""
+msgid "E436: No \"%s\" entry in termcap"
+msgstr "E436: Trong termcap không có bản ghi \"%s\""
 
-#: ../spell.c:9286
-#, c-format
-msgid "E780: .sug file is for newer version of Vim: %s"
-msgstr ""
+# TODO: Capitalise first word of message?
+msgid "E437: Terminal capability \"cm\" required"
+msgstr "E437: cần khả năng của terminal \"cm\""
 
-#: ../spell.c:9295
-#, c-format
-msgid "E781: .sug file doesn't match .spl file: %s"
+msgid ""
+"\n"
+"--- Terminal keys ---"
 msgstr ""
+"\n"
+"--- Phím terminal ---"
 
-#: ../spell.c:9305
-#, fuzzy, c-format
-msgid "E782: error while reading .sug file: %s"
-msgstr "E47: Lỗi khi đọc tập tin lỗi"
-
-#. This should have been checked when generating the .spl
-#. file.
-#: ../spell.c:11575
-msgid "E783: duplicate char in MAP entry"
-msgstr ""
+msgid "new shell started\n"
+msgstr "đã chạy shell mới\n"
 
-#: ../syntax.c:266
-msgid "No Syntax items defined for this buffer"
-msgstr "Không có phần tử cú pháp nào được định nghĩa cho bộ đệm này"
+msgid "Vim: Error reading input, exiting...\n"
+msgstr "Vim: Lỗi đọc dữ liệu nhập, thoát...\n"
 
-#: ../syntax.c:3083 ../syntax.c:3104 ../syntax.c:3127
-#, c-format
-msgid "E390: Illegal argument: %s"
-msgstr "E390: Tham số không cho phép: %s"
+msgid "No undo possible; continue anyway"
+msgstr "Không thể hủy thao tác; tiếp tục thực hiện"
 
-#: ../syntax.c:3299
-#, c-format
-msgid "E391: No such syntax cluster: %s"
-msgstr "E391: Không có cụm cú pháp như vậy: %s"
+# TODO: Capitalise first word of message?
+msgid "E438: u_undo: Line numbers wrong"
+msgstr "E438: u_undo: số thứ tự dòng không đúng"
 
-#: ../syntax.c:3433
-msgid "syncing on C-style comments"
-msgstr "Đồng bộ hóa theo chú thích kiểu C"
+msgid "1 change"
+msgstr "duy nhất 1 thay đổi"
 
-#: ../syntax.c:3439
-msgid "no syncing"
-msgstr "không đồng bộ hóa"
+#, c-format
+msgid "%ld changes"
+msgstr "%ld thay đổi"
 
-#: ../syntax.c:3441
-msgid "syncing starts "
-msgstr "đồng bộ hóa bắt đầu "
+# TODO: Capitalise first word of message?
+msgid "E439: Undo list corrupt"
+msgstr "E439: danh sách hủy thao tác (undo) bị hỏng"
 
-#: ../syntax.c:3443 ../syntax.c:3506
-msgid " lines before top line"
-msgstr " dòng trước dòng đầu tiên"
+# TODO: Capitalise first word of message?
+msgid "E440: Undo line missing"
+msgstr "E440: bị mất dòng hủy thao tác"
 
-#: ../syntax.c:3448
 msgid ""
 "\n"
-"--- Syntax sync items ---"
+"MS-Windows 16/32-bit GUI version"
 msgstr ""
 "\n"
-"--- Phần tử đồng bộ hóa cú pháp ---"
+"Phiên bản với giao diện đồ họa GUI cho MS-Windows 16/32 bit"
 
-#: ../syntax.c:3452
 msgid ""
 "\n"
-"syncing on items"
+"MS-Windows 32-bit GUI version"
 msgstr ""
 "\n"
-"đồng bộ hóa theo phần tử"
+"Phiên bản với giao diện đồ họa GUI cho MS-Windows 32 bit"
+
+msgid " in Win32s mode"
+msgstr " trong chế độ Win32"
+
+msgid " with OLE support"
+msgstr " với hỗ trợ OLE"
 
-#: ../syntax.c:3457
 msgid ""
 "\n"
-"--- Syntax items ---"
+"MS-Windows 32-bit console version"
 msgstr ""
 "\n"
-"--- Phần tử cú pháp ---"
-
-#: ../syntax.c:3475
-#, c-format
-msgid "E392: No such syntax cluster: %s"
-msgstr "E392: Không có cụm cú pháp như vậy: %s"
-
-#: ../syntax.c:3497
-msgid "minimal "
-msgstr "nhỏ nhất "
-
-#: ../syntax.c:3503
-msgid "maximal "
-msgstr "lớn nhất "
+"Phiên bản console cho MS-Windows 32 bit"
 
-#: ../syntax.c:3513
-msgid "; match "
-msgstr "; tương ứng "
+msgid ""
+"\n"
+"MS-Windows 16-bit version"
+msgstr ""
+"\n"
+"Phiên bản cho MS-Windows 16 bit"
 
-#: ../syntax.c:3515
-msgid " line breaks"
-msgstr " chuyển dòng"
+msgid ""
+"\n"
+"32-bit MS-DOS version"
+msgstr ""
+"\n"
+"Phiên bản cho MS-DOS 32 bit"
 
-#: ../syntax.c:4076
-msgid "E395: contains argument not accepted here"
-msgstr "E395: không được sử dụng tham số contains ở đây"
+msgid ""
+"\n"
+"16-bit MS-DOS version"
+msgstr ""
+"\n"
+"Phiên bản cho MS-DOS 16 bit"
 
-#: ../syntax.c:4096
-#, fuzzy
-msgid "E844: invalid cchar value"
-msgstr "E474: Tham số không cho phép"
+msgid ""
+"\n"
+"MacOS X (unix) version"
+msgstr ""
+"\n"
+"Phiên bản cho MacOS X (unix)"
 
-#: ../syntax.c:4107
-msgid "E393: group[t]here not accepted here"
-msgstr "E393: không được sử dụng group[t]here ở đây"
+msgid ""
+"\n"
+"MacOS X version"
+msgstr ""
+"\n"
+"Phiên bản cho MacOS X"
 
-#: ../syntax.c:4126
-#, c-format
-msgid "E394: Didn't find region item for %s"
-msgstr "E394: Phần tử vùng cho %s không tìm thấy"
+msgid ""
+"\n"
+"MacOS version"
+msgstr ""
+"\n"
+"Phiên bản cho MacOS"
 
-#: ../syntax.c:4188
-msgid "E397: Filename required"
-msgstr "E397: Yêu cầu tên tập tin"
+msgid ""
+"\n"
+"RISC OS version"
+msgstr ""
+"\n"
+"Phiên bản cho RISC OS"
 
-#: ../syntax.c:4221
-#, fuzzy
-msgid "E847: Too many syntax includes"
-msgstr "E77: Quá nhiều tên tập tin"
+msgid ""
+"\n"
+"Included patches: "
+msgstr ""
+"\n"
+"Bao gồm các bản vá lỗi: "
 
-#: ../syntax.c:4303
-#, fuzzy, c-format
-msgid "E789: Missing ']': %s"
-msgstr "E398: Thiếu '=': %s"
+msgid "Modified by "
+msgstr "Với các thay đổi bởi "
 
-#: ../syntax.c:4531
-#, c-format
-msgid "E398: Missing '=': %s"
-msgstr "E398: Thiếu '=': %s"
+msgid ""
+"\n"
+"Compiled "
+msgstr ""
+"\n"
+"Được biên dịch "
 
-#: ../syntax.c:4666
-#, c-format
-msgid "E399: Not enough arguments: syntax region %s"
-msgstr "E399: Không đủ tham số: vùng cú pháp %s"
+msgid "by "
+msgstr "bởi "
 
-#: ../syntax.c:4870
-#, fuzzy
-msgid "E848: Too many syntax clusters"
-msgstr "E391: Không có cụm cú pháp như vậy: %s"
+msgid ""
+"\n"
+"Huge version "
+msgstr ""
+"\n"
+"Phiên bản khổng lồ "
 
-#: ../syntax.c:4954
-msgid "E400: No cluster specified"
-msgstr "E400: Chưa chỉ ra cụm"
+msgid ""
+"\n"
+"Big version "
+msgstr ""
+"\n"
+"Phiên bản lớn "
 
-#. end delimiter not found
-#: ../syntax.c:4986
-#, c-format
-msgid "E401: Pattern delimiter not found: %s"
-msgstr "E401: Không tìm thấy ký tự phân chia mẫu (pattern): %s"
+msgid ""
+"\n"
+"Normal version "
+msgstr ""
+"\n"
+"Phiên bản thông thường "
 
-#: ../syntax.c:5049
-#, c-format
-msgid "E402: Garbage after pattern: %s"
-msgstr "E402: Rác ở sau mẫu (pattern): %s"
+msgid ""
+"\n"
+"Small version "
+msgstr ""
+"\n"
+"Phiên bản nhỏ "
 
-#: ../syntax.c:5120
-msgid "E403: syntax sync: line continuations pattern specified twice"
-msgstr "E403: đồng bộ hóa cú pháp: mẫu tiếp tục của dòng chỉ ra hai lần"
+msgid ""
+"\n"
+"Tiny version "
+msgstr ""
+"\n"
+"Phiên bản \"tí hon\" "
 
-#: ../syntax.c:5169
-#, c-format
-msgid "E404: Illegal arguments: %s"
-msgstr "E404: Tham số không cho phép: %s"
+msgid "without GUI."
+msgstr "không có giao diện đồ họa GUI."
 
-#: ../syntax.c:5217
-#, c-format
-msgid "E405: Missing equal sign: %s"
-msgstr "E405: Thiếu dấu bằng: %s"
+msgid "with GTK2-GNOME GUI."
+msgstr "với giao diện đồ họa GUI GTK2-GNOME."
 
-#: ../syntax.c:5222
-#, c-format
-msgid "E406: Empty argument: %s"
-msgstr "E406: Tham số trống rỗng: %s"
+msgid "with GTK-GNOME GUI."
+msgstr "với giao diện đồ họa GUI GTK-GNOME."
 
-#: ../syntax.c:5240
-#, c-format
-msgid "E407: %s not allowed here"
-msgstr "E407: %s không được cho phép ở đây"
+msgid "with GTK2 GUI."
+msgstr "với giao diện đồ họa GUI GTK2."
 
-#: ../syntax.c:5246
-#, c-format
-msgid "E408: %s must be first in contains list"
-msgstr "E408: %s phải là đầu tiên trong danh sách contains"
+msgid "with GTK GUI."
+msgstr "với giao diện đồ họa GUI GTK."
 
-#: ../syntax.c:5304
-#, c-format
-msgid "E409: Unknown group name: %s"
-msgstr "E409: Tên nhóm không biết: %s"
+msgid "with X11-Motif GUI."
+msgstr "với giao diện đồ họa GUI X11-Motif."
 
-#: ../syntax.c:5512
-#, c-format
-msgid "E410: Invalid :syntax subcommand: %s"
-msgstr "E410: Câu lệnh con :syntax không đúng: %s"
+msgid "with X11-neXtaw GUI."
+msgstr "với giao diện đồ họa GUI X11-neXtaw."
 
-#: ../syntax.c:5854
-msgid ""
-"  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"
-msgstr ""
+msgid "with X11-Athena GUI."
+msgstr "với giao diện đồ họa GUI X11-Athena."
 
-#: ../syntax.c:6146
-msgid "E679: recursive loop loading syncolor.vim"
-msgstr ""
+msgid "with BeOS GUI."
+msgstr "với giao diện đồ họa GUI BeOS."
 
-#: ../syntax.c:6256
-#, c-format
-msgid "E411: highlight group not found: %s"
-msgstr "E411: không tìm thấy nhóm chiếu sáng cú pháp: %s"
+msgid "with Photon GUI."
+msgstr "với giao diện đồ họa GUI Photon."
 
-#: ../syntax.c:6278
-#, c-format
-msgid "E412: Not enough arguments: \":highlight link %s\""
-msgstr "E412: Không đủ tham số: \":highlight link %s\""
+msgid "with GUI."
+msgstr "với giao diện đồ họa GUI."
 
-#: ../syntax.c:6284
-#, c-format
-msgid "E413: Too many arguments: \":highlight link %s\""
-msgstr "E413: Quá nhiều tham số: \":highlight link %s\""
+msgid "with Carbon GUI."
+msgstr "với giao diện đồ họa GUI Carbon."
 
-#: ../syntax.c:6302
-msgid "E414: group has settings, highlight link ignored"
-msgstr "E414: nhóm có thiết lập riêng, chiếu sáng liên kết bị bỏ qua"
+msgid "with Cocoa GUI."
+msgstr "với giao diện đồ họa GUI Cocoa."
 
-#: ../syntax.c:6367
-#, c-format
-msgid "E415: unexpected equal sign: %s"
-msgstr "E415: dấu bằng không được mong đợi: %s"
+msgid "with (classic) GUI."
+msgstr "với giao diện đồ họa (cổ điển) GUI."
 
-#: ../syntax.c:6395
-#, c-format
-msgid "E416: missing equal sign: %s"
-msgstr "E416: thiếu dấu bằng: %s"
-
-#: ../syntax.c:6418
-#, c-format
-msgid "E417: missing argument: %s"
-msgstr "E417: thiếu tham số: %s"
-
-#: ../syntax.c:6446
-#, c-format
-msgid "E418: Illegal value: %s"
-msgstr "E418: Giá trị không cho phép: %s"
-
-#: ../syntax.c:6496
-msgid "E419: FG color unknown"
-msgstr "E419: Không rõ màu văn bản (FG)"
-
-#: ../syntax.c:6504
-msgid "E420: BG color unknown"
-msgstr "E420: Không rõ màu nền sau (BG)"
-
-#: ../syntax.c:6564
-#, c-format
-msgid "E421: Color name or number not recognized: %s"
-msgstr "E421: Tên hoặc số của màu không được nhận ra: %s"
-
-#: ../syntax.c:6714
-#, c-format
-msgid "E422: terminal code too long: %s"
-msgstr "E422: mã terminal quá dài: %s"
-
-#: ../syntax.c:6753
-#, c-format
-msgid "E423: Illegal argument: %s"
-msgstr "E423: Tham số không cho phép: %s"
-
-#: ../syntax.c:6925
-msgid "E424: Too many different highlighting attributes in use"
-msgstr "E424: Sử dụng quá nhiều thuộc tính chiếu sáng cú pháp"
-
-#: ../syntax.c:7427
-msgid "E669: Unprintable character in group name"
-msgstr "E669: Ký tự không thể tin ra trong tên nhóm"
-
-#: ../highlight_group.c:1756
-msgid "E5248: Invalid character in group name"
-msgstr "E5248: Ký tự không cho phép trong tên nhóm"
-
-#: ../syntax.c:7448
-msgid "E849: Too many highlight and syntax groups"
-msgstr ""
-
-#: ../tag.c:104
-msgid "E555: at bottom of tag stack"
-msgstr "E555: ở cuối đống thẻ ghi"
-
-#: ../tag.c:105
-msgid "E556: at top of tag stack"
-msgstr "E556: ở đầu đống thẻ ghi"
-
-#: ../tag.c:380
-msgid "E425: Cannot go before first matching tag"
-msgstr "E425: Không chuyển được tới vị trí ở trước thẻ ghi tương ứng đầu tiên"
-
-#: ../tag.c:504
-#, c-format
-msgid "E426: tag not found: %s"
-msgstr "E426: không tìm thấy thẻ ghi: %s"
-
-#: ../tag.c:528
-msgid "  # pri kind tag"
-msgstr "  # pri loại thẻ ghi"
-
-#: ../tag.c:531
-msgid "file\n"
-msgstr "tập tin\n"
-
-#: ../tag.c:829
-msgid "E427: There is only one matching tag"
-msgstr "E427: Chỉ có một thẻ ghi tương ứng"
-
-#: ../tag.c:831
-msgid "E428: Cannot go beyond last matching tag"
-msgstr "E428: Không chuyển được tới vị trí ở sau thẻ ghi tương ứng cuối cùng"
-
-#: ../tag.c:850
-#, c-format
-msgid "File \"%s\" does not exist"
-msgstr "Tập tin \"%s\" không tồn tại"
-
-#. Give an indication of the number of matching tags
-#: ../tag.c:859
-#, c-format
-msgid "tag %d of %d%s"
-msgstr "thẻ ghi %d của %d%s"
-
-#: ../tag.c:862
-msgid " or more"
-msgstr " và hơn nữa"
-
-#: ../tag.c:864
-msgid "  Using tag with different case!"
-msgstr " Đang sử dụng thẻ ghi với kiểu chữ khác!"
-
-#: ../tag.c:909
-#, c-format
-msgid "E429: File \"%s\" does not exist"
-msgstr "E429: Tập tin \"%s\" không tồn tại"
-
-#. Highlight title
-#: ../tag.c:960
-msgid ""
-"\n"
-"  # TO tag         FROM line  in file/text"
-msgstr ""
-"\n"
-"  # TỚI thẻ ghi        TỪ   dòng  trong tập tin/văn bản"
-
-#: ../tag.c:1303
-#, c-format
-msgid "Searching tags file %s"
-msgstr "Tìm kiếm tập tin thẻ ghi %s"
-
-#: ../tag.c:1545
-msgid "Ignoring long line in tags file"
-msgstr ""
-
-#: ../tag.c:1915
-#, c-format
-msgid "E431: Format error in tags file \"%s\""
-msgstr "E431: Lỗi định dạng trong tập tin thẻ ghi \"%s\""
-
-#: ../tag.c:1917
-#, c-format
-msgid "Before byte %"
-msgstr "Trước byte %"
-
-#: ../tag.c:1929
-#, c-format
-msgid "E432: Tags file not sorted: %s"
-msgstr "E432: Tập tin thẻ ghi chưa được sắp xếp: %s"
-
-#. never opened any tags file
-#: ../tag.c:1960
-msgid "E433: No tags file"
-msgstr "E433: Không có tập tin thẻ ghi"
-
-#: ../tag.c:2536
-msgid "E434: Can't find tag pattern"
-msgstr "E434: Không tìm thấy mẫu thẻ ghi"
-
-#: ../tag.c:2544
-msgid "E435: Couldn't find tag, just guessing!"
-msgstr "E435: Không tìm thấy thẻ ghi, đang thử đoán!"
-
-#: ../tag.c:2797
-#, c-format
-msgid "Duplicate field name: %s"
-msgstr ""
-
-#: ../term.c:1442
-msgid "' not known. Available builtin terminals are:"
-msgstr "' không rõ. Có các terminal gắn sẵn (builtin) sau:"
-
-#: ../term.c:1463
-msgid "defaulting to '"
-msgstr "theo mặc định '"
-
-#: ../term.c:1731
-msgid "E557: Cannot open termcap file"
-msgstr "E557: Không thể mở tập tin termcap"
-
-#: ../term.c:1735
-msgid "E558: Terminal entry not found in terminfo"
-msgstr "E558: Trong terminfo không có bản ghi nào về terminal này"
-
-#: ../term.c:1737
-msgid "E559: Terminal entry not found in termcap"
-msgstr "E559: Trong termcap không có bản ghi nào về terminal này"
-
-#: ../term.c:1878
-#, c-format
-msgid "E436: No \"%s\" entry in termcap"
-msgstr "E436: Trong termcap không có bản ghi \"%s\""
-
-#: ../term.c:2249
-msgid "E437: terminal capability \"cm\" required"
-msgstr "E437: cần khả năng của terminal \"cm\""
-
-#. Highlight title
-#: ../term.c:4376
-msgid ""
-"\n"
-"--- Terminal keys ---"
-msgstr ""
-"\n"
-"--- Phím terminal ---"
-
-#: ../ui.c:481
-msgid "Vim: Error reading input, exiting...\n"
-msgstr "Vim: Lỗi đọc dữ liệu nhập, thoát...\n"
-
-#. This happens when the FileChangedRO autocommand changes the
-#. * file in a way it becomes shorter.
-#: ../undo.c:379
-msgid "E881: Line count changed unexpectedly"
-msgstr ""
-
-#: ../undo.c:627
-#, fuzzy, c-format
-msgid "E828: Cannot open undo file for writing: %s"
-msgstr "E212: Không thể mở tập tin để ghi nhớ"
-
-#: ../undo.c:717
-#, c-format
-msgid "E825: Corrupted undo file (%s): %s"
-msgstr ""
-
-#: ../undo.c:1039
-msgid "Cannot write undo file in any directory in 'undodir'"
-msgstr ""
-
-#: ../undo.c:1074
-#, c-format
-msgid "Will not overwrite with undo file, cannot read: %s"
-msgstr ""
-
-#: ../undo.c:1092
-#, c-format
-msgid "Will not overwrite, this is not an undo file: %s"
-msgstr ""
-
-#: ../undo.c:1108
-msgid "Skipping undo file write, nothing to undo"
-msgstr ""
-
-#: ../undo.c:1121
-#, fuzzy, c-format
-msgid "Writing undo file: %s"
-msgstr "Ghi tập tin viminfo \"%s\""
-
-#: ../undo.c:1213
-#, fuzzy, c-format
-msgid "E829: write error in undo file: %s"
-msgstr "E297: Lỗi ghi nhớ tập tin trao đổi (swap)"
-
-#: ../undo.c:1280
-#, c-format
-msgid "Not reading undo file, owner differs: %s"
-msgstr ""
-
-#: ../undo.c:1292
-#, fuzzy, c-format
-msgid "Reading undo file: %s"
-msgstr "Đọc tập tin viminfo \"%s\"%s%s%s"
-
-#: ../undo.c:1299
-#, fuzzy, c-format
-msgid "E822: Cannot open undo file for reading: %s"
-msgstr "E195: Không thể mở tập tin viminfo để đọc"
-
-#: ../undo.c:1308
-#, fuzzy, c-format
-msgid "E823: Not an undo file: %s"
-msgstr "E484: Không mở được tập tin %s"
-
-#: ../undo.c:1313
-#, fuzzy, c-format
-msgid "E824: Incompatible undo file: %s"
-msgstr "E484: Không mở được tập tin %s"
-
-#: ../undo.c:1328
-msgid "File contents changed, cannot use undo info"
-msgstr ""
-
-#: ../undo.c:1497
-#, fuzzy, c-format
-msgid "Finished reading undo file %s"
-msgstr "thực hiện xong %s"
-
-#: ../undo.c:1586 ../undo.c:1812
-msgid "Already at oldest change"
-msgstr ""
-
-#: ../undo.c:1597 ../undo.c:1814
-msgid "Already at newest change"
-msgstr ""
-
-#: ../undo.c:1806
-#, fuzzy, c-format
-msgid "E830: Undo number % not found"
-msgstr "E92: Bộ đệm % không được tìm thấy"
-
-#: ../undo.c:1979
-msgid "E438: u_undo: line numbers wrong"
-msgstr "E438: u_undo: số thứ tự dòng không đúng"
-
-#: ../undo.c:2183
-#, fuzzy
-msgid "more line"
-msgstr "Thêm 1 dòng"
-
-#: ../undo.c:2185
-#, fuzzy
-msgid "more lines"
-msgstr "Thêm 1 dòng"
-
-#: ../undo.c:2187
-#, fuzzy
-msgid "line less"
-msgstr "Bớt 1 dòng"
-
-#: ../undo.c:2189
-#, fuzzy
-msgid "fewer lines"
-msgstr "Bớt % dòng"
-
-#: ../undo.c:2193
-#, fuzzy
-msgid "change"
-msgstr "duy nhất 1 thay đổi"
-
-#: ../undo.c:2195
-#, fuzzy
-msgid "changes"
-msgstr "duy nhất 1 thay đổi"
-
-#: ../undo.c:2225
-#, fuzzy, c-format
-msgid "% %s; %s #%  %s"
-msgstr "Trên % dòng %s %d lần"
-
-#: ../undo.c:2228
-msgid "before"
-msgstr ""
-
-#: ../undo.c:2228
-msgid "after"
-msgstr ""
-
-#: ../undo.c:2325
-#, fuzzy
-msgid "Nothing to undo"
-msgstr "Không tìm thấy ánh xạ"
-
-#: ../undo.c:2330
-msgid "number changes  when               saved"
-msgstr ""
-
-#: ../undo.c:2360
-#, fuzzy, c-format
-msgid "% seconds ago"
-msgstr "% Cột; "
-
-#: ../undo.c:2372
-#, fuzzy
-msgid "E790: undojoin is not allowed after undo"
-msgstr "E407: %s không được cho phép ở đây"
-
-#: ../undo.c:2466
-msgid "E439: undo list corrupt"
-msgstr "E439: danh sách hủy thao tác (undo) bị hỏng"
-
-#: ../undo.c:2495
-msgid "E440: undo line missing"
-msgstr "E440: bị mất dòng hủy thao tác"
-
-#: ../version.c:600
-msgid ""
-"\n"
-"Included patches: "
-msgstr ""
-"\n"
-"Bao gồm các bản vá lỗi: "
-
-#: ../version.c:627
-#, fuzzy
-msgid ""
-"\n"
-"Extra patches: "
-msgstr "Sự tương ứng con ngoài:\n"
-
-#: ../version.c:639 ../version.c:864
-msgid "Modified by "
-msgstr "Với các thay đổi bởi "
-
-#: ../version.c:646
-msgid ""
-"\n"
-"Compiled "
-msgstr ""
-"\n"
-"Được biên dịch "
-
-#: ../version.c:649
-msgid "by "
-msgstr "bởi "
-
-#: ../version.c:660
-msgid ""
-"\n"
-"Huge version "
-msgstr ""
-"\n"
-"Phiên bản khổng lồ "
-
-#: ../version.c:661
-msgid "without GUI."
-msgstr "không có giao diện đồ họa GUI."
-
-#: ../version.c:662
 msgid "  Features included (+) or not (-):\n"
 msgstr "  Tính năng có (+) hoặc không (-):\n"
 
-#: ../version.c:667
-msgid "   system vimrc file: \""
-msgstr "            tập tin vimrc chung cho hệ thống: \""
-
-#: ../version.c:672
-msgid "     user vimrc file: \""
-msgstr "         tập tin vimrc của người dùng: \""
-
-#: ../version.c:677
-msgid " 2nd user vimrc file: \""
-msgstr "  tập tin vimrc thứ hai của người dùng: \""
-
-#: ../version.c:682
-msgid " 3rd user vimrc file: \""
-msgstr "  tập tin vimrc thứ ba của người dùng: \""
-
-#: ../version.c:687
-msgid "      user exrc file: \""
-msgstr "         tập tin exrc của người dùng: \""
-
-#: ../version.c:692
-msgid "  2nd user exrc file: \""
-msgstr "  tập tin exrc thứ hai của người dùng: \""
-
-#: ../version.c:699
-msgid "  fall-back for $VIM: \""
-msgstr "          giá trị $VIM theo mặc định: \""
-
-#: ../version.c:705
-msgid " f-b for $VIMRUNTIME: \""
-msgstr "   giá trị $VIMRUNTIME theo mặc định: \""
-
-#: ../version.c:709
-msgid "Compilation: "
-msgstr "Tham số biên dịch: "
-
-#: ../version.c:712
-msgid "Linking: "
-msgstr "Liên kết: "
-
-#: ../version.c:717
-msgid "  DEBUG BUILD"
-msgstr "  BIÊN DỊCH SỬA LỖI (DEBUG)"
-
-#: ../version.c:767
-msgid "VIM - Vi IMproved"
-msgstr "VIM ::: Vi IMproved (Vi cải tiến) ::: Phiên bản tiếng Việt"
-
-#: ../version.c:769
-msgid "version "
-msgstr "phiên bản "
-
-#: ../version.c:770
-msgid "by Bram Moolenaar et al."
-msgstr "Do Bram Moolenaar và những người khác thực hiện"
-
-#: ../version.c:774
-msgid "Vim is open source and freely distributable"
-msgstr "Vim là chương trình mã nguồn mở và phân phối tự do"
-
-#: ../version.c:776
-msgid "Help poor children in Uganda!"
-msgstr "Hãy giúp đỡ trẻ em nghèo Uganda!"
-
-#: ../version.c:777
-msgid "type  :help iccf       for information "
-msgstr "hãy gõ :help iccf       để biết thêm thông tin"
-
-#: ../version.c:779
-msgid "type  :q               to exit         "
-msgstr "    hãy gõ :q               để thoát khỏi chương trình     "
-
-#: ../version.c:780
-msgid "type  :help  or    for on-line help"
-msgstr " hãy gõ :help hoặc   để có được trợ giúp        "
-
-#: ../version.c:781
-msgid "type  :help version7   for version info"
-msgstr "hãy gõ :help version7   để biết về phiên bản này  "
-
-#: ../version.c:784
-msgid "Running in Vi compatible mode"
-msgstr "Làm việc trong chế độ tương thích với Vi"
-
-#: ../version.c:785
-msgid "type  :set nocp        for Vim defaults"
-msgstr "hãy gõ :set nocp        để chuyển vào chế độ Vim     "
-
-#: ../version.c:786
-msgid "type  :help cp-default for info on this"
-msgstr "hãy gõ :help cp-default để có thêm thông tin về điều này"
-
-#: ../version.c:827
-msgid "Sponsor Vim development!"
-msgstr "Hãy giúp đỡ phát triển Vim!"
-
-#: ../version.c:828
-msgid "Become a registered Vim user!"
-msgstr "Hãy trở thành người dùng đăng ký của Vim!"
-
-#: ../version.c:831
-msgid "type  :help sponsor    for information "
-msgstr "hãy gõ :help sponsor    để biết thêm thông tin "
-
-#: ../version.c:832
-msgid "type  :help register   for information "
-msgstr "hãy gõ :help register   để biết thêm thông tin "
-
-#: ../version.c:834
-msgid "menu  Help->Sponsor/Register  for information    "
-msgstr "trình đơn Trợ giúp->Giúp đỡ/Đăng ký để biết thêm thông tin    "
-
-#: ../window.c:119
-msgid "Already only one window"
-msgstr "Chỉ có một cửa sổ"
-
-#: ../window.c:224
-msgid "E441: There is no preview window"
-msgstr "E441: Không có cửa sổ xem trước"
-
-#: ../window.c:559
-msgid "E442: Can't split topleft and botright at the same time"
-msgstr ""
-"E442: Cửa sổ không thể đồng thời ở bên trái phía trên và bên phải phía dưới"
-
-#: ../window.c:1228
-msgid "E443: Cannot rotate when another window is split"
-msgstr "E443: Không đổi được chỗ khi cửa sổ khác được chia"
-
-#: ../window.c:1803
-msgid "E444: Cannot close last window"
-msgstr "E444: Không được đóng cửa sổ cuối cùng"
-
-#: ../window.c:1810
-#, fuzzy
-msgid "E813: Cannot close autocmd window"
-msgstr "E444: Không được đóng cửa sổ cuối cùng"
-
-#: ../window.c:1814
-#, fuzzy
-msgid "E814: Cannot close window, only autocmd window would remain"
-msgstr "E444: Không được đóng cửa sổ cuối cùng"
-
-#: ../window.c:2717
-msgid "E445: Other window contains changes"
-msgstr "E445: Cửa sổ khác có thay đổi chưa được ghi nhớ"
-
-#: ../window.c:4805
-msgid "E446: No file name under cursor"
-msgstr "E446: Không có tên tập tin tại vị trí con trỏ"
-
-#~ msgid "[Error List]"
-#~ msgstr "[Danh sách lỗi]"
-
-#~ msgid "[No File]"
-#~ msgstr "[Không có tập tin]"
-
-#~ msgid "Patch file"
-#~ msgstr "Tập tin vá lỗi (patch)"
-
-#~ msgid "E106: Unknown variable: \"%s\""
-#~ msgstr "E106: Biến không biết: \"%s\""
-
-#~ msgid ""
-#~ "&OK\n"
-#~ "&Cancel"
-#~ msgstr ""
-#~ "&OK\n"
-#~ "&Hủy bỏ"
-
-#~ msgid "E240: No connection to Vim server"
-#~ msgstr "E240: Không có kết nối với máy chủ Vim"
-
-#~ msgid "E277: Unable to read a server reply"
-#~ msgstr "E277: Máy chủ không trả lời"
-
-#~ msgid "E241: Unable to send to %s"
-#~ msgstr "E241: Không thể gửi tin nhắn tới %s"
-
-#~ msgid "E130: Undefined function: %s"
-#~ msgstr "E130: Hàm số %s chưa xác định"
-
-#~ msgid "Save As"
-#~ msgstr "Ghi nhớ như"
-
-#~ msgid "Source Vim script"
-#~ msgstr "Thực hiện script của Vim"
-
-#~ msgid "Edit File"
-#~ msgstr "Soạn thảo tập tin"
-
-#~ msgid " (NOT FOUND)"
-#~ msgstr " (KHÔNG TÌM THẤY)"
-
-#~ msgid "Edit File in new window"
-#~ msgstr "Soạn thảo tập tin trong cửa sổ mới"
-
-#~ msgid "Append File"
-#~ msgstr "Thêm tập tin"
-
-#~ msgid "Window position: X %d, Y %d"
-#~ msgstr "Vị trí cửa sổ: X %d, Y %d"
-
-#~ msgid "Save Redirection"
-#~ msgstr "Chuyển hướng ghi nhớ"
-
-#~ msgid "Save View"
-#~ msgstr "Ghi nhớ vẻ ngoài"
-
-#~ msgid "Save Session"
-#~ msgstr "Ghi nhớ buổi làm việc"
-
-#~ msgid "Save Setup"
-#~ msgstr "Ghi nhớ cấu hình"
-
-#~ msgid "E196: No digraphs in this version"
-#~ msgstr "E196: Trong phiên bản này chữ ghép không được hỗ trợ"
-
-#~ msgid "[NL found]"
-#~ msgstr "[tìm thấy ký tự NL]"
-
-#~ msgid "[crypted]"
-#~ msgstr "[đã mã hóa]"
-
-#~ msgid "[CONVERSION ERROR]"
-#~ msgstr "[LỖI CHUYỂN BẢNG MÃ]"
-
-#~ msgid "NetBeans disallows writes of unmodified buffers"
-#~ msgstr "NetBeans không cho phép ghi nhớ bộ đệm chưa có thay đổi nào"
-
-#~ msgid "Partial writes disallowed for NetBeans buffers"
-#~ msgstr "Ghi nhớ một phần bộ đệm NetBeans không được cho phép"
-
-#~ msgid "E460: The resource fork would be lost (add ! to override)"
-#~ msgstr ""
-#~ "E460: Nhánh tài nguyên sẽ bị mất (thêm ! để bỏ qua việc kiểm tra lại)"
-
-#~ msgid " "
-#~ msgstr " "
-
-#~ msgid "E616: vim_SelFile: can't get font %s"
-#~ msgstr "E616: vim_SelFile: không tìm thấy phông chữ %s"
-
-#~ msgid "E614: vim_SelFile: can't return to current directory"
-#~ msgstr "E614: vim_SelFile: không trở lại được thư mục hiện thời"
-
-#~ msgid "Pathname:"
-#~ msgstr "Đường dẫn tới tập tin:"
-
-#~ msgid "E615: vim_SelFile: can't get current directory"
-#~ msgstr "E615: vim_SelFile: không tìm thấy thư mục hiện thời"
-
-#~ msgid "OK"
-#~ msgstr "Đồng ý"
-
-#~ msgid "Cancel"
-#~ msgstr "Hủy bỏ"
-
-#~ msgid "Vim dialog"
-#~ msgstr "Hộp thoại Vim"
-
-#~ msgid "Scrollbar Widget: Could not get geometry of thumb pixmap."
-#~ msgstr "Thanh cuộn: Không thể xác định hình học của thanh cuộn."
-
-#~ msgid "E232: Cannot create BalloonEval with both message and callback"
-#~ msgstr ""
-#~ "E232: Không tạo được BalloonEval với cả thông báo và lời gọi ngược lại"
-
-#~ msgid "E229: Cannot start the GUI"
-#~ msgstr "E229: Không chạy được giao diện đồ họa GUI"
-
-#~ msgid "E230: Cannot read from \"%s\""
-#~ msgstr "E230: Không đọc được từ \"%s\""
-
-#~ msgid "E665: Cannot start GUI, no valid font found"
-#~ msgstr ""
-#~ "E665: Không chạy được giao diện đồ họa GUI, đưa ra phông chữ không đúng"
-
-#~ msgid "E231: 'guifontwide' invalid"
-#~ msgstr "E231: 'guifontwide' có giá trị không đúng"
-
-#~ msgid "E599: Value of 'imactivatekey' is invalid"
-#~ msgstr "E599: Giá trị của 'imactivatekey' không đúng"
-
-#~ msgid "E254: Cannot allocate color %s"
-#~ msgstr "E254: Không chỉ định được màu %s"
-
-#~ msgid "Vim dialog..."
-#~ msgstr "Hộp thoại Vim..."
-
-#~ msgid "Input _Methods"
-#~ msgstr "Phương pháp _nhập liệu"
-
-#~ msgid "VIM - Search and Replace..."
-#~ msgstr "VIM - Tìm kiếm và thay thế..."
-
-#~ msgid "VIM - Search..."
-#~ msgstr "VIM - Tìm kiếm..."
-
-#~ msgid "Find what:"
-#~ msgstr "Tìm kiếm gì:"
-
-#~ msgid "Replace with:"
-#~ msgstr "Thay thế bởi:"
-
-#~ msgid "Match whole word only"
-#~ msgstr "Chỉ tìm tương ứng hoàn toàn với từ"
-
-#~ msgid "Match case"
-#~ msgstr "Có tính kiểu chữ"
-
-#~ msgid "Direction"
-#~ msgstr "Hướng"
-
-#~ msgid "Up"
-#~ msgstr "Lên"
-
-#~ msgid "Down"
-#~ msgstr "Xuống"
-
-#~ msgid "Find Next"
-#~ msgstr "Tìm tiếp"
-
-#~ msgid "Replace"
-#~ msgstr "Thay thế"
-
-#~ msgid "Replace All"
-#~ msgstr "Thay thế tất cả"
-
-#~ msgid "Vim: Received \"die\" request from session manager\n"
-#~ msgstr "Vim: Nhận được yêu cầu \"chết\" (dừng) từ trình quản lý màn hình\n"
-
-#~ msgid "Vim: Main window unexpectedly destroyed\n"
-#~ msgstr "Vim: Cửa sổ chính đã bị đóng đột ngột\n"
-
-#~ msgid "Font Selection"
-#~ msgstr "Chọn phông chữ"
-
-#~ msgid "Used CUT_BUFFER0 instead of empty selection"
-#~ msgstr "Sử dụng CUT_BUFFER0 thay cho lựa chọn trống rỗng"
-
-#~ msgid "Filter"
-#~ msgstr "Đầu lọc"
-
-#~ msgid "Directories"
-#~ msgstr "Thư mục"
-
-#~ msgid "Help"
-#~ msgstr "Trợ giúp"
-
-#~ msgid "Files"
-#~ msgstr "Tập tin"
-
-#~ msgid "Selection"
-#~ msgstr "Lựa chọn"
-
-#~ msgid "Undo"
-#~ msgstr "Hủy thao tác"
-
-#~ msgid "E671: Cannot find window title \"%s\""
-#~ msgstr "E671: Không tìm được tiêu đề cửa sổ \"%s\""
-
-#~ msgid "E243: Argument not supported: \"-%s\"; Use the OLE version."
-#~ msgstr ""
-#~ "E243: Tham số không được hỗ trợ: \"-%s\"; Hãy sử dụng phiên bản OLE."
-
-#~ msgid "E672: Unable to open window inside MDI application"
-#~ msgstr "E672: Không mở được cửa sổ bên trong ứng dụng MDI"
-
-#~ msgid "Find string (use '\\\\' to find  a '\\')"
-#~ msgstr "Tìm kiếm chuỗi (hãy sử dụng '\\\\' để tìm kiếm dấu '\\')"
-
-#~ msgid "Find & Replace (use '\\\\' to find  a '\\')"
-#~ msgstr "Tìm kiếm và Thay thế (hãy sử dụng '\\\\' để tìm kiếm dấu '\\')"
-
-#~ msgid ""
-#~ "Vim E458: Cannot allocate colormap entry, some colors may be incorrect"
-#~ msgstr ""
-#~ "Vim E458: Không chỉ định được bản ghi trong bảng màu, một vài màu có thể "
-#~ "hiển thị không chính xác"
-
-#~ msgid "E250: Fonts for the following charsets are missing in fontset %s:"
-#~ msgstr "E250: Trong bộ phông chữ %s thiếu phông cho các bảng mã sau:"
-
-#~ msgid "E252: Fontset name: %s"
-#~ msgstr "E252: Bộ phông chữ: %s"
-
-#~ msgid "Font '%s' is not fixed-width"
-#~ msgstr "Phông chữ '%s' không phải là phông có độ rộng cố định (fixed-width)"
-
-#~ msgid "E253: Fontset name: %s\n"
-#~ msgstr "E253: Bộ phông chữ: %s\n"
-
-#~ msgid "Font0: %s\n"
-#~ msgstr "Font0: %s\n"
-
-#~ msgid "Font1: %s\n"
-#~ msgstr "Font1: %s\n"
-
-#~ msgid "Font% width is not twice that of font0\n"
-#~ msgstr ""
-#~ "Chiều rộng phông chữ font% phải lớn hơn hai lần so với chiều rộng "
-#~ "font0\n"
-
-#~ msgid "Font0 width: %\n"
-#~ msgstr "Chiều rộng font0: %\n"
-
-#~ msgid ""
-#~ "Font1 width: %\n"
-#~ "\n"
-#~ msgstr ""
-#~ "Chiều rộng font1: %\n"
-#~ "\n"
-
-#~ msgid "E256: Hangul automata ERROR"
-#~ msgstr "E256: LỖI máy tự động Hangual (tiếng Hàn)"
-
-#~ msgid "E563: stat error"
-#~ msgstr "E563: lỗi stat"
-
-#~ msgid "E625: cannot open cscope database: %s"
-#~ msgstr "E625: không mở được cơ sở dữ liệu cscope: %s"
-
-#~ msgid "E626: cannot get cscope database information"
-#~ msgstr "E626: không lấy được thông tin về cơ sở dữ liệu cscope"
-
-#~ msgid "E569: maximum number of cscope connections reached"
-#~ msgstr "E569: đã đạt tới số kết nối lớn nhất cho phép với cscope"
-
-#~ msgid ""
-#~ "E263: Sorry, this command is disabled, the Python library could not be "
-#~ "loaded."
-#~ msgstr ""
-#~ "E263: Rất tiếc câu lệnh này không làm việc, vì thư viện Python chưa được "
-#~ "nạp."
-
-#~ msgid "E659: Cannot invoke Python recursively"
-#~ msgstr "E659: Không thể gọi Python một cách đệ quy"
-
-#~ msgid "can't delete OutputObject attributes"
-#~ msgstr "Không xóa được thuộc tính OutputObject"
-
-#~ msgid "softspace must be an integer"
-#~ msgstr "giá trị softspace phải là một số nguyên"
-
-#~ msgid "invalid attribute"
-#~ msgstr "thuộc tính không đúng"
-
-#~ msgid "writelines() requires list of strings"
-#~ msgstr "writelines() yêu cầu một danh sách các chuỗi"
-
-#~ msgid "E264: Python: Error initialising I/O objects"
-#~ msgstr "E264: Python: Lỗi khi bắt đầu sử dụng vật thể I/O"
-
-#~ msgid "invalid expression"
-#~ msgstr "biểu thức không đúng"
-
-#~ msgid "expressions disabled at compile time"
-#~ msgstr "biểu thức bị tắt khi biên dịch"
-
-#~ msgid "attempt to refer to deleted buffer"
-#~ msgstr "cố chỉ đến bộ đệm đã bị xóa"
-
-#~ msgid "line number out of range"
-#~ msgstr "số thứ tự của dòng vượt quá giới hạn"
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "invalid mark name"
-#~ msgstr "tên dấu hiệu không đúng"
-
-#~ msgid "no such buffer"
-#~ msgstr "không có bộ đệm như vậy"
-
-#~ msgid "attempt to refer to deleted window"
-#~ msgstr "cố chỉ đến cửa sổ đã bị đóng"
-
-#~ msgid "readonly attribute"
-#~ msgstr "thuộc tính chỉ đọc"
-
-#~ msgid "cursor position outside buffer"
-#~ msgstr "vị trí con trỏ nằm ngoài bộ đệm"
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "no such window"
-#~ msgstr "không có cửa sổ như vậy"
-
-#~ msgid "cannot save undo information"
-#~ msgstr "không ghi được thông tin về việc hủy thao tác"
-
-#~ msgid "cannot delete line"
-#~ msgstr "không xóa được dòng"
-
-#~ msgid "cannot replace line"
-#~ msgstr "không thay thế được dòng"
-
-#~ msgid "cannot insert line"
-#~ msgstr "không chèn được dòng"
-
-#~ msgid "string cannot contain newlines"
-#~ msgstr "chuỗi không thể chứa ký tự dòng mới"
-
-#~ msgid ""
-#~ "E266: Sorry, this command is disabled, the Ruby library could not be "
-#~ "loaded."
-#~ msgstr ""
-#~ "E266: Rất tiếc câu lệnh này không làm việc, vì thư viện Ruby chưa đượcnạp."
-
-#~ msgid "E273: unknown longjmp status %d"
-#~ msgstr "E273: không rõ trạng thái của longjmp %d"
-
-#~ msgid "Toggle implementation/definition"
-#~ msgstr "Bật tắt giữa thi hành/định nghĩa"
-
-#~ msgid "Show base class of"
-#~ msgstr "Hiển thị hạng cơ bản của"
-
-#~ msgid "Show overridden member function"
-#~ msgstr "Hiển thị hàm số bị nạp đè lên"
-
-#~ msgid "Retrieve from file"
-#~ msgstr "Nhận từ tập tin"
-
-#~ msgid "Retrieve from project"
-#~ msgstr "Nhận từ dự án"
-
-#~ msgid "Retrieve from all projects"
-#~ msgstr "Nhận từ tất cả các dự án"
-
-#~ msgid "Retrieve"
-#~ msgstr "Nhận"
-
-#~ msgid "Show source of"
-#~ msgstr "Hiển thị mã nguồn"
-
-#~ msgid "Find symbol"
-#~ msgstr "Tìm ký hiệu"
-
-#~ msgid "Browse class"
-#~ msgstr "Duyệt hạng"
-
-#~ msgid "Show class in hierarchy"
-#~ msgstr "Hiển thị hạng trong hệ thống cấp bậc"
-
-#~ msgid "Show class in restricted hierarchy"
-#~ msgstr "Hiển thị hạng trong hệ thống cấp bậc giới hạn"
-
-#~ msgid "Xref refers to"
-#~ msgstr "Xref chỉ đến"
-
-#~ msgid "Xref referred by"
-#~ msgstr "Liên kết đến xref từ"
-
-#~ msgid "Xref has a"
-#~ msgstr "Xref có một"
-
-#~ msgid "Xref used by"
-#~ msgstr "Xref được sử dụng bởi"
-
-#~ msgid "Show docu of"
-#~ msgstr "Hiển thị docu của"
-
-#~ msgid "Generate docu for"
-#~ msgstr "Tạo docu cho"
-
-#~ msgid ""
-#~ "Cannot connect to SNiFF+. Check environment (sniffemacs must be found in "
-#~ "$PATH).\n"
-#~ msgstr ""
-#~ "Không kết nối được tới SNiFF+. Hãy kiểm tra cấu hình môi trường."
-#~ "(sniffemacs phải được chỉ ra trong biến $PATH).\n"
-
-#~ msgid "E274: Sniff: Error during read. Disconnected"
-#~ msgstr "E274: Sniff: Lỗi trong thời gian đọc. Ngắt kết nối"
-
-#~ msgid "SNiFF+ is currently "
-#~ msgstr "Trong thời điểm hiện nay SNiFF+ "
-
-#~ msgid "not "
-#~ msgstr "không "
-
-#~ msgid "connected"
-#~ msgstr "được kết nối"
-
-#~ msgid "E275: Unknown SNiFF+ request: %s"
-#~ msgstr "E275: không rõ yêu cầu của SNiFF+: %s"
-
-#~ msgid "E276: Error connecting to SNiFF+"
-#~ msgstr "E276: Lỗi kết nối với SNiFF+"
-
-#~ msgid "E278: SNiFF+ not connected"
-#~ msgstr "E278: SNiFF+ chưa được kết nối"
-
-#~ msgid "Sniff: Error during write. Disconnected"
-#~ msgstr "Sniff: Lỗi trong thời gian ghi nhớ. Ngắt kết nối"
-
-#~ msgid "not implemented yet"
-#~ msgstr "tạm thời chưa được thực thi"
-
-#~ msgid "unknown option"
-#~ msgstr "tùy chọn không rõ"
-
-#~ msgid "cannot set line(s)"
-#~ msgstr "không thể đặt (các) dòng"
-
-#~ msgid "mark not set"
-#~ msgstr "dấu hiệu chưa được đặt"
-
-#~ msgid "row %d column %d"
-#~ msgstr "hàng %d cột %d"
-
-#~ msgid "cannot insert/append line"
-#~ msgstr "không thể chèn hoặc thêm dòng"
-
-#~ msgid "unknown flag: "
-#~ msgstr "cờ không biết: "
-
-#~ msgid "unknown vimOption"
-#~ msgstr "không rõ tùy chọn vimOption"
-
-#~ msgid "keyboard interrupt"
-#~ msgstr "sự gián đoạn của bàn phím"
-
-#~ msgid "vim error"
-#~ msgstr "lỗi của vim"
-
-#~ msgid "cannot create buffer/window command: object is being deleted"
-#~ msgstr ""
-#~ "không tạo được câu lệnh của bộ đệm hay của cửa sổ: vật thể đang bị xóa"
-
-#~ msgid ""
-#~ "cannot register callback command: buffer/window is already being deleted"
-#~ msgstr ""
-#~ "không đăng ký được câu lệnh gọi ngược: bộ đệm hoặc cửa sổ đang bị xóa"
-
-#~ msgid ""
-#~ "E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-"
-#~ "dev@vim.org"
-#~ msgstr ""
-#~ "E280: LỖI NẶNG CỦA TCL: bị hỏng danh sách liên kết!? Hãy thông báo việc "
-#~ "nàyđến danh sách thư (mailing list) vim-dev@vim.org"
-
-#~ msgid "cannot register callback command: buffer/window reference not found"
-#~ msgstr ""
-#~ "không đăng ký được câu lệnh gọi ngược: không tìm thấy liên kết đến bộ đệm "
-#~ "hoặc cửa sổ"
-
-#~ msgid ""
-#~ "E571: Sorry, this command is disabled: the Tcl library could not be "
-#~ "loaded."
-#~ msgstr ""
-#~ "E571: Rất tiếc là câu lệnh này không làm việc, vì thư viện Tcl chưa được "
-#~ "nạp"
-
-#~ msgid ""
-#~ "E281: TCL ERROR: exit code is not int!? Please report this to vim-dev@vim."
-#~ "org"
-#~ msgstr ""
-#~ "E281: LỖI TCL: mã thoát ra không phải là một số nguyên!? Hãy thông báo "
-#~ "điều này đến danh sách thư (mailing list) vim-dev@vim.org"
-
-#~ msgid "cannot get line"
-#~ msgstr "không nhận được dòng"
-
-#~ msgid "Unable to register a command server name"
-#~ msgstr "Không đăng ký được một tên cho máy chủ câu lệnh"
-
-#~ msgid "E248: Failed to send command to the destination program"
-#~ msgstr "E248: Gửi câu lệnh vào chương trình khác không thành công"
-
-#~ msgid "E251: VIM instance registry property is badly formed.  Deleted!"
-#~ msgstr "E251: Thuộc tính đăng ký của Vim được định dạng không đúng.  Xóa!"
-
-#~ msgid "This Vim was not compiled with the diff feature."
-#~ msgstr "Vim không được biên dịch với tính năng hỗ trợ xem khác biệt (diff)."
-
-#~ msgid "-register\t\tRegister this gvim for OLE"
-#~ msgstr "-register\t\tĐăng ký gvim này cho OLE"
-
-#~ msgid "-unregister\t\tUnregister gvim for OLE"
-#~ msgstr "-unregister\t\tBỏ đăng ký gvim này cho OLE"
-
-#~ msgid "-g\t\t\tRun using GUI (like \"gvim\")"
-#~ msgstr "-g\t\t\tSử dụng giao diện đồ họa GUI (giống \"gvim\")"
-
-#~ msgid "-f  or  --nofork\tForeground: Don't fork when starting GUI"
-#~ msgstr ""
-#~ "-f  hoặc  --nofork\tTrong chương trình hoạt động: Không thực hiện fork "
-#~ "khi chạy GUI"
-
-#~ msgid "-V[N]\t\tVerbose level"
-#~ msgstr "-V[N]\t\tMức độ chi tiết của thông báo"
-
-#~ msgid "-f\t\t\tDon't use newcli to open window"
-#~ msgstr "-f\t\t\tKhông sử dụng newcli để mở cửa sổ"
-
-#~ msgid "-dev \t\tUse  for I/O"
-#~ msgstr "-dev \t\tSử dụng  cho I/O"
-
-#~ msgid "-U \t\tUse  instead of any .gvimrc"
-#~ msgstr "-U \t\tSử dụng  thay thế cho mọi .gvimrc"
-
-#~ msgid "-x\t\t\tEdit encrypted files"
-#~ msgstr "-x\t\t\tSoạn thảo tập tin đã mã hóa"
-
-#~ msgid "-display \tConnect vim to this particular X-server"
-#~ msgstr "-display \tKết nối vim tới máy chủ X đã chỉ ra"
-
-#~ msgid "-X\t\t\tDo not connect to X server"
-#~ msgstr "-X\t\t\tKhông thực hiện việc kết nối tới máy chủ X"
-
-#~ msgid "--remote \tEdit  in a Vim server if possible"
-#~ msgstr "--remote \tSoạn thảo  trên máy chủ Vim nếu có thể"
-
-#~ msgid "--remote-silent   Same, don't complain if there is no server"
-#~ msgstr ""
-#~ "--remote-silent   Cũng vậy, nhưng không kêu ca dù không có máy "
-#~ "chủ"
-
-#~ msgid ""
-#~ "--remote-wait   As --remote but wait for files to have been edited"
-#~ msgstr "--remote-wait   Cũng như --remote, nhưng chờ sự kết thúc"
-
-#~ msgid ""
-#~ "--remote-wait-silent   Same, don't complain if there is no server"
-#~ msgstr ""
-#~ "--remote-wait-silent   Cũng vậy, nhưng không kêu ca dù không có "
-#~ "máy chủ"
-
-#~ msgid "--remote-send \tSend  to a Vim server and exit"
-#~ msgstr "--remote-send \tGửi  lên máy chủ Vim và thoát"
-
-#~ msgid ""
-#~ "--remote-expr \tEvaluate  in a Vim server and print result"
-#~ msgstr ""
-#~ "--remote-expr \tTính  trên máy chủ Vim và in ra kết "
-#~ "quả"
-
-#~ msgid "--serverlist\t\tList available Vim server names and exit"
-#~ msgstr "--serverlist\t\tHiển thị danh sách máy chủ Vim và thoát"
-
-#~ msgid "--servername \tSend to/become the Vim server "
-#~ msgstr "--servername \tGửi lên (hoặc trở thành) máy chủ Vim với "
-
-#~ msgid ""
-#~ "\n"
-#~ "Arguments recognised by gvim (Motif version):\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Tham số cho gvim (phiên bản Motif):\n"
-
-#~ msgid ""
-#~ "\n"
-#~ "Arguments recognised by gvim (neXtaw version):\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Tham số cho gvim (phiên bản neXtaw):\n"
-
-#~ msgid ""
-#~ "\n"
-#~ "Arguments recognised by gvim (Athena version):\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Tham số cho gvim (phiên bản Athena):\n"
-
-#~ msgid "-display \tRun vim on "
-#~ msgstr "-display \tChạy vim trong  đã chỉ ra"
-
-#~ msgid "-iconic\t\tStart vim iconified"
-#~ msgstr "-iconic\t\tChạy vim ở dạng thu nhỏ"
-
-#~ msgid "-name \t\tUse resource as if vim was "
-#~ msgstr "-name \t\tSử dụng tài nguyên giống như khi vim có "
-
-#~ msgid "\t\t\t  (Unimplemented)\n"
-#~ msgstr "\t\t\t  (Chưa được thực thi)\n"
-
-#~ msgid "-background \tUse  for the background (also: -bg)"
-#~ msgstr "-background \tSử dụng  chỉ ra cho nền (cũng như: -bg)"
-
-#~ msgid "-foreground \tUse  for normal text (also: -fg)"
-#~ msgstr ""
-#~ "-foreground \tSử dụng  cho văn bản thông thường (cũng như: -fg)"
+msgid "   system vimrc file: \""
+msgstr "            tập tin vimrc chung cho hệ thống: \""
 
-#~ msgid "-font \t\tUse  for normal text (also: -fn)"
-#~ msgstr ""
-#~ "-font \t\tSử dụng  chữ cho văn bản thông thường (cũng như: -"
-#~ "fn)"
+msgid "     user vimrc file: \""
+msgstr "         tập tin vimrc của người dùng: \""
 
-#~ msgid "-boldfont \tUse  for bold text"
-#~ msgstr "-boldfont \tSử dụng  chữ cho văn bản in đậm"
+msgid " 2nd user vimrc file: \""
+msgstr "  tập tin vimrc thứ hai của người dùng: \""
 
-#~ msgid "-italicfont \tUse  for italic text"
-#~ msgstr "-italicfont \tSử dụng  chữ cho văn bản in nghiêng"
+msgid " 3rd user vimrc file: \""
+msgstr "  tập tin vimrc thứ ba của người dùng: \""
 
-#~ msgid "-geometry \tUse  for initial geometry (also: -geom)"
-#~ msgstr ""
-#~ "-geometry \tSử dụng  ban đầu (cũng như: -geom)"
+msgid "      user exrc file: \""
+msgstr "         tập tin exrc của người dùng: \""
 
-#~ msgid "-borderwidth \tUse a border width of  (also: -bw)"
-#~ msgstr ""
-#~ "-borderwidth \tSử dụng đường viền có chiều  (cũng như: -bw)"
+msgid "  2nd user exrc file: \""
+msgstr "  tập tin exrc thứ hai của người dùng: \""
 
-#~ msgid ""
-#~ "-scrollbarwidth   Use a scrollbar width of  (also: -sw)"
-#~ msgstr ""
-#~ "-scrollbarwidth  Sử dụng thanh cuộn với chiều  (cũng như: -sw)"
+msgid "  system gvimrc file: \""
+msgstr "            tập tin gvimrc chung cho hệ thống: \""
 
-#~ msgid "-menuheight \tUse a menu bar height of  (also: -mh)"
-#~ msgstr ""
-#~ "-menuheight \tSử dụng thanh trình đơn với chiều  (cũng như: -mh)"
+msgid "    user gvimrc file: \""
+msgstr "         tập tin gvimrc của người dùng: \""
 
-#~ msgid "-reverse\t\tUse reverse video (also: -rv)"
-#~ msgstr "-reverse\t\tSử dụng chế độ video đảo ngược (cũng như: -rv)"
+msgid "2nd user gvimrc file: \""
+msgstr "  tập tin gvimrc thứ hai của người dùng: \""
 
-#~ msgid "+reverse\t\tDon't use reverse video (also: +rv)"
-#~ msgstr "+reverse\t\tKhông sử dụng chế độ video đảo ngược (cũng như: +rv)"
+msgid "3rd user gvimrc file: \""
+msgstr "  tập tin gvimrc thứ ba của người dùng: \""
 
-#~ msgid "-xrm \tSet the specified resource"
-#~ msgstr "-xrm \tĐặt  chỉ ra"
-
-#~ msgid ""
-#~ "\n"
-#~ "Arguments recognised by gvim (RISC OS version):\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Tham số cho gvim (phiên bản RISC OS):\n"
+msgid "    system menu file: \""
+msgstr "             tập tin trình đơn chung cho hệ thống: \""
 
-#~ msgid "--columns \tInitial width of window in columns"
-#~ msgstr "--columns \tChiều rộng ban đầu của cửa sổ tính theo số cột"
+msgid "  fall-back for $VIM: \""
+msgstr "          giá trị $VIM theo mặc định: \""
 
-#~ msgid "--rows \tInitial height of window in rows"
-#~ msgstr "--rows \tChiều cao ban đầu của cửa sổ tính theo số dòng"
+msgid " f-b for $VIMRUNTIME: \""
+msgstr "   giá trị $VIMRUNTIME theo mặc định: \""
 
-#~ msgid ""
-#~ "\n"
-#~ "Arguments recognised by gvim (GTK+ version):\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Tham số cho gvim (phiên bản GTK+):\n"
+msgid "Compilation: "
+msgstr "Tham số biên dịch: "
 
-#~ msgid "-display \tRun vim on  (also: --display)"
-#~ msgstr ""
-#~ "-display \tChạy vim trên  chỉ ra (cũng như: --display)"
+msgid "Compiler: "
+msgstr "Trình biên dịch: "
 
-#~ msgid "--role \tSet a unique role to identify the main window"
-#~ msgstr "--role \tĐặt  duy nhất để nhận diện cửa sổ chính"
+msgid "Linking: "
+msgstr "Liên kết: "
 
-#~ msgid "--socketid \tOpen Vim inside another GTK widget"
-#~ msgstr "--socketid \tMở Vim bên trong thành phần GTK khác"
+msgid "  DEBUG BUILD"
+msgstr "  BIÊN DỊCH SỬA LỖI (DEBUG)"
 
-#~ msgid "-P \tOpen Vim inside parent application"
-#~ msgstr "-P \tMở Vim bên trong ứng dụng mẹ"
+msgid "VIM - Vi IMproved"
+msgstr "VIM ::: Vi IMproved (Vi cải tiến) ::: Phiên bản tiếng Việt"
 
-#~ msgid "No display"
-#~ msgstr "Không có màn hình"
+msgid "version "
+msgstr "phiên bản "
 
-#~ msgid ": Send failed.\n"
-#~ msgstr ": Gửi không thành công.\n"
+msgid "by Bram Moolenaar et al."
+msgstr "Do Bram Moolenaar và những người khác thực hiện"
 
-#~ msgid ": Send failed. Trying to execute locally\n"
-#~ msgstr ": Gửi không thành công. Thử thực hiện nội bộ\n"
+msgid "Vim is open source and freely distributable"
+msgstr "Vim là chương trình mã nguồn mở và phân phối tự do"
 
-#~ msgid "%d of %d edited"
-#~ msgstr "đã soạn thảo %d từ %d"
+msgid "Help poor children in Uganda!"
+msgstr "Hãy giúp đỡ trẻ em nghèo Uganda!"
 
-#~ msgid "No display: Send expression failed.\n"
-#~ msgstr "Không có màn hình: gửi biểu thức không thành công.\n"
+msgid "type  :help iccf       for information "
+msgstr "hãy gõ :help iccf       để biết thêm thông tin"
 
-#~ msgid ": Send expression failed.\n"
-#~ msgstr ": Gửi biểu thức không thành công.\n"
+msgid "type  :q               to exit         "
+msgstr "    hãy gõ :q               để thoát khỏi chương trình     "
 
-#~ msgid "E543: Not a valid codepage"
-#~ msgstr "E543: Bảng mã không cho phép"
+msgid "type  :help  or    for on-line help"
+msgstr " hãy gõ :help hoặc   để có được trợ giúp        "
 
-#~ msgid "E285: Failed to create input context"
-#~ msgstr "E285: Không tạo được nội dung nhập vào"
+msgid "type  :help version9   for version info"
+msgstr "hãy gõ :help version9   để biết về phiên bản này  "
 
-#~ msgid "E286: Failed to open input method"
-#~ msgstr "E286: Việc thử mở phương pháp nhập không thành công"
+msgid "Running in Vi compatible mode"
+msgstr "Làm việc trong chế độ tương thích với Vi"
 
-#~ msgid "E287: Warning: Could not set destroy callback to IM"
-#~ msgstr ""
-#~ "E287: Cảnh báo: không đặt được sự gọi ngược hủy diệt thành phương pháp "
-#~ "nhập"
+msgid "type  :set nocp        for Vim defaults"
+msgstr "hãy gõ :set nocp        để chuyển vào chế độ Vim     "
 
-#~ msgid "E288: input method doesn't support any style"
-#~ msgstr "E288: phương pháp nhập không hỗ trợ bất kỳ phong cách (style) nào"
+msgid "type  :help cp-default for info on this"
+msgstr "hãy gõ :help cp-default để có thêm thông tin về điều này"
 
-#~ msgid "E289: input method doesn't support my preedit type"
-#~ msgstr "E289: phương pháp nhập không hỗ trợ loại soạn thảo trước của Vim"
+msgid "menu  Help->Orphans           for information    "
+msgstr "trình đơn Trợ giúp->Mồ côi             để có thêm thông tin     "
 
-#~ msgid "E290: over-the-spot style requires fontset"
-#~ msgstr "E290: phong cách over-the-spot yêu cầu một bộ phông chữ"
+msgid "Running modeless, typed text is inserted"
+msgstr "Không chế độ, văn bản nhập vào sẽ được chèn"
 
-#~ msgid "E291: Your GTK+ is older than 1.2.3. Status area disabled"
-#~ msgstr "E291: GTK+ cũ hơn 1.2.3. Vùng chỉ trạng thái không làm việc"
+msgid "menu  Edit->Global Settings->Toggle Insert Mode  "
+msgstr "trình đơn Soạn thảo->Thiết lập chung->Chế độ chèn                     "
 
-#~ msgid "E292: Input Method Server is not running"
-#~ msgstr "E292: Máy chủ phương pháp nhập liệu chưa được chạy"
+msgid "                              for two modes      "
+msgstr "                                 cho hai chế độ               "
 
-#~ msgid ""
-#~ "\n"
-#~ "         [not usable with this version of Vim]"
-#~ msgstr ""
-#~ "\n"
-#~ "         [không sử dụng được với phiên bản này của Vim]"
+msgid "menu  Edit->Global Settings->Toggle Vi Compatible"
+msgstr ""
+"trình đơn Soạn thảo->Thiết lập chung->Tương thích với Vi                "
 
-#~ msgid ""
-#~ "&Open Read-Only\n"
-#~ "&Edit anyway\n"
-#~ "&Recover\n"
-#~ "&Quit\n"
-#~ "&Abort\n"
-#~ "&Delete it"
-#~ msgstr ""
-#~ "&O Mở chỉ để đọc\n"
-#~ "&E Vẫn soạn thảo\n"
-#~ "&R Phục hồi\n"
-#~ "&Q Thoát\n"
-#~ "&A Gián đoạn&D Xóa nó"
+msgid "                              for Vim defaults   "
+msgstr ""
+"                                 để chuyển vào chế độ Vim mặc định       "
 
-#~ msgid "Tear off this menu"
-#~ msgstr "Chia cắt trình đơn này"
+msgid "Sponsor Vim development!"
+msgstr "Hãy giúp đỡ phát triển Vim!"
 
-#~ msgid "[string too long]"
-#~ msgstr "[chuỗi quá dài]"
+msgid "Become a registered Vim user!"
+msgstr "Hãy trở thành người dùng đăng ký của Vim!"
 
-#~ msgid "Hit ENTER to continue"
-#~ msgstr "Nhấn phím ENTER để tiếp tục"
+msgid "type  :help sponsor    for information "
+msgstr "hãy gõ :help sponsor    để biết thêm thông tin "
 
-#~ msgid " (RET/BS: line, SPACE/b: page, d/u: half page, q: quit)"
-#~ msgstr " (RET/BS: dòng, SPACE/b: trang, d/u: nửa trang, q: thoát)"
+msgid "type  :help register   for information "
+msgstr "hãy gõ :help register   để biết thêm thông tin "
 
-#~ msgid " (RET: line, SPACE: page, d: half page, q: quit)"
-#~ msgstr " (RET: dòng, SPACE: trang, d: nửa trang, q: thoát)"
+msgid "menu  Help->Sponsor/Register  for information    "
+msgstr "trình đơn Trợ giúp->Giúp đỡ/Đăng ký để biết thêm thông tin    "
 
-#~ msgid "Save File dialog"
-#~ msgstr "Ghi nhớ tập tin"
+msgid "WARNING: Windows 95/98/ME detected"
+msgstr "CẢNH BÁO: nhận ra Windows 95/98/ME"
 
-#~ msgid "Open File dialog"
-#~ msgstr "Mở tập tin"
+msgid "type  :help windows95  for info on this"
+msgstr "hãy gõ :help windows95  để biết thêm thông tin     "
 
-#~ msgid "E338: Sorry, no file browser in console mode"
-#~ msgstr ""
-#~ "E338: Xin lỗi nhưng không có trình duyệt tập tin trong chế độ kênh giao "
-#~ "tác (console)"
+msgid "E441: There is no preview window"
+msgstr "E441: Không có cửa sổ xem trước"
 
-#~ msgid "Vim: preserving files...\n"
-#~ msgstr "Vim: ghi nhớ các tập tin...\n"
+msgid "E442: Can't split topleft and botright at the same time"
+msgstr ""
+"E442: Cửa sổ không thể đồng thời ở bên trái phía trên và bên phải phía dưới"
 
-#~ msgid "Vim: Finished.\n"
-#~ msgstr "Vim: Đã xong.\n"
+msgid "E443: Cannot rotate when another window is split"
+msgstr "E443: Không đổi được chỗ khi cửa sổ khác được chia"
 
-#~ msgid "ERROR: "
-#~ msgstr "LỖI: "
+msgid "E444: Cannot close last window"
+msgstr "E444: Không được đóng cửa sổ cuối cùng"
 
-#~ msgid ""
-#~ "\n"
-#~ "[bytes] total alloc-freed %-%, in use %, peak use "
-#~ "%\n"
-#~ msgstr ""
-#~ "\n"
-#~ "[byte] tổng phân phối-còn trống %-%, sử dụng %, "
-#~ "píc sử dụng %\n"
+msgid "Already only one window"
+msgstr "Chỉ có một cửa sổ"
 
-#~ msgid ""
-#~ "[calls] total re/malloc()'s %, total free()'s %\n"
-#~ "\n"
-#~ msgstr ""
-#~ "[gọi] tổng re/malloc() %, tổng free() %\n"
-#~ "\n"
+msgid "E445: Other window contains changes"
+msgstr "E445: Cửa sổ khác có thay đổi chưa được ghi nhớ"
 
-#~ msgid "E340: Line is becoming too long"
-#~ msgstr "E340: Dòng đang trở thành quá dài"
+msgid "E446: No file name under cursor"
+msgstr "E446: Không có tên tập tin tại vị trí con trỏ"
 
-#~ msgid "E341: Internal error: lalloc(%, )"
-#~ msgstr "E341: Lỗi nội bộ: lalloc(%, )"
+#, c-format
+msgid "E447: Can't find file \"%s\" in path"
+msgstr "E447: Không tìm thấy tập tin \"%s\" trong đường dẫn"
 
-#~ msgid "E547: Illegal mouseshape"
-#~ msgstr "E547: Dạng trỏ chuột không cho phép"
+#, c-format
+msgid "E370: Could not load library %s"
+msgstr "E370: Không nạp được thư viện %s"
 
-#~ msgid "Enter encryption key: "
-#~ msgstr "Nhập mật khẩu để mã hóa: "
+msgid "Sorry, this command is disabled: the Perl library could not be loaded."
+msgstr "Xin lỗi, câu lệnh này bị tắt: không nạp được thư viện Perl."
 
-#~ msgid "Enter same key again: "
-#~ msgstr "      Nhập lại mật khẩu:"
+msgid "E299: Perl evaluation forbidden in sandbox without the Safe module"
+msgstr ""
+"E299: Không cho phép sự tính toán Perl trong hộp cát mà không có môđun An "
+"toàn"
 
-#~ msgid "Keys don't match!"
-#~ msgstr "Hai mật khẩu không trùng nhau!"
+msgid "Edit with &multiple Vims"
+msgstr "Soạn thảo trong nhiều Vi&m"
 
-#~ msgid "Cannot connect to Netbeans #2"
-#~ msgstr "Không kết nối được với Netbeans #2"
+msgid "Edit with single &Vim"
+msgstr "Soạn thảo trong một &Vim"
 
-#~ msgid "Cannot connect to Netbeans"
-#~ msgstr "Không kết nối được với NetBeans"
+msgid "&Diff with Vim"
+msgstr "&So sánh (diff) qua Vim"
 
-#~ msgid "E668: Wrong access mode for NetBeans connection info file: \"%s\""
-#~ msgstr ""
-#~ "E668: Chế độ truy cập thông tin về liên kết với NetBeans không đúng: \"%s"
-#~ "\""
+msgid "Edit with &Vim"
+msgstr "Soạn thảo trong &Vim"
 
-#~ msgid "read from Netbeans socket"
-#~ msgstr "đọc từ socket NetBeans"
+msgid "Edit with existing Vim - &"
+msgstr "Soạn thảo trong Vim đã chạy - &"
 
-#~ msgid "E658: NetBeans connection lost for buffer %"
-#~ msgstr "E658: Bị mất liên kết với NetBeans cho bộ đệm %"
+msgid "Edits the selected file(s) with Vim"
+msgstr "Soạn thảo (các) tập tin đã chọn trong Vim"
 
-#~ msgid "freeing % lines"
-#~ msgstr "đã làm sạch % dòng"
+msgid "Error creating process: Check if gvim is in your path!"
+msgstr "Lỗi tạo tiến trình: Hãy kiểm tra xem gvim có trong đường dẫn không!"
 
-#~ msgid "E530: Cannot change term in GUI"
-#~ msgstr "E530: Không thể thay đổi terminal trong giao diện đồ họa GUI"
+msgid "gvimext.dll error"
+msgstr "lỗi gvimext.dll"
 
-#~ msgid "E531: Use \":gui\" to start the GUI"
-#~ msgstr "E531: Hãy sử dụng \":gui\" để chạy giao diện đồ họa GUI"
+msgid "Path length too long!"
+msgstr "Đường dẫn quá dài!"
 
-#~ msgid "E617: Cannot be changed in the GTK+ 2 GUI"
-#~ msgstr "E617: Không thể thay đổi trong giao diện đồ họa GTK+ 2"
+msgid "--No lines in buffer--"
+msgstr "-- Không có dòng nào trong bộ đệm --"
 
-#~ msgid "E597: can't select fontset"
-#~ msgstr "E597: không chọn được bộ phông chữ"
+msgid "E470: Command aborted"
+msgstr "E470: Câu lệnh bị dừng"
 
-#~ msgid "E598: Invalid fontset"
-#~ msgstr "E598: Bộ phông chữ không đúng"
+msgid "E471: Argument required"
+msgstr "E471: Cần chỉ ra tham số"
 
-#~ msgid "E533: can't select wide font"
-#~ msgstr "E533: không chọn được phông chữ với các ký tự có chiều rộng gấp đôi"
+msgid "E10: \\ should be followed by /, ? or &"
+msgstr "E10: Sau \\ phải là các ký tự /, ? hoặc &"
 
-#~ msgid "E534: Invalid wide font"
-#~ msgstr "E534: Phông chữ, với ký tự có chiều rộng gấp đôi, không đúng"
+msgid "E11: Invalid in command-line window;  executes, CTRL-C quits"
+msgstr "E11: Lỗi trong cửa sổ dòng lệnh;  thực hiện, CTRL-C thoát"
 
-#~ msgid "E538: No mouse support"
-#~ msgstr "E538: Chuột không được hỗ trợ"
+msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search"
+msgstr ""
+"E12: Câu lệnh không cho phép từ exrc/vimrc trong thư mục hiện thời hoặc "
+"trong tìm kiếm thẻ ghi"
 
-#~ msgid "cannot open "
-#~ msgstr "không mở được "
+msgid "E171: Missing :endif"
+msgstr "E171: Thiếu câu lệnh :endif"
 
-#~ msgid "VIM: Can't open window!\n"
-#~ msgstr "VIM: Không mở được cửa sổ!\n"
+msgid "E600: Missing :endtry"
+msgstr "E600: Thiếu câu lệnh :endtry"
 
-#~ msgid "Need Amigados version 2.04 or later\n"
-#~ msgstr "Cần Amigados phiên bản 2.04 hoặc mới hơn\n"
+msgid "E170: Missing :endwhile"
+msgstr "E170: Thiếu câu lệnh :endwhile"
 
-#~ msgid "Need %s version %\n"
-#~ msgstr "Cần %s phiên bản %\n"
+msgid "E588: :endwhile without :while"
+msgstr "E588: Câu lệnh :endwhile không có lệnh :while (1 cặp)"
 
-#~ msgid "Cannot open NIL:\n"
-#~ msgstr "Không mở được NIL:\n"
+msgid "E13: File exists (add ! to override)"
+msgstr "E13: Tập tin đã tồn tại (thêm ! để ghi chèn)"
 
-#~ msgid "Cannot create "
-#~ msgstr "Không tạo được "
+msgid "E472: Command failed"
+msgstr "E472: Không thực hiện thành công câu lệnh"
 
-#~ msgid "Vim exiting with %d\n"
-#~ msgstr "Thoát Vim với mã %d\n"
+#, c-format
+msgid "E234: Unknown fontset: %s"
+msgstr "E234: Không rõ bộ phông chữ: %s"
 
-#~ msgid "cannot change console mode ?!\n"
-#~ msgstr "không thay đổi được chế độ kênh giao tác (console)?!\n"
+#, c-format
+msgid "E235: Unknown font: %s"
+msgstr "E235: Không rõ phông chữ: %s"
 
-#~ msgid "mch_get_shellsize: not a console??\n"
-#~ msgstr "mch_get_shellsize: không phải là kênh giao tác (console)??\n"
+#, c-format
+msgid "E236: Font \"%s\" is not fixed-width"
+msgstr "E236: Phông chữ \"%s\" không có độ rộng cố định (fixed-width)"
 
-#~ msgid "Cannot execute "
-#~ msgstr "Không chạy được "
+msgid "E473: Internal error"
+msgstr "E473: Lỗi nội bộ"
 
-#~ msgid "shell "
-#~ msgstr "shell "
+msgid "Interrupted"
+msgstr "Bị gián đoạn"
 
-#~ msgid " returned\n"
-#~ msgstr " thoát\n"
+msgid "E14: Invalid address"
+msgstr "E14: Địa chỉ không cho phép"
 
-#~ msgid "ANCHOR_BUF_SIZE too small."
-#~ msgstr "Giá trị ANCHOR_BUF_SIZE quá nhỏ."
+msgid "E474: Invalid argument"
+msgstr "E474: Tham số không cho phép"
 
-#~ msgid "I/O ERROR"
-#~ msgstr "LỖI I/O (NHẬP/XUẤT)"
+#, c-format
+msgid "E475: Invalid argument: %s"
+msgstr "E475: Tham số không cho phép: %s"
 
-#~ msgid "...(truncated)"
-#~ msgstr "...(bị cắt bớt)"
+#, c-format
+msgid "E15: Invalid expression: %s"
+msgstr "E15: Biểu thức không cho phép: %s"
 
-#~ msgid "'columns' is not 80, cannot execute external commands"
-#~ msgstr ""
-#~ "Tùy chọn 'columns' khác 80, chương trình ngoại trú không thể thực hiện"
+msgid "E16: Invalid range"
+msgstr "E16: Vùng không cho phép"
 
-#~ msgid "to %s on %s"
-#~ msgstr "tới %s trên %s"
+msgid "E476: Invalid command"
+msgstr "E476: Câu lệnh không cho phép"
 
-#~ msgid "E613: Unknown printer font: %s"
-#~ msgstr "E613: Không rõ phông chữ của máy in: %s"
+#, c-format
+msgid "E17: \"%s\" is a directory"
+msgstr "E17: \"%s\" là mộ thư mục"
 
-#~ msgid "E238: Print error: %s"
-#~ msgstr "E238: Lỗi in: %s"
+msgid "E18: Unexpected characters before '='"
+msgstr "E18: Ở trước '=' có các ký tự không mong đợi"
 
-#~ msgid "Printing '%s'"
-#~ msgstr "Đang in '%s'"
+#, c-format
+msgid "E364: Library call failed for \"%s()\""
+msgstr "E364: Gọi hàm số \"%s()\" của thư viện không thành công"
 
-#~ msgid "E244: Illegal charset name \"%s\" in font name \"%s\""
-#~ msgstr "E244: Tên bảng mã không cho phép \"%s\" trong tên phông chữ \"%s\""
+#, c-format
+msgid "E448: Could not load library function %s"
+msgstr "E448: Nạp hàm số %s của thư viện không thành công"
 
-#~ msgid "E245: Illegal char '%c' in font name \"%s\""
-#~ msgstr "E245: Ký tự không cho phép '%c' trong tên phông chữ \"%s\""
+msgid "E19: Mark has invalid line number"
+msgstr "E19: Dấu hiệu chỉ đến một số thứ tự dòng không đúng"
 
-#~ msgid "Vim: Double signal, exiting\n"
-#~ msgstr "Vim: Tín hiệu đôi, thoát\n"
+msgid "E20: Mark not set"
+msgstr "E20: Dấu hiệu không được xác định"
 
-#~ msgid "Vim: Caught deadly signal %s\n"
-#~ msgstr "Vim: Nhận được tín hiệu chết %s\n"
+msgid "E21: Cannot make changes, 'modifiable' is off"
+msgstr "E21: Không thể thay đổi, vì tùy chọn 'modifiable' bị tắt"
 
-#~ msgid "Vim: Caught deadly signal\n"
-#~ msgstr "Vim: Nhận được tín hiệu chết\n"
+msgid "E22: Scripts nested too deep"
+msgstr "E22: Các script lồng vào nhau quá sâu"
 
-#~ msgid "Opening the X display took % msec"
-#~ msgstr "Mở màn hình X mất % mili giây"
+msgid "E23: No alternate file"
+msgstr "E23: Không có tập tin xen kẽ"
 
-#~ msgid ""
-#~ "\n"
-#~ "Vim: Got X error\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Vim: Lỗi X\n"
+msgid "E24: No such abbreviation"
+msgstr "E24: Không có chữ viết tắt như vậy"
 
-#~ msgid "Testing the X display failed"
-#~ msgstr "Kiểm tra màn hình X không thành công"
+msgid "E477: No ! allowed"
+msgstr "E477: Không cho phép !"
 
-#~ msgid "Opening the X display timed out"
-#~ msgstr "Không mở được màn hình X trong thời gian cho phép (time out)"
+msgid "E25: GUI cannot be used: Not enabled at compile time"
+msgstr "E25: Không sử dụng được giao diện đồ họa vì không chọn khi biên dịch"
 
-#~ msgid ""
-#~ "\n"
-#~ "Cannot execute shell sh\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Không chạy được shell sh\n"
+msgid "E26: Hebrew cannot be used: Not enabled at compile time\n"
+msgstr "E26: Tiếng Do thái không được chọn khi biên dịch\n"
 
-#~ msgid ""
-#~ "\n"
-#~ "Cannot create pipes\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Không tạo được đường ống (pipe)\n"
+msgid "E27: Farsi cannot be used: Not enabled at compile time\n"
+msgstr "E27: Tiếng Farsi không được chọn khi biên dịch\n"
 
-#~ msgid ""
-#~ "\n"
-#~ "Cannot fork\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Không thực hiện được fork()\n"
+msgid "E800: Arabic cannot be used: Not enabled at compile time\n"
+msgstr "E800: Tiếng Ả Rập không được chọn khi biên dịch\n"
 
-#~ msgid ""
-#~ "\n"
-#~ "Command terminated\n"
-#~ msgstr ""
-#~ "\n"
-#~ "Câu lệnh bị gián đoạn\n"
+#, c-format
+msgid "E28: No such highlight group name: %s"
+msgstr "E28: Nhóm chiếu sáng cú pháp %s không tồn tại"
 
-#~ msgid "XSMP lost ICE connection"
-#~ msgstr "XSMP mất kết nối ICE"
+msgid "E29: No inserted text yet"
+msgstr "E29: Tạm thời chưa có văn bản được chèn"
 
-#~ msgid "Opening the X display failed"
-#~ msgstr "Mở màn hình X không thành công"
+msgid "E30: No previous command line"
+msgstr "E30: Không có dòng lệnh trước"
 
-#~ msgid "XSMP handling save-yourself request"
-#~ msgstr "XSMP xử lý yêu cầu tự động ghi nhớ"
+msgid "E31: No such mapping"
+msgstr "E31: Không có ánh xạ (mapping) như vậy"
 
-#~ msgid "XSMP opening connection"
-#~ msgstr "XSMP mở kết nối"
+msgid "E479: No match"
+msgstr "E479: Không có tương ứng"
 
-#~ msgid "XSMP ICE connection watch failed"
-#~ msgstr "XSMP mất theo dõi kết nối ICE"
+#, c-format
+msgid "E480: No match: %s"
+msgstr "E480: Không có tương ứng: %s"
 
-#~ msgid "XSMP SmcOpenConnection failed: %s"
-#~ msgstr "XSMP thực hiện SmcOpenConnection không thành công: %s"
+msgid "E32: No file name"
+msgstr "E32: Không có tên tập tin"
 
-#~ msgid "At line"
-#~ msgstr "Tại dòng"
+msgid "E33: No previous substitute regular expression"
+msgstr "E33: Không có biểu thức chính quy trước để thay thế"
 
-#~ msgid "Could not allocate memory for command line."
-#~ msgstr "Không phân chia được bộ nhớ cho dòng lệnh."
+msgid "E34: No previous command"
+msgstr "E34: Không có câu lệnh trước"
 
-#~ msgid "VIM Error"
-#~ msgstr "Lỗi VIM"
+msgid "E35: No previous regular expression"
+msgstr "E35: Không có biểu thức chính quy trước"
 
-#~ msgid "Could not load vim32.dll!"
-#~ msgstr "Không nạp được vim32.dll!"
+msgid "E481: No range allowed"
+msgstr "E481: Không cho phép sử dụng phạm vi"
 
-#~ msgid "Could not fix up function pointers to the DLL!"
-#~ msgstr "Không sửa được cái chỉ (pointer) hàm số tới DLL!"
+msgid "E36: Not enough room"
+msgstr "E36: Không đủ chỗ trống"
 
-#~ msgid "shell returned %d"
-#~ msgstr "thoát shell với mã %d"
+# TODO: Capitalise first word of message?
+msgid "E247: No registered server named \"%s\""
+msgstr "E247: máy chủ \"%s\" chưa đăng ký"
 
-#~ msgid "Vim: Caught %s event\n"
-#~ msgstr "Vim: Nhận được sự kiện %s\n"
+#, c-format
+msgid "E482: Can't create file %s"
+msgstr "E482: Không tạo được tập tin %s"
 
-#~ msgid "close"
-#~ msgstr "đóng"
+msgid "E483: Can't get temp file name"
+msgstr "E483: Không nhận được tên tập tin tạm thời (temp)"
 
-#~ msgid "logoff"
-#~ msgstr "thoát"
-
-#~ msgid "shutdown"
-#~ msgstr "tắt máy"
-
-#~ msgid "E371: Command not found"
-#~ msgstr "E371: Câu lệnh không tìm thấy"
-
-#~ msgid ""
-#~ "VIMRUN.EXE not found in your $PATH.\n"
-#~ "External commands will not pause after completion.\n"
-#~ "See  :help win32-vimrun  for more information."
-#~ msgstr ""
-#~ "Không tìm thấy VIMRUN.EXE trong $PATH.\n"
-#~ "Lệnh ngoại trú sẽ không dừng lại sau khi hoàn thành.\n"
-#~ "Thông tin chi tiết xem trong :help win32-vimrun"
-
-#~ msgid "Vim Warning"
-#~ msgstr "Cảnh báo Vim"
-
-#~ msgid "E56: %s* operand could be empty"
-#~ msgstr "E56: operand %s* không thể rỗng"
-
-#~ msgid "E57: %s+ operand could be empty"
-#~ msgstr "E57: operand %s+ không thể rỗng"
-
-#~ msgid "E58: %s{ operand could be empty"
-#~ msgstr "E58: operand %s{ không thể rỗng"
-
-#~ msgid "E361: Crash intercepted; regexp too complex?"
-#~ msgstr "E361: Sự cố được ngăn chặn; biểu thức chính quy quá phức tạp?"
-
-#~ msgid "E363: pattern caused out-of-stack error"
-#~ msgstr "E363: sử dụng mẫu (pattern) gây ra lỗi out-of-stack"
-
-#~ msgid "E396: containedin argument not accepted here"
-#~ msgstr "E396: không được sử dụng tham số containedin ở đây"
-
-#~ msgid "Enter nr of choice ( to abort): "
-#~ msgstr "Hãy chọn số cần thiết ( để dừng):"
-
-#~ msgid "E430: Tag file path truncated for %s\n"
-#~ msgstr "E430: Đường dẫn tới tập tin thẻ ghi bị cắt bớt cho %s\n"
-
-#~ msgid "new shell started\n"
-#~ msgstr "đã chạy shell mới\n"
-
-#~ msgid "No undo possible; continue anyway"
-#~ msgstr "Không thể hủy thao tác; tiếp tục thực hiện"
-
-#~ msgid ""
-#~ "\n"
-#~ "MS-Windows 16/32-bit GUI version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản với giao diện đồ họa GUI cho MS-Windows 16/32 bit"
-
-#~ msgid ""
-#~ "\n"
-#~ "MS-Windows 32-bit GUI version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản với giao diện đồ họa GUI cho MS-Windows 32 bit"
-
-#~ msgid " in Win32s mode"
-#~ msgstr " trong chế độ Win32"
-
-#~ msgid " with OLE support"
-#~ msgstr " với hỗ trợ OLE"
-
-#~ msgid ""
-#~ "\n"
-#~ "MS-Windows 32-bit console version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản console cho MS-Windows 32 bit"
-
-#~ msgid ""
-#~ "\n"
-#~ "MS-Windows 16-bit version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho MS-Windows 16 bit"
-
-#~ msgid ""
-#~ "\n"
-#~ "32-bit MS-DOS version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho MS-DOS 32 bit"
-
-#~ msgid ""
-#~ "\n"
-#~ "16-bit MS-DOS version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho MS-DOS 16 bit"
-
-#~ msgid ""
-#~ "\n"
-#~ "MacOS X (unix) version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho MacOS X (unix)"
-
-#~ msgid ""
-#~ "\n"
-#~ "MacOS X version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho MacOS X"
-
-#~ msgid ""
-#~ "\n"
-#~ "MacOS version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho MacOS"
-
-#~ msgid ""
-#~ "\n"
-#~ "RISC OS version"
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản cho RISC OS"
-
-#~ msgid ""
-#~ "\n"
-#~ "Big version "
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản lớn "
-
-#~ msgid ""
-#~ "\n"
-#~ "Normal version "
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản thông thường "
-
-#~ msgid ""
-#~ "\n"
-#~ "Small version "
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản nhỏ "
-
-#~ msgid ""
-#~ "\n"
-#~ "Tiny version "
-#~ msgstr ""
-#~ "\n"
-#~ "Phiên bản \"tí hon\" "
-
-#~ msgid "with GTK2-GNOME GUI."
-#~ msgstr "với giao diện đồ họa GUI GTK2-GNOME."
-
-#~ msgid "with GTK-GNOME GUI."
-#~ msgstr "với giao diện đồ họa GUI GTK-GNOME."
-
-#~ msgid "with GTK2 GUI."
-#~ msgstr "với giao diện đồ họa GUI GTK2."
-
-#~ msgid "with GTK GUI."
-#~ msgstr "với giao diện đồ họa GUI GTK."
-
-#~ msgid "with X11-Motif GUI."
-#~ msgstr "với giao diện đồ họa GUI X11-Motif."
-
-#~ msgid "with X11-neXtaw GUI."
-#~ msgstr "với giao diện đồ họa GUI X11-neXtaw."
-
-#~ msgid "with X11-Athena GUI."
-#~ msgstr "với giao diện đồ họa GUI X11-Athena."
+#, c-format
+msgid "E484: Can't open file %s"
+msgstr "E484: Không mở được tập tin %s"
 
-#~ msgid "with BeOS GUI."
-#~ msgstr "với giao diện đồ họa GUI BeOS."
+#, c-format
+msgid "E485: Can't read file %s"
+msgstr "E485: Không đọc được tập tin %s"
 
-#~ msgid "with Photon GUI."
-#~ msgstr "với giao diện đồ họa GUI Photon."
+msgid "E37: No write since last change (add ! to override)"
+msgstr "E37: Thay đổi chưa được ghi nhớ (thêm ! để bỏ qua ghi nhớ)"
 
-#~ msgid "with GUI."
-#~ msgstr "với giao diện đồ họa GUI."
+msgid "E38: Null argument"
+msgstr "E38: Tham sô bằng 0"
 
-#~ msgid "with Carbon GUI."
-#~ msgstr "với giao diện đồ họa GUI Carbon."
+msgid "E39: Number expected"
+msgstr "E39: Yêu cầu một số"
 
-#~ msgid "with Cocoa GUI."
-#~ msgstr "với giao diện đồ họa GUI Cocoa."
+#, c-format
+msgid "E40: Can't open errorfile %s"
+msgstr "E40: Không mở được tập tin lỗi %s"
 
-#~ msgid "with (classic) GUI."
-#~ msgstr "với giao diện đồ họa (cổ điển) GUI."
+# TODO: Capitalise first word of message?
+msgid "E233: Cannot open display"
+msgstr "E233: không mở được màn hình"
 
-#~ msgid "  system gvimrc file: \""
-#~ msgstr "            tập tin gvimrc chung cho hệ thống: \""
+msgid "E41: Out of memory!"
+msgstr "E41: Không đủ bộ nhớ!"
 
-#~ msgid "    user gvimrc file: \""
-#~ msgstr "         tập tin gvimrc của người dùng: \""
+msgid "Pattern not found"
+msgstr "Không tìm thấy mẫu (pattern)"
 
-#~ msgid "2nd user gvimrc file: \""
-#~ msgstr "  tập tin gvimrc thứ hai của người dùng: \""
+#, c-format
+msgid "E486: Pattern not found: %s"
+msgstr "E486: Không tìm thấy mẫu (pattern): %s"
 
-#~ msgid "3rd user gvimrc file: \""
-#~ msgstr "  tập tin gvimrc thứ ba của người dùng: \""
+msgid "E487: Argument must be positive"
+msgstr "E487: Tham số phải là một số dương"
 
-#~ msgid "    system menu file: \""
-#~ msgstr "             tập tin trình đơn chung cho hệ thống: \""
+msgid "E459: Cannot go back to previous directory"
+msgstr "E459: Không quay lại được thư mục trước đó"
 
-#~ msgid "Compiler: "
-#~ msgstr "Trình biên dịch: "
+msgid "E42: No Errors"
+msgstr "E42: Không có lỗi"
 
-#~ msgid "menu  Help->Orphans           for information    "
-#~ msgstr "trình đơn Trợ giúp->Mồ côi             để có thêm thông tin     "
+msgid "E43: Damaged match string"
+msgstr "E43: Chuỗi tương ứng bị hỏng"
 
-#~ msgid "Running modeless, typed text is inserted"
-#~ msgstr "Không chế độ, văn bản nhập vào sẽ được chèn"
+msgid "E44: Corrupted regexp program"
+msgstr "E44: Chương trình xử lý biểu thức chính quy bị hỏng"
 
-#~ msgid "menu  Edit->Global Settings->Toggle Insert Mode  "
-#~ msgstr ""
-#~ "trình đơn Soạn thảo->Thiết lập chung->Chế độ chèn                     "
+msgid "E45: 'readonly' option is set (add ! to override)"
+msgstr "E45: Tùy chọn 'readonly' được bật (Hãy thêm ! để lờ đi)"
 
-#~ msgid "                              for two modes      "
-#~ msgstr "                                 cho hai chế độ               "
+#, c-format
+msgid "E46: Cannot set read-only variable \"%s\""
+msgstr "E46: Không thay đổi được biến chỉ đọc \"%s\""
 
-#~ msgid "menu  Edit->Global Settings->Toggle Vi Compatible"
-#~ msgstr ""
-#~ "trình đơn Soạn thảo->Thiết lập chung->Tương thích với Vi                "
+msgid "E47: Error while reading errorfile"
+msgstr "E47: Lỗi khi đọc tập tin lỗi"
 
-#~ msgid "                              for Vim defaults   "
-#~ msgstr ""
-#~ "                                 để chuyển vào chế độ Vim mặc định       "
+msgid "E48: Not allowed in sandbox"
+msgstr "E48: Không cho phép trong hộp cát (sandbox)"
 
-#~ msgid "WARNING: Windows 95/98/ME detected"
-#~ msgstr "CẢNH BÁO: nhận ra Windows 95/98/ME"
+msgid "E523: Not allowed here"
+msgstr "E523: Không cho phép ở đây"
 
-#~ msgid "type  :help windows95  for info on this"
-#~ msgstr "hãy gõ :help windows95  để biết thêm thông tin     "
+msgid "E359: Screen mode setting not supported"
+msgstr "E359: Chế độ màn hình không được hỗ trợ"
 
-#~ msgid "E370: Could not load library %s"
-#~ msgstr "E370: Không nạp được thư viện %s"
+msgid "E49: Invalid scroll size"
+msgstr "E49: Kích thước thanh cuộn không cho phép"
 
-#~ msgid ""
-#~ "Sorry, this command is disabled: the Perl library could not be loaded."
-#~ msgstr "Xin lỗi, câu lệnh này bị tắt: không nạp được thư viện Perl."
+msgid "E91: 'shell' option is empty"
+msgstr "E91: Tùy chọn 'shell' là một chuỗi rỗng"
 
-#~ msgid "E299: Perl evaluation forbidden in sandbox without the Safe module"
-#~ msgstr ""
-#~ "E299: Không cho phép sự tính toán Perl trong hộp cát mà không có môđun An "
-#~ "toàn"
+msgid "E255: Couldn't read in sign data!"
+msgstr "E255: Không đọc được dữ liệu về ký tự!"
 
-#~ msgid "Edit with &multiple Vims"
-#~ msgstr "Soạn thảo trong nhiều Vi&m"
+msgid "E72: Close error on swap file"
+msgstr "E72: Lỗi đóng tập tin trao đổi (swap)"
 
-#~ msgid "Edit with single &Vim"
-#~ msgstr "Soạn thảo trong một &Vim"
+# TODO: Capitalise first word of message?
+msgid "E73: Tag stack empty"
+msgstr "E73: đống thẻ ghi rỗng"
 
-#~ msgid "&Diff with Vim"
-#~ msgstr "&So sánh (diff) qua Vim"
+msgid "E74: Command too complex"
+msgstr "E74: Câu lệnh quá phức tạp"
 
-#~ msgid "Edit with &Vim"
-#~ msgstr "Soạn thảo trong &Vim"
+msgid "E75: Name too long"
+msgstr "E75: Tên quá dài"
 
-#~ msgid "Edit with existing Vim - &"
-#~ msgstr "Soạn thảo trong Vim đã chạy - &"
+msgid "E76: Too many ["
+msgstr "E76: Quá nhiều ký tự ["
 
-#~ msgid "Edits the selected file(s) with Vim"
-#~ msgstr "Soạn thảo (các) tập tin đã chọn trong Vim"
+msgid "E77: Too many file names"
+msgstr "E77: Quá nhiều tên tập tin"
 
-#~ msgid "Error creating process: Check if gvim is in your path!"
-#~ msgstr "Lỗi tạo tiến trình: Hãy kiểm tra xem gvim có trong đường dẫn không!"
+msgid "E488: Trailing characters"
+msgstr "E488: Ký tự thừa ở đuôi"
 
-#~ msgid "gvimext.dll error"
-#~ msgstr "lỗi gvimext.dll"
+msgid "E78: Unknown mark"
+msgstr "E78: Dấu hiệu không biết"
 
-#~ msgid "Path length too long!"
-#~ msgstr "Đường dẫn quá dài!"
+msgid "E79: Cannot expand wildcards"
+msgstr "E79: Không thực hiện được phép thế theo wildcard"
 
-#~ msgid "E234: Unknown fontset: %s"
-#~ msgstr "E234: Không rõ bộ phông chữ: %s"
+msgid "E591: 'winheight' cannot be smaller than 'winminheight'"
+msgstr "E591: giá trị của 'winheight' không thể nhỏ hơn 'winminheight'"
 
-#~ msgid "E235: Unknown font: %s"
-#~ msgstr "E235: Không rõ phông chữ: %s"
+msgid "E592: 'winwidth' cannot be smaller than 'winminwidth'"
+msgstr "E592: giá trị của 'winwidth' không thể nhỏ hơn 'winminwidth'"
 
-#~ msgid "E448: Could not load library function %s"
-#~ msgstr "E448: Nạp hàm số %s của thư viện không thành công"
+msgid "E80: Error while writing"
+msgstr "E80: Lỗi khi ghi nhớ"
 
-#~ msgid "E26: Hebrew cannot be used: Not enabled at compile time\n"
-#~ msgstr "E26: Tiếng Do thái không được chọn khi biên dịch\n"
+msgid "Zero count"
+msgstr "Giá trị của bộ đếm bằng 0"
 
-#~ msgid "E27: Farsi cannot be used: Not enabled at compile time\n"
-#~ msgstr "E27: Tiếng Farsi không được chọn khi biên dịch\n"
+msgid "E81: Using  not in a script context"
+msgstr "E81: Sử dụng  ngoài phạm vi script"
 
-#~ msgid "E800: Arabic cannot be used: Not enabled at compile time\n"
-#~ msgstr "E800: Tiếng Ả Rập không được chọn khi biên dịch\n"
+msgid "E449: Invalid expression received"
+msgstr "E449: Nhận được một biểu thức không cho phép"
 
-#~ msgid "E247: no registered server named \"%s\""
-#~ msgstr "E247: máy chủ \"%s\" chưa đăng ký"
+msgid "E463: Region is guarded, cannot modify"
+msgstr "E463: Không thể thay đổi vùng đã được bảo vệ"
 
-#~ msgid "E233: cannot open display"
-#~ msgstr "E233: không mở được màn hình"
+msgid "No tutorial with that name found"
+msgstr "Không tìm thấy hướng dẫn (tutorial) có tên đó"
 
-#~ msgid "E463: Region is guarded, cannot modify"
-#~ msgstr "E463: Không thể thay đổi vùng đã được bảo vệ"
+msgid "Only one argument accepted (check spaces)"
+msgstr "Chỉ chấp nhận một tham số (vui lòng kiểm tra dấu cách)"
-- 
cgit 


From fe1e2eff062605f7e617885011160a678822fd0c Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 8 Dec 2024 09:25:43 +0800
Subject: fix(lua): avoid vim._with() double-free with cmdmod (#31505)

---
 src/nvim/lua/stdlib.c             |  1 +
 test/functional/lua/with_spec.lua | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index e719d99640..5ebff3a809 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -619,6 +619,7 @@ static int nlua_with(lua_State *L)
   int rets = 0;
 
   cmdmod_T save_cmdmod = cmdmod;
+  CLEAR_FIELD(cmdmod);
   cmdmod.cmod_flags = flags;
   apply_cmdmod(&cmdmod);
 
diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua
index 6127e83619..92e798e7f3 100644
--- a/test/functional/lua/with_spec.lua
+++ b/test/functional/lua/with_spec.lua
@@ -1621,4 +1621,21 @@ describe('vim._with', function()
     matches('Invalid buffer', get_error('{ buf = -1 }, function() end'))
     matches('Invalid window', get_error('{ win = -1 }, function() end'))
   end)
+
+  it('no double-free when called from :filter browse oldfiles #31501', function()
+    exec_lua([=[
+      vim.api.nvim_create_autocmd('BufEnter', {
+        callback = function()
+          vim._with({ lockmarks = true }, function() end)
+        end,
+      })
+      vim.cmd([[
+        let v:oldfiles = ['Xoldfile']
+        call nvim_input('1')
+        noswapfile filter /Xoldfile/ browse oldfiles
+      ]])
+    ]=])
+    n.assert_alive()
+    eq('Xoldfile', fn.bufname('%'))
+  end)
 end)
-- 
cgit 


From ca4f688ad4d6160e0ec0cb858a6559eb8a39b594 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 8 Dec 2024 17:53:01 +0800
Subject: vim-patch:9.1.0913: no error check for neg values for 'messagesopt'
 (#31511)

Problem:  no error check for neg values for 'messagesopt'
          (after v9.1.0908)
Solution: add additional error checks and tests (h-east)

closes: vim/vim#16187

https://github.com/vim/vim/commit/65be834c30fb43abb2e41585b41eefcd2ae06c01

Nvim's getdigits() checks for overflow, so the code change isn't needed.

Co-authored-by: h-east 
---
 src/nvim/message.c                | 2 ++
 test/old/testdir/gen_opt_test.vim | 4 +++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/nvim/message.c b/src/nvim/message.c
index d8e6fd3001..edb332a786 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1098,11 +1098,13 @@ int messagesopt_changed(void)
     return FAIL;
   }
 
+  assert(messages_history_new >= 0);
   // "history" must be <= 10000
   if (messages_history_new > 10000) {
     return FAIL;
   }
 
+  assert(messages_wait_new >= 0);
   // "wait" must be <= 10000
   if (messages_wait_new > 10000) {
     return FAIL;
diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim
index 22ffde5269..a2ad1eb8b4 100644
--- a/test/old/testdir/gen_opt_test.vim
+++ b/test/old/testdir/gen_opt_test.vim
@@ -268,7 +268,9 @@ let test_values = {
       \		'hit-enter,history:1,wait:1'],
       \		['xxx', 'history:500', 'hit-enter,history:-1',
       \		'hit-enter,history:10001', 'history:0,wait:10001',
-      \		'hit-enter']],
+      \		'hit-enter', 'history:10,wait:99999999999999999999',
+      \		'history:99999999999999999999,wait:10', 'wait:10',
+      \		'history:-10', 'history:10,wait:-10']],
       \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']],
       \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'],
       \		['xxx', 'n,v,i']],
-- 
cgit 


From 4bfdd1ee9dee15c4b066412335f8cafbc3208f60 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Sat, 7 Dec 2024 10:39:56 +0000
Subject: refactor(lsp): better tracking of requests

Not essential, but adds robustness and hardening for future
changes.
---
 runtime/doc/lsp.txt            |  19 ++++----
 runtime/lua/vim/lsp/client.lua | 101 +++++++++++++++++++++++++----------------
 2 files changed, 72 insertions(+), 48 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 1607f3492c..d9e536b79b 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -946,7 +946,7 @@ Lua module: vim.lsp.client                                        *lsp-client*
       • {handlers}              (`table`) The handlers
                                 used by the client as described in
                                 |lsp-handler|.
-      • {requests}              (`table`)
+      • {requests}              (`table`)
                                 The current pending requests in flight to the
                                 server. Entries are key-value pairs with the
                                 key being the request id while the value is a
@@ -982,9 +982,9 @@ Lua module: vim.lsp.client                                        *lsp-client*
                                 lenses, ...) triggers the command. Client
                                 commands take precedence over the global
                                 command registry.
-      • {settings}              (`table`) Map with language server specific
-                                settings. These are returned to the language
-                                server if requested via
+      • {settings}              (`lsp.LSPObject`) Map with language server
+                                specific settings. These are returned to the
+                                language server if requested via
                                 `workspace/configuration`. Keys are
                                 case-sensitive.
       • {flags}                 (`table`) A table with flags for the client.
@@ -1077,8 +1077,8 @@ Lua module: vim.lsp.client                                        *lsp-client*
                                 an array.
       • {handlers}?           (`table`) Map of language
                               server method names to |lsp-handler|
-      • {settings}?           (`table`) Map with language server specific
-                              settings. See the {settings} in
+      • {settings}?           (`lsp.LSPObject`) Map with language server
+                              specific settings. See the {settings} in
                               |vim.lsp.Client|.
       • {commands}?           (`table`)
                               Table that maps string of clientside commands to
@@ -1088,9 +1088,10 @@ Lua module: vim.lsp.client                                        *lsp-client*
                               command name, and the value is a function which
                               is called if any LSP action (code action, code
                               lenses, ...) triggers the command.
-      • {init_options}?       (`table`) Values to pass in the initialization
-                              request as `initializationOptions`. See
-                              `initialize` in the LSP spec.
+      • {init_options}?       (`lsp.LSPObject`) Values to pass in the
+                              initialization request as
+                              `initializationOptions`. See `initialize` in the
+                              LSP spec.
       • {name}?               (`string`, default: client-id) Name in log
                               messages.
       • {get_language_id}?    (`fun(bufnr: integer, filetype: string): string`)
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index e03fb2415d..5eefe4600e 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -75,7 +75,7 @@ local validate = vim.validate
 ---
 --- Map with language server specific settings.
 --- See the {settings} in |vim.lsp.Client|.
---- @field settings? table
+--- @field settings? lsp.LSPObject
 ---
 --- Table that maps string of clientside commands to user-defined functions.
 --- Commands passed to `start()` take precedence over the global command registry. Each key
@@ -85,7 +85,7 @@ local validate = vim.validate
 ---
 --- Values to pass in the initialization request as `initializationOptions`. See `initialize` in
 --- the LSP spec.
---- @field init_options? table
+--- @field init_options? lsp.LSPObject
 ---
 --- Name in log messages.
 --- (default: client-id)
@@ -164,7 +164,7 @@ local validate = vim.validate
 --- for an active request, or "cancel" for a cancel request. It will be
 --- "complete" ephemerally while executing |LspRequest| autocmds when replies
 --- are received from the server.
---- @field requests table
+--- @field requests table
 ---
 --- copy of the table that was passed by the user
 --- to |vim.lsp.start()|.
@@ -189,9 +189,6 @@ local validate = vim.validate
 ---
 --- @field attached_buffers table
 ---
---- Buffers that should be attached to upon initialize()
---- @field package _buffers_to_attach table
----
 --- @field private _log_prefix string
 ---
 --- Track this so that we can escalate automatically if we've already tried a
@@ -210,7 +207,7 @@ local validate = vim.validate
 --- Map with language server specific settings. These are returned to the
 --- language server if requested via `workspace/configuration`. Keys are
 --- case-sensitive.
---- @field settings table
+--- @field settings lsp.LSPObject
 ---
 --- A table with flags for the client. The current (experimental) flags are:
 --- @field flags vim.lsp.Client.Flags
@@ -265,9 +262,6 @@ local valid_encodings = {
   ['utf8'] = 'utf-8',
   ['utf16'] = 'utf-16',
   ['utf32'] = 'utf-32',
-  UTF8 = 'utf-8',
-  UTF16 = 'utf-16',
-  UTF32 = 'utf-32',
 }
 
 --- Normalizes {encoding} to valid LSP encoding names.
@@ -276,7 +270,7 @@ local valid_encodings = {
 local function validate_encoding(encoding)
   validate('encoding', encoding, 'string', true)
   if not encoding then
-    return valid_encodings.UTF16
+    return valid_encodings.utf16
   end
   return valid_encodings[encoding:lower()]
     or error(
@@ -604,6 +598,57 @@ function Client:_resolve_handler(method)
   return self.handlers[method] or lsp.handlers[method]
 end
 
+--- @private
+--- @param id integer
+--- @param req_type 'pending'|'complete'|'cancel'|
+--- @param bufnr? integer (only required for req_type='pending')
+--- @param method? string (only required for req_type='pending')
+function Client:_process_request(id, req_type, bufnr, method)
+  local pending = req_type == 'pending'
+
+  validate('id', id, 'number')
+  if pending then
+    validate('bufnr', bufnr, 'number')
+    validate('method', method, 'string')
+  end
+
+  local cur_request = self.requests[id]
+
+  if pending and cur_request then
+    log.error(
+      self._log_prefix,
+      ('Cannot create request with id %d as one already exists'):format(id)
+    )
+    return
+  elseif not pending and not cur_request then
+    log.error(
+      self._log_prefix,
+      ('Cannot find request with id %d whilst attempting to %s'):format(id, req_type)
+    )
+    return
+  end
+
+  if cur_request then
+    bufnr = cur_request.bufnr
+    method = cur_request.method
+  end
+
+  assert(bufnr and method)
+
+  local request = { type = req_type, bufnr = bufnr, method = method }
+
+  -- Clear 'complete' requests
+  -- Note 'pending' and 'cancelled' requests are cleared when the server sends a response
+  -- which is processed via the notify_reply_callback argument to rpc.request.
+  self.requests[id] = req_type ~= 'complete' and request or nil
+
+  api.nvim_exec_autocmds('LspRequest', {
+    buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
+    modeline = false,
+    data = { client_id = self.id, request_id = id, request = request },
+  })
+end
+
 --- Sends a request to the server.
 ---
 --- This is a thin wrapper around {client.rpc.request} with some additional
@@ -632,33 +677,20 @@ function Client:request(method, params, handler, bufnr)
   local version = lsp.util.buf_versions[bufnr]
   log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr)
   local success, request_id = self.rpc.request(method, params, function(err, result)
-    local context = {
+    handler(err, result, {
       method = method,
       client_id = self.id,
       bufnr = bufnr,
       params = params,
       version = version,
-    }
-    handler(err, result, context)
-  end, function(request_id)
-    local request = self.requests[request_id]
-    request.type = 'complete'
-    api.nvim_exec_autocmds('LspRequest', {
-      buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
-      modeline = false,
-      data = { client_id = self.id, request_id = request_id, request = request },
     })
-    self.requests[request_id] = nil
+  end, function(request_id)
+    -- Called when the server sends a response to the request (including cancelled acknowledgment).
+    self:_process_request(request_id, 'complete')
   end)
 
   if success and request_id then
-    local request = { type = 'pending', bufnr = bufnr, method = method }
-    self.requests[request_id] = request
-    api.nvim_exec_autocmds('LspRequest', {
-      buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
-      modeline = false,
-      data = { client_id = self.id, request_id = request_id, request = request },
-    })
+    self:_process_request(request_id, 'pending', bufnr, method)
   end
 
   return success, request_id
@@ -756,16 +788,7 @@ end
 --- @return boolean status indicating if the notification was successful.
 --- @see |Client:notify()|
 function Client:cancel_request(id)
-  validate('id', id, 'number')
-  local request = self.requests[id]
-  if request and request.type == 'pending' then
-    request.type = 'cancel'
-    api.nvim_exec_autocmds('LspRequest', {
-      buffer = api.nvim_buf_is_valid(request.bufnr) and request.bufnr or nil,
-      modeline = false,
-      data = { client_id = self.id, request_id = id, request = request },
-    })
-  end
+  self:_process_request(id, 'cancel')
   return self.rpc.notify(ms.dollar_cancelRequest, { id = id })
 end
 
-- 
cgit 


From 84d9f4f9f9efbfc72630f01f5f837c50fd7020d4 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 8 Dec 2024 20:29:25 +0800
Subject: vim-patch:9.1.0915: GVim: default font size a bit too small (#31516)

Problem:  GVim: default font size a bit too small
Solution: increase guifont size to 12 pt on GTK builds
          of gVim (matveyt).

fixes: vim/vim#16172
closes: vim/vim#16178

https://github.com/vim/vim/commit/ad3b6a3340a4ab02c1e5bc4a6d6a5fb858b671d3

Co-authored-by: matveyt 
---
 test/old/testdir/runtest.vim        | 4 ++++
 test/old/testdir/test_highlight.vim | 4 ++--
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/test/old/testdir/runtest.vim b/test/old/testdir/runtest.vim
index 058635c332..3d210695c4 100644
--- a/test/old/testdir/runtest.vim
+++ b/test/old/testdir/runtest.vim
@@ -55,6 +55,10 @@ silent! endwhile
 
 " In the GUI we can always change the screen size.
 if has('gui_running')
+  if has('gui_gtk')
+    " to keep screendump size unchanged
+    set guifont=Monospace\ 10
+  endif
   set columns=80 lines=25
 endif
 
diff --git a/test/old/testdir/test_highlight.vim b/test/old/testdir/test_highlight.vim
index 0a64c63d30..56c7a9656f 100644
--- a/test/old/testdir/test_highlight.vim
+++ b/test/old/testdir/test_highlight.vim
@@ -785,8 +785,8 @@ func Test_1_highlight_Normalgroup_exists()
   if !has('gui_running')
     call assert_match('hi Normal\s*clear', hlNormal)
   elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
-    " expect is DEFAULT_FONT of gui_gtk_x11.c
-    call assert_match('hi Normal\s*font=Monospace 10', hlNormal)
+    " expect is DEFAULT_FONT of gui_gtk_x11.c (any size)
+    call assert_match('hi Normal\s*font=Monospace\>', hlNormal)
   elseif has('gui_motif')
     " expect is DEFAULT_FONT of gui_x11.c
     call assert_match('hi Normal\s*font=7x13', hlNormal)
-- 
cgit 


From d7b3add63e92cfa595dde7af115747dee63980cc Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 9 Dec 2024 08:06:58 +0800
Subject: vim-patch:eda923e: runtime(netrw): do not detach when launching
 external programs in gvim

On Debian 12 when detaching the program wouldn't launch at all

closes: vim/vim#16168

https://github.com/vim/vim/commit/eda923e9c9e639bc4f02b8b3ead1e7d27981e552

Co-authored-by: Konfekt 
---
 runtime/autoload/netrw.vim | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index 6a26a7d0b1..073d255e19 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -38,6 +38,7 @@
 "   2024 Nov 14 by Vim Project: small fixes to netrw#BrowseX (#16056)
 "   2024 Nov 23 by Vim Project: update decompress defaults (#16104)
 "   2024 Nov 23 by Vim Project: fix powershell escaping issues (#16094)
+"   2024 Dec 04 by Vim Project: do not detach for gvim (#16168)
 "   }}}
 " Former Maintainer:	Charles E Campbell
 " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
@@ -5040,7 +5041,7 @@ if has('unix')
   endfun
  else
   fun! netrw#Launch(args)
-    exe ':silent ! nohup' a:args s:redir() '&' | redraw!
+    exe ':silent ! nohup' a:args s:redir() (has('gui_running') ? '' : '&') | redraw!
   endfun
  endif
 elseif has('win32')
-- 
cgit 


From 3d318be8cd9b85f497411d08e9ff26af53117773 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 9 Dec 2024 08:08:37 +0800
Subject: vim-patch:92b3666: runtime(netrw): only check first arg of
 netrw_browsex_viewer for being executable

fixes: vim/vim#16185

https://github.com/vim/vim/commit/92b36663f8d0e507f60f357c6add6f6c9148a951

Co-authored-by: Christian Brabandt 
---
 runtime/autoload/netrw.vim | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index 073d255e19..cbe0b29620 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -39,6 +39,7 @@
 "   2024 Nov 23 by Vim Project: update decompress defaults (#16104)
 "   2024 Nov 23 by Vim Project: fix powershell escaping issues (#16094)
 "   2024 Dec 04 by Vim Project: do not detach for gvim (#16168)
+"   2024 Dec 08 by Vim Project: check the first arg of netrw_browsex_viewer for being executable (#16185)
 "   }}}
 " Former Maintainer:	Charles E Campbell
 " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
@@ -5071,7 +5072,9 @@ elseif executable('open')
 endif
 
 fun! s:viewer()
-  if exists('g:netrw_browsex_viewer') && executable(g:netrw_browsex_viewer)
+  " g:netrw_browsex_viewer could be a string of program + its arguments,
+  " test if first argument is executable
+  if exists('g:netrw_browsex_viewer') && executable(split(g:netrw_browsex_viewer)[0])
     " extract any viewing options.  Assumes that they're set apart by spaces.
     "   call Decho("extract any viewing options from g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand(""))
     if g:netrw_browsex_viewer =~ '\s'
-- 
cgit 


From 3bb2d027597107a3d7f84ef61507104fd4dc050a Mon Sep 17 00:00:00 2001
From: Maria José Solano 
Date: Sun, 8 Dec 2024 19:18:40 -0800
Subject: docs: fix type of vim.validate value

---
 runtime/doc/lua.txt        | 2 +-
 runtime/lua/vim/shared.lua | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 80808abd51..dad3d92238 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2477,7 +2477,7 @@ vim.validate({name}, {value}, {validator}, {optional}, {message})
 
     Parameters: ~
       • {name}       (`string`) Argument name
-      • {value}      (`string`) Argument value
+      • {value}      (`any`) Argument value
       • {validator}  (`vim.validate.Validator`)
                      • (`string|string[]`): Any value that can be returned
                        from |lua-type()| in addition to `'callable'`:
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 9314d34314..0fe8e99350 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -1013,7 +1013,7 @@ do
   --- best performance.
   ---
   --- @param name string Argument name
-  --- @param value string Argument value
+  --- @param value any Argument value
   --- @param validator vim.validate.Validator
   ---   - (`string|string[]`): Any value that can be returned from |lua-type()| in addition to
   ---     `'callable'`: `'boolean'`, `'callable'`, `'function'`, `'nil'`, `'number'`, `'string'`, `'table'`,
-- 
cgit 


From 1b90f4a9c472e674d707cbc01520423d305f6b17 Mon Sep 17 00:00:00 2001
From: Eisuke Kawashima 
Date: Tue, 10 Dec 2024 09:10:27 +0900
Subject: build: mark CMake variables advanced #31412

The variables are not marked as advanced, thus they appear in e.g. `ccmake`.
---
 CMakeLists.txt          | 5 +++++
 cmake/Deps.cmake        | 2 ++
 src/nvim/CMakeLists.txt | 1 +
 3 files changed, 8 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9723de9176..ae5704cfe4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -187,6 +187,7 @@ if(NOT PREFER_LUA)
   find_program(LUA_PRG NAMES luajit)
 endif()
 find_program(LUA_PRG NAMES lua5.1 lua5.2 lua)
+mark_as_advanced(LUA_PRG)
 if(NOT LUA_PRG)
   message(FATAL_ERROR "Failed to find a Lua 5.1-compatible interpreter")
 endif()
@@ -200,6 +201,7 @@ message(STATUS "Using Lua interpreter: ${LUA_PRG}")
 if(NOT LUA_GEN_PRG)
   set(LUA_GEN_PRG "${LUA_PRG}" CACHE FILEPATH "Path to the lua used for code generation.")
 endif()
+mark_as_advanced(LUA_GEN_PRG)
 message(STATUS "Using Lua interpreter for code generation: ${LUA_GEN_PRG}")
 
 option(COMPILE_LUA "Pre-compile Lua sources into bytecode (for sources that are included in the binary)" ON)
@@ -219,6 +221,7 @@ if(COMPILE_LUA AND NOT WIN32)
     endif()
   endif()
 endif()
+mark_as_advanced(LUAC_PRG)
 if(LUAC_PRG)
   message(STATUS "Using Lua compiler: ${LUAC_PRG}")
 endif()
@@ -229,7 +232,9 @@ if(CI_LINT)
   set(LINT_REQUIRED "REQUIRED")
 endif()
 find_program(SHELLCHECK_PRG shellcheck ${LINT_REQUIRED})
+mark_as_advanced(SHELLCHECK_PRG)
 find_program(STYLUA_PRG stylua ${LINT_REQUIRED})
+mark_as_advanced(STYLUA_PRG)
 
 set(STYLUA_DIRS runtime scripts src test contrib)
 
diff --git a/cmake/Deps.cmake b/cmake/Deps.cmake
index 519826654f..5902ca6970 100644
--- a/cmake/Deps.cmake
+++ b/cmake/Deps.cmake
@@ -19,6 +19,7 @@ if(APPLE)
 endif()
 
 find_program(CACHE_PRG NAMES ccache sccache)
+mark_as_advanced(CACHE_PRG)
 if(CACHE_PRG)
   set(CMAKE_C_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E env CCACHE_SLOPPINESS=pch_defines,time_macros ${CACHE_PRG})
   list(APPEND DEPS_CMAKE_CACHE_ARGS -DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER})
@@ -27,6 +28,7 @@ endif()
 # MAKE_PRG
 if(UNIX)
   find_program(MAKE_PRG NAMES gmake make)
+  mark_as_advanced(MAKE_PRG)
   if(NOT MAKE_PRG)
     message(FATAL_ERROR "GNU Make is required to build the dependencies.")
   else()
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index cffdcf0415..cce8ab5b25 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -826,6 +826,7 @@ target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv)
 #-------------------------------------------------------------------------------
 
 find_program(CLANG_TIDY_PRG clang-tidy)
+mark_as_advanced(CLANG_TIDY_PRG)
 set(EXCLUDE_CLANG_TIDY typval_encode.c.h ui_events.in.h)
 if(WIN32)
   list(APPEND EXCLUDE_CLANG_TIDY
-- 
cgit 


From 4889935a7a0e188d6fb247cbc3b3bb0c5b0ed731 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Tue, 10 Dec 2024 01:27:39 +0100
Subject: docs(vvars): adjust lua types for vim.v variables #31510

- classes for v:event and v:completed_item
- add remaining unknown types
---
 runtime/lua/vim/_meta/vvars.lua       | 20 ++++-----
 runtime/lua/vim/_meta/vvars_extra.lua | 77 +++++++++++++++++++++++++++++++++++
 src/nvim/vvars.lua                    | 10 +++++
 3 files changed, 97 insertions(+), 10 deletions(-)
 create mode 100644 runtime/lua/vim/_meta/vvars_extra.lua

diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua
index 264907109f..445da4e02f 100644
--- a/runtime/lua/vim/_meta/vvars.lua
+++ b/runtime/lua/vim/_meta/vvars.lua
@@ -15,7 +15,7 @@ vim.v.argv = ...
 --- Argument for evaluating 'formatexpr' and used for the typed
 --- character when using  in an abbreviation `:map-`.
 --- It is also used by the `InsertCharPre` and `InsertEnter` events.
---- @type any
+--- @type string
 vim.v.char = ...
 
 --- The name of the character encoding of a file to be converted.
@@ -60,7 +60,7 @@ vim.v.collate = ...
 --- mode.
 --- Note: Plugins can modify the value to emulate the builtin
 --- `CompleteDone` event behavior.
---- @type any
+--- @type vim.v.completed_item
 vim.v.completed_item = ...
 
 --- The count given for the last Normal mode command.  Can be used
@@ -90,7 +90,7 @@ vim.v.count1 = ...
 --- This variable can not be set directly, use the `:language`
 --- command.
 --- See `multi-lang`.
---- @type any
+--- @type string
 vim.v.ctype = ...
 
 --- Normally zero.  When a deadly signal is caught it's set to
@@ -199,7 +199,7 @@ vim.v.errors = ...
 ---   reason           Reason for completion being done. `CompleteDone`
 ---   complete_word    The word that was selected, empty if abandoned complete.
 ---   complete_type    See `complete_info_mode`
---- @type any
+--- @type vim.v.event
 vim.v.event = ...
 
 --- The value of the exception most recently caught and not
@@ -225,7 +225,7 @@ vim.v.exception = ...
 --- ```vim
 ---   :au VimLeave * echo "Exit value is " .. v:exiting
 --- ```
---- @type any
+--- @type integer?
 vim.v.exiting = ...
 
 --- Special value used to put "false" in JSON and msgpack.  See
@@ -421,7 +421,7 @@ vim.v.mouse_winid = ...
 --- and `msgpackdump()`. All types inside dictionary are fixed
 --- (not editable) empty lists. To check whether some list is one
 --- of msgpack types, use `is` operator.
---- @type any
+--- @type table
 vim.v.msgpack_types = ...
 
 --- Special value used to put "null" in JSON and NIL in msgpack.
@@ -565,7 +565,7 @@ vim.v.relnum = ...
 --- typed command.
 --- This can be used to find out why your script causes the
 --- hit-enter prompt.
---- @type any
+--- @type string
 vim.v.scrollstart = ...
 
 --- Search direction:  1 after a forward search, 0 after a
@@ -707,13 +707,13 @@ vim.v.termrequest = ...
 vim.v.termresponse = ...
 
 --- Must be set before using `test_garbagecollect_now()`.
---- @type any
+--- @type integer
 vim.v.testing = ...
 
 --- Full filename of the last loaded or saved session file.
 --- Empty when no session file has been saved.  See `:mksession`.
 --- Modifiable (can be set).
---- @type any
+--- @type string
 vim.v.this_session = ...
 
 --- The point where the exception most recently caught and not
@@ -730,7 +730,7 @@ vim.v.this_session = ...
 --- ```
 ---
 --- Output: "Exception from test.vim, line 2"
---- @type any
+--- @type string
 vim.v.throwpoint = ...
 
 --- Special value used to put "true" in JSON and msgpack.  See
diff --git a/runtime/lua/vim/_meta/vvars_extra.lua b/runtime/lua/vim/_meta/vvars_extra.lua
new file mode 100644
index 0000000000..7ef3021e89
--- /dev/null
+++ b/runtime/lua/vim/_meta/vvars_extra.lua
@@ -0,0 +1,77 @@
+--- @meta _
+error('Cannot require a meta file')
+
+--- Extra types for vim.v dictionary fields
+
+--- @class vim.v.completed_item
+--- @field word? string the text that will be inserted, mandatory
+--- abbreviation of "word"; when not empty it is used in the menu instead of "word"
+--- @field abbr? string
+--- extra text for the popup menu, displayed after "word" or "abbr"
+--- @field menu? string
+--- more information about the item, can be displayed in a preview window
+--- @field info? string
+--- @field kind? string single letter indicating the type of completion
+--- when non-zero case is to be ignored when comparing items to be equal; when
+--- omitted zero is used, thus items that only differ in case are added
+--- @field icase? integer
+--- when non-zero, always treat this item to be equal when comparing. Which
+--- means, "equal=1" disables filtering of this item.
+--- @field equal? integer
+--- when non-zero this match will be added even when an item with the same word
+--- is already present.
+--- @field dup? integer
+--- when non-zero this match will be added even when it is an empty string
+--- @field empty? integer
+--- custom data which is associated with the item and available
+--- in |v:completed_item|; it can be any type; defaults to an empty string
+--- @field user_data? any
+--- an additional highlight group whose attributes are combined
+--- with |hl-PmenuSel| and |hl-Pmenu| or |hl-PmenuMatchSel| and |hl-PmenuMatch|
+--- highlight attributes in the popup menu to apply cterm and gui properties
+--- (with higher priority) like strikethrough to the completion items abbreviation
+--- @field abbr_hlgroup? string
+--- an additional highlight group specifically for setting the highlight
+--- attributes of the completion kind. When this field is present, it will
+--- override the |hl-PmenuKind| highlight group, allowing for the customization
+--- of ctermfg and guifg properties for the completion kind
+--- @field kind_hlgroup? string
+
+--- @class vim.v.event
+--- Whether the event triggered during an aborting condition (e.g. |c_Esc| or
+--- |c_CTRL-C| for |CmdlineLeave|).
+--- @field abort? boolean
+--- @field chan? integer See |channel-id|
+--- @field info? table Dict of arbitrary event data.
+--- @field cmdlevel? integer Level of cmdline.
+--- @field cmdtype? string Type of cmdline, |cmdline-char|.
+--- @field cwd? string Current working directory.
+--- @field inclusive? boolean Motion is |inclusive|, else exclusive.
+--- @field scope? string Event-specific scope name.
+--- Current |operator|. Also set for Ex commands (unlike |v:operator|). For
+--- example if |TextYankPost| is triggered by the |:yank| Ex command then
+--- `v:event.operator` is "y".
+--- @field operator? string
+--- Text stored in the register as a |readfile()|-style list of lines.
+--- @field regcontents? string
+--- Requested register (e.g "x" for "xyy) or the empty string for an unnamed operation.
+--- @field regname? string
+--- @field regtype? string Type of register as returned by |getregtype()|.
+--- @field visual? boolean Selection is visual (as opposed to, e.g., via motion).
+--- @field completed_item? vim.v.completed_item
+--- Current selected complete item on |CompleteChanged|, Is `{}` when no
+--- complete item selected.
+--- @field height? integer
+--- @field width? integer Height of popup menu on |CompleteChanged|
+--- @field row? integer Width of popup menu on |CompleteChanged|
+--- Col count of popup menu on |CompleteChanged|, relative to screen.
+--- @field col? integer
+--- @field size? integer Total number of completion items on |CompleteChanged|.
+--- Is |v:true| if popup menu have scrollbar, or |v:false| if not.
+--- @field scrollbar? boolean
+--- Is |v:true| if the event fired while changing window  (or tab) on |DirChanged|.
+--- @field changed_window? boolean
+--- @field status? boolean Job status or exit code, -1 means "unknown". |TermClose|
+--- @field reason? string Reason for completion being done. |CompleteDone|
+--- The word that was selected, empty if abandoned complete. @field complete_word? string
+--- @field complete_type? string See |complete_info_mode|
diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua
index f1b8c22bdd..e705c02e83 100644
--- a/src/nvim/vvars.lua
+++ b/src/nvim/vvars.lua
@@ -10,6 +10,7 @@ M.vars = {
     ]=],
   },
   char = {
+    type = 'string',
     desc = [=[
       Argument for evaluating 'formatexpr' and used for the typed
       character when using  in an abbreviation |:map-|.
@@ -63,6 +64,7 @@ M.vars = {
     ]=],
   },
   completed_item = {
+    type = 'vim.v.completed_item',
     desc = [=[
       Dictionary containing the |complete-items| for the most
       recently completed word after |CompleteDone|.  Empty if the
@@ -94,6 +96,7 @@ M.vars = {
     ]=],
   },
   ctype = {
+    type = 'string',
     desc = [=[
       The current locale setting for characters of the runtime
       environment.  This allows Vim scripts to be aware of the
@@ -158,6 +161,7 @@ M.vars = {
     ]=],
   },
   event = {
+    type = 'vim.v.event',
     desc = [=[
       Dictionary of event data for the current |autocommand|.  Valid
       only during the event lifetime; storing or passing v:event is
@@ -238,6 +242,7 @@ M.vars = {
     ]=],
   },
   exiting = {
+    type = 'integer?',
     desc = [=[
       Exit code, or |v:null| before invoking the |VimLeavePre|
       and |VimLeave| autocmds.  See |:q|, |:x| and |:cquit|.
@@ -470,6 +475,7 @@ M.vars = {
     ]=],
   },
   msgpack_types = {
+    type = 'table',
     desc = [=[
       Dictionary containing msgpack types used by |msgpackparse()|
       and |msgpackdump()|. All types inside dictionary are fixed
@@ -635,6 +641,7 @@ M.vars = {
     ]=],
   },
   scrollstart = {
+    type = 'string',
     desc = [=[
       String describing the script or function that caused the
       screen to scroll up.  It's only set when it is empty, thus the
@@ -798,11 +805,13 @@ M.vars = {
     ]=],
   },
   testing = {
+    type = 'integer',
     desc = [=[
       Must be set before using `test_garbagecollect_now()`.
     ]=],
   },
   this_session = {
+    type = 'string',
     desc = [=[
       Full filename of the last loaded or saved session file.
       Empty when no session file has been saved.  See |:mksession|.
@@ -810,6 +819,7 @@ M.vars = {
     ]=],
   },
   throwpoint = {
+    type = 'string',
     desc = [=[
       The point where the exception most recently caught and not
       finished was thrown.  Not set when commands are typed.  See
-- 
cgit 


From 2336389d236f3d80575a5139e92c551c005b0eff Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 Dec 2024 13:24:54 +0800
Subject: vim-patch:9.0.2112: [security]: overflow in shift_line

Problem:  [security]: overflow in shift_line
Solution: allow a max indent of INT_MAX

[security]: overflow in shift_line

When shifting lines in operator pending mode and using a very large
value, we may overflow the size of integer. Fix this by using a long
variable, testing if the result would be larger than INT_MAX and if so,
indent by INT_MAX value.

Special case: We cannot use long here, since on 32bit architectures (or
on Windows?), it typically cannot take larger values than a plain int,
so we have to use long long count, decide whether the resulting
multiplication of the shiftwidth value * amount is larger than INT_MAX
and if so, we will store INT_MAX as possible larges value in the long
long count variable.

Then we can safely cast it back to int when calling the functions to set
the indent (set_indent() or change_indent()). So this should be safe.

Add a test that when using a huge value in operator pending mode for
shifting, we will shift by INT_MAX

closes: vim/vim#13535

https://github.com/vim/vim/commit/6bf131888a3d1de62bbfa8a7ea03c0ddccfd496e

Skip the test for now, as it takes too long and requires other fixes.

Co-authored-by: Christian Brabandt 
---
 src/nvim/ops.c                   | 16 ++++++++++------
 test/old/testdir/test_indent.vim | 12 ++++++++++++
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 63c78936ba..1bba1154f2 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -283,11 +283,11 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
   if (sw_val == 0) {
     sw_val = 1;              // shouldn't happen, just in case
   }
-  int count = get_indent();  // get current indent
+  int64_t count = get_indent();  // get current indent
 
   if (round) {  // round off indent
-    int i = count / sw_val;  // number of 'shiftwidth' rounded down
-    int j = count % sw_val;  // extra spaces
+    int i = (int)(count / sw_val);  // number of 'shiftwidth' rounded down
+    int j = (int)(count % sw_val);  // extra spaces
     if (j && left) {  // first remove extra spaces
       amount--;
     }
@@ -301,15 +301,19 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
     if (left) {
       count = MAX(count - sw_val * amount, 0);
     } else {
-      count += sw_val * amount;
+      if ((int64_t)sw_val * (int64_t)amount > INT_MAX - count) {
+        count = INT_MAX;
+      } else {
+        count += (int64_t)sw_val * (int64_t)amount;
+      }
     }
   }
 
   // Set new indent
   if (State & VREPLACE_FLAG) {
-    change_indent(INDENT_SET, count, false, call_changed_bytes);
+    change_indent(INDENT_SET, (int)count, false, call_changed_bytes);
   } else {
-    set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
+    set_indent((int)count, call_changed_bytes ? SIN_CHANGED : 0);
   }
 }
 
diff --git a/test/old/testdir/test_indent.vim b/test/old/testdir/test_indent.vim
index dcacc11663..6afc3ff405 100644
--- a/test/old/testdir/test_indent.vim
+++ b/test/old/testdir/test_indent.vim
@@ -276,4 +276,16 @@ func Test_formatting_keeps_first_line_indent()
   bwipe!
 endfunc
 
+" Test for indenting with large amount, causes overflow
+func Test_indent_overflow_count()
+  throw 'skipped: TODO: '
+  new
+  setl sw=8
+  call setline(1, "abc")
+  norm! V2147483647>
+  " indents by INT_MAX
+  call assert_equal(2147483647, indent(1))
+  close!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From ac230370f3de2925b3ca9443e8d52d7fc4311aba Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 Dec 2024 13:38:35 +0800
Subject: vim-patch:9.0.2113: Coverity warns for another overflow in
 shift_line()

Problem:  Coverity warns for another overflow in shift_line()
Solution: Test for INT_MAX after the if condition, cast integer values
          to (long long) before multiplying.

https://github.com/vim/vim/commit/22a97fc241361aa91bda84e5344d5b7c0cda3e81

Co-authored-by: Christian Brabandt 
---
 src/nvim/ops.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 1bba1154f2..5325beb654 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -296,19 +296,19 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
     } else {
       i += amount;
     }
-    count = i * sw_val;
+    count = (int64_t)i * (int64_t)sw_val;
   } else {  // original vi indent
     if (left) {
-      count = MAX(count - sw_val * amount, 0);
+      count = MAX(count - (int64_t)sw_val * (int64_t)amount, 0);
     } else {
-      if ((int64_t)sw_val * (int64_t)amount > INT_MAX - count) {
-        count = INT_MAX;
-      } else {
-        count += (int64_t)sw_val * (int64_t)amount;
-      }
+      count += (int64_t)sw_val * (int64_t)amount;
     }
   }
 
+  if (count > INT_MAX) {
+    count = INT_MAX;
+  }
+
   // Set new indent
   if (State & VREPLACE_FLAG) {
     change_indent(INDENT_SET, (int)count, false, call_changed_bytes);
-- 
cgit 


From 7a7ed0c8ac2d66d695da5bd3f90536e8c5c11ccc Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 Dec 2024 13:52:40 +0800
Subject: vim-patch:9.0.2122: [security]: prevent overflow in indenting

Problem:  [security]: prevent overflow in indenting
Solution: use long long and remove cast to (int)

The shiftwidth option values are defined as being long. However, when
calculating the actual amount of indent, we cast down to (int), which
may cause the shiftwidth value to become negative and later it may even
cause Vim to try to allocate a huge amount of memory.

We already use long and long long variable types to calculate the indent
(and detect possible overflows), so the cast to (int) seems superfluous
and can be safely removed. So let's just remove the (int) cast and
calculate the indent using longs.

Additionally, the 'shiftwidth' option value is also used when determining
the actual 'cino' options. There it can again cause another overflow, so
make sure it is safe in parse_cino() as well.

fixes: vim/vim#13554
closes: vim/vim#13555

https://github.com/vim/vim/commit/3770574e4a70e810add9929973c51f9070c8c851

Co-authored-by: Christian Brabandt 
---
 src/nvim/indent_c.c              | 82 +++++++++++++++++++++-------------------
 test/old/testdir/test_indent.vim | 15 ++++++++
 2 files changed, 59 insertions(+), 38 deletions(-)

diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 98b0d6003a..9b5c80dd09 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -1689,7 +1689,7 @@ void parse_cino(buf_T *buf)
       p++;
     }
     char *digits_start = p;   // remember where the digits start
-    int n = getdigits_int(&p, true, 0);
+    int64_t n = getdigits_int(&p, true, 0);
     divider = 0;
     if (*p == '.') {        // ".5s" means a fraction.
       fraction = atoi(++p);
@@ -1717,119 +1717,125 @@ void parse_cino(buf_T *buf)
       n = -n;
     }
 
+    if (n > INT_MAX) {
+      n = INT_MAX;
+    } else if (n < INT_MIN) {
+      n = INT_MIN;
+    }
+
     // When adding an entry here, also update the default 'cinoptions' in
     // doc/indent.txt, and add explanation for it!
     switch (*l) {
     case '>':
-      buf->b_ind_level = n;
+      buf->b_ind_level = (int)n;
       break;
     case 'e':
-      buf->b_ind_open_imag = n;
+      buf->b_ind_open_imag = (int)n;
       break;
     case 'n':
-      buf->b_ind_no_brace = n;
+      buf->b_ind_no_brace = (int)n;
       break;
     case 'f':
-      buf->b_ind_first_open = n;
+      buf->b_ind_first_open = (int)n;
       break;
     case '{':
-      buf->b_ind_open_extra = n;
+      buf->b_ind_open_extra = (int)n;
       break;
     case '}':
-      buf->b_ind_close_extra = n;
+      buf->b_ind_close_extra = (int)n;
       break;
     case '^':
-      buf->b_ind_open_left_imag = n;
+      buf->b_ind_open_left_imag = (int)n;
       break;
     case 'L':
-      buf->b_ind_jump_label = n;
+      buf->b_ind_jump_label = (int)n;
       break;
     case ':':
-      buf->b_ind_case = n;
+      buf->b_ind_case = (int)n;
       break;
     case '=':
-      buf->b_ind_case_code = n;
+      buf->b_ind_case_code = (int)n;
       break;
     case 'b':
-      buf->b_ind_case_break = n;
+      buf->b_ind_case_break = (int)n;
       break;
     case 'p':
-      buf->b_ind_param = n;
+      buf->b_ind_param = (int)n;
       break;
     case 't':
-      buf->b_ind_func_type = n;
+      buf->b_ind_func_type = (int)n;
       break;
     case '/':
-      buf->b_ind_comment = n;
+      buf->b_ind_comment = (int)n;
       break;
     case 'c':
-      buf->b_ind_in_comment = n;
+      buf->b_ind_in_comment = (int)n;
       break;
     case 'C':
-      buf->b_ind_in_comment2 = n;
+      buf->b_ind_in_comment2 = (int)n;
       break;
     case 'i':
-      buf->b_ind_cpp_baseclass = n;
+      buf->b_ind_cpp_baseclass = (int)n;
       break;
     case '+':
-      buf->b_ind_continuation = n;
+      buf->b_ind_continuation = (int)n;
       break;
     case '(':
-      buf->b_ind_unclosed = n;
+      buf->b_ind_unclosed = (int)n;
       break;
     case 'u':
-      buf->b_ind_unclosed2 = n;
+      buf->b_ind_unclosed2 = (int)n;
       break;
     case 'U':
-      buf->b_ind_unclosed_noignore = n;
+      buf->b_ind_unclosed_noignore = (int)n;
       break;
     case 'W':
-      buf->b_ind_unclosed_wrapped = n;
+      buf->b_ind_unclosed_wrapped = (int)n;
       break;
     case 'w':
-      buf->b_ind_unclosed_whiteok = n;
+      buf->b_ind_unclosed_whiteok = (int)n;
       break;
     case 'm':
-      buf->b_ind_matching_paren = n;
+      buf->b_ind_matching_paren = (int)n;
       break;
     case 'M':
-      buf->b_ind_paren_prev = n;
+      buf->b_ind_paren_prev = (int)n;
       break;
     case ')':
-      buf->b_ind_maxparen = n;
+      buf->b_ind_maxparen = (int)n;
       break;
     case '*':
-      buf->b_ind_maxcomment = n;
+      buf->b_ind_maxcomment = (int)n;
       break;
     case 'g':
-      buf->b_ind_scopedecl = n;
+      buf->b_ind_scopedecl = (int)n;
       break;
     case 'h':
-      buf->b_ind_scopedecl_code = n;
+      buf->b_ind_scopedecl_code = (int)n;
       break;
     case 'j':
-      buf->b_ind_java = n;
+      buf->b_ind_java = (int)n;
       break;
     case 'J':
-      buf->b_ind_js = n;
+      buf->b_ind_js = (int)n;
       break;
     case 'l':
-      buf->b_ind_keep_case_label = n;
+      buf->b_ind_keep_case_label = (int)n;
       break;
     case '#':
-      buf->b_ind_hash_comment = n;
+      buf->b_ind_hash_comment = (int)n;
       break;
     case 'N':
-      buf->b_ind_cpp_namespace = n;
+      buf->b_ind_cpp_namespace = (int)n;
       break;
     case 'k':
-      buf->b_ind_if_for_while = n;
+      buf->b_ind_if_for_while = (int)n;
       break;
     case 'E':
-      buf->b_ind_cpp_extern_c = n;
+      buf->b_ind_cpp_extern_c = (int)n;
       break;
     case 'P':
-      buf->b_ind_pragma = n;
+      buf->b_ind_pragma = (int)n;
       break;
     }
     if (*p == ',') {
diff --git a/test/old/testdir/test_indent.vim b/test/old/testdir/test_indent.vim
index 6afc3ff405..0998b04443 100644
--- a/test/old/testdir/test_indent.vim
+++ b/test/old/testdir/test_indent.vim
@@ -288,4 +288,19 @@ func Test_indent_overflow_count()
   close!
 endfunc
 
+func Test_indent_overflow_count2()
+  throw 'skipped: Nvim does not support 64-bit number options'
+  new
+  " this only works, when long is 64bits
+  try
+    setl sw=0x180000000
+  catch /^Vim\%((\a\+)\)\=:E487:/
+  throw 'Skipped: value negative on this platform'
+  endtry
+  call setline(1, "\tabc")
+  norm! <<
+  call assert_equal(0, indent(1))
+  close!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From 6c81c16e1b8dd1650960bf1cea57ce05b26f7822 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 Dec 2024 13:48:59 +0800
Subject: vim-patch:9.0.2124: INT overflow detection logic can be simplified

Problem:  INT overflow logic can be simplified
Solution: introduce trim_to_int() function

closes: vim/vim#13556

https://github.com/vim/vim/commit/2b0882fa6555b4d0197afbdfc32a4533cf6aacf4

vim-patch:9.0.2138: Overflow logic requires long long

Problem:  Overflow logic requires long long
Solution: Define vimlong_T data type to make life easier
          for porters

closes: vim/vim#13598

https://github.com/vim/vim/commit/fda700cb04612fe2f9301a9ba820309175decabf

Cherry-pick ops.c change from patch 9.1.0608.

Co-authored-by: Ernie Rael 
---
 src/nvim/indent_c.c |  9 +++------
 src/nvim/math.c     |  6 ++++++
 src/nvim/ops.c      | 13 +++++--------
 3 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 9b5c80dd09..c9b7a1ba3f 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -14,6 +14,7 @@
 #include "nvim/indent_c.h"
 #include "nvim/macros_defs.h"
 #include "nvim/mark_defs.h"
+#include "nvim/math.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
 #include "nvim/option.h"
@@ -1708,7 +1709,7 @@ void parse_cino(buf_T *buf)
       } else {
         n *= sw;
         if (divider) {
-          n += (sw * fraction + divider / 2) / divider;
+          n += ((int64_t)sw * fraction + divider / 2) / divider;
         }
       }
       p++;
@@ -1717,11 +1718,7 @@ void parse_cino(buf_T *buf)
       n = -n;
     }
 
-    if (n > INT_MAX) {
-      n = INT_MAX;
-    } else if (n < INT_MIN) {
-      n = INT_MIN;
-    }
+    n = trim_to_int(n);
 
     // When adding an entry here, also update the default 'cinoptions' in
     // doc/indent.txt, and add explanation for it!
diff --git a/src/nvim/math.c b/src/nvim/math.c
index 4ca212413b..0b5886d36c 100644
--- a/src/nvim/math.c
+++ b/src/nvim/math.c
@@ -106,3 +106,9 @@ int vim_append_digit_int(int *value, int digit)
   *value = x * 10 + digit;
   return OK;
 }
+
+/// Return something that fits into an int.
+int trim_to_int(int64_t x)
+{
+  return x > INT_MAX ? INT_MAX : x < INT_MIN ? INT_MIN : (int)x;
+}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 5325beb654..9524c67301 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -48,6 +48,7 @@
 #include "nvim/macros_defs.h"
 #include "nvim/mark.h"
 #include "nvim/mark_defs.h"
+#include "nvim/math.h"
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
@@ -286,8 +287,8 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
   int64_t count = get_indent();  // get current indent
 
   if (round) {  // round off indent
-    int i = (int)(count / sw_val);  // number of 'shiftwidth' rounded down
-    int j = (int)(count % sw_val);  // extra spaces
+    int i = trim_to_int(count / sw_val);  // number of 'shiftwidth' rounded down
+    int j = trim_to_int(count % sw_val);  // extra spaces
     if (j && left) {  // first remove extra spaces
       amount--;
     }
@@ -305,15 +306,11 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
     }
   }
 
-  if (count > INT_MAX) {
-    count = INT_MAX;
-  }
-
   // Set new indent
   if (State & VREPLACE_FLAG) {
-    change_indent(INDENT_SET, (int)count, false, call_changed_bytes);
+    change_indent(INDENT_SET, trim_to_int(count), false, call_changed_bytes);
   } else {
-    set_indent((int)count, call_changed_bytes ? SIN_CHANGED : 0);
+    set_indent(trim_to_int(count), call_changed_bytes ? SIN_CHANGED : 0);
   }
 }
 
-- 
cgit 


From e4bb185441ca57b739775d3f01a13419d9299f29 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 Dec 2024 14:05:39 +0800
Subject: vim-patch:9.1.0917: various vartabstop and shiftround bugs when
 shifting lines

Problem:  various vartabstop and shiftround bugs when shifting lines
Solution: Fix the bugs, add new tests for shifting lines in various ways
          (Gary Johnson)

fixes: vim/vim#14891
closes: vim/vim#16193

https://github.com/vim/vim/commit/eed63f96d26723ff31a9728647eed526d06a553d

Co-authored-by: Gary Johnson 
---
 src/nvim/ops.c                  | 141 ++++++-
 test/old/testdir/test_shift.vim | 807 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 931 insertions(+), 17 deletions(-)

diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 9524c67301..71a005bd24 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -274,21 +274,53 @@ void op_shift(oparg_T *oap, bool curs_top, int amount)
   changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
 }
 
-/// Shift the current line one shiftwidth left (if left != 0) or right
-/// leaves cursor on first blank in the line.
-///
-/// @param call_changed_bytes  call changed_bytes()
-void shift_line(bool left, bool round, int amount, int call_changed_bytes)
+/// Return the tabstop width at the index of the variable tabstop array.  If an
+/// index greater than the length of the array is given, the last tabstop width
+/// in the array is returned.
+static int get_vts(const int *vts_array, int index)
+{
+  int ts;
+
+  if (index < 1) {
+    ts = 0;
+  } else if (index <= vts_array[0]) {
+    ts = vts_array[index];
+  } else {
+    ts = vts_array[vts_array[0]];
+  }
+
+  return ts;
+}
+
+/// Return the sum of all the tabstops through the index-th.
+static int get_vts_sum(const int *vts_array, int index)
 {
-  int sw_val = get_sw_value_indent(curbuf, left);
-  if (sw_val == 0) {
-    sw_val = 1;              // shouldn't happen, just in case
+  int sum = 0;
+  int i;
+
+  // Perform the summation for indeces within the actual array.
+  for (i = 1; i <= index && i <= vts_array[0]; i++) {
+    sum += vts_array[i];
   }
+
+  // Add topstops whose indeces exceed the actual array.
+  if (i <= index) {
+    sum += vts_array[vts_array[0]] * (index - vts_array[0]);
+  }
+
+  return sum;
+}
+
+/// @param left    true if shift is to the left
+/// @param count   true if new indent is to be to a tabstop
+/// @param amount  number of shifts
+static int64_t get_new_sw_indent(bool left, bool round, int64_t amount, int64_t sw_val)
+{
   int64_t count = get_indent();  // get current indent
 
   if (round) {  // round off indent
-    int i = trim_to_int(count / sw_val);  // number of 'shiftwidth' rounded down
-    int j = trim_to_int(count % sw_val);  // extra spaces
+    int64_t i = trim_to_int(count / sw_val);  // number of 'shiftwidth' rounded down
+    int64_t j = trim_to_int(count % sw_val);  // extra spaces
     if (j && left) {  // first remove extra spaces
       amount--;
     }
@@ -297,15 +329,98 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
     } else {
       i += amount;
     }
-    count = (int64_t)i * (int64_t)sw_val;
+    count = i * sw_val;
   } else {  // original vi indent
     if (left) {
-      count = MAX(count - (int64_t)sw_val * (int64_t)amount, 0);
+      count = MAX(count - sw_val * amount, 0);
+    } else {
+      count += sw_val * amount;
+    }
+  }
+
+  return count;
+}
+
+/// @param left    true if shift is to the left
+/// @param count   true if new indent is to be to a tabstop
+/// @param amount  number of shifts
+static int64_t get_new_vts_indent(bool left, bool round, int amount, int *vts_array)
+{
+  int64_t indent = get_indent();
+  int vtsi = 0;
+  int vts_indent = 0;
+  int ts = 0;         // Silence uninitialized variable warning.
+
+  // Find the tabstop at or to the left of the current indent.
+  while (vts_indent <= indent) {
+    vtsi++;
+    ts = get_vts(vts_array, vtsi);
+    vts_indent += ts;
+  }
+  vts_indent -= ts;
+  vtsi--;
+
+  // Extra indent spaces to the right of the tabstop
+  int64_t offset = indent - vts_indent;
+
+  if (round) {
+    if (left) {
+      if (offset == 0) {
+        indent = get_vts_sum(vts_array, vtsi - amount);
+      } else {
+        indent = get_vts_sum(vts_array, vtsi - (amount - 1));
+      }
+    } else {
+      indent = get_vts_sum(vts_array, vtsi + amount);
+    }
+  } else {
+    if (left) {
+      if (amount > vtsi) {
+        indent = 0;
+      } else {
+        indent = get_vts_sum(vts_array, vtsi - amount) + offset;
+      }
     } else {
-      count += (int64_t)sw_val * (int64_t)amount;
+      indent = get_vts_sum(vts_array, vtsi + amount) + offset;
     }
   }
 
+  return indent;
+}
+
+/// Shift the current line 'amount' shiftwidth(s) left (if 'left' is true) or
+/// right.
+///
+/// The rules for choosing a shiftwidth are:  If 'shiftwidth' is non-zero, use
+/// 'shiftwidth'; else if 'vartabstop' is not empty, use 'vartabstop'; else use
+/// 'tabstop'.  The Vim documentation says nothing about 'softtabstop' or
+/// 'varsofttabstop' affecting the shiftwidth, and neither affects the
+/// shiftwidth in current versions of Vim, so they are not considered here.
+///
+/// @param left                true if shift is to the left
+/// @param count               true if new indent is to be to a tabstop
+/// @param amount              number of shifts
+/// @param call_changed_bytes  call changed_bytes()
+void shift_line(bool left, bool round, int amount, int call_changed_bytes)
+{
+  int64_t count;
+  int64_t sw_val = curbuf->b_p_sw;
+  int64_t ts_val = curbuf->b_p_ts;
+  int *vts_array = curbuf->b_p_vts_array;
+
+  if (sw_val != 0) {
+    // 'shiftwidth' is not zero; use it as the shift size.
+    count = get_new_sw_indent(left, round, amount, sw_val);
+  } else if ((vts_array == NULL) || (vts_array[0] == 0)) {
+    // 'shiftwidth' is zero and 'vartabstop' is empty; use 'tabstop' as the
+    // shift size.
+    count = get_new_sw_indent(left, round, amount, ts_val);
+  } else {
+    // 'shiftwidth' is zero and 'vartabstop' is defined; use 'vartabstop'
+    // to determine the new indent.
+    count = get_new_vts_indent(left, round, amount, vts_array);
+  }
+
   // Set new indent
   if (State & VREPLACE_FLAG) {
     change_indent(INDENT_SET, trim_to_int(count), false, call_changed_bytes);
diff --git a/test/old/testdir/test_shift.vim b/test/old/testdir/test_shift.vim
index ec357dac88..f31c5a11e6 100644
--- a/test/old/testdir/test_shift.vim
+++ b/test/old/testdir/test_shift.vim
@@ -108,10 +108,809 @@ func Test_ex_shift_errors()
   call assert_fails('>!', 'E477:')
   call assert_fails('', 'E493:')
-  call assert_fails('execute "2,1>"', 'E493:')
-  " call assert_fails('2,1<', 'E493:')
-  call assert_fails('execute "2,1<"', 'E493:')
+  call assert_fails('2,1>', 'E493:')
+  call assert_fails('2,1<', 'E493:')
+endfunc
+
+" Test inserting a backspace at the start of a line.
+"
+" This is to verify the proper behavior of tabstop_start() as called from
+" ins_bs().
+"
+func Test_shift_ins_bs()
+  set backspace=indent,start
+  set softtabstop=11
+
+  call setline(1, repeat(" ", 33) . "word")
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 22) . "word", getline(1))
+  call setline(1, repeat(" ", 23) . "word")
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 22) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 11) . "word", getline(1))
+
+  set backspace& softtabstop&
+  bw!
+endfunc
+
+" Test inserting a backspace at the start of a line, with 'varsofttabstop'.
+"
+func Test_shift_ins_bs_vartabs()
+  CheckFeature vartabs
+  set backspace=indent,start
+  set varsofttabstop=13,11,7
+
+  call setline(1, repeat(" ", 44) . "word")
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 38) . "word", getline(1))
+  call setline(1, repeat(" ", 39) . "word")
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 38) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 31) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 24) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 13) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(                  "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(                  "word", getline(1))
+
+  set backspace& varsofttabstop&
+  bw!
+endfunc
+
+" Test the >> and << normal-mode commands.
+"
+func Test_shift_norm()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=5
+  set tabstop=7
+
+  call setline(1, "  word")
+
+  " Shift by 'shiftwidth' right and left.
+
+  norm! >>
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  12) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  17) . "word", getline(1))
+
+  norm! <<
+  call assert_equal(repeat(" ",  12) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "  word")
+
+  norm! >>
+  call assert_equal(repeat(" ",  9) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  16) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  23) . "word", getline(1))
+
+  norm! <<
+  call assert_equal(repeat(" ",  16) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  9) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test the >> and << normal-mode commands, with 'vartabstop'.
+"
+func Test_shift_norm_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "  word")
+
+  norm! >>
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  38) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  49) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  60) . "word", getline(1))
+
+  norm! <<
+  call assert_equal(repeat(" ",  49) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  38) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftwidth& vartabstop&
+  bw!
+endfunc
+
+" Test the >> and << normal-mode commands with 'shiftround'.
+"
+func Test_shift_norm_round()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftround
+  set shiftwidth=5
+  set tabstop=7
+
+  call setline(1, "word")
+
+  " Shift by 'shiftwidth' right and left.
+
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  5) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  10) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  15) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  20) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  25) . "word", getline(1))
+
+  norm! <<
+  call assert_equal(repeat(" ",  20) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  15) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  10) . "word", getline(1))
+  exe "norm! I  "
+  norm! <<
+  call assert_equal(repeat(" ",  10) . "word", getline(1))
+
+  call setline(1, repeat(" ", 7) . "word")
+  norm! <<
+  call assert_equal(repeat(" ",  5) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  call setline(1, repeat(" ", 2) . "word")
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "word")
+
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  14) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  28) . "word", getline(1))
+  norm! >>
+  call assert_equal(repeat(" ",  35) . "word", getline(1))
+
+  norm! <<
+  call assert_equal(repeat(" ",  28) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  14) . "word", getline(1))
+  exe "norm! I  "
+  norm! <<
+  call assert_equal(repeat(" ",  14) . "word", getline(1))
+
+  call setline(1, repeat(" ", 9) . "word")
+  norm! <<
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  call setline(1, repeat(" ", 2) . "word")
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftround& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test the >> and << normal-mode commands with 'shiftround' and 'vartabstop'.
+"
+func Test_shift_norm_round_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftround
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "word")
+
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  36) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  47) . "word", getline(1))
+  exe "norm! I  "
+  norm! >>
+  call assert_equal(repeat(" ",  58) . "word", getline(1))
+
+  exe "norm! I  "
+  norm! <<
+  call assert_equal(repeat(" ",  58) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  47) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  36) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  exe "norm! I  "
+  norm! <<
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  exe "norm! I  "
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftround& shiftwidth& vartabstop&
+  bw!
+endfunc
+
+" Test the V> and V< visual-mode commands.
+"
+" See ":help v_<" and ":help v_>".  See also the last paragraph of "3. Simple
+" changes", ":help simple-change", immediately above "4. Complex changes",
+" ":help complex-change".
+"
+func Test_shift_vis()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=5
+  set tabstop=7
+
+  call setline(1, "  word")
+
+  " Shift by 'shiftwidth' right and left.
+
+  norm! V>
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  norm! V2>
+  call assert_equal(repeat(" ",  17) . "word", getline(1))
+  norm! V3>
+  call assert_equal(repeat(" ",  32) . "word", getline(1))
+
+  norm! V<
+  call assert_equal(repeat(" ",  27) . "word", getline(1))
+  norm! V2<
+  call assert_equal(repeat(" ",  17) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "  word")
+
+  norm! V>
+  call assert_equal(repeat(" ",  9) . "word", getline(1))
+  norm! V2>
+  call assert_equal(repeat(" ",  23) . "word", getline(1))
+  norm! V3>
+  call assert_equal(repeat(" ",  44) . "word", getline(1))
+
+  norm! V<
+  call assert_equal(repeat(" ",  37) . "word", getline(1))
+  norm! V2<
+  call assert_equal(repeat(" ",  23) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test the V> and V< visual-mode commands, with 'vartabstop'.
+"
+" See ":help v_<" and ":help v_>".  See also the last paragraph of "3. Simple
+" changes", ":help simple-change", immediately above "4. Complex changes",
+" ":help complex-change".
+"
+func Test_shift_vis_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "  word")
+
+  norm! V>
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  norm! V2>
+  call assert_equal(repeat(" ",  49) . "word", getline(1))
+  norm! V3>
+  call assert_equal(repeat(" ",  82) . "word", getline(1))
+
+  norm! V<
+  call assert_equal(repeat(" ",  71) . "word", getline(1))
+  norm! V2<
+  call assert_equal(repeat(" ",  49) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftwidth& vartabstop&
+  bw!
+endfunc
+
+" Test the V> and V< visual-mode commands with 'shiftround'.
+"
+func Test_shift_vis_round()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftround
+  set shiftwidth=5
+  set tabstop=7
+
+  call setline(1, "word")
+
+  " Shift by 'shiftwidth' right and left.
+
+  exe "norm! I  "
+  norm! V>
+  call assert_equal(repeat(" ",  5) . "word", getline(1))
+  exe "norm! I  "
+  norm! V2>
+  call assert_equal(repeat(" ",  15) . "word", getline(1))
+  exe "norm! I  "
+  norm! V3>
+  call assert_equal(repeat(" ",  30) . "word", getline(1))
+
+  exe "norm! I  "
+  norm! V2<
+  call assert_equal(repeat(" ",  25) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  10) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  5) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "word")
+
+  exe "norm! I  "
+  norm! V>
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  exe "norm! I  "
+  norm! V2>
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  exe "norm! I  "
+  norm! V3>
+  call assert_equal(repeat(" ",  42) . "word", getline(1))
+
+  exe "norm! I  "
+  norm! V<
+  call assert_equal(repeat(" ",  42) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  35) . "word", getline(1))
+  norm! V2<
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  call setline(1, "  word")
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+
+  set expandtab& shiftround& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test the V> and V< visual-mode commands with 'shiftround' and 'vartabstop'.
+"
+func Test_shift_vis_round_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftround
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "word")
+
+  exe "norm! I  "
+  norm! V>
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  exe "norm! I  "
+  norm! V3>
+  call assert_equal(repeat(" ",  58) . "word", getline(1))
+
+  exe "norm! I  "
+  norm! V2<
+  call assert_equal(repeat(" ",  47) . "word", getline(1))
+  exe "norm! I  "
+  norm! V3<
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  exe "norm! I  "
+  norm! V3<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  exe "norm! I  "
+  norm! V<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftround& shiftwidth& vartabstop&
+  bw!
+endfunc
+
+" Test the :> and :< ex-mode commands.
+"
+func Test_shift_ex()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=5
+  set tabstop=7
+
+  call setline(1, "  word")
+
+  " Shift by 'shiftwidth' right and left.
+
+  >
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  >>
+  call assert_equal(repeat(" ",  17) . "word", getline(1))
+  >>>
+  call assert_equal(repeat(" ",  32) . "word", getline(1))
+
+  <<<<
+  call assert_equal(repeat(" ",  12) . "word", getline(1))
+  <
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  <
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  <
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "  word")
+
+  >
+  call assert_equal(repeat(" ",  9) . "word", getline(1))
+  >>
+  call assert_equal(repeat(" ",  23) . "word", getline(1))
+  >>>
+  call assert_equal(repeat(" ",  44) . "word", getline(1))
+
+  <<<<
+  call assert_equal(repeat(" ",  16) . "word", getline(1))
+  <<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  <
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test the :> and :< ex-mode commands, with vartabstop.
+"
+func Test_shift_ex_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "  word")
+
+  >
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  >>
+  call assert_equal(repeat(" ",  49) . "word", getline(1))
+  >>>
+  call assert_equal(repeat(" ",  82) . "word", getline(1))
+
+  <<<<
+  call assert_equal(repeat(" ",  38) . "word", getline(1))
+  <<
+  call assert_equal(repeat(" ",  2) . "word", getline(1))
+  <
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftwidth& vartabstop&
+  bw!
+endfunc
+
+" Test the :> and :< ex-mode commands with 'shiftround'.
+"
+func Test_shift_ex_round()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftround
+  set shiftwidth=5
+  set tabstop=7
+
+  call setline(1, "word")
+
+  " Shift by 'shiftwidth' right and left.
+
+  exe "norm! I  "
+  >
+  call assert_equal(repeat(" ",  5) . "word", getline(1))
+  exe "norm! I  "
+  >>
+  call assert_equal(repeat(" ",  15) . "word", getline(1))
+  exe "norm! I  "
+  >>>
+  call assert_equal(repeat(" ",  30) . "word", getline(1))
+
+  exe "norm! I  "
+  <<<<
+  call assert_equal(repeat(" ",  15) . "word", getline(1))
+  exe "norm! I  "
+  <<
+  call assert_equal(repeat(" ",  10) . "word", getline(1))
+  <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  >>
+  <<<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "word")
+
+  exe "norm! I  "
+  >
+  call assert_equal(repeat(" ",  7) . "word", getline(1))
+  exe "norm! I  "
+  >>
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  exe "norm! I  "
+  >>>
+  call assert_equal(repeat(" ",  42) . "word", getline(1))
+
+  exe "norm! I  "
+  <<<<
+  call assert_equal(repeat(" ",  21) . "word", getline(1))
+  exe "norm! I  "
+  <<
+  call assert_equal(repeat(" ",  14) . "word", getline(1))
+  exe "norm! I  "
+  <<<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  >>
+  <<<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftround& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test the :> and :< ex-mode commands with 'shiftround' and 'vartabstop'.
+"
+func Test_shift_ex_round_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftround
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "word")
+
+  exe "norm! I  "
+  >
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  exe "norm! I  "
+  >>
+  call assert_equal(repeat(" ",  47) . "word", getline(1))
+  >>>
+  call assert_equal(repeat(" ",  80) . "word", getline(1))
+
+  <<<<
+  call assert_equal(repeat(" ",  36) . "word", getline(1))
+  exe "norm! I  "
+  <<
+  call assert_equal(repeat(" ",  19) . "word", getline(1))
+  exe "norm! I  "
+  <<
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+  <
+  call assert_equal(repeat(" ",  0) . "word", getline(1))
+
+  set expandtab& shiftround& shiftwidth& vartabstop&
+  bw!
+endfunc
+
+" Test shifting lines with  and .
+"
+func Test_shift_ins()
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=5
+  set tabstop=7
+
+  " Shift by 'shiftwidth' right and left.
+
+  call setline(1, repeat(" ", 7) . "word")
+  exe "norm! 9|i\"
+  call assert_equal(repeat(" ", 10) . "word", getline(1))
+  exe "norm! A\"
+  call assert_equal(repeat(" ", 15) . "word", getline(1))
+  exe "norm! I  \"
+  call assert_equal(repeat(" ", 20) . "word", getline(1))
+
+  exe "norm! I  \"
+  call assert_equal(repeat(" ", 20) . "word", getline(1))
+  exe "norm! I  "
+  exe "norm! 24|i\"
+  call assert_equal(repeat(" ", 20) . "word", getline(1))
+  exe "norm! A\"
+  call assert_equal(repeat(" ", 15) . "word", getline(1))
+  exe "norm! I  "
+  exe "norm! A\\"
+  call assert_equal(repeat(" ", 10) . "word", getline(1))
+  exe "norm! I\\\"
+  call assert_equal(repeat(" ", 0) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 0) . "word", getline(1))
+
+  " Shift by 'tabstop' right and left.
+
+  set shiftwidth=0
+  call setline(1, "word")
+
+  call setline(1, repeat(" ", 9) . "word")
+  exe "norm! 11|i\"
+  call assert_equal(repeat(" ", 14) . "word", getline(1))
+  exe "norm! A\"
+  call assert_equal(repeat(" ", 21) . "word", getline(1))
+  exe "norm! I  \"
+  call assert_equal(repeat(" ", 28) . "word", getline(1))
+
+  exe "norm! I  \"
+  call assert_equal(repeat(" ", 28) . "word", getline(1))
+  exe "norm! I  "
+  exe "norm! 32|i\"
+  call assert_equal(repeat(" ", 28) . "word", getline(1))
+  exe "norm! A\"
+  call assert_equal(repeat(" ", 21) . "word", getline(1))
+  exe "norm! I  "
+  exe "norm! A\\"
+  call assert_equal(repeat(" ", 14) . "word", getline(1))
+  exe "norm! I\\\"
+  call assert_equal(repeat(" ", 0) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 0) . "word", getline(1))
+
+  set expandtab& shiftwidth& tabstop&
+  bw!
+endfunc
+
+" Test shifting lines with  and , with 'vartabstop'.
+"
+func Test_shift_ins_vartabs()
+  CheckFeature vartabs
+  set expandtab                 " Don't want to worry about tabs vs. spaces in
+                                " results.
+
+  set shiftwidth=0
+  set vartabstop=19,17,11
+
+  " Shift by 'vartabstop' right and left.
+
+  call setline(1, "word")
+
+  call setline(1, repeat(" ", 9) . "word")
+  exe "norm! 11|i\"
+  call assert_equal(repeat(" ", 19) . "word", getline(1))
+  exe "norm! A\"
+  call assert_equal(repeat(" ", 36) . "word", getline(1))
+  exe "norm! I  \"
+  call assert_equal(repeat(" ", 47) . "word", getline(1))
+
+  exe "norm! I  \"
+  call assert_equal(repeat(" ", 47) . "word", getline(1))
+  exe "norm! I  "
+  exe "norm! 51|i\"
+  call assert_equal(repeat(" ", 47) . "word", getline(1))
+  exe "norm! A\"
+  call assert_equal(repeat(" ", 36) . "word", getline(1))
+  exe "norm! I  "
+  exe "norm! A\\"
+  call assert_equal(repeat(" ", 19) . "word", getline(1))
+  exe "norm! I\\\"
+  call assert_equal(repeat(" ", 0) . "word", getline(1))
+  exe "norm! I\"
+  call assert_equal(repeat(" ", 0) . "word", getline(1))
+
+  set expandtab& shiftwidth& vartabstop&
+  bw!
 endfunc
 
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From d1fd674df3eb07049c780d6b7821e17d471fdc4c Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 Dec 2024 14:53:02 +0800
Subject: fix(ui): update title in more cases (#31508)

---
 src/nvim/drawscreen.c             | 26 +++++++++++++++---
 src/nvim/ex_getln.c               |  5 ++++
 src/nvim/normal.c                 |  4 ---
 src/nvim/terminal.c               |  1 +
 test/functional/ui/title_spec.lua | 57 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 85 insertions(+), 8 deletions(-)

diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index a939038603..0961aabf21 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -679,6 +679,10 @@ int update_screen(void)
 
   updating_screen = false;
 
+  if (need_maketitle) {
+    maketitle();
+  }
+
   // Clear or redraw the command line.  Done last, because scrolling may
   // mess up the command line.
   if (clear_cmdline || redraw_cmdline || redraw_mode) {
@@ -856,6 +860,19 @@ void setcursor_mayforce(win_T *wp, bool force)
   }
 }
 
+/// Mark the title and icon for redraw if either of them uses statusline format.
+///
+/// @return  whether either title or icon uses statusline format.
+bool redraw_custom_title_later(void)
+{
+  if ((p_icon && (stl_syntax & STL_IN_ICON))
+      || (p_title && (stl_syntax & STL_IN_TITLE))) {
+    need_maketitle = true;
+    return true;
+  }
+  return false;
+}
+
 /// Show current cursor info in ruler and various other places
 ///
 /// @param always  if false, only show ruler if position has changed.
@@ -889,10 +906,7 @@ void show_cursor_info_later(bool force)
       curwin->w_redr_status = true;
     }
 
-    if ((p_icon && (stl_syntax & STL_IN_ICON))
-        || (p_title && (stl_syntax & STL_IN_TITLE))) {
-      need_maketitle = true;
-    }
+    redraw_custom_title_later();
   }
 
   curwin->w_stl_cursor = curwin->w_cursor;
@@ -2773,6 +2787,10 @@ void redraw_statuslines(void)
   if (redraw_tabline) {
     draw_tabline();
   }
+
+  if (need_maketitle) {
+    maketitle();
+  }
 }
 
 /// Redraw all status lines at the bottom of frame "frp".
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 2d9d4417dd..85fbdbd20a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -847,6 +847,10 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
       found_one = true;
     }
 
+    if (redraw_custom_title_later()) {
+      found_one = true;
+    }
+
     if (found_one) {
       redraw_statuslines();
     }
@@ -959,6 +963,7 @@ theend:
     msg_ext_clear_later();
   }
   if (!cmd_silent) {
+    redraw_custom_title_later();
     status_redraw_all();  // redraw to show mode change
   }
 
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 92a6068c5a..4d2abf1c8c 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1372,10 +1372,6 @@ static void normal_redraw(NormalState *s)
     }
   }
 
-  if (need_maketitle) {
-    maketitle();
-  }
-
   curbuf->b_last_used = time(NULL);
 
   // Display message after redraw. If an external message is still visible,
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index b4496d6758..6d4af0fc57 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -619,6 +619,7 @@ bool terminal_enter(void)
   invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
   showmode();
   curwin->w_redr_status = true;  // For mode() in statusline. #8323
+  redraw_custom_title_later();
   ui_busy_start();
   apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf);
   may_trigger_modechanged();
diff --git a/test/functional/ui/title_spec.lua b/test/functional/ui/title_spec.lua
index 66eb15478b..aa9ac3f5b5 100644
--- a/test/functional/ui/title_spec.lua
+++ b/test/functional/ui/title_spec.lua
@@ -37,6 +37,63 @@ describe('title', function()
     end)
   end)
 
+  it('is updated in Insert mode', function()
+    api.nvim_set_option_value('title', true, {})
+    screen:expect(function()
+      eq('[No Name] - Nvim', screen.title)
+    end)
+    feed('ifoo')
+    screen:expect(function()
+      eq('[No Name] + - Nvim', screen.title)
+    end)
+    feed('')
+    api.nvim_set_option_value('titlestring', '%m %f (%{mode(1)}) | nvim', {})
+    screen:expect(function()
+      eq('[+] [No Name] (n) | nvim', screen.title)
+    end)
+    feed('i')
+    screen:expect(function()
+      eq('[+] [No Name] (i) | nvim', screen.title)
+    end)
+    feed('')
+    screen:expect(function()
+      eq('[+] [No Name] (n) | nvim', screen.title)
+    end)
+  end)
+
+  it('is updated in Cmdline mode', function()
+    api.nvim_set_option_value('title', true, {})
+    api.nvim_set_option_value('titlestring', '%f (%{mode(1)}) | nvim', {})
+    screen:expect(function()
+      eq('[No Name] (n) | nvim', screen.title)
+    end)
+    feed(':')
+    screen:expect(function()
+      eq('[No Name] (c) | nvim', screen.title)
+    end)
+    feed('')
+    screen:expect(function()
+      eq('[No Name] (n) | nvim', screen.title)
+    end)
+  end)
+
+  it('is updated in Terminal mode', function()
+    api.nvim_set_option_value('title', true, {})
+    api.nvim_set_option_value('titlestring', '(%{mode(1)}) | nvim', {})
+    fn.termopen({ n.testprg('shell-test'), 'INTERACT' })
+    screen:expect(function()
+      eq('(nt) | nvim', screen.title)
+    end)
+    feed('i')
+    screen:expect(function()
+      eq('(t) | nvim', screen.title)
+    end)
+    feed([[]])
+    screen:expect(function()
+      eq('(nt) | nvim', screen.title)
+    end)
+  end)
+
   describe('is not changed by', function()
     local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1'
     local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2'
-- 
cgit 


From c3899419d429c2b2ae05fe90ef349c3a80931e17 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 9 Dec 2024 20:36:34 +0100
Subject: vim-patch:ad4764f: runtime(proto): include filetype plugin for
 protobuf

closes: vim/vim#16199

https://github.com/vim/vim/commit/ad4764f65b678938c1b252245e1af1ae150fbce8

Co-authored-by: David Pedersen 
---
 runtime/ftplugin/proto.vim | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
 create mode 100644 runtime/ftplugin/proto.vim

diff --git a/runtime/ftplugin/proto.vim b/runtime/ftplugin/proto.vim
new file mode 100644
index 0000000000..585f4461d3
--- /dev/null
+++ b/runtime/ftplugin/proto.vim
@@ -0,0 +1,18 @@
+" Vim filetype plugin
+" Language:	Protobuf
+" Maintainer:	David Pedersen 
+" Last Change:	2024 Dec 09
+
+if exists('b:did_ftplugin')
+  finish
+endif
+let b:did_ftplugin = 1
+
+setlocal formatoptions-=t formatoptions+=croql
+
+setlocal comments=s1:/*,mb:*,ex:*/,://
+setlocal commentstring=//\ %s
+
+let b:undo_ftplugin = "setlocal formatoptions< comments< commentstring<"
+
+" vim: sw=2 sts=2 et
-- 
cgit 


From 4182e98752105eb62b412e1fb065923694e57d4d Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 9 Dec 2024 20:37:36 +0100
Subject: vim-patch:b66cac1: runtime(typst): add definition lists to
 formatlistpat, update maintainer

closes: vim/vim#16192

https://github.com/vim/vim/commit/b66cac1a8ed8636a38e867226f5bb621c96ff322

Co-authored-by: Luca Saccarola 
---
 runtime/autoload/typst.vim | 5 +++--
 runtime/compiler/typst.vim | 5 +++--
 runtime/ftplugin/typst.vim | 9 ++++++---
 runtime/indent/typst.vim   | 5 +++--
 runtime/syntax/typst.vim   | 5 +++--
 5 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/runtime/autoload/typst.vim b/runtime/autoload/typst.vim
index 6d097dd922..362da3f45e 100644
--- a/runtime/autoload/typst.vim
+++ b/runtime/autoload/typst.vim
@@ -1,6 +1,7 @@
 " Language:    Typst
-" Maintainer:  Gregory Anders
-" Last Change: 2024 Nov 02
+" Previous Maintainer:  Gregory Anders
+" Maintainer:  Luca Saccarola 
+" Last Change: 2024 Dec 09
 " Based on:    https://github.com/kaarmu/typst.vim
 
 function! typst#indentexpr() abort
diff --git a/runtime/compiler/typst.vim b/runtime/compiler/typst.vim
index 33e55818e9..13699f4675 100644
--- a/runtime/compiler/typst.vim
+++ b/runtime/compiler/typst.vim
@@ -1,7 +1,8 @@
 " Vim compiler file
 " Language:    Typst
-" Maintainer:  Gregory Anders
-" Last Change: 2024-07-14
+" Previous Maintainer:  Gregory Anders
+" Maintainer:  Luca Saccarola 
+" Last Change: 2024 Dec 09
 " Based on:    https://github.com/kaarmu/typst.vim
 
 if exists('current_compiler')
diff --git a/runtime/ftplugin/typst.vim b/runtime/ftplugin/typst.vim
index 09b65d0e52..08929bbdbe 100644
--- a/runtime/ftplugin/typst.vim
+++ b/runtime/ftplugin/typst.vim
@@ -1,7 +1,8 @@
 " Vim filetype plugin file
 " Language:    Typst
-" Maintainer:  Gregory Anders
-" Last Change: 2024 Dev 01
+" Previous Maintainer:  Gregory Anders
+" Maintainer:  Luca Saccarola 
+" Last Change: 2024 Dec 09
 " Based on:    https://github.com/kaarmu/typst.vim
 
 if exists('b:did_ftplugin')
@@ -12,8 +13,10 @@ let b:did_ftplugin = 1
 setlocal commentstring=//\ %s
 setlocal comments=s1:/*,mb:*,ex:*/,://
 setlocal formatoptions+=croqn
+" Numbered Lists
 setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
-setlocal formatlistpat+=\\\|^\\s*[-+\]\\s\\+
+" Unordered (-), Ordered (+) and definition (/) Lists
+setlocal formatlistpat+=\\\|^\\s*[-+/\]\\s\\+
 setlocal suffixesadd=.typ
 
 let b:undo_ftplugin = 'setl cms< com< fo< flp< sua<'
diff --git a/runtime/indent/typst.vim b/runtime/indent/typst.vim
index 6aaa04a53a..c990b968c8 100644
--- a/runtime/indent/typst.vim
+++ b/runtime/indent/typst.vim
@@ -1,7 +1,8 @@
 " Vim indent file
 " Language:    Typst
-" Maintainer:  Gregory Anders 
-" Last Change: 2024-07-14
+" Previous Maintainer:  Gregory Anders
+" Maintainer:  Luca Saccarola 
+" Last Change: 2024 Dec 09
 " Based on:    https://github.com/kaarmu/typst.vim
 
 if exists('b:did_indent')
diff --git a/runtime/syntax/typst.vim b/runtime/syntax/typst.vim
index dae1424780..b1f05ec853 100644
--- a/runtime/syntax/typst.vim
+++ b/runtime/syntax/typst.vim
@@ -1,7 +1,8 @@
 " Vim syntax file
 " Language:    Typst
-" Maintainer:  Gregory Anders 
-" Last Change: 2024 Nov 02
+" Previous Maintainer:  Gregory Anders
+" Maintainer:  Luca Saccarola 
+" Last Change: 2024 Dec 09
 " Based on:    https://github.com/kaarmu/typst.vim
 
 if exists('b:current_syntax')
-- 
cgit 


From ca760e645ba4d1fdb0b6fff3ac98231c3d683306 Mon Sep 17 00:00:00 2001
From: Tomasz N 
Date: Tue, 10 Dec 2024 11:39:41 +0100
Subject: fix(messages): no message kind for :write messages #31519

- Problem: cannot replace the initial bufwrite message (from `filemess`) by the final one (`"test.lua" [New] 0L, 0B written`), when using `vim.ui_attach`.
- Solution: add kind to both messages.
---
 runtime/doc/ui.txt                   | 1 +
 src/nvim/bufwrite.c                  | 2 ++
 test/functional/ui/messages_spec.lua | 2 +-
 3 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 77eddfd8e1..7696dbdb9d 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -789,6 +789,7 @@ must handle.
 	kind
 	    Name indicating the message kind:
 		"" (empty)	Unknown (consider a |feature-request|)
+		"bufwrite"	|:write| message
 		"confirm"	|confirm()| or |:confirm| dialog
 		"confirm_sub"	|:substitute| confirm dialog |:s_c|
 		"emsg"		Error (|errors|, internal error, |:throw|, …)
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
index 95639bed70..2cf02403da 100644
--- a/src/nvim/bufwrite.c
+++ b/src/nvim/bufwrite.c
@@ -1148,6 +1148,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
     msg_scroll = true;              // don't overwrite previous file message
   }
   if (!filtering) {
+    msg_ext_set_kind("bufwrite");
     // show that we are busy
 #ifndef UNIX
     filemess(buf, sfname, "");
@@ -1763,6 +1764,7 @@ restore_backup:
     if (msg_add_fileformat(fileformat)) {
       insert_space = true;
     }
+    msg_ext_set_kind("bufwrite");
     msg_add_lines(insert_space, lnum, nchars);       // add line/char count
     if (!shortmess(SHM_WRITE)) {
       if (append) {
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 164b840b35..e5316f264b 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -1112,7 +1112,7 @@ stack traceback:
     command('write ' .. fname)
     screen:expect({
       messages = {
-        { content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = '' },
+        { content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = 'bufwrite' },
       },
     })
   end)
-- 
cgit 


From 3f1d09bc94d02266d6fa588a2ccd1be1ca084cf7 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 1 Nov 2024 16:31:51 +0000
Subject: feat(lsp): add vim.lsp.config and vim.lsp.enable

Design goals/requirements:
- Default configuration of a server can be distributed across multiple sources.
  - And via RTP discovery.
- Default configuration can be specified for all servers.
- Configuration _can_ be project specific.

Solution:

- Two new API's:
  - `vim.lsp.config(name, cfg)`:
    - Used to define default configurations for servers of name.
    - Can be used like a table or called as a function.
    - Use `vim.lsp.confg('*', cfg)` to specify default config for all
      servers.
  - `vim.lsp.enable(name)`
    - Used to enable servers of name. Uses configuration defined
    via `vim.lsp.config()`.
---
 runtime/doc/lsp.txt                 | 221 ++++++++++++++++++++++++++-----
 runtime/doc/news.txt                |   3 +
 runtime/doc/options.txt             |   1 +
 runtime/lua/vim/_meta/options.lua   |   1 +
 runtime/lua/vim/lsp.lua             | 250 +++++++++++++++++++++++++++++++++++-
 runtime/lua/vim/lsp/client.lua      |  21 +--
 runtime/lua/vim/lsp/health.lua      |  74 ++++++++---
 runtime/lua/vim/shared.lua          |  10 ++
 scripts/gen_vimdoc.lua              |   2 +
 src/nvim/options.lua                |   1 +
 test/functional/plugin/lsp_spec.lua |  93 ++++++++++++--
 11 files changed, 601 insertions(+), 76 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index d9e536b79b..64145ebf11 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -28,31 +28,114 @@ Follow these steps to get LSP features:
    upstream installation instructions. You can find language servers here:
    https://microsoft.github.io/language-server-protocol/implementors/servers/
 
-2. Use |vim.lsp.start()| to start the LSP server (or attach to an existing
-   one) when a file is opened. Example: >lua
-    -- Create an event handler for the FileType autocommand
-    vim.api.nvim_create_autocmd('FileType', {
-      -- This handler will fire when the buffer's 'filetype' is "python"
-      pattern = 'python',
-      callback = function(args)
-        vim.lsp.start({
-          name = 'my-server-name',
-          cmd = {'name-of-language-server-executable', '--option', 'arg1', 'arg2'},
-
-          -- Set the "root directory" to the parent directory of the file in the
-          -- current buffer (`args.buf`) that contains either a "setup.py" or a
-          -- "pyproject.toml" file. Files that share a root directory will reuse
-          -- the connection to the same LSP server.
-          root_dir = vim.fs.root(args.buf, {'setup.py', 'pyproject.toml'}),
-        })
-      end,
-    })
+2. Use |vim.lsp.config()| to define a configuration for an LSP client.
+    Example: >lua
+      vim.lsp.config['luals'] = {
+        -- Command and arguments to start the server.
+        cmd = { 'lua-language-server' }
+
+        -- Filetypes to automatically attach to.
+        filetypes = { 'lua' },
+
+        -- Sets the "root directory" to the parent directory of the file in the
+        -- current buffer that contains either a ".luarc.json" or a
+        -- ".luarc.jsonc" file. Files that share a root directory will reuse
+        -- the connection to the same LSP server.
+        root_markers = { '.luarc.json', '.luarc.jsonc' },
+
+        -- Specific settings to send to the server. The schema for this is
+        -- defined by the server. For example the schema for lua-language-server
+        -- can be found here https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json
+        settings = {
+          Lua = {
+            runtime = {
+              version = 'LuaJIT',
+            }
+          }
+        }
+      }
 <
-3. Check that the buffer is attached to the server: >vim
-    :checkhealth lsp
+3. Use |vim.lsp.enable()| to enable a configuration.
+   Example: >lua
+     vim.lsp.enable('luals')
+<
+4. Check that the buffer is attached to the server: >vim
+    :checkhealth vim.lsp
+<
+5. (Optional) Configure keymaps and autocommands to use LSP features.
+   |lsp-attach|
+
+                                                        *lsp-config*
+
+Configurations for LSP clients is done via |vim.lsp.config()|.
+
+When an LSP client starts, it resolves a configuration by merging
+configurations, in increasing priority, from the following:
 
-4. (Optional) Configure keymaps and autocommands to use LSP features. |lsp-config|
+1. Configuration defined for the `'*'` name.
 
+2. Configuration from the result of sourcing all `lsp/.lua` files
+   in 'runtimepath' for a server of name `name`.
+
+   Note: because of this, calls to |vim.lsp.config()| in `lsp/*.lua` are
+   treated independently to other calls. This ensures configurations
+   defined in `lsp/*.lua` have a lower priority.
+
+3. Configurations defined anywhere else.
+
+Note: The merge semantics of configurations follow the behaviour of
+|vim.tbl_deep_extend()|.
+
+Example:
+
+Given: >lua
+  -- Defined in init.lua
+  vim.lsp.config('*', {
+    capabilities = {
+      textDocument = {
+        semanticTokens = {
+          multilineTokenSupport = true,
+        }
+      }
+    }
+    root_markers = { '.git' },
+  })
+
+  -- Defined in ../lsp/clangd.lua
+  vim.lsp.config('clangd', {
+    cmd = { 'clangd' },
+    root_markers = { '.clangd', 'compile_commands.json' },
+    filetypes = { 'c', 'cpp' },
+  })
+
+  -- Defined in init.lua
+  vim.lsp.config('clangd', {
+    filetypes = { 'c' },
+  })
+<
+Results in the configuration: >lua
+  {
+    -- From the clangd configuration in /lsp/clangd.lua
+    cmd = { 'clangd' },
+
+    -- From the clangd configuration in /lsp/clangd.lua
+    -- Overrides the * configuration in init.lua
+    root_markers = { '.clangd', 'compile_commands.json' },
+
+    -- From the clangd configuration in init.lua
+    -- Overrides the * configuration in init.lua
+    filetypes = { 'c' },
+
+    -- From the * configuration in init.lua
+    capabilities = {
+      textDocument = {
+        semanticTokens = {
+          multilineTokenSupport = true,
+        }
+      }
+    }
+  }
+<
                                                         *lsp-defaults*
 When the Nvim LSP client starts it enables diagnostics |vim.diagnostic| (see
 |vim.diagnostic.config()| to customize). It also sets various default options,
@@ -98,7 +181,7 @@ To override or delete any of the above defaults, set or unset the options on
       end,
     })
 <
-                                                        *lsp-config*
+                                                        *lsp-attach*
 To use other LSP features, set keymaps and other buffer options on
 |LspAttach|. Not all language servers provide the same capabilities. Use
 capability checks to ensure you only use features supported by the language
@@ -107,16 +190,16 @@ server. Example: >lua
     vim.api.nvim_create_autocmd('LspAttach', {
       callback = function(args)
         local client = vim.lsp.get_client_by_id(args.data.client_id)
-        if client.supports_method('textDocument/implementation') then
+        if client:supports_method('textDocument/implementation') then
           -- Create a keymap for vim.lsp.buf.implementation
         end
 
-        if client.supports_method('textDocument/completion') then
+        if client:supports_method('textDocument/completion') then
           -- Enable auto-completion
           vim.lsp.completion.enable(true, client.id, args.buf, {autotrigger = true})
         end
 
-        if client.supports_method('textDocument/formatting') then
+        if client:supports_method('textDocument/formatting') then
           -- Format the current buffer on save
           vim.api.nvim_create_autocmd('BufWritePre', {
             buffer = args.buf,
@@ -465,7 +548,7 @@ EVENTS                                                            *lsp-events*
 LspAttach                                                          *LspAttach*
     After an LSP client attaches to a buffer. The |autocmd-pattern| is the
     name of the buffer. When used from Lua, the client ID is passed to the
-    callback in the "data" table. See |lsp-config| for an example.
+    callback in the "data" table. See |lsp-attach| for an example.
 
 LspDetach                                                          *LspDetach*
     Just before an LSP client detaches from a buffer. The |autocmd-pattern|
@@ -478,7 +561,7 @@ LspDetach                                                          *LspDetach*
         local client = vim.lsp.get_client_by_id(args.data.client_id)
 
         -- Remove the autocommand to format the buffer on save, if it exists
-        if client.supports_method('textDocument/formatting') then
+        if client:supports_method('textDocument/formatting') then
           vim.api.nvim_clear_autocmds({
             event = 'BufWritePre',
             buffer = args.buf,
@@ -590,6 +673,27 @@ LspTokenUpdate                                                *LspTokenUpdate*
 ==============================================================================
 Lua module: vim.lsp                                                 *lsp-core*
 
+*vim.lsp.Config*
+    Extends: |vim.lsp.ClientConfig|
+
+
+    Fields: ~
+      • {cmd}?           (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`)
+                         See `cmd` in |vim.lsp.ClientConfig|.
+      • {filetypes}?     (`string[]`) Filetypes the client will attach to, if
+                         activated by `vim.lsp.enable()`. If not provided,
+                         then the client will attach to all filetypes.
+      • {root_markers}?  (`string[]`) Directory markers (.e.g. '.git/') where
+                         the LSP server will base its workspaceFolders,
+                         rootUri, and rootPath on initialization. Unused if
+                         `root_dir` is provided.
+      • {reuse_client}?  (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`)
+                         Predicate used to decide if a client should be
+                         re-used. Used on all running clients. The default
+                         implementation re-uses a client if name and root_dir
+                         matches.
+
+
 buf_attach_client({bufnr}, {client_id})          *vim.lsp.buf_attach_client()*
     Implements the `textDocument/did…` notifications required to track a
     buffer for any language server.
@@ -689,7 +793,7 @@ commands                                                    *vim.lsp.commands*
     value is a function which is called if any LSP action (code action, code
     lenses, ...) triggers the command.
 
-    If a LSP response contains a command for which no matching entry is
+    If an LSP response contains a command for which no matching entry is
     available in this registry, the command will be executed via the LSP
     server using `workspace/executeCommand`.
 
@@ -698,6 +802,65 @@ commands                                                    *vim.lsp.commands*
 
     The second argument is the `ctx` of |lsp-handler|
 
+config({name}, {cfg})                                       *vim.lsp.config()*
+    Update the configuration for an LSP client.
+
+    Use name '*' to set default configuration for all clients.
+
+    Can also be table-assigned to redefine the configuration for a client.
+
+    Examples:
+    • Add a root marker for all clients: >lua
+        vim.lsp.config('*', {
+          root_markers = { '.git' },
+        })
+<
+    • Add additional capabilities to all clients: >lua
+        vim.lsp.config('*', {
+          capabilities = {
+            textDocument = {
+              semanticTokens = {
+                multilineTokenSupport = true,
+              }
+            }
+          }
+        })
+<
+    • (Re-)define the configuration for clangd: >lua
+        vim.lsp.config.clangd = {
+          cmd = {
+            'clangd',
+            '--clang-tidy',
+            '--background-index',
+            '--offset-encoding=utf-8',
+          },
+          root_markers = { '.clangd', 'compile_commands.json' },
+          filetypes = { 'c', 'cpp' },
+        }
+<
+    • Get configuration for luals: >lua
+        local cfg = vim.lsp.config.luals
+<
+
+    Parameters: ~
+      • {name}  (`string`)
+      • {cfg}   (`vim.lsp.Config`) See |vim.lsp.Config|.
+
+enable({name}, {enable})                                    *vim.lsp.enable()*
+    Enable an LSP server to automatically start when opening a buffer.
+
+    Uses configuration defined with `vim.lsp.config`.
+
+    Examples: >lua
+          vim.lsp.enable('clangd')
+
+          vim.lsp.enable({'luals', 'pyright'})
+<
+
+    Parameters: ~
+      • {name}    (`string|string[]`) Name(s) of client(s) to enable.
+      • {enable}  (`boolean?`) `true|nil` to enable, `false` to disable.
+
 foldclose({kind}, {winid})                               *vim.lsp.foldclose()*
     Close all {kind} of folds in the the window with {winid}.
 
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index ef055161df..71ec84c2f2 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -237,6 +237,9 @@ LSP
 • Functions in |vim.lsp.Client| can now be called as methods.
 • Implemented LSP folding: |vim.lsp.foldexpr()|
   https://microsoft.github.io/language-server-protocol/specification/#textDocument_foldingRange
+• |vim.lsp.config()| has been added to define default configurations for
+  servers. In addition, configurations can be specified in `lsp/.lua`.
+• |vim.lsp.enable()| has been added to enable servers.
 
 LUA
 
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 5763d16cad..6fe208f506 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4810,6 +4810,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 	  indent/	indent scripts |indent-expression|
 	  keymap/	key mapping files |mbyte-keymap|
 	  lang/		menu translations |:menutrans|
+	  lsp/		LSP client configurations |lsp-config|
 	  lua/		|Lua| plugins
 	  menu.vim	GUI menus |menu.vim|
 	  pack/		packages |:packadd|
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 7a8c3a6c29..fecbece655 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -5010,6 +5010,7 @@ vim.go.ruf = vim.go.rulerformat
 ---   indent/	indent scripts `indent-expression`
 ---   keymap/	key mapping files `mbyte-keymap`
 ---   lang/		menu translations `:menutrans`
+---   lsp/		LSP client configurations `lsp-config`
 ---   lua/		`Lua` plugins
 ---   menu.vim	GUI menus `menu.vim`
 ---   pack/		packages `:packadd`
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index ebdc050405..596e1b609b 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -316,6 +316,240 @@ local function create_and_initialize_client(config)
   return client.id, nil
 end
 
+--- @class vim.lsp.Config : vim.lsp.ClientConfig
+---
+--- See `cmd` in [vim.lsp.ClientConfig].
+--- @field cmd? string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
+---
+--- Filetypes the client will attach to, if activated by `vim.lsp.enable()`.
+--- If not provided, then the client will attach to all filetypes.
+--- @field filetypes? string[]
+---
+--- Directory markers (.e.g. '.git/') where the LSP server will base its workspaceFolders,
+--- rootUri, and rootPath on initialization. Unused if `root_dir` is provided.
+--- @field root_markers? string[]
+---
+--- Predicate used to decide if a client should be re-used. Used on all
+--- running clients. The default implementation re-uses a client if name and
+--- root_dir matches.
+--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean
+
+--- Update the configuration for an LSP client.
+---
+--- Use name '*' to set default configuration for all clients.
+---
+--- Can also be table-assigned to redefine the configuration for a client.
+---
+--- Examples:
+---
+--- - Add a root marker for all clients:
+---   ```lua
+---      vim.lsp.config('*', {
+---          root_markers = { '.git' },
+---        })
+---        ```
+--- - Add additional capabilities to all clients:
+---   ```lua
+---      vim.lsp.config('*', {
+---          capabilities = {
+---            textDocument = {
+---              semanticTokens = {
+---                multilineTokenSupport = true,
+---              }
+---            }
+---          }
+---        })
+---        ```
+--- - (Re-)define the configuration for clangd:
+---   ```lua
+---      vim.lsp.config.clangd = {
+---          cmd = {
+---            'clangd',
+---            '--clang-tidy',
+---            '--background-index',
+---            '--offset-encoding=utf-8',
+---          },
+---          root_markers = { '.clangd', 'compile_commands.json' },
+---          filetypes = { 'c', 'cpp' },
+---        }
+---        ```
+--- - Get configuration for luals:
+---   ```lua
+---      local cfg = vim.lsp.config.luals
+---        ```
+---
+--- @param name string
+--- @param cfg vim.lsp.Config
+--- @diagnostic disable-next-line:assign-type-mismatch
+function lsp.config(name, cfg)
+  local _, _ = name, cfg -- ignore unused
+  -- dummy proto for docs
+end
+
+lsp._enabled_configs = {} --- @type table
+
+--- If a config in vim.lsp.config() is accessed then the resolved config becomes invalid.
+--- @param name string
+local function invalidate_enabled_config(name)
+  if name == '*' then
+    for _, v in pairs(lsp._enabled_configs) do
+      v.resolved_config = nil
+    end
+  elseif lsp._enabled_configs[name] then
+    lsp._enabled_configs[name].resolved_config = nil
+  end
+end
+
+--- @nodoc
+--- @class vim.lsp.config
+--- @field [string] vim.lsp.Config
+--- @field package _configs table
+lsp.config = setmetatable({ _configs = {} }, {
+  --- @param self vim.lsp.config
+  --- @param name string
+  --- @return vim.lsp.Config
+  __index = function(self, name)
+    validate('name', name, 'string')
+    invalidate_enabled_config(name)
+    self._configs[name] = self._configs[name] or {}
+    return self._configs[name]
+  end,
+
+  --- @param self vim.lsp.config
+  --- @param name string
+  --- @param cfg vim.lsp.Config
+  __newindex = function(self, name, cfg)
+    validate('name', name, 'string')
+    validate('cfg', cfg, 'table')
+    invalidate_enabled_config(name)
+    self._configs[name] = cfg
+  end,
+
+  --- @param self vim.lsp.config
+  --- @param name string
+  --- @param cfg vim.lsp.Config
+  __call = function(self, name, cfg)
+    validate('name', name, 'string')
+    validate('cfg', cfg, 'table')
+    invalidate_enabled_config(name)
+    self[name] = vim.tbl_deep_extend('force', self._configs[name] or {}, cfg)
+  end,
+})
+
+--- @private
+--- @param name string
+--- @return vim.lsp.Config
+function lsp._resolve_config(name)
+  local econfig = lsp._enabled_configs[name] or {}
+
+  if not econfig.resolved_config then
+    -- Resolve configs from lsp/*.lua
+    -- Calls to vim.lsp.config in lsp/* have a lower precedence than calls from other sites.
+    local orig_configs = lsp.config._configs
+    lsp.config._configs = {}
+    pcall(vim.cmd.runtime, { ('lsp/%s.lua'):format(name), bang = true })
+    local rtp_configs = lsp.config._configs
+    lsp.config._configs = orig_configs
+
+    local config = vim.tbl_deep_extend(
+      'force',
+      lsp.config._configs['*'] or {},
+      rtp_configs[name] or {},
+      lsp.config._configs[name] or {}
+    )
+
+    config.name = name
+
+    validate('cmd', config.cmd, { 'function', 'table' })
+    validate('cmd', config.reuse_client, 'function', true)
+    -- All other fields are validated in client.create
+
+    econfig.resolved_config = config
+  end
+
+  return assert(econfig.resolved_config)
+end
+
+local lsp_enable_autocmd_id --- @type integer?
+
+--- @param bufnr integer
+local function lsp_enable_callback(bufnr)
+  -- Only ever attach to buffers that represent an actual file.
+  if vim.bo[bufnr].buftype ~= '' then
+    return
+  end
+
+  --- @param config vim.lsp.Config
+  local function can_start(config)
+    if config.filetypes and not vim.tbl_contains(config.filetypes, vim.bo[bufnr].filetype) then
+      return false
+    elseif type(config.cmd) == 'table' and vim.fn.executable(config.cmd[1]) == 0 then
+      return false
+    end
+
+    return true
+  end
+
+  for name in vim.spairs(lsp._enabled_configs) do
+    local config = lsp._resolve_config(name)
+
+    if can_start(config) then
+      -- Deepcopy config so changes done in the client
+      -- do not propagate back to the enabled configs.
+      config = vim.deepcopy(config)
+
+      vim.lsp.start(config, {
+        bufnr = bufnr,
+        reuse_client = config.reuse_client,
+        _root_markers = config.root_markers,
+      })
+    end
+  end
+end
+
+--- Enable an LSP server to automatically start when opening a buffer.
+---
+--- Uses configuration defined with `vim.lsp.config`.
+---
+--- Examples:
+---
+--- ```lua
+---   vim.lsp.enable('clangd')
+---
+---   vim.lsp.enable({'luals', 'pyright'})
+--- ```
+---
+--- @param name string|string[] Name(s) of client(s) to enable.
+--- @param enable? boolean `true|nil` to enable, `false` to disable.
+function lsp.enable(name, enable)
+  validate('name', name, { 'string', 'table' })
+
+  local names = vim._ensure_list(name) --[[@as string[] ]]
+  for _, nm in ipairs(names) do
+    if nm == '*' then
+      error('Invalid name')
+    end
+    lsp._enabled_configs[nm] = enable == false and nil or {}
+  end
+
+  if not next(lsp._enabled_configs) then
+    if lsp_enable_autocmd_id then
+      api.nvim_del_autocmd(lsp_enable_autocmd_id)
+      lsp_enable_autocmd_id = nil
+    end
+    return
+  end
+
+  -- Only ever create autocmd once to reuse computation of config merging.
+  lsp_enable_autocmd_id = lsp_enable_autocmd_id
+    or api.nvim_create_autocmd('FileType', {
+      group = api.nvim_create_augroup('nvim.lsp.enable', {}),
+      callback = function(args)
+        lsp_enable_callback(args.buf)
+      end,
+    })
+end
+
 --- @class vim.lsp.start.Opts
 --- @inlinedoc
 ---
@@ -334,6 +568,8 @@ end
 ---
 --- Suppress error reporting if the LSP server fails to start (default false).
 --- @field silent? boolean
+---
+--- @field package _root_markers? string[]
 
 --- Create a new LSP client and start a language server or reuses an already
 --- running client if one is found matching `name` and `root_dir`.
@@ -379,6 +615,11 @@ function lsp.start(config, opts)
   local reuse_client = opts.reuse_client or reuse_client_default
   local bufnr = vim._resolve_bufnr(opts.bufnr)
 
+  if not config.root_dir and opts._root_markers then
+    config = vim.deepcopy(config)
+    config.root_dir = vim.fs.root(bufnr, opts._root_markers)
+  end
+
   for _, client in pairs(all_clients) do
     if reuse_client(client, config) then
       if opts.attach == false then
@@ -387,9 +628,8 @@ function lsp.start(config, opts)
 
       if lsp.buf_attach_client(bufnr, client.id) then
         return client.id
-      else
-        return nil
       end
+      return
     end
   end
 
@@ -398,7 +638,7 @@ function lsp.start(config, opts)
     if not opts.silent then
       vim.notify(err, vim.log.levels.WARN)
     end
-    return nil
+    return
   end
 
   if opts.attach == false then
@@ -408,8 +648,6 @@ function lsp.start(config, opts)
   if client_id and lsp.buf_attach_client(bufnr, client_id) then
     return client_id
   end
-
-  return nil
 end
 
 --- Consumes the latest progress messages from all clients and formats them as a string.
@@ -1275,7 +1513,7 @@ end
 --- and the value is a function which is called if any LSP action
 --- (code action, code lenses, ...) triggers the command.
 ---
---- If a LSP response contains a command for which no matching entry is
+--- If an LSP response contains a command for which no matching entry is
 --- available in this registry, the command will be executed via the LSP server
 --- using `workspace/executeCommand`.
 ---
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index 5eefe4600e..72043c18dd 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -359,16 +359,6 @@ local function get_name(id, config)
   return tostring(id)
 end
 
---- @generic T
---- @param x elem_or_list?
---- @return T[]
-local function ensure_list(x)
-  if type(x) == 'table' then
-    return x
-  end
-  return { x }
-end
-
 --- @nodoc
 --- @param config vim.lsp.ClientConfig
 --- @return vim.lsp.Client?
@@ -395,13 +385,13 @@ function Client.create(config)
     settings = config.settings or {},
     flags = config.flags or {},
     get_language_id = config.get_language_id or default_get_language_id,
-    capabilities = config.capabilities or lsp.protocol.make_client_capabilities(),
+    capabilities = config.capabilities,
     workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir),
     root_dir = config.root_dir,
     _before_init_cb = config.before_init,
-    _on_init_cbs = ensure_list(config.on_init),
-    _on_exit_cbs = ensure_list(config.on_exit),
-    _on_attach_cbs = ensure_list(config.on_attach),
+    _on_init_cbs = vim._ensure_list(config.on_init),
+    _on_exit_cbs = vim._ensure_list(config.on_exit),
+    _on_attach_cbs = vim._ensure_list(config.on_attach),
     _on_error_cb = config.on_error,
     _trace = get_trace(config.trace),
 
@@ -417,6 +407,9 @@ function Client.create(config)
     messages = { name = name, messages = {}, progress = {}, status = {} },
   }
 
+  self.capabilities =
+    vim.tbl_deep_extend('force', lsp.protocol.make_client_capabilities(), self.capabilities or {})
+
   --- @class lsp.DynamicCapabilities
   --- @nodoc
   self.dynamic_capabilities = {
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 0d314108fe..d2cf888d89 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -28,42 +28,45 @@ local function check_log()
   report_fn(string.format('Log size: %d KB', log_size / 1000))
 end
 
+--- @param f function
+--- @return string
+local function func_tostring(f)
+  local info = debug.getinfo(f, 'S')
+  return (''):format(info.source, info.linedefined)
+end
+
 local function check_active_clients()
   vim.health.start('vim.lsp: Active Clients')
   local clients = vim.lsp.get_clients()
   if next(clients) then
     for _, client in pairs(clients) do
       local cmd ---@type string
-      if type(client.config.cmd) == 'table' then
-        cmd = table.concat(client.config.cmd --[[@as table]], ' ')
-      elseif type(client.config.cmd) == 'function' then
-        cmd = tostring(client.config.cmd)
+      local ccmd = client.config.cmd
+      if type(ccmd) == 'table' then
+        cmd = vim.inspect(ccmd)
+      elseif type(ccmd) == 'function' then
+        cmd = func_tostring(ccmd)
       end
       local dirs_info ---@type string
       if client.workspace_folders and #client.workspace_folders > 1 then
-        dirs_info = string.format(
-          '  Workspace folders:\n    %s',
-          vim
-            .iter(client.workspace_folders)
-            ---@param folder lsp.WorkspaceFolder
-            :map(function(folder)
-              return folder.name
-            end)
-            :join('\n    ')
-        )
+        local wfolders = {} --- @type string[]
+        for _, dir in ipairs(client.workspace_folders) do
+          wfolders[#wfolders + 1] = dir.name
+        end
+        dirs_info = ('- Workspace folders:\n    %s'):format(table.concat(wfolders, '\n    '))
       else
         dirs_info = string.format(
-          '  Root directory: %s',
+          '- Root directory: %s',
           client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~')
         ) or nil
       end
       report_info(table.concat({
         string.format('%s (id: %d)', client.name, client.id),
         dirs_info,
-        string.format('  Command: %s', cmd),
-        string.format('  Settings: %s', vim.inspect(client.settings, { newline = '\n  ' })),
+        string.format('- Command: %s', cmd),
+        string.format('- Settings: %s', vim.inspect(client.settings, { newline = '\n  ' })),
         string.format(
-          '  Attached buffers: %s',
+          '- Attached buffers: %s',
           vim.iter(pairs(client.attached_buffers)):map(tostring):join(', ')
         ),
       }, '\n'))
@@ -174,10 +177,45 @@ local function check_position_encodings()
   end
 end
 
+local function check_enabled_configs()
+  vim.health.start('vim.lsp: Enabled Configurations')
+
+  for name in vim.spairs(vim.lsp._enabled_configs) do
+    local config = vim.lsp._resolve_config(name)
+    local text = {} --- @type string[]
+    text[#text + 1] = ('%s:'):format(name)
+    for k, v in
+      vim.spairs(config --[[@as table]])
+    do
+      local v_str --- @type string?
+      if k == 'name' then
+        v_str = nil
+      elseif k == 'filetypes' or k == 'root_markers' then
+        v_str = table.concat(v, ', ')
+      elseif type(v) == 'function' then
+        v_str = func_tostring(v)
+      else
+        v_str = vim.inspect(v, { newline = '\n  ' })
+      end
+
+      if k == 'cmd' and type(v) == 'table' and vim.fn.executable(v[1]) == 0 then
+        report_warn(("'%s' is not executable. Configuration will not be used."):format(v[1]))
+      end
+
+      if v_str then
+        text[#text + 1] = ('- %s: %s'):format(k, v_str)
+      end
+    end
+    text[#text + 1] = ''
+    report_info(table.concat(text, '\n'))
+  end
+end
+
 --- Performs a healthcheck for LSP
 function M.check()
   check_log()
   check_active_clients()
+  check_enabled_configs()
   check_watcher()
   check_position_encodings()
 end
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 0fe8e99350..24c3f243e5 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -1409,4 +1409,14 @@ function vim._resolve_bufnr(bufnr)
   return bufnr
 end
 
+--- @generic T
+--- @param x elem_or_list?
+--- @return T[]
+function vim._ensure_list(x)
+  if type(x) == 'table' then
+    return x
+  end
+  return { x }
+end
+
 return vim
diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua
index 3f870c561f..34f1dc9e38 100755
--- a/scripts/gen_vimdoc.lua
+++ b/scripts/gen_vimdoc.lua
@@ -515,6 +515,8 @@ local function inline_type(obj, classes)
   elseif desc == '' then
     if ty_islist then
       desc = desc .. 'A list of objects with the following fields:'
+    elseif cls.parent then
+      desc = desc .. fmt('Extends |%s| with the additional fields:', cls.parent)
     else
       desc = desc .. 'A table with the following fields:'
     end
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index afce4a918b..de152fb315 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -6755,6 +6755,7 @@ return {
           indent/	indent scripts |indent-expression|
           keymap/	key mapping files |mbyte-keymap|
           lang/		menu translations |:menutrans|
+          lsp/		LSP client configurations |lsp-config|
           lua/		|Lua| plugins
           menu.vim	GUI menus |menu.vim|
           pack/		packages |:packadd|
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index e735e20ff5..79952cb933 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -6098,15 +6098,6 @@ describe('LSP', function()
       end
 
       eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities().
-      eq(false, check_registered(vim.empty_dict()))
-      eq(
-        false,
-        check_registered({
-          workspace = {
-            ignoreMe = true,
-          },
-        })
-      )
       eq(
         false,
         check_registered({
@@ -6129,4 +6120,88 @@ describe('LSP', function()
       )
     end)
   end)
+
+  describe('vim.lsp.config() and vim.lsp.enable()', function()
+    it('can merge settings from "*"', function()
+      eq(
+        {
+          name = 'foo',
+          cmd = { 'foo' },
+          root_markers = { '.git' },
+        },
+        exec_lua(function()
+          vim.lsp.config('*', { root_markers = { '.git' } })
+          vim.lsp.config('foo', { cmd = { 'foo' } })
+
+          return vim.lsp._resolve_config('foo')
+        end)
+      )
+    end)
+
+    it('sets up an autocmd', function()
+      eq(
+        1,
+        exec_lua(function()
+          vim.lsp.config('foo', {
+            cmd = { 'foo' },
+            root_markers = { '.foorc' },
+          })
+          vim.lsp.enable('foo')
+          return #vim.api.nvim_get_autocmds({
+            group = 'nvim.lsp.enable',
+            event = 'FileType',
+          })
+        end)
+      )
+    end)
+
+    it('attaches to buffers', function()
+      exec_lua(create_server_definition)
+
+      local tmp1 = t.tmpname(true)
+      local tmp2 = t.tmpname(true)
+
+      exec_lua(function()
+        local server = _G._create_server({
+          handlers = {
+            initialize = function(_, _, callback)
+              callback(nil, { capabilities = {} })
+            end,
+          },
+        })
+
+        vim.lsp.config('foo', {
+          cmd = server.cmd,
+          filetypes = { 'foo' },
+          root_markers = { '.foorc' },
+        })
+
+        vim.lsp.config('bar', {
+          cmd = server.cmd,
+          filetypes = { 'bar' },
+          root_markers = { '.foorc' },
+        })
+
+        vim.lsp.enable('foo')
+        vim.lsp.enable('bar')
+
+        vim.cmd.edit(tmp1)
+        vim.bo.filetype = 'foo'
+        _G.foo_buf = vim.api.nvim_get_current_buf()
+
+        vim.cmd.edit(tmp2)
+        vim.bo.filetype = 'bar'
+        _G.bar_buf = vim.api.nvim_get_current_buf()
+      end)
+
+      eq(
+        { 1, 'foo', 1, 'bar' },
+        exec_lua(function()
+          local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) })
+          local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) })
+          return { #foos, foos[1].name, #bars, bars[1].name }
+        end)
+      )
+    end)
+  end)
 end)
-- 
cgit 


From 492ae57aa6f8973e939e9b0ade24c44b29fffcbe Mon Sep 17 00:00:00 2001
From: Colin Kennedy 
Date: Wed, 11 Dec 2024 03:27:08 -0800
Subject: docs(annotation): return types for Vimscript functions #31546

---
 runtime/doc/builtin.txt                     | 154 ++++++++++++++--------------
 runtime/lua/vim/_meta/api_keysets_extra.lua |   1 +
 runtime/lua/vim/_meta/vimfn.lua             | 154 ++++++++++++++--------------
 src/nvim/eval.lua                           |  79 +++++++++++++-
 4 files changed, 231 insertions(+), 157 deletions(-)

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index f2942b959b..398947a31f 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1407,7 +1407,7 @@ ctxset({context} [, {index}])                                         *ctxset()*
                   • {index} (`integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 ctxsize()                                                            *ctxsize()*
 		Returns the size of the |context-stack|.
@@ -1618,7 +1618,7 @@ did_filetype()                                                  *did_filetype()*
 		file.
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 diff_filler({lnum})                                              *diff_filler()*
 		Returns the number of filler lines above line {lnum}.
@@ -1633,7 +1633,7 @@ diff_filler({lnum})                                              *diff_filler()*
                   • {lnum} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 diff_hlID({lnum}, {col})                                           *diff_hlID()*
 		Returns the highlight ID for diff mode at line {lnum} column
@@ -1674,7 +1674,7 @@ digraph_get({chars})                                       *digraph_get()* *E121
                   • {chars} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 digraph_getlist([{listall}])                                 *digraph_getlist()*
 		Return a list of digraphs.  If the {listall} argument is given
@@ -1695,7 +1695,7 @@ digraph_getlist([{listall}])                                 *digraph_getlist()*
                   • {listall} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string[][]`)
 
 digraph_set({chars}, {digraph})                                  *digraph_set()*
 		Add digraph {chars} to the list.  {chars} must be a string
@@ -1756,7 +1756,7 @@ empty({expr})                                                          *empty()*
                   • {expr} (`any`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 environ()                                                            *environ()*
 		Return all of environment variables as dictionary. You can
@@ -1783,7 +1783,7 @@ escape({string}, {chars})                                             *escape()*
                   • {chars} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 eval({string})                                                          *eval()*
 		Evaluate {string} and return the result.  Especially useful to
@@ -2666,7 +2666,7 @@ foreach({expr1}, {expr2})                                            *foreach()*
                   • {expr2} (`string|function`)
 
                 Return: ~
-                  (`any`)
+                  (`string|table`)
 
 fullcommand({name})                                              *fullcommand()*
 		Get the full command name from a short abbreviated command
@@ -3000,7 +3000,7 @@ getbufline({buf}, {lnum} [, {end}])                               *getbufline()*
                   • {end} (`integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`string[]`)
 
 getbufoneline({buf}, {lnum})                                   *getbufoneline()*
 		Just like `getbufline()` but only get one line and return it
@@ -3295,7 +3295,7 @@ getcmdscreenpos()                                            *getcmdscreenpos()*
 		|setcmdline()|.
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 getcmdtype()                                                      *getcmdtype()*
 		Return the current command-line type. Possible return values
@@ -4299,7 +4299,7 @@ gettext({text})                                                      *gettext()*
                   • {text} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 getwininfo([{winid}])                                             *getwininfo()*
 		Returns information about windows as a |List| with Dictionaries.
@@ -4467,7 +4467,7 @@ glob2regpat({string})                                            *glob2regpat()*
                   • {string} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]])       *globpath()*
 		Perform glob() for String {expr} on all directories in {path}
@@ -4824,7 +4824,7 @@ iconv({string}, {from}, {to})                                          *iconv()*
                   • {to} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 id({expr})                                                                *id()*
 		Returns a |String| which is a unique identifier of the
@@ -4847,7 +4847,7 @@ id({expr})                                                                *id()*
                   • {expr} (`any`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 indent({lnum})                                                        *indent()*
 		The result is a Number, which is indent of line {lnum} in the
@@ -4897,7 +4897,7 @@ index({object}, {expr} [, {start} [, {ic}]])                           *index()*
                   • {ic} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 indexof({object}, {expr} [, {opts}])                                 *indexof()*
 		Returns the index of an item in {object} where {expr} is
@@ -4944,7 +4944,7 @@ indexof({object}, {expr} [, {opts}])                                 *indexof()*
                   • {opts} (`table?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 input({prompt} [, {text} [, {completion}]])                            *input()*
 
@@ -4954,7 +4954,7 @@ input({prompt} [, {text} [, {completion}]])                            *input()*
                   • {completion} (`string?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 input({opts})
 		The result is a String, which is whatever the user typed on
@@ -5071,7 +5071,7 @@ input({opts})
                   • {opts} (`table`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 inputlist({textlist})                                              *inputlist()*
 		{textlist} must be a |List| of strings.  This |List| is
@@ -5103,7 +5103,7 @@ inputrestore()                                                  *inputrestore()*
 		Returns TRUE when there is nothing to restore, FALSE otherwise.
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 inputsave()                                                        *inputsave()*
 		Preserve typeahead (also from mappings) and clear it, so that
@@ -5114,7 +5114,7 @@ inputsave()                                                        *inputsave()*
 		Returns TRUE when out of memory, FALSE otherwise.
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 inputsecret({prompt} [, {text}])                                 *inputsecret()*
 		This function acts much like the |input()| function with but
@@ -5132,7 +5132,7 @@ inputsecret({prompt} [, {text}])                                 *inputsecret()*
                   • {text} (`string?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 insert({object}, {item} [, {idx}])                                    *insert()*
 		When {object} is a |List| or a |Blob| insert {item} at the start
@@ -5183,10 +5183,10 @@ invert({expr})                                                        *invert()*
 <
 
                 Parameters: ~
-                  • {expr} (`number`)
+                  • {expr} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 isabsolutepath({path})                                        *isabsolutepath()*
 		The result is a Number, which is |TRUE| when {path} is an
@@ -5399,7 +5399,7 @@ jobstart({cmd} [, {opts}])                                          *jobstart()*
                   • {opts} (`table?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 jobstop({id})                                                        *jobstop()*
 		Stop |job-id| {id} by sending SIGTERM to the job process. If
@@ -5415,7 +5415,7 @@ jobstop({id})                                                        *jobstop()*
                   • {id} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 jobwait({jobs} [, {timeout}])                                        *jobwait()*
 		Waits for jobs and their |on_exit| handlers to complete.
@@ -5461,7 +5461,7 @@ join({list} [, {sep}])                                                  *join()*
                   • {sep} (`string?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 json_decode({expr})                                              *json_decode()*
 		Convert {expr} from JSON object.  Accepts |readfile()|-style
@@ -5500,7 +5500,7 @@ json_encode({expr})                                              *json_encode()*
                   • {expr} (`any`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 keys({dict})                                                            *keys()*
 		Return a |List| with all the keys of {dict}.  The |List| is in
@@ -5510,7 +5510,7 @@ keys({dict})                                                            *keys()*
                   • {dict} (`table`)
 
                 Return: ~
-                  (`any`)
+                  (`string[]`)
 
 keytrans({string})                                                  *keytrans()*
 		Turn the internal byte representation of keys into a form that
@@ -5523,7 +5523,7 @@ keytrans({string})                                                  *keytrans()*
                   • {string} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 len({expr})                                                         *len()* *E701*
 		The result is a Number, which is the length of the argument.
@@ -5537,10 +5537,10 @@ len({expr})                                                         *len()* *E70
 		Otherwise an error is given and returns zero.
 
                 Parameters: ~
-                  • {expr} (`any`)
+                  • {expr} (`any[]`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 libcall({libname}, {funcname}, {argument})                 *libcall()* *E364* *E368*
 		Call function {funcname} in the run-time library {libname}
@@ -5666,7 +5666,7 @@ lispindent({lnum})                                                *lispindent()*
                   • {lnum} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 list2blob({list})                                                  *list2blob()*
 		Return a Blob concatenating all the number values in {list}.
@@ -5682,7 +5682,7 @@ list2blob({list})                                                  *list2blob()*
                   • {list} (`any[]`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 list2str({list} [, {utf8}])                                         *list2str()*
 		Convert each number in {list} to a character string can
@@ -5705,14 +5705,14 @@ list2str({list} [, {utf8}])                                         *list2str()*
                   • {utf8} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 localtime()                                                        *localtime()*
 		Return the current time, measured as seconds since 1st Jan
 		1970.  See also |strftime()|, |strptime()| and |getftime()|.
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 log({expr})                                                              *log()*
 		Return the natural logarithm (base e) of {expr} as a |Float|.
@@ -5729,7 +5729,7 @@ log({expr})                                                              *log()*
                   • {expr} (`number`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 log10({expr})                                                          *log10()*
 		Return the logarithm of Float {expr} to base 10 as a |Float|.
@@ -5745,7 +5745,7 @@ log10({expr})                                                          *log10()*
                   • {expr} (`number`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 luaeval({expr} [, {expr}])                                           *luaeval()*
 		Evaluate Lua expression {expr} and return its result converted
@@ -6714,7 +6714,7 @@ min({expr})                                                              *min()*
                   • {expr} (`any`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 mkdir({name} [, {flags} [, {prot}]])                              *mkdir()* *E739*
 		Create directory {name}.
@@ -6762,7 +6762,7 @@ mkdir({name} [, {flags} [, {prot}]])                              *mkdir()* *E73
                   • {prot} (`string?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 mode([{expr}])                                                          *mode()*
 		Return a string that indicates the current mode.
@@ -6937,7 +6937,7 @@ nextnonblank({lnum})                                            *nextnonblank()*
                   • {lnum} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 nr2char({expr} [, {utf8}])                                           *nr2char()*
 		Return a string with a single character, which has the number
@@ -6959,7 +6959,7 @@ nr2char({expr} [, {utf8}])                                           *nr2char()*
                   • {utf8} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 nvim_...({...})                                      *nvim_...()* *E5555* *eval-api*
 		Call nvim |api| functions. The type checking of arguments will
@@ -7016,7 +7016,7 @@ pathshorten({path} [, {len}])                                    *pathshorten()*
                   • {len} (`integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 perleval({expr})                                                    *perleval()*
 		Evaluate |perl| expression {expr} and return its result
@@ -7056,7 +7056,7 @@ pow({x}, {y})                                                            *pow()*
                   • {y} (`number`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 prevnonblank({lnum})                                            *prevnonblank()*
 		Return the line number of the first line at or above {lnum}
@@ -7071,7 +7071,7 @@ prevnonblank({lnum})                                            *prevnonblank()*
                   • {lnum} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 printf({fmt}, {expr1} ...)                                            *printf()*
 		Return a String with {fmt}, where "%" items are replaced by
@@ -7901,7 +7901,7 @@ rename({from}, {to})                                                  *rename()*
                   • {to} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 repeat({expr}, {count})                                               *repeat()*
 		Repeat {expr} {count} times and return the concatenated
@@ -7937,7 +7937,7 @@ resolve({filename})                                             *resolve()* *E65
                   • {filename} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 reverse({object})                                                    *reverse()*
 		Reverse the order of items in {object}.  {object} can be a
@@ -7974,7 +7974,7 @@ round({expr})                                                          *round()*
                   • {expr} (`number`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 rpcnotify({channel}, {event} [, {args}...])                        *rpcnotify()*
 		Sends {event} to {channel} via |RPC| and returns immediately.
@@ -7989,7 +7989,7 @@ rpcnotify({channel}, {event} [, {args}...])                        *rpcnotify()*
                   • {...} (`any`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 rpcrequest({channel}, {method} [, {args}...])                     *rpcrequest()*
 		Sends a request to {channel} to invoke {method} via
@@ -8033,7 +8033,7 @@ screenattr({row}, {col})                                          *screenattr()*
                   • {col} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 screenchar({row}, {col})                                          *screenchar()*
 		The result is a Number, which is the character at position
@@ -8050,7 +8050,7 @@ screenchar({row}, {col})                                          *screenchar()*
                   • {col} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 screenchars({row}, {col})                                        *screenchars()*
 		The result is a |List| of Numbers.  The first number is the same
@@ -8064,7 +8064,7 @@ screenchars({row}, {col})                                        *screenchars()*
                   • {col} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer[]`)
 
 screencol()                                                        *screencol()*
 		The result is a Number, which is the current screen column of
@@ -8082,7 +8082,7 @@ screencol()                                                        *screencol()*
 <
 
                 Return: ~
-                  (`any`)
+                  (`integer[]`)
 
 screenpos({winid}, {lnum}, {col})                                  *screenpos()*
 		The result is a Dict with the screen position of the text
@@ -8125,7 +8125,7 @@ screenrow()                                                        *screenrow()*
 		Note: Same restrictions as with |screencol()|.
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 screenstring({row}, {col})                                      *screenstring()*
 		The result is a String that contains the base character and
@@ -8140,7 +8140,7 @@ screenstring({row}, {col})                                      *screenstring()*
                   • {col} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) *search()*
 		Search for regexp pattern {pattern}.  The search starts at the
@@ -8567,7 +8567,7 @@ serverlist()                                                      *serverlist()*
 <
 
                 Return: ~
-                  (`any`)
+                  (`string[]`)
 
 serverstart([{address}])                                         *serverstart()*
 		Opens a socket or named pipe at {address} and listens for
@@ -8607,7 +8607,7 @@ serverstart([{address}])                                         *serverstart()*
                   • {address} (`string?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 serverstop({address})                                             *serverstop()*
 		Closes the pipe or socket at {address}.
@@ -8619,7 +8619,7 @@ serverstop({address})                                             *serverstop()*
                   • {address} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 setbufline({buf}, {lnum}, {text})                                 *setbufline()*
 		Set line {lnum} to {text} in buffer {buf}.  This works like
@@ -8652,7 +8652,7 @@ setbufline({buf}, {lnum}, {text})                                 *setbufline()*
                   • {text} (`string|string[]`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 setbufvar({buf}, {varname}, {val})                                 *setbufvar()*
 		Set option or local variable {varname} in buffer {buf} to
@@ -8772,7 +8772,7 @@ setcmdline({str} [, {pos}])                                       *setcmdline()*
                   • {pos} (`integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 setcmdpos({pos})                                                   *setcmdpos()*
 		Set the cursor position in the command line to byte position
@@ -9104,7 +9104,7 @@ setqflist({list} [, {action} [, {what}]])                          *setqflist()*
                   • {what} (`vim.fn.setqflist.what?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 setreg({regname}, {value} [, {options}])                              *setreg()*
 		Set the register {regname} to {value}.
@@ -9275,7 +9275,7 @@ sha256({string})                                                      *sha256()*
                   • {string} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 shellescape({string} [, {special}])                              *shellescape()*
 		Escape {string} for use as a shell command argument.
@@ -9314,7 +9314,7 @@ shellescape({string} [, {special}])                              *shellescape()*
                   • {special} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 shiftwidth([{col}])                                               *shiftwidth()*
 		Returns the effective value of 'shiftwidth'. This is the
@@ -9792,7 +9792,7 @@ simplify({filename})                                                *simplify()*
                   • {filename} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 sin({expr})                                                              *sin()*
 		Return the sine of {expr}, measured in radians, as a |Float|.
@@ -9808,7 +9808,7 @@ sin({expr})                                                              *sin()*
                   • {expr} (`number`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 sinh({expr})                                                            *sinh()*
 		Return the hyperbolic sine of {expr} as a |Float| in the range
@@ -9971,7 +9971,7 @@ soundfold({word})                                                  *soundfold()*
                   • {word} (`string`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 spellbadword([{sentence}])                                      *spellbadword()*
 		Without argument: The result is the badly spelled word under
@@ -10030,7 +10030,7 @@ spellsuggest({word} [, {max} [, {capital}]])                    *spellsuggest()*
                   • {capital} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string[]`)
 
 split({string} [, {pattern} [, {keepempty}]])                          *split()*
 		Make a |List| out of {string}.  When {pattern} is omitted or
@@ -10063,7 +10063,7 @@ split({string} [, {pattern} [, {keepempty}]])                          *split()*
                   • {keepempty} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string[]`)
 
 sqrt({expr})                                                            *sqrt()*
 		Return the non-negative square root of Float {expr} as a
@@ -11188,7 +11188,7 @@ termopen({cmd} [, {opts}])                                          *termopen()*
                   • {opts} (`table?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 test_garbagecollect_now()                            *test_garbagecollect_now()*
 		Like |garbagecollect()|, but executed right away.  This must
@@ -11649,7 +11649,7 @@ virtcol2col({winid}, {lnum}, {col})                              *virtcol2col()*
                   • {col} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 visualmode([{expr}])                                              *visualmode()*
 		The result is a String, which describes the last Visual mode
@@ -11673,7 +11673,7 @@ visualmode([{expr}])                                              *visualmode()*
                   • {expr} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 wait({timeout}, {condition} [, {interval}])                             *wait()*
 		Waits until {condition} evaluates to |TRUE|, where {condition}
@@ -11815,7 +11815,7 @@ win_id2win({expr})                                                *win_id2win()*
                   • {expr} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 win_move_separator({nr}, {offset})                        *win_move_separator()*
 		Move window {nr}'s vertical separator (i.e., the right border)
@@ -12040,7 +12040,7 @@ winnr([{arg}])                                                         *winnr()*
                   • {arg} (`string|integer?`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 winrestcmd()                                                      *winrestcmd()*
 		Returns a sequence of |:resize| commands that should restore
@@ -12054,7 +12054,7 @@ winrestcmd()                                                      *winrestcmd()*
 <
 
                 Return: ~
-                  (`any`)
+                  (`string`)
 
 winrestview({dict})                                              *winrestview()*
 		Uses the |Dictionary| returned by |winsaveview()| to restore
@@ -12126,7 +12126,7 @@ winwidth({nr})                                                      *winwidth()*
                   • {nr} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 wordcount()                                                        *wordcount()*
 		The result is a dictionary of byte/chars/word statistics for
@@ -12216,11 +12216,11 @@ xor({expr}, {expr})                                                      *xor()*
 <
 
                 Parameters: ~
-                  • {expr} (`number`)
-                  • {expr1} (`number`)
+                  • {expr} (`integer`)
+                  • {expr1} (`integer`)
 
                 Return: ~
-                  (`any`)
+                  (`integer`)
 
 ==============================================================================
 2. Matching a pattern in a String			*string-match*
diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua
index e8e901acb2..806b3e49c0 100644
--- a/runtime/lua/vim/_meta/api_keysets_extra.lua
+++ b/runtime/lua/vim/_meta/api_keysets_extra.lua
@@ -159,6 +159,7 @@ error('Cannot require a meta file')
 --- @field bg? integer
 --- @field sp? integer
 --- @field default? true
+--- @field link? string
 --- @field blend? integer
 --- @field cterm? vim.api.keyset.hl_info.cterm
 
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index e207f641de..77c78f03d4 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -1228,7 +1228,7 @@ function vim.fn.ctxpush(types) end
 ---
 --- @param context table
 --- @param index? integer
---- @return any
+--- @return integer
 function vim.fn.ctxset(context, index) end
 
 --- Returns the size of the |context-stack|.
@@ -1421,7 +1421,7 @@ function vim.fn.dictwatcherdel(dict, pattern, callback) end
 --- editing another buffer to set 'filetype' and load a syntax
 --- file.
 ---
---- @return any
+--- @return integer
 function vim.fn.did_filetype() end
 
 --- Returns the number of filler lines above line {lnum}.
@@ -1433,7 +1433,7 @@ function vim.fn.did_filetype() end
 --- Returns 0 if the current window is not in diff mode.
 ---
 --- @param lnum integer
---- @return any
+--- @return integer
 function vim.fn.diff_filler(lnum) end
 
 --- Returns the highlight ID for diff mode at line {lnum} column
@@ -1468,7 +1468,7 @@ function vim.fn.diff_hlID(lnum, col) end
 --- <
 ---
 --- @param chars string
---- @return any
+--- @return string
 function vim.fn.digraph_get(chars) end
 
 --- Return a list of digraphs.  If the {listall} argument is given
@@ -1486,7 +1486,7 @@ function vim.fn.digraph_get(chars) end
 --- <
 ---
 --- @param listall? boolean
---- @return any
+--- @return string[][]
 function vim.fn.digraph_getlist(listall) end
 
 --- Add digraph {chars} to the list.  {chars} must be a string
@@ -1538,7 +1538,7 @@ function vim.fn.digraph_setlist(digraphlist) end
 --- - A |Blob| is empty when its length is zero.
 ---
 --- @param expr any
---- @return any
+--- @return integer
 function vim.fn.empty(expr) end
 
 --- Return all of environment variables as dictionary. You can
@@ -1561,7 +1561,7 @@ function vim.fn.environ() end
 ---
 --- @param string string
 --- @param chars string
---- @return any
+--- @return string
 function vim.fn.escape(string, chars) end
 
 --- Evaluate {string} and return the result.  Especially useful to
@@ -2368,7 +2368,7 @@ function vim.fn.foldtextresult(lnum) end
 ---
 --- @param expr1 string|table
 --- @param expr2 string|function
---- @return any
+--- @return string|table
 function vim.fn.foreach(expr1, expr2) end
 
 --- Get the full command name from a short abbreviated command
@@ -2675,7 +2675,7 @@ function vim.fn.getbufinfo(dict) end
 --- @param buf integer|string
 --- @param lnum integer
 --- @param end_? integer
---- @return any
+--- @return string[]
 function vim.fn.getbufline(buf, lnum, end_) end
 
 --- Just like `getbufline()` but only get one line and return it
@@ -2943,7 +2943,7 @@ function vim.fn.getcmdprompt() end
 --- Also see |getcmdpos()|, |setcmdpos()|, |getcmdline()| and
 --- |setcmdline()|.
 ---
---- @return any
+--- @return integer
 function vim.fn.getcmdscreenpos() end
 
 --- Return the current command-line type. Possible return values
@@ -3869,7 +3869,7 @@ function vim.fn.gettagstack(winnr) end
 --- strings.
 ---
 --- @param text string
---- @return any
+--- @return string
 function vim.fn.gettext(text) end
 
 --- Returns information about windows as a |List| with Dictionaries.
@@ -4020,7 +4020,7 @@ function vim.fn.glob(expr, nosuf, list, alllinks) end
 --- a backslash usually means a path separator.
 ---
 --- @param string string
---- @return any
+--- @return string
 function vim.fn.glob2regpat(string) end
 
 --- Perform glob() for String {expr} on all directories in {path}
@@ -4354,7 +4354,7 @@ function vim.fn.hostname() end
 --- @param string string
 --- @param from string
 --- @param to string
---- @return any
+--- @return string
 function vim.fn.iconv(string, from, to) end
 
 --- Returns a |String| which is a unique identifier of the
@@ -4374,7 +4374,7 @@ function vim.fn.iconv(string, from, to) end
 --- reuse identifiers of the garbage-collected ones.
 ---
 --- @param expr any
---- @return any
+--- @return string
 function vim.fn.id(expr) end
 
 --- The result is a Number, which is indent of line {lnum} in the
@@ -4418,7 +4418,7 @@ function vim.fn.indent(lnum) end
 --- @param expr any
 --- @param start? integer
 --- @param ic? boolean
---- @return any
+--- @return integer
 function vim.fn.index(object, expr, start, ic) end
 
 --- Returns the index of an item in {object} where {expr} is
@@ -4462,14 +4462,14 @@ function vim.fn.index(object, expr, start, ic) end
 --- @param object any
 --- @param expr any
 --- @param opts? table
---- @return any
+--- @return integer
 function vim.fn.indexof(object, expr, opts) end
 
 ---
 --- @param prompt string
 --- @param text? string
 --- @param completion? string
---- @return any
+--- @return string
 function vim.fn.input(prompt, text, completion) end
 
 --- The result is a String, which is whatever the user typed on
@@ -4583,7 +4583,7 @@ function vim.fn.input(prompt, text, completion) end
 --- <
 ---
 --- @param opts table
---- @return any
+--- @return string
 function vim.fn.input(opts) end
 
 --- @deprecated
@@ -4618,7 +4618,7 @@ function vim.fn.inputlist(textlist) end
 --- called.  Calling it more often is harmless though.
 --- Returns TRUE when there is nothing to restore, FALSE otherwise.
 ---
---- @return any
+--- @return integer
 function vim.fn.inputrestore() end
 
 --- Preserve typeahead (also from mappings) and clear it, so that
@@ -4628,7 +4628,7 @@ function vim.fn.inputrestore() end
 --- many inputrestore() calls.
 --- Returns TRUE when out of memory, FALSE otherwise.
 ---
---- @return any
+--- @return integer
 function vim.fn.inputsave() end
 
 --- This function acts much like the |input()| function with but
@@ -4643,7 +4643,7 @@ function vim.fn.inputsave() end
 ---
 --- @param prompt string
 --- @param text? string
---- @return any
+--- @return string
 function vim.fn.inputsecret(prompt, text) end
 
 --- When {object} is a |List| or a |Blob| insert {item} at the start
@@ -4689,8 +4689,8 @@ function vim.fn.interrupt() end
 ---   let bits = invert(bits)
 --- <
 ---
---- @param expr number
---- @return any
+--- @param expr integer
+--- @return integer
 function vim.fn.invert(expr) end
 
 --- The result is a Number, which is |TRUE| when {path} is an
@@ -4890,7 +4890,7 @@ function vim.fn.jobsend(...) end
 ---
 --- @param cmd string|string[]
 --- @param opts? table
---- @return any
+--- @return integer
 function vim.fn.jobstart(cmd, opts) end
 
 --- Stop |job-id| {id} by sending SIGTERM to the job process. If
@@ -4903,7 +4903,7 @@ function vim.fn.jobstart(cmd, opts) end
 --- exited or stopped.
 ---
 --- @param id integer
---- @return any
+--- @return integer
 function vim.fn.jobstop(id) end
 
 --- Waits for jobs and their |on_exit| handlers to complete.
@@ -4943,7 +4943,7 @@ function vim.fn.jobwait(jobs, timeout) end
 ---
 --- @param list any[]
 --- @param sep? string
---- @return any
+--- @return string
 function vim.fn.join(list, sep) end
 
 --- Convert {expr} from JSON object.  Accepts |readfile()|-style
@@ -4976,14 +4976,14 @@ function vim.fn.json_decode(expr) end
 --- |Blob|s are converted to arrays of the individual bytes.
 ---
 --- @param expr any
---- @return any
+--- @return string
 function vim.fn.json_encode(expr) end
 
 --- Return a |List| with all the keys of {dict}.  The |List| is in
 --- arbitrary order.  Also see |items()| and |values()|.
 ---
 --- @param dict table
---- @return any
+--- @return string[]
 function vim.fn.keys(dict) end
 
 --- Turn the internal byte representation of keys into a form that
@@ -4993,7 +4993,7 @@ function vim.fn.keys(dict) end
 --- <  
 ---
 --- @param string string
---- @return any
+--- @return string
 function vim.fn.keytrans(string) end
 
 --- @deprecated
@@ -5012,8 +5012,8 @@ function vim.fn.last_buffer_nr() end
 --- |Dictionary| is returned.
 --- Otherwise an error is given and returns zero.
 ---
---- @param expr any
---- @return any
+--- @param expr any[]
+--- @return integer
 function vim.fn.len(expr) end
 
 --- Call function {funcname} in the run-time library {libname}
@@ -5124,7 +5124,7 @@ function vim.fn.line2byte(lnum) end
 --- When {lnum} is invalid, -1 is returned.
 ---
 --- @param lnum integer
---- @return any
+--- @return integer
 function vim.fn.lispindent(lnum) end
 
 --- Return a Blob concatenating all the number values in {list}.
@@ -5137,7 +5137,7 @@ function vim.fn.lispindent(lnum) end
 --- |blob2list()| does the opposite.
 ---
 --- @param list any[]
---- @return any
+--- @return string
 function vim.fn.list2blob(list) end
 
 --- Convert each number in {list} to a character string can
@@ -5157,13 +5157,13 @@ function vim.fn.list2blob(list) end
 ---
 --- @param list any[]
 --- @param utf8? boolean
---- @return any
+--- @return string
 function vim.fn.list2str(list, utf8) end
 
 --- Return the current time, measured as seconds since 1st Jan
 --- 1970.  See also |strftime()|, |strptime()| and |getftime()|.
 ---
---- @return any
+--- @return integer
 function vim.fn.localtime() end
 
 --- Return the natural logarithm (base e) of {expr} as a |Float|.
@@ -5177,7 +5177,7 @@ function vim.fn.localtime() end
 --- <  5.0
 ---
 --- @param expr number
---- @return any
+--- @return number
 function vim.fn.log(expr) end
 
 --- Return the logarithm of Float {expr} to base 10 as a |Float|.
@@ -5190,7 +5190,7 @@ function vim.fn.log(expr) end
 --- <  -2.0
 ---
 --- @param expr number
---- @return any
+--- @return number
 function vim.fn.log10(expr) end
 
 --- {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
@@ -6090,7 +6090,7 @@ function vim.fn.menu_info(name, mode) end
 --- an error.  An empty |List| or |Dictionary| results in zero.
 ---
 --- @param expr any
---- @return any
+--- @return number
 function vim.fn.min(expr) end
 
 --- Create directory {name}.
@@ -6135,7 +6135,7 @@ function vim.fn.min(expr) end
 --- @param name string
 --- @param flags? string
 --- @param prot? string
---- @return any
+--- @return integer
 function vim.fn.mkdir(name, flags, prot) end
 
 --- Return a string that indicates the current mode.
@@ -6298,7 +6298,7 @@ function vim.fn.msgpackparse(data) end
 --- See also |prevnonblank()|.
 ---
 --- @param lnum integer
---- @return any
+--- @return integer
 function vim.fn.nextnonblank(lnum) end
 
 --- Return a string with a single character, which has the number
@@ -6317,7 +6317,7 @@ function vim.fn.nextnonblank(lnum) end
 ---
 --- @param expr integer
 --- @param utf8? boolean
---- @return any
+--- @return string
 function vim.fn.nr2char(expr, utf8) end
 
 --- Bitwise OR on the two arguments.  The arguments are converted
@@ -6351,7 +6351,7 @@ vim.fn['or'] = function(expr, expr1) end
 ---
 --- @param path string
 --- @param len? integer
---- @return any
+--- @return string
 function vim.fn.pathshorten(path, len) end
 
 --- Evaluate |perl| expression {expr} and return its result
@@ -6385,7 +6385,7 @@ function vim.fn.perleval(expr) end
 ---
 --- @param x number
 --- @param y number
---- @return any
+--- @return number
 function vim.fn.pow(x, y) end
 
 --- Return the line number of the first line at or above {lnum}
@@ -6397,7 +6397,7 @@ function vim.fn.pow(x, y) end
 --- Also see |nextnonblank()|.
 ---
 --- @param lnum integer
---- @return any
+--- @return integer
 function vim.fn.prevnonblank(lnum) end
 
 --- Return a String with {fmt}, where "%" items are replaced by
@@ -7172,7 +7172,7 @@ function vim.fn.remove(dict, key) end
 ---
 --- @param from string
 --- @param to string
---- @return any
+--- @return integer
 function vim.fn.rename(from, to) end
 
 --- Repeat {expr} {count} times and return the concatenated
@@ -7202,7 +7202,7 @@ vim.fn['repeat'] = function(expr, count) end
 --- path name) and also keeps a trailing path separator.
 ---
 --- @param filename string
---- @return any
+--- @return string
 function vim.fn.resolve(filename) end
 
 --- Reverse the order of items in {object}.  {object} can be a
@@ -7233,7 +7233,7 @@ function vim.fn.reverse(object) end
 --- <  -5.0
 ---
 --- @param expr number
---- @return any
+--- @return number
 function vim.fn.round(expr) end
 
 --- Sends {event} to {channel} via |RPC| and returns immediately.
@@ -7245,7 +7245,7 @@ function vim.fn.round(expr) end
 --- @param channel integer
 --- @param event string
 --- @param ... any
---- @return any
+--- @return integer
 function vim.fn.rpcnotify(channel, event, ...) end
 
 --- Sends a request to {channel} to invoke {method} via
@@ -7302,7 +7302,7 @@ function vim.fn.rubyeval(expr) end
 ---
 --- @param row integer
 --- @param col integer
---- @return any
+--- @return integer
 function vim.fn.screenattr(row, col) end
 
 --- The result is a Number, which is the character at position
@@ -7316,7 +7316,7 @@ function vim.fn.screenattr(row, col) end
 ---
 --- @param row integer
 --- @param col integer
---- @return any
+--- @return integer
 function vim.fn.screenchar(row, col) end
 
 --- The result is a |List| of Numbers.  The first number is the same
@@ -7327,7 +7327,7 @@ function vim.fn.screenchar(row, col) end
 ---
 --- @param row integer
 --- @param col integer
---- @return any
+--- @return integer[]
 function vim.fn.screenchars(row, col) end
 
 --- The result is a Number, which is the current screen column of
@@ -7344,7 +7344,7 @@ function vim.fn.screenchars(row, col) end
 ---   noremap GG echom screencol()
 --- <
 ---
---- @return any
+--- @return integer[]
 function vim.fn.screencol() end
 
 --- The result is a Dict with the screen position of the text
@@ -7383,7 +7383,7 @@ function vim.fn.screenpos(winid, lnum, col) end
 ---
 --- Note: Same restrictions as with |screencol()|.
 ---
---- @return any
+--- @return integer
 function vim.fn.screenrow() end
 
 --- The result is a String that contains the base character and
@@ -7395,7 +7395,7 @@ function vim.fn.screenrow() end
 ---
 --- @param row integer
 --- @param col integer
---- @return any
+--- @return string
 function vim.fn.screenstring(row, col) end
 
 --- Search for regexp pattern {pattern}.  The search starts at the
@@ -7800,7 +7800,7 @@ function vim.fn.searchpos(pattern, flags, stopline, timeout, skip) end
 ---   echo serverlist()
 --- <
 ---
---- @return any
+--- @return string[]
 function vim.fn.serverlist() end
 
 --- Opens a socket or named pipe at {address} and listens for
@@ -7837,7 +7837,7 @@ function vim.fn.serverlist() end
 --- <
 ---
 --- @param address? string
---- @return any
+--- @return string
 function vim.fn.serverstart(address) end
 
 --- Closes the pipe or socket at {address}.
@@ -7846,7 +7846,7 @@ function vim.fn.serverstart(address) end
 --- address in |serverlist()|.
 ---
 --- @param address string
---- @return any
+--- @return integer
 function vim.fn.serverstop(address) end
 
 --- Set line {lnum} to {text} in buffer {buf}.  This works like
@@ -7876,7 +7876,7 @@ function vim.fn.serverstop(address) end
 --- @param buf integer|string
 --- @param lnum integer
 --- @param text string|string[]
---- @return any
+--- @return integer
 function vim.fn.setbufline(buf, lnum, text) end
 
 --- Set option or local variable {varname} in buffer {buf} to
@@ -7981,7 +7981,7 @@ function vim.fn.setcharsearch(dict) end
 ---
 --- @param str string
 --- @param pos? integer
---- @return any
+--- @return integer
 function vim.fn.setcmdline(str, pos) end
 
 --- Set the cursor position in the command line to byte position
@@ -8291,7 +8291,7 @@ function vim.fn.setpos(expr, list) end
 --- @param list vim.quickfix.entry[]
 --- @param action? string
 --- @param what? vim.fn.setqflist.what
---- @return any
+--- @return integer
 function vim.fn.setqflist(list, action, what) end
 
 --- Set the register {regname} to {value}.
@@ -8444,7 +8444,7 @@ function vim.fn.setwinvar(nr, varname, val) end
 --- checksum of {string}.
 ---
 --- @param string string
---- @return any
+--- @return string
 function vim.fn.sha256(string) end
 
 --- Escape {string} for use as a shell command argument.
@@ -8480,7 +8480,7 @@ function vim.fn.sha256(string) end
 ---
 --- @param string string
 --- @param special? boolean
---- @return any
+--- @return string
 function vim.fn.shellescape(string, special) end
 
 --- Returns the effective value of 'shiftwidth'. This is the
@@ -8932,7 +8932,7 @@ function vim.fn.sign_unplacelist(list) end
 --- links before simplifying the path name, use |resolve()|.
 ---
 --- @param filename string
---- @return any
+--- @return string
 function vim.fn.simplify(filename) end
 
 --- Return the sine of {expr}, measured in radians, as a |Float|.
@@ -8945,7 +8945,7 @@ function vim.fn.simplify(filename) end
 --- <  0.763301
 ---
 --- @param expr number
---- @return any
+--- @return number
 function vim.fn.sin(expr) end
 
 --- Return the hyperbolic sine of {expr} as a |Float| in the range
@@ -9093,7 +9093,7 @@ function vim.fn.sort(list, how, dict) end
 --- the method can be quite slow.
 ---
 --- @param word string
---- @return any
+--- @return string
 function vim.fn.soundfold(word) end
 
 --- Without argument: The result is the badly spelled word under
@@ -9146,7 +9146,7 @@ function vim.fn.spellbadword(sentence) end
 --- @param word string
 --- @param max? integer
 --- @param capital? boolean
---- @return any
+--- @return string[]
 function vim.fn.spellsuggest(word, max, capital) end
 
 --- Make a |List| out of {string}.  When {pattern} is omitted or
@@ -9176,7 +9176,7 @@ function vim.fn.spellsuggest(word, max, capital) end
 --- @param string string
 --- @param pattern? string
 --- @param keepempty? boolean
---- @return any
+--- @return string[]
 function vim.fn.split(string, pattern, keepempty) end
 
 --- Return the non-negative square root of Float {expr} as a
@@ -10179,7 +10179,7 @@ function vim.fn.tempname() end
 ---
 --- @param cmd string|string[]
 --- @param opts? table
---- @return any
+--- @return integer
 function vim.fn.termopen(cmd, opts) end
 
 --- Return a list with information about timers.
@@ -10579,7 +10579,7 @@ function vim.fn.virtcol(expr, list, winid) end
 --- @param winid integer
 --- @param lnum integer
 --- @param col integer
---- @return any
+--- @return integer
 function vim.fn.virtcol2col(winid, lnum, col) end
 
 --- The result is a String, which describes the last Visual mode
@@ -10600,7 +10600,7 @@ function vim.fn.virtcol2col(winid, lnum, col) end
 --- the old value is returned.  See |non-zero-arg|.
 ---
 --- @param expr? boolean
---- @return any
+--- @return string
 function vim.fn.visualmode(expr) end
 
 --- Waits until {condition} evaluates to |TRUE|, where {condition}
@@ -10717,7 +10717,7 @@ function vim.fn.win_id2tabwin(expr) end
 --- Return 0 if the window cannot be found in the current tabpage.
 ---
 --- @param expr integer
---- @return any
+--- @return integer
 function vim.fn.win_id2win(expr) end
 
 --- Move window {nr}'s vertical separator (i.e., the right border)
@@ -10915,7 +10915,7 @@ function vim.fn.winline() end
 --- <
 ---
 --- @param arg? string|integer
---- @return any
+--- @return integer
 function vim.fn.winnr(arg) end
 
 --- Returns a sequence of |:resize| commands that should restore
@@ -10928,7 +10928,7 @@ function vim.fn.winnr(arg) end
 ---   exe cmd
 --- <
 ---
---- @return any
+--- @return string
 function vim.fn.winrestcmd() end
 
 --- Uses the |Dictionary| returned by |winsaveview()| to restore
@@ -10993,7 +10993,7 @@ function vim.fn.winsaveview() end
 --- option.
 ---
 --- @param nr integer
---- @return any
+--- @return integer
 function vim.fn.winwidth(nr) end
 
 --- The result is a dictionary of byte/chars/word statistics for
@@ -11078,7 +11078,7 @@ function vim.fn.writefile(object, fname, flags) end
 ---   let bits = xor(bits, 0x80)
 --- <
 ---
---- @param expr number
---- @param expr1 number
---- @return any
+--- @param expr integer
+--- @param expr1 integer
+--- @return integer
 function vim.fn.xor(expr, expr1) end
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 08c9cd3991..cf80a26e1c 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -1639,6 +1639,7 @@ M.funcs = {
     ]=],
     name = 'ctxset',
     params = { { 'context', 'table' }, { 'index', 'integer' } },
+    returns = 'integer',
     signature = 'ctxset({context} [, {index}])',
   },
   ctxsize = {
@@ -1869,6 +1870,7 @@ M.funcs = {
     fast = true,
     name = 'did_filetype',
     params = {},
+    returns = 'integer',
     signature = 'did_filetype()',
   },
   diff_filler = {
@@ -1886,6 +1888,7 @@ M.funcs = {
     ]=],
     name = 'diff_filler',
     params = { { 'lnum', 'integer' } },
+    returns = 'integer',
     signature = 'diff_filler({lnum})',
   },
   diff_hlID = {
@@ -1930,6 +1933,7 @@ M.funcs = {
     ]=],
     name = 'digraph_get',
     params = { { 'chars', 'string' } },
+    returns = 'string',
     signature = 'digraph_get({chars})',
   },
   digraph_getlist = {
@@ -1952,6 +1956,7 @@ M.funcs = {
     ]=],
     name = 'digraph_getlist',
     params = { { 'listall', 'boolean' } },
+    returns = 'string[][]',
     signature = 'digraph_getlist([{listall}])',
   },
   digraph_set = {
@@ -2016,6 +2021,7 @@ M.funcs = {
     ]=],
     name = 'empty',
     params = { { 'expr', 'any' } },
+    returns = 'integer',
     signature = 'empty({expr})',
   },
   environ = {
@@ -2048,6 +2054,7 @@ M.funcs = {
     fast = true,
     name = 'escape',
     params = { { 'string', 'string' }, { 'chars', 'string' } },
+    returns = 'string',
     signature = 'escape({string}, {chars})',
   },
   eval = {
@@ -3018,6 +3025,7 @@ M.funcs = {
     ]=],
     name = 'foreach',
     params = { { 'expr1', 'string|table' }, { 'expr2', 'string|function' } },
+    returns = 'string|table',
     signature = 'foreach({expr1}, {expr2})',
   },
   foreground = {
@@ -3372,6 +3380,7 @@ M.funcs = {
     ]=],
     name = 'getbufline',
     params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' }, { 'end', 'integer' } },
+    returns = 'string[]',
     signature = 'getbufline({buf}, {lnum} [, {end}])',
   },
   getbufoneline = {
@@ -3700,6 +3709,7 @@ M.funcs = {
     ]=],
     name = 'getcmdscreenpos',
     params = {},
+    returns = 'integer',
     signature = 'getcmdscreenpos()',
   },
   getcmdtype = {
@@ -4781,6 +4791,7 @@ M.funcs = {
     ]=],
     name = 'gettext',
     params = { { 'text', 'string' } },
+    returns = 'string',
     signature = 'gettext({text})',
   },
   getwininfo = {
@@ -4965,6 +4976,7 @@ M.funcs = {
     ]=],
     name = 'glob2regpat',
     params = { { 'string', 'string' } },
+    returns = 'string',
     signature = 'glob2regpat({string})',
   },
   globpath = {
@@ -5372,6 +5384,7 @@ M.funcs = {
     fast = true,
     name = 'iconv',
     params = { { 'string', 'string' }, { 'from', 'string' }, { 'to', 'string' } },
+    returns = 'string',
     signature = 'iconv({string}, {from}, {to})',
   },
   id = {
@@ -5395,6 +5408,7 @@ M.funcs = {
     ]=],
     name = 'id',
     params = { { 'expr', 'any' } },
+    returns = 'string',
     signature = 'id({expr})',
   },
   indent = {
@@ -5447,6 +5461,7 @@ M.funcs = {
     ]=],
     name = 'index',
     params = { { 'object', 'any' }, { 'expr', 'any' }, { 'start', 'integer' }, { 'ic', 'boolean' } },
+    returns = 'integer',
     signature = 'index({object}, {expr} [, {start} [, {ic}]])',
   },
   indexof = {
@@ -5494,6 +5509,7 @@ M.funcs = {
     ]=],
     name = 'indexof',
     params = { { 'object', 'any' }, { 'expr', 'any' }, { 'opts', 'table' } },
+    returns = 'integer',
     signature = 'indexof({object}, {expr} [, {opts}])',
   },
   input = {
@@ -5502,6 +5518,7 @@ M.funcs = {
     desc = '',
     name = 'input',
     params = { { 'prompt', 'string' }, { 'text', 'string' }, { 'completion', 'string' } },
+    returns = 'string',
     signature = 'input({prompt} [, {text} [, {completion}]])',
   },
   input__1 = {
@@ -5621,6 +5638,7 @@ M.funcs = {
     ]=],
     name = 'input',
     params = { { 'opts', 'table' } },
+    returns = 'string',
     signature = 'input({opts})',
   },
   inputdialog = {
@@ -5667,6 +5685,7 @@ M.funcs = {
     ]=],
     name = 'inputrestore',
     params = {},
+    returns = 'integer',
     signature = 'inputrestore()',
   },
   inputsave = {
@@ -5680,6 +5699,7 @@ M.funcs = {
     ]=],
     name = 'inputsave',
     params = {},
+    returns = 'integer',
     signature = 'inputsave()',
   },
   inputsecret = {
@@ -5699,6 +5719,7 @@ M.funcs = {
     ]=],
     name = 'inputsecret',
     params = { { 'prompt', 'string' }, { 'text', 'string' } },
+    returns = 'string',
     signature = 'inputsecret({prompt} [, {text}])',
   },
   insert = {
@@ -5756,7 +5777,8 @@ M.funcs = {
       <
     ]=],
     name = 'invert',
-    params = { { 'expr', 'number' } },
+    params = { { 'expr', 'integer' } },
+    returns = 'integer',
     signature = 'invert({expr})',
   },
   isabsolutepath = {
@@ -6006,6 +6028,7 @@ M.funcs = {
     ]=],
     name = 'jobstart',
     params = { { 'cmd', 'string|string[]' }, { 'opts', 'table' } },
+    returns = 'integer',
     signature = 'jobstart({cmd} [, {opts}])',
   },
   jobstop = {
@@ -6022,6 +6045,7 @@ M.funcs = {
     ]=],
     name = 'jobstop',
     params = { { 'id', 'integer' } },
+    returns = 'integer',
     signature = 'jobstop({id})',
   },
   jobwait = {
@@ -6069,6 +6093,7 @@ M.funcs = {
     ]=],
     name = 'join',
     params = { { 'list', 'any[]' }, { 'sep', 'string' } },
+    returns = 'string',
     signature = 'join({list} [, {sep}])',
   },
   json_decode = {
@@ -6112,6 +6137,7 @@ M.funcs = {
     ]=],
     name = 'json_encode',
     params = { { 'expr', 'any' } },
+    returns = 'string',
     signature = 'json_encode({expr})',
   },
   keys = {
@@ -6124,6 +6150,7 @@ M.funcs = {
     ]=],
     name = 'keys',
     params = { { 'dict', 'table' } },
+    returns = 'string[]',
     signature = 'keys({dict})',
   },
   keytrans = {
@@ -6139,6 +6166,7 @@ M.funcs = {
     ]=],
     name = 'keytrans',
     params = { { 'string', 'string' } },
+    returns = 'string',
     signature = 'keytrans({string})',
   },
   last_buffer_nr = {
@@ -6165,7 +6193,8 @@ M.funcs = {
 
     ]=],
     name = 'len',
-    params = { { 'expr', 'any' } },
+    params = { { 'expr', 'any[]' } },
+    returns = 'integer',
     signature = 'len({expr})',
     tags = { 'E701' },
   },
@@ -6300,6 +6329,7 @@ M.funcs = {
     ]=],
     name = 'lispindent',
     params = { { 'lnum', 'integer' } },
+    returns = 'integer',
     signature = 'lispindent({lnum})',
   },
   list2blob = {
@@ -6318,6 +6348,7 @@ M.funcs = {
     ]=],
     name = 'list2blob',
     params = { { 'list', 'any[]' } },
+    returns = 'string',
     signature = 'list2blob({list})',
   },
   list2str = {
@@ -6342,6 +6373,7 @@ M.funcs = {
     ]=],
     name = 'list2str',
     params = { { 'list', 'any[]' }, { 'utf8', 'boolean' } },
+    returns = 'string',
     signature = 'list2str({list} [, {utf8}])',
   },
   localtime = {
@@ -6351,6 +6383,7 @@ M.funcs = {
     ]=],
     name = 'localtime',
     params = {},
+    returns = 'integer',
     signature = 'localtime()',
   },
   log = {
@@ -6371,6 +6404,7 @@ M.funcs = {
     float_func = 'log',
     name = 'log',
     params = { { 'expr', 'number' } },
+    returns = 'number',
     signature = 'log({expr})',
   },
   log10 = {
@@ -6390,6 +6424,7 @@ M.funcs = {
     float_func = 'log10',
     name = 'log10',
     params = { { 'expr', 'number' } },
+    returns = 'number',
     signature = 'log10({expr})',
   },
   luaeval = {
@@ -7422,6 +7457,7 @@ M.funcs = {
     ]=],
     name = 'min',
     params = { { 'expr', 'any' } },
+    returns = 'number',
     signature = 'min({expr})',
   },
   mkdir = {
@@ -7470,6 +7506,7 @@ M.funcs = {
     ]=],
     name = 'mkdir',
     params = { { 'name', 'string' }, { 'flags', 'string' }, { 'prot', 'string' } },
+    returns = 'integer',
     signature = 'mkdir({name} [, {flags} [, {prot}]])',
     tags = { 'E739' },
   },
@@ -7649,6 +7686,7 @@ M.funcs = {
     ]=],
     name = 'nextnonblank',
     params = { { 'lnum', 'integer' } },
+    returns = 'integer',
     signature = 'nextnonblank({lnum})',
   },
   nr2char = {
@@ -7672,6 +7710,7 @@ M.funcs = {
     ]=],
     name = 'nr2char',
     params = { { 'expr', 'integer' }, { 'utf8', 'boolean' } },
+    returns = 'string',
     signature = 'nr2char({expr} [, {utf8}])',
   },
   nvim_api__ = {
@@ -7733,6 +7772,7 @@ M.funcs = {
     ]=],
     name = 'pathshorten',
     params = { { 'path', 'string' }, { 'len', 'integer' } },
+    returns = 'string',
     signature = 'pathshorten({path} [, {len}])',
   },
   perleval = {
@@ -7776,6 +7816,7 @@ M.funcs = {
     ]=],
     name = 'pow',
     params = { { 'x', 'number' }, { 'y', 'number' } },
+    returns = 'number',
     signature = 'pow({x}, {y})',
   },
   prevnonblank = {
@@ -7793,6 +7834,7 @@ M.funcs = {
     ]=],
     name = 'prevnonblank',
     params = { { 'lnum', 'integer' } },
+    returns = 'integer',
     signature = 'prevnonblank({lnum})',
   },
   printf = {
@@ -8682,6 +8724,7 @@ M.funcs = {
     ]=],
     name = 'rename',
     params = { { 'from', 'string' }, { 'to', 'string' } },
+    returns = 'integer',
     signature = 'rename({from}, {to})',
   },
   ['repeat'] = {
@@ -8724,6 +8767,7 @@ M.funcs = {
     fast = true,
     name = 'resolve',
     params = { { 'filename', 'string' } },
+    returns = 'string',
     signature = 'resolve({filename})',
   },
   reverse = {
@@ -8765,6 +8809,7 @@ M.funcs = {
     float_func = 'round',
     name = 'round',
     params = { { 'expr', 'number' } },
+    returns = 'number',
     signature = 'round({expr})',
   },
   rpcnotify = {
@@ -8778,6 +8823,7 @@ M.funcs = {
     ]=],
     name = 'rpcnotify',
     params = { { 'channel', 'integer' }, { 'event', 'string' }, { '...', 'any' } },
+    returns = 'integer',
     signature = 'rpcnotify({channel}, {event} [, {args}...])',
   },
   rpcrequest = {
@@ -8849,6 +8895,7 @@ M.funcs = {
     ]=],
     name = 'screenattr',
     params = { { 'row', 'integer' }, { 'col', 'integer' } },
+    returns = 'integer',
     signature = 'screenattr({row}, {col})',
   },
   screenchar = {
@@ -8867,6 +8914,7 @@ M.funcs = {
     ]=],
     name = 'screenchar',
     params = { { 'row', 'integer' }, { 'col', 'integer' } },
+    returns = 'integer',
     signature = 'screenchar({row}, {col})',
   },
   screenchars = {
@@ -8882,6 +8930,7 @@ M.funcs = {
     ]=],
     name = 'screenchars',
     params = { { 'row', 'integer' }, { 'col', 'integer' } },
+    returns = 'integer[]',
     signature = 'screenchars({row}, {col})',
   },
   screencol = {
@@ -8902,6 +8951,7 @@ M.funcs = {
     ]=],
     name = 'screencol',
     params = {},
+    returns = 'integer[]',
     signature = 'screencol()',
   },
   screenpos = {
@@ -8947,6 +8997,7 @@ M.funcs = {
     ]=],
     name = 'screenrow',
     params = {},
+    returns = 'integer',
     signature = 'screenrow()',
   },
   screenstring = {
@@ -8963,6 +9014,7 @@ M.funcs = {
     ]=],
     name = 'screenstring',
     params = { { 'row', 'integer' }, { 'col', 'integer' } },
+    returns = 'string',
     signature = 'screenstring({row}, {col})',
   },
   search = {
@@ -9404,6 +9456,7 @@ M.funcs = {
     ]=],
     name = 'serverlist',
     params = {},
+    returns = 'string[]',
     signature = 'serverlist()',
   },
   serverstart = {
@@ -9444,6 +9497,7 @@ M.funcs = {
     ]=],
     name = 'serverstart',
     params = { { 'address', 'string' } },
+    returns = 'string',
     signature = 'serverstart([{address}])',
   },
   serverstop = {
@@ -9456,6 +9510,7 @@ M.funcs = {
     ]=],
     name = 'serverstop',
     params = { { 'address', 'string' } },
+    returns = 'integer',
     signature = 'serverstop({address})',
   },
   setbufline = {
@@ -9489,6 +9544,7 @@ M.funcs = {
     ]=],
     name = 'setbufline',
     params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' }, { 'text', 'string|string[]' } },
+    returns = 'integer',
     signature = 'setbufline({buf}, {lnum}, {text})',
   },
   setbufvar = {
@@ -9614,6 +9670,7 @@ M.funcs = {
     ]=],
     name = 'setcmdline',
     params = { { 'str', 'string' }, { 'pos', 'integer' } },
+    returns = 'integer',
     signature = 'setcmdline({str} [, {pos}])',
   },
   setcmdpos = {
@@ -9964,6 +10021,7 @@ M.funcs = {
       { 'action', 'string' },
       { 'what', 'vim.fn.setqflist.what' },
     },
+    returns = 'integer',
     signature = 'setqflist({list} [, {action} [, {what}]])',
   },
   setreg = {
@@ -10140,6 +10198,7 @@ M.funcs = {
     ]=],
     name = 'sha256',
     params = { { 'string', 'string' } },
+    returns = 'string',
     signature = 'sha256({string})',
   },
   shellescape = {
@@ -10180,6 +10239,7 @@ M.funcs = {
     ]=],
     name = 'shellescape',
     params = { { 'string', 'string' }, { 'special', 'boolean' } },
+    returns = 'string',
     signature = 'shellescape({string} [, {special}])',
   },
   shiftwidth = {
@@ -10694,6 +10754,7 @@ M.funcs = {
     ]=],
     name = 'simplify',
     params = { { 'filename', 'string' } },
+    returns = 'string',
     signature = 'simplify({filename})',
   },
   sin = {
@@ -10713,6 +10774,7 @@ M.funcs = {
     float_func = 'sin',
     name = 'sin',
     params = { { 'expr', 'number' } },
+    returns = 'number',
     signature = 'sin({expr})',
   },
   sinh = {
@@ -10879,6 +10941,7 @@ M.funcs = {
     ]=],
     name = 'soundfold',
     params = { { 'word', 'string' } },
+    returns = 'string',
     signature = 'soundfold({word})',
   },
   spellbadword = {
@@ -10940,6 +11003,7 @@ M.funcs = {
     ]=],
     name = 'spellsuggest',
     params = { { 'word', 'string' }, { 'max', 'integer' }, { 'capital', 'boolean' } },
+    returns = 'string[]',
     signature = 'spellsuggest({word} [, {max} [, {capital}]])',
   },
   split = {
@@ -10973,6 +11037,7 @@ M.funcs = {
     ]=],
     name = 'split',
     params = { { 'string', 'string' }, { 'pattern', 'string' }, { 'keepempty', 'boolean' } },
+    returns = 'string[]',
     signature = 'split({string} [, {pattern} [, {keepempty}]])',
   },
   sqrt = {
@@ -12209,6 +12274,7 @@ M.funcs = {
     ]=],
     name = 'termopen',
     params = { { 'cmd', 'string|string[]' }, { 'opts', 'table' } },
+    returns = 'integer',
     signature = 'termopen({cmd} [, {opts}])',
   },
   test_garbagecollect_now = {
@@ -12718,6 +12784,7 @@ M.funcs = {
     ]=],
     name = 'virtcol2col',
     params = { { 'winid', 'integer' }, { 'lnum', 'integer' }, { 'col', 'integer' } },
+    returns = 'integer',
     signature = 'virtcol2col({winid}, {lnum}, {col})',
   },
   visualmode = {
@@ -12742,6 +12809,7 @@ M.funcs = {
     ]=],
     name = 'visualmode',
     params = { { 'expr', 'boolean' } },
+    returns = 'string',
     signature = 'visualmode([{expr}])',
   },
   wait = {
@@ -12899,6 +12967,7 @@ M.funcs = {
     ]=],
     name = 'win_id2win',
     params = { { 'expr', 'integer' } },
+    returns = 'integer',
     signature = 'win_id2win({expr})',
   },
   win_move_separator = {
@@ -13145,6 +13214,7 @@ M.funcs = {
     ]=],
     name = 'winnr',
     params = { { 'arg', 'string|integer' } },
+    returns = 'integer',
     signature = 'winnr([{arg}])',
   },
   winrestcmd = {
@@ -13161,6 +13231,7 @@ M.funcs = {
     ]=],
     name = 'winrestcmd',
     params = {},
+    returns = 'string',
     signature = 'winrestcmd()',
   },
   winrestview = {
@@ -13240,6 +13311,7 @@ M.funcs = {
     ]=],
     name = 'winwidth',
     params = { { 'nr', 'integer' } },
+    returns = 'integer',
     signature = 'winwidth({nr})',
   },
   wordcount = {
@@ -13335,7 +13407,8 @@ M.funcs = {
       <
     ]=],
     name = 'xor',
-    params = { { 'expr', 'number' }, { 'expr', 'number' } },
+    params = { { 'expr', 'integer' }, { 'expr', 'integer' } },
+    returns = 'integer',
     signature = 'xor({expr}, {expr})',
   },
 }
-- 
cgit 


From 3dfb9e6f60d9ca27ff140a9300cc1a43e38aa2ee Mon Sep 17 00:00:00 2001
From: Riley Bruins 
Date: Wed, 11 Dec 2024 04:34:24 -0800
Subject: feat(treesitter): include capture id in return value of
 `get_captures_at_pos()` #30559

**Problem:** Currently, it is difficult to get node(s)-level metadata
for a capture returned by `get_captures_at_pos()`. This is because it is
stored in `metadata[id]` and we do not have access to the value of `id`,
so to get this value we have to iterate over the keys of `metadata`. See
[this commit](https://github.com/neovim/neovim/commit/d63622930001b39b12f14112fc3abb55b760c447#diff-8bd4742121c2f359d0345f3c6c253a58220f1a28670cc4e1c957992232059a6cR16).

Things would be much simpler if we were given the `id` of the capture so
we could use it to just index `metadata` directly.

**Solution:** Include `id` in the data returned by
`get_captures_at_pos()`
---
 runtime/doc/news.txt                          |  1 +
 runtime/doc/treesitter.txt                    |  6 +++---
 runtime/lua/vim/treesitter.lua                | 18 +++++++++++-------
 test/functional/treesitter/highlight_spec.lua |  5 +++--
 4 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 71ec84c2f2..07b1b8646a 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -289,6 +289,7 @@ TREESITTER
   false, which allows it to return anonymous nodes as well as named nodes.
 • |treesitter-directive-trim!| can trim all whitespace (not just empty lines)
   from both sides of a node.
+• |vim.treesitter.get_captures_at_pos()| now returns the `id` of each capture
 
 TUI
 
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 877d90a3b7..a0860c60a6 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -902,8 +902,8 @@ get_captures_at_pos({bufnr}, {row}, {col})
     Returns a list of highlight captures at the given position
 
     Each capture is represented by a table containing the capture name as a
-    string as well as a table of metadata (`priority`, `conceal`, ...; empty
-    if none are defined).
+    string, the capture's language, a table of metadata (`priority`,
+    `conceal`, ...; empty if none are defined), and the id of the capture.
 
     Parameters: ~
       • {bufnr}  (`integer`) Buffer number (0 for current buffer)
@@ -911,7 +911,7 @@ get_captures_at_pos({bufnr}, {row}, {col})
       • {col}    (`integer`) Position column
 
     Return: ~
-        (`{capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata}[]`)
+        (`{capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata, id: integer}[]`)
 
 get_node({opts})                                   *vim.treesitter.get_node()*
     Returns the smallest named node at the given position
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 5f4e796413..89dc4e289a 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -245,14 +245,15 @@ end
 
 --- Returns a list of highlight captures at the given position
 ---
---- Each capture is represented by a table containing the capture name as a string as
---- well as a table of metadata (`priority`, `conceal`, ...; empty if none are defined).
+--- Each capture is represented by a table containing the capture name as a string, the capture's
+--- language, a table of metadata (`priority`, `conceal`, ...; empty if none are defined), and the
+--- id of the capture.
 ---
 ---@param bufnr integer Buffer number (0 for current buffer)
 ---@param row integer Position row
 ---@param col integer Position column
 ---
----@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata}[]
+---@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata, id: integer}[]
 function M.get_captures_at_pos(bufnr, row, col)
   bufnr = vim._resolve_bufnr(bufnr)
   local buf_highlighter = M.highlighter.active[bufnr]
@@ -285,12 +286,15 @@ function M.get_captures_at_pos(bufnr, row, col)
 
     local iter = q:query():iter_captures(root, buf_highlighter.bufnr, row, row + 1)
 
-    for capture, node, metadata in iter do
+    for id, node, metadata in iter do
       if M.is_in_node_range(node, row, col) then
         ---@diagnostic disable-next-line: invisible
-        local c = q._query.captures[capture] -- name of the capture in the query
-        if c ~= nil then
-          table.insert(matches, { capture = c, metadata = metadata, lang = tree:lang() })
+        local capture = q._query.captures[id] -- name of the capture in the query
+        if capture ~= nil then
+          table.insert(
+            matches,
+            { capture = capture, metadata = metadata, lang = tree:lang(), id = id }
+          )
         end
       end
     end
diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index 028c01f14a..7f0a3cb342 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -12,6 +12,7 @@ local fn = n.fn
 local eq = t.eq
 
 local hl_query_c = [[
+  ; query
   (ERROR) @error
 
   "if" @keyword
@@ -639,8 +640,8 @@ describe('treesitter highlighting (C)', function()
     }
 
     eq({
-      { capture = 'constant', metadata = { priority = '101' }, lang = 'c' },
-      { capture = 'type', metadata = {}, lang = 'c' },
+      { capture = 'constant', metadata = { priority = '101' }, lang = 'c', id = 14 },
+      { capture = 'type', metadata = {}, lang = 'c', id = 3 },
     }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]])
   end)
 
-- 
cgit 


From ff1791c9e59bccaee685a537e094f7d6bdc3b122 Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Wed, 11 Dec 2024 21:20:10 +0800
Subject: fix(float): close preview float window when no selected #29745

Problem: Float preview window still exist when back at original.
Or no info item is selected.

Solution: if selected is -1 or no info is selected, if float preview
window exist close it first.
---
 src/nvim/popupmenu.c                  |  18 ++---
 test/functional/ui/popupmenu_spec.lua | 141 +++++++++++++++++++++++++---------
 2 files changed, 112 insertions(+), 47 deletions(-)

diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 953d2e75a6..8bf145f520 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -941,11 +941,14 @@ static bool pum_set_selected(int n, int repeat)
   pum_selected = n;
   unsigned cur_cot_flags = get_cot_flags();
   bool use_float = (cur_cot_flags & kOptCotFlagPopup) != 0;
-  // when new leader add and info window is shown and no selected we still
-  // need use the first index item to update the info float window position.
-  bool force_select = use_float && pum_selected < 0 && win_float_find_preview();
-  if (force_select) {
-    pum_selected = 0;
+
+  // Close the floating preview window if 'selected' is -1, indicating a return to the original
+  // state. It is also closed when the selected item has no corresponding info item.
+  if (use_float && (pum_selected < 0 || pum_array[pum_selected].pum_info == NULL)) {
+    win_T *wp = win_float_find_preview();
+    if (wp) {
+      win_close(wp, true, true);
+    }
   }
 
   if ((pum_selected >= 0) && (pum_selected < pum_size)) {
@@ -1164,11 +1167,6 @@ static bool pum_set_selected(int n, int repeat)
     }
   }
 
-  // restore before selected value
-  if (force_select) {
-    pum_selected = n;
-  }
-
   return resized;
 }
 
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index 8fe8975b4a..a4bf602e57 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -1684,25 +1684,26 @@ describe('builtin popupmenu', function()
           }
         end
 
-        -- info window position should be adjusted when new leader add
-        feed('o')
+        -- delete one character make the pum width smaller than before
+        -- info window position should be adjusted when popupmenu width changed
+        feed('')
         if multigrid then
-          screen:expect {
+          screen:expect({
             grid = [[
-          ## grid 1
-            [2:----------------------------------------]|*10
-            [3:----------------------------------------]|
-          ## grid 2
-            o^                                       |
-            {1:~                                       }|*9
-          ## grid 3
-            {2:-- }{8:Back at original}                     |
-          ## grid 4
-            {n:1info}|
-            {n:     }|
-          ## grid 5
-            {n:one            }|
-          ]],
+            ## grid 1
+              [2:----------------------------------------]|*10
+              [3:----------------------------------------]|
+            ## grid 2
+              on^                                      |
+              {1:~                                       }|*9
+            ## grid 3
+              {2:-- }{5:match 1 of 3}                         |
+            ## grid 4
+              {n:1info}|
+              {n:     }|
+            ## grid 5
+              {s:one            }|
+            ]],
             float_pos = {
               [5] = { -1, 'NW', 2, 1, 0, false, 100 },
               [4] = { 1001, 'NW', 1, 1, 15, false, 50 },
@@ -1713,7 +1714,7 @@ describe('builtin popupmenu', function()
                 topline = 0,
                 botline = 2,
                 curline = 0,
-                curcol = 1,
+                curcol = 2,
                 linecount = 1,
                 sum_scroll_delta = 0,
               },
@@ -1727,22 +1728,88 @@ describe('builtin popupmenu', function()
                 sum_scroll_delta = 0,
               },
             },
-          }
+            win_viewport_margins = {
+              [2] = {
+                bottom = 0,
+                left = 0,
+                right = 0,
+                top = 0,
+                win = 1000,
+              },
+              [4] = {
+                bottom = 0,
+                left = 0,
+                right = 0,
+                top = 0,
+                win = 1001,
+              },
+            },
+          })
         else
-          screen:expect {
+          screen:expect({
             grid = [[
-            o^                                       |
-            {n:one            1info}{1:                    }|
-            {1:~              }{n:     }{1:                    }|
-            {1:~                                       }|*7
-            {2:-- }{8:Back at original}                     |
-          ]],
-          }
+              on^                                      |
+              {s:one            }{n:1info}{1:                    }|
+              {1:~              }{n:     }{1:                    }|
+              {1:~                                       }|*7
+              {2:-- }{5:match 1 of 3}                         |
+            ]],
+          })
+        end
+
+        -- when back to original the preview float should be closed.
+        feed('')
+        if multigrid then
+          screen:expect({
+            grid = [[
+            ## grid 1
+              [2:----------------------------------------]|*10
+              [3:----------------------------------------]|
+            ## grid 2
+              on^                                      |
+              {1:~                                       }|*9
+            ## grid 3
+              {2:-- }{8:Back at original}                     |
+            ## grid 5
+              {n:one            }|
+            ]],
+            float_pos = {
+              [5] = { -1, 'NW', 2, 1, 0, false, 100 },
+            },
+            win_viewport = {
+              [2] = {
+                win = 1000,
+                topline = 0,
+                botline = 2,
+                curline = 0,
+                curcol = 2,
+                linecount = 1,
+                sum_scroll_delta = 0,
+              },
+            },
+            win_viewport_margins = {
+              [2] = {
+                bottom = 0,
+                left = 0,
+                right = 0,
+                top = 0,
+                win = 1000,
+              },
+            },
+          })
+        else
+          screen:expect({
+            grid = [[
+              on^                                      |
+              {n:one            }{1:                         }|
+              {1:~                                       }|*8
+              {2:-- }{8:Back at original}                     |
+            ]],
+          })
         end
 
         -- test nvim__complete_set_info
-        feed('cc')
-        vim.uv.sleep(10)
+        feed('S')
         if multigrid then
           screen:expect {
             grid = [[
@@ -1758,13 +1825,13 @@ describe('builtin popupmenu', function()
             {n:one                }|
             {n:two                }|
             {s:looooooooooooooong }|
-          ## grid 6
+          ## grid 7
             {n:3info}|
             {n:     }|
           ]],
             float_pos = {
               [5] = { -1, 'NW', 2, 1, 0, false, 100 },
-              [6] = { 1002, 'NW', 1, 1, 19, false, 50 },
+              [7] = { 1003, 'NW', 1, 1, 19, false, 50 },
             },
             win_viewport = {
               [2] = {
@@ -1776,8 +1843,8 @@ describe('builtin popupmenu', function()
                 linecount = 1,
                 sum_scroll_delta = 0,
               },
-              [6] = {
-                win = 1002,
+              [7] = {
+                win = 1003,
                 topline = 0,
                 botline = 2,
                 curline = 0,
@@ -1819,12 +1886,12 @@ describe('builtin popupmenu', function()
             {s: one                }|
             {n: two                }|
             {n: looooooooooooooong }|
-          ## grid 7
+          ## grid 8
             {n:1info}|
             {n:     }|
           ]],
             float_pos = {
-              [7] = { 1003, 'NW', 1, 1, 14, false, 50 },
+              [8] = { 1004, 'NW', 1, 1, 14, false, 50 },
               [5] = { -1, 'NW', 2, 1, 19, false, 100 },
             },
             win_viewport = {
@@ -1837,8 +1904,8 @@ describe('builtin popupmenu', function()
                 linecount = 1,
                 sum_scroll_delta = 0,
               },
-              [7] = {
-                win = 1003,
+              [8] = {
+                win = 1004,
                 topline = 0,
                 botline = 2,
                 curline = 0,
-- 
cgit 


From 442d338cb50e4cf08c58cb82b6d33b6d5df9fb1b Mon Sep 17 00:00:00 2001
From: Jonny Kong 
Date: Wed, 11 Dec 2024 08:48:17 -0500
Subject: fix(uri): uri_encode encodes brackets incorrectly for RFC2732 #31284

**Problem:**
The brackets in the RFC2732 regular expression are currently unescaped,
causing them to be misinterpreted as special characters denoting
character groups rather than as literal characters.

**Solution:**
Escape the brackets.
Fix #31270
---
 runtime/lua/vim/uri.lua          | 2 +-
 test/functional/lua/uri_spec.lua | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index b4e4098b91..6323f61256 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -15,7 +15,7 @@ local PATTERNS = {
   rfc2396 = "^A-Za-z0-9%-_.!~*'()",
   -- RFC 2732
   -- https://tools.ietf.org/html/rfc2732
-  rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
+  rfc2732 = "^A-Za-z0-9%-_.!~*'()%[%]",
   -- RFC 3986
   -- https://tools.ietf.org/html/rfc3986#section-2.2
   rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua
index 258b96bc43..d706f8b78b 100644
--- a/test/functional/lua/uri_spec.lua
+++ b/test/functional/lua/uri_spec.lua
@@ -252,4 +252,12 @@ describe('URI methods', function()
       end
     )
   end)
+
+  describe('encode to uri', function()
+    it('rfc2732 including brackets', function()
+      exec_lua("str = '[:]'")
+      exec_lua("rfc = 'rfc2732'")
+      eq('[%3a]', exec_lua('return vim.uri_encode(str, rfc)'))
+    end)
+  end)
 end)
-- 
cgit 


From 21961967ffef6d49512b83a23b6c93bb8b80389a Mon Sep 17 00:00:00 2001
From: Jeremy Fleischman 
Date: Wed, 11 Dec 2024 17:29:54 -0800
Subject: feat(diagnostic): update quickfix list by title #31486

Previously, there was a singleton diagnostics quickfix list. Now there's
effectively one per title (up to vim's internal limit on quickfix
lists).

Suggested by mfussenegger https://github.com/neovim/neovim/pull/30868#pullrequestreview-2385761374.
---
 runtime/doc/diagnostic.txt     |  3 ++-
 runtime/doc/news.txt           |  4 ++--
 runtime/lua/vim/diagnostic.lua | 31 ++++++++++++++++++-------------
 3 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt
index eaa3681caa..3437717467 100644
--- a/runtime/doc/diagnostic.txt
+++ b/runtime/doc/diagnostic.txt
@@ -874,7 +874,8 @@ setqflist({opts})                                 *vim.diagnostic.setqflist()*
                 • {open}? (`boolean`, default: `true`) Open quickfix list
                   after setting.
                 • {title}? (`string`) Title of quickfix list. Defaults to
-                  "Diagnostics".
+                  "Diagnostics". If there's already a quickfix list with this
+                  title, it's updated. If not, a new quickfix list is created.
                 • {severity}? (`vim.diagnostic.SeverityFilter`) See
                   |diagnostic-severity|.
 
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 07b1b8646a..e7d4b92f7e 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -315,8 +315,8 @@ UI
   |hl-PmenuSel| and |hl-PmenuMatch| both inherit from |hl-Pmenu|, and
   |hl-PmenuMatchSel| inherits highlights from both |hl-PmenuSel| and
   |hl-PmenuMatch|.
-• |vim.diagnostic.setqflist()| updates existing diagnostics quickfix list if one
-  exists.
+• |vim.diagnostic.setqflist()| updates an existing quickfix list with the
+  given title if found
 
 • |ui-messages| content chunks now also contain the highlight group ID.
 
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 340bca4f6b..1b61cf8f71 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -2,7 +2,19 @@ local api, if_nil = vim.api, vim.F.if_nil
 
 local M = {}
 
-local _qf_id = nil
+--- @param title string
+--- @return integer?
+local function get_qf_id_for_title(title)
+  local lastqflist = vim.fn.getqflist({ nr = '$' })
+  for i = 1, lastqflist.nr do
+    local qflist = vim.fn.getqflist({ nr = i, id = 0, title = 0 })
+    if qflist.title == title then
+      return qflist.id
+    end
+  end
+
+  return nil
+end
 
 --- [diagnostic-structure]()
 ---
@@ -845,24 +857,16 @@ local function set_list(loclist, opts)
   if loclist then
     vim.fn.setloclist(winnr, {}, 'u', { title = title, items = items })
   else
-    -- Check if the diagnostics quickfix list no longer exists.
-    if _qf_id and vim.fn.getqflist({ id = _qf_id }).id == 0 then
-      _qf_id = nil
-    end
+    local qf_id = get_qf_id_for_title(title)
 
     -- If we already have a diagnostics quickfix, update it rather than creating a new one.
     -- This avoids polluting the finite set of quickfix lists, and preserves the currently selected
     -- entry.
-    vim.fn.setqflist({}, _qf_id and 'u' or ' ', {
+    vim.fn.setqflist({}, qf_id and 'u' or ' ', {
       title = title,
       items = items,
-      id = _qf_id,
+      id = qf_id,
     })
-
-    -- Get the id of the newly created quickfix list.
-    if _qf_id == nil then
-      _qf_id = vim.fn.getqflist({ id = 0 }).id
-    end
   end
   if open then
     api.nvim_command(loclist and 'lwindow' or 'botright cwindow')
@@ -2037,7 +2041,8 @@ end
 --- (default: `true`)
 --- @field open? boolean
 ---
---- Title of quickfix list. Defaults to "Diagnostics".
+--- Title of quickfix list. Defaults to "Diagnostics". If there's already a quickfix list with this
+--- title, it's updated. If not, a new quickfix list is created.
 --- @field title? string
 ---
 --- See |diagnostic-severity|.
-- 
cgit 


From 5eda7aafe995bfefd46fe859f32fa8581ab7f15d Mon Sep 17 00:00:00 2001
From: Jeremy Fleischman 
Date: Wed, 11 Dec 2024 17:32:07 -0800
Subject: fix(diagnostic): setqflist() is stuck after
 vim.lsp.buf.document_symbol #31553

Previously, when updating the quickfix diagnostics list, we'd update it,
and then open the quickfix buffer, but there was no guarantee that the
quickfix buffer would be displaying the quickfix diagnostics list (it
could very possibly be displaying some other quickfix list!).

This fixes things so we first select the quickfix list before opening the
quickfix buffer. If `open` is not specified, the behavior is the same as
before: we update the diagnostics quickfix list, but do not navigate to
it.

fixes https://github.com/neovim/neovim/issues/31540
---
 runtime/lua/vim/diagnostic.lua | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 1b61cf8f71..62997924d9 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -869,7 +869,16 @@ local function set_list(loclist, opts)
     })
   end
   if open then
-    api.nvim_command(loclist and 'lwindow' or 'botright cwindow')
+    if loclist then
+      api.nvim_command('lwindow')
+    else
+      -- First navigate to the diagnostics quickfix list.
+      local nr = vim.fn.getqflist({ id = _qf_id, nr = 0 }).nr
+      api.nvim_command(nr .. 'chistory')
+
+      -- Now open the quickfix list.
+      api.nvim_command('botright cwindow')
+    end
   end
 end
 
-- 
cgit 


From 130f4344cf1a8fdafcf62b392ead863d1a1379f3 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Sun, 8 Dec 2024 10:32:29 +0000
Subject: refactor(lsp/rpc): move transport logic to separate module

---
 runtime/doc/lsp.txt                |  18 +--
 runtime/lua/vim/lsp/_transport.lua | 182 +++++++++++++++++++++++
 runtime/lua/vim/lsp/rpc.lua        | 288 +++++++++++--------------------------
 scripts/luacats_grammar.lua        |   4 +-
 4 files changed, 274 insertions(+), 218 deletions(-)
 create mode 100644 runtime/lua/vim/lsp/_transport.lua

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 64145ebf11..83d191ed48 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -2433,14 +2433,15 @@ should_log({level})                                 *vim.lsp.log.should_log()*
 Lua module: vim.lsp.rpc                                              *lsp-rpc*
 
 *vim.lsp.rpc.PublicClient*
+    Client RPC object
 
     Fields: ~
-      • {request}     (`fun(method: string, params: table?, callback: fun(err: lsp.ResponseError?, result: any), notify_reply_callback: fun(message_id: integer)?):boolean,integer?`)
-                      see |vim.lsp.rpc.request()|
-      • {notify}      (`fun(method: string, params: any):boolean`) see
+      • {request}     (`fun(method: string, params: table?, callback: fun(err?: lsp.ResponseError, result: any), notify_reply_callback?: fun(message_id: integer)):boolean,integer?`)
+                      See |vim.lsp.rpc.request()|
+      • {notify}      (`fun(method: string, params: any): boolean`) See
                       |vim.lsp.rpc.notify()|
-      • {is_closing}  (`fun(): boolean`)
-      • {terminate}   (`fun()`)
+      • {is_closing}  (`fun(): boolean`) Indicates if the RPC is closing.
+      • {terminate}   (`fun()`) Terminates the RPC client.
 
 
 connect({host_or_path}, {port})                        *vim.lsp.rpc.connect()*
@@ -2541,12 +2542,7 @@ start({cmd}, {dispatchers}, {extra_spawn_params})        *vim.lsp.rpc.start()*
                                 See |vim.system()|
 
     Return: ~
-        (`vim.lsp.rpc.PublicClient`) Client RPC object, with these methods:
-        • `notify()` |vim.lsp.rpc.notify()|
-        • `request()` |vim.lsp.rpc.request()|
-        • `is_closing()` returns a boolean indicating if the RPC is closing.
-        • `terminate()` terminates the RPC client. See
-          |vim.lsp.rpc.PublicClient|.
+        (`vim.lsp.rpc.PublicClient`) See |vim.lsp.rpc.PublicClient|.
 
 
 ==============================================================================
diff --git a/runtime/lua/vim/lsp/_transport.lua b/runtime/lua/vim/lsp/_transport.lua
new file mode 100644
index 0000000000..19ff2a8ab0
--- /dev/null
+++ b/runtime/lua/vim/lsp/_transport.lua
@@ -0,0 +1,182 @@
+local uv = vim.uv
+local log = require('vim.lsp.log')
+
+local is_win = vim.fn.has('win32') == 1
+
+--- Checks whether a given path exists and is a directory.
+---@param filename string path to check
+---@return boolean
+local function is_dir(filename)
+  local stat = uv.fs_stat(filename)
+  return stat and stat.type == 'directory' or false
+end
+
+--- @class (private) vim.lsp.rpc.Transport
+--- @field write fun(self: vim.lsp.rpc.Transport, msg: string)
+--- @field is_closing fun(self: vim.lsp.rpc.Transport): boolean
+--- @field terminate fun(self: vim.lsp.rpc.Transport)
+
+--- @class (private,exact) vim.lsp.rpc.Transport.Run : vim.lsp.rpc.Transport
+--- @field new fun(): vim.lsp.rpc.Transport.Run
+--- @field sysobj? vim.SystemObj
+local TransportRun = {}
+
+--- @return vim.lsp.rpc.Transport.Run
+function TransportRun.new()
+  return setmetatable({}, { __index = TransportRun })
+end
+
+--- @param cmd string[] Command to start the LSP server.
+--- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams
+--- @param on_read fun(err: any, data: string)
+--- @param on_exit fun(code: integer, signal: integer)
+function TransportRun:run(cmd, extra_spawn_params, on_read, on_exit)
+  local function on_stderr(_, chunk)
+    if chunk then
+      log.error('rpc', cmd[1], 'stderr', chunk)
+    end
+  end
+
+  extra_spawn_params = extra_spawn_params or {}
+
+  if extra_spawn_params.cwd then
+    assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
+  end
+
+  local detached = not is_win
+  if extra_spawn_params.detached ~= nil then
+    detached = extra_spawn_params.detached
+  end
+
+  local ok, sysobj_or_err = pcall(vim.system, cmd, {
+    stdin = true,
+    stdout = on_read,
+    stderr = on_stderr,
+    cwd = extra_spawn_params.cwd,
+    env = extra_spawn_params.env,
+    detach = detached,
+  }, function(obj)
+    on_exit(obj.code, obj.signal)
+  end)
+
+  if not ok then
+    local err = sysobj_or_err --[[@as string]]
+    local sfx = err:match('ENOENT')
+        and '. The language server is either not installed, missing from PATH, or not executable.'
+      or string.format(' with error message: %s', err)
+
+    error(('Spawning language server with cmd: `%s` failed%s'):format(vim.inspect(cmd), sfx))
+  end
+
+  self.sysobj = sysobj_or_err --[[@as vim.SystemObj]]
+end
+
+function TransportRun:write(msg)
+  assert(self.sysobj):write(msg)
+end
+
+function TransportRun:is_closing()
+  return self.sysobj == nil or self.sysobj:is_closing()
+end
+
+function TransportRun:terminate()
+  assert(self.sysobj):kill(15)
+end
+
+--- @class (private,exact) vim.lsp.rpc.Transport.Connect : vim.lsp.rpc.Transport
+--- @field new fun(): vim.lsp.rpc.Transport.Connect
+--- @field handle? uv.uv_pipe_t|uv.uv_tcp_t
+--- Connect returns a PublicClient synchronously so the caller
+--- can immediately send messages before the connection is established
+--- -> Need to buffer them until that happens
+--- @field connected boolean
+--- @field closing boolean
+--- @field msgbuf vim.Ringbuf
+--- @field on_exit? fun(code: integer, signal: integer)
+local TransportConnect = {}
+
+--- @return vim.lsp.rpc.Transport.Connect
+function TransportConnect.new()
+  return setmetatable({
+    connected = false,
+    -- size should be enough because the client can't really do anything until initialization is done
+    -- which required a response from the server - implying the connection got established
+    msgbuf = vim.ringbuf(10),
+    closing = false,
+  }, { __index = TransportConnect })
+end
+
+--- @param host_or_path string
+--- @param port? integer
+--- @param on_read fun(err: any, data: string)
+--- @param on_exit? fun(code: integer, signal: integer)
+function TransportConnect:connect(host_or_path, port, on_read, on_exit)
+  self.on_exit = on_exit
+  self.handle = (
+    port and assert(uv.new_tcp(), 'Could not create new TCP socket')
+    or assert(uv.new_pipe(false), 'Pipe could not be opened.')
+  )
+
+  local function on_connect(err)
+    if err then
+      local address = not port and host_or_path or (host_or_path .. ':' .. port)
+      vim.schedule(function()
+        vim.notify(
+          string.format('Could not connect to %s, reason: %s', address, vim.inspect(err)),
+          vim.log.levels.WARN
+        )
+      end)
+      return
+    end
+    self.handle:read_start(on_read)
+    self.connected = true
+    for msg in self.msgbuf do
+      self.handle:write(msg)
+    end
+  end
+
+  if not port then
+    self.handle:connect(host_or_path, on_connect)
+    return
+  end
+
+  --- @diagnostic disable-next-line:param-type-mismatch bad UV typing
+  local info = uv.getaddrinfo(host_or_path, nil)
+  local resolved_host = info and info[1] and info[1].addr or host_or_path
+  self.handle:connect(resolved_host, port, on_connect)
+end
+
+function TransportConnect:write(msg)
+  if self.connected then
+    local _, err = self.handle:write(msg)
+    if err and not self.closing then
+      log.error('Error on handle:write: %q', err)
+    end
+    return
+  end
+
+  self.msgbuf:push(msg)
+end
+
+function TransportConnect:is_closing()
+  return self.closing
+end
+
+function TransportConnect:terminate()
+  if self.closing then
+    return
+  end
+  self.closing = true
+  if self.handle then
+    self.handle:shutdown()
+    self.handle:close()
+  end
+  if self.on_exit then
+    self.on_exit(0, 0)
+  end
+end
+
+return {
+  TransportRun = TransportRun,
+  TransportConnect = TransportConnect,
+}
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 2327a37ab1..a0d1fe776b 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -1,18 +1,8 @@
-local uv = vim.uv
 local log = require('vim.lsp.log')
 local protocol = require('vim.lsp.protocol')
+local lsp_transport = require('vim.lsp._transport')
 local validate, schedule_wrap = vim.validate, vim.schedule_wrap
 
-local is_win = vim.fn.has('win32') == 1
-
---- Checks whether a given path exists and is a directory.
----@param filename string path to check
----@return boolean
-local function is_dir(filename)
-  local stat = uv.fs_stat(filename)
-  return stat and stat.type == 'directory' or false
-end
-
 --- Embeds the given string into a table and correctly computes `Content-Length`.
 ---
 ---@param message string
@@ -242,8 +232,11 @@ local default_dispatchers = {
   end,
 }
 
----@private
-function M.create_read_loop(handle_body, on_no_chunk, on_error)
+--- @private
+--- @param handle_body fun(body: string)
+--- @param on_exit? fun()
+--- @param on_error fun(err: any)
+function M.create_read_loop(handle_body, on_exit, on_error)
   local parse_chunk = coroutine.wrap(request_parser_loop) --[[@as fun(chunk: string?): vim.lsp.rpc.Headers?, string?]]
   parse_chunk()
   return function(err, chunk)
@@ -253,8 +246,8 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error)
     end
 
     if not chunk then
-      if on_no_chunk then
-        on_no_chunk()
+      if on_exit then
+        on_exit()
       end
       return
     end
@@ -262,7 +255,7 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error)
     while true do
       local headers, body = parse_chunk(chunk)
       if headers then
-        handle_body(body)
+        handle_body(assert(body))
         chunk = ''
       else
         break
@@ -282,14 +275,14 @@ local Client = {}
 ---@private
 function Client:encode_and_send(payload)
   log.debug('rpc.send', payload)
-  if self.transport.is_closing() then
+  if self.transport:is_closing() then
     return false
   end
   local jsonstr = assert(
     vim.json.encode(payload),
     string.format("Couldn't encode payload '%s'", vim.inspect(payload))
   )
-  self.transport.write(format_message_with_content_length(jsonstr))
+  self.transport:write(format_message_with_content_length(jsonstr))
   return true
 end
 
@@ -323,7 +316,7 @@ end
 ---@param method string The invoked LSP method
 ---@param params table? Parameters for the invoked LSP method
 ---@param callback fun(err?: lsp.ResponseError, result: any) Callback to invoke
----@param notify_reply_callback fun(message_id: integer)|nil Callback to invoke as soon as a request is no longer pending
+---@param notify_reply_callback? fun(message_id: integer) Callback to invoke as soon as a request is no longer pending
 ---@return boolean success `true` if request could be sent, `false` if not
 ---@return integer? message_id if request could be sent, `nil` if not
 function Client:request(method, params, callback, notify_reply_callback)
@@ -337,21 +330,16 @@ function Client:request(method, params, callback, notify_reply_callback)
     method = method,
     params = params,
   })
-  local message_callbacks = self.message_callbacks
-  local notify_reply_callbacks = self.notify_reply_callbacks
-  if result then
-    if message_callbacks then
-      message_callbacks[message_id] = schedule_wrap(callback)
-    else
-      return false, nil
-    end
-    if notify_reply_callback and notify_reply_callbacks then
-      notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback)
-    end
-    return result, message_id
-  else
-    return false, nil
+
+  if not result then
+    return false
+  end
+
+  self.message_callbacks[message_id] = schedule_wrap(callback)
+  if notify_reply_callback then
+    self.notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback)
   end
+  return result, message_id
 end
 
 ---@package
@@ -370,7 +358,7 @@ end
 ---@param ... any
 ---@return boolean status
 ---@return any head
----@return any|nil ...
+---@return any? ...
 function Client:pcall_handler(errkind, status, head, ...)
   if not status then
     self:on_error(errkind, head, ...)
@@ -385,7 +373,7 @@ end
 ---@param ... any
 ---@return boolean status
 ---@return any head
----@return any|nil ...
+---@return any? ...
 function Client:try_call(errkind, fn, ...)
   return self:pcall_handler(errkind, pcall(fn, ...))
 end
@@ -394,7 +382,8 @@ end
 -- time and log them. This would require storing the timestamp. I could call
 -- them with an error then, perhaps.
 
----@package
+--- @package
+--- @param body string
 function Client:handle_body(body)
   local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } })
   if not ok then
@@ -406,7 +395,7 @@ function Client:handle_body(body)
   if type(decoded) ~= 'table' then
     self:on_error(M.client_errors.INVALID_SERVER_MESSAGE, decoded)
   elseif type(decoded.method) == 'string' and decoded.id then
-    local err --- @type lsp.ResponseError|nil
+    local err --- @type lsp.ResponseError?
     -- Schedule here so that the users functions don't trigger an error and
     -- we can still use the result.
     vim.schedule(coroutine.wrap(function()
@@ -453,45 +442,36 @@ function Client:handle_body(body)
     local result_id = assert(tonumber(decoded.id), 'response id must be a number')
 
     -- Notify the user that a response was received for the request
-    local notify_reply_callbacks = self.notify_reply_callbacks
-    local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
+    local notify_reply_callback = self.notify_reply_callbacks[result_id]
     if notify_reply_callback then
       validate('notify_reply_callback', notify_reply_callback, 'function')
       notify_reply_callback(result_id)
-      notify_reply_callbacks[result_id] = nil
+      self.notify_reply_callbacks[result_id] = nil
     end
 
-    local message_callbacks = self.message_callbacks
-
     -- Do not surface RequestCancelled to users, it is RPC-internal.
     if decoded.error then
-      local mute_error = false
+      assert(type(decoded.error) == 'table')
       if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
         log.debug('Received cancellation ack', decoded)
-        mute_error = true
-      end
-
-      if mute_error then
         -- Clear any callback since this is cancelled now.
         -- This is safe to do assuming that these conditions hold:
         -- - The server will not send a result callback after this cancellation.
         -- - If the server sent this cancellation ACK after sending the result, the user of this RPC
         -- client will ignore the result themselves.
-        if result_id and message_callbacks then
-          message_callbacks[result_id] = nil
+        if result_id then
+          self.message_callbacks[result_id] = nil
         end
         return
       end
     end
 
-    local callback = message_callbacks and message_callbacks[result_id]
+    local callback = self.message_callbacks[result_id]
     if callback then
-      message_callbacks[result_id] = nil
+      self.message_callbacks[result_id] = nil
       validate('callback', callback, 'function')
       if decoded.error then
-        decoded.error = setmetatable(decoded.error, {
-          __tostring = M.format_rpc_error,
-        })
+        setmetatable(decoded.error, { __tostring = M.format_rpc_error })
       end
       self:try_call(
         M.client_errors.SERVER_RESULT_CALLBACK_ERROR,
@@ -517,11 +497,6 @@ function Client:handle_body(body)
   end
 end
 
----@class (private) vim.lsp.rpc.Transport
----@field write fun(msg: string)
----@field is_closing fun(): boolean
----@field terminate fun()
-
 ---@param dispatchers vim.lsp.rpc.Dispatchers
 ---@param transport vim.lsp.rpc.Transport
 ---@return vim.lsp.rpc.Client
@@ -536,11 +511,20 @@ local function new_client(dispatchers, transport)
   return setmetatable(state, { __index = Client })
 end
 
----@class vim.lsp.rpc.PublicClient
----@field request fun(method: string, params: table?, callback: fun(err: lsp.ResponseError|nil, result: any), notify_reply_callback: fun(message_id: integer)|nil):boolean,integer? see |vim.lsp.rpc.request()|
----@field notify fun(method: string, params: any):boolean see |vim.lsp.rpc.notify()|
----@field is_closing fun(): boolean
----@field terminate fun()
+--- Client RPC object
+--- @class vim.lsp.rpc.PublicClient
+---
+--- See [vim.lsp.rpc.request()]
+--- @field request fun(method: string, params: table?, callback: fun(err?: lsp.ResponseError, result: any), notify_reply_callback?: fun(message_id: integer)):boolean,integer?
+---
+--- See [vim.lsp.rpc.notify()]
+--- @field notify fun(method: string, params: any): boolean
+---
+--- Indicates if the RPC is closing.
+--- @field is_closing fun(): boolean
+---
+--- Terminates the RPC client.
+--- @field terminate fun()
 
 ---@param client vim.lsp.rpc.Client
 ---@return vim.lsp.rpc.PublicClient
@@ -551,20 +535,20 @@ local function public_client(client)
 
   ---@private
   function result.is_closing()
-    return client.transport.is_closing()
+    return client.transport:is_closing()
   end
 
   ---@private
   function result.terminate()
-    client.transport.terminate()
+    client.transport:terminate()
   end
 
   --- Sends a request to the LSP server and runs {callback} upon response.
   ---
   ---@param method (string) The invoked LSP method
   ---@param params (table?) Parameters for the invoked LSP method
-  ---@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke
-  ---@param notify_reply_callback fun(message_id: integer)|nil Callback to invoke as soon as a request is no longer pending
+  ---@param callback fun(err: lsp.ResponseError?, result: any) Callback to invoke
+  ---@param notify_reply_callback? fun(message_id: integer) Callback to invoke as soon as a request is no longer pending
   ---@return boolean success `true` if request could be sent, `false` if not
   ---@return integer? message_id if request could be sent, `nil` if not
   function result.request(method, params, callback, notify_reply_callback)
@@ -610,6 +594,21 @@ local function merge_dispatchers(dispatchers)
   return merged
 end
 
+--- @param client vim.lsp.rpc.Client
+--- @param on_exit? fun()
+local function create_client_read_loop(client, on_exit)
+  --- @param body string
+  local function handle_body(body)
+    client:handle_body(body)
+  end
+
+  local function on_error(err)
+    client:on_error(M.client_errors.READ_ERROR, err)
+  end
+
+  return M.create_read_loop(handle_body, on_exit, on_error)
+end
+
 --- Create a LSP RPC client factory that connects to either:
 ---
 ---  - a named pipe (windows)
@@ -623,77 +622,20 @@ end
 ---@param port integer? TCP port to connect to. If absent the first argument must be a pipe
 ---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
 function M.connect(host_or_path, port)
+  validate('host_or_path', host_or_path, 'string')
+  validate('port', port, 'number', true)
+
   return function(dispatchers)
+    validate('dispatchers', dispatchers, 'table', true)
+
     dispatchers = merge_dispatchers(dispatchers)
-    local handle = (
-      port == nil
-        and assert(
-          uv.new_pipe(false),
-          string.format('Pipe with name %s could not be opened.', host_or_path)
-        )
-      or assert(uv.new_tcp(), 'Could not create new TCP socket')
-    )
-    local closing = false
-    -- Connect returns a PublicClient synchronously so the caller
-    -- can immediately send messages before the connection is established
-    -- -> Need to buffer them until that happens
-    local connected = false
-    -- size should be enough because the client can't really do anything until initialization is done
-    -- which required a response from the server - implying the connection got established
-    local msgbuf = vim.ringbuf(10)
-    local transport = {
-      write = function(msg)
-        if connected then
-          local _, err = handle:write(msg)
-          if err and not closing then
-            log.error('Error on handle:write: %q', err)
-          end
-        else
-          msgbuf:push(msg)
-        end
-      end,
-      is_closing = function()
-        return closing
-      end,
-      terminate = function()
-        if not closing then
-          closing = true
-          handle:shutdown()
-          handle:close()
-          dispatchers.on_exit(0, 0)
-        end
-      end,
-    }
+
+    local transport = lsp_transport.TransportConnect.new()
     local client = new_client(dispatchers, transport)
-    local function on_connect(err)
-      if err then
-        local address = port == nil and host_or_path or (host_or_path .. ':' .. port)
-        vim.schedule(function()
-          vim.notify(
-            string.format('Could not connect to %s, reason: %s', address, vim.inspect(err)),
-            vim.log.levels.WARN
-          )
-        end)
-        return
-      end
-      local handle_body = function(body)
-        client:handle_body(body)
-      end
-      handle:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
-        client:on_error(M.client_errors.READ_ERROR, read_err)
-      end))
-      connected = true
-      for msg in msgbuf do
-        handle:write(msg)
-      end
-    end
-    if port == nil then
-      handle:connect(host_or_path, on_connect)
-    else
-      local info = uv.getaddrinfo(host_or_path, nil)
-      local resolved_host = info and info[1] and info[1].addr or host_or_path
-      handle:connect(resolved_host, port, on_connect)
-    end
+    local on_read = create_client_read_loop(client, function()
+      transport:terminate()
+    end)
+    transport:connect(host_or_path, port, on_read, dispatchers.on_exit)
 
     return public_client(client)
   end
@@ -713,83 +655,19 @@ end
 --- @param cmd string[] Command to start the LSP server.
 --- @param dispatchers? vim.lsp.rpc.Dispatchers
 --- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams
---- @return vim.lsp.rpc.PublicClient : Client RPC object, with these methods:
----   - `notify()` |vim.lsp.rpc.notify()|
----   - `request()` |vim.lsp.rpc.request()|
----   - `is_closing()` returns a boolean indicating if the RPC is closing.
----   - `terminate()` terminates the RPC client.
+--- @return vim.lsp.rpc.PublicClient
 function M.start(cmd, dispatchers, extra_spawn_params)
   log.info('Starting RPC client', { cmd = cmd, extra = extra_spawn_params })
 
   validate('cmd', cmd, 'table')
   validate('dispatchers', dispatchers, 'table', true)
 
-  extra_spawn_params = extra_spawn_params or {}
-
-  if extra_spawn_params.cwd then
-    assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
-  end
-
   dispatchers = merge_dispatchers(dispatchers)
 
-  local sysobj ---@type vim.SystemObj
-
-  local client = new_client(dispatchers, {
-    write = function(msg)
-      sysobj:write(msg)
-    end,
-    is_closing = function()
-      return sysobj == nil or sysobj:is_closing()
-    end,
-    terminate = function()
-      sysobj:kill(15)
-    end,
-  })
-
-  local handle_body = function(body)
-    client:handle_body(body)
-  end
-
-  local stdout_handler = M.create_read_loop(handle_body, nil, function(err)
-    client:on_error(M.client_errors.READ_ERROR, err)
-  end)
-
-  local stderr_handler = function(_, chunk)
-    if chunk then
-      log.error('rpc', cmd[1], 'stderr', chunk)
-    end
-  end
-
-  local detached = not is_win
-  if extra_spawn_params.detached ~= nil then
-    detached = extra_spawn_params.detached
-  end
-
-  local ok, sysobj_or_err = pcall(vim.system, cmd, {
-    stdin = true,
-    stdout = stdout_handler,
-    stderr = stderr_handler,
-    cwd = extra_spawn_params.cwd,
-    env = extra_spawn_params.env,
-    detach = detached,
-  }, function(obj)
-    dispatchers.on_exit(obj.code, obj.signal)
-  end)
-
-  if not ok then
-    local err = sysobj_or_err --[[@as string]]
-    local sfx --- @type string
-    if string.match(err, 'ENOENT') then
-      sfx = '. The language server is either not installed, missing from PATH, or not executable.'
-    else
-      sfx = string.format(' with error message: %s', err)
-    end
-    local msg =
-      string.format('Spawning language server with cmd: `%s` failed%s', vim.inspect(cmd), sfx)
-    error(msg)
-  end
-
-  sysobj = sysobj_or_err --[[@as vim.SystemObj]]
+  local transport = lsp_transport.TransportRun.new()
+  local client = new_client(dispatchers, transport)
+  local on_read = create_client_read_loop(client)
+  transport:run(cmd, extra_spawn_params, on_read, dispatchers.on_exit)
 
   return public_client(client)
 end
diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua
index 34c1470fea..b700bcf58f 100644
--- a/scripts/luacats_grammar.lua
+++ b/scripts/luacats_grammar.lua
@@ -160,9 +160,9 @@ local typedef = P({
   return vim.trim(match):gsub('^%((.*)%)$', '%1'):gsub('%?+', '?')
 end
 
-local opt_exact = opt(Cg(Pf('(exact)'), 'access'))
 local access = P('private') + P('protected') + P('package')
 local caccess = Cg(access, 'access')
+local cattr = Cg(comma(access + P('exact')), 'access')
 local desc_delim = Sf '#:' + ws
 local desc = Cg(rep(any), 'desc')
 local opt_desc = opt(desc_delim * desc)
@@ -178,7 +178,7 @@ local grammar = P {
     + annot('type', comma1(Ct(v.ctype)) * opt_desc)
     + annot('cast', ty_name * ws * opt(Sf('+-')) * v.ctype)
     + annot('generic', ty_name * opt(colon * v.ctype))
-    + annot('class', opt_exact * opt(paren(caccess)) * fill * ty_name * opt_parent)
+    + annot('class', opt(paren(cattr)) * fill * ty_name * opt_parent)
     + annot('field', opt(caccess * ws) * v.field_name * ws * v.ctype * opt_desc)
     + annot('operator', ty_name * opt(paren(Cg(v.ctype, 'argtype'))) * colon * v.ctype)
     + annot(access)
-- 
cgit 


From de794f2d24099b73419ce2cd98424f702908040f Mon Sep 17 00:00:00 2001
From: Jeremy Fleischman 
Date: Thu, 12 Dec 2024 02:41:57 -0800
Subject: fix(diagnostic): broken variable reference #31557

---
 runtime/lua/vim/diagnostic.lua | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 62997924d9..ded7a8f89d 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -854,10 +854,11 @@ local function set_list(loclist, opts)
   -- numbers beyond the end of the buffer
   local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], false)
   local items = M.toqflist(diagnostics)
+  local qf_id = nil
   if loclist then
     vim.fn.setloclist(winnr, {}, 'u', { title = title, items = items })
   else
-    local qf_id = get_qf_id_for_title(title)
+    qf_id = get_qf_id_for_title(title)
 
     -- If we already have a diagnostics quickfix, update it rather than creating a new one.
     -- This avoids polluting the finite set of quickfix lists, and preserves the currently selected
@@ -868,16 +869,17 @@ local function set_list(loclist, opts)
       id = qf_id,
     })
   end
+
   if open then
-    if loclist then
-      api.nvim_command('lwindow')
-    else
+    if qf_id then
       -- First navigate to the diagnostics quickfix list.
-      local nr = vim.fn.getqflist({ id = _qf_id, nr = 0 }).nr
+      local nr = vim.fn.getqflist({ id = qf_id, nr = 0 }).nr
       api.nvim_command(nr .. 'chistory')
 
       -- Now open the quickfix list.
       api.nvim_command('botright cwindow')
+    else
+      api.nvim_command('lwindow')
     end
   end
 end
-- 
cgit 


From 17383870dd3b7f04eddd48ed929cc25c7e102277 Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Thu, 12 Dec 2024 18:45:57 +0800
Subject: fix(float): re-sort layers when grid zindex changed #30259

Problem: when zindex is changed in vim.schedule the zindex sort in
layers not changed.

Solution: resort layers when zindex changed.
---
 src/nvim/ui_compositor.c          |  29 +++++++++++
 src/nvim/window.c                 |   6 +++
 test/functional/ui/float_spec.lua | 105 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+)

diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 4cddc3dc82..38d882462d 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -101,6 +101,35 @@ bool ui_comp_should_draw(void)
   return composed_uis != 0 && valid_screen;
 }
 
+/// Raises or lowers the layer, syncing comp_index with zindex.
+///
+/// This function adjusts the position of a layer in the layers array
+/// based on its zindex, either raising or lowering it.
+///
+/// @param[in]  layer_idx  Index of the layer to be raised or lowered.
+/// @param[in]  raise      Raise the layer if true, else lower it.
+void ui_comp_layers_adjust(size_t layer_idx, bool raise)
+{
+  size_t size = layers.size;
+  ScreenGrid *layer = layers.items[layer_idx];
+
+  if (raise) {
+    while (layer_idx < size - 1 && layer->zindex > layers.items[layer_idx + 1]->zindex) {
+      layers.items[layer_idx] = layers.items[layer_idx + 1];
+      layers.items[layer_idx]->comp_index = layer_idx;
+      layer_idx++;
+    }
+  } else {
+    while (layer_idx > 0 && layer->zindex < layers.items[layer_idx - 1]->zindex) {
+      layers.items[layer_idx] = layers.items[layer_idx - 1];
+      layers.items[layer_idx]->comp_index = layer_idx;
+      layer_idx--;
+    }
+  }
+  layers.items[layer_idx] = layer;
+  layer->comp_index = layer_idx;
+}
+
 /// Places `grid` at (col,row) position with (width * height) size.
 /// Adds `grid` as the top layer if it is a new layer.
 ///
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 79c3ce9304..938d9d7618 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -852,7 +852,13 @@ void ui_ext_win_position(win_T *wp, bool validate)
       }
     }
 
+    bool resort = wp->w_grid_alloc.comp_index != 0
+                  && wp->w_grid_alloc.zindex != wp->w_config.zindex;
+    bool raise = resort && wp->w_grid_alloc.zindex < wp->w_config.zindex;
     wp->w_grid_alloc.zindex = wp->w_config.zindex;
+    if (resort) {
+      ui_comp_layers_adjust(wp->w_grid_alloc.comp_index, raise);
+    }
     if (ui_has(kUIMultigrid)) {
       String anchor = cstr_as_string(float_anchor_str[c.anchor]);
       if (!c.hide) {
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 57ef9bcff6..c676e9d5f2 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -8542,6 +8542,111 @@ describe('float window', function()
                                                   |
         ]]}
       end
+
+      --
+      -- Check that floats are positioned correctly after changing the zindexes.
+      --
+      command('fclose')
+      exec_lua([[
+        local win1, win3 = ...
+        vim.api.nvim_win_set_config(win1, { zindex = 400, title = 'win_400', title_pos = 'center', border = 'double' })
+        vim.api.nvim_win_set_config(win3, { zindex = 300, title = 'win_300', title_pos = 'center', border = 'single' })
+      ]], win1, win3)
+      if multigrid then
+        screen:expect({
+          grid = [[
+          ## grid 1
+            [2:----------------------------------------]|*6
+            [3:----------------------------------------]|
+          ## grid 2
+            ^                                        |
+            {0:~                                       }|*5
+          ## grid 3
+                                                    |
+          ## grid 4
+            {5:╔══════}{11:win_400}{5:═══════╗}|
+            {5:║}{7:                    }{5:║}|
+            {5:║}{7:~                   }{5:║}|*2
+            {5:╚════════════════════╝}|
+          ## grid 6
+            {5:┌──────}{11:win_300}{5:───────┐}|
+            {5:│}{8:                    }{5:│}|
+            {5:│}{8:~                   }{5:│}|*2
+            {5:└────────────────────┘}|
+          ]], float_pos={
+          [4] = {1001, "NW", 1, 1, 5, true, 400};
+          [6] = {1003, "NW", 1, 3, 7, true, 300};
+        }, win_viewport={
+          [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+          [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+          [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+        }, win_viewport_margins={
+          [2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 },
+          [4] = { bottom = 1, left = 1, right = 1, top = 1, win = 1001 },
+          [6] = { bottom = 1, left = 1, right = 1, top = 1, win = 1003 }
+        }})
+      else
+        screen:expect({
+          grid = [[
+            ^                                        |
+            {0:~    }{5:╔══════}{11:win_400}{5:═══════╗}{0:             }|
+            {0:~    }{5:║}{7:                    }{5:║─┐}{0:           }|
+            {0:~    }{5:║}{7:~                   }{5:║}{8: }{5:│}{0:           }|*2
+            {0:~    }{5:╚════════════════════╝}{8: }{5:│}{0:           }|
+                   {5:└────────────────────┘}           |
+          ]]
+        })
+      end
+      exec_lua([[
+        local win1, win3 = ...
+        vim.api.nvim_win_set_config(win1, { zindex = 100, title='win_100' })
+        vim.api.nvim_win_set_config(win3, { zindex = 150, title='win_150' })
+      ]], win1, win3)
+      if multigrid then
+        screen:expect({
+          grid = [[
+          ## grid 1
+            [2:----------------------------------------]|*6
+            [3:----------------------------------------]|
+          ## grid 2
+            ^                                        |
+            {0:~                                       }|*5
+          ## grid 3
+                                                    |
+          ## grid 4
+            {5:╔}{11:win_100}{5:═════════════╗}|
+            {5:║}{7:                    }{5:║}|
+            {5:║}{7:~                   }{5:║}|*2
+            {5:╚════════════════════╝}|
+          ## grid 6
+            {5:┌}{11:win_150}{5:─────────────┐}|
+            {5:│}{8:                    }{5:│}|
+            {5:│}{8:~                   }{5:│}|*2
+            {5:└────────────────────┘}|
+          ]], float_pos={
+          [4] = {1001, "NW", 1, 1, 5, true, 100};
+          [6] = {1003, "NW", 1, 3, 7, true, 150};
+        }, win_viewport={
+          [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+          [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+          [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+        }, win_viewport_margins={
+          [2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 },
+          [4] = { bottom = 1, left = 1, right = 1, top = 1, win = 1001 },
+          [6] = { bottom = 1, left = 1, right = 1, top = 1, win = 1003 }
+        }})
+      else
+        screen:expect({
+          grid = [[
+            ^                                        |
+            {0:~    }{5:╔}{11:w}{5:┌}{11:win_150}{5:─────────────┐}{0:           }|
+            {0:~    }{5:║}{7: }{5:│}{8:                    }{5:│}{0:           }|
+            {0:~    }{5:║}{7:~}{5:│}{8:~                   }{5:│}{0:           }|*2
+            {0:~    }{5:╚═└────────────────────┘}{0:           }|
+                                                    |
+          ]]
+        })
+      end
     end)
 
     it('can use winbar', function()
-- 
cgit 


From 2e73ba102ac65f08fdbb6d2b87e90390abdca10c Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Thu, 12 Dec 2024 22:16:05 +0100
Subject: vim-patch:9.1.0919: filetype: some assembler files are not recognized

Problem:  filetype: some assembler are files not recognized
Solution: detect '*.nasm' files as nasm filetype and '*.masm' as masm
          filetype (Wu, Zhenyu)

closes: vim/vim#16194

https://github.com/vim/vim/commit/d66d68763d0947c292a9fdda4da6fda3650fa563

Co-authored-by: Wu, Zhenyu 
---
 runtime/doc/syntax.txt             | 7 +++++--
 runtime/lua/vim/filetype.lua       | 2 ++
 test/old/testdir/test_filetype.vim | 3 +++
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 75a855bbdd..c5d3422f62 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -414,12 +414,15 @@ There are many types of assembly languages that all use the same file name
 extensions.  Therefore you will have to select the type yourself, or add a
 line in the assembly file that Vim will recognize.  Currently these syntax
 files are included:
-	asm		GNU assembly (the default)
+	asm		GNU assembly (usually have .s or .S extension and were
+			already built using C compiler such as GCC or CLANG)
 	asm68k		Motorola 680x0 assembly
 	asmh8300	Hitachi H-8300 version of GNU assembly
 	ia64		Intel Itanium 64
 	fasm		Flat assembly (https://flatassembler.net)
-	masm		Microsoft assembly (probably works for any 80x86)
+	masm		Microsoft assembly (.masm files are compiled with
+			Microsoft's Macro Assembler. This is only supported
+			for x86, x86_64, ARM and AARCH64 CPU families)
 	nasm		Netwide assembly
 	tasm		Turbo Assembly (with opcodes 80x86 up to Pentium, and
 			MMX)
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index b983e7d1c6..8a51d2a9d3 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -743,6 +743,7 @@ local extension = {
   mkd = detect.markdown,
   markdown = detect.markdown,
   mdown = detect.markdown,
+  masm = 'masm',
   mhtml = 'mason',
   mason = 'mason',
   master = 'master',
@@ -805,6 +806,7 @@ local extension = {
   n1ql = 'n1ql',
   nql = 'n1ql',
   nanorc = 'nanorc',
+  nasm = 'nasm',
   NSA = 'natural',
   NSC = 'natural',
   NSG = 'natural',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 7e6a6dfac5..d1551885b1 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -114,6 +114,7 @@ func s:GetFilenameChecks() abort
     \ 'arduino': ['file.ino', 'file.pde'],
     \ 'art': ['file.art'],
     \ 'asciidoc': ['file.asciidoc', 'file.adoc'],
+    \ 'asm': ['file.s', 'file.S', 'file.a', 'file.A'],
     \ 'asn': ['file.asn', 'file.asn1'],
     \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'],
     \ 'astro': ['file.astro'],
@@ -453,6 +454,7 @@ func s:GetFilenameChecks() abort
     \ 'map': ['file.map'],
     \ 'maple': ['file.mv', 'file.mpl', 'file.mws'],
     \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'],
+    \ 'masm': ['file.masm'],
     \ 'mason': ['file.mason', 'file.mhtml'],
     \ 'master': ['file.mas', 'file.master'],
     \ 'matlab': ['file.m'],
@@ -529,6 +531,7 @@ func s:GetFilenameChecks() abort
     \ 'n1ql': ['file.n1ql', 'file.nql'],
     \ 'named': ['namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'],
     \ 'nanorc': ['/etc/nanorc', 'file.nanorc', 'any/etc/nanorc'],
+    \ 'nasm': ['file.nasm'],
     \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'],
     \ 'ncf': ['file.ncf'],
     \ 'neomuttlog': ['/home/user/.neomuttdebug1'],
-- 
cgit 


From 3e3a9843004683db142bd17f9bf36a5dab7b7ac8 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Thu, 12 Dec 2024 22:15:06 +0100
Subject: vim-patch:5c42c77: runtime(netrw): do not pollute search history with
 symlinks

fixes: vim/vim#16206

https://github.com/vim/vim/commit/5c42c7731536418c53273932d7ef76b80b001f38

Co-authored-by: Christian Brabandt 
---
 runtime/autoload/netrw.vim | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index cbe0b29620..116e105b57 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -40,6 +40,7 @@
 "   2024 Nov 23 by Vim Project: fix powershell escaping issues (#16094)
 "   2024 Dec 04 by Vim Project: do not detach for gvim (#16168)
 "   2024 Dec 08 by Vim Project: check the first arg of netrw_browsex_viewer for being executable (#16185)
+"   2024 Dec 12 by Vim Project: do not pollute the search history (#16206)
 "   }}}
 " Former Maintainer:	Charles E Campbell
 " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
@@ -11759,7 +11760,8 @@ endfun
 " s:ShowLink: used to modify thin and tree listings to show links {{{2
 fun! s:ShowLink()
   if exists("b:netrw_curdir")
-   norm! $?\a
+   keepp :norm! $?\a
+   "call histdel("/",-1)
    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop")
     let basedir = s:NetrwTreePath(w:netrw_treetop)
    else
-- 
cgit 


From 8a0b203875610ce61539492c5aceb9c72e959806 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 13 Dec 2024 19:41:59 +0800
Subject: vim-patch:9.1.0921: popupmenu logic is a bit convoluted

Problem:  popupmenu logic is a bit convoluted
Solution: slightly refactor logic and use MIN/MAX() macros to simplify
          (glepnir)

Define the MAX/MIN macros. Since we support some older platforms, C
compilers may not be as smart. This helps reduce unnecessary if
statements and redundant ternary expressions. Pre-calculate some
expressions by defining variables. Remove unnecessary parentheses.
Adjust certain lines to avoid exceeding 80 columns.

closes: vim/vim#16205

https://github.com/vim/vim/commit/c942f84aadffd0c8969ecf81e3e9103722b2714f

Co-authored-by: glepnir 
---
 src/nvim/popupmenu.c | 62 ++++++++++++++--------------------------------------
 1 file changed, 16 insertions(+), 46 deletions(-)

diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 8bf145f520..744b2fd82e 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -234,12 +234,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
     }
 
     // Figure out the size and position of the pum.
-    if (size < PUM_DEF_HEIGHT) {
-      pum_height = size;
-    } else {
-      pum_height = PUM_DEF_HEIGHT;
-    }
-
+    pum_height = MIN(size, PUM_DEF_HEIGHT);
     if (p_ph > 0 && pum_height > p_ph) {
       pum_height = (int)p_ph;
     }
@@ -256,11 +251,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
         context_lines = 0;
       } else {
         // Leave two lines of context if possible
-        if (curwin->w_wrow - curwin->w_cline_row >= 2) {
-          context_lines = 2;
-        } else {
-          context_lines = curwin->w_wrow - curwin->w_cline_row;
-        }
+        context_lines = MIN(2, curwin->w_wrow - curwin->w_cline_row);
       }
 
       if (pum_win_row - min_row >= size + context_lines) {
@@ -285,20 +276,13 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
       } else {
         // Leave two lines of context if possible
         validate_cheight(curwin);
-        if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) {
-          context_lines = 3;
-        } else {
-          context_lines = curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow;
-        }
+        int cline_visible_offset = curwin->w_cline_row +
+                                   curwin->w_cline_height - curwin->w_wrow;
+        context_lines = MIN(3, cline_visible_offset);
       }
 
       pum_row = pum_win_row + context_lines;
-      if (size > below_row - pum_row) {
-        pum_height = below_row - pum_row;
-      } else {
-        pum_height = size;
-      }
-
+      pum_height = MIN(below_row - pum_row, size);
       if (p_ph > 0 && pum_height > p_ph) {
         pum_height = (int)p_ph;
       }
@@ -353,15 +337,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
         pum_width = max_col - pum_col - pum_scrollbar;
       }
 
-      if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
-          && pum_width > p_pw) {
-        // the width is more than needed for the items, make it
-        // narrower
-        pum_width = max_width + pum_kind_width + pum_extra_width + 1;
-
-        if (pum_width < p_pw) {
-          pum_width = (int)p_pw;
-        }
+      int content_width = max_width + pum_kind_width + pum_extra_width + 1;
+      if (pum_width > content_width && pum_width > p_pw) {
+        // Reduce width to fit item
+        pum_width = MAX(content_width, (int)p_pw);
       } else if (((cursor_col - min_col > p_pw
                    || cursor_col - min_col > max_width) && !pum_rl)
                  || (pum_rl && (cursor_col < max_col - p_pw
@@ -373,13 +352,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
             pum_col = max_col - 1;
           }
         } else if (!pum_rl) {
-          if (win_start_col > max_col - max_width - pum_scrollbar
-              && max_width <= p_pw) {
+          int right_edge_col = max_col - max_width - pum_scrollbar;
+          if (win_start_col > right_edge_col && max_width <= p_pw) {
             // use full width to end of the screen
-            pum_col = max_col - max_width - pum_scrollbar;
-            if (pum_col < min_col) {
-              pum_col = min_col;
-            }
+            pum_col = MAX(min_col, right_edge_col);
           }
         }
 
@@ -400,12 +376,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
               pum_width = max_col - pum_col - 1;
             }
           }
-        } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
-                   && pum_width > p_pw) {
-          pum_width = max_width + pum_kind_width + pum_extra_width + 1;
-          if (pum_width < p_pw) {
-            pum_width = (int)p_pw;
-          }
+        } else if (pum_width > content_width && pum_width > p_pw) {
+          pum_width = MIN(content_width, (int)p_pw);
         }
       }
     } else if (max_col - min_col < def_width) {
@@ -997,9 +969,7 @@ static bool pum_set_selected(int n, int repeat)
       }
     }
     // adjust for the number of lines displayed
-    if (pum_first > pum_size - pum_height) {
-      pum_first = pum_size - pum_height;
-    }
+    pum_first = MIN(pum_first, pum_size - pum_height);
 
     // Show extra info in the preview window if there is something and
     // 'completeopt' contains "preview".
-- 
cgit 


From 1386d36e7687edb1b65e338045eaff37ab32d582 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 13 Dec 2024 21:33:06 +0800
Subject: vim-patch:9.1.092: vim-patch:9.1.0923: wrong MIN macro in popupmenu.c

Problem:  wrong MIN macro in popupmenu.c (after v9.1.0921)
          (zeertzjq)
Solution: change it to MAX()

https://github.com/vim/vim/commit/618c4d36ca92a62212a37e787c202229ceff8537

Co-authored-by: Christian Brabandt 
Co-authored-by: glepnir 
---
 src/nvim/popupmenu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 744b2fd82e..5dc9ddfd60 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -377,7 +377,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
             }
           }
         } else if (pum_width > content_width && pum_width > p_pw) {
-          pum_width = MIN(content_width, (int)p_pw);
+          pum_width = MAX(content_width, (int)p_pw);
         }
       }
     } else if (max_col - min_col < def_width) {
-- 
cgit 


From 7940ec69136fa992c98aa7b37265fbc2e619232e Mon Sep 17 00:00:00 2001
From: Luca Saccarola <96259932+saccarosium@users.noreply.github.com>
Date: Fri, 13 Dec 2024 15:26:20 +0100
Subject: fix(man.lua): `:Man ` does not complete #31569

closes: #31512
---
 runtime/lua/man.lua | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index 114c94f9e5..e5343234fc 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -576,9 +576,8 @@ function M.man_complete(arg_lead, cmd_line, _)
   end
 
   if #args == 1 then
-    -- returning full completion is laggy. Require some arg_lead to complete
-    -- return complete('', '', '')
-    return {}
+    -- XXX: This (full completion) is laggy, but without it tab-complete is broken. #31512
+    return complete('', '', '')
   end
 
   if arg_lead:match('^[^()]+%([^()]*$') then
-- 
cgit 


From 9c20342297391c4076809964e799f2c7705b819b Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 13 Dec 2024 10:51:33 +0000
Subject: fix(lsp): reuse client if configs match and no root dir

Problem:
An LSP configuration that creates client with no root_dir or
workspace_folders can result in vim.lsp.enable attaching to it multiple
times.

Solution:
When checking existing clients, reuse a client if it wasn't initially
configured have any workspace_folders. This more closely matches the
behaviour we had prior to d9235ef
---
 runtime/lua/vim/lsp.lua                        | 17 +++++++++-----
 test/functional/plugin/lsp/completion_spec.lua | 17 +++++++-------
 test/functional/plugin/lsp_spec.lua            | 32 ++++++++++++++++++++++++--
 3 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 596e1b609b..6a8c3d1ff3 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -201,23 +201,28 @@ local function reuse_client_default(client, config)
   end
 
   local config_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir)
-    or {}
-  local config_folders_included = 0
 
-  if not next(config_folders) then
-    return false
+  if not config_folders or not next(config_folders) then
+    -- Reuse if the client was configured with no workspace folders
+    local client_config_folders =
+      lsp._get_workspace_folders(client.config.workspace_folders or client.config.root_dir)
+    return not client_config_folders or not next(client_config_folders)
   end
 
   for _, config_folder in ipairs(config_folders) do
+    local found = false
     for _, client_folder in ipairs(client.workspace_folders) do
       if config_folder.uri == client_folder.uri then
-        config_folders_included = config_folders_included + 1
+        found = true
         break
       end
     end
+    if not found then
+      return false
+    end
   end
 
-  return config_folders_included == #config_folders
+  return true
 end
 
 --- Reset defaults set by `set_defaults`.
diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua
index 39b6ddc105..15ac9da657 100644
--- a/test/functional/plugin/lsp/completion_spec.lua
+++ b/test/functional/plugin/lsp/completion_spec.lua
@@ -731,9 +731,10 @@ describe('vim.lsp.completion: item conversion', function()
   )
 end)
 
+--- @param name string
 --- @param completion_result lsp.CompletionList
 --- @return integer
-local function create_server(completion_result)
+local function create_server(name, completion_result)
   return exec_lua(function()
     local server = _G._create_server({
       capabilities = {
@@ -751,7 +752,7 @@ local function create_server(completion_result)
     local bufnr = vim.api.nvim_get_current_buf()
     vim.api.nvim_win_set_buf(0, bufnr)
     return vim.lsp.start({
-      name = 'dummy',
+      name = name,
       cmd = server.cmd,
       on_attach = function(client, bufnr0)
         vim.lsp.completion.enable(true, client.id, bufnr0, {
@@ -800,7 +801,7 @@ describe('vim.lsp.completion: protocol', function()
   end
 
   it('fetches completions and shows them using complete on trigger', function()
-    create_server({
+    create_server('dummy', {
       isIncomplete = false,
       items = {
         {
@@ -892,7 +893,7 @@ describe('vim.lsp.completion: protocol', function()
   end)
 
   it('merges results from multiple clients', function()
-    create_server({
+    create_server('dummy1', {
       isIncomplete = false,
       items = {
         {
@@ -900,7 +901,7 @@ describe('vim.lsp.completion: protocol', function()
         },
       },
     })
-    create_server({
+    create_server('dummy2', {
       isIncomplete = false,
       items = {
         {
@@ -933,7 +934,7 @@ describe('vim.lsp.completion: protocol', function()
         },
       },
     }
-    local client_id = create_server(completion_list)
+    local client_id = create_server('dummy', completion_list)
 
     exec_lua(function()
       _G.called = false
@@ -970,7 +971,7 @@ describe('vim.lsp.completion: protocol', function()
   end)
 
   it('enable(…,{convert=fn}) custom word/abbr format', function()
-    create_server({
+    create_server('dummy', {
       isIncomplete = false,
       items = {
         {
@@ -1012,7 +1013,7 @@ describe('vim.lsp.completion: integration', function()
     exec_lua(function()
       vim.o.completeopt = 'menuone,noselect'
     end)
-    create_server(completion_list)
+    create_server('dummy', completion_list)
     feed('i world0ih')
     retry(nil, nil, function()
       eq(
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 79952cb933..d2ef166983 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -5184,8 +5184,8 @@ describe('LSP', function()
         local win = vim.api.nvim_get_current_win()
         vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' })
         vim.api.nvim_win_set_cursor(win, { 3, 6 })
-        local client_id1 = assert(vim.lsp.start({ name = 'dummy', cmd = server1.cmd }))
-        local client_id2 = assert(vim.lsp.start({ name = 'dummy', cmd = server2.cmd }))
+        local client_id1 = assert(vim.lsp.start({ name = 'dummy1', cmd = server1.cmd }))
+        local client_id2 = assert(vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }))
         local response
         vim.lsp.buf.definition({
           on_list = function(r)
@@ -6203,5 +6203,33 @@ describe('LSP', function()
         end)
       )
     end)
+
+    it('does not attach to buffers more than once if no root_dir', function()
+      exec_lua(create_server_definition)
+
+      local tmp1 = t.tmpname(true)
+
+      eq(
+        1,
+        exec_lua(function()
+          local server = _G._create_server({
+            handlers = {
+              initialize = function(_, _, callback)
+                callback(nil, { capabilities = {} })
+              end,
+            },
+          })
+
+          vim.lsp.config('foo', { cmd = server.cmd, filetypes = { 'foo' } })
+          vim.lsp.enable('foo')
+
+          vim.cmd.edit(assert(tmp1))
+          vim.bo.filetype = 'foo'
+          vim.bo.filetype = 'foo'
+
+          return #vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })
+        end)
+      )
+    end)
   end)
 end)
-- 
cgit 


From 65b1733405e558e80527096a0ba42b5a678c9b54 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Fri, 13 Dec 2024 06:46:40 -0800
Subject: Revert "fix(Man.lua): trigger completion even without arguments"
 #31572

This reverts commit 7940ec69136fa992c98aa7b37265fbc2e619232e.
---
 runtime/lua/man.lua | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index e5343234fc..114c94f9e5 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -576,8 +576,9 @@ function M.man_complete(arg_lead, cmd_line, _)
   end
 
   if #args == 1 then
-    -- XXX: This (full completion) is laggy, but without it tab-complete is broken. #31512
-    return complete('', '', '')
+    -- returning full completion is laggy. Require some arg_lead to complete
+    -- return complete('', '', '')
+    return {}
   end
 
   if arg_lead:match('^[^()]+%([^()]*$') then
-- 
cgit 


From f9dd6826210335d8b37455002d767d1b37c09ce4 Mon Sep 17 00:00:00 2001
From: Colin Kennedy 
Date: Wed, 11 Dec 2024 01:01:14 -0800
Subject: docs(annotations): added `---@generic` support

---
 runtime/doc/builtin.txt         | 24 ++++++++++++------------
 runtime/lua/vim/_meta/vimfn.lua | 29 +++++++++++++++++------------
 scripts/gen_eval_files.lua      |  4 ++++
 src/nvim/eval.lua               | 29 ++++++++++++++++++++++-------
 4 files changed, 55 insertions(+), 31 deletions(-)

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 398947a31f..5f74080e0c 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1305,10 +1305,10 @@ copy({expr})                                                            *copy()*
 		Also see |deepcopy()|.
 
                 Parameters: ~
-                  • {expr} (`any`)
+                  • {expr} (`T`)
 
                 Return: ~
-                  (`any`)
+                  (`T`)
 
 cos({expr})                                                              *cos()*
 		Return the cosine of {expr}, measured in radians, as a |Float|.
@@ -1490,11 +1490,11 @@ deepcopy({expr} [, {noref}])                                   *deepcopy()* *E69
 		Also see |copy()|.
 
                 Parameters: ~
-                  • {expr} (`any`)
+                  • {expr} (`T`)
                   • {noref} (`boolean?`)
 
                 Return: ~
-                  (`any`)
+                  (`T`)
 
 delete({fname} [, {flags}])                                           *delete()*
 		Without {flags} or with {flags} empty: Deletes the file by the
@@ -5281,7 +5281,7 @@ items({dict})                                                          *items()*
 		the index.
 
                 Parameters: ~
-                  • {dict} (`any`)
+                  • {dict} (`table`)
 
                 Return: ~
                   (`any`)
@@ -6567,7 +6567,7 @@ max({expr})                                                              *max()*
                   • {expr} (`any`)
 
                 Return: ~
-                  (`any`)
+                  (`number`)
 
 menu_get({path} [, {modes}])                                        *menu_get()*
 		Returns a |List| of |Dictionaries| describing |menus| (defined
@@ -7733,11 +7733,11 @@ reduce({object}, {func} [, {initial}])                           *reduce()* *E99
 
                 Parameters: ~
                   • {object} (`any`)
-                  • {func} (`function`)
+                  • {func} (`fun(accumulator: T, current: any): any`)
                   • {initial} (`any?`)
 
                 Return: ~
-                  (`any`)
+                  (`T`)
 
 reg_executing()                                                *reg_executing()*
 		Returns the single letter name of the register being executed.
@@ -7951,10 +7951,10 @@ reverse({object})                                                    *reverse()*
 <
 
                 Parameters: ~
-                  • {object} (`any`)
+                  • {object} (`T[]`)
 
                 Return: ~
-                  (`any`)
+                  (`T[]`)
 
 round({expr})                                                          *round()*
 		Round off {expr} to the nearest integral value and return it
@@ -9952,12 +9952,12 @@ sort({list} [, {how} [, {dict}]])                                  *sort()* *E70
 <
 
                 Parameters: ~
-                  • {list} (`any`)
+                  • {list} (`T[]`)
                   • {how} (`string|function?`)
                   • {dict} (`any?`)
 
                 Return: ~
-                  (`any`)
+                  (`T[]`)
 
 soundfold({word})                                                  *soundfold()*
 		Return the sound-folded equivalent of {word}.  Uses the first
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 77c78f03d4..cf1beda15f 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -1147,8 +1147,9 @@ function vim.fn.confirm(msg, choices, default, type) end
 --- A |Dictionary| is copied in a similar way as a |List|.
 --- Also see |deepcopy()|.
 ---
---- @param expr any
---- @return any
+--- @generic T
+--- @param expr T
+--- @return T
 function vim.fn.copy(expr) end
 
 --- Return the cosine of {expr}, measured in radians, as a |Float|.
@@ -1308,9 +1309,10 @@ function vim.fn.debugbreak(pid) end
 --- {noref} set to 1 will fail.
 --- Also see |copy()|.
 ---
---- @param expr any
+--- @generic T
+--- @param expr T
 --- @param noref? boolean
---- @return any
+--- @return T
 function vim.fn.deepcopy(expr, noref) end
 
 --- Without {flags} or with {flags} empty: Deletes the file by the
@@ -4769,7 +4771,7 @@ function vim.fn.isnan(expr) end
 --- cases, items() returns a List with the index and the value at
 --- the index.
 ---
---- @param dict any
+--- @param dict table
 --- @return any
 function vim.fn.items(dict) end
 
@@ -5952,7 +5954,7 @@ function vim.fn.matchstrpos(expr, pat, start, count) end
 --- an error.  An empty |List| or |Dictionary| results in zero.
 ---
 --- @param expr any
---- @return any
+--- @return number
 function vim.fn.max(expr) end
 
 --- Returns a |List| of |Dictionaries| describing |menus| (defined
@@ -7016,10 +7018,11 @@ function vim.fn.readfile(fname, type, max) end
 ---   echo reduce('xyz', { acc, val -> acc .. ',' .. val })
 --- <
 ---
+--- @generic T
 --- @param object any
---- @param func function
+--- @param func fun(accumulator: T, current: any): any
 --- @param initial? any
---- @return any
+--- @return T
 function vim.fn.reduce(object, func, initial) end
 
 --- Returns the single letter name of the register being executed.
@@ -7215,8 +7218,9 @@ function vim.fn.resolve(filename) end
 ---   let revlist = reverse(copy(mylist))
 --- <
 ---
---- @param object any
---- @return any
+--- @generic T
+--- @param object T[]
+--- @return T[]
 function vim.fn.reverse(object) end
 
 --- Round off {expr} to the nearest integral value and return it
@@ -9079,10 +9083,11 @@ function vim.fn.sockconnect(mode, address, opts) end
 ---   eval mylist->sort({i1, i2 -> i1 - i2})
 --- <
 ---
---- @param list any
+--- @generic T
+--- @param list T[]
 --- @param how? string|function
 --- @param dict? any
---- @return any
+--- @return T[]
 function vim.fn.sort(list, how, dict) end
 
 --- Return the sound-folded equivalent of {word}.  Uses the first
diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua
index de9df5054f..0970ae503a 100755
--- a/scripts/gen_eval_files.lua
+++ b/scripts/gen_eval_files.lua
@@ -496,6 +496,10 @@ local function render_eval_meta(f, fun, write)
     end
   end
 
+  for _, text in ipairs(vim.fn.reverse(fun.generics or {})) do
+    write(fmt('--- @generic %s', text))
+  end
+
   local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0
 
   for i, param in ipairs(params) do
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index cf80a26e1c..9de189cc16 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -17,6 +17,7 @@
 --- @field deprecated? true
 --- @field returns? string|false
 --- @field returns_desc? string
+--- @field generics? string[] Used to write `---@generic` annotations over a function.
 --- @field signature? string
 --- @field desc? string
 --- @field params [string, string, string][]
@@ -1521,9 +1522,10 @@ M.funcs = {
       A |Dictionary| is copied in a similar way as a |List|.
       Also see |deepcopy()|.
     ]=],
+    generics = { 'T' },
     name = 'copy',
-    params = { { 'expr', 'any' } },
-    returns = 'any',
+    params = { { 'expr', 'T' } },
+    returns = 'T',
     signature = 'copy({expr})',
   },
   cos = {
@@ -1739,8 +1741,10 @@ M.funcs = {
       Also see |copy()|.
 
     ]=],
+    generics = { 'T' },
     name = 'deepcopy',
-    params = { { 'expr', 'any' }, { 'noref', 'boolean' } },
+    params = { { 'expr', 'T' }, { 'noref', 'boolean' } },
+    returns = 'T',
     signature = 'deepcopy({expr} [, {noref}])',
   },
   delete = {
@@ -5894,7 +5898,7 @@ M.funcs = {
       the index.
     ]=],
     name = 'items',
-    params = { { 'dict', 'any' } },
+    params = { { 'dict', 'table' } },
     signature = 'items({dict})',
   },
   jobclose = {
@@ -7309,6 +7313,7 @@ M.funcs = {
     ]=],
     name = 'max',
     params = { { 'expr', 'any' } },
+    returns = 'number',
     signature = 'max({expr})',
   },
   menu_get = {
@@ -8520,7 +8525,13 @@ M.funcs = {
       <
     ]=],
     name = 'reduce',
-    params = { { 'object', 'any' }, { 'func', 'function' }, { 'initial', 'any' } },
+    generics = { 'T' },
+    params = {
+      { 'object', 'any' },
+      { 'func', 'fun(accumulator: T, current: any): any' },
+      { 'initial', 'any' },
+    },
+    returns = 'T',
     signature = 'reduce({object}, {func} [, {initial}])',
   },
   reg_executing = {
@@ -8785,7 +8796,9 @@ M.funcs = {
       <
     ]=],
     name = 'reverse',
-    params = { { 'object', 'any' } },
+    generics = { 'T' },
+    params = { { 'object', 'T[]' } },
+    returns = 'T[]',
     signature = 'reverse({object})',
   },
   round = {
@@ -10924,7 +10937,9 @@ M.funcs = {
       <
     ]=],
     name = 'sort',
-    params = { { 'list', 'any' }, { 'how', 'string|function' }, { 'dict', 'any' } },
+    generics = { 'T' },
+    params = { { 'list', 'T[]' }, { 'how', 'string|function' }, { 'dict', 'any' } },
+    returns = 'T[]',
     signature = 'sort({list} [, {how} [, {dict}]])',
   },
   soundfold = {
-- 
cgit 


From 433b342baa04b35fb1ab24d4ef38f126d12ea714 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Sat, 14 Dec 2024 19:21:50 +0100
Subject: feat(ui): sign/statuscolumn can combine highlight attrs #31575

Problem:
Since e049c6e4c08a, most statusline-like UI elements can combine
highlight attrs, except for sign/statuscolumn.

Solution:
Implement for sign/statuscolumn.
---
 runtime/doc/news.txt                     |   9 +-
 src/nvim/drawline.c                      |  11 +-
 test/functional/api/extmark_spec.lua     |   8 +-
 test/functional/legacy/signs_spec.lua    |   9 +-
 test/functional/ui/decorations_spec.lua  | 271 +++++++++++++++----------------
 test/functional/ui/float_spec.lua        |  22 +--
 test/functional/ui/sign_spec.lua         | 153 +++++++++--------
 test/functional/ui/statuscolumn_spec.lua |  21 ++-
 8 files changed, 265 insertions(+), 239 deletions(-)

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index e7d4b92f7e..5e70d75cfd 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -307,7 +307,7 @@ UI
   which controls the tool used to open the given path or URL. If you want to
   globally set this, you can override vim.ui.open using the same approach
   described at |vim.paste()|.
-- `vim.ui.open()` now supports
+• `vim.ui.open()` now supports
   [lemonade](https://github.com/lemonade-command/lemonade) as an option for
   opening urls/files. This is handy if you are in an ssh connection and use
   `lemonade`.
@@ -317,7 +317,6 @@ UI
   |hl-PmenuMatch|.
 • |vim.diagnostic.setqflist()| updates an existing quickfix list with the
   given title if found
-
 • |ui-messages| content chunks now also contain the highlight group ID.
 
 ==============================================================================
@@ -339,9 +338,9 @@ These existing features changed their behavior.
   more emoji characters than before, including those encoded with multiple
   emoji codepoints combined with ZWJ (zero width joiner) codepoints.
 
-• Text in the 'statusline', 'tabline', and 'winbar' now inherits highlights
-  from the respective |hl-StatusLine|, |hl-TabLine|, and |hl-WinBar| highlight
-  groups.
+• Custom highlights in 'rulerformat', 'statuscolumn', 'statusline', 'tabline',
+  'winbar' and the number column (through |:sign-define| `numhl`) now combine
+  with their respective highlight groups, as opposed to |hl-Normal|.
 
 • |vim.on_key()| callbacks won't be invoked recursively when a callback itself
   consumes input.
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index bf14ce1d4a..b8f21d7454 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -462,10 +462,12 @@ void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, in
 static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, int sign_cul_attr)
 {
   SignTextAttrs sattr = wlv->sattrs[sign_idx];
+  int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
 
   if (sattr.text[0] && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
     int attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr)
                ? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0;
+    attr = hl_combine_attr(scl_attr, attr);
     int fill = nrcol ? number_width(wp) + 1 : SIGN_WIDTH;
     draw_col_fill(wlv, schar_from_ascii(' '), fill, attr);
     int sign_pos = wlv->off - SIGN_WIDTH - (int)nrcol;
@@ -474,8 +476,7 @@ static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, i
     linebuf_char[sign_pos + 1] = sattr.text[1];
   } else {
     assert(!nrcol);  // handled in draw_lnum_col()
-    int attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
-    draw_col_fill(wlv, schar_from_ascii(' '), SIGN_WIDTH, attr);
+    draw_col_fill(wlv, schar_from_ascii(' '), SIGN_WIDTH, scl_attr);
   }
 }
 
@@ -559,8 +560,8 @@ static void draw_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int
     } else {
       // Draw the line number (empty space after wrapping).
       int width = number_width(wp) + 1;
-      int attr = (sign_num_attr > 0 && wlv->filler_todo <= 0)
-                 ? sign_num_attr : get_line_number_attr(wp, wlv);
+      int attr = hl_combine_attr(get_line_number_attr(wp, wlv),
+                                 wlv->filler_todo <= 0 ? sign_num_attr : 0);
       if (wlv->row == wlv->startrow + wlv->filler_lines
           && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) {
         char buf[32];
@@ -640,7 +641,7 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir
     draw_col_buf(wp, wlv, transbuf, translen, attr, false);
     p = sp->start;
     int hl = sp->userhl;
-    attr = hl < 0 ? syn_id2attr(-hl) : stcp->num_attr;
+    attr = hl < 0 ? hl_combine_attr(stcp->num_attr, syn_id2attr(-hl)) : stcp->num_attr;
   }
   size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true);
   draw_col_buf(wp, wlv, transbuf, translen, attr, false);
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
index 49c55288e8..6a94881093 100644
--- a/test/functional/api/extmark_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -1731,7 +1731,7 @@ describe('API/extmarks', function()
     -- mark with invalidate is removed
     command('d2')
     screen:expect([[
-      S2^aaa bbb ccc                           |
+      {7:S2}^aaa bbb ccc                           |
       {7:  }aaa bbb ccc                           |*3
       {7:  }                                      |
                                               |
@@ -1739,9 +1739,9 @@ describe('API/extmarks', function()
     -- mark is restored with undo_restore == true
     command('silent undo')
     screen:expect([[
-      S1{7:  }^aaa bbb ccc                         |
-      S2S1aaa bbb ccc                         |
-      S2{7:  }aaa bbb ccc                         |
+      {7:S1  }^aaa bbb ccc                         |
+      {7:S2S1}aaa bbb ccc                         |
+      {7:S2  }aaa bbb ccc                         |
       {7:    }aaa bbb ccc                         |*2
                                               |
     ]])
diff --git a/test/functional/legacy/signs_spec.lua b/test/functional/legacy/signs_spec.lua
index ac7c8cd0bc..af355f27e6 100644
--- a/test/functional/legacy/signs_spec.lua
+++ b/test/functional/legacy/signs_spec.lua
@@ -26,6 +26,9 @@ describe('signs', function()
   -- oldtest: Test_sign_cursor_position()
   it('are drawn correctly', function()
     local screen = Screen.new(75, 6)
+    screen:add_extra_attr_ids({
+      [100] = { foreground = Screen.colors.Blue4, background = Screen.colors.Yellow },
+    })
     exec([[
       call setline(1, [repeat('x', 75), 'mmmm', 'yyyy'])
       call cursor(2,1)
@@ -37,7 +40,7 @@ describe('signs', function()
     screen:expect([[
       {7:  }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
       {7:  }xx                                                                       |
-      {10:=>}^mmmm                                                                     |
+      {100:=>}^mmmm                                                                     |
       {7:  }yyyy                                                                     |
       {1:~                                                                          }|
                                                                                  |
@@ -48,7 +51,7 @@ describe('signs', function()
     screen:expect([[
       {7:  }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
       {7:  }xx                                                                       |
-      {10:-)}^mmmm                                                                     |
+      {100:-)}^mmmm                                                                     |
       {7:  }yyyy                                                                     |
       {1:~                                                                          }|
                                                                                  |
@@ -59,7 +62,7 @@ describe('signs', function()
     screen:expect([[
       {7:  }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
       {7:  }xx                                                                       |
-      {10:-)}{4:^mmmm                                                                     }|
+      {100:-)}{4:^mmmm                                                                     }|
       {7:  }yyyy                                                                     |
       {1:~                                                                          }|
                                                                                  |
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index fbf16f3afe..da0fb9849a 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -631,7 +631,7 @@ describe('decorations providers', function()
       {14:  }hello97                               |
       {14:  }hello98                               |
       {14:  }hello99                               |
-      X ^hello100                              |
+      {14:X }^hello100                              |
       {14:  }hello101                              |
       {14:  }hello102                              |
       {14:  }hello103                              |
@@ -2301,13 +2301,16 @@ describe('extmark decorations', function()
 
   it('works with both hl_group and sign_hl_group', function()
     screen:try_resize(50, 3)
+    screen:add_extra_attr_ids({
+      [100] = { background = Screen.colors.WebGray, foreground = Screen.colors.Blue, bold = true },
+    })
     insert('abcdefghijklmn')
     api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S', sign_hl_group='NonText', hl_group='Error', end_col=14})
-    screen:expect{grid=[[
-      {1:S }{4:abcdefghijklm^n}                                  |
+    screen:expect([[
+      {100:S }{9:abcdefghijklm^n}                                  |
       {1:~                                                 }|
                                                         |
-    ]]}
+    ]])
   end)
 
   it('virt_text_repeat_linebreak repeats virtual text on wrapped lines', function()
@@ -5064,16 +5067,16 @@ l5
 
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S'})
 
-    screen:expect{grid=[[
+    screen:expect([[
       {7:  }^l1                                              |
-      S l2                                              |
+      {7:S }l2                                              |
       {7:  }l3                                              |
       {7:  }l4                                              |
       {7:  }l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add a single sign (with end row)', function()
@@ -5082,16 +5085,16 @@ l5
 
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1})
 
-    screen:expect{grid=[[
+    screen:expect([[
       {7:  }^l1                                              |
-      S l2                                              |
+      {7:S }l2                                              |
       {7:  }l3                                              |
       {7:  }l4                                              |
       {7:  }l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add a single sign and text highlight', function()
@@ -5099,16 +5102,16 @@ l5
     feed 'gg'
 
     api.nvim_buf_set_extmark(0, ns, 1, 0, {sign_text='S', hl_group='Todo', end_col=1})
-    screen:expect{grid=[[
+    screen:expect([[
       {7:  }^l1                                              |
-      S {100:l}2                                              |
+      {7:S }{100:l}2                                              |
       {7:  }l3                                              |
       {7:  }l4                                              |
       {7:  }l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
 
     api.nvim_buf_clear_namespace(0, ns, 0, -1)
   end)
@@ -5119,16 +5122,16 @@ l5
 
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2})
 
-    screen:expect{grid=[[
+    screen:expect([[
       {7:  }^l1                                              |
-      S l2                                              |
-      S l3                                              |
+      {7:S }l2                                              |
+      {7:S }l3                                              |
       {7:  }l4                                              |
       {7:  }l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add multiple signs (multiple extmarks)', function()
@@ -5138,16 +5141,16 @@ l5
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S1'})
     api.nvim_buf_set_extmark(0, ns, 3, -1, {sign_text='S2', end_row = 4})
 
-    screen:expect{grid=[[
+    screen:expect([[
       {7:  }^l1                                              |
-      S1l2                                              |
+      {7:S1}l2                                              |
       {7:  }l3                                              |
-      S2l4                                              |
-      S2l5                                              |
+      {7:S2}l4                                              |
+      {7:S2}l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add multiple signs (multiple extmarks) 2', function()
@@ -5156,16 +5159,16 @@ l5
 
     api.nvim_buf_set_extmark(0, ns, 3, -1, {sign_text='S1'})
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row = 3})
-    screen:expect{grid=[[
+    screen:expect([[
       {7:    }^l1                                            |
-      S2{7:  }l2                                            |
-      S2{7:  }l3                                            |
-      S2S1l4                                            |
+      {7:S2  }l2                                            |
+      {7:S2  }l3                                            |
+      {7:S2S1}l4                                            |
       {7:    }l5                                            |
       {7:    }                                              |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add multiple signs (multiple extmarks) 3', function()
@@ -5176,16 +5179,16 @@ l5
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2})
     api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S2', end_row=3})
 
-    screen:expect{grid=[[
+    screen:expect([[
       {7:    }^l1                                            |
-      S1{7:  }l2                                            |
-      S2S1l3                                            |
-      S2{7:  }l4                                            |
+      {7:S1  }l2                                            |
+      {7:S2S1}l3                                            |
+      {7:S2  }l4                                            |
       {7:    }l5                                            |
       {7:    }                                              |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add multiple signs (multiple extmarks) 4', function()
@@ -5195,16 +5198,16 @@ l5
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0})
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row=1})
 
-    screen:expect{grid=[[
-      S1^l1                                              |
-      S2l2                                              |
+    screen:expect([[
+      {7:S1}^l1                                              |
+      {7:S2}l2                                              |
       {7:  }l3                                              |
       {7:  }l4                                              |
       {7:  }l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('works with old signs', function()
@@ -5219,16 +5222,16 @@ l5
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S4'})
     api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S5'})
 
-    screen:expect{grid=[[
-      S4S1^l1                                            |
-      S2x l2                                            |
-      S5{7:  }l3                                            |
+    screen:expect([[
+      {7:S4S1}^l1                                            |
+      {7:S2x }l2                                            |
+      {7:S5  }l3                                            |
       {7:    }l4                                            |
       {7:    }l5                                            |
       {7:    }                                              |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('works with old signs (with range)', function()
@@ -5244,16 +5247,16 @@ l5
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S4'})
     api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S5'})
 
-    screen:expect{grid=[[
-      S4S3S1^l1                                          |
-      S3S2x l2                                          |
-      S5S3{7:  }l3                                          |
-      S3{7:    }l4                                          |
-      S3{7:    }l5                                          |
+    screen:expect([[
+      {7:S4S3S1}^l1                                          |
+      {7:S3S2x }l2                                          |
+      {7:S5S3  }l3                                          |
+      {7:S3    }l4                                          |
+      {7:S3    }l5                                          |
       {7:      }                                            |
       {1:~                                                 }|*3
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add a ranged sign (with start out of view)', function()
@@ -5264,14 +5267,14 @@ l5
 
     api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='X', end_row=3})
 
-    screen:expect{grid=[[
-      X {7:  }^l3                                            |
-      X {7:  }l4                                            |
+    screen:expect([[
+      {7:X   }^l3                                            |
+      {7:X   }l4                                            |
       {7:    }l5                                            |
       {7:    }                                              |
       {1:~                                                 }|*5
                                                         |
-    ]]}
+    ]])
   end)
 
   it('can add lots of signs', function()
@@ -5293,11 +5296,11 @@ l5
       api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text='Z' })
     end
 
-    screen:expect{grid=[[
-      Z Y X W {100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h}                 |*8
-      Z Y X W {100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h}                 |
+    screen:expect([[
+      {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h}                 |*8
+      {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h}                 |
                                               |
-    ]]}
+    ]])
   end)
 
   it('works with priority #19716', function()
@@ -5313,20 +5316,20 @@ l5
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200})
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1})
 
-    screen:expect{grid=[[
-      S5S4O3S2S1^l1        |
+    screen:expect([[
+      {7:S5S4O3S2S1}^l1        |
       {7:          }l2        |
                           |
-    ]]}
+    ]])
 
     -- Check truncation works too
     api.nvim_set_option_value('signcolumn', 'auto', {})
 
-    screen:expect{grid=[[
-      S5^l1                |
+    screen:expect([[
+      {7:S5}^l1                |
       {7:  }l2                |
                           |
-    ]]}
+    ]])
   end)
 
   it('does not overflow with many old signs #23852', function()
@@ -5343,21 +5346,21 @@ l5
     command([[exe 'sign place 07 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
     command([[exe 'sign place 08 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
     command([[exe 'sign place 09 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
-    screen:expect{grid=[[
-      O3O3O3O3O3O3O3O3O3^  |
+    screen:expect([[
+      {7:O3O3O3O3O3O3O3O3O3}^  |
       {1:~                   }|
                           |
-    ]]}
+    ]])
 
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1})
     screen:expect_unchanged()
 
     api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200})
-    screen:expect{grid=[[
-      S5O3O3O3O3O3O3O3O3^  |
+    screen:expect([[
+      {7:S5O3O3O3O3O3O3O3O3}^  |
       {1:~                   }|
                           |
-    ]]}
+    ]])
 
     assert_alive()
   end)
@@ -5383,12 +5386,12 @@ l5
     api.nvim_buf_set_extmark(0, ns, 1, -1, {invalidate = true, sign_text='S3'})
     feed('2Gdd')
 
-    screen:expect{grid=[[
-      S1l1                |
-      S1^l3                |
-      S1l4                |
+    screen:expect([[
+      {7:S1}l1                |
+      {7:S1}^l3                |
+      {7:S1}l4                |
                           |
-    ]]}
+    ]])
   end)
 
   it('correct width with multiple overlapping signs', function()
@@ -5400,36 +5403,36 @@ l5
     feed('gg')
 
     local s1 = [[
-      S2S1^l1              |
-      S3S2l2              |
-      S3S2l3              |
+      {7:S2S1}^l1              |
+      {7:S3S2}l2              |
+      {7:S3S2}l3              |
                           |
     ]]
-    screen:expect{grid=s1}
+    screen:expect(s1)
     -- Correct width when :move'ing a line with signs
     command('move2')
-    screen:expect{grid=[[
-      S3{7:    }l2            |
-      S3S2S1^l1            |
+    screen:expect([[
+      {7:S3    }l2            |
+      {7:S3S2S1}^l1            |
       {7:      }l3            |
                           |
-    ]]}
+    ]])
     command('silent undo')
     screen:expect{grid=s1}
     command('d')
-    screen:expect{grid=[[
-      S3S2S1^l2            |
-      S3S2{7:  }l3            |
+    screen:expect([[
+      {7:S3S2S1}^l2            |
+      {7:S3S2  }l3            |
       {7:      }l4            |
                           |
-    ]]}
+    ]])
     command('d')
-    screen:expect{grid=[[
-      S3S2S1^l3            |
+    screen:expect([[
+      {7:S3S2S1}^l3            |
       {7:      }l4            |
       {7:      }l5            |
                           |
-    ]]}
+    ]])
   end)
 
   it('correct width when adding and removing multiple signs', function()
@@ -5452,12 +5455,12 @@ l5
       redraw!
       call nvim_buf_del_extmark(0, ns, s1)
     ]])
-    screen:expect{grid=[[
-      S1^l1                |
-      S1l2                |
-      S1l3                |
+    screen:expect([[
+      {7:S1}^l1                |
+      {7:S1}l2                |
+      {7:S1}l3                |
                           |
-    ]]}
+    ]])
   end)
 
   it('correct width when deleting lines', function()
@@ -5472,12 +5475,12 @@ l5
       call nvim_buf_del_extmark(0, ns, s3)
       norm 4Gdd
     ]])
-    screen:expect{grid=[[
+    screen:expect([[
       {7:    }l3              |
-      S2S1l5              |
+      {7:S2S1}l5              |
       {7:    }^                |
                           |
-    ]]}
+    ]])
   end)
 
   it('correct width when splitting lines with signs on different columns', function()
@@ -5487,12 +5490,12 @@ l5
     api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S1'})
     api.nvim_buf_set_extmark(0, ns, 0, 1, {sign_text='S2'})
     feed('a')
-    screen:expect{grid=[[
-      S1l                 |
-      S2^1                 |
+    screen:expect([[
+      {7:S1}l                 |
+      {7:S2}^1                 |
       {7:  }l2                |
                           |
-    ]]}
+    ]])
   end)
 
   it('correct width after wiping a buffer', function()
@@ -5501,12 +5504,12 @@ l5
     feed('gg')
     local buf = api.nvim_get_current_buf()
     api.nvim_buf_set_extmark(buf, ns, 0, 0, { sign_text = 'h' })
-    screen:expect{grid=[[
-      h ^l1                |
+    screen:expect([[
+      {7:h }^l1                |
       {7:  }l2                |
       {7:  }l3                |
                           |
-    ]]}
+    ]])
     api.nvim_win_set_buf(0, api.nvim_create_buf(false, true))
     api.nvim_buf_delete(buf, {unload=true, force=true})
     api.nvim_buf_set_lines(buf, 0, -1, false, {''})
@@ -5537,12 +5540,12 @@ l5
       end)
     ]])
 
-    screen:expect{grid=[[
-      S1^l1                |
-      S2l2                |
-      S4l3                |
+    screen:expect([[
+      {7:S1}^l1                |
+      {7:S2}l2                |
+      {7:S4}l3                |
                           |
-    ]]}
+    ]])
   end)
 
   it('no crash with sign after many marks #27137', function()
@@ -5553,11 +5556,11 @@ l5
     end
     api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1'})
 
-    screen:expect{grid=[[
-      S1{9:^a}                 |
+    screen:expect([[
+      {7:S1}{9:^a}                 |
       {1:~                   }|*2
                           |
-    ]]}
+    ]])
   end)
 
   it('correct sort order with multiple namespaces and same id', function()
@@ -5565,11 +5568,11 @@ l5
     api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1', id = 1})
     api.nvim_buf_set_extmark(0, ns2, 0, 0, {sign_text = 'S2', id = 1})
 
-    screen:expect{grid=[[
-      S2S1^                                              |
+    screen:expect([[
+      {7:S2S1}^                                              |
       {1:~                                                 }|*8
                                                         |
-    ]]}
+    ]])
   end)
 
   it('correct number of signs after deleting text (#27046)', function()
@@ -5586,12 +5589,12 @@ l5
     api.nvim_buf_set_extmark(0, ns, 30, 0, {end_row = 30, end_col = 3, hl_group = 'Error'})
     command('0d29')
 
-    screen:expect{grid=[[
-      S4S3S2S1{9:^foo}                                       |
-      S5{7:      }{9:foo}                                       |
+    screen:expect([[
+      {7:S4S3S2S1}{9:^foo}                                       |
+      {7:S5      }{9:foo}                                       |
       {1:~                                                 }|*7
       29 fewer lines                                    |
-    ]]}
+    ]])
 
     api.nvim_buf_clear_namespace(0, ns, 0, -1)
   end)
@@ -5599,21 +5602,17 @@ l5
   it([[correct numberwidth with 'signcolumn' set to "number" #28984]], function()
     command('set number numberwidth=1 signcolumn=number')
     api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' })
-    screen:expect({
-      grid = [[
-        S1 ^                                               |
-        {1:~                                                 }|*8
-                                                          |
-      ]]
-    })
+    screen:expect([[
+      {7:S1 }^                                               |
+      {1:~                                                 }|*8
+                                                        |
+    ]])
     api.nvim_buf_del_extmark(0, ns, 1)
-    screen:expect({
-      grid = [[
-        {8:1 }^                                                |
-        {1:~                                                 }|*8
-                                                          |
-      ]]
-    })
+    screen:expect([[
+      {8:1 }^                                                |
+      {1:~                                                 }|*8
+                                                        |
+    ]])
   end)
 
   it('supports emoji as signs', function()
@@ -5626,10 +5625,10 @@ l5
     api.nvim_buf_set_extmark(0, ns, 4, 0, {sign_text='❤x'})
     screen:expect([[
       {7:  }^l1                                              |
-      🧑‍🌾l2                                              |
-      ❤️l3                                              |
-      ❤ l4                                              |
-      ❤xl5                                              |
+      {7:🧑‍🌾}l2                                              |
+      {7:❤️}l3                                              |
+      {7:❤ }l4                                              |
+      {7:❤x}l5                                              |
       {7:  }                                                |
       {1:~                                                 }|*3
                                                         |
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index c676e9d5f2..be0a9b80cf 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -1046,6 +1046,8 @@ describe('float window', function()
         [26] = {blend = 80, background = Screen.colors.Gray0};
         [27] = {foreground = Screen.colors.Black, background = Screen.colors.LightGrey};
         [28] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey};
+        [29] = {background = Screen.colors.Yellow1, foreground = Screen.colors.Blue4};
+        [30] = {background = Screen.colors.Grey, foreground = Screen.colors.Blue4, bold = true};
       }
       screen:set_default_attr_ids(attrs)
     end)
@@ -1451,14 +1453,14 @@ describe('float window', function()
           [2:----------------------------------------]|*6
           [3:----------------------------------------]|
         ## grid 2
-          {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
+          {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
           {19:   }{14:  2 }{22:y}                                |
           {19:   }{14:  3 }{22: }                                |
           {0:~                                       }|*3
         ## grid 3
                                                   |
         ## grid 4
-          {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }|
+          {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }|
           {19:  }{15:y                 }|
           {19:  }{15:                  }|
           {15:                    }|
@@ -1466,9 +1468,9 @@ describe('float window', function()
 
       else
         screen:expect([[
-          {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
+          {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
           {19:   }{14:  2 }{22:y}                                |
-          {19:   }{14:  3 }{22: }  {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }          |
+          {19:   }{14:  3 }{22: }  {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }          |
           {0:~         }{19:  }{15:y                 }{0:          }|
           {0:~         }{19:  }{15:                  }{0:          }|
           {0:~         }{15:                    }{0:          }|
@@ -1551,14 +1553,14 @@ describe('float window', function()
           [2:----------------------------------------]|*6
           [3:----------------------------------------]|
         ## grid 2
-          {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
+          {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
           {19:   }{14:  2 }{22:y}                                |
           {19:   }{14:  3 }{22: }                                |
           {0:~                                       }|*3
         ## grid 3
                                                   |
         ## grid 4
-          {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }|
+          {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }|
           {19:  }{15:y                 }|
           {19:  }{15:                  }|
           {15:                    }|
@@ -1566,9 +1568,9 @@ describe('float window', function()
 
       else
         screen:expect([[
-          {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
+          {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20:  1 }{22:^x}{21:                                }|
           {19:   }{14:  2 }{22:y}                                |
-          {19:   }{14:  3 }{22: }  {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }          |
+          {19:   }{14:  3 }{22: }  {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x                 }          |
           {0:~         }{19:  }{15:y                 }{0:          }|
           {0:~         }{19:  }{15:                  }{0:          }|
           {0:~         }{15:                    }{0:          }|
@@ -1621,7 +1623,7 @@ describe('float window', function()
           [2:----------------------------------------]|*6
           [3:----------------------------------------]|
         ## grid 2
-          {20:   1}{19:   }{22:^x}{21:                                }|
+          {20:   1}{30:   }{22:^x}{21:                                }|
           {14:   2}{19:   }{22:y}                                |
           {14:   3}{19:   }{22: }                                |
           {0:~                                       }|*3
@@ -1634,7 +1636,7 @@ describe('float window', function()
         ]], float_pos={[4] = {1001, "NW", 1, 4, 10, true}}}
       else
         screen:expect{grid=[[
-          {20:   1}{19:   }{22:^x}{21:                                }|
+          {20:   1}{30:   }{22:^x}{21:                                }|
           {14:   2}{19:   }{22:y}                                |
           {14:   3}{19:   }{22: }  {15:x                   }          |
           {0:~         }{15:y                   }{0:          }|
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index b7a2429ada..bd3887b44f 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -14,6 +14,12 @@ describe('Signs', function()
     screen = Screen.new()
     screen:add_extra_attr_ids {
       [100] = { bold = true, foreground = Screen.colors.Magenta1 },
+      [101] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Yellow1 },
+      [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Yellow },
+      [103] = { background = Screen.colors.Yellow, reverse = true },
+      [104] = { reverse = true, foreground = Screen.colors.Grey100, background = Screen.colors.Red },
+      [105] = { bold = true, background = Screen.colors.Red1, foreground = Screen.colors.Gray100 },
+      [106] = { foreground = Screen.colors.Brown, reverse = true },
     }
   end)
 
@@ -27,8 +33,8 @@ describe('Signs', function()
         sign place 2 line=2 name=piet2 buffer=1
       ]])
       screen:expect([[
-        {10:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}a                                                  |
-        {10:𠜎̀́̂̃̄̅}b                                                  |
+        {101:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}a                                                  |
+        {101:𠜎̀́̂̃̄̅}b                                                  |
         {7:  }^                                                   |
         {1:~                                                    }|*10
                                                              |
@@ -45,9 +51,9 @@ describe('Signs', function()
         sign place 3 line=1 name=pietx buffer=1
       ]])
       screen:expect([[
-        {10:>!}a                                                  |
+        {101:>!}a                                                  |
         {7:  }b                                                  |
-        {10:>>}c                                                  |
+        {101:>>}c                                                  |
         {7:  }^                                                   |
         {1:~                                                    }|*9
                                                              |
@@ -80,13 +86,13 @@ describe('Signs', function()
       ]])
       screen:expect([[
         {7:  }{21:^a                                                  }|
-        {10:>>}b                                                  |
+        {101:>>}b                                                  |
         {7:  }c                                                  |
         {7:  }                                                   |
         {1:~                                                    }|*2
         {3:[No Name] [+]                                        }|
         {7:  }{21:a                                                  }|
-        {10:>>}b                                                  |
+        {101:>>}b                                                  |
         {7:  }c                                                  |
         {7:  }                                                   |
         {1:~                                                    }|
@@ -110,16 +116,23 @@ describe('Signs', function()
         sign place 6 line=4 name=pietxx buffer=1
       ]])
       screen:expect([[
-        {10:>>}{8:  1 }a                                              |
+        {101:>>}{8:  1 }a                                              |
         {7:  }{8:  2 }{9:b                                              }|
         {7:  }{13:  3 }c                                              |
-        {10:>>}{13:  4 }{9:^                                               }|
+        {101:>>}{13:  4 }{9:^                                               }|
         {1:~                                                    }|*9
                                                              |
       ]])
       -- Check that 'statuscolumn' correctly applies numhl
       exec('set statuscolumn=%s%=%l\\ ')
-      screen:expect_unchanged()
+      screen:expect([[
+        {102:>>}{8:  1 }a                                              |
+        {7:  }{8:  2 }{9:b                                              }|
+        {7:  }{13:  3 }c                                              |
+        {101:>>}{13:  4 }{9:^                                               }|
+        {1:~                                                    }|*9
+                                                             |
+      ]])
     end)
 
     it('highlights the cursorline sign with culhl', function()
@@ -132,33 +145,33 @@ describe('Signs', function()
         set cursorline
       ]])
       screen:expect([[
-        {10:>>}a                                                  |
-        {10:>>}b                                                  |
+        {101:>>}a                                                  |
+        {101:>>}b                                                  |
         {9:>>}{21:^c                                                  }|
         {1:~                                                    }|*10
                                                              |
       ]])
       feed('k')
       screen:expect([[
-        {10:>>}a                                                  |
+        {101:>>}a                                                  |
         {9:>>}{21:^b                                                  }|
-        {10:>>}c                                                  |
+        {101:>>}c                                                  |
         {1:~                                                    }|*10
                                                              |
       ]])
       exec('set nocursorline')
       screen:expect([[
-        {10:>>}a                                                  |
-        {10:>>}^b                                                  |
-        {10:>>}c                                                  |
+        {101:>>}a                                                  |
+        {101:>>}^b                                                  |
+        {101:>>}c                                                  |
         {1:~                                                    }|*10
                                                              |
       ]])
       exec('set cursorline cursorlineopt=line')
       screen:expect([[
-        {10:>>}a                                                  |
-        {10:>>}{21:^b                                                  }|
-        {10:>>}c                                                  |
+        {101:>>}a                                                  |
+        {101:>>}{21:^b                                                  }|
+        {101:>>}c                                                  |
         {1:~                                                    }|*10
                                                              |
       ]])
@@ -166,16 +179,24 @@ describe('Signs', function()
       exec('hi! link SignColumn IncSearch')
       feed('Go2G')
       screen:expect([[
-        {10:>>}a                                                  |
-        {9:>>}^b                                                  |
-        {10:>>}c                                                  |
+        {103:>>}a                                                  |
+        {104:>>}^b                                                  |
+        {103:>>}c                                                  |
         {2:  }                                                   |
         {1:~                                                    }|*9
                                                              |
       ]])
+
       -- Check that 'statuscolumn' cursorline/signcolumn highlights are the same (#21726)
       exec('set statuscolumn=%s')
-      screen:expect_unchanged()
+      screen:expect([[
+        {102:>>}a                                                  |
+        {105:>>}^b                                                  |
+        {102:>>}c                                                  |
+        {106:  }                                                   |
+        {1:~                                                    }|*9
+                                                             |
+      ]])
     end)
 
     it('multiple signs #9295', function()
@@ -196,7 +217,7 @@ describe('Signs', function()
       screen:expect([[
         {7:    }{8:  1 }a                                            |
         {7:    }{8:  2 }b                                            |
-        WW{10:>>}{8:  3 }c                                            |
+        {7:WW}{101:>>}{8:  3 }c                                            |
         {7:    }{8:  4 }^                                             |
         {1:~                                                    }|*9
                                                              |
@@ -209,9 +230,9 @@ describe('Signs', function()
         sign place 3 line=2 name=pietError buffer=1
       ]])
       screen:expect([[
-        {9:XX}{10:>>}{8:  1 }a                                            |
-        {10:>>}{9:XX}{8:  2 }b                                            |
-        WW{10:>>}{8:  3 }c                                            |
+        {9:XX}{101:>>}{8:  1 }a                                            |
+        {101:>>}{9:XX}{8:  2 }b                                            |
+        {7:WW}{101:>>}{8:  3 }c                                            |
         {7:    }{8:  4 }^                                             |
         {1:~                                                    }|*9
                                                              |
@@ -220,8 +241,8 @@ describe('Signs', function()
       exec('set signcolumn=yes:1')
       screen:expect([[
         {9:XX}{8:  1 }a                                              |
-        {10:>>}{8:  2 }b                                              |
-        WW{8:  3 }c                                              |
+        {101:>>}{8:  2 }b                                              |
+        {7:WW}{8:  3 }c                                              |
         {7:  }{8:  4 }^                                               |
         {1:~                                                    }|*9
                                                              |
@@ -229,9 +250,9 @@ describe('Signs', function()
       -- "auto:3" accommodates all the signs we defined so far.
       exec('set signcolumn=auto:3')
       local s3 = [[
-        {9:XX}{10:>>}{7:  }{8:  1 }a                                          |
-        {10:>>}{9:XX}{7:  }{8:  2 }b                                          |
-        WW{10:>>}{9:XX}{8:  3 }c                                          |
+        {9:XX}{101:>>}{7:  }{8:  1 }a                                          |
+        {101:>>}{9:XX}{7:  }{8:  2 }b                                          |
+        {7:WW}{101:>>}{9:XX}{8:  3 }c                                          |
         {7:      }{8:  4 }^                                           |
         {1:~                                                    }|*9
                                                              |
@@ -240,9 +261,9 @@ describe('Signs', function()
       -- Check "yes:9".
       exec('set signcolumn=yes:9')
       screen:expect([[
-        {9:XX}{10:>>}{7:              }{8:  1 }a                              |
-        {10:>>}{9:XX}{7:              }{8:  2 }b                              |
-        WW{10:>>}{9:XX}{7:            }{8:  3 }c                              |
+        {9:XX}{101:>>}{7:              }{8:  1 }a                              |
+        {101:>>}{9:XX}{7:              }{8:  2 }b                              |
+        {7:WW}{101:>>}{9:XX}{7:            }{8:  3 }c                              |
         {7:                  }{8:  4 }^                               |
         {1:~                                                    }|*9
                                                              |
@@ -255,8 +276,8 @@ describe('Signs', function()
       exec('3move1')
       exec('2d')
       screen:expect([[
-        {9:XX}{10:>>}{8:  1 }a                                            |
-        {10:>>}{9:XX}{8:  2 }^b                                            |
+        {9:XX}{101:>>}{8:  1 }a                                            |
+        {101:>>}{9:XX}{8:  2 }^b                                            |
         {7:    }{8:  3 }                                             |
         {1:~                                                    }|*10
                                                              |
@@ -264,8 +285,8 @@ describe('Signs', function()
       -- character deletion does not delete signs.
       feed('x')
       screen:expect([[
-        {9:XX}{10:>>}{8:  1 }a                                            |
-        {10:>>}{9:XX}{8:  2 }^                                             |
+        {9:XX}{101:>>}{8:  1 }a                                            |
+        {101:>>}{9:XX}{8:  2 }^                                             |
         {7:    }{8:  3 }                                             |
         {1:~                                                    }|*10
                                                              |
@@ -301,7 +322,7 @@ describe('Signs', function()
       exec('sign define pietSearch text=>> texthl=Search')
       exec('sign place 1 line=1 name=pietSearch buffer=1')
       screen:expect([[
-        {10:>>}{7:  }{8:  1 }a                                            |
+        {101:>>}{7:  }{8:  1 }a                                            |
         {7:    }{8:  2 }b                                            |
         {7:    }{8:  3 }c                                            |
         {7:    }{8:  4 }^                                             |
@@ -316,7 +337,7 @@ describe('Signs', function()
         sign place 4 line=1 name=pietSearch buffer=1
       ]])
       screen:expect([[
-        {10:>>>>>>>>}{8:  1 }a                                        |
+        {101:>>>>>>>>}{8:  1 }a                                        |
         {7:        }{8:  2 }b                                        |
         {7:        }{8:  3 }c                                        |
         {7:        }{8:  4 }^                                         |
@@ -328,7 +349,7 @@ describe('Signs', function()
       screen:expect_unchanged()
       exec('sign unplace 4')
       screen:expect([[
-        {10:>>>>>>}{8:  1 }a                                          |
+        {101:>>>>>>}{8:  1 }a                                          |
         {7:      }{8:  2 }b                                          |
         {7:      }{8:  3 }c                                          |
         {7:      }{8:  4 }^                                           |
@@ -345,7 +366,7 @@ describe('Signs', function()
         sign place 8 line=1 name=pietSearch buffer=1
       ]])
       screen:expect([[
-        {10:>>>>>>>>>>}{8:  1 }a                                      |
+        {101:>>>>>>>>>>}{8:  1 }a                                      |
         {7:          }{8:  2 }b                                      |
         {7:          }{8:  3 }c                                      |
         {7:          }{8:  4 }^                                       |
@@ -375,7 +396,7 @@ describe('Signs', function()
       -- single column with 1 sign with text and one sign without
       exec('sign place 1 line=1 name=pietSearch buffer=1')
       screen:expect([[
-        {10:>>}{8:  1 }a                                              |
+        {101:>>}{8:  1 }a                                              |
         {7:  }{8:  2 }b                                              |
         {7:  }{8:  3 }c                                              |
         {7:  }{8:  4 }^                                               |
@@ -396,7 +417,7 @@ describe('Signs', function()
       -- line number should be drawn if sign has no text
       -- no signcolumn, line number for "a" is Search, for "b" is Error, for "c" is LineNr
       screen:expect([[
-        {10: >> }a                                                |
+        {101: >> }a                                                |
         {9:  2 }b                                                |
         {8:  3 }c                                                |
         {8:  4 }^                                                 |
@@ -406,7 +427,7 @@ describe('Signs', function()
       -- number column on wrapped part of a line should be empty
       feed('gg100aa')
       screen:expect([[
-        {10: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+        {101: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
         {9:    }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
         {9:    }aa^a                                              |
         {9:  2 }b                                                |
@@ -423,7 +444,7 @@ describe('Signs', function()
       -- number column on virtual lines should be empty
       screen:expect([[
         {8:    }VIRT LINES                                       |
-        {10: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+        {101: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
         {9:    }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
         {9:    }aa^a                                              |
         {9:  2 }b                                                |
@@ -439,7 +460,7 @@ describe('Signs', function()
       exec('sign place 100000 line=1 name=piet buffer=1')
       feed(':sign place')
       screen:expect([[
-        {10:>>}                                                   |
+        {101:>>}                                                   |
         {1:~                                                    }|*6
         {3:                                                     }|
         :sign place                                          |
@@ -452,7 +473,7 @@ describe('Signs', function()
 
       feed('')
       screen:expect([[
-        {10:>>}^                                                   |
+        {101:>>}^                                                   |
         {1:~                                                    }|*12
                                                              |
       ]])
@@ -470,7 +491,7 @@ describe('Signs', function()
       {7:  }a                                                  |
       {7:  }^c                                                  |
       {7:  }d                                                  |
-      >>e                                                  |
+      {7:>>}e                                                  |
       {1:~                                                    }|*9
                                                            |
     ]])
@@ -498,7 +519,7 @@ describe('Signs', function()
       {7:  }b                                                  |
       {7:  }c                                                  |
       {7:  }d                                                  |
-      >>e                                                  |
+      {7:>>}e                                                  |
       {1:~                                                    }|*7
                                                            |
     ]])
@@ -550,7 +571,7 @@ describe('Signs', function()
     exec('silent undo')
     screen:expect([[
       {7:  }1                                                  |
-      S1^2                                                  |
+      {7:S1}^2                                                  |
       {7:  }3                                                  |
       {7:  }4                                                  |
       {1:~                                                    }|*9
@@ -575,23 +596,19 @@ describe('Signs', function()
       sign place 2 line=9 name=S2
     ]])
     -- Now placed at end of buffer
-    local s1 = {
-      grid = [[
-        S2^                                                   |
-        {1:~                                                    }|*12
-                                                             |
-      ]],
-    }
+    local s1 = [[
+      {7:S2}^                                                   |
+      {1:~                                                    }|*12
+                                                           |
+    ]]
     screen:expect(s1)
     -- Signcolumn tracking used to not count signs placed beyond end of buffer here
     exec('set signcolumn=auto:9')
-    screen:expect({
-      grid = [[
-        S2S1^                                                 |
-        {1:~                                                    }|*12
-                                                             |
-      ]],
-    })
+    screen:expect([[
+      {7:S2S1}^                                                 |
+      {1:~                                                    }|*12
+                                                           |
+    ]])
     -- Unplacing the sign does not crash by decrementing tracked signs below zero
     exec('sign unplace 1')
     screen:expect(s1)
@@ -658,7 +675,7 @@ describe('Signs', function()
       {7:  }497                     │{7:  }2                       |
       {7:  }498                     │{7:  }3                       |
       {7:  }499                     │{7:  }4                       |
-      ! ^500                     │{7:  }5                       |
+      {7:! }^500                     │{7:  }5                       |
       {3:[No Name] [+]              }{2:[No Name] [+]             }|
                                                            |
     ]])
@@ -675,7 +692,7 @@ describe('Signs', function()
       {7:  }497                     │{7:  }2                       |
       {7:  }498                     │{7:  }3                       |
       {7:  }499                     │{7:  }4                       |
-      ! ^500                     │{7:  }5                       |
+      {7:! }^500                     │{7:  }5                       |
       {3:[No Name] [+]              }{2:[No Name] [+]             }|
       :lua log, needs_clear = {}, true                     |
     ]])
diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua
index 268e7173e6..ba60bab7e6 100644
--- a/test/functional/ui/statuscolumn_spec.lua
+++ b/test/functional/ui/statuscolumn_spec.lua
@@ -236,6 +236,11 @@ describe('statuscolumn', function()
     command("call setline(1,repeat([repeat('aaaaa',10)],16))")
     screen:add_extra_attr_ids {
       [100] = { foreground = Screen.colors.Red, background = Screen.colors.LightGray },
+      [101] = {
+        bold = true,
+        background = Screen.colors.WebGray,
+        foreground = Screen.colors.DarkBlue,
+      },
     }
     command('hi! CursorLine guifg=Red guibg=NONE')
     screen:expect([[
@@ -308,7 +313,7 @@ describe('statuscolumn', function()
       {7: }{8:  │}{7:    }{8: }aaaaaa                                      |
       {7: }{8: 7│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:  │}{7:    }{8: }aaaaaa                                      |
-      {7:+}{15: 8│}{7:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101:+}{15: 8│}{101:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7: }{8: 9│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:  │}{7:    }{8: }aaaaaa                                      |
       {7: }{8:10│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -326,7 +331,7 @@ describe('statuscolumn', function()
       {7: }{8: 6│}{7:    }{8: }aaaaaa                                      |
       {7: }{8: 7│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8: 7│}{7:    }{8: }aaaaaa                                      |
-      {7:+}{15: 8│}{7:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101:+}{15: 8│}{101:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7: }{8: 9│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8: 9│}{7:    }{8: }aaaaaa                                      |
       {7: }{8:10│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -344,7 +349,7 @@ describe('statuscolumn', function()
       {7: }{8:  2│}{7:    }{8: }aaaaaaa                                    |
       {7: }{8:  1│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:  1│}{7:    }{8: }aaaaaaa                                    |
-      {7:+}{15:  0│}{7:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101:+}{15:  0│}{101:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7: }{8:  1│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:  1│}{7:    }{8: }aaaaaaa                                    |
       {7: }{8:  2│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -361,7 +366,7 @@ describe('statuscolumn', function()
       {7: }{8:   │}{7:    }{8: }aaaaaaa                                    |
       {7: }{8:  1│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:   │}{7:    }{8: }aaaaaaa                                    |
-      {7:+}{15:  0│}{7:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101:+}{15:  0│}{101:    }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7: }{8:  1│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:   │}{7:    }{8: }aaaaaaa                                    |
       {7: }{8:  2│}{7:    }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -386,7 +391,7 @@ describe('statuscolumn', function()
       {7: }{8:   │}{7:                  }{8: }aaaaaaaaaaaaaaaaaaaaa        |
       {7: }{8:  1│}{7:                  }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:   │}{7:                  }{8: }aaaaaaaaaaaaaaaaaaaaa        |
-      {7:+}{15:  0│}{7:                  }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaa}|
+      {101:+}{15:  0│}{101:                  }{15: }{100:^+--  1 line: aaaaaaaaaaaaaaaa}|
       {7: }{8:  1│}{7:                  }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7: }{8:   │}{7:                  }{8: }aaaaaaaaaaaaaaaaaaaaa        |
       {7: }{8:  2│}{7:                  }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -397,7 +402,7 @@ describe('statuscolumn', function()
     command('set cpoptions+=n')
     feed('Hgjg0')
     screen:expect([[
-      {7: }{15:  0│}{8:>>}{7:                }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101: }{15:  0│>>}{101:                }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7:                   }{19:^aaaaaaaaaaaaaaaaaaaaa             }|
       {7: }{8:  3│}{1:>!}{7:                }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {7:                   }aaaaaaaaaaaaaaaaaaaaa             |
@@ -416,7 +421,7 @@ describe('statuscolumn', function()
     command('sign unplace 2')
     feed('J2gjg0')
     screen:expect([[
-      {7: }{15:  0│}{8:>>}{7:                }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101: }{15:  0│>>}{101:                }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7:                   }     {19:aaaaaaaaaaaaaaaaaaaaa aaaaaaa}|
       {7:                   }     {19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7:                   }     {19:^aaaaaaaaaaaaaa               }|
@@ -434,7 +439,7 @@ describe('statuscolumn', function()
     command('set nobreakindent')
     feed('$g0')
     screen:expect([[
-      {7: }{15:  0│}{8:>>}{7:                }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {101: }{15:  0│>>}{101:                }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7:                   }{19:aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa}|
       {7:                   }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
       {7:                   }{19:^aaaa                              }|
-- 
cgit 


From caa93b5e1e621e63777def5d9fdd49d613e1c274 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 15 Dec 2024 07:05:12 +0800
Subject: vim-patch:ed89206: runtime(doc): add a note about inclusive motions
 and exclusive selection

related: vim/vim#16202

https://github.com/vim/vim/commit/ed89206efe404a94e8424ccfe03c978fd93470f1

Co-authored-by: Christian Brabandt 
---
 runtime/doc/motion.txt            | 7 +++++++
 runtime/doc/options.txt           | 2 ++
 runtime/lua/vim/_meta/options.lua | 2 ++
 src/nvim/options.lua              | 2 ++
 4 files changed, 13 insertions(+)

diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 48e13d795e..ec4732c05a 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -86,6 +86,13 @@ command.  There are however, two general exceptions:
    end of the motion is moved to the end of the previous line and the motion
    becomes inclusive.  Example: "}" moves to the first line after a paragraph,
    but "d}" will not include that line.
+
+					*inclusive-motion-selection-exclusive*
+When 'selection' is "exclusive", |Visual| mode is active and an inclusive
+motion has been used, the cursor position will be adjusted by another
+character to the right, so that visual selction includes the expected text and
+can be acted upon.
+
 						*exclusive-linewise*
 2. If the motion is exclusive, the end of the motion is in column 1 and the
    start of the motion was at or before the first non-blank in the line, the
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 6fe208f506..84f0bfe141 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4982,6 +4982,8 @@ A jump table for the options with a short description can be found at |Q_op|.
 	selection.
 	When "old" is used and 'virtualedit' allows the cursor to move past
 	the end of line the line break still isn't included.
+	When "exclusive" is used, cursor position in visual mode will be
+	adjusted for inclusive motions |inclusive-motion-selection-exclusive|.
 	Note that when "exclusive" is used and selecting from the end
 	backwards, you cannot include the last character of a line, when
 	starting in Normal mode and 'virtualedit' empty.
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index fecbece655..63bf0df5f6 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -5214,6 +5214,8 @@ vim.go.sect = vim.go.sections
 --- selection.
 --- When "old" is used and 'virtualedit' allows the cursor to move past
 --- the end of line the line break still isn't included.
+--- When "exclusive" is used, cursor position in visual mode will be
+--- adjusted for inclusive motions `inclusive-motion-selection-exclusive`.
 --- Note that when "exclusive" is used and selecting from the end
 --- backwards, you cannot include the last character of a line, when
 --- starting in Normal mode and 'virtualedit' empty.
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index de152fb315..6dc32658fe 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -7011,6 +7011,8 @@ return {
         selection.
         When "old" is used and 'virtualedit' allows the cursor to move past
         the end of line the line break still isn't included.
+        When "exclusive" is used, cursor position in visual mode will be
+        adjusted for inclusive motions |inclusive-motion-selection-exclusive|.
         Note that when "exclusive" is used and selecting from the end
         backwards, you cannot include the last character of a line, when
         starting in Normal mode and 'virtualedit' empty.
-- 
cgit 


From 3406b0091122449318cf53c997eacc0621d02ee2 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 15 Dec 2024 07:09:57 +0800
Subject: vim-patch:fbe9a69: runtime(doc): Add a reference to |++opt| and
 |+cmd| at `:h :pedit`

closes: vim/vim#16217

https://github.com/vim/vim/commit/fbe9a6903a5b66d5b546a5a080726cba50372df5

Co-authored-by: Yinzuo Jiang 
---
 runtime/doc/windows.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 24aa7b1b11..a6e5984c82 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -985,6 +985,8 @@ CTRL-W g }						*CTRL-W_g}*
 		position isn't changed.  Useful example: >
 			:pedit +/fputc /usr/include/stdio.h
 <
+		Also see |++opt| and |+cmd|.
+
 							*:ps* *:psearch*
 :[range]ps[earch][!] [count] [/]pattern[/]
 		Works like |:ijump| but shows the found match in the preview
-- 
cgit 


From 852b6a6bcee56dc3d6c0f18f3e0119e3ffd682cc Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 15 Dec 2024 08:19:18 +0800
Subject: vim-patch:9.1.0927: style issues in insexpand.c (#31581)

Problem:  style issues in insexpand.c
Solution: add braces, use ternary operator to improve style
          (glepnir)

closes: vim/vim#16210

https://github.com/vim/vim/commit/6e19993991cfbea2e00435cc706a15ba7e766c55

vim-patch:9.1.0922: wrong MIN macro in popupmenu.c
vim-patch:9.1.0923: too many strlen() calls in filepath.c
vim-patch:9.1.0924: patch 9.1.0923 causes issues

Co-authored-by: glepnir 
---
 src/nvim/insexpand.c | 28 +++++++++-------------------
 1 file changed, 9 insertions(+), 19 deletions(-)

diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index b18c4ead41..2e3a2e39ac 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -756,7 +756,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir
 
     // "char_len" may be smaller than "compl_char_len" when using
     // thesaurus, only use the minimum when comparing.
-    int min_len = char_len < compl_char_len ? char_len : compl_char_len;
+    int min_len = MIN(char_len, compl_char_len);
 
     str = ins_compl_infercase_gettext(str, char_len, compl_char_len, min_len, &tofree);
   }
@@ -1007,9 +1007,9 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase)
   Direction dir = compl_direction;
 
   for (int i = 0; i < num_matches && add_r != FAIL; i++) {
-    if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
-                               CP_FAST | (icase ? CP_ICASE : 0),
-                               false, NULL)) == OK) {
+    add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
+                          CP_FAST | (icase ? CP_ICASE : 0), false, NULL);
+    if (add_r == OK) {
       // If dir was BACKWARD then honor it just once.
       dir = FORWARD;
     }
@@ -1277,21 +1277,15 @@ static int ins_compl_build_pum(void)
   i = 0;
   comp = match_head;
   while (comp != NULL) {
-    if (comp->cp_text[CPT_ABBR] != NULL) {
-      compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR];
-    } else {
-      compl_match_array[i].pum_text = comp->cp_str;
-    }
+    compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR] != NULL
+                                    ? comp->cp_text[CPT_ABBR] : comp->cp_str;
     compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND];
     compl_match_array[i].pum_info = comp->cp_text[CPT_INFO];
     compl_match_array[i].pum_score = comp->cp_score;
     compl_match_array[i].pum_user_abbr_hlattr = comp->cp_user_abbr_hlattr;
     compl_match_array[i].pum_user_kind_hlattr = comp->cp_user_kind_hlattr;
-    if (comp->cp_text[CPT_MENU] != NULL) {
-      compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU];
-    } else {
-      compl_match_array[i++].pum_extra = comp->cp_fname;
-    }
+    compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU] != NULL
+                                       ? comp->cp_text[CPT_MENU] : comp->cp_fname;
     compl_T *match_next = comp->cp_match_next;
     comp->cp_match_next = NULL;
     comp = match_next;
@@ -1578,11 +1572,7 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags,
       char *ptr = buf;
       while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) {
         ptr = regmatch->startp[0];
-        if (ctrl_x_mode_line_or_eval()) {
-          ptr = find_line_end(ptr);
-        } else {
-          ptr = find_word_end(ptr);
-        }
+        ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr);
         int add_r = ins_compl_add_infercase(regmatch->startp[0],
                                             (int)(ptr - regmatch->startp[0]),
                                             p_ic, files[i], *dir, false);
-- 
cgit 


From 820984daea8ed8143b0380f628dd54c2f2f581af Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 15 Dec 2024 12:03:41 +0100
Subject: build(deps): bump tree-sitter to v0.24.5

---
 cmake.deps/deps.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index f7827fc47a..dd18b1cb0c 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -50,8 +50,8 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a
 TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e
 TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz
 TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5
-TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/e3c82633389256ccc2c5ab2e509046cbf20453d3.tar.gz
-TREESITTER_SHA256 61a21f5d83cfe256472bfa941123a6941fb45073784ee7ec0bc32fdd52f7a4e4
+TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.5.tar.gz
+TREESITTER_SHA256 b5ac48acf5a04fd82ccd4246ad46354d9c434be26c9606233917549711e4252c
 
 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.2.tar.gz
 WASMTIME_SHA256 6d1c17c756b83f29f629963228e5fa208ba9d6578421ba2cd07132b6a120accb
-- 
cgit 


From 6c2c77b128e74c69be0d4fd79637c742f7eaba34 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 15 Dec 2024 12:04:33 +0100
Subject: vim-patch:9.1.0926: filetype: Pixi lock files are not recognized

Problem:  filetype: Pixi lock files are not recognized
Solution: detect "pixi.lock" file as yaml filetype
          (Brandon Maier)

Reference:
https://pixi.sh/latest/features/lockfile/

closes: vim/vim#16212

https://github.com/vim/vim/commit/7d1bb90dcf711c732a49e0a45e56028a4853a17d

Co-authored-by: Brandon Maier 
---
 runtime/lua/vim/filetype.lua       | 1 +
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 8a51d2a9d3..eb68e24acf 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1874,6 +1874,7 @@ local filename = {
   ['.clangd'] = 'yaml',
   ['.clang-format'] = 'yaml',
   ['.clang-tidy'] = 'yaml',
+  ['pixi.lock'] = 'yaml',
   ['yarn.lock'] = 'yaml',
   matplotlibrc = 'yaml',
   ['.condarc'] = 'yaml',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index d1551885b1..222cdb9504 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -890,7 +890,7 @@ func s:GetFilenameChecks() abort
     \ 'xslt': ['file.xsl', 'file.xslt'],
     \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
     \ 'yaml': ['file.yaml', 'file.yml', 'file.eyaml', 'any/.bundle/config', '.clangd', '.clang-format', '.clang-tidy', 'file.mplstyle', 'matplotlibrc', 'yarn.lock',
-    \          '/home/user/.kube/config', '.condarc', 'condarc'],
+    \          '/home/user/.kube/config', '.condarc', 'condarc', 'pixi.lock'],
     \ 'yang': ['file.yang'],
     \ 'yuck': ['file.yuck'],
     \ 'z8a': ['file.z8a'],
-- 
cgit 


From cc38630d39d2aa99183756d55849117daf6dfbfa Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 15 Dec 2024 19:27:12 +0100
Subject: vim-patch:9.1.0929: filetype: lalrpop files are not recognized

Problem:  filetype: lalrpop files are not recognized
Solution: detect '*.lalrpop' files as lalrpop filetype
          (David Thievon)

References:
https://github.com/lalrpop/lalrpop

closes: vim/vim#16223

https://github.com/vim/vim/commit/5a2e0cf5f11c611c9b01f1bd6e7294edf0dd2bf4

Co-authored-by: David Thievon 
---
 runtime/lua/vim/filetype.lua       | 1 +
 test/old/testdir/test_filetype.vim | 1 +
 2 files changed, 2 insertions(+)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index eb68e24acf..83e82392de 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -672,6 +672,7 @@ local extension = {
   k = 'kwt',
   ACE = 'lace',
   ace = 'lace',
+  lalrpop = 'lalrpop',
   latte = 'latte',
   lte = 'latte',
   ld = 'ld',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 222cdb9504..18957da220 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -404,6 +404,7 @@ func s:GetFilenameChecks() abort
     \ 'kscript': ['file.ks'],
     \ 'kwt': ['file.k'],
     \ 'lace': ['file.ace', 'file.ACE'],
+    \ 'lalrpop': ['file.lalrpop'],
     \ 'latte': ['file.latte', 'file.lte'],
     \ 'ld': ['file.ld', 'any/usr/lib/aarch64-xilinx-linux/ldscripts/aarch64elf32b.x'],
     \ 'ldapconf': ['ldap.conf', '.ldaprc', 'ldaprc'],
-- 
cgit 


From 01a97d2ad75a459cad850d542f9ad7c4467cb380 Mon Sep 17 00:00:00 2001
From: phanium <91544758+phanen@users.noreply.github.com>
Date: Mon, 16 Dec 2024 16:59:24 +0800
Subject: fix(api): nvim_win_set_buf(0, 0) fails if 'winfixbuf' is set #31576

## Problem
With 'winfixbuf' enabled, `nvim_win_set_buf` and `nvim_set_current_buf` fail
even if targeting the already-current buffer.

    vim.wo.winfixbuf = true
    vim.api.nvim_win_set_buf(0, 0)
    vim.api.nvim_set_current_buf(0)

Solution:
Check for this condition.
---
 src/nvim/api/vim.c                         |  2 +-
 src/nvim/api/window.c                      |  2 +-
 test/functional/options/winfixbuf_spec.lua | 38 ++++++++++++++++++++++--------
 3 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 1262af5e40..42fc21deac 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -888,7 +888,7 @@ void nvim_set_current_buf(Buffer buffer, Error *err)
 {
   buf_T *buf = find_buffer_by_handle(buffer, err);
 
-  if (!buf) {
+  if (!buf || curwin->w_buffer == buf) {
     return;
   }
 
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index ee7729ce81..f415415fa7 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -59,7 +59,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
 {
   win_T *win = find_window_by_handle(window, err);
   buf_T *buf = find_buffer_by_handle(buffer, err);
-  if (!win || !buf) {
+  if (!win || !buf || win->w_buffer == buf) {
     return;
   }
 
diff --git a/test/functional/options/winfixbuf_spec.lua b/test/functional/options/winfixbuf_spec.lua
index 124f194b5a..5bed2fc72f 100644
--- a/test/functional/options/winfixbuf_spec.lua
+++ b/test/functional/options/winfixbuf_spec.lua
@@ -8,8 +8,8 @@ describe("Nvim API calls with 'winfixbuf'", function()
     clear()
   end)
 
-  it("Calling vim.api.nvim_win_set_buf with 'winfixbuf'", function()
-    local results = exec_lua([[
+  it('vim.api.nvim_win_set_buf on non-current buffer', function()
+    local ok = exec_lua([[
       local function _setup_two_buffers()
         local buffer = vim.api.nvim_create_buf(true, true)
 
@@ -23,16 +23,16 @@ describe("Nvim API calls with 'winfixbuf'", function()
 
       local other_buffer = _setup_two_buffers()
       local current_window = 0
-      local results, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer)
+      local ok, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer)
 
-      return results
+      return ok
     ]])
 
-    assert(results == false)
+    assert(not ok)
   end)
 
-  it("Calling vim.api.nvim_set_current_buf with 'winfixbuf'", function()
-    local results = exec_lua([[
+  it('vim.api.nvim_set_current_buf on non-current buffer', function()
+    local ok = exec_lua([[
       local function _setup_two_buffers()
         local buffer = vim.api.nvim_create_buf(true, true)
 
@@ -45,11 +45,29 @@ describe("Nvim API calls with 'winfixbuf'", function()
       end
 
       local other_buffer = _setup_two_buffers()
-      local results, _ = pcall(vim.api.nvim_set_current_buf, other_buffer)
+      local ok, _ = pcall(vim.api.nvim_set_current_buf, other_buffer)
 
-      return results
+      return ok
     ]])
 
-    assert(results == false)
+    assert(not ok)
+  end)
+
+  it('vim.api.nvim_win_set_buf on current buffer', function()
+    exec_lua([[
+      vim.wo.winfixbuf = true
+      local curbuf = vim.api.nvim_get_current_buf()
+      vim.api.nvim_win_set_buf(0, curbuf)
+      assert(vim.api.nvim_get_current_buf() == curbuf)
+    ]])
+  end)
+
+  it('vim.api.nvim_set_current_buf on current buffer', function()
+    exec_lua([[
+      vim.wo.winfixbuf = true
+      local curbuf = vim.api.nvim_get_current_buf()
+      vim.api.nvim_set_current_buf(curbuf)
+      assert(vim.api.nvim_get_current_buf() == curbuf)
+    ]])
   end)
 end)
-- 
cgit 


From 9c6a3703bb15d56fecdd962512f69f0ccf6d398c Mon Sep 17 00:00:00 2001
From: Tomasz N 
Date: Mon, 16 Dec 2024 12:07:27 +0100
Subject: fix(messages): no message kind for :undo messages #31590

Problem: cannot handle `:undo` and `:redo` messages in a special way,
e.g. replace one by another.

Solution: add `undo` kind.
---
 runtime/doc/ui.txt                   |  1 +
 src/nvim/undo.c                      |  1 +
 test/functional/ui/messages_spec.lua | 30 ++++++++++++++++++++++++++++++
 3 files changed, 32 insertions(+)

diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 7696dbdb9d..6fb000b285 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -805,6 +805,7 @@ must handle.
 		"quickfix"	Quickfix navigation message
 		"search_cmd"	Entered search command
 		"search_count"	Search count message ("S" flag of 'shortmess')
+		"undo"		|:undo| and |:redo| message
 		"wildlist"	'wildmode' "list" message
 		"wmsg"		Warning ("search hit BOTTOM", |W10|, …)
 	    New kinds may be added in the future; clients should treat unknown
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 233e323f37..d523e55640 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1862,6 +1862,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
     u_oldcount = -1;
   }
 
+  msg_ext_set_kind("undo");
   int count = startcount;
   while (count--) {
     // Do the change warning now, so that it triggers FileChangedRO when
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index e5316f264b..2c1297b768 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -240,6 +240,36 @@ describe('ui/ext_messages', function()
         },
       },
     })
+
+    -- undo
+    feed('uu')
+    screen:expect({
+      grid = [[
+        ^                         |
+        {1:~                        }|*4
+      ]],
+      messages = {
+        {
+          content = { { 'Already at oldest change' } },
+          kind = 'undo',
+        },
+      },
+    })
+
+    feed('')
+    screen:expect({
+      grid = [[
+        line 1                   |
+        line^                     |
+        {1:~                        }|*3
+      ]],
+      messages = {
+        {
+          content = { { 'Already at newest change' } },
+          kind = 'undo',
+        },
+      },
+    })
   end)
 
   it(':echoerr', function()
-- 
cgit 


From 2d6f57b2891247f9ca0f6fb75c4b93fb2c714dc4 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Tue, 10 Dec 2024 14:03:44 +0100
Subject: refactor(wininfo): change wininfo from a linked list to an array

"wininfo" is going to be my next victim. The main problem with wininfo
is that it is "all or nothing", i e either all state about a buffer in a
window is considered valid or none of it is. This needs to be fixed to
address some long running grievances.

For now this is just a warmup: refactor it from a linked list to a
vector.
---
 src/klib/kvec.h        |  4 +++
 src/nvim/buffer.c      | 79 ++++++++++++++++++++++----------------------------
 src/nvim/buffer_defs.h |  8 ++---
 src/nvim/eval/buffer.c |  5 ++--
 src/nvim/eval/funcs.c  |  7 +++--
 src/nvim/ex_session.c  |  5 ++--
 src/nvim/globals.h     |  3 --
 src/nvim/window.c      | 42 ++++++++++++---------------
 8 files changed, 67 insertions(+), 86 deletions(-)

diff --git a/src/klib/kvec.h b/src/klib/kvec.h
index 1b9e6fd9f8..ea8cbe48cf 100644
--- a/src/klib/kvec.h
+++ b/src/klib/kvec.h
@@ -135,6 +135,10 @@
          : 0UL)), \
      &(v).items[(i)]))
 
+#define kv_shift(v, i, n) ((v).size -= (n), (i) < (v).size \
+                           && memmove(&kv_A(v, (i)), &kv_A(v, (i)+(n)), \
+                                      ((v).size-(i))*sizeof(kv_A(v, i))))
+
 #define kv_printf(v, ...) kv_do_printf(&(v), __VA_ARGS__)
 
 /// Type of a vector with a few first members allocated on stack
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 56ddadeb5c..5dcf7d8f49 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -875,6 +875,7 @@ static void free_buffer(buf_T *buf)
   aubuflocal_remove(buf);
   xfree(buf->additional_data);
   xfree(buf->b_prompt_text);
+  kv_destroy(buf->b_wininfo);
   callback_free(&buf->b_prompt_callback);
   callback_free(&buf->b_prompt_interrupt);
   clear_fmark(&buf->b_last_cursor, 0);
@@ -901,13 +902,10 @@ static void free_buffer(buf_T *buf)
 /// Free the b_wininfo list for buffer "buf".
 static void clear_wininfo(buf_T *buf)
 {
-  wininfo_T *wip;
-
-  while (buf->b_wininfo != NULL) {
-    wip = buf->b_wininfo;
-    buf->b_wininfo = wip->wi_next;
-    free_wininfo(wip, buf);
+  for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
+    free_wininfo(kv_A(buf->b_wininfo, i), buf);
   }
+  kv_size(buf->b_wininfo) = 0;
 }
 
 /// Free stuff in the buffer for ":bdel" and when wiping out the buffer.
@@ -1926,7 +1924,8 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
   }
 
   clear_wininfo(buf);
-  buf->b_wininfo = xcalloc(1, sizeof(wininfo_T));
+  WinInfo *curwin_info = xcalloc(1, sizeof(WinInfo));
+  kv_push(buf->b_wininfo, curwin_info);
 
   if (buf == curbuf) {
     free_buffer_stuff(buf, kBffInitChangedtick);  // delete local vars et al.
@@ -1964,9 +1963,9 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
     buf_copy_options(buf, BCO_ALWAYS);
   }
 
-  buf->b_wininfo->wi_mark = (fmark_T)INIT_FMARK;
-  buf->b_wininfo->wi_mark.mark.lnum = lnum;
-  buf->b_wininfo->wi_win = curwin;
+  curwin_info->wi_mark = (fmark_T)INIT_FMARK;
+  curwin_info->wi_mark.mark.lnum = lnum;
+  curwin_info->wi_win = curwin;
 
   hash_init(&buf->b_s.b_keywtab);
   hash_init(&buf->b_s.b_keywtab_ic);
@@ -2631,30 +2630,26 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T
                      bool copy_options)
   FUNC_ATTR_NONNULL_ARG(1)
 {
-  wininfo_T *wip;
+  WinInfo *wip;
 
-  for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
+  size_t i;
+  for (i = 0; i < kv_size(buf->b_wininfo); i++) {
+    wip = kv_A(buf->b_wininfo, i);
     if (wip->wi_win == win) {
       break;
     }
   }
-  if (wip == NULL) {
+
+  if (i == kv_size(buf->b_wininfo)) {
     // allocate a new entry
-    wip = xcalloc(1, sizeof(wininfo_T));
+    wip = xcalloc(1, sizeof(WinInfo));
     wip->wi_win = win;
     if (lnum == 0) {            // set lnum even when it's 0
       lnum = 1;
     }
   } else {
     // remove the entry from the list
-    if (wip->wi_prev) {
-      wip->wi_prev->wi_next = wip->wi_next;
-    } else {
-      buf->b_wininfo = wip->wi_next;
-    }
-    if (wip->wi_next) {
-      wip->wi_next->wi_prev = wip->wi_prev;
-    }
+    kv_shift(buf->b_wininfo, i, 1);
     if (copy_options && wip->wi_optset) {
       clear_winopt(&wip->wi_opt);
       deleteFoldRecurse(buf, &wip->wi_folds);
@@ -2679,17 +2674,15 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T
   }
 
   // insert the entry in front of the list
-  wip->wi_next = buf->b_wininfo;
-  buf->b_wininfo = wip;
-  wip->wi_prev = NULL;
-  if (wip->wi_next) {
-    wip->wi_next->wi_prev = wip;
-  }
+  kv_pushp(buf->b_wininfo);
+  memmove(&kv_A(buf->b_wininfo, 1), &kv_A(buf->b_wininfo, 0),
+          (kv_size(buf->b_wininfo) - 1) * sizeof(kv_A(buf->b_wininfo, 0)));
+  kv_A(buf->b_wininfo, 0) = wip;
 }
 
 /// Check that "wip" has 'diff' set and the diff is only for another tab page.
 /// That's because a diff is local to a tab page.
-static bool wininfo_other_tab_diff(wininfo_T *wip)
+static bool wininfo_other_tab_diff(WinInfo *wip)
   FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
 {
   if (!wip->wi_opt.wo_diff) {
@@ -2713,42 +2706,38 @@ static bool wininfo_other_tab_diff(wininfo_T *wip)
 /// @param skip_diff_buffer  when true, avoid windows with 'diff' set that is in another tab page.
 ///
 /// @return  NULL when there isn't any info.
-static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buffer)
+static WinInfo *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buffer)
   FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
 {
-  wininfo_T *wip;
-
-  for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
+  for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
+    WinInfo *wip = kv_A(buf->b_wininfo, i);
     if (wip->wi_win == curwin
         && (!skip_diff_buffer || !wininfo_other_tab_diff(wip))
         && (!need_options || wip->wi_optset)) {
-      break;
+      return wip;
     }
   }
 
-  if (wip != NULL) {
-    return wip;
-  }
-
   // If no wininfo for curwin, use the first in the list (that doesn't have
   // 'diff' set and is in another tab page).
   // If "need_options" is true skip entries that don't have options set,
   // unless the window is editing "buf", so we can copy from the window
   // itself.
   if (skip_diff_buffer) {
-    for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
+    for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
+      WinInfo *wip = kv_A(buf->b_wininfo, i);
       if (!wininfo_other_tab_diff(wip)
           && (!need_options
               || wip->wi_optset
               || (wip->wi_win != NULL
                   && wip->wi_win->w_buffer == buf))) {
-        break;
+        return wip;
       }
     }
-  } else {
-    wip = buf->b_wininfo;
+  } else if (kv_size(buf->b_wininfo)) {
+    return kv_A(buf->b_wininfo, 0);
   }
-  return wip;
+  return NULL;
 }
 
 /// Reset the local window options to the values last used in this window.
@@ -2760,7 +2749,7 @@ void get_winopts(buf_T *buf)
   clear_winopt(&curwin->w_onebuf_opt);
   clearFolding(curwin);
 
-  wininfo_T *const wip = find_wininfo(buf, true, true);
+  WinInfo *const wip = find_wininfo(buf, true, true);
   if (wip != NULL && wip->wi_win != curwin && wip->wi_win != NULL
       && wip->wi_win->w_buffer == buf) {
     win_T *wp = wip->wi_win;
@@ -2800,7 +2789,7 @@ fmark_T *buflist_findfmark(buf_T *buf)
 {
   static fmark_T no_position = { { 1, 0, 0 }, 0, 0, { 0 }, NULL };
 
-  wininfo_T *const wip = find_wininfo(buf, false, false);
+  WinInfo *const wip = find_wininfo(buf, false, false);
   return (wip == NULL) ? &no_position : &(wip->wi_mark);
 }
 
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index bb6eef3c29..3fe44beab9 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -70,7 +70,7 @@ typedef struct {
 // Mask to check for flags that prevent normal writing
 #define BF_WRITE_MASK   (BF_NOTEDITED + BF_NEW + BF_READERR)
 
-typedef struct wininfo_S wininfo_T;
+typedef struct wininfo_S WinInfo;
 typedef struct frame_S frame_T;
 typedef uint64_t disptick_T;  // display tick type
 
@@ -85,7 +85,7 @@ typedef struct {
 
 // Structure that contains all options that are local to a window.
 // Used twice in a window: for the current buffer and for all buffers.
-// Also used in wininfo_T.
+// Also used in WinInfo.
 typedef struct {
   int wo_arab;
 #define w_p_arab w_onebuf_opt.wo_arab  // 'arabic'
@@ -219,8 +219,6 @@ typedef struct {
 // The window-info is kept in a list at b_wininfo.  It is kept in
 // most-recently-used order.
 struct wininfo_S {
-  wininfo_T *wi_next;         // next entry or NULL for last entry
-  wininfo_T *wi_prev;         // previous entry or NULL for first entry
   win_T *wi_win;          // pointer to window that did set wi_mark
   fmark_T wi_mark;                // last cursor mark in the file
   bool wi_optset;               // true when wi_opt has useful values
@@ -411,7 +409,7 @@ struct file_buffer {
                                 // change
   linenr_T b_mod_xlines;        // number of extra buffer lines inserted;
                                 // negative when lines were deleted
-  wininfo_T *b_wininfo;         // list of last used info for each window
+  kvec_t(WinInfo *) b_wininfo;  // list of last used info for each window
   disptick_T b_mod_tick_syn;    // last display tick syntax was updated
   disptick_T b_mod_tick_decor;  // last display tick decoration providers
                                 // where invoked
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
index 73bfd6db2a..b4181eb186 100644
--- a/src/nvim/eval/buffer.c
+++ b/src/nvim/eval/buffer.c
@@ -66,12 +66,11 @@ buf_T *find_buffer(typval_T *avar)
 /// If there is a window for "curbuf", make it the current window.
 static void find_win_for_curbuf(void)
 {
-  wininfo_T *wip;
-
   // The b_wininfo list should have the windows that recently contained the
   // buffer, going over this is faster than going over all the windows.
   // Do check the buffer is still there.
-  FOR_ALL_BUF_WININFO(curbuf, wip) {
+  for (size_t i = 0; i < kv_size(curbuf->b_wininfo); i++) {
+    WinInfo *wip = kv_A(curbuf->b_wininfo, i);
     if (wip->wi_win != NULL && wip->wi_win->w_buffer == curbuf) {
       curwin = wip->wi_win;
       break;
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index f700e732a9..768d7664f7 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -2402,14 +2402,15 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
   if (buf == curwin->w_buffer) {
     changelistindex = curwin->w_changelistidx;
   } else {
-    wininfo_T *wip;
+    changelistindex = buf->b_changelistlen;
 
-    FOR_ALL_BUF_WININFO(buf, wip) {
+    for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
+      WinInfo *wip = kv_A(buf->b_wininfo, i);
       if (wip->wi_win == curwin) {
+        changelistindex = wip->wi_changelistidx;
         break;
       }
     }
-    changelistindex = wip != NULL ? wip->wi_changelistidx : buf->b_changelistlen;
   }
   tv_list_append_number(rettv->vval.v_list, (varnumber_T)changelistindex);
 
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index e7837467e4..ddc2705a02 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -659,9 +659,8 @@ static int makeopens(FILE *fd, char *dirnow)
         && buf->b_fname != NULL
         && buf->b_p_bl) {
       if (fprintf(fd, "badd +%" PRId64 " ",
-                  buf->b_wininfo == NULL
-                  ? 1
-                  : (int64_t)buf->b_wininfo->wi_mark.mark.lnum) < 0
+                  kv_size(buf->b_wininfo) == 0
+                  ? 1 : (int64_t)kv_A(buf->b_wininfo, 0)->wi_mark.mark.lnum) < 0
           || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
         return FAIL;
       }
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 472b77ccbe..a473180349 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -405,9 +405,6 @@ EXTERN buf_T *curbuf INIT( = NULL);    // currently active buffer
 #define FOR_ALL_BUFFERS_BACKWARDS(buf) \
   for (buf_T *buf = lastbuf; buf != NULL; buf = buf->b_prev)
 
-#define FOR_ALL_BUF_WININFO(buf, wip) \
-  for ((wip) = (buf)->b_wininfo; (wip) != NULL; (wip) = (wip)->wi_next)
-
 // List of files being edited (global argument list).  curwin->w_alist points
 // to this when the window is using the global argument list.
 EXTERN alist_T global_alist;    // global argument list
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 938d9d7618..b1c483547c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -5175,8 +5175,8 @@ win_T *win_alloc(win_T *after, bool hidden)
   return new_wp;
 }
 
-// Free one wininfo_T.
-void free_wininfo(wininfo_T *wip, buf_T *bp)
+// Free one WinInfo.
+void free_wininfo(WinInfo *wip, buf_T *bp)
 {
   if (wip->wi_optset) {
     clear_winopt(&wip->wi_opt);
@@ -5241,30 +5241,24 @@ void win_free(win_T *wp, tabpage_T *tp)
   // Remove the window from the b_wininfo lists, it may happen that the
   // freed memory is re-used for another window.
   FOR_ALL_BUFFERS(buf) {
-    for (wininfo_T *wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
+    WinInfo *wip_wp = NULL;
+    size_t pos_null = kv_size(buf->b_wininfo);
+    for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
+      WinInfo *wip = kv_A(buf->b_wininfo, i);
       if (wip->wi_win == wp) {
-        wininfo_T *wip2;
-
-        // If there already is an entry with "wi_win" set to NULL it
-        // must be removed, it would never be used.
-        // Skip "wip" itself, otherwise Coverity complains.
-        for (wip2 = buf->b_wininfo; wip2 != NULL; wip2 = wip2->wi_next) {
-          // `wip2 != wip` to satisfy Coverity. #14884
-          if (wip2 != wip && wip2->wi_win == NULL) {
-            if (wip2->wi_next != NULL) {
-              wip2->wi_next->wi_prev = wip2->wi_prev;
-            }
-            if (wip2->wi_prev == NULL) {
-              buf->b_wininfo = wip2->wi_next;
-            } else {
-              wip2->wi_prev->wi_next = wip2->wi_next;
-            }
-            free_wininfo(wip2, buf);
-            break;
-          }
-        }
+        wip_wp = wip;
+      } else if (wip->wi_win == NULL) {
+        pos_null = i;
+      }
+    }
 
-        wip->wi_win = NULL;
+    if (wip_wp) {
+      wip_wp->wi_win = NULL;
+      // If there already is an entry with "wi_win" set to NULL it
+      // must be removed, it would never be used.
+      if (pos_null < kv_size(buf->b_wininfo)) {
+        free_wininfo(kv_A(buf->b_wininfo, pos_null), buf);
+        kv_shift(buf->b_wininfo, pos_null, 1);
       }
     }
   }
-- 
cgit 


From 167a2383b9966ac227a77b0221088246e14ce75a Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Mon, 16 Dec 2024 04:00:20 -0800
Subject: fix(api): not using TRY_WRAP, generic error messages #31595

Problem:
- API functions using `try_start` directly instead of `TRY_WRAP`, do not
  surface the underlying error message, and instead show generic things
  like "Failed to set buffer".
- Error handling code is duplicated in the API impl, instead of
  delegating to the vim buffer/window handling logic.

Solution:
- Use `TRY_WRAP`.
---
 src/nvim/api/private/helpers.c             |  9 +--
 src/nvim/api/vim.c                         | 19 ++-----
 src/nvim/api/window.c                      |  7 +--
 src/nvim/window.c                          | 40 +++++---------
 test/functional/api/window_spec.lua        |  2 +-
 test/functional/options/winfixbuf_spec.lua | 88 +++++++++++-------------------
 test/old/testdir/test_winfixbuf.vim        |  6 +-
 7 files changed, 62 insertions(+), 109 deletions(-)

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 8ddaecc58e..5bf66a092f 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -101,9 +101,9 @@ bool try_leave(const TryState *const tstate, Error *const err)
   return ret;
 }
 
-/// Start block that may cause vimscript exceptions
+/// Starts a block that may cause Vimscript exceptions; must be mirrored by `try_end()` call.
 ///
-/// Each try_start() call should be mirrored by try_end() call.
+/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file).
 ///
 /// To be used as a replacement of `:try … catch … endtry` in C code, in cases
 /// when error flag could not already be set. If there may be pending error
@@ -114,8 +114,9 @@ void try_start(void)
   trylevel++;
 }
 
-/// End try block, set the error message if any and return true if an error
-/// occurred.
+/// Ends a `try_start` block; sets error message if any and returns true if an error occurred.
+///
+/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file).
 ///
 /// @param err Pointer to the stack-allocated error object
 /// @return true if an error occurred
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 42fc21deac..1acfa0d34b 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -596,6 +596,7 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er
   int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
   TryState tstate;
 
+  // XXX: intentionally not using `TRY_WRAP`, to avoid `did_emsg=false` in `try_end`.
   try_enter(&tstate);
   do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie);
   vim_ignored = try_leave(&tstate, err);
@@ -888,23 +889,13 @@ void nvim_set_current_buf(Buffer buffer, Error *err)
 {
   buf_T *buf = find_buffer_by_handle(buffer, err);
 
-  if (!buf || curwin->w_buffer == buf) {
-    return;
-  }
-
-  if (curwin->w_p_wfb) {
-    api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
+  if (!buf) {
     return;
   }
 
-  try_start();
-  int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
-  if (!try_end(err) && result == FAIL) {
-    api_set_error(err,
-                  kErrorTypeException,
-                  "Failed to switch to buffer %d",
-                  buffer);
-  }
+  TRY_WRAP(err, {
+    do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
+  });
 }
 
 /// Gets the current list of window handles.
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index f415415fa7..170d5c6cdb 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -59,12 +59,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
 {
   win_T *win = find_window_by_handle(window, err);
   buf_T *buf = find_buffer_by_handle(buffer, err);
-  if (!win || !buf || win->w_buffer == buf) {
-    return;
-  }
-
-  if (win->w_p_wfb) {
-    api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
+  if (!win || !buf) {
     return;
   }
 
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 938d9d7618..1430b2efb2 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -746,40 +746,28 @@ void win_set_buf(win_T *win, buf_T *buf, Error *err)
   RedrawingDisabled++;
 
   switchwin_T switchwin;
-  if (switch_win_noblock(&switchwin, win, tab, true) == FAIL) {
-    api_set_error(err,
-                  kErrorTypeException,
-                  "Failed to switch to window %d",
-                  win->handle);
-    goto cleanup;
-  }
-
-  try_start();
 
-  const int save_acd = p_acd;
-  if (!switchwin.sw_same_win) {
-    // Temporarily disable 'autochdir' when setting buffer in another window.
-    p_acd = false;
-  }
+  TRY_WRAP(err, {
+    int win_result = switch_win_noblock(&switchwin, win, tab, true);
+    if (win_result != FAIL) {
+      const int save_acd = p_acd;
+      if (!switchwin.sw_same_win) {
+        // Temporarily disable 'autochdir' when setting buffer in another window.
+        p_acd = false;
+      }
 
-  int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
+      do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
 
-  if (!switchwin.sw_same_win) {
-    p_acd = save_acd;
-  }
-
-  if (!try_end(err) && result == FAIL) {
-    api_set_error(err,
-                  kErrorTypeException,
-                  "Failed to set buffer %d",
-                  buf->handle);
-  }
+      if (!switchwin.sw_same_win) {
+        p_acd = save_acd;
+      }
+    }
+  });
 
   // If window is not current, state logic will not validate its cursor. So do it now.
   // Still needed if do_buffer returns FAIL (e.g: autocmds abort script after buffer was set).
   validate_cursor(curwin);
 
-cleanup:
   restore_win_noblock(&switchwin, true);
   RedrawingDisabled--;
 }
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index 92999f383a..4662ace4bf 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -1664,7 +1664,7 @@ describe('API/win', function()
         autocmd BufWinEnter * ++once let fired = v:true
       ]])
       eq(
-        'Failed to set buffer 2',
+        'Vim:E37: No write since last change (add ! to override)',
         pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' })
       )
       eq(false, eval('fired'))
diff --git a/test/functional/options/winfixbuf_spec.lua b/test/functional/options/winfixbuf_spec.lua
index 5bed2fc72f..a01650ea71 100644
--- a/test/functional/options/winfixbuf_spec.lua
+++ b/test/functional/options/winfixbuf_spec.lua
@@ -1,73 +1,51 @@
 local n = require('test.functional.testnvim')()
+local t = require('test.testutil')
 
 local clear = n.clear
 local exec_lua = n.exec_lua
 
-describe("Nvim API calls with 'winfixbuf'", function()
+describe("'winfixbuf'", function()
   before_each(function()
     clear()
   end)
 
-  it('vim.api.nvim_win_set_buf on non-current buffer', function()
-    local ok = exec_lua([[
-      local function _setup_two_buffers()
-        local buffer = vim.api.nvim_create_buf(true, true)
-
-        vim.api.nvim_create_buf(true, true)  -- Make another buffer
-
-        local current_window = 0
-        vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window})
-
-        return buffer
-      end
-
-      local other_buffer = _setup_two_buffers()
-      local current_window = 0
-      local ok, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer)
-
-      return ok
+  ---@return integer
+  local function setup_winfixbuf()
+    return exec_lua([[
+      local buffer = vim.api.nvim_create_buf(true, true)
+      vim.api.nvim_create_buf(true, true)  -- Make another buffer
+      vim.wo.winfixbuf = true
+      return buffer
     ]])
-
-    assert(not ok)
+  end
+
+  it('nvim_win_set_buf on non-current buffer', function()
+    local other_buf = setup_winfixbuf()
+    t.eq(
+      "Vim:E1513: Cannot switch buffer. 'winfixbuf' is enabled",
+      t.pcall_err(n.api.nvim_win_set_buf, 0, other_buf)
+    )
   end)
 
-  it('vim.api.nvim_set_current_buf on non-current buffer', function()
-    local ok = exec_lua([[
-      local function _setup_two_buffers()
-        local buffer = vim.api.nvim_create_buf(true, true)
-
-        vim.api.nvim_create_buf(true, true)  -- Make another buffer
-
-        local current_window = 0
-        vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window})
-
-        return buffer
-      end
-
-      local other_buffer = _setup_two_buffers()
-      local ok, _ = pcall(vim.api.nvim_set_current_buf, other_buffer)
-
-      return ok
-    ]])
-
-    assert(not ok)
+  it('nvim_set_current_buf on non-current buffer', function()
+    local other_buf = setup_winfixbuf()
+    t.eq(
+      "Vim:E1513: Cannot switch buffer. 'winfixbuf' is enabled",
+      t.pcall_err(n.api.nvim_set_current_buf, other_buf)
+    )
   end)
 
-  it('vim.api.nvim_win_set_buf on current buffer', function()
-    exec_lua([[
-      vim.wo.winfixbuf = true
-      local curbuf = vim.api.nvim_get_current_buf()
-      vim.api.nvim_win_set_buf(0, curbuf)
-      assert(vim.api.nvim_get_current_buf() == curbuf)
-    ]])
+  it('nvim_win_set_buf on current buffer', function()
+    setup_winfixbuf()
+    local curbuf = n.api.nvim_get_current_buf()
+    n.api.nvim_win_set_buf(0, curbuf)
+    t.eq(curbuf, n.api.nvim_get_current_buf())
   end)
 
-  it('vim.api.nvim_set_current_buf on current buffer', function()
-    exec_lua([[
-      vim.wo.winfixbuf = true
-      local curbuf = vim.api.nvim_get_current_buf()
-      vim.api.nvim_set_current_buf(curbuf)
-      assert(vim.api.nvim_get_current_buf() == curbuf)
-    ]])
+  it('nvim_set_current_buf on current buffer', function()
+    setup_winfixbuf()
+    local curbuf = n.api.nvim_get_current_buf()
+    n.api.nvim_set_current_buf(curbuf)
+    t.eq(curbuf, n.api.nvim_get_current_buf())
   end)
 end)
diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim
index 1777bec184..cc8ff86fa1 100644
--- a/test/old/testdir/test_winfixbuf.vim
+++ b/test/old/testdir/test_winfixbuf.vim
@@ -2613,7 +2613,7 @@ EOF
 
   try
     pyxdo test_winfixbuf_Test_pythonx_pyxdo_set_buffer()
-  catch /pynvim\.api\.common\.NvimError: E1513:/
+  catch /pynvim\.api\.common\.NvimError: Vim:E1513:/
     let l:caught = 1
   endtry
 
@@ -2644,7 +2644,7 @@ func Test_pythonx_pyxfile()
 
   try
     pyxfile file.py
-  catch /pynvim\.api\.common\.NvimError: E1513:/
+  catch /pynvim\.api\.common\.NvimError: Vim:E1513:/
     let l:caught = 1
   endtry
 
@@ -2676,7 +2676,7 @@ import vim
 buffer = vim.vars["_previous_buffer"]
 vim.current.buffer = vim.buffers[buffer]
 EOF
-  catch /pynvim\.api\.common\.NvimError: E1513:/
+  catch /pynvim\.api\.common\.NvimError: Vim:E1513:/
     let l:caught = 1
   endtry
 
-- 
cgit 


From 6c975515c5608b500ac96fae598f0b5a48e03ddb Mon Sep 17 00:00:00 2001
From: Juan Cruz De La Torre 
Date: Mon, 16 Dec 2024 11:38:57 -0300
Subject: fix(diagnostic): vim.diagnostic.setqflist() opens loclist on first
 call #31585

Problem:
Regression from de794f2d2409: `vim.diagnostic.setqflist{open=true}` attempts to
open the location list instead of the diagnostics quickfix list if it didn't
exist before. This is because we are using `qf_id` to decide which to open, but
`qf_id=nil` when there is no existing diagnostics quickfix list with a given
title ("Diagnostics" by default).

Solution:
- Revert to using `loclist` to decide which to open.
- Add tests.
---
 runtime/lua/vim/diagnostic.lua          |  2 +-
 test/functional/lua/diagnostic_spec.lua | 69 +++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index ded7a8f89d..39ef18e0b4 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -871,7 +871,7 @@ local function set_list(loclist, opts)
   end
 
   if open then
-    if qf_id then
+    if not loclist then
       -- First navigate to the diagnostics quickfix list.
       local nr = vim.fn.getqflist({ id = qf_id, nr = 0 }).nr
       api.nvim_command(nr .. 'chistory')
diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua
index eb1ac3e6a1..e52a8991ce 100644
--- a/test/functional/lua/diagnostic_spec.lua
+++ b/test/functional/lua/diagnostic_spec.lua
@@ -5,6 +5,7 @@ local command = n.command
 local clear = n.clear
 local exec_lua = n.exec_lua
 local eq = t.eq
+local neq = t.neq
 local matches = t.matches
 local api = n.api
 local pcall_err = t.pcall_err
@@ -3212,6 +3213,74 @@ describe('vim.diagnostic', function()
     end)
   end)
 
+  describe('setqflist()', function()
+    it('updates existing diagnostics quickfix if one already exists', function()
+      local result = exec_lua(function()
+        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
+
+        vim.fn.setqflist({}, ' ', { title = 'Diagnostics' })
+        local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id
+
+        vim.diagnostic.setqflist({ title = 'Diagnostics' })
+        local qf_id = vim.fn.getqflist({ id = 0, nr = '$' }).id
+
+        return { diagnostics_qf_id, qf_id }
+      end)
+
+      eq(result[1], result[2])
+    end)
+
+    it('navigates to existing diagnostics quickfix if one already exists and open=true', function()
+      local result = exec_lua(function()
+        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
+
+        vim.fn.setqflist({}, ' ', { title = 'Diagnostics' })
+        local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id
+
+        vim.fn.setqflist({}, ' ', { title = 'Other' })
+
+        vim.diagnostic.setqflist({ title = 'Diagnostics', open = true })
+        local qf_id = vim.fn.getqflist({ id = 0 }).id
+
+        return { diagnostics_qf_id, qf_id }
+      end)
+
+      eq(result[1], result[2])
+    end)
+
+    it('sets new diagnostics quickfix as active when open=true', function()
+      local result = exec_lua(function()
+        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
+
+        vim.fn.setqflist({}, ' ', { title = 'Other' })
+        local other_qf_id = vim.fn.getqflist({ id = 0 }).id
+
+        vim.diagnostic.setqflist({ title = 'Diagnostics', open = true })
+        local qf_id = vim.fn.getqflist({ id = 0 }).id
+
+        return { other_qf_id, qf_id }
+      end)
+
+      neq(result[1], result[2])
+    end)
+
+    it('opens quickfix window when open=true', function()
+      local qf_winid = exec_lua(function()
+        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
+
+        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
+          _G.make_error('Error', 1, 1, 1, 1),
+        })
+
+        vim.diagnostic.setqflist({ open = true })
+
+        return vim.fn.getqflist({ winid = 0 }).winid
+      end)
+
+      neq(0, qf_winid)
+    end)
+  end)
+
   describe('match()', function()
     it('matches a string', function()
       local msg = 'ERROR: george.txt:19:84:Two plus two equals five'
-- 
cgit 


From b5c0290803508c0dc996a9bed70f5fa9ceb93c44 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Mon, 16 Dec 2024 15:31:01 +0100
Subject: ci(build.yml): disable security restriction

A new security restriction in Ubuntu 24.04 prevents users from using
`unshare`, so we need to disable it in order for the test to work
properly.
---
 .github/workflows/build.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ab313729b9..3211216c85 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -89,7 +89,9 @@ jobs:
           for d in *; do (cd "$d"; rm -rf ./autom4te.cache; make clean || true; make distclean || true); done
 
       - name: Re-build bundled dependencies with no network access
-        run: unshare --map-root-user --net make deps DEPS_CMAKE_FLAGS=-DUSE_EXISTING_SRC_DIR=ON
+        run: |
+          sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
+          unshare --map-root-user --net make deps DEPS_CMAKE_FLAGS=-DUSE_EXISTING_SRC_DIR=ON
 
       - name: Build
         run: make CMAKE_FLAGS="-D CI_BUILD=ON"
-- 
cgit 


From 47f2769b462eb6bd1c10efec3c32ed55134ce628 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 13 Dec 2024 14:22:59 +0000
Subject: fix(Man): completion on Mac

Problem:
`man -w` does not work on recent versions of MacOs.

Solution:
Make it so an empty result is interpreted as an error unless silent=true
---
 runtime/lua/man.lua                 |  9 ++++++---
 test/functional/plugin/man_spec.lua | 10 ++++++++++
 test/testutil.lua                   | 11 +++++++++++
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index 114c94f9e5..bf2cc477fd 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -21,9 +21,12 @@ end
 local function system(cmd, silent, env)
   local r = vim.system(cmd, { env = env, timeout = 10000 }):wait()
 
-  if r.code ~= 0 and not silent then
-    local cmd_str = table.concat(cmd, ' ')
-    man_error(string.format("command error '%s': %s", cmd_str, r.stderr))
+  if not silent then
+    if r.code ~= 0 then
+      local cmd_str = table.concat(cmd, ' ')
+      man_error(string.format("command error '%s': %s", cmd_str, r.stderr))
+    end
+    assert(r.stdout ~= '')
   end
 
   return assert(r.stdout)
diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua
index 8906e60dce..e3f3de6252 100644
--- a/test/functional/plugin/man_spec.lua
+++ b/test/functional/plugin/man_spec.lua
@@ -263,4 +263,14 @@ describe(':Man', function()
       { '1', 'other_man' },
     }, get_search_history('other_man(1)'))
   end)
+
+  it('can complete', function()
+    t.skip(t.is_os('mac') and t.is_arch('x86_64'), 'not supported on intel mac')
+    eq(
+      true,
+      exec_lua(function()
+        return #require('man').man_complete('f', 'Man g') > 0
+      end)
+    )
+  end)
 end)
diff --git a/test/testutil.lua b/test/testutil.lua
index 00b30d74d5..007e017ac6 100644
--- a/test/testutil.lua
+++ b/test/testutil.lua
@@ -409,6 +409,17 @@ function M.is_os(s)
   )
 end
 
+local architecture = uv.os_uname().machine
+
+--- @param s 'x86_64'|'arm64'
+--- @return boolean
+function M.is_arch(s)
+  if not (s == 'x86_64' or s == 'arm64') then
+    error('unknown architecture: ' .. tostring(s))
+  end
+  return s == architecture
+end
+
 local tmpname_id = 0
 local tmpdir = os.getenv('TMPDIR') or os.getenv('TEMP')
 local tmpdir_is_local = not not (tmpdir and tmpdir:find('Xtest'))
-- 
cgit 


From fb8372adb3b9f50d4d18eba6f650c3728353ab00 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 16 Dec 2024 15:07:44 +0100
Subject: build(deps): bump luajit to HEAD - f73e649a9

---
 cmake.deps/deps.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index dd18b1cb0c..6473a81880 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -1,8 +1,8 @@
 LIBUV_URL https://github.com/libuv/libuv/archive/v1.49.2.tar.gz
 LIBUV_SHA256 388ffcf3370d4cf7c4b3a3205504eea06c4be5f9e80d2ab32d19f8235accc1cf
 
-LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/19878ec05c239ccaf5f3d17af27670a963e25b8b.tar.gz
-LUAJIT_SHA256 e91acbe181cf6ffa3ef15870b8e620131002240ba24c5c779fd0131db021517f
+LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/f73e649a954b599fc184726c376476e7a5c439ca.tar.gz
+LUAJIT_SHA256 bc992b3ae0a8f5f0ebbf141626b7c99fac794c94ec6896d973582525c7ef868d
 
 LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz
 LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333
-- 
cgit 


From 022449b5223659d515b78bada7de2fac8718820a Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Mon, 16 Dec 2024 08:34:16 -0800
Subject: fix(api): generic error messages, not using TRY_WRAP #31596

Problem:
- API functions using `try_start` directly, do not surface the
  underlying error message, and instead show generic messages.
- Error-handling code is duplicated in the API impl.
- Failure modes are not tested.

Solution:
- Use `TRY_WRAP`.
- Add tests.
---
 runtime/doc/api.txt                         |  8 +++---
 runtime/lua/vim/_meta/api.lua               |  6 ++---
 src/nvim/api/vim.c                          | 35 +++++++-------------------
 src/nvim/api/vimscript.c                    | 24 ++++++++----------
 src/nvim/api/window.c                       | 38 +++++++++++------------------
 test/functional/api/vim_spec.lua            | 22 ++++++++++++++++-
 test/functional/api/window_spec.lua         | 18 ++++++++++++++
 test/functional/autocmd/dirchanged_spec.lua |  9 +++----
 test/functional/lua/vim_spec.lua            | 11 +++++++++
 9 files changed, 92 insertions(+), 79 deletions(-)

diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index cb3b2a3f77..70fda5ce8a 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1647,11 +1647,9 @@ nvim_command({command})                                       *nvim_command()*
 
     On execution error: fails with Vimscript error, updates v:errmsg.
 
-    Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate
-    multiple lines of Vim script or an Ex command directly, use
-    |nvim_exec2()|. To construct an Ex command using a structured format and
-    then execute it, use |nvim_cmd()|. To modify an Ex command before
-    evaluating it, use |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
+    Prefer |nvim_cmd()| or |nvim_exec2()| instead. To modify an Ex command in
+    a structured way before executing it, modify the result of
+    |nvim_parse_cmd()| then pass it to |nvim_cmd()|.
 
     Parameters: ~
       • {command}  Ex command string
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index b2385197bd..55274963ed 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -885,10 +885,8 @@ function vim.api.nvim_cmd(cmd, opts) end
 ---
 --- On execution error: fails with Vimscript error, updates v:errmsg.
 ---
---- Prefer using `nvim_cmd()` or `nvim_exec2()` over this. To evaluate multiple lines of Vim script
---- or an Ex command directly, use `nvim_exec2()`. To construct an Ex command using a structured
---- format and then execute it, use `nvim_cmd()`. To modify an Ex command before evaluating it, use
---- `nvim_parse_cmd()` in conjunction with `nvim_cmd()`.
+--- Prefer `nvim_cmd()` or `nvim_exec2()` instead. To modify an Ex command in a structured way
+--- before executing it, modify the result of `nvim_parse_cmd()` then pass it to `nvim_cmd()`.
 ---
 --- @param command string Ex command string
 function vim.api.nvim_command(command) end
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 1acfa0d34b..d82f90d1dd 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -669,16 +669,9 @@ void nvim_set_current_dir(String dir, Error *err)
   memcpy(string, dir.data, dir.size);
   string[dir.size] = NUL;
 
-  try_start();
-
-  if (!changedir_func(string, kCdScopeGlobal)) {
-    if (!try_end(err)) {
-      api_set_error(err, kErrorTypeException, "Failed to change directory");
-    }
-    return;
-  }
-
-  try_end(err);
+  TRY_WRAP(err, {
+    changedir_func(string, kCdScopeGlobal);
+  });
 }
 
 /// Gets the current line.
@@ -942,14 +935,9 @@ void nvim_set_current_win(Window window, Error *err)
     return;
   }
 
-  try_start();
-  goto_tabpage_win(win_find_tabpage(win), win);
-  if (!try_end(err) && win != curwin) {
-    api_set_error(err,
-                  kErrorTypeException,
-                  "Failed to switch to window %d",
-                  window);
-  }
+  TRY_WRAP(err, {
+    goto_tabpage_win(win_find_tabpage(win), win);
+  });
 }
 
 /// Creates a new, empty, unnamed buffer.
@@ -1208,14 +1196,9 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
     return;
   }
 
-  try_start();
-  goto_tabpage_tp(tp, true, true);
-  if (!try_end(err) && tp != curtab) {
-    api_set_error(err,
-                  kErrorTypeException,
-                  "Failed to switch to tabpage %d",
-                  tabpage);
-  }
+  TRY_WRAP(err, {
+    goto_tabpage_tp(tp, true, true);
+  });
 }
 
 /// Pastes at cursor (in any mode), and sets "redo" so dot (|.|) will repeat the input. UIs call
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 165cc93fbe..0ff2b037ce 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -125,19 +125,17 @@ theend:
 ///
 /// On execution error: fails with Vimscript error, updates v:errmsg.
 ///
-/// Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate multiple lines of Vim script
-/// or an Ex command directly, use |nvim_exec2()|. To construct an Ex command using a structured
-/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
-/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
+/// Prefer |nvim_cmd()| or |nvim_exec2()| instead. To modify an Ex command in a structured way
+/// before executing it, modify the result of |nvim_parse_cmd()| then pass it to |nvim_cmd()|.
 ///
 /// @param command  Ex command string
 /// @param[out] err Error details (Vim error), if any
 void nvim_command(String command, Error *err)
   FUNC_API_SINCE(1)
 {
-  try_start();
-  do_cmdline_cmd(command.data);
-  try_end(err);
+  TRY_WRAP(err, {
+    do_cmdline_cmd(command.data);
+  });
 }
 
 /// Evaluates a Vimscript |expression|. Dicts and Lists are recursively expanded.
@@ -283,13 +281,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
   bool mustfree = false;
   switch (dict.type) {
   case kObjectTypeString:
-    try_start();
-    if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) {
-      api_set_error(err, kErrorTypeException,
-                    "Failed to evaluate dict expression");
-    }
-    clear_evalarg(&EVALARG_EVALUATE, NULL);
-    if (try_end(err)) {
+    TRY_WRAP(err, {
+      eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE);
+      clear_evalarg(&EVALARG_EVALUATE, NULL);
+    });
+    if (ERROR_SET(err)) {
       return rv;
     }
     // Evaluation of the string arg created a new dict or increased the
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 170d5c6cdb..f5e8d8f086 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -182,14 +182,9 @@ void nvim_win_set_height(Window window, Integer height, Error *err)
     return;
   }
 
-  if (height > INT_MAX || height < INT_MIN) {
-    api_set_error(err, kErrorTypeValidation, "Height value outside range");
-    return;
-  }
-
-  try_start();
-  win_setheight_win((int)height, win);
-  try_end(err);
+  TRY_WRAP(err, {
+    win_setheight_win((int)height, win);
+  });
 }
 
 /// Gets the window width
@@ -224,14 +219,9 @@ void nvim_win_set_width(Window window, Integer width, Error *err)
     return;
   }
 
-  if (width > INT_MAX || width < INT_MIN) {
-    api_set_error(err, kErrorTypeValidation, "Width value outside range");
-    return;
-  }
-
-  try_start();
-  win_setwidth_win((int)width, win);
-  try_end(err);
+  TRY_WRAP(err, {
+    win_setwidth_win((int)width, win);
+  });
 }
 
 /// Gets a window-scoped (w:) variable
@@ -436,15 +426,15 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
   }
   tabpage_T *tabpage = win_find_tabpage(win);
 
-  try_start();
   Object res = OBJECT_INIT;
-  win_execute_T win_execute_args;
-  if (win_execute_before(&win_execute_args, win, tabpage)) {
-    Array args = ARRAY_DICT_INIT;
-    res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err);
-  }
-  win_execute_after(&win_execute_args);
-  try_end(err);
+  TRY_WRAP(err, {
+    win_execute_T win_execute_args;
+    if (win_execute_before(&win_execute_args, win, tabpage)) {
+      Array args = ARRAY_DICT_INIT;
+      res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err);
+    }
+    win_execute_after(&win_execute_args);
+  });
   return res;
 }
 
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 2eeb5c18a1..879df690d1 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -695,7 +695,7 @@ describe('API', function()
         pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 })
       )
       eq(
-        'Failed to evaluate dict expression',
+        'Vim:E121: Undefined variable: foo',
         pcall_err(request, 'nvim_call_dict_function', 'foo', 'f', { 1, 2 })
       )
       eq('dict not found', pcall_err(request, 'nvim_call_dict_function', '42', 'f', { 1, 2 }))
@@ -1957,6 +1957,16 @@ describe('API', function()
       api.nvim_set_current_win(api.nvim_list_wins()[2])
       eq(api.nvim_list_wins()[2], api.nvim_get_current_win())
     end)
+
+    it('failure modes', function()
+      n.command('split')
+
+      eq('Invalid window id: 9999', pcall_err(api.nvim_set_current_win, 9999))
+
+      -- XXX: force nvim_set_current_win to fail somehow.
+      n.command("au WinLeave * throw 'foo'")
+      eq('WinLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_win, 1000))
+    end)
   end)
 
   describe('nvim_{get,set}_current_tabpage, nvim_list_tabpages', function()
@@ -1976,6 +1986,16 @@ describe('API', function()
       eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage())
       eq(api.nvim_list_wins()[2], api.nvim_get_current_win())
     end)
+
+    it('failure modes', function()
+      n.command('tabnew')
+
+      eq('Invalid tabpage id: 999', pcall_err(api.nvim_set_current_tabpage, 999))
+
+      -- XXX: force nvim_set_current_tabpage to fail somehow.
+      n.command("au TabLeave * throw 'foo'")
+      eq('TabLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_tabpage, 1))
+    end)
   end)
 
   describe('nvim_get_mode', function()
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index 4662ace4bf..078d581b6f 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -359,6 +359,15 @@ describe('API/win', function()
       eq(2, api.nvim_win_get_height(api.nvim_list_wins()[2]))
     end)
 
+    it('failure modes', function()
+      command('split')
+      eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_height, 999999, 10))
+      eq(
+        'Wrong type for argument 2 when calling nvim_win_set_height, expecting Integer',
+        pcall_err(api.nvim_win_set_height, 0, 0.9)
+      )
+    end)
+
     it('correctly handles height=1', function()
       command('split')
       api.nvim_set_current_win(api.nvim_list_wins()[1])
@@ -409,6 +418,15 @@ describe('API/win', function()
       eq(2, api.nvim_win_get_width(api.nvim_list_wins()[2]))
     end)
 
+    it('failure modes', function()
+      command('vsplit')
+      eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_width, 999999, 10))
+      eq(
+        'Wrong type for argument 2 when calling nvim_win_set_width, expecting Integer',
+        pcall_err(api.nvim_win_set_width, 0, 0.9)
+      )
+    end)
+
     it('do not cause ml_get errors with foldmethod=expr #19989', function()
       insert([[
         aaaaa
diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua
index 1cde0e0552..9b572df568 100644
--- a/test/functional/autocmd/dirchanged_spec.lua
+++ b/test/functional/autocmd/dirchanged_spec.lua
@@ -351,11 +351,10 @@ describe('autocmd DirChanged and DirChangedPre', function()
     eq(2, eval('g:cdprecount'))
     eq(2, eval('g:cdcount'))
 
-    local status, err = pcall(function()
-      request('nvim_set_current_dir', '/doesnotexist')
-    end)
-    eq(false, status)
-    eq('Failed to change directory', string.match(err, ': (.*)'))
+    eq(
+      'Vim:E344: Can\'t find directory "/doesnotexist" in cdpath',
+      t.pcall_err(request, 'nvim_set_current_dir', '/doesnotexist')
+    )
     eq({ directory = '/doesnotexist', scope = 'global', changed_window = false }, eval('g:evpre'))
     eq(3, eval('g:cdprecount'))
     eq(2, eval('g:cdcount'))
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 3cfbfe167a..9e75861aa0 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -3955,6 +3955,17 @@ stack traceback:
       eq(win2, val)
     end)
 
+    it('failure modes', function()
+      matches(
+        'nvim_exec2%(%): Vim:E492: Not an editor command: fooooo',
+        pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() vim.cmd 'fooooo' end)]])
+      )
+      eq(
+        'Error executing lua: [string ""]:0: fooooo',
+        pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() error('fooooo') end)]])
+      )
+    end)
+
     it('does not cause ml_get errors with invalid visual selection', function()
       -- Add lines to the current buffer and make another window looking into an empty buffer.
       exec_lua [[
-- 
cgit 


From 98e361031636d0f7838d59254b1fc2a49cb388e8 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 17 Dec 2024 08:19:41 +0800
Subject: vim-patch:41d6de2: runtime(doc): update the change.txt help file

https://github.com/vim/vim/commit/41d6de2974429f5fc76fbeacc233a1fa66c6f869

Co-authored-by: Antonio Giovanni Colombo 
---
 runtime/doc/change.txt  | 15 +++++++--------
 runtime/doc/various.txt |  3 +++
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index 768a449581..9e44f54e6b 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1443,18 +1443,17 @@ since formatting is highly dependent on the type of file.  It makes
 sense to use an |autoload| script, so the corresponding script is only loaded
 when actually needed and the script should be called format.vim.
 
-For example, the XML filetype plugin distributed with Vim in the $VIMRUNTIME
-directory, sets the 'formatexpr' option to: >
+For example, the XML filetype plugin distributed with Vim in the
+$VIMRUNTIME/ftplugin directory, sets the 'formatexpr' option to: >
 
    setlocal formatexpr=xmlformat#Format()
 
 That means, you will find the corresponding script, defining the
-xmlformat#Format() function, in the directory:
-`$VIMRUNTIME/autoload/xmlformat.vim`
+xmlformat#Format() function, in the file `$VIMRUNTIME/autoload/xmlformat.vim`
 
 Here is an example script that removes trailing whitespace from the selected
-text.  Put it in your autoload directory, e.g. ~/.vim/autoload/format.vim: >
-
+text.  Put it in your autoload directory, e.g. ~/.vim/autoload/format.vim:
+>vim
   func! format#Format()
     " only reformat on explicit gq command
     if mode() != 'n'
@@ -1487,7 +1486,7 @@ debugging it helps to set the 'debug' option.
 
 							*right-justify*
 There is no command in Vim to right justify text.  You can do it with
-an external command, like "par" (e.g.: "!}par" to format until the end of the
+an external command, like "par" (e.g.: `:.,}!par` to format until the end of the
 paragraph) or set 'formatprg' to "par".
 
 							*format-comments*
@@ -1553,7 +1552,7 @@ type of comment string.  A part consists of:
 	some indent for the start or end part that can be removed.
 
 When a string has none of the 'f', 's', 'm' or 'e' flags, Vim assumes the
-comment string repeats at the start of each line.  The flags field may be
+comment string repeats at the start of each line.  The {flags} field may be
 empty.
 
 Any blank space in the text before and after the {string} is part of the
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 6074931565..57d6e48468 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -323,6 +323,9 @@ gx			Opens the current filepath or URL (decided by
 			To avoid the hit-enter prompt use: >
 				:silent !{cmd}
 <
+							*:!-range*
+:{range}!{cmd}		Like |:!| but execute {cmd} for each line in the
+			{range}.
 							*:!!*
 :!!			Repeat last ":!{cmd}".
 
-- 
cgit 


From 59f38ef3c446afb4f6fcf113874b5c6e1af46d8e Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 17 Dec 2024 08:18:06 +0800
Subject: vim-patch:0a4e57f: runtime(doc): fix a few minor errors from the last
 doc updates

1) move the section at :h inclusive-motion-selection-exclusive a few
lines below, so that it doesn't live in between the 2 exceptions.

2) remove the tag :h :!-range. It's not accurate (because it is actually
a filter) and this command is already described at :h :range!

https://github.com/vim/vim/commit/0a4e57f44abc05033f839b4538efee8120f7d967

Co-authored-by: Christian Brabandt 
---
 runtime/doc/motion.txt  | 13 ++++++-------
 runtime/doc/various.txt |  3 ---
 2 files changed, 6 insertions(+), 10 deletions(-)

diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index ec4732c05a..41fcdc3e1b 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -86,13 +86,6 @@ command.  There are however, two general exceptions:
    end of the motion is moved to the end of the previous line and the motion
    becomes inclusive.  Example: "}" moves to the first line after a paragraph,
    but "d}" will not include that line.
-
-					*inclusive-motion-selection-exclusive*
-When 'selection' is "exclusive", |Visual| mode is active and an inclusive
-motion has been used, the cursor position will be adjusted by another
-character to the right, so that visual selction includes the expected text and
-can be acted upon.
-
 						*exclusive-linewise*
 2. If the motion is exclusive, the end of the motion is in column 1 and the
    start of the motion was at or before the first non-blank in the line, the
@@ -122,6 +115,12 @@ This cannot be repeated: >
 	endif
 Note that when using ":" any motion becomes charwise exclusive.
 
+					*inclusive-motion-selection-exclusive*
+When 'selection' is "exclusive", |Visual| mode is active and an inclusive
+motion has been used, the cursor position will be adjusted by another
+character to the right, so that visual selction includes the expected text and
+can be acted upon.
+
 								*forced-motion*
 FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE
 
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 57d6e48468..6074931565 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -323,9 +323,6 @@ gx			Opens the current filepath or URL (decided by
 			To avoid the hit-enter prompt use: >
 				:silent !{cmd}
 <
-							*:!-range*
-:{range}!{cmd}		Like |:!| but execute {cmd} for each line in the
-			{range}.
 							*:!!*
 :!!			Repeat last ":!{cmd}".
 
-- 
cgit 


From 7d082d4816f1dcc9c0b3a6ed4323b9d827153de1 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 17 Dec 2024 08:22:19 +0800
Subject: vim-patch:3920bb4: runtime(doc): document how to minimize fold
 computation costs

closes: vim/vim#16224

https://github.com/vim/vim/commit/3920bb4356aa7324a4be1071c87524a2f921d921

Co-authored-by: Konfekt 
---
 runtime/doc/fold.txt | 41 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt
index b844e0ed85..9798170dbc 100644
--- a/runtime/doc/fold.txt
+++ b/runtime/doc/fold.txt
@@ -82,9 +82,11 @@ The most efficient is to call a function without arguments: >
 The function must use v:lnum.  See |expr-option-function|.
 
 These are the conditions with which the expression is evaluated:
+
 - The current buffer and window are set for the line.
 - The variable "v:lnum" is set to the line number.
-- The result is used for the fold level in this way:
+
+The result of foldexpr then determines the fold level as follows:
   value			meaning ~
   0			the line is not in a fold
   1, 2, ..		the line is in a fold with this level
@@ -99,6 +101,8 @@ These are the conditions with which the expression is evaluated:
   "<1", "<2", ..	a fold with this level ends at this line
   ">1", ">2", ..	a fold with this level starts at this line
 
+The result values "=", "s" and "a" are more expensive, please see |fold-expr-slow|.
+
 It is not required to mark the start (end) of a fold with ">1" ("<1"), a fold
 will also start (end) when the fold level is higher (lower) than the fold
 level of the previous line.
@@ -112,12 +116,6 @@ recognized, there is no error message and the fold level will be zero.
 For debugging the 'debug' option can be set to "msg", the error messages will
 be visible then.
 
-Note: Since the expression has to be evaluated for every line, this fold
-method can be very slow!
-
-Try to avoid the "=", "a" and "s" return values, since Vim often has to search
-backwards for a line for which the fold level is defined.  This can be slow.
-
 If the 'foldexpr' expression starts with s: or ||, then it is replaced
 with the script ID (|local-function|). Examples: >
 		set foldexpr=s:MyFoldExpr()
@@ -143,6 +141,35 @@ end in that line.
 It may happen that folds are not updated properly.  You can use |zx| or |zX|
 to force updating folds.
 
+Minimizing Computational Cost			             *fold-expr-slow*
+
+Due to its computational cost, this fold method can make Vim unresponsive,
+especially when the fold level of all lines have to be initially computed.
+Afterwards, after each change, Vim restricts the computation of foldlevels
+to those lines whose fold level was affected by it (and reuses the known
+foldlevels of all the others).
+
+The fold expression should therefore strive to minimize the number of dependent
+lines needed for the computation of a given line: For example, try to avoid the
+"=", "a" and "s" return values, because these will require the evaluation of the
+fold levels on previous lines until an independent fold level is found.
+
+If this proves difficult, the next best thing could be to cache all fold levels
+in a buffer-local variable (b:foldlevels) that is only updated on |b:changedtick|:
+>vim
+  func MyFoldFunc()
+    if b:lasttick == b:changedtick
+      return b:foldlevels[v:lnum - 1]
+    endif
+    let b:lasttick = b:changedtick
+    let b:foldlevels = []
+    " compute foldlevels ...
+    return b:foldlevels[v:lnum - 1]
+  enddef
+  set foldexpr=s:MyFoldFunc()
+<
+In above example further speedup was gained by using a function without
+arguments (that must still use v:lnum). See |expr-option-function|.
 
 SYNTAX						*fold-syntax*
 
-- 
cgit 


From 15153c4cd5319652bcdcd608fe5d4f0fa1eb9419 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 17 Dec 2024 09:03:26 +0800
Subject: vim-patch:9.1.0938: exclusive selection not respected when
 re-selecting block mode (#31603)

Problem:  exclusive selection not respected when re-selecting block mode
          (Matt Ellis)
Solution: advance selection by another character when using
          selection=exclusive and visual block mode

fixes: vim/vim#16202
closes: vim/vim#16219

https://github.com/vim/vim/commit/bb955894734b287abfadd3a25786a42038d18d61

Co-authored-by: Christian Brabandt 
---
 src/nvim/normal.c                |  3 +++
 test/old/testdir/test_visual.vim | 46 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 4d2abf1c8c..a664535c0f 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -5038,6 +5038,9 @@ static void nv_visual(cmdarg_T *cap)
         assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX);
         curwin->w_curswant += resel_VIsual_vcol * cap->count0 - 1;
         curwin->w_cursor.lnum = lnum;
+        if (*p_sel == 'e') {
+          curwin->w_curswant++;
+        }
         coladvance(curwin, curwin->w_curswant);
       } else {
         curwin->w_set_curswant = true;
diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim
index e25327ddd4..39388cbc27 100644
--- a/test/old/testdir/test_visual.vim
+++ b/test/old/testdir/test_visual.vim
@@ -2718,4 +2718,50 @@ func Test_visual_block_cursor_insert_enter()
   bwipe!
 endfunc
 
+func Test_visual_block_exclusive_selection()
+  new
+  set selection=exclusive
+  call setline(1, ['asöd asdf', 'asdf asdf', 'as€d asdf', 'asdf asdf'])
+  call cursor(1, 1)
+  exe ":norm! \eh3j~"
+  call assert_equal(['ASÖd asdf', 'ASDf asdf', 'AS€d asdf', 'ASDf asdf'], getline(1, '$'))
+  exe ":norm! 1v~"
+  call assert_equal(['asöd asdf', 'asdf asdf', 'as€d asdf', 'asdf asdf'], getline(1, '$'))
+  bwipe!
+  set selection&vim
+endfunc
+
+func Test_visual_block_exclusive_selection_adjusted()
+  new
+  " Test that the end-position of the visual selection is adjusted for exclusive selection
+  set selection=exclusive
+  call setline(1, ['asöd asdf  ', 'asdf asdf  ', 'as€d asdf  ', 'asdf asdf  '])
+  call cursor(1, 1)
+  " inclusive motion
+  exe ":norm! \e3jy"
+  call assert_equal([0, 4, 5, 0], getpos("'>"))
+  " exclusive motion
+  exe ":norm! \ta3jy"
+  call assert_equal([0, 4, 6, 0], getpos("'>"))
+  " another inclusive motion
+  exe ":norm! \g_3jy"
+  call assert_equal([0, 4, 10, 0], getpos("'>"))
+
+  " Reset selection option to Vim default
+  set selection&vim
+  call cursor(1, 1)
+
+  " inclusive motion
+  exe ":norm! \e3jy"
+  call assert_equal([0, 4, 4, 0], getpos("'>"))
+  " exclusive motion
+  exe ":norm! \ta3jy"
+  call assert_equal([0, 4, 5, 0], getpos("'>"))
+  " another inclusive motion
+  exe ":norm! \g_3jy"
+  call assert_equal([0, 4, 9, 0], getpos("'>"))
+  bwipe!
+  set selection&vim
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From b03e790cddd19b57fa91f4fbfcc30c28f3c173bf Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 17 Dec 2024 11:34:30 +0800
Subject: vim-patch:9.1.0934: hard to view an existing buffer in the preview
 window (#31605)

Problem:  hard to view an existing buffer in the preview window
Solution: add the :pbuffer command (Yinzuo Jiang)

Similar as `:pedit` and `:buffer` command. `:pbuffer` edits buffer [N]
from the buffer list in the preview window.

`:pbuffer` can also open special buffer, for example terminal buffer.

closes: vim/vim#16222

https://github.com/vim/vim/commit/a2a2fe841ed2efdbb1f8055f752a3a4d0988ae9d

Cherry-pick Test_popup_and_previewwindow_dump() changes from patch
9.0.0625.
Cherry-pick Run_noroom_for_newwindow_test() changes from patches
8.2.0432 and 9.0.0363.

Co-authored-by: Yinzuo Jiang 
---
 runtime/doc/index.txt                 |  1 +
 runtime/doc/windows.txt               |  7 ++++
 src/nvim/ex_cmds.lua                  |  6 +++
 src/nvim/ex_docmd.c                   | 33 +++++++++++++--
 test/functional/ui/popupmenu_spec.lua | 75 +++++++++++++++++++++++++++++++++++
 test/old/testdir/test_popup.vim       | 33 ++++++++++-----
 test/old/testdir/test_preview.vim     | 47 ++++++++++++++++++----
 test/old/testdir/test_statusline.vim  |  4 ++
 test/old/testdir/test_window_cmd.vim  | 31 ++++++++-------
 test/old/testdir/test_winfixbuf.vim   | 12 ++++++
 10 files changed, 214 insertions(+), 35 deletions(-)

diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index d81d59c56f..0182123a12 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1472,6 +1472,7 @@ tag		command		action ~
 |:ownsyntax|	:ow[nsyntax]	set new local syntax highlight for this window
 |:packadd|	:pa[ckadd]	add a plugin from 'packpath'
 |:packloadall|	:packl[oadall]	load all packages under 'packpath'
+|:pbuffer|	:pb[uffer]	edit buffer in the preview window
 |:pclose|	:pc[lose]	close preview window
 |:pedit|	:ped[it]	edit file in the preview window
 |:perl|		:pe[rl]		execute perl command
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index a6e5984c82..d3c58a28a4 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -978,6 +978,13 @@ CTRL-W g }						*CTRL-W_g}*
 		it.  Make the new Preview window (if required) N high.  If N is
 		not given, 'previewheight' is used.
 
+							*:pb* *:pbuffer*
+:[N]pb[uffer][!] [+cmd] [N]
+		Edit buffer [N] from the buffer list in the preview window.
+		If [N] is not given, the current buffer remains being edited.
+		See |:buffer-!| for [!].  This will also edit a buffer that is
+		not in the buffer list, without setting the 'buflisted' flag.
+
 							*:ped* *:pedit*
 :ped[it][!] [++opt] [+cmd] {file}
 		Edit {file} in the preview window.  The preview window is
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index c8574c805c..0cdc397e9c 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -1946,6 +1946,12 @@ M.cmds = {
     addr_type = 'ADDR_NONE',
     func = 'ex_packloadall',
   },
+  {
+    command = 'pbuffer',
+    flags = bit.bor(BANG, RANGE, BUFNAME, BUFUNL, COUNT, EXTRA, CMDARG, TRLBAR),
+    addr_type = 'ADDR_BUFFERS',
+    func = 'ex_pbuffer',
+  },
   {
     command = 'pclose',
     flags = bit.bor(BANG, TRLBAR),
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index b32a8b867f..052bf3b9f7 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -4501,6 +4501,12 @@ static void ex_bunload(exarg_T *eap)
 /// :[N]buffer [N]       to buffer N
 /// :[N]sbuffer [N]      to buffer N
 static void ex_buffer(exarg_T *eap)
+{
+  do_exbuffer(eap);
+}
+
+/// ":buffer" command and alike.
+static void do_exbuffer(exarg_T *eap)
 {
   if (*eap->arg) {
     eap->errmsg = ex_errmsg(e_trailing_arg, eap->arg);
@@ -7002,14 +7008,35 @@ static void ex_ptag(exarg_T *eap)
 static void ex_pedit(exarg_T *eap)
 {
   win_T *curwin_save = curwin;
+  prepare_preview_window();
+
+  // Edit the file.
+  do_exedit(eap, NULL);
+
+  back_to_current_window(curwin_save);
+}
+
+/// ":pbuffer"
+static void ex_pbuffer(exarg_T *eap)
+{
+  win_T *curwin_save = curwin;
+  prepare_preview_window();
+
+  // Go to the buffer.
+  do_exbuffer(eap);
 
+  back_to_current_window(curwin_save);
+}
+
+static void prepare_preview_window(void)
+{
   // Open the preview window or popup and make it the current window.
   g_do_tagpreview = (int)p_pvh;
   prepare_tagpreview(true);
+}
 
-  // Edit the file.
-  do_exedit(eap, NULL);
-
+static void back_to_current_window(win_T *curwin_save)
+{
   if (curwin != curwin_save && win_valid(curwin_save)) {
     // Return cursor to where we were
     validate_cursor(curwin);
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index a4bf602e57..beb3ae385c 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -1542,6 +1542,81 @@ describe('builtin popupmenu', function()
     end)
 
     if not multigrid then
+      describe('popup and preview window do not overlap', function()
+        before_each(function()
+          screen:try_resize(53, 20)
+        end)
+
+        -- oldtest: Test_popup_and_previewwindow_dump_pedit()
+        it('with :pedit', function()
+          exec([[
+            set previewheight=9
+            silent! pedit
+            call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
+            exec "norm! G\\"
+          ]])
+          feed('o')
+          n.poke_eventloop()
+          feed('')
+          screen:expect([[
+            ab0                                                  |
+            ab1                                                  |
+            ab2                                                  |
+            ab3                                                  |
+            ab4                                                  |
+            ab5                                                  |
+            ab6                                                  |
+            ab7                                                  |
+            ab8                                                  |
+            {s:ab0            }{c: }{3:ew][+]                               }|
+            {n:ab1            }{c: }                                     |
+            {n:ab2            }{c: }                                     |
+            {n:ab3            }{c: }                                     |
+            {n:ab4            }{s: }                                     |
+            {n:ab5            }{s: }                                     |
+            {n:ab6            }{s: }                                     |
+            ab0^                                                  |
+            {1:~                                                    }|
+            {4:[No Name] [+]                                        }|
+            {2:-- Keyword Local completion (^N^P) }{5:match 1 of 10}     |
+          ]])
+        end)
+
+        -- oldtest: Test_popup_and_previewwindow_dump_pbuffer()
+        it('with :pbuffer', function()
+          exec([[
+            set previewheight=9
+            silent! pbuffer
+            call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
+            exec "norm! G\\\"
+          ]])
+          feed('o')
+          n.poke_eventloop()
+          feed('')
+          screen:expect([[
+            ab0                                                  |
+            ab1                                                  |
+            ab2                                                  |
+            ab3                                                  |
+            ab4                                                  |
+            ab5                                                  |
+            ab6                                                  |
+            ab7                                                  |
+            ab8                                                  |
+            {s:ab0            }{c: }{3:ew][+]                               }|
+            {n:ab1            }{c: }                                     |
+            {n:ab2            }{c: }                                     |
+            {n:ab3            }{s: }                                     |
+            {n:ab4            }{s: }                                     |
+            {n:ab5            }{s: }                                     |
+            ab0^                                                  |
+            {1:~                                                    }|*2
+            {4:[No Name] [+]                                        }|
+            {2:-- Keyword Local completion (^N^P) }{5:match 1 of 10}     |
+          ]])
+        end)
+      end)
+
       -- oldtest: Test_pum_with_preview_win()
       it('preview window opened during completion', function()
         exec([[
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index 33e86678c8..a24e133ce6 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -748,17 +748,11 @@ func Test_popup_and_preview_autocommand()
   bw!
 endfunc
 
-func Test_popup_and_previewwindow_dump()
+func s:run_popup_and_previewwindow_dump(lines, dumpfile)
   CheckScreendump
   CheckFeature quickfix
 
-  let lines =<< trim END
-    set previewheight=9
-    silent! pedit
-    call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
-    exec "norm! G\\"
-  END
-  call writefile(lines, 'Xscript')
+  call writefile(a:lines, 'Xscript', 'D')
   let buf = RunVimInTerminal('-S Xscript', {})
 
   " wait for the script to finish
@@ -768,11 +762,30 @@ func Test_popup_and_previewwindow_dump()
   call term_sendkeys(buf, "o")
   call TermWait(buf, 50)
   call term_sendkeys(buf, "\\")
-  call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {})
+  call VerifyScreenDump(buf, a:dumpfile, {})
 
   call term_sendkeys(buf, "\u")
   call StopVimInTerminal(buf)
-  call delete('Xscript')
+endfunc
+
+func Test_popup_and_previewwindow_dump_pedit()
+  let lines =<< trim END
+    set previewheight=9
+    silent! pedit
+    call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
+    exec "norm! G\\"
+  END
+  call s:run_popup_and_previewwindow_dump(lines, 'Test_popup_and_previewwindow_pedit')
+endfunc
+
+func Test_popup_and_previewwindow_dump_pbuffer()
+  let lines =<< trim END
+    set previewheight=9
+    silent! pbuffer
+    call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
+    exec "norm! G\\\"
+  END
+  call s:run_popup_and_previewwindow_dump(lines, 'Test_popup_and_previewwindow_pbuffer')
 endfunc
 
 func Test_balloon_split()
diff --git a/test/old/testdir/test_preview.vim b/test/old/testdir/test_preview.vim
index b7b908e761..d24b09b7d2 100644
--- a/test/old/testdir/test_preview.vim
+++ b/test/old/testdir/test_preview.vim
@@ -15,6 +15,20 @@ func Test_Psearch()
   bwipe
 endfunc
 
+func s:goto_preview_and_close()
+  " Go to the preview window
+  wincmd P
+  call assert_equal(1, &previewwindow)
+  call assert_equal('preview', win_gettype())
+
+  " Close preview window
+  wincmd z
+  call assert_equal(1, winnr('$'))
+  call assert_equal(0, &previewwindow)
+
+  call assert_fails('wincmd P', 'E441:')
+endfunc
+
 func Test_window_preview()
   CheckFeature quickfix
 
@@ -23,17 +37,36 @@ func Test_window_preview()
   call assert_equal(2, winnr('$'))
   call assert_equal(0, &previewwindow)
 
-  " Go to the preview window
-  wincmd P
-  call assert_equal(1, &previewwindow)
-  call assert_equal('preview', win_gettype())
+  call s:goto_preview_and_close()
+endfunc
 
-  " Close preview window
-  wincmd z
+func Test_window_preview_from_pbuffer()
+  CheckFeature quickfix
+
+  call writefile(['/* some C code */'], 'Xpreview.c', 'D')
+  edit Xpreview.c
+  const buf_num = bufnr('%')
+  enew
   call assert_equal(1, winnr('$'))
+  exe 'pbuffer ' .  buf_num
+  call assert_equal(2, winnr('$'))
   call assert_equal(0, &previewwindow)
 
-  call assert_fails('wincmd P', 'E441:')
+  call s:goto_preview_and_close()
+endfunc
+
+func Test_window_preview_terminal()
+  CheckFeature quickfix
+  " CheckFeature terminal
+
+  term " ++curwin
+  const buf_num = bufnr('$')
+  call assert_equal(1, winnr('$'))
+  exe 'pbuffer' . buf_num
+  call assert_equal(2, winnr('$'))
+  call assert_equal(0, &previewwindow)
+
+  call s:goto_preview_and_close()
 endfunc
 
 func Test_window_preview_from_help()
diff --git a/test/old/testdir/test_statusline.vim b/test/old/testdir/test_statusline.vim
index c8162ced07..c9f79dfef7 100644
--- a/test/old/testdir/test_statusline.vim
+++ b/test/old/testdir/test_statusline.vim
@@ -220,6 +220,10 @@ func Test_statusline()
   wincmd j
   call assert_match('^\[Preview\],PRV\s*$', s:get_statusline())
   pclose
+  pbuffer
+  wincmd j
+  call assert_match('^\[Preview\],PRV\s*$', s:get_statusline())
+  pclose
 
   " %y: Type of file in the buffer, e.g., "[vim]". See 'filetype'.
   " %Y: Type of file in the buffer, e.g., ",VIM". See 'filetype'.
diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim
index e173aa1e73..343bc9fd83 100644
--- a/test/old/testdir/test_window_cmd.vim
+++ b/test/old/testdir/test_window_cmd.vim
@@ -1185,20 +1185,20 @@ func Run_noroom_for_newwindow_test(dir_arg)
   let dir = (a:dir_arg == 'v') ? 'vert ' : ''
 
   " Open as many windows as possible
-  for i in range(500)
+  while v:true
     try
       exe dir . 'new'
     catch /E36:/
       break
     endtry
-  endfor
+  endwhile
 
-  call writefile(['first', 'second', 'third'], 'Xfile1')
-  call writefile([], 'Xfile2')
-  call writefile([], 'Xfile3')
+  call writefile(['first', 'second', 'third'], 'Xnorfile1')
+  call writefile([], 'Xnorfile2')
+  call writefile([], 'Xnorfile3')
 
   " Argument list related commands
-  args Xfile1 Xfile2 Xfile3
+  args Xnorfile1 Xnorfile2 Xnorfile3
   next
   for cmd in ['sargument 2', 'snext', 'sprevious', 'sNext', 'srewind',
 			\ 'sfirst', 'slast']
@@ -1209,13 +1209,13 @@ func Run_noroom_for_newwindow_test(dir_arg)
   " Buffer related commands
   set modified
   hide enew
-  for cmd in ['sbuffer Xfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind',
+  for cmd in ['sbuffer Xnorfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind',
 		\ 'sbfirst', 'sblast', 'sball', 'sbmodified', 'sunhide']
     call assert_fails(dir .. cmd, 'E36:')
   endfor
 
   " Window related commands
-  for cmd in ['split', 'split Xfile2', 'new', 'new Xfile3', 'sview Xfile1',
+  for cmd in ['split', 'split Xnorfile2', 'new', 'new Xnorfile3', 'sview Xnorfile1',
 		\ 'sfind runtest.vim']
     call assert_fails(dir .. cmd, 'E36:')
   endfor
@@ -1238,7 +1238,8 @@ func Run_noroom_for_newwindow_test(dir_arg)
     call assert_fails(dir .. 'lopen', 'E36:')
 
     " Preview window
-    call assert_fails(dir .. 'pedit Xfile2', 'E36:')
+    call assert_fails(dir .. 'pedit Xnorfile2', 'E36:')
+    call assert_fails(dir .. 'pbuffer', 'E36:')
     call setline(1, 'abc')
     call assert_fails(dir .. 'psearch abc', 'E36:')
   endif
@@ -1246,15 +1247,15 @@ func Run_noroom_for_newwindow_test(dir_arg)
   " Window commands (CTRL-W ^ and CTRL-W f)
   if a:dir_arg == 'h'
     call assert_fails('call feedkeys("\^", "xt")', 'E36:')
-    call setline(1, 'Xfile1')
+    call setline(1, 'Xnorfile1')
     call assert_fails('call feedkeys("gg\f", "xt")', 'E36:')
   endif
   enew!
 
   " Tag commands (:stag, :stselect and :stjump)
   call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
-        \ "second\tXfile1\t2",
-        \ "third\tXfile1\t3",],
+        \ "second\tXnorfile1\t2",
+        \ "third\tXnorfile1\t3",],
         \ 'Xtags')
   set tags=Xtags
   call assert_fails(dir .. 'stag second', 'E36:')
@@ -1276,9 +1277,9 @@ func Run_noroom_for_newwindow_test(dir_arg)
   endif
 
   %bwipe!
-  call delete('Xfile1')
-  call delete('Xfile2')
-  call delete('Xfile3')
+  call delete('Xnorfile1')
+  call delete('Xnorfile2')
+  call delete('Xnorfile3')
   only
 endfunc
 
diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim
index cc8ff86fa1..f7986fdda3 100644
--- a/test/old/testdir/test_winfixbuf.vim
+++ b/test/old/testdir/test_winfixbuf.vim
@@ -2545,6 +2545,18 @@ func Test_pedit()
   call assert_equal(l:other, bufnr())
 endfunc
 
+" Allow :pbuffer because, unlike :buffer, it uses a separate window
+func Test_pbuffer()
+  call s:reset_all_buffers()
+
+  let l:other = s:make_buffer_pairs()
+
+  exe 'pbuffer ' . l:other
+
+  execute "normal \w"
+  call assert_equal(l:other, bufnr())
+endfunc
+
 " Fail :pop but :pop! is allowed
 func Test_pop()
   call s:reset_all_buffers()
-- 
cgit 


From 6bf2a6fc5bb395b67c88cb26d332f882a106c7ab Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Tue, 17 Dec 2024 13:12:22 +0100
Subject: refactor(api): always use TRY_WRAP #31600

Problem:  Two separate try/end wrappers, that only marginally differ by
          restoring a few variables. Wrappers that don't restore
          previous state are dangerous to use in "api-fast" functions.
Solution: Remove wrappers that don't restore the previous state.
          Always use TRY_WRAP.
---
 src/nvim/api/buffer.c          | 346 ++++++++++++++++++++---------------------
 src/nvim/api/private/helpers.c |  79 ++++------
 src/nvim/api/private/helpers.h |  14 +-
 src/nvim/api/tabpage.c         |   8 +-
 src/nvim/api/vim.c             | 116 +++++++-------
 src/nvim/api/vimscript.c       |  30 ++--
 src/nvim/api/window.c          |  30 ++--
 src/nvim/ex_getln.c            |  95 ++++++-----
 src/nvim/lua/stdlib.c          |  48 +++---
 src/nvim/option.c              |   2 +-
 10 files changed, 359 insertions(+), 409 deletions(-)

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 9480292d9a..2b6aa8b371 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -360,93 +360,91 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
     memchrsub(lines[i], NUL, NL, l.size);
   }
 
-  try_start();
-
-  if (!MODIFIABLE(buf)) {
-    api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
-    goto end;
-  }
-
-  if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) {
-    api_set_error(err, kErrorTypeException, "Failed to save undo information");
-    goto end;
-  }
-
-  bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0);
-
-  // If the size of the range is reducing (ie, new_len < old_len) we
-  // need to delete some old_len. We do this at the start, by
-  // repeatedly deleting line "start".
-  size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
-  for (size_t i = 0; i < to_delete; i++) {
-    if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) {
-      api_set_error(err, kErrorTypeException, "Failed to delete line");
+  TRY_WRAP(err, {
+    if (!MODIFIABLE(buf)) {
+      api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
       goto end;
     }
-  }
 
-  if (to_delete > 0) {
-    extra -= (ptrdiff_t)to_delete;
-  }
+    if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) {
+      api_set_error(err, kErrorTypeException, "Failed to save undo information");
+      goto end;
+    }
 
-  // For as long as possible, replace the existing old_len with the
-  // new old_len. This is a more efficient operation, as it requires
-  // less memory allocation and freeing.
-  size_t to_replace = old_len < new_len ? old_len : new_len;
-  bcount_t inserted_bytes = 0;
-  for (size_t i = 0; i < to_replace; i++) {
-    int64_t lnum = start + (int64_t)i;
+    bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0);
 
-    VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
-      goto end;
-    });
+    // If the size of the range is reducing (ie, new_len < old_len) we
+    // need to delete some old_len. We do this at the start, by
+    // repeatedly deleting line "start".
+    size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
+    for (size_t i = 0; i < to_delete; i++) {
+      if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) {
+        api_set_error(err, kErrorTypeException, "Failed to delete line");
+        goto end;
+      }
+    }
 
-    if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) {
-      api_set_error(err, kErrorTypeException, "Failed to replace line");
-      goto end;
+    if (to_delete > 0) {
+      extra -= (ptrdiff_t)to_delete;
     }
 
-    inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
-  }
+    // For as long as possible, replace the existing old_len with the
+    // new old_len. This is a more efficient operation, as it requires
+    // less memory allocation and freeing.
+    size_t to_replace = old_len < new_len ? old_len : new_len;
+    bcount_t inserted_bytes = 0;
+    for (size_t i = 0; i < to_replace; i++) {
+      int64_t lnum = start + (int64_t)i;
+
+      VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
+        goto end;
+      });
+
+      if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) {
+        api_set_error(err, kErrorTypeException, "Failed to replace line");
+        goto end;
+      }
 
-  // Now we may need to insert the remaining new old_len
-  for (size_t i = to_replace; i < new_len; i++) {
-    int64_t lnum = start + (int64_t)i - 1;
+      inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
+    }
 
-    VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
-      goto end;
-    });
+    // Now we may need to insert the remaining new old_len
+    for (size_t i = to_replace; i < new_len; i++) {
+      int64_t lnum = start + (int64_t)i - 1;
 
-    if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
-      api_set_error(err, kErrorTypeException, "Failed to insert line");
-      goto end;
-    }
+      VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
+        goto end;
+      });
 
-    inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
+      if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
+        api_set_error(err, kErrorTypeException, "Failed to insert line");
+        goto end;
+      }
 
-    extra++;
-  }
+      inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
 
-  // Adjust marks. Invalidate any which lie in the
-  // changed range, and move any in the remainder of the buffer.
-  linenr_T adjust = end > start ? MAXLNUM : 0;
-  mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra,
-                  true, true, kExtmarkNOOP);
+      extra++;
+    }
 
-  extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0,
-                 deleted_bytes, (int)new_len, 0, inserted_bytes,
-                 kExtmarkUndo);
+    // Adjust marks. Invalidate any which lie in the
+    // changed range, and move any in the remainder of the buffer.
+    linenr_T adjust = end > start ? MAXLNUM : 0;
+    mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra,
+                    true, true, kExtmarkNOOP);
 
-  changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
+    extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0,
+                   deleted_bytes, (int)new_len, 0, inserted_bytes,
+                   kExtmarkUndo);
 
-  FOR_ALL_TAB_WINDOWS(tp, win) {
-    if (win->w_buffer == buf) {
-      fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra);
-    }
-  }
+    changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
 
-end:
-  try_end(err);
+    FOR_ALL_TAB_WINDOWS(tp, win) {
+      if (win->w_buffer == buf) {
+        fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra);
+      }
+    }
+    end:;
+  });
 }
 
 /// Sets (replaces) a range in the buffer
@@ -593,101 +591,99 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
     new_byte += (bcount_t)(last_item.size) + 1;
   }
 
-  try_start();
-
-  if (!MODIFIABLE(buf)) {
-    api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
-    goto end;
-  }
-
-  // Small note about undo states: unlike set_lines, we want to save the
-  // undo state of one past the end_row, since end_row is inclusive.
-  if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
-    api_set_error(err, kErrorTypeException, "Failed to save undo information");
-    goto end;
-  }
-
-  ptrdiff_t extra = 0;  // lines added to text, can be negative
-  size_t old_len = (size_t)(end_row - start_row + 1);
-
-  // If the size of the range is reducing (ie, new_len < old_len) we
-  // need to delete some old_len. We do this at the start, by
-  // repeatedly deleting line "start".
-  size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
-  for (size_t i = 0; i < to_delete; i++) {
-    if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) {
-      api_set_error(err, kErrorTypeException, "Failed to delete line");
+  TRY_WRAP(err, {
+    if (!MODIFIABLE(buf)) {
+      api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
       goto end;
     }
-  }
 
-  if (to_delete > 0) {
-    extra -= (ptrdiff_t)to_delete;
-  }
-
-  // For as long as possible, replace the existing old_len with the
-  // new old_len. This is a more efficient operation, as it requires
-  // less memory allocation and freeing.
-  size_t to_replace = old_len < new_len ? old_len : new_len;
-  for (size_t i = 0; i < to_replace; i++) {
-    int64_t lnum = start_row + (int64_t)i;
-
-    VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
+    // Small note about undo states: unlike set_lines, we want to save the
+    // undo state of one past the end_row, since end_row is inclusive.
+    if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
+      api_set_error(err, kErrorTypeException, "Failed to save undo information");
       goto end;
-    });
+    }
 
-    if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) {
-      api_set_error(err, kErrorTypeException, "Failed to replace line");
-      goto end;
+    ptrdiff_t extra = 0;  // lines added to text, can be negative
+    size_t old_len = (size_t)(end_row - start_row + 1);
+
+    // If the size of the range is reducing (ie, new_len < old_len) we
+    // need to delete some old_len. We do this at the start, by
+    // repeatedly deleting line "start".
+    size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
+    for (size_t i = 0; i < to_delete; i++) {
+      if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) {
+        api_set_error(err, kErrorTypeException, "Failed to delete line");
+        goto end;
+      }
     }
-  }
 
-  // Now we may need to insert the remaining new old_len
-  for (size_t i = to_replace; i < new_len; i++) {
-    int64_t lnum = start_row + (int64_t)i - 1;
+    if (to_delete > 0) {
+      extra -= (ptrdiff_t)to_delete;
+    }
 
-    VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
-      goto end;
-    });
+    // For as long as possible, replace the existing old_len with the
+    // new old_len. This is a more efficient operation, as it requires
+    // less memory allocation and freeing.
+    size_t to_replace = old_len < new_len ? old_len : new_len;
+    for (size_t i = 0; i < to_replace; i++) {
+      int64_t lnum = start_row + (int64_t)i;
 
-    if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
-      api_set_error(err, kErrorTypeException, "Failed to insert line");
-      goto end;
+      VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
+        goto end;
+      });
+
+      if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) {
+        api_set_error(err, kErrorTypeException, "Failed to replace line");
+        goto end;
+      }
     }
 
-    extra++;
-  }
+    // Now we may need to insert the remaining new old_len
+    for (size_t i = to_replace; i < new_len; i++) {
+      int64_t lnum = start_row + (int64_t)i - 1;
 
-  colnr_T col_extent = (colnr_T)(end_col
-                                 - ((end_row == start_row) ? start_col : 0));
-
-  // Adjust marks. Invalidate any which lie in the
-  // changed range, and move any in the remainder of the buffer.
-  // Do not adjust any cursors. need to use column-aware logic (below)
-  linenr_T adjust = end_row >= start_row ? MAXLNUM : 0;
-  mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra,
-                  true, true, kExtmarkNOOP);
-
-  extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
-                 (int)(end_row - start_row), col_extent, old_byte,
-                 (int)new_len - 1, (colnr_T)last_item.size, new_byte,
-                 kExtmarkUndo);
-
-  changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
-
-  FOR_ALL_TAB_WINDOWS(tp, win) {
-    if (win->w_buffer == buf) {
-      if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) {
-        fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row,
-                        (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size);
-      } else {
-        fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
+      VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
+        goto end;
+      });
+
+      if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
+        api_set_error(err, kErrorTypeException, "Failed to insert line");
+        goto end;
       }
+
+      extra++;
     }
-  }
 
-end:
-  try_end(err);
+    colnr_T col_extent = (colnr_T)(end_col
+                                   - ((end_row == start_row) ? start_col : 0));
+
+    // Adjust marks. Invalidate any which lie in the
+    // changed range, and move any in the remainder of the buffer.
+    // Do not adjust any cursors. need to use column-aware logic (below)
+    linenr_T adjust = end_row >= start_row ? MAXLNUM : 0;
+    mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra,
+                    true, true, kExtmarkNOOP);
+
+    extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
+                   (int)(end_row - start_row), col_extent, old_byte,
+                   (int)new_len - 1, (colnr_T)last_item.size, new_byte,
+                   kExtmarkUndo);
+
+    changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
+
+    FOR_ALL_TAB_WINDOWS(tp, win) {
+      if (win->w_buffer == buf) {
+        if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) {
+          fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row,
+                          (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size);
+        } else {
+          fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
+        }
+      }
+    }
+    end:;
+  });
 }
 
 /// Gets a range from the buffer.
@@ -965,26 +961,27 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err)
     return;
   }
 
-  try_start();
-
-  const bool is_curbuf = buf == curbuf;
-  const int save_acd = p_acd;
-  if (!is_curbuf) {
-    // Temporarily disable 'autochdir' when setting file name for another buffer.
-    p_acd = false;
-  }
+  int ren_ret = OK;
+  TRY_WRAP(err, {
+    const bool is_curbuf = buf == curbuf;
+    const int save_acd = p_acd;
+    if (!is_curbuf) {
+      // Temporarily disable 'autochdir' when setting file name for another buffer.
+      p_acd = false;
+    }
 
-  // Using aucmd_*: autocommands will be executed by rename_buffer
-  aco_save_T aco;
-  aucmd_prepbuf(&aco, buf);
-  int ren_ret = rename_buffer(name.data);
-  aucmd_restbuf(&aco);
+    // Using aucmd_*: autocommands will be executed by rename_buffer
+    aco_save_T aco;
+    aucmd_prepbuf(&aco, buf);
+    ren_ret = rename_buffer(name.data);
+    aucmd_restbuf(&aco);
 
-  if (!is_curbuf) {
-    p_acd = save_acd;
-  }
+    if (!is_curbuf) {
+      p_acd = save_acd;
+    }
+  });
 
-  if (try_end(err)) {
+  if (ERROR_SET(err)) {
     return;
   }
 
@@ -1204,15 +1201,18 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
   if (!buf) {
     return NIL;
   }
-  try_start();
-  aco_save_T aco;
-  aucmd_prepbuf(&aco, buf);
 
-  Array args = ARRAY_DICT_INIT;
-  Object res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err);
+  Object res = OBJECT_INIT;
+  TRY_WRAP(err, {
+    aco_save_T aco;
+    aucmd_prepbuf(&aco, buf);
+
+    Array args = ARRAY_DICT_INIT;
+    res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err);
+
+    aucmd_restbuf(&aco);
+  });
 
-  aucmd_restbuf(&aco);
-  try_end(err);
   return res;
 }
 
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 5bf66a092f..4389ae3b35 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -42,8 +42,10 @@
 
 /// Start block that may cause Vimscript exceptions while evaluating another code
 ///
-/// Used when caller is supposed to be operating when other Vimscript code is being
-/// processed and that “other Vimscript code” must not be affected.
+/// Used just in case caller is supposed to be operating when other Vimscript code
+/// is being processed and that “other Vimscript code” must not be affected.
+///
+/// @warning Avoid calling directly; use TRY_WRAP instead.
 ///
 /// @param[out]  tstate  Location where try state should be saved.
 void try_enter(TryState *const tstate)
@@ -55,75 +57,33 @@ void try_enter(TryState *const tstate)
     .current_exception = current_exception,
     .msg_list = (const msglist_T *const *)msg_list,
     .private_msg_list = NULL,
-    .trylevel = trylevel,
     .got_int = got_int,
     .did_throw = did_throw,
     .need_rethrow = need_rethrow,
     .did_emsg = did_emsg,
   };
+  // `msg_list` controls the collection of abort-causing non-exception errors,
+  // which would otherwise be ignored.  This pattern is from do_cmdline().
   msg_list = &tstate->private_msg_list;
   current_exception = NULL;
-  trylevel = 1;
   got_int = false;
   did_throw = false;
   need_rethrow = false;
   did_emsg = false;
-}
-
-/// End try block, set the error message if any and restore previous state
-///
-/// @warning Return is consistent with most functions (false on error), not with
-///          try_end (true on error).
-///
-/// @param[in]  tstate  Previous state to restore.
-/// @param[out]  err  Location where error should be saved.
-///
-/// @return false if error occurred, true otherwise.
-bool try_leave(const TryState *const tstate, Error *const err)
-  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
-  const bool ret = !try_end(err);
-  assert(trylevel == 0);
-  assert(!need_rethrow);
-  assert(!got_int);
-  assert(!did_throw);
-  assert(!did_emsg);
-  assert(msg_list == &tstate->private_msg_list);
-  assert(*msg_list == NULL);
-  assert(current_exception == NULL);
-  msg_list = (msglist_T **)tstate->msg_list;
-  current_exception = tstate->current_exception;
-  trylevel = tstate->trylevel;
-  got_int = tstate->got_int;
-  did_throw = tstate->did_throw;
-  need_rethrow = tstate->need_rethrow;
-  did_emsg = tstate->did_emsg;
-  return ret;
-}
-
-/// Starts a block that may cause Vimscript exceptions; must be mirrored by `try_end()` call.
-///
-/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file).
-///
-/// To be used as a replacement of `:try … catch … endtry` in C code, in cases
-/// when error flag could not already be set. If there may be pending error
-/// state at the time try_start() is executed which needs to be preserved,
-/// try_enter()/try_leave() pair should be used instead.
-void try_start(void)
-{
   trylevel++;
 }
 
-/// Ends a `try_start` block; sets error message if any and returns true if an error occurred.
+/// Ends a `try_enter` block; sets error message if any.
 ///
-/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file).
+/// @warning Avoid calling directly; use TRY_WRAP instead.
 ///
-/// @param err Pointer to the stack-allocated error object
-/// @return true if an error occurred
-bool try_end(Error *err)
+/// @param[out] err Pointer to the stack-allocated error object
+void try_leave(const TryState *const tstate, Error *const err)
+  FUNC_ATTR_NONNULL_ALL
 {
   // Note: all globals manipulated here should be saved/restored in
   // try_enter/try_leave.
+  assert(trylevel > 0);
   trylevel--;
 
   // Set by emsg(), affects aborting().  See also enter_cleanup().
@@ -166,7 +126,20 @@ bool try_end(Error *err)
     discard_current_exception();
   }
 
-  return ERROR_SET(err);
+  assert(msg_list == &tstate->private_msg_list);
+  assert(*msg_list == NULL);
+  assert(current_exception == NULL);
+  assert(!got_int);
+  assert(!did_throw);
+  assert(!need_rethrow);
+  assert(!did_emsg);
+  // Restore the exception context.
+  msg_list = (msglist_T **)tstate->msg_list;
+  current_exception = tstate->current_exception;
+  got_int = tstate->got_int;
+  did_throw = tstate->did_throw;
+  need_rethrow = tstate->need_rethrow;
+  did_emsg = tstate->did_emsg;
 }
 
 /// Recursively expands a vimscript value in a dict
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index d06f5c9c65..03ff811449 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -147,27 +147,19 @@ typedef struct {
   except_T *current_exception;
   msglist_T *private_msg_list;
   const msglist_T *const *msg_list;
-  int trylevel;
   int got_int;
   bool did_throw;
   int need_rethrow;
   int did_emsg;
 } TryState;
 
-// `msg_list` controls the collection of abort-causing non-exception errors,
-// which would otherwise be ignored.  This pattern is from do_cmdline().
-//
 // TODO(bfredl): prepare error-handling at "top level" (nv_event).
 #define TRY_WRAP(err, code) \
   do { \
-    msglist_T **saved_msg_list = msg_list; \
-    msglist_T *private_msg_list; \
-    msg_list = &private_msg_list; \
-    private_msg_list = NULL; \
-    try_start(); \
+    TryState tstate; \
+    try_enter(&tstate); \
     code; \
-    try_end(err); \
-    msg_list = saved_msg_list;  /* Restore the exception context. */ \
+    try_leave(&tstate, err); \
   } while (0)
 
 // Execute code with cursor position saved and restored and textlock active.
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 56a3f1cf23..b4d519dc98 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -146,11 +146,9 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err)
   }
 
   if (tp == curtab) {
-    try_start();
-    win_goto(wp);
-    if (!try_end(err) && curwin != wp) {
-      api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win);
-    }
+    TRY_WRAP(err, {
+      win_goto(wp);
+    });
   } else if (tp->tp_curwin != wp) {
     tp->tp_prevwin = tp->tp_curwin;
     tp->tp_curwin = wp;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index d82f90d1dd..fce7a86245 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -594,12 +594,10 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er
   kvi_init(cookie.rv);
 
   int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
-  TryState tstate;
 
-  // XXX: intentionally not using `TRY_WRAP`, to avoid `did_emsg=false` in `try_end`.
-  try_enter(&tstate);
-  do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie);
-  vim_ignored = try_leave(&tstate, err);
+  TRY_WRAP(err, {
+    do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie);
+  });
 
   return arena_take_arraybuilder(arena, &cookie.rv);
 }
@@ -952,68 +950,70 @@ void nvim_set_current_win(Window window, Error *err)
 Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
   FUNC_API_SINCE(6)
 {
-  try_start();
-  // Block autocommands for now so they don't mess with the buffer before we
-  // finish configuring it.
-  block_autocmds();
-
-  buf_T *buf = buflist_new(NULL, NULL, 0,
-                           BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
-  if (buf == NULL) {
-    unblock_autocmds();
-    goto fail;
-  }
+  Buffer ret = 0;
 
-  // Open the memline for the buffer. This will avoid spurious autocmds when
-  // a later nvim_buf_set_lines call would have needed to "open" the buffer.
-  if (ml_open(buf) == FAIL) {
-    unblock_autocmds();
-    goto fail;
-  }
-
-  // Set last_changedtick to avoid triggering a TextChanged autocommand right
-  // after it was added.
-  buf->b_last_changedtick = buf_get_changedtick(buf);
-  buf->b_last_changedtick_i = buf_get_changedtick(buf);
-  buf->b_last_changedtick_pum = buf_get_changedtick(buf);
+  TRY_WRAP(err, {
+    // Block autocommands for now so they don't mess with the buffer before we
+    // finish configuring it.
+    block_autocmds();
+
+    buf_T *buf = buflist_new(NULL, NULL, 0,
+                             BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
+    if (buf == NULL) {
+      unblock_autocmds();
+      goto fail;
+    }
 
-  // Only strictly needed for scratch, but could just as well be consistent
-  // and do this now. Buffer is created NOW, not when it later first happens
-  // to reach a window or aucmd_prepbuf() ..
-  buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
+    // Open the memline for the buffer. This will avoid spurious autocmds when
+    // a later nvim_buf_set_lines call would have needed to "open" the buffer.
+    if (ml_open(buf) == FAIL) {
+      unblock_autocmds();
+      goto fail;
+    }
 
-  if (scratch) {
-    set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0,
-                          kOptScopeBuf, buf);
-    set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0,
-                          kOptScopeBuf, buf);
-    assert(buf->b_ml.ml_mfp->mf_fd < 0);  // ml_open() should not have opened swapfile already
-    buf->b_p_swf = false;
-    buf->b_p_ml = false;
-  }
+    // Set last_changedtick to avoid triggering a TextChanged autocommand right
+    // after it was added.
+    buf->b_last_changedtick = buf_get_changedtick(buf);
+    buf->b_last_changedtick_i = buf_get_changedtick(buf);
+    buf->b_last_changedtick_pum = buf_get_changedtick(buf);
+
+    // Only strictly needed for scratch, but could just as well be consistent
+    // and do this now. Buffer is created NOW, not when it later first happens
+    // to reach a window or aucmd_prepbuf() ..
+    buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
+
+    if (scratch) {
+      set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0,
+                            kOptScopeBuf, buf);
+      set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0,
+                            kOptScopeBuf, buf);
+      assert(buf->b_ml.ml_mfp->mf_fd < 0);  // ml_open() should not have opened swapfile already
+      buf->b_p_swf = false;
+      buf->b_p_ml = false;
+    }
 
-  unblock_autocmds();
+    unblock_autocmds();
 
-  bufref_T bufref;
-  set_bufref(&bufref, buf);
-  if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, false, buf)
-      && !bufref_valid(&bufref)) {
-    goto fail;
-  }
-  if (listed
-      && apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf)
-      && !bufref_valid(&bufref)) {
-    goto fail;
-  }
+    bufref_T bufref;
+    set_bufref(&bufref, buf);
+    if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, false, buf)
+        && !bufref_valid(&bufref)) {
+      goto fail;
+    }
+    if (listed
+        && apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf)
+        && !bufref_valid(&bufref)) {
+      goto fail;
+    }
 
-  try_end(err);
-  return buf->b_fnum;
+    ret = buf->b_fnum;
+    fail:;
+  });
 
-fail:
-  if (!try_end(err)) {
+  if (ret == 0 && !ERROR_SET(err)) {
     api_set_error(err, kErrorTypeException, "Failed to create buffer");
   }
-  return 0;
+  return ret;
 }
 
 /// Open a terminal instance in a buffer
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 0ff2b037ce..67db615b20 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -78,24 +78,24 @@ String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *
     capture_ga = &capture_local;
   }
 
-  try_start();
-  if (opts->output) {
-    msg_silent++;
-    msg_col = 0;  // prevent leading spaces
-  }
+  TRY_WRAP(err, {
+    if (opts->output) {
+      msg_silent++;
+      msg_col = 0;  // prevent leading spaces
+    }
 
-  const sctx_T save_current_sctx = api_set_sctx(channel_id);
+    const sctx_T save_current_sctx = api_set_sctx(channel_id);
 
-  do_source_str(src.data, "nvim_exec2()");
-  if (opts->output) {
-    capture_ga = save_capture_ga;
-    msg_silent = save_msg_silent;
-    // Put msg_col back where it was, since nothing should have been written.
-    msg_col = save_msg_col;
-  }
+    do_source_str(src.data, "nvim_exec2()");
+    if (opts->output) {
+      capture_ga = save_capture_ga;
+      msg_silent = save_msg_silent;
+      // Put msg_col back where it was, since nothing should have been written.
+      msg_col = save_msg_col;
+    }
 
-  current_sctx = save_current_sctx;
-  try_end(err);
+    current_sctx = save_current_sctx;
+  });
 
   if (ERROR_SET(err)) {
     goto theend;
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index f5e8d8f086..387dad899e 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -368,19 +368,16 @@ void nvim_win_hide(Window window, Error *err)
   }
 
   tabpage_T *tabpage = win_find_tabpage(win);
-  TryState tstate;
-  try_enter(&tstate);
-
-  // Never close the autocommand window.
-  if (is_aucmd_win(win)) {
-    emsg(_(e_autocmd_close));
-  } else if (tabpage == curtab) {
-    win_close(win, false, false);
-  } else {
-    win_close_othertab(win, false, tabpage);
-  }
-
-  vim_ignored = try_leave(&tstate, err);
+  TRY_WRAP(err, {
+    // Never close the autocommand window.
+    if (is_aucmd_win(win)) {
+      emsg(_(e_autocmd_close));
+    } else if (tabpage == curtab) {
+      win_close(win, false, false);
+    } else {
+      win_close_othertab(win, false, tabpage);
+    }
+  });
 }
 
 /// Closes the window (like |:close| with a |window-ID|).
@@ -400,10 +397,9 @@ void nvim_win_close(Window window, Boolean force, Error *err)
   }
 
   tabpage_T *tabpage = win_find_tabpage(win);
-  TryState tstate;
-  try_enter(&tstate);
-  ex_win_close(force, win, tabpage == curtab ? NULL : tabpage);
-  vim_ignored = try_leave(&tstate, err);
+  TRY_WRAP(err, {
+    ex_win_close(force, win, tabpage == curtab ? NULL : tabpage);
+  });
 }
 
 /// Calls a function with window as temporary current window.
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 85fbdbd20a..2c1653006c 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -787,9 +787,7 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
   setmouse();
   setcursor();
 
-  TryState tstate;
   Error err = ERROR_INIT;
-  bool tl_ret = true;
   char firstcbuf[2];
   firstcbuf[0] = (char)(firstc > 0 ? firstc : '-');
   firstcbuf[1] = 0;
@@ -802,20 +800,19 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
     tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
     tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
     tv_dict_set_keys_readonly(dict);
-    try_enter(&tstate);
+    TRY_WRAP(&err, {
+      apply_autocmds(EVENT_CMDLINEENTER, firstcbuf, firstcbuf, false, curbuf);
+      restore_v_event(dict, &save_v_event);
+    });
 
-    apply_autocmds(EVENT_CMDLINEENTER, firstcbuf, firstcbuf, false, curbuf);
-    restore_v_event(dict, &save_v_event);
-
-    tl_ret = try_leave(&tstate, &err);
-    if (!tl_ret && ERROR_SET(&err)) {
+    if (ERROR_SET(&err)) {
       msg_putchar('\n');
       msg_scroll = true;
       msg_puts_hl(err.msg, HLF_E, true);
       api_clear_error(&err);
       redrawcmd();
     }
-    tl_ret = true;
+    err = ERROR_INIT;
   }
   may_trigger_modechanged();
 
@@ -873,10 +870,10 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
     // not readonly:
     tv_dict_add_bool(dict, S_LEN("abort"),
                      s->gotesc ? kBoolVarTrue : kBoolVarFalse);
-    try_enter(&tstate);
-    apply_autocmds(EVENT_CMDLINELEAVE, firstcbuf, firstcbuf, false, curbuf);
-    // error printed below, to avoid redraw issues
-    tl_ret = try_leave(&tstate, &err);
+    TRY_WRAP(&err, {
+      apply_autocmds(EVENT_CMDLINELEAVE, firstcbuf, firstcbuf, false, curbuf);
+      // error printed below, to avoid redraw issues
+    });
     if (tv_dict_get_number(dict, "abort") != 0) {
       s->gotesc = true;
     }
@@ -929,7 +926,7 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
   msg_scroll = s->save_msg_scroll;
   redir_off = false;
 
-  if (!tl_ret && ERROR_SET(&err)) {
+  if (ERROR_SET(&err)) {
     msg_putchar('\n');
     emsg(err.msg);
     did_emsg = false;
@@ -937,7 +934,7 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
   }
 
   // When the command line was typed, no need for a wait-return prompt.
-  if (s->some_key_typed && tl_ret) {
+  if (s->some_key_typed && !ERROR_SET(&err)) {
     need_wait_return = false;
   }
 
@@ -2315,11 +2312,13 @@ static win_T *cmdpreview_open_win(buf_T *cmdpreview_buf)
 
   win_T *preview_win = curwin;
   Error err = ERROR_INIT;
+  int result = OK;
 
   // Switch to preview buffer
-  try_start();
-  int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, cmdpreview_buf->handle, 0);
-  if (try_end(&err) || result == FAIL) {
+  TRY_WRAP(&err, {
+    result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, cmdpreview_buf->handle, 0);
+  });
+  if (ERROR_SET(&err) || result == FAIL) {
     api_clear_error(&err);
     return NULL;
   }
@@ -2600,9 +2599,10 @@ static bool cmdpreview_may_show(CommandLineState *s)
   // open the preview window. The preview callback also handles doing the changes and highlights for
   // the preview.
   Error err = ERROR_INIT;
-  try_start();
-  cmdpreview_type = execute_cmd(&ea, &cmdinfo, true);
-  if (try_end(&err)) {
+  TRY_WRAP(&err, {
+    cmdpreview_type = execute_cmd(&ea, &cmdinfo, true);
+  });
+  if (ERROR_SET(&err)) {
     api_clear_error(&err);
     cmdpreview_type = 0;
   }
@@ -2643,7 +2643,6 @@ end:
 static void do_autocmd_cmdlinechanged(int firstc)
 {
   if (has_event(EVENT_CMDLINECHANGED)) {
-    TryState tstate;
     Error err = ERROR_INIT;
     save_v_event_T save_v_event;
     dict_T *dict = get_v_event(&save_v_event);
@@ -2656,13 +2655,11 @@ static void do_autocmd_cmdlinechanged(int firstc)
     tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
     tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
     tv_dict_set_keys_readonly(dict);
-    try_enter(&tstate);
-
-    apply_autocmds(EVENT_CMDLINECHANGED, firstcbuf, firstcbuf, false, curbuf);
-    restore_v_event(dict, &save_v_event);
-
-    bool tl_ret = try_leave(&tstate, &err);
-    if (!tl_ret && ERROR_SET(&err)) {
+    TRY_WRAP(&err, {
+      apply_autocmds(EVENT_CMDLINECHANGED, firstcbuf, firstcbuf, false, curbuf);
+      restore_v_event(dict, &save_v_event);
+    });
+    if (ERROR_SET(&err)) {
       msg_putchar('\n');
       msg_scroll = true;
       msg_puts_hl(err.msg, HLF_E, true);
@@ -3179,11 +3176,9 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
   static int prev_prompt_errors = 0;
   Callback color_cb = CALLBACK_NONE;
   bool can_free_cb = false;
-  TryState tstate;
   Error err = ERROR_INIT;
   const char *err_errmsg = e_intern2;
   bool dgc_ret = true;
-  bool tl_ret = true;
 
   if (colored_ccline->prompt_id != prev_prompt_id) {
     prev_prompt_errors = 0;
@@ -3196,16 +3191,16 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
     assert(colored_ccline->input_fn);
     color_cb = colored_ccline->highlight_callback;
   } else if (colored_ccline->cmdfirstc == ':') {
-    try_enter(&tstate);
-    err_errmsg = N_("E5408: Unable to get g:Nvim_color_cmdline callback: %s");
-    dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
-                                   &color_cb);
-    tl_ret = try_leave(&tstate, &err);
+    TRY_WRAP(&err, {
+      err_errmsg = N_("E5408: Unable to get g:Nvim_color_cmdline callback: %s");
+      dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
+                                     &color_cb);
+    });
     can_free_cb = true;
   } else if (colored_ccline->cmdfirstc == '=') {
     color_expr_cmdline(colored_ccline, ccline_colors);
   }
-  if (!tl_ret || !dgc_ret) {
+  if (ERROR_SET(&err) || !dgc_ret) {
     goto color_cmdline_error;
   }
 
@@ -3226,20 +3221,22 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
   // correct, with msg_col it just misses leading `:`. Since `redraw!` in
   // callback lags this is least of the user problems.
   //
-  // Also using try_enter() because error messages may overwrite typed
+  // Also using TRY_WRAP because error messages may overwrite typed
   // command-line which is not expected.
   getln_interrupted_highlight = false;
-  try_enter(&tstate);
-  err_errmsg = N_("E5407: Callback has thrown an exception: %s");
-  const int saved_msg_col = msg_col;
-  msg_silent++;
-  const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv);
-  msg_silent--;
-  msg_col = saved_msg_col;
-  if (got_int) {
-    getln_interrupted_highlight = true;
-  }
-  if (!try_leave(&tstate, &err) || !cbcall_ret) {
+  bool cbcall_ret = true;
+  TRY_WRAP(&err, {
+    err_errmsg = N_("E5407: Callback has thrown an exception: %s");
+    const int saved_msg_col = msg_col;
+    msg_silent++;
+    cbcall_ret = callback_call(&color_cb, 1, &arg, &tv);
+    msg_silent--;
+    msg_col = saved_msg_col;
+    if (got_int) {
+      getln_interrupted_highlight = true;
+    }
+  });
+  if (ERROR_SET(&err) || !cbcall_ret) {
     goto color_cmdline_error;
   }
   if (tv.v_type != VAR_LIST) {
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 5ebff3a809..4de25f4265 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -623,38 +623,32 @@ static int nlua_with(lua_State *L)
   cmdmod.cmod_flags = flags;
   apply_cmdmod(&cmdmod);
 
-  if (buf || win) {
-    try_start();
-  }
-
-  aco_save_T aco;
-  win_execute_T win_execute_args;
   Error err = ERROR_INIT;
+  TRY_WRAP(&err, {
+    aco_save_T aco;
+    win_execute_T win_execute_args;
 
-  if (win) {
-    tabpage_T *tabpage = win_find_tabpage(win);
-    if (!win_execute_before(&win_execute_args, win, tabpage)) {
-      goto end;
+    if (win) {
+      tabpage_T *tabpage = win_find_tabpage(win);
+      if (!win_execute_before(&win_execute_args, win, tabpage)) {
+        goto end;
+      }
+    } else if (buf) {
+      aucmd_prepbuf(&aco, buf);
     }
-  } else if (buf) {
-    aucmd_prepbuf(&aco, buf);
-  }
 
-  int s = lua_gettop(L);
-  lua_pushvalue(L, 2);
-  status = lua_pcall(L, 0, LUA_MULTRET, 0);
-  rets = lua_gettop(L) - s;
+    int s = lua_gettop(L);
+    lua_pushvalue(L, 2);
+    status = lua_pcall(L, 0, LUA_MULTRET, 0);
+    rets = lua_gettop(L) - s;
 
-  if (win) {
-    win_execute_after(&win_execute_args);
-  } else if (buf) {
-    aucmd_restbuf(&aco);
-  }
-
-end:
-  if (buf || win) {
-    try_end(&err);
-  }
+    if (win) {
+      win_execute_after(&win_execute_args);
+    } else if (buf) {
+      aucmd_restbuf(&aco);
+    }
+    end:;
+  });
 
   undo_cmdmod(&cmdmod);
   cmdmod = save_cmdmod;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index e261f06b42..9ad8256f16 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3933,7 +3933,7 @@ static bool switch_option_context(void *const ctx, OptScope scope, void *const f
         == FAIL) {
       restore_win_noblock(switchwin, true);
 
-      if (try_end(err)) {
+      if (ERROR_SET(err)) {
         return false;
       }
       api_set_error(err, kErrorTypeException, "Problem while switching windows");
-- 
cgit 


From df367cf91cdd805d907f758cb295c6b36fe39480 Mon Sep 17 00:00:00 2001
From: Shougo 
Date: Tue, 17 Dec 2024 21:19:55 +0900
Subject: test: unreliable test "messages &messagesopt wait" #31548

---
 test/functional/legacy/messages_spec.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua
index 063bd05618..adf75c2836 100644
--- a/test/functional/legacy/messages_spec.lua
+++ b/test/functional/legacy/messages_spec.lua
@@ -709,7 +709,7 @@ describe('messages', function()
     feed('')
 
     -- Check no hit-enter prompt when "wait:" is set
-    command('set messagesopt=wait:100,history:500')
+    command('set messagesopt=wait:500,history:500')
     feed(":echo 'foo' | echo 'bar' | echo 'baz'\n")
     screen:expect({
       grid = [[
@@ -720,7 +720,7 @@ describe('messages', function()
       bar                                          |
       baz                                          |
     ]],
-      timeout = 100,
+      timeout = 500,
     })
     screen:expect([[
       ^                                             |
-- 
cgit 


From 0dd933265ff2e64786fd30f949e767e10f401519 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Tue, 17 Dec 2024 07:11:41 -0600
Subject: feat(terminal)!: cursor shape and blink (#31562)

When a terminal application running inside the terminal emulator sets
the cursor shape or blink status of the cursor, update the cursor in the
parent terminal to match.

This removes the "virtual cursor" that has been in use by the terminal
emulator since the beginning. The original rationale for using the
virtual cursor was to avoid having to support additional UI methods to
change the cursor color for other (non-TUI) UIs, instead relying on the
TermCursor and TermCursorNC highlight groups.

The TermCursor highlight group is now used in the default 'guicursor'
value, which has a new entry for Terminal mode. However, the
TermCursorNC highlight group is no longer supported: since terminal
windows now use the real cursor, when the window is not focused there is
no cursor displayed in the window at all, so there is nothing to
highlight. Users can still use the StatusLineTermNC highlight group to
differentiate non-focused terminal windows.

BREAKING CHANGE: The TermCursorNC highlight group is no longer supported.
---
 runtime/doc/deprecated.txt                         |   4 +
 runtime/doc/news.txt                               |  11 +
 runtime/doc/options.txt                            |   6 +-
 runtime/doc/syntax.txt                             |   2 -
 runtime/doc/terminal.txt                           |   2 +-
 runtime/doc/vim_diff.txt                           |   1 -
 runtime/lua/vim/_meta/options.lua                  |   6 +-
 runtime/syntax/vim.vim                             |   2 +-
 src/nvim/cursor.c                                  |   2 +
 src/nvim/cursor_shape.c                            |   3 +
 src/nvim/cursor_shape.h                            |   3 +-
 src/nvim/highlight.h                               |   1 -
 src/nvim/highlight_defs.h                          |   1 -
 src/nvim/highlight_group.c                         |   1 -
 src/nvim/option_vars.h                             |   2 +-
 src/nvim/options.lua                               |   8 +-
 src/nvim/state.c                                   |  10 +-
 src/nvim/terminal.c                                | 184 +++++++++--
 src/nvim/tui/tui.c                                 |   2 +-
 test/functional/api/vim_spec.lua                   |   4 +-
 test/functional/autocmd/focus_spec.lua             |   8 +-
 test/functional/core/job_spec.lua                  |   4 +-
 test/functional/core/log_spec.lua                  |   2 +-
 test/functional/core/main_spec.lua                 |   2 +-
 test/functional/core/startup_spec.lua              |   4 +-
 test/functional/legacy/highlight_spec.lua          |   2 +-
 test/functional/lua/ui_event_spec.lua              |  12 +-
 test/functional/terminal/altscreen_spec.lua        |  12 +-
 test/functional/terminal/api_spec.lua              |   8 +-
 test/functional/terminal/buffer_spec.lua           |  32 +-
 test/functional/terminal/cursor_spec.lua           | 355 ++++++++++++++++-----
 test/functional/terminal/highlight_spec.lua        |  91 +++---
 test/functional/terminal/mouse_spec.lua            |  82 ++---
 test/functional/terminal/scrollback_spec.lua       |  38 +--
 test/functional/terminal/tui_spec.lua              | 321 +++++++++----------
 test/functional/terminal/window_spec.lua           |  30 +-
 test/functional/terminal/window_split_tab_spec.lua |  10 +-
 test/functional/testterm.lua                       |  10 +-
 test/functional/ui/cursor_spec.lua                 |  22 +-
 test/functional/ui/hlstate_spec.lua                |   8 +-
 test/functional/ui/messages_spec.lua               | 124 +++----
 test/functional/ui/output_spec.lua                 |   4 +-
 test/functional/ui/screen.lua                      |   4 +-
 43 files changed, 901 insertions(+), 539 deletions(-)

diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index ab9c0b2ce8..592b6389c4 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -38,6 +38,10 @@ DIAGNOSTICS
 - The "cursor_position" parameter of |vim.diagnostic.JumpOpts| is renamed to
   "pos"
 
+HIGHLIGHTS
+• *TermCursorNC*	As of Nvim 0.11, unfocused |terminal| windows no
+			longer have any cursor.
+
 TREESITTER
 • *TSNode:child_containing_descendant()*	Use
   |TSNode:child_with_descendant()| instead; it is identical except that it can
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 5e70d75cfd..ad0835e80f 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -87,6 +87,11 @@ EVENTS
 • |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in
   |api-fast| context.
 
+HIGHLIGHTS
+
+• |TermCursorNC| is removed and no longer supported. Unfocused terminals no
+  longer have a cursor.
+
 LSP
 
 • Improved rendering of LSP hover docs. |K-lsp-default|
@@ -281,6 +286,12 @@ TERMINAL
   'scrollback' are not reflown.
 • The |terminal| now supports OSC 8 escape sequences and will display
   hyperlinks in supporting host terminals.
+• The |terminal| now uses the actual cursor, rather than a "virtual" cursor.
+  This means that escape codes sent by applications running in a terminal
+  buffer can change the cursor shape and visibility. However, it also
+  means that the |TermCursorNC| highlight group is no longer supported: an
+  unfocused terminal window will have no cursor at all (so there is nothing to
+  highlight).
 
 TREESITTER
 
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 84f0bfe141..c2ed19f34f 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -2977,7 +2977,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 	An |OptionSet| autocmd can be used to set it up to match automatically.
 
 				*'guicursor'* *'gcr'* *E545* *E546* *E548* *E549*
-'guicursor' 'gcr'	string	(default "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20")
+'guicursor' 'gcr'	string	(default "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,t:block-blinkon500-blinkoff500-TermCursor")
 			global
 	Configures the cursor style for each mode. Works in the GUI and many
 	terminals.  See |tui-cursor-shape|.
@@ -3005,6 +3005,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 		ci	Command-line Insert mode
 		cr	Command-line Replace mode
 		sm	showmatch in Insert mode
+		t	Terminal mode
 		a	all modes
 	The argument-list is a dash separated list of these arguments:
 		hor{N}	horizontal bar, {N} percent of the character height
@@ -3021,7 +3022,8 @@ A jump table for the options with a short description can be found at |Q_op|.
 			cursor is not shown.  Times are in msec.  When one of
 			the numbers is zero, there is no blinking. E.g.: >vim
 				set guicursor=n:blinkon0
-<			- Default is "blinkon0" for each mode.
+<
+			Default is "blinkon0" for each mode.
 		{group-name}
 			Highlight group that decides the color and font of the
 			cursor.
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index c5d3422f62..df4d0f7260 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -5162,8 +5162,6 @@ EndOfBuffer	Filler lines (~) after the end of the buffer.
 		By default, this is highlighted like |hl-NonText|.
 							*hl-TermCursor*
 TermCursor	Cursor in a focused terminal.
-							*hl-TermCursorNC*
-TermCursorNC	Cursor in an unfocused terminal.
 							*hl-ErrorMsg*
 ErrorMsg	Error messages on the command line.
 							*hl-WinSeparator*
diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt
index 5a1421f430..f9536c2f0c 100644
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -101,7 +101,7 @@ Configuration					*terminal-config*
 
 Options:		'modified', 'scrollback'
 Events:			|TermOpen|, |TermEnter|, |TermLeave|, |TermClose|
-Highlight groups:	|hl-TermCursor|, |hl-TermCursorNC|
+Highlight groups:	|hl-TermCursor|
 
 Terminal sets local defaults for some options, which may differ from your
 global configuration.
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index bfef5d5d4a..c93d2b119e 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -325,7 +325,6 @@ Highlight groups:
 - |hl-MsgSeparator| highlights separator for scrolled messages
 - |hl-Substitute|
 - |hl-TermCursor|
-- |hl-TermCursorNC|
 - |hl-WinSeparator| highlights window separators
 - |hl-Whitespace| highlights 'listchars' whitespace
 - |hl-WinBar| highlights 'winbar'
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 63bf0df5f6..e5cea884c5 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -2783,6 +2783,7 @@ vim.go.gp = vim.go.grepprg
 --- 	ci	Command-line Insert mode
 --- 	cr	Command-line Replace mode
 --- 	sm	showmatch in Insert mode
+--- 	t	Terminal mode
 --- 	a	all modes
 --- The argument-list is a dash separated list of these arguments:
 --- 	hor{N}	horizontal bar, {N} percent of the character height
@@ -2802,7 +2803,8 @@ vim.go.gp = vim.go.grepprg
 --- ```vim
 --- 			set guicursor=n:blinkon0
 --- ```
---- - Default is "blinkon0" for each mode.
+---
+--- 		Default is "blinkon0" for each mode.
 --- 	{group-name}
 --- 		Highlight group that decides the color and font of the
 --- 		cursor.
@@ -2848,7 +2850,7 @@ vim.go.gp = vim.go.grepprg
 ---
 ---
 --- @type string
-vim.o.guicursor = "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20"
+vim.o.guicursor = "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,t:block-blinkon500-blinkoff500-TermCursor"
 vim.o.gcr = vim.o.guicursor
 vim.go.guicursor = vim.o.guicursor
 vim.go.gcr = vim.go.guicursor
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 3ad04e2957..2a4bb2c8c9 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -63,7 +63,7 @@ syn keyword vimGroup contained	Comment Constant String Character Number Boolean
 syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit DiffText PmenuSbar TabLineSel TabLineFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC CursorIM LineNrAbove LineNrBelow
 syn match vimHLGroup contained "\"
 syn keyword vimOnlyHLGroup contained	Menu Scrollbar ToolbarButton ToolbarLine Tooltip VisualNOS
-syn keyword nvimHLGroup contained	FloatBorder FloatFooter FloatTitle MsgSeparator NormalFloat NormalNC Substitute TermCursor TermCursorNC VisualNC Whitespace WinBar WinBarNC WinSeparator
+syn keyword nvimHLGroup contained	FloatBorder FloatFooter FloatTitle MsgSeparator NormalFloat NormalNC Substitute TermCursor VisualNC Whitespace WinBar WinBarNC WinSeparator
 "}}}2
 syn case match
 
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 2b18c51571..a248a4133e 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -341,9 +341,11 @@ void check_cursor_col(win_T *win)
   } else if (win->w_cursor.col >= len) {
     // Allow cursor past end-of-line when:
     // - in Insert mode or restarting Insert mode
+    // - in Terminal mode
     // - in Visual mode and 'selection' isn't "old"
     // - 'virtualedit' is set
     if ((State & MODE_INSERT) || restart_edit
+        || (State & MODE_TERMINAL)
         || (VIsual_active && *p_sel != 'o')
         || (cur_ve_flags & kOptVeFlagOnemore)
         || virtual_active(win)) {
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index 1f11367618..a058394b9f 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -45,6 +45,7 @@ cursorentry_T shape_table[SHAPE_IDX_COUNT] = {
   { "more", 0, 0, 0,   0,   0,   0, 0, 0, "m", SHAPE_MOUSE },
   { "more_lastline", 0, 0, 0,   0,   0,   0, 0, 0, "ml", SHAPE_MOUSE },
   { "showmatch", 0, 0, 0, 100, 100, 100, 0, 0, "sm", SHAPE_CURSOR },
+  { "terminal", 0, 0, 0, 0, 0, 0, 0, 0, "t", SHAPE_CURSOR },
 };
 
 /// Converts cursor_shapes into an Array of Dictionaries
@@ -321,6 +322,8 @@ int cursor_get_mode_idx(void)
 {
   if (State == MODE_SHOWMATCH) {
     return SHAPE_IDX_SM;
+  } else if (State == MODE_TERMINAL) {
+    return SHAPE_IDX_TERM;
   } else if (State & VREPLACE_FLAG) {
     return SHAPE_IDX_R;
   } else if (State & REPLACE_FLAG) {
diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h
index 21967a81f4..6d9e7de2e5 100644
--- a/src/nvim/cursor_shape.h
+++ b/src/nvim/cursor_shape.h
@@ -23,7 +23,8 @@ typedef enum {
   SHAPE_IDX_MORE   = 14,      ///< Hit-return or More
   SHAPE_IDX_MOREL  = 15,      ///< Hit-return or More in last line
   SHAPE_IDX_SM     = 16,      ///< showing matching paren
-  SHAPE_IDX_COUNT  = 17,
+  SHAPE_IDX_TERM   = 17,      ///< Terminal mode
+  SHAPE_IDX_COUNT  = 18,
 } ModeShape;
 
 typedef enum {
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index 1be70100de..a89d778474 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -15,7 +15,6 @@ EXTERN const char *hlf_names[] INIT( = {
   [HLF_8] = "SpecialKey",
   [HLF_EOB] = "EndOfBuffer",
   [HLF_TERM] = "TermCursor",
-  [HLF_TERMNC] = "TermCursorNC",
   [HLF_AT] = "NonText",
   [HLF_D] = "Directory",
   [HLF_E] = "ErrorMsg",
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index a3c4062714..cbbc28311f 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -63,7 +63,6 @@ typedef enum {
                   ///< displayed different from what it is
   HLF_EOB,        ///< after the last line in the buffer
   HLF_TERM,       ///< terminal cursor focused
-  HLF_TERMNC,     ///< terminal cursor unfocused
   HLF_AT,         ///< @ characters at end of screen, characters that don't really exist in the text
   HLF_D,          ///< directories in CTRL-D listing
   HLF_E,          ///< error messages
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index cc1b833c3c..f1f5f47630 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -178,7 +178,6 @@ static const char *highlight_init_both[] = {
   "default link StatusLineTermNC StatusLineNC",
   "default link TabLine          StatusLineNC",
   "default link TabLineFill      TabLine",
-  "default link TermCursorNC     NONE",
   "default link VertSplit        WinSeparator",
   "default link VisualNOS        Visual",
   "default link Whitespace       NonText",
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index 97455380cc..f4d0a9a4b0 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -12,7 +12,7 @@
 // option_vars.h: definition of global variables for settable options
 
 #define HIGHLIGHT_INIT \
-  "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
+  "8:SpecialKey,~:EndOfBuffer,z:TermCursor,@:NonText,d:Directory,e:ErrorMsg," \
   "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
   "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC," \
   "c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 6dc32658fe..3142c30080 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -3631,7 +3631,9 @@ return {
     {
       abbreviation = 'gcr',
       cb = 'did_set_guicursor',
-      defaults = { if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20' },
+      defaults = {
+        if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,t:block-blinkon500-blinkoff500-TermCursor',
+      },
       deny_duplicates = true,
       desc = [=[
         Configures the cursor style for each mode. Works in the GUI and many
@@ -3660,6 +3662,7 @@ return {
         	ci	Command-line Insert mode
         	cr	Command-line Replace mode
         	sm	showmatch in Insert mode
+        	t	Terminal mode
         	a	all modes
         The argument-list is a dash separated list of these arguments:
         	hor{N}	horizontal bar, {N} percent of the character height
@@ -3676,7 +3679,8 @@ return {
         		cursor is not shown.  Times are in msec.  When one of
         		the numbers is zero, there is no blinking. E.g.: >vim
         			set guicursor=n:blinkon0
-        <			- Default is "blinkon0" for each mode.
+        <
+        		Default is "blinkon0" for each mode.
         	{group-name}
         		Highlight group that decides the color and font of the
         		cursor.
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 32e2a8d652..c4041dda07 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -138,14 +138,20 @@ void state_handle_k_event(void)
 /// Return true if in the current mode we need to use virtual.
 bool virtual_active(win_T *wp)
 {
-  unsigned cur_ve_flags = get_ve_flags(wp);
-
   // While an operator is being executed we return "virtual_op", because
   // VIsual_active has already been reset, thus we can't check for "block"
   // being used.
   if (virtual_op != kNone) {
     return virtual_op;
   }
+
+  // In Terminal mode the cursor can be positioned anywhere by the application
+  if (State & MODE_TERMINAL) {
+    return true;
+  }
+
+  unsigned cur_ve_flags = get_ve_flags(wp);
+
   return cur_ve_flags == kOptVeFlagAll
          || ((cur_ve_flags & kOptVeFlagBlock) && VIsual_active && VIsual_mode == Ctrl_V)
          || ((cur_ve_flags & kOptVeFlagInsert) && (State & MODE_INSERT));
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 6d4af0fc57..fa08f3d6ca 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -52,6 +52,7 @@
 #include "nvim/channel.h"
 #include "nvim/channel_defs.h"
 #include "nvim/cursor.h"
+#include "nvim/cursor_shape.h"
 #include "nvim/drawline.h"
 #include "nvim/drawscreen.h"
 #include "nvim/eval.h"
@@ -160,14 +161,18 @@ struct terminal {
   int invalid_start, invalid_end;   // invalid rows in libvterm screen
   struct {
     int row, col;
+    int shape;
     bool visible;
+    bool blink;
   } cursor;
-  bool pending_resize;              // pending width/height
 
-  bool color_set[16];
+  struct {
+    bool resize;          ///< pending width/height
+    bool cursor;          ///< pending cursor shape or blink change
+    StringBuilder *send;  ///< When there is a pending TermRequest autocommand, block and store input.
+  } pending;
 
-  // When there is a pending TermRequest autocommand, block and store input.
-  StringBuilder *pending_send;
+  bool color_set[16];
 
   char *selection_buffer;  /// libvterm selection buffer
   StringBuilder selection;  /// Growable array containing full selection data
@@ -207,24 +212,24 @@ static void emit_termrequest(void **argv)
   apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, false, AUGROUP_ALL, buf, NULL, &data);
   xfree(payload);
 
-  StringBuilder *term_pending_send = term->pending_send;
-  term->pending_send = NULL;
+  StringBuilder *term_pending_send = term->pending.send;
+  term->pending.send = NULL;
   if (kv_size(*pending_send)) {
     terminal_send(term, pending_send->items, pending_send->size);
     kv_destroy(*pending_send);
   }
   if (term_pending_send != pending_send) {
-    term->pending_send = term_pending_send;
+    term->pending.send = term_pending_send;
   }
   xfree(pending_send);
 }
 
 static void schedule_termrequest(Terminal *term, char *payload, size_t payload_length)
 {
-  term->pending_send = xmalloc(sizeof(StringBuilder));
-  kv_init(*term->pending_send);
+  term->pending.send = xmalloc(sizeof(StringBuilder));
+  kv_init(*term->pending.send);
   multiqueue_put(main_loop.events, emit_termrequest, term, payload, (void *)payload_length,
-                 term->pending_send);
+                 term->pending.send);
 }
 
 static int parse_osc8(VTermStringFragment frag, int *attr)
@@ -363,7 +368,7 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
   // Create a new terminal instance and configure it
   Terminal *term = *termpp = xcalloc(1, sizeof(Terminal));
   term->opts = opts;
-  term->cursor.visible = true;
+
   // Associate the terminal instance with the new buffer
   term->buf_handle = buf->handle;
   buf->terminal = term;
@@ -387,6 +392,28 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
   vterm_state_set_selection_callbacks(state, &vterm_selection_callbacks, term,
                                       term->selection_buffer, SELECTIONBUF_SIZE);
 
+  VTermValue cursor_shape;
+  switch (shape_table[SHAPE_IDX_TERM].shape) {
+  case SHAPE_BLOCK:
+    cursor_shape.number = VTERM_PROP_CURSORSHAPE_BLOCK;
+    break;
+  case SHAPE_HOR:
+    cursor_shape.number = VTERM_PROP_CURSORSHAPE_UNDERLINE;
+    break;
+  case SHAPE_VER:
+    cursor_shape.number = VTERM_PROP_CURSORSHAPE_BAR_LEFT;
+    break;
+  }
+  vterm_state_set_termprop(state, VTERM_PROP_CURSORSHAPE, &cursor_shape);
+
+  VTermValue cursor_blink;
+  if (shape_table[SHAPE_IDX_TERM].blinkon != 0 && shape_table[SHAPE_IDX_TERM].blinkoff != 0) {
+    cursor_blink.boolean = true;
+  } else {
+    cursor_blink.boolean = false;
+  }
+  vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &cursor_blink);
+
   // force a initial refresh of the screen to ensure the buffer will always
   // have as many lines as screen rows when refresh_scrollback is called
   term->invalid_start = 0;
@@ -565,7 +592,7 @@ void terminal_check_size(Terminal *term)
 
   vterm_set_size(term->vt, height, width);
   vterm_screen_flush_damage(term->vts);
-  term->pending_resize = true;
+  term->pending.resize = true;
   invalidate_terminal(term, -1, -1);
 }
 
@@ -614,16 +641,28 @@ bool terminal_enter(void)
   curwin->w_p_so = 0;
   curwin->w_p_siso = 0;
 
+  // Save the existing cursor entry since it may be modified by the application
+  cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM];
+
+  // Update the cursor shape table and flush changes to the UI
+  s->term->pending.cursor = true;
+  refresh_cursor(s->term);
+
   adjust_topline(s->term, buf, 0);  // scroll to end
-  // erase the unfocused cursor
-  invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
   showmode();
   curwin->w_redr_status = true;  // For mode() in statusline. #8323
   redraw_custom_title_later();
-  ui_busy_start();
+  if (!s->term->cursor.visible) {
+    // Hide cursor if it should be hidden
+    ui_busy_start();
+  }
+  ui_cursor_shape();
   apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf);
   may_trigger_modechanged();
 
+  // Tell the terminal it has focus
+  terminal_focus(s->term, true);
+
   s->state.execute = terminal_execute;
   s->state.check = terminal_check;
   state_enter(&s->state);
@@ -635,6 +674,9 @@ bool terminal_enter(void)
   RedrawingDisabled = s->save_rd;
   apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
 
+  shape_table[SHAPE_IDX_TERM] = save_cursorentry;
+  ui_mode_info_set();
+
   if (save_curwin == curwin->handle) {  // Else: window was closed.
     curwin->w_p_cul = save_w_p_cul;
     if (save_w_p_culopt) {
@@ -649,8 +691,9 @@ bool terminal_enter(void)
     free_string_option(save_w_p_culopt);
   }
 
-  // draw the unfocused cursor
-  invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
+  // Tell the terminal it lost focus
+  terminal_focus(s->term, false);
+
   if (curbuf->terminal == s->term && !s->close) {
     terminal_check_cursor();
   }
@@ -659,7 +702,11 @@ bool terminal_enter(void)
   } else {
     unshowmode(true);
   }
-  ui_busy_stop();
+  if (!s->term->cursor.visible) {
+    // If cursor was hidden, show it again
+    ui_busy_stop();
+  }
+  ui_cursor_shape();
   if (s->close) {
     bool wipe = s->term->buf_handle != 0;
     s->term->destroy = true;
@@ -810,6 +857,19 @@ static int terminal_execute(VimState *state, int key)
     return 0;
   }
   if (s->term != curbuf->terminal) {
+    // Active terminal buffer changed, flush terminal's cursor state to the UI
+    curbuf->terminal->pending.cursor = true;
+
+    if (!s->term->cursor.visible) {
+      // If cursor was hidden, show it again
+      ui_busy_stop();
+    }
+
+    if (!curbuf->terminal->cursor.visible) {
+      // Hide cursor if it should be hidden
+      ui_busy_start();
+    }
+
     invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
     invalidate_terminal(curbuf->terminal,
                         curbuf->terminal->cursor.row,
@@ -857,8 +917,8 @@ static void terminal_send(Terminal *term, const char *data, size_t size)
   if (term->closed) {
     return;
   }
-  if (term->pending_send) {
-    kv_concat_len(*term->pending_send, data, size);
+  if (term->pending.send) {
+    kv_concat_len(*term->pending.send, data, size);
     return;
   }
   term->opts.write_cb(data, size, term->opts.data);
@@ -1063,14 +1123,6 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
       attr_id = hl_combine_attr(attr_id, cell.uri);
     }
 
-    if (term->cursor.visible && term->cursor.row == row
-        && term->cursor.col == col) {
-      attr_id = hl_combine_attr(attr_id,
-                                is_focused(term) && wp == curwin
-                                ? win_hl_attr(wp, HLF_TERM)
-                                : win_hl_attr(wp, HLF_TERMNC));
-    }
-
     term_attrs[col] = attr_id;
   }
 }
@@ -1085,6 +1137,17 @@ bool terminal_running(const Terminal *term)
   return !term->closed;
 }
 
+static void terminal_focus(const Terminal *term, bool focus)
+  FUNC_ATTR_NONNULL_ALL
+{
+  VTermState *state = vterm_obtain_state(term->vt);
+  if (focus) {
+    vterm_state_focus_in(state);
+  } else {
+    vterm_state_focus_out(state);
+  }
+}
+
 // }}}
 // libvterm callbacks {{{
 
@@ -1106,8 +1169,7 @@ static int term_movecursor(VTermPos new_pos, VTermPos old_pos, int visible, void
   Terminal *term = data;
   term->cursor.row = new_pos.row;
   term->cursor.col = new_pos.col;
-  invalidate_terminal(term, old_pos.row, old_pos.row + 1);
-  invalidate_terminal(term, new_pos.row, new_pos.row + 1);
+  invalidate_terminal(term, -1, -1);
   return 1;
 }
 
@@ -1135,8 +1197,17 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
     break;
 
   case VTERM_PROP_CURSORVISIBLE:
+    if (is_focused(term)) {
+      if (!val->boolean && term->cursor.visible) {
+        // Hide the cursor
+        ui_busy_start();
+      } else if (val->boolean && !term->cursor.visible) {
+        // Unhide the cursor
+        ui_busy_stop();
+      }
+      invalidate_terminal(term, -1, -1);
+    }
     term->cursor.visible = val->boolean;
-    invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
     break;
 
   case VTERM_PROP_TITLE: {
@@ -1172,6 +1243,18 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
     term->forward_mouse = (bool)val->number;
     break;
 
+  case VTERM_PROP_CURSORBLINK:
+    term->cursor.blink = val->boolean;
+    term->pending.cursor = true;
+    invalidate_terminal(term, -1, -1);
+    break;
+
+  case VTERM_PROP_CURSORSHAPE:
+    term->cursor.shape = val->number;
+    term->pending.cursor = true;
+    invalidate_terminal(term, -1, -1);
+    break;
+
   default:
     return 0;
   }
@@ -1849,12 +1932,47 @@ static void refresh_terminal(Terminal *term)
   refresh_size(term, buf);
   refresh_scrollback(term, buf);
   refresh_screen(term, buf);
+  refresh_cursor(term);
   aucmd_restbuf(&aco);
 
   int ml_added = buf->b_ml.ml_line_count - ml_before;
   adjust_topline(term, buf, ml_added);
 }
 
+static void refresh_cursor(Terminal *term)
+  FUNC_ATTR_NONNULL_ALL
+{
+  if (!is_focused(term) || !term->pending.cursor) {
+    return;
+  }
+  term->pending.cursor = false;
+
+  if (term->cursor.blink) {
+    // For the TUI, this value doesn't actually matter, as long as it's non-zero. The terminal
+    // emulator dictates the blink frequency, not the application.
+    // For GUIs we just pick an arbitrary value, for now.
+    shape_table[SHAPE_IDX_TERM].blinkon = 500;
+    shape_table[SHAPE_IDX_TERM].blinkoff = 500;
+  } else {
+    shape_table[SHAPE_IDX_TERM].blinkon = 0;
+    shape_table[SHAPE_IDX_TERM].blinkoff = 0;
+  }
+
+  switch (term->cursor.shape) {
+  case VTERM_PROP_CURSORSHAPE_BLOCK:
+    shape_table[SHAPE_IDX_TERM].shape = SHAPE_BLOCK;
+    break;
+  case VTERM_PROP_CURSORSHAPE_UNDERLINE:
+    shape_table[SHAPE_IDX_TERM].shape = SHAPE_HOR;
+    break;
+  case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
+    shape_table[SHAPE_IDX_TERM].shape = SHAPE_VER;
+    break;
+  }
+
+  ui_mode_info_set();
+}
+
 /// Calls refresh_terminal() on all invalidated_terminals.
 static void refresh_timer_cb(TimeWatcher *watcher, void *data)
 {
@@ -1875,11 +1993,11 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data)
 
 static void refresh_size(Terminal *term, buf_T *buf)
 {
-  if (!term->pending_resize || term->closed) {
+  if (!term->pending.resize || term->closed) {
     return;
   }
 
-  term->pending_resize = false;
+  term->pending.resize = false;
   int width, height;
   vterm_get_size(term->vt, &height, &width);
   term->invalid_start = 0;
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 603db5b891..d514a597b1 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1339,7 +1339,7 @@ static void tui_set_mode(TUIData *tui, ModeShape mode)
   case SHAPE_VER:
     shape = 5; break;
   }
-  UNIBI_SET_NUM_VAR(tui->params[0], shape + (int)(c.blinkon == 0));
+  UNIBI_SET_NUM_VAR(tui->params[0], shape + (int)(c.blinkon == 0 || c.blinkoff == 0));
   unibi_out_ext(tui, tui->unibi_ext.set_cursor_style);
 }
 
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 879df690d1..130be9987f 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -3785,7 +3785,7 @@ describe('API', function()
       screen:expect {
         grid = [[
                                                           |
-        {1:~}{102: }{4:                                       }{1:         }|
+        {1:~}{4:^                                        }{1:         }|
         {1:~}{4:                                        }{1:         }|*4
         {1:~                                                 }|*3
         {5:-- TERMINAL --}                                    |
@@ -3801,7 +3801,7 @@ describe('API', function()
       screen:expect {
         grid = [[
                                                           |
-        {1:~}{4:herrejösses!}{102: }{4:                           }{1:         }|
+        {1:~}{4:herrejösses!^                            }{1:         }|
         {1:~}{4:                                        }{1:         }|*4
         {1:~                                                 }|*3
         {5:-- TERMINAL --}                                    |
diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua
index 7f6092bf48..177e8997cf 100644
--- a/test/functional/autocmd/focus_spec.lua
+++ b/test/functional/autocmd/focus_spec.lua
@@ -47,7 +47,7 @@ describe('autoread TUI FocusGained/FocusLost', function()
 
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -57,7 +57,7 @@ describe('autoread TUI FocusGained/FocusLost', function()
     feed_command('edit ' .. path)
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:xtest-foo                                         }|
       :edit xtest-foo                                   |
@@ -68,7 +68,7 @@ describe('autoread TUI FocusGained/FocusLost', function()
     feed_data('\027[O')
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:xtest-foo                                         }|
       :edit xtest-foo                                   |
@@ -83,7 +83,7 @@ describe('autoread TUI FocusGained/FocusLost', function()
 
     screen:expect {
       grid = [[
-      {1:l}ine 1                                            |
+      ^line 1                                            |
       line 2                                            |
       line 3                                            |
       line 4                                            |
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 618c294566..b4d80d4ed4 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -1198,7 +1198,7 @@ describe('jobs', function()
     })
     -- Wait for startup to complete, so that all terminal responses are received.
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       ~                                                 |*3
       {1:[No Name]                       0,0-1          All}|
                                                         |
@@ -1208,7 +1208,7 @@ describe('jobs', function()
     feed(':q')
     screen:expect([[
                                                         |
-      [Process exited 0]{1: }                               |
+      [Process exited 0]^                                |
                                                         |*4
       {3:-- TERMINAL --}                                    |
     ]])
diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua
index 57dfd6364c..571bece833 100644
--- a/test/functional/core/log_spec.lua
+++ b/test/functional/core/log_spec.lua
@@ -46,7 +46,7 @@ describe('log', function()
         env = env,
       })
       screen:expect([[
-        {1: }                                                 |
+        ^                                                  |
         ~                                                 |*4
                                                           |
         {3:-- TERMINAL --}                                    |
diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index 3b7cefb89f..bfa5a0eb6a 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -120,7 +120,7 @@ describe('command-line option', function()
       feed('i:cq')
       screen:expect([[
                                                 |
-        [Process exited 1]{2: }                     |
+        [Process exited 1]^                      |
                                                 |*5
         {5:-- TERMINAL --}                          |
       ]])
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index f3c477d210..319a037342 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -1153,7 +1153,7 @@ describe('user config init', function()
         -- `i` to enter Terminal mode, `a` to allow
         feed('ia')
         screen:expect([[
-                                                            |
+          ^                                                  |
           ~                                                 |*4
           [No Name]                       0,0-1          All|
                                                             |
@@ -1162,7 +1162,7 @@ describe('user config init', function()
         feed(':echo g:exrc_file')
         screen:expect(string.format(
           [[
-                                                            |
+          ^                                                  |
           ~                                                 |*4
           [No Name]                       0,0-1          All|
           %s%s|
diff --git a/test/functional/legacy/highlight_spec.lua b/test/functional/legacy/highlight_spec.lua
index e663931bd7..24d6abcc6b 100644
--- a/test/functional/legacy/highlight_spec.lua
+++ b/test/functional/legacy/highlight_spec.lua
@@ -29,8 +29,8 @@ describe(':highlight', function()
                                          |
       TermCursor     {2:xxx} {18:cterm=}reverse   |
                          {18:gui=}reverse     |
-      TermCursorNC   xxx cleared         |
       NonText        {1:xxx} {18:ctermfg=}12      |
+                         {18:gui=}bold        |
       {6:-- More --}^                         |
     ]])
     feed('q')
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
index f1cf657d78..25cc46e260 100644
--- a/test/functional/lua/ui_event_spec.lua
+++ b/test/functional/lua/ui_event_spec.lua
@@ -263,7 +263,7 @@ describe('vim.ui_attach', function()
       ]],
       messages = {
         {
-          content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 7 } },
+          content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 6 } },
           kind = 'emsg',
         },
       },
@@ -280,7 +280,7 @@ describe('vim.ui_attach', function()
       ]],
       messages = {
         {
-          content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 19 } },
+          content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 18 } },
           kind = 'confirm_sub',
         },
       },
@@ -348,7 +348,7 @@ describe('vim.ui_attach', function()
         foo^                                     |
         {1:~                                       }|*4
       ]],
-      showmode = { { '-- INSERT --', 5, 12 } },
+      showmode = { { '-- INSERT --', 5, 11 } },
     })
     feed(':1mes clear:mes')
     screen:expect({
@@ -375,7 +375,7 @@ describe('vim.ui_attach', function()
             {
               'Error executing vim.schedule lua callback: [string ""]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string ""]:2: in function <[string ""]:2>',
               9,
-              7,
+              6,
             },
           },
           kind = 'lua_error',
@@ -385,13 +385,13 @@ describe('vim.ui_attach', function()
             {
               'Error executing vim.schedule lua callback: [string ""]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string ""]:2: in function <[string ""]:2>',
               9,
-              7,
+              6,
             },
           },
           kind = 'lua_error',
         },
         {
-          content = { { 'Press ENTER or type command to continue', 100, 19 } },
+          content = { { 'Press ENTER or type command to continue', 100, 18 } },
           kind = 'return_prompt',
         },
       },
diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua
index 4a61e0203d..839e37f541 100644
--- a/test/functional/terminal/altscreen_spec.lua
+++ b/test/functional/terminal/altscreen_spec.lua
@@ -35,13 +35,13 @@ describe(':terminal altscreen', function()
       line6                                             |
       line7                                             |
       line8                                             |
-      {1: }                                                 |
+      ^                                                  |
       {3:-- TERMINAL --}                                    |
     ]])
     enter_altscreen()
     screen:expect([[
                                                         |*5
-      {1: }                                                 |
+      ^                                                  |
       {3:-- TERMINAL --}                                    |
     ]])
     eq(10, api.nvim_buf_line_count(0))
@@ -68,7 +68,7 @@ describe(':terminal altscreen', function()
         line6                                             |
         line7                                             |
         line8                                             |
-        {1: }                                                 |
+        ^                                                  |
         {3:-- TERMINAL --}                                    |
       ]])
       feed('gg')
@@ -103,7 +103,7 @@ describe(':terminal altscreen', function()
         line14                                            |
         line15                                            |
         line16                                            |
-        {1: }                                                 |
+        ^                                                  |
         {3:-- TERMINAL --}                                    |
       ]])
     end)
@@ -132,7 +132,7 @@ describe(':terminal altscreen', function()
       screen:expect([[
                                                           |*2
         rows: 4, cols: 50                                 |
-        {1: }                                                 |
+        ^                                                  |
         {3:-- TERMINAL --}                                    |
       ]])
     end
@@ -160,7 +160,7 @@ describe(':terminal altscreen', function()
           line5                                             |
           line6                                             |
           line7                                             |
-          line8                                             |
+          ^line8                                             |
           {3:-- TERMINAL --}                                    |
         ]])
       end)
diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua
index b550df80c3..a8e5367176 100644
--- a/test/functional/terminal/api_spec.lua
+++ b/test/functional/terminal/api_spec.lua
@@ -33,7 +33,7 @@ describe('api', function()
   it('qa! RPC request during insert-mode', function()
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*4
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -45,7 +45,7 @@ describe('api', function()
 
     -- Wait for socket creation.
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*4
       ]] .. socket_name .. [[                       |
       {3:-- TERMINAL --}                                    |
@@ -57,7 +57,7 @@ describe('api', function()
     tt.feed_data('i[tui] insert-mode')
     -- Wait for stdin to be processed.
     screen:expect([[
-      [tui] insert-mode{1: }                                |
+      [tui] insert-mode^                                 |
       {4:~                                                 }|*4
       {3:-- INSERT --}                                      |
       {3:-- TERMINAL --}                                    |
@@ -73,7 +73,7 @@ describe('api', function()
       [tui] insert-mode                                 |
       [socket 1] this is more t                         |
       han 25 columns                                    |
-      [socket 2] input{1: }                                 |
+      [socket 2] input^                                  |
       {4:~                        }                         |
       {3:-- INSERT --}                                      |
       {3:-- TERMINAL --}                                    |
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 05258a9e50..edb4c928c1 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -89,7 +89,7 @@ describe(':terminal buffer', function()
       feed('')
       screen:expect([[
         tty ready                                         |
-        {2:^ }                                                 |
+        ^                                                  |
                                                           |*5
       ]])
     end)
@@ -109,7 +109,7 @@ describe(':terminal buffer', function()
     feed('dd')
     screen:expect([[
       tty ready                                         |
-      {2:^ }                                                 |
+      ^                                                  |
                                                         |*4
       {8:E21: Cannot make changes, 'modifiable' is off}     |
     ]])
@@ -122,7 +122,7 @@ describe(':terminal buffer', function()
     screen:expect([[
       ^tty ready                                         |
       appended tty ready                                |*2
-      {2: }                                                 |
+                                                        |
                                                         |*2
       :let @a = "appended " . @a                        |
     ]])
@@ -142,7 +142,7 @@ describe(':terminal buffer', function()
     screen:expect([[
       ^tty ready                                         |
       appended tty ready                                |
-      {2: }                                                 |
+                                                        |
                                                         |*3
       :put a                                            |
     ]])
@@ -151,7 +151,7 @@ describe(':terminal buffer', function()
     screen:expect([[
       tty ready                                         |
       appended tty ready                                |*2
-      {2: }                                                 |
+                                                        |
                                                         |
       ^                                                  |
       :6put a                                           |
@@ -198,7 +198,7 @@ describe(':terminal buffer', function()
       {4:~                                                 }|
       {5:==========                                        }|
       rows: 2, cols: 50                                 |
-      {2: }                                                 |
+                                                        |
       {18:==========                                        }|
                                                         |
     ]])
@@ -234,7 +234,7 @@ describe(':terminal buffer', function()
     command('set rightleft')
     screen:expect([[
                                                ydaer ytt|
-      {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
                                                         |*4
       {3:-- TERMINAL --}                                    |
     ]])
@@ -277,7 +277,7 @@ describe(':terminal buffer', function()
     screen:expect {
       grid = [[
       tty ready                                         |
-      {2:^ }                                                 |
+      ^                                                  |
                                                         |*4
       {3:-- (terminal) --}                                  |
     ]],
@@ -288,7 +288,7 @@ describe(':terminal buffer', function()
     screen:expect {
       grid = [[
       tty ready                                         |
-      {2: }                                                 |
+                                                        |
                                                         |*4
       :let g:x = 17^                                     |
     ]],
@@ -298,7 +298,7 @@ describe(':terminal buffer', function()
     screen:expect {
       grid = [[
       tty ready                                         |
-      {1: }                                                 |
+      ^                                                  |
                                                         |*4
       {3:-- TERMINAL --}                                    |
     ]],
@@ -534,7 +534,7 @@ describe('terminal input', function()
       'while 1 | redraw | echo keytrans(getcharstr()) | endwhile',
     })
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                       0,0-1          All}|
                                                         |
@@ -594,7 +594,7 @@ describe('terminal input', function()
                                                           |
         {4:~                                                 }|*3
         {5:[No Name]                       0,0-1          All}|
-        %s{1: }{MATCH: *}|
+        %s^ {MATCH: *}|
         {3:-- TERMINAL --}                                    |
       ]]):format(key))
     end
@@ -624,7 +624,7 @@ if is_os('win') then
       > :: appended :: tty ready                        |
       > :: tty ready                                    |
       > :: appended :: tty ready                        |
-      ^> {2: }                                               |
+      ^>                                                 |
       :let @a = @a . "\n:: appended " . @a . "\n\n"     |
       ]])
       -- operator count is also taken into consideration
@@ -635,7 +635,7 @@ if is_os('win') then
       > :: appended :: tty ready                        |
       > :: tty ready                                    |
       > :: appended :: tty ready                        |
-      ^> {2: }                                               |
+      ^>                                                 |
       :let @a = @a . "\n:: appended " . @a . "\n\n"     |
       ]])
     end)
@@ -649,7 +649,7 @@ if is_os('win') then
                                                         |
       > :: tty ready                                    |
       > :: appended :: tty ready                        |
-      > {2: }                                               |
+      >                                                 |
                                                         |
       ^                                                  |
       :put a                                            |
@@ -662,7 +662,7 @@ if is_os('win') then
       > :: appended :: tty ready                        |
       > :: tty ready                                    |
       > :: appended :: tty ready                        |
-      ^> {2: }                                               |
+      ^>                                                 |
       :6put a                                           |
       ]])
     end)
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index 4d25fe62ad..d4d08fb24c 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -1,13 +1,12 @@
 local t = require('test.testutil')
 local n = require('test.functional.testnvim')()
-local Screen = require('test.functional.ui.screen')
 local tt = require('test.functional.testterm')
 
 local feed, clear = n.feed, n.clear
 local testprg, command = n.testprg, n.command
 local eq, eval = t.eq, n.eval
 local matches = t.matches
-local poke_eventloop = n.poke_eventloop
+local call = n.call
 local hide_cursor = tt.hide_cursor
 local show_cursor = tt.show_cursor
 local is_os = t.is_os
@@ -25,7 +24,7 @@ describe(':terminal cursor', function()
     tt.feed_data('testing cursor')
     screen:expect([[
       tty ready                                         |
-      testing cursor{1: }                                   |
+      testing cursor^                                    |
                                                         |*4
       {3:-- TERMINAL --}                                    |
     ]])
@@ -35,7 +34,7 @@ describe(':terminal cursor', function()
     feed('')
     screen:expect([[
       tty ready                                         |
-      {2:^ }                                                 |
+      ^                                                  |
                                                         |*5
     ]])
   end)
@@ -49,7 +48,7 @@ describe(':terminal cursor', function()
       screen:expect([[
         {7:  1 }tty ready                                     |
         {7:  2 }^rows: 6, cols: 46                             |
-        {7:  3 }{2: }                                             |
+        {7:  3 }                                              |
         {7:  4 }                                              |
         {7:  5 }                                              |
         {7:  6 }                                              |
@@ -61,7 +60,7 @@ describe(':terminal cursor', function()
       screen:expect([[
         {7:  1 }tty ready                                     |
         {7:  2 }^rows: 6, cols: 46                             |
-        {7:  3 }{2: }                                             |
+        {7:  3 }                                              |
         {7:  4 }                                              |
         {7:  5 }                                              |
         {7:  6 }                                              |
@@ -72,7 +71,7 @@ describe(':terminal cursor', function()
       screen:expect([[
         {7:  1 }tty ready                                     |
         {7:  2 }rows: 6, cols: 46                             |
-        {7:  3 }{1: }                                             |
+        {7:  3 }^                                              |
         {7:  4 }                                              |
         {7:  5 }                                              |
         {7:  6 }                                              |
@@ -82,8 +81,8 @@ describe(':terminal cursor', function()
   end)
 
   describe('when invisible', function()
-    it('is not highlighted and is detached from screen cursor', function()
-      skip(is_os('win'))
+    it('is not highlighted', function()
+      skip(is_os('win'), '#31587')
       hide_cursor()
       screen:expect([[
         tty ready                                         |
@@ -93,58 +92,238 @@ describe(':terminal cursor', function()
       show_cursor()
       screen:expect([[
         tty ready                                         |
-        {1: }                                                 |
+        ^                                                  |
                                                           |*4
         {3:-- TERMINAL --}                                    |
       ]])
       -- same for when the terminal is unfocused
       feed('')
       hide_cursor()
+      screen:expect({
+        grid = [[
+        tty ready                                         |
+        ^                                                  |
+                                                          |*5
+      ]],
+        unchanged = true,
+      })
+      show_cursor()
+      screen:expect({
+        grid = [[
+        tty ready                                         |
+        ^                                                  |
+                                                          |*5
+      ]],
+        unchanged = true,
+      })
+    end)
+
+    it('becomes visible when exiting Terminal mode', function()
+      skip(is_os('win'), '#31587')
+      hide_cursor()
+      screen:expect([[
+        tty ready                                         |
+                                                          |*5
+        {3:-- TERMINAL --}                                    |
+      ]])
+      feed('')
       screen:expect([[
         tty ready                                         |
         ^                                                  |
                                                           |*5
       ]])
-      show_cursor()
+      feed('i')
       screen:expect([[
         tty ready                                         |
-        {2:^ }                                                 |
                                                           |*5
+        {3:-- TERMINAL --}                                    |
       ]])
     end)
   end)
-end)
 
-describe('cursor with customized highlighting', function()
-  local screen
+  it('can be modified by application #3681', function()
+    skip(is_os('win'), '#31587')
+    local idx ---@type number
+    for i, v in ipairs(screen._mode_info) do
+      if v.name == 'terminal' then
+        idx = i
+      end
+    end
+    assert(idx)
 
-  before_each(function()
-    clear()
-    command('highlight TermCursor ctermfg=45 ctermbg=46 cterm=NONE')
-    command('highlight TermCursorNC ctermfg=55 ctermbg=56 cterm=NONE')
-    screen = Screen.new(50, 7, { rgb = false })
-    screen:set_default_attr_ids({
-      [1] = { foreground = 45, background = 46 },
-      [2] = { foreground = 55, background = 56 },
-      [3] = { bold = true },
+    local states = {
+      [1] = { blink = true, shape = 'block' },
+      [2] = { blink = false, shape = 'block' },
+      [3] = { blink = true, shape = 'horizontal' },
+      [4] = { blink = false, shape = 'horizontal' },
+      [5] = { blink = true, shape = 'vertical' },
+      [6] = { blink = false, shape = 'vertical' },
+    }
+
+    for k, v in pairs(states) do
+      tt.feed_csi(('%d q'):format(k))
+      screen:expect({
+        grid = [[
+        tty ready                                         |
+        ^                                                  |
+                                                          |*4
+        {3:-- TERMINAL --}                                    |
+      ]],
+        condition = function()
+          if v.blink then
+            eq(500, screen._mode_info[idx].blinkon)
+            eq(500, screen._mode_info[idx].blinkoff)
+          else
+            eq(0, screen._mode_info[idx].blinkon)
+            eq(0, screen._mode_info[idx].blinkoff)
+          end
+          eq(v.shape, screen._mode_info[idx].cursor_shape)
+        end,
+      })
+    end
+
+    feed([[]])
+
+    screen:expect([[
+      tty ready                                         |
+      ^                                                  |
+                                                        |*5
+    ]])
+
+    -- Cursor returns to default on TermLeave
+    eq(500, screen._mode_info[idx].blinkon)
+    eq(500, screen._mode_info[idx].blinkoff)
+    eq('block', screen._mode_info[idx].cursor_shape)
+  end)
+
+  it('can be modified per terminal', function()
+    skip(is_os('win'), '#31587')
+    local idx ---@type number
+    for i, v in ipairs(screen._mode_info) do
+      if v.name == 'terminal' then
+        idx = i
+      end
+    end
+    assert(idx)
+
+    -- Set cursor to vertical bar with blink
+    tt.feed_csi('5 q')
+    screen:expect({
+      grid = [[
+      tty ready                                         |
+      ^                                                  |
+                                                        |*4
+      {3:-- TERMINAL --}                                    |
+    ]],
+      condition = function()
+        eq(500, screen._mode_info[idx].blinkon)
+        eq(500, screen._mode_info[idx].blinkoff)
+        eq('vertical', screen._mode_info[idx].cursor_shape)
+      end,
+    })
+
+    tt.hide_cursor()
+    screen:expect({
+      grid = [[
+      tty ready                                         |
+                                                        |
+                                                        |*4
+      {3:-- TERMINAL --}                                    |
+    ]],
+      condition = function()
+        eq(500, screen._mode_info[idx].blinkon)
+        eq(500, screen._mode_info[idx].blinkoff)
+        eq('vertical', screen._mode_info[idx].cursor_shape)
+      end,
     })
-    command('call termopen(["' .. testprg('tty-test') .. '"])')
+
+    -- Exit terminal mode to reset terminal cursor settings to default and
+    -- create a new terminal window
+    feed([[]])
+    command('set statusline=~~~')
+    command('new')
+    call('termopen', { testprg('tty-test') })
     feed('i')
-    poke_eventloop()
+    screen:expect({
+      grid = [[
+      tty ready                                         |
+      ^                                                  |
+      {17:~~~                                               }|
+      rows: 2, cols: 50                                 |
+                                                        |
+      {18:~~~                                               }|
+      {3:-- TERMINAL --}                                    |
+    ]],
+      condition = function()
+        -- New terminal, cursor resets to defaults
+        eq(500, screen._mode_info[idx].blinkon)
+        eq(500, screen._mode_info[idx].blinkoff)
+        eq('block', screen._mode_info[idx].cursor_shape)
+      end,
+    })
+
+    -- Set cursor to underline, no blink
+    tt.feed_csi('4 q')
+    screen:expect({
+      grid = [[
+      tty ready                                         |
+      ^                                                  |
+      {17:~~~                                               }|
+      rows: 2, cols: 50                                 |
+                                                        |
+      {18:~~~                                               }|
+      {3:-- TERMINAL --}                                    |
+    ]],
+      condition = function()
+        eq(0, screen._mode_info[idx].blinkon)
+        eq(0, screen._mode_info[idx].blinkoff)
+        eq('horizontal', screen._mode_info[idx].cursor_shape)
+      end,
+    })
+
+    -- Switch back to first terminal, cursor should still be hidden
+    command('wincmd p')
+    screen:expect({
+      grid = [[
+      tty ready                                         |
+                                                        |
+      {18:~~~                                               }|
+      rows: 2, cols: 50                                 |
+                                                        |
+      {17:~~~                                               }|
+      {3:-- TERMINAL --}                                    |
+    ]],
+      condition = function()
+        eq(500, screen._mode_info[idx].blinkon)
+        eq(500, screen._mode_info[idx].blinkoff)
+        eq('vertical', screen._mode_info[idx].cursor_shape)
+      end,
+    })
   end)
 
-  it('overrides the default highlighting', function()
+  it('can be positioned arbitrarily', function()
+    clear()
+    screen = tt.setup_child_nvim({
+      '-u',
+      'NONE',
+      '-i',
+      'NONE',
+      '--cmd',
+      n.nvim_set .. ' noshowmode',
+    })
     screen:expect([[
-      tty ready                                         |
-      {1: }                                                 |
-                                                        |*4
+      ^                                                  |
+      ~                                                 |*4
+                                                        |
       {3:-- TERMINAL --}                                    |
     ]])
-    feed('')
+
+    feed('i')
     screen:expect([[
-      tty ready                                         |
-      {2:^ }                                                 |
-                                                        |*5
+              ^                                          |
+      ~                                                 |*4
+                                                        |
+      {3:-- TERMINAL --}                                    |
     ]])
   end)
 end)
@@ -183,7 +362,7 @@ describe('buffer cursor position is correct in terminal without number column',
     screen:expect([[
                                                                             |*4
       Entering Ex mode.  Type "visual" to go to Normal mode.                |
-      :{2:^ }                                                                    |
+      :^                                                                     |
       {3:-- TERMINAL --}                                                        |
     ]])
   end
@@ -200,7 +379,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :aaaaaaaa{2:^ }                                                            |
+        :aaaaaaaa^                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 9 }, eval('nvim_win_get_cursor(0)'))
@@ -208,7 +387,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :aaaaaaa^a{4: }                                                            |
+        :aaaaaaa^a                                                             |
                                                                               |
       ]])
       eq({ 6, 8 }, eval('nvim_win_get_cursor(0)'))
@@ -219,7 +398,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :aaaaaa{2:^a}a                                                             |
+        :aaaaaa^aa                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 7 }, eval('nvim_win_get_cursor(0)'))
@@ -227,7 +406,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :aaaaa^a{4:a}a                                                             |
+        :aaaaa^aaa                                                             |
                                                                               |
       ]])
       eq({ 6, 6 }, eval('nvim_win_get_cursor(0)'))
@@ -238,7 +417,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :a{2:^a}aaaaaa                                                             |
+        :a^aaaaaaa                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 2 }, eval('nvim_win_get_cursor(0)'))
@@ -246,7 +425,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :^a{4:a}aaaaaa                                                             |
+        :^aaaaaaaa                                                             |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -263,7 +442,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µµµµµµµµ{2:^ }                                                            |
+        :µµµµµµµµ^                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 17 }, eval('nvim_win_get_cursor(0)'))
@@ -271,7 +450,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µµµµµµµ^µ{4: }                                                            |
+        :µµµµµµµ^µ                                                             |
                                                                               |
       ]])
       eq({ 6, 15 }, eval('nvim_win_get_cursor(0)'))
@@ -282,7 +461,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µµµµµµ{2:^µ}µ                                                             |
+        :µµµµµµ^µµ                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 13 }, eval('nvim_win_get_cursor(0)'))
@@ -290,7 +469,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µµµµµ^µ{4:µ}µ                                                             |
+        :µµµµµ^µµµ                                                             |
                                                                               |
       ]])
       eq({ 6, 11 }, eval('nvim_win_get_cursor(0)'))
@@ -301,7 +480,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µ{2:^µ}µµµµµµ                                                             |
+        :µ^µµµµµµµ                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 3 }, eval('nvim_win_get_cursor(0)'))
@@ -309,7 +488,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :^µ{4:µ}µµµµµµ                                                             |
+        :^µµµµµµµµ                                                             |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -326,7 +505,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{2:^ }                                                            |
+        :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 33 }, eval('nvim_win_get_cursor(0)'))
@@ -334,7 +513,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{4: }                                                            |
+        :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳                                                             |
                                                                               |
       ]])
       eq({ 6, 29 }, eval('nvim_win_get_cursor(0)'))
@@ -346,7 +525,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µ̳µ̳µ̳µ̳µ̳µ̳{2:^µ̳}µ̳                                                             |
+        :µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 25 }, eval('nvim_win_get_cursor(0)'))
@@ -354,7 +533,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µ̳µ̳µ̳µ̳µ̳^µ̳{4:µ̳}µ̳                                                             |
+        :µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳                                                             |
                                                                               |
       ]])
       eq({ 6, 21 }, eval('nvim_win_get_cursor(0)'))
@@ -366,7 +545,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :µ̳{2:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳                                                             |
+        :µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳                                                             |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 5 }, eval('nvim_win_get_cursor(0)'))
@@ -374,7 +553,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :^µ̳{4:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳                                                             |
+        :^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳                                                             |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -391,7 +570,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :哦哦哦哦哦哦哦哦{2:^ }                                                    |
+        :哦哦哦哦哦哦哦哦^                                                     |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 25 }, eval('nvim_win_get_cursor(0)'))
@@ -399,7 +578,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :哦哦哦哦哦哦哦^哦{4: }                                                    |
+        :哦哦哦哦哦哦哦^哦                                                     |
                                                                               |
       ]])
       eq({ 6, 22 }, eval('nvim_win_get_cursor(0)'))
@@ -410,7 +589,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :哦哦哦哦哦哦{2:^哦}哦                                                     |
+        :哦哦哦哦哦哦^哦哦                                                     |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 19 }, eval('nvim_win_get_cursor(0)'))
@@ -418,7 +597,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :哦哦哦哦哦^哦{4:哦}哦                                                     |
+        :哦哦哦哦哦^哦哦哦                                                     |
                                                                               |
       ]])
       eq({ 6, 16 }, eval('nvim_win_get_cursor(0)'))
@@ -429,7 +608,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :哦{2:^哦}哦哦哦哦哦哦                                                     |
+        :哦^哦哦哦哦哦哦哦                                                     |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 4 }, eval('nvim_win_get_cursor(0)'))
@@ -437,7 +616,7 @@ describe('buffer cursor position is correct in terminal without number column',
       screen:expect([[
                                                                               |*4
         Entering Ex mode.  Type "visual" to go to Normal mode.                |
-        :^哦{4:哦}哦哦哦哦哦哦                                                     |
+        :^哦哦哦哦哦哦哦哦                                                     |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -450,7 +629,7 @@ describe('buffer cursor position is correct in terminal without number column',
     screen:expect([[
                                                                             |*4
       Entering Ex mode.  Type "visual" to go to Normal mode.                |
-      :aaaaaaaa    {2:^ }                                                        |
+      :aaaaaaaa    ^                                                         |
       {3:-- TERMINAL --}                                                        |
     ]])
     matches('^:aaaaaaaa    [ ]*$', eval('nvim_get_current_line()'))
@@ -459,7 +638,7 @@ describe('buffer cursor position is correct in terminal without number column',
     screen:expect([[
                                                                             |*4
       Entering Ex mode.  Type "visual" to go to Normal mode.                |
-      :aaaaaaaa   ^ {4: }                                                        |
+      :aaaaaaaa   ^                                                          |
                                                                             |
     ]])
     eq({ 6, 12 }, eval('nvim_win_get_cursor(0)'))
@@ -504,7 +683,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
       {7:  3 }                                                                  |
       {7:  4 }                                                                  |
       {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-      {7:  6 }:{2:^ }                                                                |
+      {7:  6 }:^                                                                 |
       {3:-- TERMINAL --}                                                        |
     ]])
   end
@@ -527,7 +706,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:aaaaaaaa{2:^ }                                                        |
+        {7:  6 }:aaaaaaaa^                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 9 }, eval('nvim_win_get_cursor(0)'))
@@ -538,7 +717,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:aaaaaaa^a{4: }                                                        |
+        {7:  6 }:aaaaaaa^a                                                         |
                                                                               |
       ]])
       eq({ 6, 8 }, eval('nvim_win_get_cursor(0)'))
@@ -552,7 +731,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:aaaaaa{2:^a}a                                                         |
+        {7:  6 }:aaaaaa^aa                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 7 }, eval('nvim_win_get_cursor(0)'))
@@ -563,7 +742,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:aaaaa^a{4:a}a                                                         |
+        {7:  6 }:aaaaa^aaa                                                         |
                                                                               |
       ]])
       eq({ 6, 6 }, eval('nvim_win_get_cursor(0)'))
@@ -577,7 +756,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:a{2:^a}aaaaaa                                                         |
+        {7:  6 }:a^aaaaaaa                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 2 }, eval('nvim_win_get_cursor(0)'))
@@ -588,7 +767,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:^a{4:a}aaaaaa                                                         |
+        {7:  6 }:^aaaaaaaa                                                         |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -608,7 +787,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µµµµµµµµ{2:^ }                                                        |
+        {7:  6 }:µµµµµµµµ^                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 17 }, eval('nvim_win_get_cursor(0)'))
@@ -619,7 +798,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µµµµµµµ^µ{4: }                                                        |
+        {7:  6 }:µµµµµµµ^µ                                                         |
                                                                               |
       ]])
       eq({ 6, 15 }, eval('nvim_win_get_cursor(0)'))
@@ -633,7 +812,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µµµµµµ{2:^µ}µ                                                         |
+        {7:  6 }:µµµµµµ^µµ                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 13 }, eval('nvim_win_get_cursor(0)'))
@@ -644,7 +823,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µµµµµ^µ{4:µ}µ                                                         |
+        {7:  6 }:µµµµµ^µµµ                                                         |
                                                                               |
       ]])
       eq({ 6, 11 }, eval('nvim_win_get_cursor(0)'))
@@ -658,7 +837,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µ{2:^µ}µµµµµµ                                                         |
+        {7:  6 }:µ^µµµµµµµ                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 3 }, eval('nvim_win_get_cursor(0)'))
@@ -669,7 +848,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:^µ{4:µ}µµµµµµ                                                         |
+        {7:  6 }:^µµµµµµµµ                                                         |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -689,7 +868,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{2:^ }                                                        |
+        {7:  6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 33 }, eval('nvim_win_get_cursor(0)'))
@@ -700,7 +879,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{4: }                                                        |
+        {7:  6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳                                                         |
                                                                               |
       ]])
       eq({ 6, 29 }, eval('nvim_win_get_cursor(0)'))
@@ -715,7 +894,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µ̳µ̳µ̳µ̳µ̳µ̳{2:^µ̳}µ̳                                                         |
+        {7:  6 }:µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 25 }, eval('nvim_win_get_cursor(0)'))
@@ -726,7 +905,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µ̳µ̳µ̳µ̳µ̳^µ̳{4:µ̳}µ̳                                                         |
+        {7:  6 }:µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳                                                         |
                                                                               |
       ]])
       eq({ 6, 21 }, eval('nvim_win_get_cursor(0)'))
@@ -741,7 +920,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:µ̳{2:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳                                                         |
+        {7:  6 }:µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳                                                         |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 5 }, eval('nvim_win_get_cursor(0)'))
@@ -752,7 +931,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:^µ̳{4:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳                                                         |
+        {7:  6 }:^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳                                                         |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -772,7 +951,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:哦哦哦哦哦哦哦哦{2:^ }                                                |
+        {7:  6 }:哦哦哦哦哦哦哦哦^                                                 |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 25 }, eval('nvim_win_get_cursor(0)'))
@@ -783,7 +962,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:哦哦哦哦哦哦哦^哦{4: }                                                |
+        {7:  6 }:哦哦哦哦哦哦哦^哦                                                 |
                                                                               |
       ]])
       eq({ 6, 22 }, eval('nvim_win_get_cursor(0)'))
@@ -797,7 +976,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:哦哦哦哦哦哦{2:^哦}哦                                                 |
+        {7:  6 }:哦哦哦哦哦哦^哦哦                                                 |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 19 }, eval('nvim_win_get_cursor(0)'))
@@ -808,7 +987,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:哦哦哦哦哦^哦{4:哦}哦                                                 |
+        {7:  6 }:哦哦哦哦哦^哦哦哦                                                 |
                                                                               |
       ]])
       eq({ 6, 16 }, eval('nvim_win_get_cursor(0)'))
@@ -822,7 +1001,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:哦{2:^哦}哦哦哦哦哦哦                                                 |
+        {7:  6 }:哦^哦哦哦哦哦哦哦                                                 |
         {3:-- TERMINAL --}                                                        |
       ]])
       eq({ 6, 4 }, eval('nvim_win_get_cursor(0)'))
@@ -833,7 +1012,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
         {7:  3 }                                                                  |
         {7:  4 }                                                                  |
         {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-        {7:  6 }:^哦{4:哦}哦哦哦哦哦哦                                                 |
+        {7:  6 }:^哦哦哦哦哦哦哦哦                                                 |
                                                                               |
       ]])
       eq({ 6, 1 }, eval('nvim_win_get_cursor(0)'))
@@ -849,7 +1028,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
       {7:  3 }                                                                  |
       {7:  4 }                                                                  |
       {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-      {7:  6 }:aaaaaaaa    {2:^ }                                                    |
+      {7:  6 }:aaaaaaaa    ^                                                     |
       {3:-- TERMINAL --}                                                        |
     ]])
     matches('^:aaaaaaaa    [ ]*$', eval('nvim_get_current_line()'))
@@ -861,7 +1040,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
       {7:  3 }                                                                  |
       {7:  4 }                                                                  |
       {7:  5 }Entering Ex mode.  Type "visual" to go to Normal mode.            |
-      {7:  6 }:aaaaaaaa   ^ {4: }                                                    |
+      {7:  6 }:aaaaaaaa   ^                                                      |
                                                                             |
     ]])
     eq({ 6, 12 }, eval('nvim_win_get_cursor(0)'))
diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua
index 7822a27b93..5ed3c72b72 100644
--- a/test/functional/terminal/highlight_spec.lua
+++ b/test/functional/terminal/highlight_spec.lua
@@ -37,7 +37,7 @@ describe(':terminal highlight', function()
     feed('i')
     screen:expect([[
       tty ready                                         |
-      {10: }                                                 |
+      ^                                                  |
                                                         |*4
       {5:-- TERMINAL --}                                    |
     ]])
@@ -61,7 +61,7 @@ describe(':terminal highlight', function()
         skip(is_os('win'))
         screen:expect(sub([[
           tty ready                                         |
-          {NUM:text}text{10: }                                         |
+          {NUM:text}text^                                          |
                                                             |*4
           {5:-- TERMINAL --}                                    |
         ]]))
@@ -84,7 +84,7 @@ describe(':terminal highlight', function()
           line6                                             |
           line7                                             |
           line8                                             |
-          {10: }                                                 |
+          ^                                                  |
           {5:-- TERMINAL --}                                    |
         ]])
         feed('gg')
@@ -195,7 +195,7 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi
   local screen = Screen.new(50, 7)
   screen:set_default_attr_ids({
     [1] = { background = Screen.colors.Grey90 }, -- CursorLine, CursorColumn
-    [2] = { reverse = true }, -- TermCursor
+    [2] = { reverse = true },
     [3] = { bold = true }, -- ModeMsg
     [4] = { background = Screen.colors.Grey90, reverse = true },
     [5] = { background = Screen.colors.Red },
@@ -234,7 +234,7 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi
     foobar foobar foobar foobar foobar foobar foobar f|
     oobar foobar foobar foobar foobar foobar foobar fo|
     obar foobar foobar foobar foobar foobar foobar foo|
-    bar foobar{2: }                                       |
+    bar foobar^                                        |
     {3:-- TERMINAL --}                                    |
   ]])
   -- Leaving terminal mode restores old values.
@@ -248,46 +248,60 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi
     {1:bar fooba^r                                        }|
                                                       |
   ]])
-  -- CursorLine and CursorColumn are combined with TermCursorNC.
-  command('highlight TermCursorNC gui=reverse')
+
+  -- Skip the rest of these tests on Windows #31587
+  if is_os('win') then
+    return
+  end
+
+  -- CursorLine and CursorColumn are combined with terminal colors.
+  tt.set_reverse()
+  tt.feed_data(' foobar')
+  tt.clear_attrs()
   screen:expect([[
     tty ready{1: }                                        |
      foobar f{1:o}obar foobar foobar foobar foobar foobar |
     foobar fo{1:o}bar foobar foobar foobar foobar foobar f|
     oobar foo{1:b}ar foobar foobar foobar foobar foobar fo|
     obar foob{1:a}r foobar foobar foobar foobar foobar foo|
-    {1:bar fooba^r}{4: }{1:                                       }|
+    {1:bar fooba^r}{4: foobar}{1:                                 }|
                                                       |
   ]])
-  feed('2gg11|')
+  feed('2gg15|')
   screen:expect([[
-    tty ready {1: }                                       |
-    {1: foobar fo^obar foobar foobar foobar foobar foobar }|
-    foobar foo{1:b}ar foobar foobar foobar foobar foobar f|
-    oobar foob{1:a}r foobar foobar foobar foobar foobar fo|
-    obar fooba{1:r} foobar foobar foobar foobar foobar foo|
-    bar foobar{4: }                                       |
+    tty ready     {1: }                                   |
+    {1: foobar foobar^ foobar foobar foobar foobar foobar }|
+    foobar foobar {1:f}oobar foobar foobar foobar foobar f|
+    oobar foobar f{1:o}obar foobar foobar foobar foobar fo|
+    obar foobar fo{1:o}bar foobar foobar foobar foobar foo|
+    bar foobar{2: foo}{4:b}{2:ar}                                 |
                                                       |
   ]])
-  -- TermCursorNC has higher precedence.
-  command('highlight TermCursorNC gui=NONE guibg=Red')
+
+  -- Set bg color to red
+  tt.feed_csi('48;2;255:0:0m')
+  tt.feed_data(' foobar')
+  tt.clear_attrs()
+  feed('2gg20|')
+
+  -- Terminal color has higher precedence
   screen:expect([[
-    tty ready {1: }                                       |
-    {1: foobar fo^obar foobar foobar foobar foobar foobar }|
-    foobar foo{1:b}ar foobar foobar foobar foobar foobar f|
-    oobar foob{1:a}r foobar foobar foobar foobar foobar fo|
-    obar fooba{1:r} foobar foobar foobar foobar foobar foo|
-    bar foobar{5: }                                       |
+    tty ready          {1: }                              |
+    {1: foobar foobar foob^ar foobar foobar foobar foobar }|
+    foobar foobar fooba{1:r} foobar foobar foobar foobar f|
+    oobar foobar foobar{1: }foobar foobar foobar foobar fo|
+    obar foobar foobar {1:f}oobar foobar foobar foobar foo|
+    bar foobar{2: foobar}{5: foobar}                          |
                                                       |
   ]])
   feed('G$')
   screen:expect([[
-    tty ready{1: }                                        |
-     foobar f{1:o}obar foobar foobar foobar foobar foobar |
-    foobar fo{1:o}bar foobar foobar foobar foobar foobar f|
-    oobar foo{1:b}ar foobar foobar foobar foobar foobar fo|
-    obar foob{1:a}r foobar foobar foobar foobar foobar foo|
-    {1:bar fooba^r}{5: }{1:                                       }|
+    tty ready              {1: }                          |
+     foobar foobar foobar f{1:o}obar foobar foobar foobar |
+    foobar foobar foobar fo{1:o}bar foobar foobar foobar f|
+    oobar foobar foobar foo{1:b}ar foobar foobar foobar fo|
+    obar foobar foobar foob{1:a}r foobar foobar foobar foo|
+    {1:bar foobar}{4: foobar}{5: fooba^r}{1:                          }|
                                                       |
   ]])
 end)
@@ -300,18 +314,17 @@ describe(':terminal highlight forwarding', function()
     screen = Screen.new(50, 7)
     screen:set_rgb_cterm(true)
     screen:set_default_attr_ids({
-      [1] = { { reverse = true }, { reverse = true } },
-      [2] = { { bold = true }, { bold = true } },
-      [3] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } },
-      [4] = { { foreground = tonumber('0xff8000') }, {} },
+      [1] = { { bold = true }, { bold = true } },
+      [2] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } },
+      [3] = { { foreground = tonumber('0xff8000') }, {} },
     })
     command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
     feed('i')
     screen:expect([[
       tty ready                                         |
-      {1: }                                                 |
+      ^                                                  |
                                                         |*4
-      {2:-- TERMINAL --}                                    |
+      {1:-- TERMINAL --}                                    |
     ]])
   end)
 
@@ -326,9 +339,9 @@ describe(':terminal highlight forwarding', function()
     screen:expect {
       grid = [[
       tty ready                                         |
-      {3:text}{4:color}text{1: }                                    |
+      {2:text}{3:color}text^                                     |
                                                         |*4
-      {2:-- TERMINAL --}                                    |
+      {1:-- TERMINAL --}                                    |
     ]],
     }
   end)
@@ -355,7 +368,7 @@ describe(':terminal highlight with custom palette', function()
     feed('i')
     screen:expect([[
       tty ready                                         |
-      {7: }                                                 |
+      ^                                                  |
                                                         |*4
       {9:-- TERMINAL --}                                    |
     ]])
@@ -369,7 +382,7 @@ describe(':terminal highlight with custom palette', function()
     tt.feed_data('text')
     screen:expect([[
       tty ready                                         |
-      {1:text}text{7: }                                         |
+      {1:text}text^                                          |
                                                         |*4
       {9:-- TERMINAL --}                                    |
     ]])
diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua
index 38d6b83417..5898484449 100644
--- a/test/functional/terminal/mouse_spec.lua
+++ b/test/functional/terminal/mouse_spec.lua
@@ -32,7 +32,7 @@ describe(':terminal mouse', function()
       line28                                            |
       line29                                            |
       line30                                            |
-      {1: }                                                 |
+      ^                                                  |
       {3:-- TERMINAL --}                                    |
     ]])
   end)
@@ -107,7 +107,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-          {1: }                                                 |
+          ^                                                  |
           {3:-- TERMINAL --}                                    |
         ]])
       end)
@@ -121,7 +121,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-           "#{1: }                                              |
+           "#^                                               |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<2,2>')
@@ -131,7 +131,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-             @##{1: }                                           |
+             @##^                                            |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<3,2>')
@@ -141,7 +141,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-                @$#{1: }                                        |
+                @$#^                                         |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<3,2>')
@@ -151,7 +151,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-                   #$#{1: }                                     |
+                   #$#^                                      |
           {3:-- TERMINAL --}                                    |
         ]])
       end)
@@ -165,7 +165,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-          `!!{1: }                                              |
+          `!!^                                               |
           {3:-- TERMINAL --}                                    |
         ]])
       end)
@@ -179,7 +179,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-           "#{1: }                                              |
+           "#^                                               |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<1,2>')
@@ -189,7 +189,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-             `"#{1: }                                           |
+             `"#^                                            |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<2,2>')
@@ -199,7 +199,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-                @##{1: }                                        |
+                @##^                                         |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<2,2>')
@@ -209,7 +209,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-                   `##{1: }                                     |
+                   `##^                                      |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<2,2>')
@@ -219,7 +219,7 @@ describe(':terminal mouse', function()
           line29                                            |
           line30                                            |
           mouse enabled                                     |
-                      ###{1: }                                  |
+                      ###^                                   |
           {3:-- TERMINAL --}                                    |
         ]])
       end)
@@ -237,7 +237,7 @@ describe(':terminal mouse', function()
           {7: 13 }line30                                        |
           {7: 14 }mouse enabled                                 |
           {7: 15 }rows: 6, cols: 46                             |
-          {7: 16 }{2: }                                             |
+          {7: 16 }                                              |
                                                             |
         ]])
         -- If click on the coordinate (0,1) of the region of the terminal
@@ -249,7 +249,7 @@ describe(':terminal mouse', function()
           {7: 13 }line30                                        |
           {7: 14 }mouse enabled                                 |
           {7: 15 }rows: 6, cols: 46                             |
-          {7: 16 } !"{1: }                                          |
+          {7: 16 } !"^                                           |
           {3:-- TERMINAL --}                                    |
         ]])
       end)
@@ -261,7 +261,7 @@ describe(':terminal mouse', function()
           line30                                            |
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
-          {1: }                                                 |
+          ^                                                  |
           ==========                                        |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -271,7 +271,7 @@ describe(':terminal mouse', function()
           line30                                            |
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
-          {2:^ }                                                 |
+          ^                                                  |
           ==========                                        |
                                                             |
         ]])
@@ -280,7 +280,7 @@ describe(':terminal mouse', function()
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
           rows: 4, cols: 50                                 |
-          {2:^ }                                                 |
+          ^                                                  |
           ==========                                        |
                                                             |*2
         ]])
@@ -293,7 +293,7 @@ describe(':terminal mouse', function()
           line30                  │{4:~                        }|
           mouse enabled           │{4:~                        }|
           rows: 5, cols: 24       │{4:~                        }|
-          {1: }                       │{4:~                        }|
+          ^                        │{4:~                        }|
           ==========               ==========               |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -303,7 +303,7 @@ describe(':terminal mouse', function()
           line30                  │{4:~                        }|
           mouse enabled           │{4:~                        }|
           rows: 5, cols: 24       │{4:~                        }|
-          {2:^ }                       │{4:~                        }|
+          ^                        │{4:~                        }|
           ==========               ==========               |
                                                             |
         ]])
@@ -313,7 +313,7 @@ describe(':terminal mouse', function()
           mouse enabled          │{4:~                         }|
           rows: 5, cols: 24      │{4:~                         }|
           rows: 5, cols: 23      │{4:~                         }|
-          {2:^ }                      │{4:~                         }|
+          ^                       │{4:~                         }|
           ==========              ==========                |
                                                             |
         ]])
@@ -327,7 +327,7 @@ describe(':terminal mouse', function()
           line30                                            |
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
-          {1: }                                                 |
+          ^                                                  |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<0,0>')
@@ -337,7 +337,7 @@ describe(':terminal mouse', function()
           line30                                            |
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
-          {2:^ }                                                 |
+          ^                                                  |
                                                             |
         ]])
         command('set showtabline=2 tabline=TABLINE | startinsert')
@@ -347,7 +347,7 @@ describe(':terminal mouse', function()
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
           rows: 4, cols: 50                                 |
-          {1: }                                                 |
+          ^                                                  |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<0,0>')
@@ -357,7 +357,7 @@ describe(':terminal mouse', function()
           mouse enabled                                     |
           rows: 5, cols: 50                                 |
           rows: 4, cols: 50                                 |
-          {2:^ }                                                 |
+          ^                                                  |
                                                             |
         ]])
         command('setlocal winbar= | startinsert')
@@ -367,7 +367,7 @@ describe(':terminal mouse', function()
           rows: 5, cols: 50                                 |
           rows: 4, cols: 50                                 |
           rows: 5, cols: 50                                 |
-          {1: }                                                 |
+          ^                                                  |
           {3:-- TERMINAL --}                                    |
         ]])
         feed('<0,0>')
@@ -377,7 +377,7 @@ describe(':terminal mouse', function()
           rows: 5, cols: 50                                 |
           rows: 4, cols: 50                                 |
           rows: 5, cols: 50                                 |
-          {2:^ }                                                 |
+          ^                                                  |
                                                             |
         ]])
       end)
@@ -391,7 +391,7 @@ describe(':terminal mouse', function()
           line29                   │line29                  |
           line30                   │line30                  |
           rows: 5, cols: 25        │rows: 5, cols: 25       |
-          {2:^ }                        │{2: }                       |
+          ^                         │                        |
           ==========                ==========              |
           :vsp                                              |
         ]])
@@ -401,7 +401,7 @@ describe(':terminal mouse', function()
           {4:~                        }│line30                  |
           {4:~                        }│rows: 5, cols: 25       |
           {4:~                        }│rows: 5, cols: 24       |
-          {4:~                        }│{2: }                       |
+          {4:~                        }│                        |
           ==========                ==========              |
           :enew | set number                                |
         ]])
@@ -411,7 +411,7 @@ describe(':terminal mouse', function()
           {7: 28 }line                 │line30                  |
           {7: 29 }line                 │rows: 5, cols: 25       |
           {7: 30 }line                 │rows: 5, cols: 24       |
-          {7: 31 }^                     │{2: }                       |
+          {7: 31 }^                     │                        |
           ==========                ==========              |
                                                             |
         ]])
@@ -421,7 +421,7 @@ describe(':terminal mouse', function()
           {7: 28 }line                 │line30                  |
           {7: 29 }line                 │rows: 5, cols: 25       |
           {7: 30 }line                 │rows: 5, cols: 24       |
-          {7: 31 }                     │{1: }                       |
+          {7: 31 }                     │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -434,7 +434,7 @@ describe(':terminal mouse', function()
           {7: 28 }line                 │rows: 5, cols: 25       |
           {7: 29 }line                 │rows: 5, cols: 24       |
           {7: 30 }line                 │mouse enabled           |
-          {7: 31 }                     │{1: }                       |
+          {7: 31 }                     │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -447,7 +447,7 @@ describe(':terminal mouse', function()
           {7: 22 }line                 │rows: 5, cols: 25       |
           {7: 23 }line                 │rows: 5, cols: 24       |
           {7: 24 }line                 │mouse enabled           |
-          {7: 25 }line                 │{1: }                       |
+          {7: 25 }line                 │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -457,7 +457,7 @@ describe(':terminal mouse', function()
           {7: 27 }line                 │rows: 5, cols: 25       |
           {7: 28 }line                 │rows: 5, cols: 24       |
           {7: 29 }line                 │mouse enabled           |
-          {7: 30 }line                 │{1: }                       |
+          {7: 30 }line                 │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -468,7 +468,7 @@ describe(':terminal mouse', function()
           {7: 17 }line                 │rows: 5, cols: 25       |
           {7: 18 }line                 │rows: 5, cols: 24       |
           {7: 19 }line                 │mouse enabled           |
-          {7: 20 }line                 │{1: }                       |
+          {7: 20 }line                 │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -483,7 +483,7 @@ describe(':terminal mouse', function()
           {7:  2 }linelinelinelineline │rows: 5, cols: 25       |
           {7:  3 }linelinelinelineline │rows: 5, cols: 24       |
           {7:  4 }linelinelinelineline │mouse enabled           |
-          {7:  5 }linelinelinelineline │{1: }                       |
+          {7:  5 }linelinelinelineline │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -493,7 +493,7 @@ describe(':terminal mouse', function()
           {7:  2 }nelinelineline       │rows: 5, cols: 25       |
           {7:  3 }nelinelineline       │rows: 5, cols: 24       |
           {7:  4 }nelinelineline       │mouse enabled           |
-          {7:  5 }nelinelineline       │{1: }                       |
+          {7:  5 }nelinelineline       │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -504,7 +504,7 @@ describe(':terminal mouse', function()
           {7:  2 }nelinelinelineline   │rows: 5, cols: 25       |
           {7:  3 }nelinelinelineline   │rows: 5, cols: 24       |
           {7:  4 }nelinelinelineline   │mouse enabled           |
-          {7:  5 }nelinelinelineline   │{1: }                       |
+          {7:  5 }nelinelinelineline   │^                        |
           ==========                ==========              |
           {3:-- TERMINAL --}                                    |
         ]])
@@ -517,7 +517,7 @@ describe(':terminal mouse', function()
           {7: 28 }l^ine                 │rows: 5, cols: 25       |
           {7: 29 }line                 │rows: 5, cols: 24       |
           {7: 30 }line                 │mouse enabled           |
-          {7: 31 }                     │{2: }                       |
+          {7: 31 }                     │                        |
           ==========                ==========              |
                                                             |
         ]])
@@ -531,7 +531,7 @@ describe(':terminal mouse', function()
           {7: 28 }line                 │rows: 5, cols: 25       |
           {7: 29 }line                 │rows: 5, cols: 24       |
           {7: 30 }line                 │mouse enabled           |
-          {7: 31 }^                     │{2: }                       |
+          {7: 31 }^                     │                        |
           ==========                ==========              |
                                                             |
         ]])
@@ -541,7 +541,7 @@ describe(':terminal mouse', function()
           rows: 5, cols: 24        │rows: 5, cols: 24       |
           mouse enabled            │mouse enabled           |
           rows: 5, cols: 25        │rows: 5, cols: 25       |
-          {2:^ }                        │{2: }                       |
+          ^                         │                        |
           ==========                ==========              |
           :bn                                               |
         ]])
@@ -551,7 +551,7 @@ describe(':terminal mouse', function()
           {7: 28 }line                 │mouse enabled           |
           {7: 29 }line                 │rows: 5, cols: 25       |
           {7: 30 }line                 │rows: 5, cols: 24       |
-          {7: 31 }^                     │{2: }                       |
+          {7: 31 }^                     │                        |
           ==========                ==========              |
           :bn                                               |
         ]])
diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua
index 1751db1aa9..0de7873200 100644
--- a/test/functional/terminal/scrollback_spec.lua
+++ b/test/functional/terminal/scrollback_spec.lua
@@ -39,7 +39,7 @@ describe(':terminal scrollback', function()
         line28                        |
         line29                        |
         line30                        |
-        {1: }                             |
+        ^                              |
         {3:-- TERMINAL --}                |
       ]])
     end)
@@ -67,7 +67,7 @@ describe(':terminal scrollback', function()
         line2                         |
         line3                         |
         line4                         |
-        {1: }                             |
+        ^                              |
         {3:-- TERMINAL --}                |
       ]])
     end)
@@ -84,7 +84,7 @@ describe(':terminal scrollback', function()
           line3                         |
           line4                         |
           line5                         |
-          {1: }                             |
+          ^                              |
           {3:-- TERMINAL --}                |
         ]])
         eq(7, api.nvim_buf_line_count(0))
@@ -102,7 +102,7 @@ describe(':terminal scrollback', function()
             line5                         |
             line6                         |
             line7                         |
-            line8{1: }                        |
+            line8^                         |
             {3:-- TERMINAL --}                |
           ]])
 
@@ -135,7 +135,7 @@ describe(':terminal scrollback', function()
             line5                         |
             line6                         |
             line7                         |
-            ^line8{2: }                        |
+            ^line8                         |
                                           |
           ]])
         end)
@@ -151,7 +151,7 @@ describe(':terminal scrollback', function()
           line3                       |
           line4                       |
           rows: 5, cols: 28           |
-          {2:^ }                           |
+          ^                            |
                                       |
         ]])
       end
@@ -168,7 +168,7 @@ describe(':terminal scrollback', function()
           screen:expect([[
             rows: 5, cols: 28         |
             rows: 3, cols: 26         |
-            {2:^ }                         |
+            ^                          |
                                       |
           ]])
           eq(8, api.nvim_buf_line_count(0))
@@ -201,7 +201,7 @@ describe(':terminal scrollback', function()
         screen:expect([[
           tty ready                     |
           rows: 4, cols: 30             |
-          {1: }                             |
+          ^                              |
                                         |
           {3:-- TERMINAL --}                |
         ]])
@@ -220,7 +220,7 @@ describe(':terminal scrollback', function()
           screen:expect([[
             rows: 4, cols: 30             |
             rows: 3, cols: 30             |
-            {1: }                             |
+            ^                              |
             {3:-- TERMINAL --}                |
           ]])
           eq(4, api.nvim_buf_line_count(0))
@@ -235,7 +235,7 @@ describe(':terminal scrollback', function()
           screen:expect([[
             rows: 4, cols: 30             |
             rows: 3, cols: 30             |
-            {1: }                             |
+            ^                              |
             {3:-- TERMINAL --}                |
           ]])
         end)
@@ -252,14 +252,14 @@ describe(':terminal scrollback', function()
         line2                         |
         line3                         |
         line4                         |
-        {1: }                             |
+        ^                              |
         {3:-- TERMINAL --}                |
       ]])
       screen:try_resize(screen._width, screen._height - 3)
       screen:expect([[
         line4                         |
         rows: 3, cols: 30             |
-        {1: }                             |
+        ^                              |
         {3:-- TERMINAL --}                |
       ]])
       eq(7, api.nvim_buf_line_count(0))
@@ -278,7 +278,7 @@ describe(':terminal scrollback', function()
           line4                         |
           rows: 3, cols: 30             |
           rows: 4, cols: 30             |
-          {1: }                             |
+          ^                              |
           {3:-- TERMINAL --}                |
         ]])
       end
@@ -300,7 +300,7 @@ describe(':terminal scrollback', function()
             rows: 3, cols: 30             |
             rows: 4, cols: 30             |
             rows: 7, cols: 30             |
-            {1: }                             |
+            ^                              |
             {3:-- TERMINAL --}                |
           ]])
           eq(9, api.nvim_buf_line_count(0))
@@ -337,7 +337,7 @@ describe(':terminal scrollback', function()
               rows: 4, cols: 30             |
               rows: 7, cols: 30             |
               rows: 11, cols: 30            |
-              {1: }                             |
+              ^                              |
                                             |
               {3:-- TERMINAL --}                |
             ]])
@@ -362,7 +362,7 @@ describe(':terminal prints more lines than the screen height and exits', functio
       line8                         |
       line9                         |
                                     |
-      [Process exited 0]{2: }           |
+      [Process exited 0]^            |
       {5:-- TERMINAL --}                |
     ]])
     feed('')
@@ -454,7 +454,7 @@ describe("'scrollback' option", function()
         39: line                      |
         40: line                      |
                                       |
-        ${1: }                            |
+        $^                             |
         {3:-- TERMINAL --}                |
       ]],
       }
@@ -493,7 +493,7 @@ describe("'scrollback' option", function()
         line28                        |
         line29                        |
         line30                        |
-        {1: }                             |
+        ^                              |
         {3:-- TERMINAL --}                |
       ]])
     local term_height = 6 -- Actual terminal screen height, not the scrollback
@@ -634,7 +634,7 @@ describe('pending scrollback line handling', function()
     screen:expect [[
       hi                            |*4
                                     |
-      [Process exited 0]{2: }           |
+      [Process exited 0]^            |
       {3:-- TERMINAL --}                |
     ]]
     assert_alive()
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index ded0cd99d3..832bacb534 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -59,7 +59,7 @@ describe('TUI', function()
       'colorscheme vim',
     })
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -105,7 +105,7 @@ describe('TUI', function()
     -- Need buffer rows to provoke the behavior.
     feed_data(':edit test/functional/fixtures/bigfile.txt\n')
     screen:expect([[
-      {1:0}000;;Cc;0;BN;;;;;N;NULL;;;;             |
+      ^0000;;Cc;0;BN;;;;;N;NULL;;;;             |
       0001;;Cc;0;BN;;;;;N;START OF HEADING;;;; |
       0002;;Cc;0;BN;;;;;N;START OF TEXT;;;;    |
       0003;;Cc;0;BN;;;;;N;END OF TEXT;;;;      |
@@ -155,7 +155,7 @@ describe('TUI', function()
       {8:FAIL 0}                                            |
       {8:FAIL 1}                                            |
       {8:FAIL 2}                                            |
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -170,7 +170,7 @@ describe('TUI', function()
       {8:FAIL 1}                                            |
       {8:FAIL 2}                                            |
                                                         |*2
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -186,7 +186,7 @@ describe('TUI', function()
       {8:FAIL 3}                                            |
       {8:FAIL 4}                                            |
       {8:FAIL 5}                                            |
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -199,7 +199,7 @@ describe('TUI', function()
       {8:FAIL 3}                                            |
       {8:FAIL 4}                                            |
       {8:FAIL 5}                                            |
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -210,7 +210,7 @@ describe('TUI', function()
       {8:FAIL 3}                                            |
       {8:FAIL 4}                                            |
       {8:FAIL 5}                                            |
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -221,7 +221,7 @@ describe('TUI', function()
       :call ManyErr()                                   |
       {8:Error detected while processing function ManyErr:} |
       {11:line    2:}                                        |
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -237,7 +237,7 @@ describe('TUI', function()
       {8:FAIL 2}                                            |
       {8:FAIL 3}                                            |
       {8:FAIL 4}                                            |
-      {10:-- More --}{1: }                                       |
+      {10:-- More --}^                                        |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -245,7 +245,7 @@ describe('TUI', function()
     feed_data('\003')
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*6
       {5:[No Name]                                         }|
                                                         |
@@ -259,7 +259,7 @@ describe('TUI', function()
     screen:expect([[
       abc                                               |
       test1                                             |
-      test2{1: }                                            |
+      test2^                                             |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -269,7 +269,7 @@ describe('TUI', function()
     screen:expect([[
       abc                                               |
       test1                                             |
-      test{1:2}                                             |
+      test^2                                             |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
                                                         |
@@ -287,14 +287,14 @@ describe('TUI', function()
       alt-j                                             |
       alt-k                                             |
       alt-l                                             |
-      {1: }                                                 |
+      ^                                                  |
       {5:[No Name] [+]                                     }|
                                                         |
       {3:-- TERMINAL --}                                    |
     ]])
     feed_data('gg')
     screen:expect([[
-      {1:a}lt-d                                             |
+      ^alt-d                                             |
       alt-f                                             |
       alt-g                                             |
       alt-h                                             |
@@ -309,7 +309,7 @@ describe('TUI', function()
     -- ALT+j inserts "ê". Nvim does not (#3982).
     feed_data('i\022\027j')
     screen:expect([[
-      {1: }                                            |
+      ^                                             |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -329,7 +329,7 @@ describe('TUI', function()
     )
     feed_data('\027[27u;')
     screen:expect([[
-      ESCsemicolo{1:n}                                      |
+      ESCsemicolo^n                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
                                                         |
@@ -343,7 +343,7 @@ describe('TUI', function()
   it('interprets  as  #17198', function()
     feed_data('i\022\027\000')
     screen:expect([[
-      {1: }                                      |
+      ^                                       |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -357,7 +357,7 @@ describe('TUI', function()
     feed_data('\022\022') -- ctrl+v
     feed_data('\022\013') -- ctrl+m
     screen:expect([[
-      {6:^G^V^M}{1: }                                           |
+      {6:^G^V^M}^                                            |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -376,7 +376,7 @@ describe('TUI', function()
       {}
     )
     screen:expect([[
-      {11:  1 }{1:0}----1----2----3----4│{11:  1 }0----1----2----3----|
+      {11:  1 }^0----1----2----3----4│{11:  1 }0----1----2----3----|
       {11:  2 }0----1----2----3----4│{11:  2 }0----1----2----3----|
       {11:  3 }0----1----2----3----4│{11:  3 }0----1----2----3----|
       {11:  4 }0----1----2----3----4│{11:  4 }0----1----2----3----|
@@ -391,7 +391,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'down', '', 0, 0, 7)
     end
     screen:expect([[
-      {11:  2 }{1:0}----1----2----3----4│{11:  1 }0----1----2----3----|
+      {11:  2 }^0----1----2----3----4│{11:  1 }0----1----2----3----|
       {11:  3 }0----1----2----3----4│{11:  2 }0----1----2----3----|
       {11:  4 }0----1----2----3----4│{11:  3 }0----1----2----3----|
       {11:  5 }0----1----2----3----4│{11:  4 }0----1----2----3----|
@@ -406,7 +406,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'down', '', 0, 0, 47)
     end
     screen:expect([[
-      {11:  2 }{1:0}----1----2----3----4│{11:  2 }0----1----2----3----|
+      {11:  2 }^0----1----2----3----4│{11:  2 }0----1----2----3----|
       {11:  3 }0----1----2----3----4│{11:  3 }0----1----2----3----|
       {11:  4 }0----1----2----3----4│{11:  4 }0----1----2----3----|
       {11:  5 }0----1----2----3----4│{11:  5 }0----1----2----3----|
@@ -421,7 +421,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'right', '', 0, 0, 7)
     end
     screen:expect([[
-      {11:  2 }{1:-}---1----2----3----4-│{11:  2 }0----1----2----3----|
+      {11:  2 }^----1----2----3----4-│{11:  2 }0----1----2----3----|
       {11:  3 }----1----2----3----4-│{11:  3 }0----1----2----3----|
       {11:  4 }----1----2----3----4-│{11:  4 }0----1----2----3----|
       {11:  5 }----1----2----3----4-│{11:  5 }0----1----2----3----|
@@ -436,7 +436,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'right', '', 0, 0, 47)
     end
     screen:expect([[
-      {11:  2 }{1:-}---1----2----3----4-│{11:  2 }----1----2----3----4|
+      {11:  2 }^----1----2----3----4-│{11:  2 }----1----2----3----4|
       {11:  3 }----1----2----3----4-│{11:  3 }----1----2----3----4|
       {11:  4 }----1----2----3----4-│{11:  4 }----1----2----3----4|
       {11:  5 }----1----2----3----4-│{11:  5 }----1----2----3----4|
@@ -451,7 +451,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 7)
     end
     screen:expect([[
-      {11:  5 }{1:-}---1----2----3----4-│{11:  2 }----1----2----3----4|
+      {11:  5 }^----1----2----3----4-│{11:  2 }----1----2----3----4|
       {11:  6 }----1----2----3----4-│{11:  3 }----1----2----3----4|
       {11:  7 }----1----2----3----4-│{11:  4 }----1----2----3----4|
       {11:  8 }----1----2----3----4-│{11:  5 }----1----2----3----4|
@@ -466,7 +466,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 47)
     end
     screen:expect([[
-      {11:  5 }{1:-}---1----2----3----4-│{11:  5 }----1----2----3----4|
+      {11:  5 }^----1----2----3----4-│{11:  5 }----1----2----3----4|
       {11:  6 }----1----2----3----4-│{11:  6 }----1----2----3----4|
       {11:  7 }----1----2----3----4-│{11:  7 }----1----2----3----4|
       {11:  8 }----1----2----3----4-│{11:  8 }----1----2----3----4|
@@ -481,7 +481,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 7)
     end
     screen:expect([[
-      {11:  5 }{1:-}---6----7----8----9 │{11:  5 }----1----2----3----4|
+      {11:  5 }^----6----7----8----9 │{11:  5 }----1----2----3----4|
       {11:  6 }----6----7----8----9 │{11:  6 }----1----2----3----4|
       {11:  7 }----6----7----8----9 │{11:  7 }----1----2----3----4|
       {11:  8 }----6----7----8----9 │{11:  8 }----1----2----3----4|
@@ -496,7 +496,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 47)
     end
     screen:expect([[
-      {11:  5 }{1:-}---6----7----8----9 │{11:  5 }5----6----7----8----|
+      {11:  5 }^----6----7----8----9 │{11:  5 }5----6----7----8----|
       {11:  6 }----6----7----8----9 │{11:  6 }5----6----7----8----|
       {11:  7 }----6----7----8----9 │{11:  7 }5----6----7----8----|
       {11:  8 }----6----7----8----9 │{11:  8 }5----6----7----8----|
@@ -512,7 +512,7 @@ describe('TUI', function()
     end
     screen:expect([[
       {11:  4 }----6----7----8----9 │{11:  5 }5----6----7----8----|
-      {11:  5 }{1:-}---6----7----8----9 │{11:  6 }5----6----7----8----|
+      {11:  5 }^----6----7----8----9 │{11:  6 }5----6----7----8----|
       {11:  6 }----6----7----8----9 │{11:  7 }5----6----7----8----|
       {11:  7 }----6----7----8----9 │{11:  8 }5----6----7----8----|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
@@ -527,7 +527,7 @@ describe('TUI', function()
     end
     screen:expect([[
       {11:  4 }----6----7----8----9 │{11:  4 }5----6----7----8----|
-      {11:  5 }{1:-}---6----7----8----9 │{11:  5 }5----6----7----8----|
+      {11:  5 }^----6----7----8----9 │{11:  5 }5----6----7----8----|
       {11:  6 }----6----7----8----9 │{11:  6 }5----6----7----8----|
       {11:  7 }----6----7----8----9 │{11:  7 }5----6----7----8----|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
@@ -542,7 +542,7 @@ describe('TUI', function()
     end
     screen:expect([[
       {11:  4 }5----6----7----8----9│{11:  4 }5----6----7----8----|
-      {11:  5 }5{1:-}---6----7----8----9│{11:  5 }5----6----7----8----|
+      {11:  5 }5^----6----7----8----9│{11:  5 }5----6----7----8----|
       {11:  6 }5----6----7----8----9│{11:  6 }5----6----7----8----|
       {11:  7 }5----6----7----8----9│{11:  7 }5----6----7----8----|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
@@ -557,7 +557,7 @@ describe('TUI', function()
     end
     screen:expect([[
       {11:  4 }5----6----7----8----9│{11:  4 }-5----6----7----8---|
-      {11:  5 }5{1:-}---6----7----8----9│{11:  5 }-5----6----7----8---|
+      {11:  5 }5^----6----7----8----9│{11:  5 }-5----6----7----8---|
       {11:  6 }5----6----7----8----9│{11:  6 }-5----6----7----8---|
       {11:  7 }5----6----7----8----9│{11:  7 }-5----6----7----8---|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
@@ -574,7 +574,7 @@ describe('TUI', function()
       {11:  1 }5----6----7----8----9│{11:  4 }-5----6----7----8---|
       {11:  2 }5----6----7----8----9│{11:  5 }-5----6----7----8---|
       {11:  3 }5----6----7----8----9│{11:  6 }-5----6----7----8---|
-      {11:  4 }5{1:-}---6----7----8----9│{11:  7 }-5----6----7----8---|
+      {11:  4 }5^----6----7----8----9│{11:  7 }-5----6----7----8---|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -589,7 +589,7 @@ describe('TUI', function()
       {11:  1 }5----6----7----8----9│{11:  1 }-5----6----7----8---|
       {11:  2 }5----6----7----8----9│{11:  2 }-5----6----7----8---|
       {11:  3 }5----6----7----8----9│{11:  3 }-5----6----7----8---|
-      {11:  4 }5{1:-}---6----7----8----9│{11:  4 }-5----6----7----8---|
+      {11:  4 }5^----6----7----8----9│{11:  4 }-5----6----7----8---|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -604,7 +604,7 @@ describe('TUI', function()
       {11:  1 }0----1----2----3----4│{11:  1 }-5----6----7----8---|
       {11:  2 }0----1----2----3----4│{11:  2 }-5----6----7----8---|
       {11:  3 }0----1----2----3----4│{11:  3 }-5----6----7----8---|
-      {11:  4 }0----1----2----3----{1:4}│{11:  4 }-5----6----7----8---|
+      {11:  4 }0----1----2----3----^4│{11:  4 }-5----6----7----8---|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -619,7 +619,7 @@ describe('TUI', function()
       {11:  1 }0----1----2----3----4│{11:  1 }0----1----2----3----|
       {11:  2 }0----1----2----3----4│{11:  2 }0----1----2----3----|
       {11:  3 }0----1----2----3----4│{11:  3 }0----1----2----3----|
-      {11:  4 }0----1----2----3----{1:4}│{11:  4 }0----1----2----3----|
+      {11:  4 }0----1----2----3----^4│{11:  4 }0----1----2----3----|
       {5:[No Name] [+]             }{1:[No Name] [+]           }|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -660,7 +660,7 @@ describe('TUI', function()
       api.nvim_input_mouse('right', 'press', '', 0, 0, 4)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~  }{13: foo }{4:                                          }|
       {4:~  }{13: bar }{4:                                          }|
       {4:~  }{13: baz }{4:                                          }|
@@ -680,7 +680,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'up', '', 0, 0, 4)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~  }{14: foo }{4:                                          }|
       {4:~  }{13: bar }{4:                                          }|
       {4:~  }{13: baz }{4:                                          }|
@@ -694,7 +694,7 @@ describe('TUI', function()
       api.nvim_input_mouse('move', '', '', 0, 3, 6)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~  }{13: foo }{4:                                          }|
       {4:~  }{13: bar }{4:                                          }|
       {4:~  }{14: baz }{4:                                          }|
@@ -708,7 +708,7 @@ describe('TUI', function()
       api.nvim_input_mouse('wheel', 'down', '', 0, 3, 6)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~  }{13: foo }{4:                                          }|
       {4:~  }{14: bar }{4:                                          }|
       {4:~  }{13: baz }{4:                                          }|
@@ -722,7 +722,7 @@ describe('TUI', function()
       api.nvim_input_mouse('left', 'press', '', 0, 2, 6)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       :let g:menustr = 'bar'                            |
@@ -740,7 +740,7 @@ describe('TUI', function()
       api.nvim_input_mouse('right', 'press', '', 0, 2, 44)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~                                                 }|*2
       {4:~                                          }{13: foo }{4:  }|
       {5:[No Name] [+]                              }{13: bar }{5:  }|
@@ -753,7 +753,7 @@ describe('TUI', function()
       api.nvim_input_mouse('right', 'drag', '', 0, 5, 47)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~                                                 }|*2
       {4:~                                          }{13: foo }{4:  }|
       {5:[No Name] [+]                              }{13: bar }{5:  }|
@@ -766,7 +766,7 @@ describe('TUI', function()
       api.nvim_input_mouse('right', 'release', '', 0, 5, 47)
     end
     screen:expect([[
-      {1:p}opup menu test                                   |
+      ^popup menu test                                   |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       :let g:menustr = 'baz'                            |
@@ -805,7 +805,7 @@ describe('TUI', function()
     feed_data(fn.nr2char(57415)) -- KP_EQUAL
     screen:expect([[
       0123456789./*-+                                   |
-      ={1: }                                                |
+      =^                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -814,7 +814,7 @@ describe('TUI', function()
     feed_data(fn.nr2char(57417)) -- KP_LEFT
     screen:expect([[
       0123456789./*-+                                   |
-      {1:=}                                                 |
+      ^=                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -823,7 +823,7 @@ describe('TUI', function()
     feed_data(fn.nr2char(57418)) -- KP_RIGHT
     screen:expect([[
       0123456789./*-+                                   |
-      ={1: }                                                |
+      =^                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -831,7 +831,7 @@ describe('TUI', function()
     ]])
     feed_data(fn.nr2char(57419)) -- KP_UP
     screen:expect([[
-      0{1:1}23456789./*-+                                   |
+      0^123456789./*-+                                   |
       =                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -841,7 +841,7 @@ describe('TUI', function()
     feed_data(fn.nr2char(57420)) -- KP_DOWN
     screen:expect([[
       0123456789./*-+                                   |
-      ={1: }                                                |
+      =^                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -850,7 +850,7 @@ describe('TUI', function()
     feed_data(fn.nr2char(57425)) -- KP_INSERT
     screen:expect([[
       0123456789./*-+                                   |
-      ={1: }                                                |
+      =^                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- REPLACE --}                                     |
@@ -859,7 +859,7 @@ describe('TUI', function()
     feed_data('\027[27u') -- ESC
     screen:expect([[
       0123456789./*-+                                   |
-      {1:=}                                                 |
+      ^=                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
                                                         |
@@ -867,7 +867,7 @@ describe('TUI', function()
     ]])
     feed_data('\027[57417;5u') -- CTRL + KP_LEFT
     screen:expect([[
-      {1:0}123456789./*-+                                   |
+      ^0123456789./*-+                                   |
       =                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -876,7 +876,7 @@ describe('TUI', function()
     ]])
     feed_data('\027[57418;2u') -- SHIFT + KP_RIGHT
     screen:expect([[
-      0123456789{1:.}/*-+                                   |
+      0123456789^./*-+                                   |
       =                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -885,7 +885,7 @@ describe('TUI', function()
     ]])
     feed_data(fn.nr2char(57426)) -- KP_DELETE
     screen:expect([[
-      0123456789{1:/}*-+                                    |
+      0123456789^/*-+                                    |
       =                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -894,7 +894,7 @@ describe('TUI', function()
     ]])
     feed_data(fn.nr2char(57423)) -- KP_HOME
     screen:expect([[
-      {1:0}123456789/*-+                                    |
+      ^0123456789/*-+                                    |
       =                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -903,7 +903,7 @@ describe('TUI', function()
     ]])
     feed_data(fn.nr2char(57424)) -- KP_END
     screen:expect([[
-      0123456789/*-{1:+}                                    |
+      0123456789/*-^+                                    |
       =                                                 |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -921,7 +921,7 @@ describe('TUI', function()
     )
     screen:expect([[
       {12: + [No Name]  + [No Name] }{3: [No Name] }{1:            }{12:X}|
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*2
       {5:[No Name]                                         }|
                                                         |
@@ -930,7 +930,7 @@ describe('TUI', function()
     feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP
     screen:expect([[
       {12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1:            }{12:X}|
-      0123456789/*-{1:+}                                    |
+      0123456789/*-^+                                    |
       =                                                 |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
@@ -940,7 +940,7 @@ describe('TUI', function()
     feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN
     screen:expect([[
       {12: + [No Name]  + [No Name] }{3: [No Name] }{1:            }{12:X}|
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*2
       {5:[No Name]                                         }|
                                                         |
@@ -961,7 +961,7 @@ describe('TUI', function()
     feed_data('\022\027[57379;48u') -- Shift + Alt + Ctrl + Super + Meta + F16
     screen:expect([[
                         |
-      {1: }           |
+      ^            |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -973,7 +973,7 @@ describe('TUI', function()
     -- "bracketed paste"
     feed_data('i""\027i\027[200~')
     screen:expect([[
-      "{1:"}                                                |
+      "^"                                                |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -982,7 +982,7 @@ describe('TUI', function()
     feed_data('pasted from terminal')
     expect_child_buf_lines({ '"pasted from terminal"' })
     screen:expect([[
-      "pasted from terminal{1:"}                            |
+      "pasted from terminal^"                            |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -994,7 +994,7 @@ describe('TUI', function()
     feed_data('\027[27u') -- ESC: go to Normal mode.
     wait_for_mode('n')
     screen:expect([[
-      "pasted from termina{1:l}"                            |
+      "pasted from termina^l"                            |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1005,7 +1005,7 @@ describe('TUI', function()
     expect_child_buf_lines({ '"pasted from terminapasted from terminalpasted from terminall"' })
     screen:expect([[
       "pasted from terminapasted from terminalpasted fro|
-      m termina{1:l}l"                                      |
+      m termina^ll"                                      |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1027,7 +1027,7 @@ describe('TUI', function()
       this is line 1                                    |
       this is line 2                                    |
       line 3 is here                                    |
-      {1: }                                                 |
+      ^                                                  |
       {5:[No Name] [+]                                     }|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -1037,7 +1037,7 @@ describe('TUI', function()
     screen:expect([[
       this{16: is line 1}                                    |
       {16:this is line 2}                                    |
-      {16:line}{1: }3 is here                                    |
+      {16:line}^ 3 is here                                    |
                                                         |
       {5:[No Name] [+]                                     }|
       {3:-- SELECT --}                                      |
@@ -1047,7 +1047,7 @@ describe('TUI', function()
     feed_data('just paste it™')
     feed_data('\027[201~')
     screen:expect([[
-      thisjust paste it{1:™}3 is here                       |
+      thisjust paste it^™3 is here                       |
                                                         |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
@@ -1084,7 +1084,7 @@ describe('TUI', function()
     feed_data('i')
     screen:expect([[
       tty ready                                         |
-      {1: }                                                 |
+      ^                                                  |
                                                         |*2
       {19:^^^^^^^                                           }|
       {3:-- TERMINAL --}                                    |*2
@@ -1094,7 +1094,7 @@ describe('TUI', function()
     feed_data('\027[201~')
     screen:expect([[
       tty ready                                         |
-      hallo{1: }                                            |
+      hallo^                                             |
                                                         |*2
       {19:^^^^^^^                                           }|
       {3:-- TERMINAL --}                                    |*2
@@ -1111,7 +1111,7 @@ describe('TUI', function()
     local expected_grid1 = [[
       line 1                                            |
       ESC:{6:^[} / CR:                                      |
-      {1:x}                                                 |
+      ^x                                                 |
       {4:~                                                 }|
       {5:[No Name] [+]                   3,1            All}|
                                                         |
@@ -1126,7 +1126,7 @@ describe('TUI', function()
       ESC:{6:^[} / CR:                                      |
       xline 1                                           |
       ESC:{6:^[} / CR:                                      |
-      {1:x}                                                 |
+      ^x                                                 |
       {5:[No Name] [+]                   5,1            Bot}|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -1165,7 +1165,7 @@ describe('TUI', function()
                                                         |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
-      :"{1:"}                                               |
+      :"^"                                               |
       {3:-- TERMINAL --}                                    |
     ]])
     -- "bracketed paste"
@@ -1179,7 +1179,7 @@ describe('TUI', function()
                                                         |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
-      :"line 1{1:"}                                         |
+      :"line 1^"                                         |
       {3:-- TERMINAL --}                                    |
     ]])
     -- Dot-repeat/redo.
@@ -1188,7 +1188,7 @@ describe('TUI', function()
     feed_data('.')
     screen:expect([[
       foo                                               |*2
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1235,7 +1235,7 @@ describe('TUI', function()
     wait_for_mode('n')
     screen:expect([[
       foo                                               |
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1249,7 +1249,7 @@ describe('TUI', function()
       {5:                                                  }|
       {8:paste: Error executing lua: [string ""]:4: f}|
       {8:ake fail}                                          |
-      {10:Press ENTER or type command to continue}{1: }          |
+      {10:Press ENTER or type command to continue}^           |
       {3:-- TERMINAL --}                                    |
     ]])
     -- Remaining chunks are discarded after vim.paste() failure.
@@ -1265,7 +1265,7 @@ describe('TUI', function()
     feed_data('.')
     screen:expect([[
       foo                                               |*2
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1275,7 +1275,7 @@ describe('TUI', function()
     feed_data('ityped input...\027[27u')
     screen:expect([[
       foo                                               |*2
-      typed input..{1:.}                                    |
+      typed input..^.                                    |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1288,7 +1288,7 @@ describe('TUI', function()
       foo                                               |
       typed input...line A                              |
       line B                                            |
-      {1: }                                                 |
+      ^                                                  |
       {5:[No Name] [+]                                     }|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -1352,7 +1352,7 @@ describe('TUI', function()
       {5:                                                  }|
       {8:paste: Error executing lua: Vim:E21: Cannot make c}|
       {8:hanges, 'modifiable' is off}                       |
-      {10:Press ENTER or type command to continue}{1: }          |
+      {10:Press ENTER or type command to continue}^           |
       {3:-- TERMINAL --}                                    |
     ]])
     feed_data('\n') --  to dismiss hit-enter prompt
@@ -1361,7 +1361,7 @@ describe('TUI', function()
     screen:expect([[
       success 1                                         |
       success 2                                         |
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|
       {5:[No Name] [+]                                     }|
                                                         |
@@ -1380,7 +1380,7 @@ describe('TUI', function()
     expected = expected .. ' end'
     screen:expect([[
       zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz|
-      zzzzzzzzzzzzzz end{1: }                               |
+      zzzzzzzzzzzzzz end^                                |
       {4:~                                                 }|*2
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -1399,7 +1399,7 @@ describe('TUI', function()
                                                         |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
-      :<{1: }                                               |
+      :<^                                                |
       {3:-- TERMINAL --}                                    |
     ]])
   end)
@@ -1420,7 +1420,7 @@ describe('TUI', function()
       item 2997                                         |
       item 2998                                         |
       item 2999                                         |
-      item 3000 end{1: }                                    |
+      item 3000 end^                                     |
       {5:[No Name] [+]                   3000,14        Bot}|
       {3:-- INSERT --}                                      |
       {3:-- TERMINAL --}                                    |
@@ -1433,7 +1433,7 @@ describe('TUI', function()
       item 2997                                         |
       item 2998                                         |
       item 2999                                         |
-      item 3000 en{1:d}d                                    |
+      item 3000 en^dd                                    |
       {5:[No Name] [+]                   5999,13        Bot}|
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -1457,7 +1457,7 @@ describe('TUI', function()
                                                         |
       pasted from terminal (1)                          |
       {6:^[}[200~                                           |
-      {1: }                                                 |
+      ^                                                  |
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
       {3:-- TERMINAL --}                                    |
@@ -1472,7 +1472,7 @@ describe('TUI', function()
     -- Send "stop paste" sequence.
     feed_data('\027[201~')
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       {3:-- INSERT --}                                      |
@@ -1487,7 +1487,7 @@ describe('TUI', function()
     feed_data('\027[2')
     feed_data('00~pasted from terminal\027[201~')
     screen:expect([[
-      pasted from terminal{1: }                             |
+      pasted from terminal^                              |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -1502,7 +1502,7 @@ describe('TUI', function()
     feed_data('\027[200~pasted from terminal\027[20')
     feed_data('1~')
     screen:expect([[
-      pasted from terminal{1: }                             |
+      pasted from terminal^                              |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -1524,7 +1524,7 @@ describe('TUI', function()
     wait_for_mode('i')
     feed_data('\027[200~pasted') -- phase 1
     screen:expect([[
-      pasted{1: }                                           |
+      pasted^                                            |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -1532,7 +1532,7 @@ describe('TUI', function()
     ]])
     feed_data(' from terminal') -- phase 2
     screen:expect([[
-      pasted from terminal{1: }                             |
+      pasted from terminal^                              |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -1568,7 +1568,7 @@ describe('TUI', function()
     feed_data('\028\014') -- crtl+\ ctrl+N
     feed_data(':set termguicolors?\n')
     screen:expect([[
-      {5:^}{6:G}                                                |
+      {6:^^G}                                                |
       {2:~                                                 }|*3
       {3:[No Name] [+]                                     }|
       notermguicolors                                   |
@@ -1577,7 +1577,7 @@ describe('TUI', function()
 
     feed_data(':set termguicolors\n')
     screen:expect([[
-      {7:^}{8:G}                                                |
+      {8:^^G}                                                |
       {9:~}{10:                                                 }|*3
       {3:[No Name] [+]                                     }|
       :set termguicolors                                |
@@ -1586,7 +1586,7 @@ describe('TUI', function()
 
     feed_data(':set notermguicolors\n')
     screen:expect([[
-      {5:^}{6:G}                                                |
+      {6:^^G}                                                |
       {2:~                                                 }|*3
       {3:[No Name] [+]                                     }|
       :set notermguicolors                              |
@@ -1634,7 +1634,7 @@ describe('TUI', function()
     child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test'))
     screen:expect {
       grid = [[
-      {1:t}ty ready                                         |
+      ^tty ready                                         |
                                                         |*3
       {2:^^^^^^^                                           }|
                                                         |
@@ -1646,7 +1646,7 @@ describe('TUI', function()
     )
     screen:expect {
       grid = [[
-      {1:t}ty ready                                         |
+      ^tty ready                                         |
       {4:text}{5:color}text                                     |
                                                         |*2
       {2:^^^^^^^                                           }|
@@ -1658,7 +1658,7 @@ describe('TUI', function()
     feed_data(':set notermguicolors\n')
     screen:expect {
       grid = [[
-      {1:t}ty ready                                         |
+      ^tty ready                                         |
       {4:text}colortext                                     |
                                                         |*2
       {6:^^^^^^^}{7:                                           }|
@@ -1681,7 +1681,7 @@ describe('TUI', function()
     child_session:request('nvim_set_hl', 0, 'Visual', { undercurl = true })
     feed_data('ifoobar\027V')
     screen:expect([[
-      {5:fooba}{1:r}                                            |
+      {5:fooba}^r                                            |
       {4:~                                                 }|*3
       {2:[No Name] [+]                                     }|
       {3:-- VISUAL LINE --}                                 |
@@ -1689,7 +1689,7 @@ describe('TUI', function()
     ]])
     child_session:request('nvim_set_hl', 0, 'Visual', { underdouble = true })
     screen:expect([[
-      {6:fooba}{1:r}                                            |
+      {6:fooba}^r                                            |
       {4:~                                                 }|*3
       {2:[No Name] [+]                                     }|
       {3:-- VISUAL LINE --}                                 |
@@ -1777,7 +1777,7 @@ describe('TUI', function()
     child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 })
     feed_data('gg')
     local singlewidth_screen = [[
-      {13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}|
+      {12:^℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}|
       {12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12:                                       }|
       ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃|
       ℃℃℃℃℃℃℃℃℃℃{4:$}                                       |
@@ -1788,7 +1788,7 @@ describe('TUI', function()
     -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width,
     -- the second cell of "℃" is a space and the attributes of the "℃" are applied to it.
     local doublewidth_screen = [[
-      {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
+      {12:^℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
       {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
       {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12:                             }|
       ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ {4:@@@@}|
@@ -1821,7 +1821,7 @@ describe('TUI', function()
     child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 })
     feed_data('gg')
     local singlewidth_screen = [[
-      {13:✓}{12:✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}|
+      {12:^✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}|
       {12:✓✓✓✓✓✓✓✓✓✓}{15:$}{12:                                       }|
       ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓|
       ✓✓✓✓✓✓✓✓✓✓{4:$}                                       |
@@ -1832,7 +1832,7 @@ describe('TUI', function()
     -- When grid assumes "✓" to be double-width but host terminal assumes it to be single-width,
     -- the second cell of "✓" is a space and the attributes of the "✓" are applied to it.
     local doublewidth_screen = [[
-      {13:✓}{12: ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }|
+      {12:^✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }|
       {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }|
       {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }{15:$}{12:                             }|
       ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ {4:@@@@}|
@@ -1870,7 +1870,7 @@ describe('TUI', function()
     -- Close the :intro message and redraw the lines.
     feed_data('\n')
     screen:expect([[
-      {13:Ꝩ}{12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}|
+      {12:^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}|
       {12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}|*310
       {12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ℃ }|
       b                                                                     |
@@ -1912,7 +1912,7 @@ describe('TUI', function()
     -- Close the :intro message and redraw the lines.
     feed_data('\n')
     screen:expect([[
-      {1:Ꝩ}ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ|
+      ^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ|
       ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ|*325
       {3:-- TERMINAL --}                                                     |
     ]])
@@ -1925,7 +1925,7 @@ describe('TUI', function()
     feed_data ':set visualbell\n'
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       :set visualbell                                   |
@@ -1939,7 +1939,7 @@ describe('TUI', function()
     feed_data 'i'
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       {3:-- INSERT --}                                      |
@@ -1954,7 +1954,7 @@ describe('TUI', function()
       grid = [[
       Vim: Caught deadly signal 'SIGTERM'               |
                                                         |*2
-      [Process exited 1]{1: }                               |
+      [Process exited 1]^                                |
                                                         |*2
       {3:-- TERMINAL --}                                    |
     ]],
@@ -1981,7 +1981,7 @@ describe('TUI', function()
       [5] = { bold = true },
     })
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {2:~}{3:                                                 }|*3
       {4:[No Name]                                         }|
                                                         |
@@ -1989,7 +1989,7 @@ describe('TUI', function()
     ]])
     feed_data('i')
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {2:~}{3:                                                 }|*3
       {4:[No Name]                                         }|
       {5:-- INSERT --}                                      |
@@ -2000,7 +2000,7 @@ describe('TUI', function()
   it('redraws on SIGWINCH even if terminal size is unchanged #23411', function()
     child_session:request('nvim_echo', { { 'foo' } }, false, {})
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       foo                                               |
@@ -2008,7 +2008,7 @@ describe('TUI', function()
     ]])
     exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigwinch')]])
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -2031,7 +2031,7 @@ describe('TUI', function()
     ]])
     feed_data('\003')
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       Type  :qa  and press  to exit Nvim         |
@@ -2046,7 +2046,7 @@ describe('TUI', function()
       {1:foo}                                               |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
-      /foo{1: }                                             |
+      /foo^                                              |
       {3:-- TERMINAL --}                                    |
     ]])
     screen:sleep(10)
@@ -2055,7 +2055,7 @@ describe('TUI', function()
       foo                                               |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
-      /foob{1: }                                            |
+      /foob^                                             |
       {3:-- TERMINAL --}                                    |
     ]])
     screen:sleep(10)
@@ -2064,7 +2064,7 @@ describe('TUI', function()
       foo                                               |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
-      /fooba{1: }                                           |
+      /fooba^                                            |
       {3:-- TERMINAL --}                                    |
     ]])
   end)
@@ -2194,7 +2194,7 @@ describe('TUI', function()
     local screen = tt.setup_child_nvim({ '--clean', '-l', script_file })
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       ~                                                 |*3
       [No Name]                       0,0-1          All|
                                                         |
@@ -2207,7 +2207,7 @@ describe('TUI', function()
       Xargv0nvim                                        |
       --embed                                           |
       --clean                                           |
-      {1:X}argv0nvim                                        |
+      ^Xargv0nvim                                        |
       [No Name] [+]                   5,1            Bot|
       4 more lines                                      |
       {3:-- TERMINAL --}                                    |
@@ -2233,7 +2233,7 @@ describe('TUI', function()
       :q                                                |
       abc                                               |
                                                         |
-      [Process exited 0]{1: }                               |
+      [Process exited 0]^                                |
                                                         |
       {3:-- TERMINAL --}                                    |
     ]])
@@ -2254,7 +2254,7 @@ describe('TUI', function()
     })
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -2264,7 +2264,7 @@ describe('TUI', function()
 
     command([[call chansend(b:terminal_job_id, "\")]])
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                    |
@@ -2287,7 +2287,7 @@ describe('TUI', function()
     }, { cols = 80 })
     screen:expect {
       grid = [[
-      {1:1}st line                                                                        |
+      ^1st line                                                                        |
                                                                                       |*2
       2nd line                                                                        |
       {5:[No Name] [+]                                                 1,1            All}|
@@ -2300,7 +2300,7 @@ describe('TUI', function()
       grid = [[
       1st line                                                                        |
                                                                                       |
-      {1: }                                                                               |
+      ^                                                                                |
       2nd line                                                                        |
       {5:[No Name] [+]                                                 1,161          All}|
                                                                                       |
@@ -2320,7 +2320,7 @@ describe('TUI', function()
     }, { extra_rows = 10, cols = 66 })
     screen:expect {
       grid = [[
-                                                                        |
+            ^                                                            |
             aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12
             aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@|
       [No Name] [+]                                   1,0-1          Top|
@@ -2339,7 +2339,7 @@ describe('TUI', function()
     -- 500-cell limit, so the buffer is flushed after these spaces.
     screen:expect {
       grid = [[
-                                                                        |
+            ^                                                            |
             aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12
             aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@|
       [No Name] [+]                                   1,0-1          Top|
@@ -2366,7 +2366,7 @@ describe('TUI', function()
 
     screen:expect {
       grid = [[
-      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       ~                                                 |*3
       [No Name] [+]                   1,1            All|
                                                         |
@@ -2378,7 +2378,8 @@ describe('TUI', function()
     feed_data(':set columns=12\n')
     screen:expect {
       grid = [[
-      aaaaaaaaaaaa                                      |*4
+      ^aaaaaaaaaaaa                                      |
+      aaaaaaaaaaaa                                      |*3
       < [+] 1,1                                         |
                                                         |
       -- TERMINAL --                                    |
@@ -2416,7 +2417,7 @@ describe('TUI UIEnter/UILeave', function()
     })
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -2426,7 +2427,7 @@ describe('TUI UIEnter/UILeave', function()
     feed_data(':echo g:evs\n')
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       ['VimEnter', 'UIEnter']                           |
@@ -2457,7 +2458,7 @@ describe('TUI FocusGained/FocusLost', function()
     })
 
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -2479,7 +2480,7 @@ describe('TUI FocusGained/FocusLost', function()
     retry(2, 3 * screen.timeout, function()
       feed_data('\027[I')
       screen:expect([[
-        {1: }                                                 |
+        ^                                                  |
         {4:~                                                 }|*3
         {5:[No Name]                                         }|
         gained                                            |
@@ -2488,7 +2489,7 @@ describe('TUI FocusGained/FocusLost', function()
 
       feed_data('\027[O')
       screen:expect([[
-        {1: }                                                 |
+        ^                                                  |
         {4:~                                                 }|*3
         {5:[No Name]                                         }|
         lost                                              |
@@ -2502,7 +2503,7 @@ describe('TUI FocusGained/FocusLost', function()
     feed_data('i')
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
       :set noshowmode                                   |
@@ -2512,7 +2513,7 @@ describe('TUI FocusGained/FocusLost', function()
     retry(2, 3 * screen.timeout, function()
       feed_data('\027[I')
       screen:expect([[
-        {1: }                                                 |
+        ^                                                  |
         {4:~                                                 }|*3
         {5:[No Name]                                         }|
         gained                                            |
@@ -2520,7 +2521,7 @@ describe('TUI FocusGained/FocusLost', function()
       ]])
       feed_data('\027[O')
       screen:expect([[
-        {1: }                                                 |
+        ^                                                  |
         {4:~                                                 }|*3
         {5:[No Name]                                         }|
         lost                                              |
@@ -2538,7 +2539,7 @@ describe('TUI FocusGained/FocusLost', function()
                                                         |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
-      :{1: }                                                |
+      :^                                                 |
       {3:-- TERMINAL --}                                    |
     ]])
     feed_data('\027[O')
@@ -2547,7 +2548,7 @@ describe('TUI FocusGained/FocusLost', function()
                                                         |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
-      :{1: }                                                |
+      :^                                                 |
       {3:-- TERMINAL --}                                    |
     ]],
       unchanged = true,
@@ -2590,7 +2591,7 @@ describe('TUI FocusGained/FocusLost', function()
     -- Wait for terminal to be ready.
     screen:expect {
       grid = [[
-      {1:r}eady $ zia                                       |
+      ^ready $ zia                                       |
                                                         |
       [Process exited 0]                                |
                                                         |*2
@@ -2602,7 +2603,7 @@ describe('TUI FocusGained/FocusLost', function()
     feed_data('\027[I')
     screen:expect {
       grid = [[
-      {1:r}eady $ zia                                       |
+      ^ready $ zia                                       |
                                                         |
       [Process exited 0]                                |
                                                         |*2
@@ -2614,7 +2615,7 @@ describe('TUI FocusGained/FocusLost', function()
 
     feed_data('\027[O')
     screen:expect([[
-      {1:r}eady $ zia                                       |
+      ^ready $ zia                                       |
                                                         |
       [Process exited 0]                                |
                                                         |*2
@@ -2634,7 +2635,7 @@ describe('TUI FocusGained/FocusLost', function()
       msg3                                              |
       msg4                                              |
       msg5                                              |
-      {10:Press ENTER or type command to continue}{1: }          |
+      {10:Press ENTER or type command to continue}^           |
       {3:-- TERMINAL --}                                    |
     ]],
     }
@@ -2647,7 +2648,7 @@ describe('TUI FocusGained/FocusLost', function()
       msg3                                              |
       msg4                                              |
       msg5                                              |
-      {10:Press ENTER or type command to continue}{1: }          |
+      {10:Press ENTER or type command to continue}^           |
       {3:-- TERMINAL --}                                    |
     ]],
       unchanged = true,
@@ -2690,7 +2691,7 @@ describe("TUI 't_Co' (terminal colors)", function()
 
     screen:expect(string.format(
       [[
-      {1: }                                                 |
+      ^                                                  |
       %s|*4
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -2701,7 +2702,7 @@ describe("TUI 't_Co' (terminal colors)", function()
     feed_data(':echo &t_Co\n')
     screen:expect(string.format(
       [[
-      {1: }                                                 |
+      ^                                                  |
       %s|*4
       %-3s                                               |
       {3:-- TERMINAL --}                                    |
@@ -3028,7 +3029,7 @@ describe('TUI', function()
     -- Wait for TUI to start.
     feed_data('Gitext')
     screen:expect([[
-      text{1: }                                             |
+      text^                                              |
       {4:~                                                 }|*4
       {3:-- INSERT --}                                      |
       {3:-- TERMINAL --}                                    |
@@ -3045,7 +3046,7 @@ describe('TUI', function()
     nvim_tui()
 
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*4
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -3055,7 +3056,7 @@ describe('TUI', function()
 
     screen:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*4
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -3305,7 +3306,7 @@ describe('TUI bg color', function()
       'autocmd OptionSet background echo "did OptionSet, yay!"',
     })
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {3:~}                                                 |*3
       {5:[No Name]                       0,0-1          All}|
       did OptionSet, yay!                               |
@@ -3343,7 +3344,7 @@ describe('TUI as a client', function()
     feed_data('iHello, World')
     screen_server:expect {
       grid = [[
-      Hello, World{1: }                                     |
+      Hello, World^                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -3353,7 +3354,7 @@ describe('TUI as a client', function()
     feed_data('\027')
     screen_server:expect {
       grid = [[
-      Hello, Worl{1:d}                                      |
+      Hello, Worl^d                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
                                                         |
@@ -3370,7 +3371,7 @@ describe('TUI as a client', function()
 
     screen_client:expect {
       grid = [[
-      Hello, Worl{1:d}                                      |
+      Hello, Worl^d                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
                                                         |
@@ -3383,7 +3384,7 @@ describe('TUI as a client', function()
     feed_data('0:set lines=3\n')
     screen_server:expect {
       grid = [[
-      {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
       {5:[No Name] [+]                                     }|
                                                         |*4
       {3:-- TERMINAL --}                                    |
@@ -3414,7 +3415,7 @@ describe('TUI as a client', function()
 
     screen_client:expect {
       grid = [[
-      Halloj{1:!}                                           |
+      Halloj^!                                           |
       {4:~                                                 }|*4
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -3428,7 +3429,7 @@ describe('TUI as a client', function()
       grid = [[
       Vim: Caught deadly signal 'SIGTERM'               |
                                                         |*2
-      [Process exited 1]{1: }                               |
+      [Process exited 1]^                                |
                                                         |*2
       {3:-- TERMINAL --}                                    |
     ]],
@@ -3457,7 +3458,7 @@ describe('TUI as a client', function()
     screen:expect([[
       Remote ui failed to start: {MATCH:.*}|
                                                                   |
-      [Process exited 1]{1: }                                         |
+      [Process exited 1]^                                          |
                                                                   |*3
       {3:-- TERMINAL --}                                              |
     ]])
@@ -3483,7 +3484,7 @@ describe('TUI as a client', function()
     })
     screen_server:expect {
       grid = [[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*3
       {5:[No Name]                                         }|
                                                         |
@@ -3494,7 +3495,7 @@ describe('TUI as a client', function()
     feed_data('iHello, World')
     screen_server:expect {
       grid = [[
-      Hello, World{1: }                                     |
+      Hello, World^                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
       {3:-- INSERT --}                                      |
@@ -3504,7 +3505,7 @@ describe('TUI as a client', function()
     feed_data('\027')
     screen_server:expect {
       grid = [[
-      Hello, Worl{1:d}                                      |
+      Hello, Worl^d                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
                                                         |
@@ -3521,7 +3522,7 @@ describe('TUI as a client', function()
 
     screen_client:expect {
       grid = [[
-      Hello, Worl{1:d}                                      |
+      Hello, Worl^d                                      |
       {4:~                                                 }|*3
       {5:[No Name] [+]                                     }|
                                                         |
@@ -3536,7 +3537,7 @@ describe('TUI as a client', function()
     screen_server:expect {
       grid = [[
                                                         |
-      [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}|
+      [Process exited ]] .. status .. [[]^ {MATCH:%s+}|
                                                         |*4
       {3:-- TERMINAL --}                                    |
     ]],
@@ -3545,7 +3546,7 @@ describe('TUI as a client', function()
     screen_client:expect {
       grid = [[
                                                         |
-      [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}|
+      [Process exited ]] .. status .. [[]^ {MATCH:%s+}|
                                                         |*4
       {3:-- TERMINAL --}                                    |
     ]],
diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua
index fdb606e959..a65d18de70 100644
--- a/test/functional/terminal/window_spec.lua
+++ b/test/functional/terminal/window_spec.lua
@@ -62,7 +62,7 @@ describe(':terminal window', function()
       screen:expect([[
         {7:1 }tty ready                                       |
         {7:2 }rows: 6, cols: 48                               |
-        {7:3 }{1: }                                               |
+        {7:3 }^                                                |
         {7:4 }                                                |
         {7:5 }                                                |
         {7:6 }                                                |
@@ -73,7 +73,7 @@ describe(':terminal window', function()
         {7:1 }tty ready                                       |
         {7:2 }rows: 6, cols: 48                               |
         {7:3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV|
-        {7:4 }WXYZ{1: }                                           |
+        {7:4 }WXYZ^                                            |
         {7:5 }                                                |
         {7:6 }                                                |
         {3:-- TERMINAL --}                                    |
@@ -87,7 +87,7 @@ describe(':terminal window', function()
         {7:       2 }rows: 6, cols: 48                        |
         {7:       3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO|
         {7:       4 }PQRSTUVWXYZrows: 6, cols: 41             |
-        {7:       5 }{1: }                                        |
+        {7:       5 }^                                         |
         {7:       6 }                                         |
         {3:-- TERMINAL --}                                    |
       ]])
@@ -98,7 +98,7 @@ describe(':terminal window', function()
         {7:       3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO|
         {7:       4 }PQRSTUVWXYZrows: 6, cols: 41             |
         {7:       5 } abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN|
-        {7:       6 }OPQRSTUVWXYZ{1: }                            |
+        {7:       6 }OPQRSTUVWXYZ^                             |
         {3:-- TERMINAL --}                                    |
       ]])
     end)
@@ -110,7 +110,7 @@ describe(':terminal window', function()
       screen:expect([[
         {7:++1  }tty ready                                    |
         {7:++2  }rows: 6, cols: 45                            |
-        {7:++3  }{1: }                                            |
+        {7:++3  }^                                             |
         {7:++4  }                                             |
         {7:++5  }                                             |
         {7:++6  }                                             |
@@ -123,7 +123,7 @@ describe(':terminal window', function()
         {7:++6  }                                             |
         {7:++7  }                                             |
         {7:++8  }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS|
-        {7:++9  }TUVWXYZ{1: }                                     |
+        {7:++9  }TUVWXYZ^                                      |
         {3:-- TERMINAL --}                                    |
       ]])
       feed_data('\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
@@ -133,7 +133,7 @@ describe(':terminal window', function()
         {7:++ 9  }STUVWXYZ                                    |
         {7:++10  }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR|
         {7:++11  }STUVWXYZrows: 6, cols: 44                   |
-        {7:++12  }{1: }                                           |
+        {7:++12  }^                                            |
         {3:-- TERMINAL --}                                    |
       ]])
     end)
@@ -144,7 +144,7 @@ describe(':terminal window', function()
       feed([[]])
       screen:expect([[
         tty ready                                         |
-        {2:^ }                                                 |
+        ^                                                  |
                                                           |*5
       ]])
       feed(':set colorcolumn=20i')
@@ -153,7 +153,7 @@ describe(':terminal window', function()
     it('wont show the color column', function()
       screen:expect([[
         tty ready                                         |
-        {1: }                                                 |
+        ^                                                  |
                                                           |*4
         {3:-- TERMINAL --}                                    |
       ]])
@@ -170,7 +170,7 @@ describe(':terminal window', function()
         line2                                             |
         line3                                             |
         line4                                             |
-        {1: }                                                 |
+        ^                                                  |
         {3:-- TERMINAL --}                                    |
       ]])
     end)
@@ -184,7 +184,7 @@ describe(':terminal window', function()
         line2                                             |
         line3                                             |
         line4                                             |
-        {2: }                                                 |
+                                                          |
                                                           |
       ]])
     end)
@@ -206,7 +206,7 @@ describe(':terminal with multigrid', function()
       [3:--------------------------------------------------]|
     ## grid 2
       tty ready                                         |
-      {1: }                                                 |
+      ^                                                  |
                                                         |*4
     ## grid 3
       {3:-- TERMINAL --}                                    |
@@ -223,7 +223,7 @@ describe(':terminal with multigrid', function()
       ## grid 2
         tty ready           |
         rows: 10, cols: 20  |
-        {1: }                   |
+        ^                    |
                             |*7
       ## grid 3
         {3:-- TERMINAL --}                                    |
@@ -241,7 +241,7 @@ describe(':terminal with multigrid', function()
       ## grid 2
         rows: 10, cols: 20                                                    |
         rows: 3, cols: 70                                                     |
-        {1: }                                                                     |
+        ^                                                                      |
       ## grid 3
         {3:-- TERMINAL --}                                    |
       ]])
@@ -260,7 +260,7 @@ describe(':terminal with multigrid', function()
         rows: 10, cols: 20                                |
         rows: 3, cols: 70                                 |
         rows: 6, cols: 50                                 |
-        {1: }                                                 |
+        ^                                                  |
                                                           |
       ## grid 3
         {3:-- TERMINAL --}                                    |
diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua
index 272fc513af..dc22c87ca0 100644
--- a/test/functional/terminal/window_split_tab_spec.lua
+++ b/test/functional/terminal/window_split_tab_spec.lua
@@ -49,7 +49,7 @@ describe(':terminal', function()
       ==========                                        |
       tty ready                                         |
       rows: 5, cols: 50                                 |
-      {2: }                                                 |
+                                                        |
                                                         |*2
       ==========                                        |
       :2split                                           |
@@ -61,7 +61,7 @@ describe(':terminal', function()
       ==========                                        |
       ^tty ready                                         |
       rows: 5, cols: 50                                 |
-      {2: }                                                 |
+                                                        |
                                                         |*2
       ==========                                        |
       :wincmd p                                         |
@@ -77,7 +77,7 @@ describe(':terminal', function()
     command('bprevious')
     screen:expect([[
       tty ready                                         |
-      ^foo{2: }                                              |
+      ^foo                                               |
                                                         |*8
     ]])
   end)
@@ -102,7 +102,7 @@ describe(':terminal', function()
     screen:expect([[
       tty ready                                      |
       rows: 7, cols: 47                              |
-      {2: }                                              |
+                                                     |
                                                      |*3
       ^                                               |
                                                      |
@@ -112,7 +112,7 @@ describe(':terminal', function()
       tty ready                                |
       rows: 7, cols: 47                        |
       rows: 4, cols: 41                        |
-      {2:^ }                                        |
+      ^                                         |
                                                |
     ]])
   end)
diff --git a/test/functional/testterm.lua b/test/functional/testterm.lua
index 3aadcc59a7..7ae28dce69 100644
--- a/test/functional/testterm.lua
+++ b/test/functional/testterm.lua
@@ -29,6 +29,10 @@ function M.feed_termcode(data)
   M.feed_data('\027' .. data)
 end
 
+function M.feed_csi(data)
+  M.feed_termcode('[' .. data)
+end
+
 function M.make_lua_executor(session)
   return function(code, ...)
     local status, rv = session:request('nvim_exec_lua', code, { ... })
@@ -78,6 +82,9 @@ end
 function M.set_undercurl()
   M.feed_termcode('[4:3m')
 end
+function M.set_reverse()
+  M.feed_termcode('[7m')
+end
 function M.set_strikethrough()
   M.feed_termcode('[9m')
 end
@@ -108,7 +115,6 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts)
   cols = cols and cols or 50
 
   api.nvim_command('highlight TermCursor cterm=reverse')
-  api.nvim_command('highlight TermCursorNC ctermbg=11')
   api.nvim_command('highlight StatusLineTerm ctermbg=2 ctermfg=0')
   api.nvim_command('highlight StatusLineTermNC ctermbg=2 ctermfg=8')
 
@@ -154,7 +160,7 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts)
     local empty_line = (' '):rep(cols)
     local expected = {
       'tty ready' .. (' '):rep(cols - 9),
-      '{1: }' .. (' '):rep(cols - 1),
+      '^' .. (' '):rep(cols),
       empty_line,
       empty_line,
       empty_line,
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index edf826a1d9..825a90fbc8 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -190,6 +190,19 @@ describe('ui/cursor', function()
         attr_lm = {},
         short_name = 'sm',
       },
+      [18] = {
+        blinkoff = 500,
+        blinkon = 500,
+        blinkwait = 0,
+        cell_percentage = 0,
+        cursor_shape = 'block',
+        name = 'terminal',
+        hl_id = 3,
+        id_lm = 3,
+        attr = { reverse = true },
+        attr_lm = { reverse = true },
+        short_name = 't',
+      },
     }
 
     screen:expect(function()
@@ -245,17 +258,20 @@ describe('ui/cursor', function()
         end
       end
       if m.hl_id then
-        m.hl_id = 66
+        m.hl_id = 65
         m.attr = { background = Screen.colors.DarkGray }
       end
       if m.id_lm then
-        m.id_lm = 73
+        m.id_lm = 72
+        m.attr_lm = {}
       end
     end
 
     -- Assert the new expectation.
     screen:expect(function()
-      eq(expected_mode_info, screen._mode_info)
+      for i, v in ipairs(expected_mode_info) do
+        eq(v, screen._mode_info[i])
+      end
       eq(true, screen._cursor_style_enabled)
       eq('normal', screen.mode)
     end)
diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua
index f8f5ee9488..745ad70efe 100644
--- a/test/functional/ui/hlstate_spec.lua
+++ b/test/functional/ui/hlstate_spec.lua
@@ -227,7 +227,7 @@ describe('ext_hlstate detailed highlights', function()
     command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
     screen:expect([[
       ^tty ready                               |
-      {1: }                                       |
+                                              |
                                               |*5
       {7:                                        }|
     ]])
@@ -242,7 +242,7 @@ describe('ext_hlstate detailed highlights', function()
       screen:expect([[
         ^tty ready                               |
         x {5:y z}                                   |
-        {1: }                                       |
+                                                |
                                                 |*4
         {7:                                        }|
       ]])
@@ -250,7 +250,7 @@ describe('ext_hlstate detailed highlights', function()
       screen:expect([[
         ^tty ready                               |
         x {2:y }{3:z}                                   |
-        {1: }                                       |
+                                                |
                                                 |*4
         {7:                                        }|
       ]])
@@ -268,7 +268,7 @@ describe('ext_hlstate detailed highlights', function()
     else
       screen:expect([[
         ^tty ready                               |
-        x {4:y}{2: }{3:z}                                   |
+        x {2:y }{3:z}                                   |
                                                 |*5
         {7:                                        }|
       ]])
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 2c1297b768..287db81a12 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -49,7 +49,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { '\ntest\n[O]k: ', 6, 11 } },
+          content = { { '\ntest\n[O]k: ', 6, 10 } },
           kind = 'confirm',
         },
       },
@@ -77,7 +77,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { '\ntest\n[O]k: ', 6, 11 } },
+          content = { { '\ntest\n[O]k: ', 6, 10 } },
           kind = 'confirm',
         },
       },
@@ -91,7 +91,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { '\ntest\n[O]k: ', 6, 11 } },
+          content = { { '\ntest\n[O]k: ', 6, 10 } },
           kind = 'confirm',
         },
         {
@@ -99,7 +99,7 @@ describe('ui/ext_messages', function()
           kind = 'echo',
         },
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -116,7 +116,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 19 } },
+          content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 18 } },
           kind = 'confirm_sub',
         },
       },
@@ -135,7 +135,7 @@ describe('ui/ext_messages', function()
       ]],
       messages = {
         {
-          content = { { 'W10: Warning: Changing a readonly file', 19, 27 } },
+          content = { { 'W10: Warning: Changing a readonly file', 19, 26 } },
           kind = 'wmsg',
         },
       },
@@ -151,7 +151,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'search hit BOTTOM, continuing at TOP', 19, 27 } },
+          content = { { 'search hit BOTTOM, continuing at TOP', 19, 26 } },
           kind = 'wmsg',
         },
       },
@@ -167,15 +167,15 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'Error detected while processing :', 9, 7 } },
+          content = { { 'Error detected while processing :', 9, 6 } },
           kind = 'emsg',
         },
         {
-          content = { { 'E605: Exception not caught: foo', 9, 7 } },
+          content = { { 'E605: Exception not caught: foo', 9, 6 } },
           kind = 'emsg',
         },
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -225,15 +225,15 @@ describe('ui/ext_messages', function()
         {
           content = {
             { '\nErrorMsg      ' },
-            { 'xxx', 9, 7 },
+            { 'xxx', 9, 6 },
             { ' ' },
-            { 'ctermfg=', 18, 6 },
+            { 'ctermfg=', 18, 5 },
             { '15 ' },
-            { 'ctermbg=', 18, 6 },
+            { 'ctermbg=', 18, 5 },
             { '1 ' },
-            { 'guifg=', 18, 6 },
+            { 'guifg=', 18, 5 },
             { 'White ' },
-            { 'guibg=', 18, 6 },
+            { 'guibg=', 18, 5 },
             { 'Red' },
           },
           kind = 'list_cmd',
@@ -280,7 +280,7 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       messages = { {
-        content = { { 'raa', 9, 7 } },
+        content = { { 'raa', 9, 6 } },
         kind = 'echoerr',
       } },
     }
@@ -307,15 +307,15 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'bork', 9, 7 } },
+          content = { { 'bork', 9, 6 } },
           kind = 'echoerr',
         },
         {
-          content = { { 'fail', 9, 7 } },
+          content = { { 'fail', 9, 6 } },
           kind = 'echoerr',
         },
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -329,19 +329,19 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'bork', 9, 7 } },
+          content = { { 'bork', 9, 6 } },
           kind = 'echoerr',
         },
         {
-          content = { { 'fail', 9, 7 } },
+          content = { { 'fail', 9, 6 } },
           kind = 'echoerr',
         },
         {
-          content = { { 'extrafail', 9, 7 } },
+          content = { { 'extrafail', 9, 6 } },
           kind = 'echoerr',
         },
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -363,7 +363,7 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       messages = { {
-        content = { { 'problem', 9, 7 } },
+        content = { { 'problem', 9, 6 } },
         kind = 'echoerr',
       } },
       cmdline = {
@@ -391,15 +391,15 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       msg_history = {
-        { kind = 'echoerr', content = { { 'raa', 9, 7 } } },
-        { kind = 'echoerr', content = { { 'bork', 9, 7 } } },
-        { kind = 'echoerr', content = { { 'fail', 9, 7 } } },
-        { kind = 'echoerr', content = { { 'extrafail', 9, 7 } } },
-        { kind = 'echoerr', content = { { 'problem', 9, 7 } } },
+        { kind = 'echoerr', content = { { 'raa', 9, 6 } } },
+        { kind = 'echoerr', content = { { 'bork', 9, 6 } } },
+        { kind = 'echoerr', content = { { 'fail', 9, 6 } } },
+        { kind = 'echoerr', content = { { 'extrafail', 9, 6 } } },
+        { kind = 'echoerr', content = { { 'problem', 9, 6 } } },
       },
       messages = {
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -424,7 +424,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'bork\nfail', 9, 7 } },
+          content = { { 'bork\nfail', 9, 6 } },
           kind = 'echoerr',
         },
       },
@@ -438,13 +438,13 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
       msg_history = {
         {
-          content = { { 'bork\nfail', 9, 7 } },
+          content = { { 'bork\nfail', 9, 6 } },
           kind = 'echoerr',
         },
       },
@@ -492,7 +492,7 @@ describe('ui/ext_messages', function()
         { content = { { 'x                     #1' } }, kind = 'list_cmd' },
         { content = { { 'y                     #2' } }, kind = 'list_cmd' },
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -507,7 +507,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { '-- INSERT --', 5, 12 } },
+      showmode = { { '-- INSERT --', 5, 11 } },
     }
 
     feed('alphpabetalphanum')
@@ -518,7 +518,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*2
     ]],
-      showmode = { { '-- INSERT --', 5, 12 } },
+      showmode = { { '-- INSERT --', 5, 11 } },
     }
 
     feed('')
@@ -529,7 +529,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*2
     ]],
-      showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 12 } },
+      showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 11 } },
     }
 
     feed('')
@@ -545,7 +545,7 @@ describe('ui/ext_messages', function()
         items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } },
         pos = 1,
       },
-      showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 1 of 2', 6, 19 } },
+      showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } },
     }
 
     -- echomsg and showmode don't overwrite each other, this is the same
@@ -567,7 +567,7 @@ describe('ui/ext_messages', function()
         content = { { 'stuff' } },
         kind = 'echomsg',
       } },
-      showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 1 of 2', 6, 19 } },
+      showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } },
     }
 
     feed('')
@@ -587,7 +587,7 @@ describe('ui/ext_messages', function()
         content = { { 'stuff' } },
         kind = 'echomsg',
       } },
-      showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 2 of 2', 6, 19 } },
+      showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 2 of 2', 6, 18 } },
     }
 
     feed(':messages')
@@ -604,7 +604,7 @@ describe('ui/ext_messages', function()
       } },
       messages = {
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -618,7 +618,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { 'recording @q', 5, 12 } },
+      showmode = { { 'recording @q', 5, 11 } },
     }
 
     feed('i')
@@ -627,7 +627,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { '-- INSERT --recording @q', 5, 12 } },
+      showmode = { { '-- INSERT --recording @q', 5, 11 } },
     }
 
     feed('')
@@ -636,7 +636,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { 'recording @q', 5, 12 } },
+      showmode = { { 'recording @q', 5, 11 } },
     }
 
     feed('q')
@@ -655,7 +655,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { 'recording @q', 5, 12 } },
+      showmode = { { 'recording @q', 5, 11 } },
       mode = 'normal',
     }
 
@@ -665,7 +665,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { 'recording @q', 5, 12 } },
+      showmode = { { 'recording @q', 5, 11 } },
       mode = 'insert',
     }
 
@@ -675,7 +675,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { 'recording @q', 5, 12 } },
+      showmode = { { 'recording @q', 5, 11 } },
       mode = 'normal',
     }
 
@@ -697,7 +697,7 @@ describe('ui/ext_messages', function()
         ^                         |
         {1:~                        }|*4
       ]],
-      ruler = { { '0,0-1   All', 9, 62 } },
+      ruler = { { '0,0-1   All', 9, 61 } },
     })
     command('hi clear MsgArea')
     feed('i')
@@ -706,7 +706,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      showmode = { { '-- INSERT --', 5, 12 } },
+      showmode = { { '-- INSERT --', 5, 11 } },
       ruler = { { '0,1     All' } },
     }
     feed('abcde12345')
@@ -744,7 +744,7 @@ describe('ui/ext_messages', function()
         {17:123}45                    |
         {1:~                        }|*3
       ]],
-      showmode = { { '-- VISUAL BLOCK --', 5, 12 } },
+      showmode = { { '-- VISUAL BLOCK --', 5, 11 } },
       showcmd = { { '2x3' } },
       ruler = { { '1,3     All' } },
     })
@@ -825,7 +825,7 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       messages = { {
-        content = { { 'bork', 9, 7 } },
+        content = { { 'bork', 9, 6 } },
         kind = 'echoerr',
       } },
     }
@@ -850,7 +850,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'E117: Unknown function: nosuchfunction', 9, 7 } },
+          content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } },
           kind = 'emsg',
         },
       },
@@ -865,12 +865,12 @@ describe('ui/ext_messages', function()
       msg_history = {
         { kind = 'echomsg', content = { { 'howdy' } } },
         { kind = '', content = { { 'Type  :qa  and press  to exit Nvim' } } },
-        { kind = 'echoerr', content = { { 'bork', 9, 7 } } },
-        { kind = 'emsg', content = { { 'E117: Unknown function: nosuchfunction', 9, 7 } } },
+        { kind = 'echoerr', content = { { 'bork', 9, 6 } } },
+        { kind = 'emsg', content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } } },
       },
       messages = {
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
@@ -943,7 +943,7 @@ stack traceback:
 	[C]: in function 'error'
 	[string ":lua"]:1: in main chunk]],
               9,
-              7,
+              6,
             },
           },
           kind = 'lua_error',
@@ -963,7 +963,7 @@ stack traceback:
       messages = {
         {
           content = {
-            { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 7 },
+            { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 6 },
           },
           kind = 'rpc_error',
         },
@@ -1092,7 +1092,7 @@ stack traceback:
     ]],
       messages = {
         {
-          content = { { 'wow, ', 10, 9 }, { 'such\n\nvery ', 9, 7 }, { 'color', 8, 13 } },
+          content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } },
           kind = 'echomsg',
         },
       },
@@ -1117,13 +1117,13 @@ stack traceback:
     ]],
       messages = {
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
       msg_history = {
         {
-          content = { { 'wow, ', 10, 9 }, { 'such\n\nvery ', 9, 7 }, { 'color', 8, 13 } },
+          content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } },
           kind = 'echomsg',
         },
       },
@@ -1783,7 +1783,7 @@ describe('ui/ext_messages', function()
       {1:~                }type  :help iccf{18:}       for information {1:                 }|
       {1:~                                                                               }|*5
     ]]
-    local showmode = { { '-- INSERT --', 5, 12 } }
+    local showmode = { { '-- INSERT --', 5, 11 } }
     screen:expect(introscreen)
 
     --  (same as :mode) does _not_ clear intro message
@@ -1858,7 +1858,7 @@ describe('ui/ext_messages', function()
     ]],
       messages = {
         {
-          content = { { 'Press ENTER or type command to continue', 6, 19 } },
+          content = { { 'Press ENTER or type command to continue', 6, 18 } },
           kind = 'return_prompt',
         },
       },
diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua
index b5a09d814c..37e0e1344b 100644
--- a/test/functional/ui/output_spec.lua
+++ b/test/functional/ui/output_spec.lua
@@ -34,7 +34,7 @@ describe('shell command :!', function()
       n.nvim_set .. ' notermguicolors',
     })
     screen:expect([[
-      {1: }                                                 |
+      ^                                                  |
       {4:~                                                 }|*4
                                                         |
       {3:-- TERMINAL --}                                    |
@@ -78,7 +78,7 @@ describe('shell command :!', function()
       29999: foo                                        |
       30000: foo                                        |
                                                         |
-      {10:Press ENTER or type command to continue}{1: }          |
+      {10:Press ENTER or type command to continue}^           |
       {3:-- TERMINAL --}                                    |
     ]],
       {
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index f5cb914299..42734d07ca 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -967,11 +967,11 @@ function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
   self._cursor_style_enabled = cursor_style_enabled
   for _, item in pairs(mode_info) do
     -- attr IDs are not stable, but their value should be
-    if item.attr_id ~= nil then
+    if item.attr_id ~= nil and self._attr_table[item.attr_id] ~= nil then
       item.attr = self._attr_table[item.attr_id][1]
       item.attr_id = nil
     end
-    if item.attr_id_lm ~= nil then
+    if item.attr_id_lm ~= nil and self._attr_table[item.attr_id_lm] ~= nil then
       item.attr_lm = self._attr_table[item.attr_id_lm][1]
       item.attr_id_lm = nil
     end
-- 
cgit 


From c830901e8cde49467d1c99c2d656a13e51979790 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 17 Dec 2024 12:40:30 +0800
Subject: vim-patch:9.1.0936: cannot highlight completed text

Problem:  cannot highlight completed text
Solution: (optionally) highlight auto-completed text using the
          ComplMatchIns highlight group (glepnir)

closes: vim/vim#16173

https://github.com/vim/vim/commit/6a38aff218f5b99a1aed7edaa357df24b9092734

Co-authored-by: glepnir 
---
 runtime/colors/vim.lua          |  1 +
 runtime/doc/news.txt            |  1 +
 runtime/doc/syntax.txt          |  2 ++
 src/nvim/drawline.c             | 13 ++++++++++-
 src/nvim/highlight_group.c      |  1 +
 src/nvim/insexpand.c            | 51 +++++++++++++++++++++++++++++++----------
 test/old/testdir/test_popup.vim | 45 ++++++++++++++++++++++++++++++++++++
 7 files changed, 101 insertions(+), 13 deletions(-)

diff --git a/runtime/colors/vim.lua b/runtime/colors/vim.lua
index 5b9309ab38..dd3e83c653 100644
--- a/runtime/colors/vim.lua
+++ b/runtime/colors/vim.lua
@@ -60,6 +60,7 @@ hi('PmenuMatch',     { link = 'Pmenu' })
 hi('PmenuMatchSel',  { link = 'PmenuSel' })
 hi('PmenuExtra',     { link = 'Pmenu' })
 hi('PmenuExtraSel',  { link = 'PmenuSel' })
+hi('ComplMatchIns',  { link = 'Normal' })
 hi('Substitute',     { link = 'Search' })
 hi('Whitespace',     { link = 'NonText' })
 hi('MsgSeparator',   { link = 'StatusLine' })
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index ad0835e80f..73704a143c 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -216,6 +216,7 @@ EDITOR
 • On Windows, filename arguments on the command-line prefixed with "~\" or
   "~/" are now expanded to the user's profile directory, not a relative path
   to a literal "~" directory.
+• |hl-ComplMatchIns| shows matched text of the currently inserted completion.
 • |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup.
 
 EVENTS
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index df4d0f7260..f5183e3247 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -5243,6 +5243,8 @@ PmenuMatch	Popup menu: Matched text in normal item. Combined with
 							*hl-PmenuMatchSel*
 PmenuMatchSel	Popup menu: Matched text in selected item. Combined with
 		|hl-PmenuMatch| and |hl-PmenuSel|.
+							*hl-ComplMatchIns*
+ComplMatchIns	Matched text of the currently inserted completion.
 							*hl-Question*
 Question	|hit-enter| prompt and yes/no questions.
 							*hl-QuickFixLine*
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index b8f21d7454..e15296572b 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -31,6 +31,7 @@
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/indent.h"
+#include "nvim/insexpand.h"
 #include "nvim/mark_defs.h"
 #include "nvim/marktree_defs.h"
 #include "nvim/match.h"
@@ -934,6 +935,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
   colnr_T vcol_prev = -1;             // "wlv.vcol" of previous character
   ScreenGrid *grid = &wp->w_grid;     // grid specific to the window
 
+  const bool in_curline = wp == curwin && lnum == curwin->w_cursor.lnum;
   const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
   const bool has_foldtext = has_fold && *wp->w_p_fdt != NUL;
 
@@ -954,6 +956,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
   int vi_attr = 0;                      // attributes for Visual and incsearch highlighting
   int area_attr = 0;                    // attributes desired by highlighting
   int search_attr = 0;                  // attributes desired by 'hlsearch'
+  int ins_match_attr = 0;               // attributes desired by PmenuMatch
   int vcol_save_attr = 0;               // saved attr for 'cursorcolumn'
   int decor_attr = 0;                   // attributes desired by syntax and extmarks
   bool has_syntax = false;              // this buffer has syntax highl.
@@ -1117,7 +1120,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
       }
 
       // Check if the char under the cursor should be inverted (highlighted).
-      if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin
+      if (!highlight_match && in_curline
           && cursor_is_block_during_visual(*p_sel == 'e')) {
         noinvcur = true;
       }
@@ -2704,6 +2707,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
       vcol_prev = wlv.vcol;
     }
 
+    if (wlv.filler_todo <= 0
+        && (State & MODE_INSERT) && in_curline && ins_compl_active()) {
+      ins_match_attr = ins_compl_col_range_attr(wlv.col);
+      if (ins_match_attr > 0) {
+        wlv.char_attr = hl_combine_attr(wlv.char_attr, ins_match_attr);
+      }
+    }
+
     // Store character to be displayed.
     // Skip characters that are left of the screen for 'nowrap'.
     if (wlv.filler_todo > 0) {
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index f1f5f47630..636124a698 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -173,6 +173,7 @@ static const char *highlight_init_both[] = {
   "default link PmenuKind        Pmenu",
   "default link PmenuKindSel     PmenuSel",
   "default link PmenuSbar        Pmenu",
+  "default link ComplMatchIns    Normal",
   "default link Substitute       Search",
   "default link StatusLineTerm   StatusLine",
   "default link StatusLineTermNC StatusLineNC",
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 2e3a2e39ac..5036ec5e45 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -256,6 +256,7 @@ static pos_T compl_startpos;
 static int compl_length = 0;
 static colnr_T compl_col = 0;           ///< column where the text starts
                                         ///< that is being completed
+static colnr_T compl_ins_end_col = 0;
 static char *compl_orig_text = NULL;    ///< text as it was before
                                         ///< completion started
 /// Undo information to restore extmarks for original text.
@@ -282,6 +283,11 @@ static size_t spell_bad_len = 0;   // length of located bad word
 
 static int compl_selected_item = -1;
 
+// "compl_match_array" points the currently displayed list of entries in the
+// popup menu.  It is NULL when there is no popup menu.
+static pumitem_T *compl_match_array = NULL;
+static int compl_match_arraysize;
+
 /// CTRL-X pressed in Insert mode.
 void ins_ctrl_x(void)
 {
@@ -943,6 +949,30 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len)
   return strncmp(match->cp_str, str, len) == 0;
 }
 
+/// when len is -1 mean use whole length of p otherwise part of p
+static void ins_compl_insert_bytes(char *p, int len)
+  FUNC_ATTR_NONNULL_ALL
+{
+  if (len == -1) {
+    len = (int)strlen(p);
+  }
+  assert(len >= 0);
+  ins_bytes_len(p, (size_t)len);
+  compl_ins_end_col = curwin->w_cursor.col - 1;
+}
+
+/// Checks if the column is within the currently inserted completion text
+/// column range. If it is, it returns a special highlight attribute.
+/// -1 mean normal item.
+int ins_compl_col_range_attr(int col)
+{
+  if (col >= compl_col && col < compl_ins_end_col) {
+    return syn_name2attr("ComplMatchIns");
+  }
+
+  return -1;
+}
+
 /// Reduce the longest common string for match "match".
 static void ins_compl_longest_match(compl_T *match)
 {
@@ -952,7 +982,7 @@ static void ins_compl_longest_match(compl_T *match)
 
     bool had_match = (curwin->w_cursor.col > compl_col);
     ins_compl_delete(false);
-    ins_bytes(compl_leader + get_compl_len());
+    ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
     ins_redraw(false);
 
     // When the match isn't there (to avoid matching itself) remove it
@@ -986,7 +1016,7 @@ static void ins_compl_longest_match(compl_T *match)
     *p = NUL;
     bool had_match = (curwin->w_cursor.col > compl_col);
     ins_compl_delete(false);
-    ins_bytes(compl_leader + get_compl_len());
+    ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
     ins_redraw(false);
 
     // When the match isn't there (to avoid matching itself) remove it
@@ -1058,11 +1088,6 @@ unsigned get_cot_flags(void)
   return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags;
 }
 
-/// "compl_match_array" points the currently displayed list of entries in the
-/// popup menu.  It is NULL when there is no popup menu.
-static pumitem_T *compl_match_array = NULL;
-static int compl_match_arraysize;
-
 /// Remove any popup menu.
 static void ins_compl_del_pum(void)
 {
@@ -1678,6 +1703,7 @@ void ins_compl_clear(void)
   compl_cont_status = 0;
   compl_started = false;
   compl_matches = 0;
+  compl_ins_end_col = 0;
   XFREE_CLEAR(compl_pattern);
   compl_patternlen = 0;
   XFREE_CLEAR(compl_leader);
@@ -1802,7 +1828,7 @@ static void ins_compl_new_leader(void)
 {
   ins_compl_del_pum();
   ins_compl_delete(true);
-  ins_bytes(compl_leader + get_compl_len());
+  ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
   compl_used_match = false;
 
   if (compl_started) {
@@ -2137,7 +2163,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval)
       const int compl_len = get_compl_len();
       const int len = (int)strlen(p);
       if (len > compl_len) {
-        ins_bytes_len(p + compl_len, (size_t)(len - compl_len));
+        ins_compl_insert_bytes(p + compl_len, len - compl_len);
       }
     }
     restore_orig_extmarks();
@@ -3639,7 +3665,7 @@ void ins_compl_insert(bool in_compl_func)
   // Make sure we don't go over the end of the string, this can happen with
   // illegal bytes.
   if (compl_len < (int)strlen(compl_shown_match->cp_str)) {
-    ins_bytes(compl_shown_match->cp_str + compl_len);
+    ins_compl_insert_bytes(compl_shown_match->cp_str + compl_len, -1);
   }
   compl_used_match = !match_at_original_text(compl_shown_match);
 
@@ -3888,14 +3914,15 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
 
   // Insert the text of the new completion, or the compl_leader.
   if (compl_no_insert && !started) {
-    ins_bytes(compl_orig_text + get_compl_len());
+    ins_compl_insert_bytes(compl_orig_text + get_compl_len(), -1);
     compl_used_match = false;
     restore_orig_extmarks();
   } else if (insert_match) {
     if (!compl_get_longest || compl_used_match) {
       ins_compl_insert(in_compl_func);
     } else {
-      ins_bytes(compl_leader + get_compl_len());
+      assert(compl_leader != NULL);
+      ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
     }
     if (!strcmp(compl_curr_match->cp_str, compl_orig_text)) {
       restore_orig_extmarks();
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index a24e133ce6..4f5b769ace 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1713,4 +1713,49 @@ func Test_pum_keep_select()
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_pum_matchins_higlight()
+  CheckScreendump
+  let lines =<< trim END
+    func Omni_test(findstart, base)
+      if a:findstart
+        return col(".")
+      endif
+      return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+    endfunc
+    set omnifunc=Omni_test
+    hi ComplMatchIns ctermfg=red
+  END
+  call writefile(lines, 'Xscript', 'D')
+  let buf = RunVimInTerminal('-S Xscript', {})
+
+  call TermWait(buf)
+  call term_sendkeys(buf, "S\\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_01', {})
+  call term_sendkeys(buf, "\\")
+
+  call TermWait(buf)
+  call term_sendkeys(buf, "S\\\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_02', {})
+  call term_sendkeys(buf, "\\")
+
+  call TermWait(buf)
+  call term_sendkeys(buf, "S\\\\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_03', {})
+  call term_sendkeys(buf, "\\")
+
+  " restore after accept
+  call TermWait(buf)
+  call term_sendkeys(buf, "S\\\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_04', {})
+  call term_sendkeys(buf, "\\")
+
+  " restore after cancel completion
+  call TermWait(buf)
+  call term_sendkeys(buf, "S\\\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_05', {})
+  call term_sendkeys(buf, "\\")
+
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From 2f7b385f2ef61626bc034bd6f3a25f5ec9f3a1f3 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 18 Dec 2024 07:34:52 +0800
Subject: vim-patch:9.1.0941: ComplMatchIns doesn't work after multibyte chars

Problem:  ComplMatchIns doesn't work after multibyte chars
          (after v9.1.0936)
Solution: Use (ptr - line) instead of wlv.col (zeertzjq).

closes: vim/vim#16233

https://github.com/vim/vim/commit/f4ccada5c372b2c14cc32490860c6995cd00268c
---
 src/nvim/drawline.c                   | 27 +++++++--------
 test/functional/ui/popupmenu_spec.lua | 65 +++++++++++++++++++++++++++++++++++
 test/old/testdir/test_popup.vim       | 10 +++---
 3 files changed, 83 insertions(+), 19 deletions(-)

diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index e15296572b..a1d03486ff 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -956,7 +956,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
   int vi_attr = 0;                      // attributes for Visual and incsearch highlighting
   int area_attr = 0;                    // attributes desired by highlighting
   int search_attr = 0;                  // attributes desired by 'hlsearch'
-  int ins_match_attr = 0;               // attributes desired by PmenuMatch
   int vcol_save_attr = 0;               // saved attr for 'cursorcolumn'
   int decor_attr = 0;                   // attributes desired by syntax and extmarks
   bool has_syntax = false;              // this buffer has syntax highl.
@@ -1632,8 +1631,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
     }
 
     // When still displaying '$' of change command, stop at cursor.
-    if (dollar_vcol >= 0 && wp == curwin
-        && lnum == wp->w_cursor.lnum && wlv.vcol >= wp->w_virtcol) {
+    if (dollar_vcol >= 0 && in_curline && wlv.vcol >= wp->w_virtcol) {
       draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
       // don't clear anything after wlv.col
       wlv_put_linebuf(wp, &wlv, wlv.col, false, bg_attr, 0);
@@ -1789,6 +1787,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
       wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
     }
 
+    // Apply ComplMatchIns highlight if needed.
+    if (wlv.filler_todo <= 0
+        && (State & MODE_INSERT) && in_curline && ins_compl_active()) {
+      int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
+      if (ins_match_attr > 0) {
+        char_attr_pri = hl_combine_attr(char_attr_pri, ins_match_attr);
+        wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
+      }
+    }
+
     if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) {
       const int v = (int)(ptr - line);
       linenr_T lnume = lnum + foldinfo.fi_lines - 1;
@@ -2449,8 +2457,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
     // With 'virtualedit' we may never reach cursor position, but we still
     // need to correct the cursor column, so do that at end of line.
     if (!did_wcol && wlv.filler_todo <= 0
-        && wp == curwin && lnum == wp->w_cursor.lnum
-        && conceal_cursor_line(wp)
+        && in_curline && conceal_cursor_line(wp)
         && (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) {
       wp->w_wcol = wlv.col - wlv.boguscols;
       if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) {
@@ -2643,7 +2650,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
 
       // Update w_cline_height and w_cline_folded if the cursor line was
       // updated (saves a call to plines_win() later).
-      if (wp == curwin && lnum == curwin->w_cursor.lnum) {
+      if (in_curline) {
         curwin->w_cline_row = startrow;
         curwin->w_cline_height = wlv.row - startrow;
         curwin->w_cline_folded = has_fold;
@@ -2707,14 +2714,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
       vcol_prev = wlv.vcol;
     }
 
-    if (wlv.filler_todo <= 0
-        && (State & MODE_INSERT) && in_curline && ins_compl_active()) {
-      ins_match_attr = ins_compl_col_range_attr(wlv.col);
-      if (ins_match_attr > 0) {
-        wlv.char_attr = hl_combine_attr(wlv.char_attr, ins_match_attr);
-      }
-    }
-
     // Store character to be displayed.
     // Skip characters that are left of the screen for 'nowrap'.
     if (wlv.filler_todo > 0) {
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index beb3ae385c..1d4709e856 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -5561,6 +5561,71 @@ describe('builtin popupmenu', function()
         ]])
         feed('')
       end)
+
+      -- oldtest: Test_pum_matchins_higlight()
+      it('with ComplMatchIns highlight', function()
+        exec([[
+          func Omni_test(findstart, base)
+            if a:findstart
+              return col(".")
+            endif
+            return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+          endfunc
+          set omnifunc=Omni_test
+          hi ComplMatchIns guifg=red
+        ]])
+
+        feed('Sαβγ ')
+        screen:expect([[
+          αβγ {8:foo}^                         |
+          {1:~  }{s: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 1 of 3}                 |
+        ]])
+        feed('')
+
+        feed('Sαβγ ')
+        screen:expect([[
+          αβγ {8:bar}^                         |
+          {1:~  }{n: foo            }{1:             }|
+          {1:~  }{s: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 2 of 3}                 |
+        ]])
+        feed('')
+
+        feed('Sαβγ ')
+        screen:expect([[
+          αβγ {8:你好}^                        |
+          {1:~  }{n: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{s: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 3 of 3}                 |
+        ]])
+        feed('')
+
+        -- restore after accept
+        feed('Sαβγ ')
+        screen:expect([[
+          αβγ foo^                         |
+          {1:~                               }|*18
+          {2:-- INSERT --}                    |
+        ]])
+        feed('')
+
+        -- restore after cancel completion
+        feed('Sαβγ ')
+        screen:expect([[
+          αβγ foo ^                        |
+          {1:~                               }|*18
+          {2:-- INSERT --}                    |
+        ]])
+        feed('')
+      end)
     end
   end
 
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index 4f5b769ace..de8674dad0 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1729,29 +1729,29 @@ func Test_pum_matchins_higlight()
   let buf = RunVimInTerminal('-S Xscript', {})
 
   call TermWait(buf)
-  call term_sendkeys(buf, "S\\")
+  call term_sendkeys(buf, "Sαβγ \\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_01', {})
   call term_sendkeys(buf, "\\")
 
   call TermWait(buf)
-  call term_sendkeys(buf, "S\\\")
+  call term_sendkeys(buf, "Sαβγ \\\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_02', {})
   call term_sendkeys(buf, "\\")
 
   call TermWait(buf)
-  call term_sendkeys(buf, "S\\\\")
+  call term_sendkeys(buf, "Sαβγ \\\\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_03', {})
   call term_sendkeys(buf, "\\")
 
   " restore after accept
   call TermWait(buf)
-  call term_sendkeys(buf, "S\\\")
+  call term_sendkeys(buf, "Sαβγ \\\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_04', {})
   call term_sendkeys(buf, "\\")
 
   " restore after cancel completion
   call TermWait(buf)
-  call term_sendkeys(buf, "S\\\")
+  call term_sendkeys(buf, "Sαβγ \\\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_05', {})
   call term_sendkeys(buf, "\\")
 
-- 
cgit 


From b7da54aa9e72a0c7684546439f078ed89ba16d0a Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 18 Dec 2024 07:49:13 +0800
Subject: vim-patch:9.1.0942: a few typos were found

Problem:  a few typos were found
Solution: fix them (zeertzjq)

closes: vim/vim#16232

https://github.com/vim/vim/commit/d32bf0a06762f9ad08334d67b4d7f235f87f9063
---
 runtime/doc/motion.txt                | 4 ++--
 test/functional/ui/popupmenu_spec.lua | 2 +-
 test/old/testdir/test_popup.vim       | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 41fcdc3e1b..284be09121 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -118,8 +118,8 @@ Note that when using ":" any motion becomes charwise exclusive.
 					*inclusive-motion-selection-exclusive*
 When 'selection' is "exclusive", |Visual| mode is active and an inclusive
 motion has been used, the cursor position will be adjusted by another
-character to the right, so that visual selction includes the expected text and
-can be acted upon.
+character to the right, so that the Visual selection includes the expected
+text and can be acted upon.
 
 								*forced-motion*
 FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index 1d4709e856..a64fc99f26 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -5562,7 +5562,7 @@ describe('builtin popupmenu', function()
         feed('')
       end)
 
-      -- oldtest: Test_pum_matchins_higlight()
+      -- oldtest: Test_pum_matchins_highlight()
       it('with ComplMatchIns highlight', function()
         exec([[
           func Omni_test(findstart, base)
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index de8674dad0..28104bdff5 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1713,7 +1713,7 @@ func Test_pum_keep_select()
   call StopVimInTerminal(buf)
 endfunc
 
-func Test_pum_matchins_higlight()
+func Test_pum_matchins_highlight()
   CheckScreendump
   let lines =<< trim END
     func Omni_test(findstart, base)
-- 
cgit 


From f7c42433c55203a1421276b33b6d3fedd652ca7e Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 18 Dec 2024 09:04:32 +0800
Subject: vim-patch:9.1.0940: Wrong cursor shape with "gq" and 'indentexpr'
 executes :normal (#31616)

Problem:  Wrong cursor shape with "gq" and 'indentexpr' executes :normal
Solution: Update cursor and mouse shape after restoring old_State.
          (zeertzjq)

closes: vim/vim#16241

Solution: Update cursor and mouse shape after restoring old_State.

https://github.com/vim/vim/commit/6c3027744e71937b24829135ba072090d7d52bc3
---
 src/nvim/textformat.c            |  7 ++++++
 test/functional/ui/mode_spec.lua | 40 ++++++++++++++++++++++++++++++++
 test/old/testdir/test_indent.vim | 49 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+)

diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c
index 06b3aa0411..b3b9cc6b44 100644
--- a/src/nvim/textformat.c
+++ b/src/nvim/textformat.c
@@ -35,6 +35,7 @@
 #include "nvim/strings.h"
 #include "nvim/textformat.h"
 #include "nvim/textobject.h"
+#include "nvim/ui.h"
 #include "nvim/undo.h"
 #include "nvim/vim_defs.h"
 #include "nvim/window.h"
@@ -1049,12 +1050,18 @@ void format_lines(linenr_T line_count, bool avoid_fex)
         State = MODE_INSERT;         // for open_line()
         smd_save = p_smd;
         p_smd = false;
+
         insertchar(NUL, INSCHAR_FORMAT
                    + (do_comments ? INSCHAR_DO_COM : 0)
                    + (do_comments && do_comments_list ? INSCHAR_COM_LIST : 0)
                    + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent);
+
         State = old_State;
         p_smd = smd_save;
+        // Cursor shape may have been updated (e.g. by :normal) in insertchar(),
+        // so it needs to be updated here.
+        ui_cursor_shape();
+
         second_indent = -1;
         // at end of par.: need to set indent of next par.
         need_set_indent = is_end_par;
diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua
index 8c6a284cd6..69b38f3ff3 100644
--- a/test/functional/ui/mode_spec.lua
+++ b/test/functional/ui/mode_spec.lua
@@ -94,6 +94,46 @@ describe('ui mode_change event', function()
     }
   end)
 
+  -- oldtest: Test_indent_norm_with_gq()
+  it('is restored to Normal mode after "gq" indents using :normal #12309', function()
+    screen:try_resize(60, 6)
+    n.exec([[
+      func Indent()
+        exe "normal! \"
+        return 0
+      endfunc
+
+      setlocal indentexpr=Indent()
+      call setline(1, [repeat('a', 80), repeat('b', 80)])
+    ]])
+
+    feed('ggVG')
+    screen:expect {
+      grid = [[
+      {17:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+      {17:aaaaaaaaaaaaaaaaaaaa}                                        |
+      ^b{17:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}|
+      {17:bbbbbbbbbbbbbbbbbbbb}                                        |
+      {1:~                                                           }|
+      {5:-- VISUAL LINE --}                                           |
+    ]],
+      mode = 'visual',
+    }
+
+    feed('gq')
+    screen:expect {
+      grid = [[
+      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+      aaaaaaaaaaaaaaaaaaaa                                        |
+      ^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+      bbbbbbbbbbbbbbbbbbbb                                        |
+      {1:~                                                           }|
+                                                                  |
+    ]],
+      mode = 'normal',
+    }
+  end)
+
   it('works in insert mode', function()
     feed('i')
     screen:expect {
diff --git a/test/old/testdir/test_indent.vim b/test/old/testdir/test_indent.vim
index 0998b04443..36670a0472 100644
--- a/test/old/testdir/test_indent.vim
+++ b/test/old/testdir/test_indent.vim
@@ -1,5 +1,8 @@
 " Test for various indent options
 
+source shared.vim
+source check.vim
+
 func Test_preserveindent()
   new
   " Test for autoindent copying indent from the previous line
@@ -303,4 +306,50 @@ func Test_indent_overflow_count2()
   close!
 endfunc
 
+" Test that mouse shape is restored to Normal mode after using "gq" when
+" 'indentexpr' executes :normal.
+func Test_indent_norm_with_gq()
+  CheckFeature mouseshape
+  CheckCanRunGui
+
+  let lines =<< trim END
+    func Indent()
+      exe "normal! \"
+      return 0
+    endfunc
+
+    setlocal indentexpr=Indent()
+  END
+  call writefile(lines, 'Xindentexpr.vim', 'D')
+
+  let lines =<< trim END
+    vim9script
+    var mouse_shapes = []
+
+    setline(1, [repeat('a', 80), repeat('b', 80)])
+
+    feedkeys('ggVG')
+    timer_start(50, (_) => {
+      mouse_shapes += [getmouseshape()]
+      timer_start(50, (_) => {
+        feedkeys('gq')
+        timer_start(50, (_) => {
+          mouse_shapes += [getmouseshape()]
+          timer_start(50, (_) => {
+            writefile(mouse_shapes, 'Xmouseshapes')
+            quit!
+          })
+        })
+      })
+    })
+  END
+  call writefile(lines, 'Xmouseshape.vim', 'D')
+
+  call RunVim([], [], "-g -S Xindentexpr.vim -S Xmouseshape.vim")
+  call WaitForAssert({-> assert_equal(['rightup-arrow', 'arrow'],
+        \ readfile('Xmouseshapes'))}, 300)
+
+  call delete('Xmouseshapes')
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From 38f554e9c496ccd0a21bea5f71d03fa6bc3774b8 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 18 Dec 2024 09:12:04 +0800
Subject: vim-patch:a977883: runtime(doc): Fix style in fold.txt (#31617)

closes: vim/vim#16236

https://github.com/vim/vim/commit/a977883ef336f83102dc0a1edbfc999e6b5c129c

Co-authored-by: h-east 
---
 runtime/doc/fold.txt | 29 ++++++++++++++++-------------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt
index 9798170dbc..9e61e40814 100644
--- a/runtime/doc/fold.txt
+++ b/runtime/doc/fold.txt
@@ -101,7 +101,8 @@ The result of foldexpr then determines the fold level as follows:
   "<1", "<2", ..	a fold with this level ends at this line
   ">1", ">2", ..	a fold with this level starts at this line
 
-The result values "=", "s" and "a" are more expensive, please see |fold-expr-slow|.
+The result values "=", "s" and "a" are more expensive, please see
+|fold-expr-slow|.
 
 It is not required to mark the start (end) of a fold with ">1" ("<1"), a fold
 will also start (end) when the fold level is higher (lower) than the fold
@@ -117,7 +118,7 @@ For debugging the 'debug' option can be set to "msg", the error messages will
 be visible then.
 
 If the 'foldexpr' expression starts with s: or ||, then it is replaced
-with the script ID (|local-function|). Examples: >
+with the script ID (|local-function|).  Examples: >
 		set foldexpr=s:MyFoldExpr()
 		set foldexpr=SomeFoldExpr()
 <
@@ -141,7 +142,7 @@ end in that line.
 It may happen that folds are not updated properly.  You can use |zx| or |zX|
 to force updating folds.
 
-Minimizing Computational Cost			             *fold-expr-slow*
+MINIMIZING COMPUTATIONAL COST				*fold-expr-slow*
 
 Due to its computational cost, this fold method can make Vim unresponsive,
 especially when the fold level of all lines have to be initially computed.
@@ -149,13 +150,15 @@ Afterwards, after each change, Vim restricts the computation of foldlevels
 to those lines whose fold level was affected by it (and reuses the known
 foldlevels of all the others).
 
-The fold expression should therefore strive to minimize the number of dependent
-lines needed for the computation of a given line: For example, try to avoid the
-"=", "a" and "s" return values, because these will require the evaluation of the
-fold levels on previous lines until an independent fold level is found.
+The fold expression should therefore strive to minimize the number of
+dependent lines needed for the computation of a given line: For example, try
+to avoid the "=", "a" and "s" return values, because these will require the
+evaluation of the fold levels on previous lines until an independent fold
+level is found.
 
-If this proves difficult, the next best thing could be to cache all fold levels
-in a buffer-local variable (b:foldlevels) that is only updated on |b:changedtick|:
+If this proves difficult, the next best thing could be to cache all fold
+levels in a buffer-local variable (b:foldlevels) that is only updated on
+|b:changedtick|:
 >vim
   func MyFoldFunc()
     if b:lasttick == b:changedtick
@@ -406,8 +409,8 @@ zX		Undo manually opened and closed folds: re-apply 'foldlevel'.
 		Also forces recomputing folds, like |zx|.
 
 							*zm*
-zm		Fold more: Subtract |v:count1| from 'foldlevel'.  If 'foldlevel' was
-		already zero nothing happens.
+zm		Fold more: Subtract |v:count1| from 'foldlevel'.  If
+		'foldlevel' was already zero nothing happens.
 		'foldenable' will be set.
 
 							*zM*
@@ -471,7 +474,7 @@ zk		Move upwards to the end of the previous fold.  A closed fold
 
 EXECUTING COMMANDS ON FOLDS ~
 
-:[range]foldd[oopen] {cmd}			*:foldd* *:folddo* *:folddoopen*
+:[range]foldd[oopen] {cmd}		*:foldd* *:folddo* *:folddoopen*
 		Execute {cmd} on all lines that are not in a closed fold.
 		When [range] is given, only these lines are used.
 		Each time {cmd} is executed the cursor is positioned on the
@@ -559,7 +562,7 @@ When there is room after the text, it is filled with the character specified
 by 'fillchars'.
 
 If the 'foldtext' expression starts with s: or ||, then it is replaced
-with the script ID (|local-function|). Examples: >
+with the script ID (|local-function|).  Examples: >
 		set foldtext=s:MyFoldText()
 		set foldtext=SomeFoldText()
 <
-- 
cgit 


From 738320188ffc389f1dc04f67a06c280f2d4ae41d Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 18 Dec 2024 10:21:52 +0800
Subject: test(old): fix incorrect comment in test_preview.vim (#31619)

---
 test/old/testdir/test_preview.vim | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/old/testdir/test_preview.vim b/test/old/testdir/test_preview.vim
index d24b09b7d2..424d93f54d 100644
--- a/test/old/testdir/test_preview.vim
+++ b/test/old/testdir/test_preview.vim
@@ -59,7 +59,8 @@ func Test_window_preview_terminal()
   CheckFeature quickfix
   " CheckFeature terminal
 
-  term " ++curwin
+  " term ++curwin
+  term
   const buf_num = bufnr('$')
   call assert_equal(1, winnr('$'))
   exe 'pbuffer' . buf_num
-- 
cgit 


From f9eb68f340f9c0dbf3b6b2da3ddbab2d5be21b61 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Wed, 18 Dec 2024 06:05:37 -0800
Subject: fix(coverity): error handling CHECKED_RETURN #31618

CID 516406:  Error handling issues  (CHECKED_RETURN)
    /src/nvim/api/vimscript.c: 284 in nvim_call_dict_function()
    278       Object rv = OBJECT_INIT;
    279
    280       typval_T rettv;
    281       bool mustfree = false;
    282       switch (dict.type) {
    283       case kObjectTypeString:
    >>>     CID 516406:  Error handling issues  (CHECKED_RETURN)
    >>>     Calling "eval0" without checking return value (as is done elsewhere 10 out of 12 times).
    284         TRY_WRAP(err, {
    285           eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE);
    286           clear_evalarg(&EVALARG_EVALUATE, NULL);
    287         });
    288         if (ERROR_SET(err)) {
    289           return rv;
---
 src/nvim/api/vimscript.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 67db615b20..e2aac07258 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -229,10 +229,9 @@ static Object _call_function(String fn, Array args, dict_T *self, Arena *arena,
   funcexe.fe_selfdict = self;
 
   TRY_WRAP(err, {
-    // call_func() retval is deceptive, ignore it.  Instead we set `msg_list`
-    // (see above) to capture abort-causing non-exception errors.
-    call_func(fn.data, (int)fn.size, &rettv, (int)args.size,
-              vim_args, &funcexe);
+    // call_func() retval is deceptive, ignore it.  Instead TRY_WRAP sets `msg_list` to capture
+    // abort-causing non-exception errors.
+    (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, vim_args, &funcexe);
   });
 
   if (!ERROR_SET(err)) {
@@ -280,18 +279,23 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
   typval_T rettv;
   bool mustfree = false;
   switch (dict.type) {
-  case kObjectTypeString:
+  case kObjectTypeString: {
+    int eval_ret;
     TRY_WRAP(err, {
-      eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE);
-      clear_evalarg(&EVALARG_EVALUATE, NULL);
-    });
+        eval_ret = eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE);
+        clear_evalarg(&EVALARG_EVALUATE, NULL);
+      });
     if (ERROR_SET(err)) {
       return rv;
     }
+    if (eval_ret != OK) {
+      abort();  // Should not happen.
+    }
     // Evaluation of the string arg created a new dict or increased the
     // refcount of a dict. Not necessary for a RPC dict.
     mustfree = true;
     break;
+  }
   case kObjectTypeDict:
     object_to_vim(dict, &rettv, err);
     break;
-- 
cgit 


From 07d5dc8938a7f5d8cf2b702ef4f26af926b6ac45 Mon Sep 17 00:00:00 2001
From: Peter Lithammer 
Date: Wed, 18 Dec 2024 15:31:25 +0100
Subject: feat(lsp): show server version in `:checkhealth` #31611

Problem:
Language server version information missing from `:checkhealth vim.lsp`.

Solution:
Store `InitializeResult.serverInfo.version` from the `initialize`
response and display for each client in `:checkhealth vim.lsp`.
---
 runtime/doc/lsp.txt            | 3 +++
 runtime/doc/news.txt           | 1 +
 runtime/lua/vim/lsp/client.lua | 6 ++++++
 runtime/lua/vim/lsp/health.lua | 3 +++
 4 files changed, 13 insertions(+)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 83d191ed48..228bbafdd2 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1126,6 +1126,9 @@ Lua module: vim.lsp.client                                        *lsp-client*
       • {server_capabilities}   (`lsp.ServerCapabilities?`) Response from the
                                 server sent on `initialize` describing the
                                 server's capabilities.
+      • {server_info}           (`lsp.ServerInfo?`) Response from the server
+                                sent on `initialize` describing information
+                                about the server.
       • {progress}              (`vim.lsp.Client.Progress`) A ring buffer
                                 (|vim.ringbuf()|) containing progress messages
                                 sent by the server. See
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 73704a143c..172fa4625c 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -115,6 +115,7 @@ LSP
 • |vim.lsp.util.make_position_params()|, |vim.lsp.util.make_range_params()|
   and |vim.lsp.util.make_given_range_params()| now require the `position_encoding`
   parameter.
+• `:checkhealth vim.lsp` displays the server version (if available).
 
 LUA
 
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index 72043c18dd..d51a45b473 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -174,6 +174,10 @@ local validate = vim.validate
 --- capabilities.
 --- @field server_capabilities lsp.ServerCapabilities?
 ---
+--- Response from the server sent on `initialize` describing information about
+--- the server.
+--- @field server_info lsp.ServerInfo?
+---
 --- A ring buffer (|vim.ringbuf()|) containing progress messages
 --- sent by the server.
 --- @field progress vim.lsp.Client.Progress
@@ -556,6 +560,8 @@ function Client:initialize()
       self.offset_encoding = self.server_capabilities.positionEncoding
     end
 
+    self.server_info = result.serverInfo
+
     if next(self.settings) then
       self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings })
     end
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index d2cf888d89..d94bc70a65 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -40,6 +40,8 @@ local function check_active_clients()
   local clients = vim.lsp.get_clients()
   if next(clients) then
     for _, client in pairs(clients) do
+      local server_version = vim.tbl_get(client, 'server_info', 'version')
+        or '? (no serverInfo.version response)'
       local cmd ---@type string
       local ccmd = client.config.cmd
       if type(ccmd) == 'table' then
@@ -62,6 +64,7 @@ local function check_active_clients()
       end
       report_info(table.concat({
         string.format('%s (id: %d)', client.name, client.id),
+        string.format('- Version: %s', server_version),
         dirs_info,
         string.format('- Command: %s', cmd),
         string.format('- Settings: %s', vim.inspect(client.settings, { newline = '\n  ' })),
-- 
cgit 


From 888a803755c58db56b5b20fcf6b812de877056c9 Mon Sep 17 00:00:00 2001
From: phanium <91544758+phanen@users.noreply.github.com>
Date: Wed, 18 Dec 2024 22:37:12 +0800
Subject: fix(lsp): vim.lsp.start fails if existing client has no
 workspace_folders #31608
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem:
regression since https://github.com/neovim/neovim/pull/31340

`nvim -l repro.lua`:
```lua
vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls' }
vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls', root_dir = 'foo' }

-- swapped case will be ok:
-- vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls', root_dir = 'foo' }
-- vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls' }
```

Failure:
```
E5113: Error while calling lua chunk: /…/lua/vim/lsp.lua:214: bad argument #1 to
'ipairs' (table expected, got nil)
stack traceback:
        [C]: in function 'ipairs'
        /…/lua/vim/lsp.lua:214: in function 'reuse_client'
        /…/lua/vim/lsp.lua:629: in function 'start'
        repro.lua:34: in main chunk
```
---
 runtime/lua/vim/lsp.lua             |  2 +-
 test/functional/plugin/lsp_spec.lua | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 6a8c3d1ff3..5a93da4298 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -211,7 +211,7 @@ local function reuse_client_default(client, config)
 
   for _, config_folder in ipairs(config_folders) do
     local found = false
-    for _, client_folder in ipairs(client.workspace_folders) do
+    for _, client_folder in ipairs(client.workspace_folders or {}) do
       if config_folder.uri == client_folder.uri then
         found = true
         break
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index d2ef166983..1f246b0914 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -1854,6 +1854,20 @@ describe('LSP', function()
         end,
       }
     end)
+
+    it('vim.lsp.start when existing client has no workspace_folders', function()
+      exec_lua(create_server_definition)
+      eq(
+        { 2, 'foo', 'foo' },
+        exec_lua(function()
+          local server = _G._create_server()
+          vim.lsp.start { cmd = server.cmd, name = 'foo' }
+          vim.lsp.start { cmd = server.cmd, name = 'foo', root_dir = 'bar' }
+          local foos = vim.lsp.get_clients()
+          return { #foos, foos[1].name, foos[2].name }
+        end)
+      )
+    end)
   end)
 
   describe('parsing tests', function()
-- 
cgit 


From 7121983c45d92349a6532f32dcde9f425e30781e Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Mon, 16 Dec 2024 16:16:57 +0000
Subject: refactor(man.lua): various changes

- Replace all uses of vim.regex with simpler Lua patterns.
- Replace all uses of vim.fn.substitute with string.gsub.
- Rework error handling so expected errors are passed back via a return.
  - These get routed up an passed to `vim.notify()`
  - Any other errors will cause a stack trace.
- Reworked the module initialization of `localfile_arg`
- Updated all type annotations.
- Refactored CLI completion by introduction a parse_cmdline()
  function.
- Simplified `show_toc()`
- Refactor highlighting
- Inline some functions
- Fix completion on MacOS 13 and earlier.
  - Prefer `manpath -q` over `man -w`
- Make completion more efficient by avoiding vim.fn.sort and vim.fn.uniq
  - Reimplement using a single loop
---
 runtime/lua/man.lua                 | 691 +++++++++++++++++-------------------
 runtime/plugin/man.lua              |  11 +-
 test/functional/plugin/man_spec.lua |  20 +-
 3 files changed, 350 insertions(+), 372 deletions(-)

diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index bf2cc477fd..e322a1f680 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -1,30 +1,19 @@
 local api, fn = vim.api, vim.fn
 
-local FIND_ARG = '-w'
-local localfile_arg = true -- Always use -l if possible. #6683
-
----@type table[]
-local buf_hls = {}
-
 local M = {}
 
-local function man_error(msg)
-  M.errormsg = 'man.lua: ' .. vim.inspect(msg)
-  error(M.errormsg)
-end
-
--- Run a system command and timeout after 30 seconds.
----@param cmd string[]
----@param silent boolean?
----@param env? table
----@return string
+--- Run a system command and timeout after 10 seconds.
+--- @param cmd string[]
+--- @param silent boolean?
+--- @param env? table
+--- @return string
 local function system(cmd, silent, env)
   local r = vim.system(cmd, { env = env, timeout = 10000 }):wait()
 
   if not silent then
     if r.code ~= 0 then
       local cmd_str = table.concat(cmd, ' ')
-      man_error(string.format("command error '%s': %s", cmd_str, r.stderr))
+      error(string.format("command error '%s': %s", cmd_str, r.stderr))
     end
     assert(r.stdout ~= '')
   end
@@ -32,65 +21,64 @@ local function system(cmd, silent, env)
   return assert(r.stdout)
 end
 
----@param line string
----@param linenr integer
-local function highlight_line(line, linenr)
-  ---@type string[]
+--- @enum Man.Attribute
+local Attrs = {
+  None = 0,
+  Bold = 1,
+  Underline = 2,
+  Italic = 3,
+}
+
+--- @param line string
+--- @param row integer
+--- @param hls {attr:Man.Attribute,row:integer,start:integer,final:integer}[]
+--- @return string
+local function render_line(line, row, hls)
+  --- @type string[]
   local chars = {}
   local prev_char = ''
   local overstrike, escape, osc8 = false, false, false
 
-  ---@type table
-  local hls = {} -- Store highlight groups as { attr, start, final }
-
-  local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3
-  local hl_groups = { [BOLD] = 'manBold', [UNDERLINE] = 'manUnderline', [ITALIC] = 'manItalic' }
-  local attr = NONE
+  local attr = Attrs.None
   local byte = 0 -- byte offset
 
-  local function end_attr_hl(attr_)
-    for i, hl in ipairs(hls) do
-      if hl.attr == attr_ and hl.final == -1 then
-        hl.final = byte
-        hls[i] = hl
-      end
-    end
-  end
+  local hls_start = #hls + 1
 
+  --- @param code integer
   local function add_attr_hl(code)
     local continue_hl = true
     if code == 0 then
-      attr = NONE
+      attr = Attrs.None
       continue_hl = false
     elseif code == 1 then
-      attr = BOLD
+      attr = Attrs.Bold
     elseif code == 22 then
-      attr = BOLD
+      attr = Attrs.Bold
       continue_hl = false
     elseif code == 3 then
-      attr = ITALIC
+      attr = Attrs.Italic
     elseif code == 23 then
-      attr = ITALIC
+      attr = Attrs.Italic
       continue_hl = false
     elseif code == 4 then
-      attr = UNDERLINE
+      attr = Attrs.Underline
     elseif code == 24 then
-      attr = UNDERLINE
+      attr = Attrs.Underline
       continue_hl = false
     else
-      attr = NONE
+      attr = Attrs.None
       return
     end
 
     if continue_hl then
-      hls[#hls + 1] = { attr = attr, start = byte, final = -1 }
+      hls[#hls + 1] = { attr = attr, row = row, start = byte, final = -1 }
     else
-      if attr == NONE then
-        for a, _ in pairs(hl_groups) do
-          end_attr_hl(a)
+      for _, a in pairs(attr == Attrs.None and Attrs or { attr }) do
+        for i = hls_start, #hls do
+          if hls[i].attr == a and hls[i].final == -1 then
+            hls[i].final = byte
+          end
         end
-      else
-        end_attr_hl(attr)
       end
     end
   end
@@ -103,11 +91,11 @@ local function highlight_line(line, linenr)
     if overstrike then
       local last_hl = hls[#hls]
       if char == prev_char then
-        if char == '_' and attr == ITALIC and last_hl and last_hl.final == byte then
+        if char == '_' and attr == Attrs.Italic and last_hl and last_hl.final == byte then
           -- This underscore is in the middle of an italic word
-          attr = ITALIC
+          attr = Attrs.Italic
         else
-          attr = BOLD
+          attr = Attrs.Bold
         end
       elseif prev_char == '_' then
         -- Even though underline is strictly what this should be. _ was used by nroff to
@@ -116,26 +104,26 @@ local function highlight_line(line, linenr)
         -- See:
         -- - https://unix.stackexchange.com/questions/274658/purpose-of-ascii-text-with-overstriking-file-format/274795#274795
         -- - https://cmd.inp.nsk.su/old/cmd2/manuals/unix/UNIX_Unleashed/ch08.htm
-        -- attr = UNDERLINE
-        attr = ITALIC
+        -- attr = Attrs.Underline
+        attr = Attrs.Italic
       elseif prev_char == '+' and char == 'o' then
         -- bullet (overstrike text '+^Ho')
-        attr = BOLD
+        attr = Attrs.Bold
         char = '·'
       elseif prev_char == '·' and char == 'o' then
         -- bullet (additional handling for '+^H+^Ho^Ho')
-        attr = BOLD
+        attr = Attrs.Bold
         char = '·'
       else
         -- use plain char
-        attr = NONE
+        attr = Attrs.None
       end
 
       -- Grow the previous highlight group if possible
       if last_hl and last_hl.attr == attr and last_hl.final == byte then
         last_hl.final = byte + #char
       else
-        hls[#hls + 1] = { attr = attr, start = byte, final = byte + #char }
+        hls[#hls + 1] = { attr = attr, row = row, start = byte, final = byte + #char }
       end
 
       overstrike = false
@@ -154,14 +142,15 @@ local function highlight_line(line, linenr)
       -- We only want to match against SGR sequences, which consist of ESC
       -- followed by '[', then a series of parameter and intermediate bytes in
       -- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117)
-      ---@type string?
+      --- @type string?
       local sgr = prev_char:match('^%[([\032-\063]*)m$')
       -- Ignore escape sequences with : characters, as specified by ITU's T.416
       -- Open Document Architecture and interchange format.
-      if sgr and not string.find(sgr, ':') then
-        local match ---@type string?
+      if sgr and not sgr:find(':') then
+        local match --- @type string?
         while sgr and #sgr > 0 do
           -- Match against SGR parameters, which may be separated by ';'
+          --- @type string?, string?
           match, sgr = sgr:match('^(%d*);?(.*)')
           add_attr_hl(match + 0) -- coerce to number
         end
@@ -187,55 +176,40 @@ local function highlight_line(line, linenr)
     end
   end
 
-  for _, hl in ipairs(hls) do
-    if hl.attr ~= NONE then
-      buf_hls[#buf_hls + 1] = {
-        0,
-        -1,
-        hl_groups[hl.attr],
-        linenr - 1,
-        hl.start,
-        hl.final,
-      }
-    end
-  end
-
   return table.concat(chars, '')
 end
 
+local HlGroups = {
+  [Attrs.Bold] = 'manBold',
+  [Attrs.Underline] = 'manUnderline',
+  [Attrs.Italic] = 'manItalic',
+}
+
 local function highlight_man_page()
   local mod = vim.bo.modifiable
   vim.bo.modifiable = true
 
   local lines = api.nvim_buf_get_lines(0, 0, -1, false)
+
+  --- @type {attr:Man.Attribute,row:integer,start:integer,final:integer}[]
+  local hls = {}
+
   for i, line in ipairs(lines) do
-    lines[i] = highlight_line(line, i)
+    lines[i] = render_line(line, i - 1, hls)
   end
+
   api.nvim_buf_set_lines(0, 0, -1, false, lines)
 
-  for _, args in ipairs(buf_hls) do
-    api.nvim_buf_add_highlight(unpack(args))
+  for _, hl in ipairs(hls) do
+    api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final)
   end
-  buf_hls = {}
 
   vim.bo.modifiable = mod
 end
 
--- replace spaces in a man page name with underscores
--- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)';
--- while editing SQL source code, it's nice to visually select 'CREATE TABLE'
--- and hit 'K', which requires this transformation
----@param str string
----@return string
-local function spaces_to_underscores(str)
-  local res = str:gsub('%s', '_')
-  return res
-end
-
----@param sect string|nil
----@param name string|nil
----@param silent boolean
-local function get_path(sect, name, silent)
+--- @param name? string
+--- @param sect? string
+local function get_path(name, sect)
   name = name or ''
   sect = sect or ''
   -- Some man implementations (OpenBSD) return all available paths from the
@@ -256,14 +230,14 @@ local function get_path(sect, name, silent)
   --
   -- Finally, we can avoid relying on -S or -s here since they are very
   -- inconsistently supported. Instead, call -w with a section and a name.
-  local cmd ---@type string[]
+  local cmd --- @type string[]
   if sect == '' then
-    cmd = { 'man', FIND_ARG, name }
+    cmd = { 'man', '-w', name }
   else
-    cmd = { 'man', FIND_ARG, sect, name }
+    cmd = { 'man', '-w', sect, name }
   end
 
-  local lines = system(cmd, silent)
+  local lines = system(cmd, true)
   local results = vim.split(lines, '\n', { trimempty = true })
 
   if #results == 0 then
@@ -279,87 +253,73 @@ local function get_path(sect, name, silent)
   end
 
   -- find any that match the specified name
-  ---@param v string
+  --- @param v string
   local namematches = vim.tbl_filter(function(v)
     local tail = fn.fnamemodify(v, ':t')
-    return string.find(tail, name, 1, true)
+    return tail:find(name, 1, true) ~= nil
   end, results) or {}
   local sectmatches = {}
 
   if #namematches > 0 and sect ~= '' then
-    ---@param v string
+    --- @param v string
     sectmatches = vim.tbl_filter(function(v)
       return fn.fnamemodify(v, ':e') == sect
     end, namematches)
   end
 
-  return fn.substitute(sectmatches[1] or namematches[1] or results[1], [[\n\+$]], '', '')
+  return (sectmatches[1] or namematches[1] or results[1]):gsub('\n+$', '')
 end
 
----@param text string
----@param pat_or_re string
-local function matchstr(text, pat_or_re)
-  local re = type(pat_or_re) == 'string' and vim.regex(pat_or_re) or pat_or_re
-
-  ---@type integer, integer
-  local s, e = re:match_str(text)
-
-  if s == nil then
-    return
+--- Attempt to extract the name and sect out of 'name(sect)'
+--- otherwise just return the largest string of valid characters in ref
+--- @param ref string
+--- @return string? name
+--- @return string? sect
+--- @return string? err
+local function parse_ref(ref)
+  if ref == '' or ref:sub(1, 1) == '-' then
+    return nil, nil, ('invalid manpage reference "%s"'):format(ref)
   end
 
-  return text:sub(vim.str_utfindex(text, 'utf-32', s) + 1, vim.str_utfindex(text, 'utf-32', e))
-end
+  -- match "()"
+  -- note: name can contain spaces
+  local name, sect = ref:match('([^()]+)%(([^()]+)%)')
+  if name then
+    -- see ':Man 3X curses' on why tolower.
+    -- TODO(nhooyr) Not sure if this is portable across OSs
+    -- but I have not seen a single uppercase section.
+    return name, sect:lower()
+  end
 
--- attempt to extract the name and sect out of 'name(sect)'
--- otherwise just return the largest string of valid characters in ref
----@param ref string
----@return string, string
-local function extract_sect_and_name_ref(ref)
-  ref = ref or ''
-  if ref:sub(1, 1) == '-' then -- try ':Man -pandoc' with this disabled.
-    man_error("manpage name cannot start with '-'")
-  end
-  local ref1 = ref:match('[^()]+%([^()]+%)')
-  if not ref1 then
-    local name = ref:match('[^()]+')
-    if not name then
-      man_error('manpage reference cannot contain only parentheses: ' .. ref)
-    end
-    return '', name
-  end
-  local parts = vim.split(ref1, '(', { plain = true })
-  -- see ':Man 3X curses' on why tolower.
-  -- TODO(nhooyr) Not sure if this is portable across OSs
-  -- but I have not seen a single uppercase section.
-  local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower()
-  local name = parts[1]
-  return sect, name
+  name = ref:match('[^()]+')
+  if not name then
+    return nil, nil, ('invalid manpage reference "%s"'):format(ref)
+  end
+  return name
 end
 
--- find_path attempts to find the path to a manpage
--- based on the passed section and name.
---
--- 1. If manpage could not be found with the given sect and name,
---    then try all the sections in b:man_default_sects.
--- 2. If it still could not be found, then we try again without a section.
--- 3. If still not found but $MANSECT is set, then we try again with $MANSECT
---    unset.
--- 4. If a path still wasn't found, return nil.
----@param sect string?
----@param name string
-function M.find_path(sect, name)
+--- Attempts to find the path to a manpage based on the passed section and name.
+---
+--- 1. If manpage could not be found with the given sect and name,
+---    then try all the sections in b:man_default_sects.
+--- 2. If it still could not be found, then we try again without a section.
+--- 3. If still not found but $MANSECT is set, then we try again with $MANSECT
+---    unset.
+--- 4. If a path still wasn't found, return nil.
+--- @param name string?
+--- @param sect string?
+--- @return string? path
+function M._find_path(name, sect)
   if sect and sect ~= '' then
-    local ret = get_path(sect, name, true)
+    local ret = get_path(name, sect)
     if ret then
       return ret
     end
   end
 
   if vim.b.man_default_sects ~= nil then
-    local sects = vim.split(vim.b.man_default_sects, ',', { plain = true, trimempty = true })
-    for _, sec in ipairs(sects) do
-      local ret = get_path(sec, name, true)
+    for sec in vim.gsplit(vim.b.man_default_sects, ',', { trimempty = true }) do
+      local ret = get_path(name, sec)
       if ret then
         return ret
       end
@@ -367,17 +327,18 @@ function M.find_path(sect, name)
   end
 
   -- if none of the above worked, we will try with no section
-  local res_empty_sect = get_path('', name, true)
-  if res_empty_sect then
-    return res_empty_sect
+  local ret = get_path(name)
+  if ret then
+    return ret
   end
 
   -- if that still didn't work, we will check for $MANSECT and try again with it
   -- unset
   if vim.env.MANSECT then
+    --- @type string
     local mansect = vim.env.MANSECT
     vim.env.MANSECT = nil
-    local res = get_path('', name, true)
+    local res = get_path(name)
     vim.env.MANSECT = mansect
     if res then
       return res
@@ -388,24 +349,27 @@ function M.find_path(sect, name)
   return nil
 end
 
-local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]])
-
--- Extracts the name/section from the 'path/name.sect', because sometimes the actual section is
--- more specific than what we provided to `man` (try `:Man 3 App::CLI`).
--- Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we
--- still want the name of the buffer to be 'printf'.
----@param path string
----@return string, string
-local function extract_sect_and_name_path(path)
+--- Extracts the name/section from the 'path/name.sect', because sometimes the
+--- actual section is more specific than what we provided to `man`
+--- (try `:Man 3 App::CLI`). Also on linux, name seems to be case-insensitive.
+--- So for `:Man PRIntf`, we still want the name of the buffer to be 'printf'.
+--- @param path string
+--- @return string name
+--- @return string sect
+local function parse_path(path)
   local tail = fn.fnamemodify(path, ':t')
-  if EXT_RE:match_str(path) then -- valid extensions
+  if
+    path:match('%.[glx]z$')
+    or path:match('%.bz2$')
+    or path:match('%.lzma$')
+    or path:match('%.Z$')
+  then
     tail = fn.fnamemodify(tail, ':r')
   end
-  local name, sect = tail:match('^(.+)%.([^.]+)$')
-  return sect, name
+  return tail:match('^(.+)%.([^.]+)$')
 end
 
----@return boolean
+--- @return boolean
 local function find_man()
   if vim.bo.filetype == 'man' then
     return true
@@ -433,22 +397,32 @@ local function set_options()
   vim.bo.filetype = 'man'
 end
 
----@param path string
----@param silent boolean?
----@return string
+--- Always use -l if possible. #6683
+--- @type boolean?
+local localfile_arg
+
+--- @param path string
+--- @param silent boolean?
+--- @return string
 local function get_page(path, silent)
   -- Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065).
   -- Soft-wrap: ftplugin/man.lua sets wrap/breakindent/….
   -- Hard-wrap: driven by `man`.
-  local manwidth ---@type integer|string
+  local manwidth --- @type integer|string
   if (vim.g.man_hardwrap or 1) ~= 1 then
     manwidth = 999
   elseif vim.env.MANWIDTH then
-    manwidth = vim.env.MANWIDTH
+    manwidth = vim.env.MANWIDTH --- @type string|integer
   else
     manwidth = api.nvim_win_get_width(0) - vim.o.wrapmargin
   end
 
+  if localfile_arg == nil then
+    local mpath = get_path('man')
+    -- Check for -l support.
+    localfile_arg = (mpath and system({ 'man', '-l', mpath }, true) or '') ~= ''
+  end
+
   local cmd = localfile_arg and { 'man', '-l', path } or { 'man', path }
 
   -- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
@@ -461,47 +435,17 @@ local function get_page(path, silent)
   })
 end
 
----@param lnum integer
----@return string
-local function getline(lnum)
-  ---@diagnostic disable-next-line
-  return fn.getline(lnum)
-end
-
----@param page string
-local function put_page(page)
-  vim.bo.modifiable = true
-  vim.bo.readonly = false
-  vim.bo.swapfile = false
-
-  api.nvim_buf_set_lines(0, 0, -1, false, vim.split(page, '\n'))
-
-  while getline(1):match('^%s*$') do
-    api.nvim_buf_set_lines(0, 0, 1, false, {})
-  end
-  -- XXX: nroff justifies text by filling it with whitespace.  That interacts
-  -- badly with our use of $MANWIDTH=999.  Hack around this by using a fixed
-  -- size for those whitespace regions.
-  -- Use try/catch to avoid setting v:errmsg.
-  vim.cmd([[
-    try
-      keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
-    catch
-    endtry
-  ]])
-  vim.cmd('1') -- Move cursor to first line
-  highlight_man_page()
-  set_options()
-end
-
+--- @param path string
+--- @param psect string
 local function format_candidate(path, psect)
-  if matchstr(path, [[\.\%(pdf\|in\)$]]) then -- invalid extensions
+  if vim.endswith(path, '.pdf') or vim.endswith(path, '.in') then
+    -- invalid extensions
     return ''
   end
-  local sect, name = extract_sect_and_name_path(path)
+  local name, sect = parse_path(path)
   if sect == psect then
     return name
-  elseif sect and name and matchstr(sect, psect .. '.\\+$') then -- invalid extensions
+  elseif sect:match(psect .. '.+$') then -- invalid extensions
     -- We include the section if the user provided section is a prefix
     -- of the actual section.
     return ('%s(%s)'):format(name, sect)
@@ -509,63 +453,54 @@ local function format_candidate(path, psect)
   return ''
 end
 
----@generic T
----@param list T[]
----@param elem T
----@return T[]
-local function move_elem_to_head(list, elem)
-  ---@diagnostic disable-next-line:no-unknown
-  local list1 = vim.tbl_filter(function(v)
-    return v ~= elem
-  end, list)
-  return { elem, unpack(list1) }
-end
-
----@param sect string
----@param name string
----@return string[]
-local function get_paths(sect, name)
+--- @param name string
+--- @param sect? string
+--- @return string[] paths
+--- @return string? err
+local function get_paths(name, sect)
   -- Try several sources for getting the list man directories:
-  --   1. `man -w` (works on most systems)
-  --   2. `manpath`
+  --   1. `manpath -q`
+  --   2. `man -w` (works on most systems)
   --   3. $MANPATH
-  local mandirs_raw = vim.F.npcall(system, { 'man', FIND_ARG })
-    or vim.F.npcall(system, { 'manpath', '-q' })
+  --
+  -- Note we prefer `manpath -q` because `man -w`:
+  -- - does not work on MacOS 14 and later.
+  -- - only returns '/usr/bin/man' on MacOS 13 and earlier.
+  --- @type string?
+  local mandirs_raw = vim.F.npcall(system, { 'manpath', '-q' })
+    or vim.F.npcall(system, { 'man', '-w' })
     or vim.env.MANPATH
 
   if not mandirs_raw then
-    man_error("Could not determine man directories from: 'man -w', 'manpath' or $MANPATH")
+    return {}, "Could not determine man directories from: 'man -w', 'manpath' or $MANPATH"
   end
 
   local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',')
-  ---@type string[]
+
+  sect = sect or ''
+
+  --- @type string[]
   local paths = fn.globpath(mandirs, 'man[^\\/]*/' .. name .. '*.' .. sect .. '*', false, true)
 
   -- Prioritize the result from find_path as it obeys b:man_default_sects.
-  local first = M.find_path(sect, name)
+  local first = M._find_path(name, sect)
   if first then
-    paths = move_elem_to_head(paths, first)
+    --- @param v string
+    paths = vim.tbl_filter(function(v)
+      return v ~= first
+    end, paths)
+    table.insert(paths, 1, first)
   end
 
   return paths
 end
 
----@param sect string
----@param psect string
----@param name string
----@return string[]
-local function complete(sect, psect, name)
-  local pages = get_paths(sect, name)
-  -- We remove duplicates in case the same manpage in different languages was found.
-  return fn.uniq(fn.sort(vim.tbl_map(function(v)
-    return format_candidate(v, psect)
-  end, pages) or {}, 'i'))
-end
-
--- see extract_sect_and_name_ref on why tolower(sect)
----@param arg_lead string
----@param cmd_line string
-function M.man_complete(arg_lead, cmd_line, _)
+--- @param arg_lead string
+--- @param cmd_line string
+--- @return string? sect
+--- @return string? psect
+--- @return string? name
+local function parse_cmdline(arg_lead, cmd_line)
   local args = vim.split(cmd_line, '%s+', { trimempty = true })
   local cmd_offset = fn.index(args, 'Man')
   if cmd_offset > 0 then
@@ -575,13 +510,13 @@ function M.man_complete(arg_lead, cmd_line, _)
   end
 
   if #args > 3 then
-    return {}
+    return
   end
 
   if #args == 1 then
     -- returning full completion is laggy. Require some arg_lead to complete
-    -- return complete('', '', '')
-    return {}
+    -- return '', '', ''
+    return
   end
 
   if arg_lead:match('^[^()]+%([^()]*$') then
@@ -590,18 +525,19 @@ function M.man_complete(arg_lead, cmd_line, _)
     -- It will offer 'priclass.d(1m)' even though section is specified as 1.
     local tmp = vim.split(arg_lead, '(', { plain = true })
     local name = tmp[1]
+    -- See extract_sect_and_name_ref on why :lower()
     local sect = (tmp[2] or ''):lower()
-    return complete(sect, '', name)
+    return sect, '', name
   end
 
   if not args[2]:match('^[^()]+$') then
     -- cursor (|) is at ':Man 3() |' or ':Man (3|' or ':Man 3() pri|'
     -- or ':Man 3() pri |'
-    return {}
+    return
   end
 
   if #args == 2 then
-    ---@type string, string
+    --- @type string, string
     local name, sect
     if arg_lead == '' then
       -- cursor (|) is at ':Man 1 |'
@@ -617,52 +553,77 @@ function M.man_complete(arg_lead, cmd_line, _)
       name = arg_lead
       sect = ''
     end
-    return complete(sect, sect, name)
+    return sect, sect, name
   end
 
   if not arg_lead:match('[^()]+$') then
     -- cursor (|) is at ':Man 3 printf |' or ':Man 3 (pr)i|'
-    return {}
+    return
   end
 
   -- cursor (|) is at ':Man 3 pri|'
-  local name = arg_lead
-  local sect = args[2]:lower()
-  return complete(sect, sect, name)
+  local name, sect = arg_lead, args[2]:lower()
+  return sect, sect, name
 end
 
----@param pattern string
----@return {name:string,filename:string,cmd:string}[]
-function M.goto_tag(pattern, _, _)
-  local sect, name = extract_sect_and_name_ref(pattern)
+--- @param arg_lead string
+--- @param cmd_line string
+function M.man_complete(arg_lead, cmd_line)
+  local sect, psect, name = parse_cmdline(arg_lead, cmd_line)
+  if not (sect and psect and name) then
+    return {}
+  end
 
-  local paths = get_paths(sect, name)
-  ---@type {name:string,title:string}[]
-  local structured = {}
+  local pages = get_paths(name, sect)
 
-  for _, path in ipairs(paths) do
-    sect, name = extract_sect_and_name_path(path)
-    if sect and name then
-      structured[#structured + 1] = {
-        name = name,
-        title = name .. '(' .. sect .. ')',
-      }
+  -- We check for duplicates in case the same manpage in different languages
+  -- was found.
+  local pages_fmt = {} --- @type string[]
+  local pages_fmt_keys = {} --- @type table
+  for _, v in ipairs(pages) do
+    local x = format_candidate(v, psect)
+    local xl = x:lower() -- ignore case when searching avoiding duplicates
+    if not pages_fmt_keys[xl] then
+      pages_fmt[#pages_fmt + 1] = x
+      pages_fmt_keys[xl] = true
     end
   end
+  table.sort(pages_fmt)
 
-  ---@param entry {name:string,title:string}
-  return vim.tbl_map(function(entry)
-    return {
-      name = entry.name,
-      filename = 'man://' .. entry.title,
+  return pages_fmt
+end
+
+--- @param pattern string
+--- @return {name:string,filename:string,cmd:string}[]
+function M.goto_tag(pattern, _, _)
+  local name, sect, err = parse_ref(pattern)
+  if err then
+    error(err)
+  end
+
+  local paths, err2 = get_paths(assert(name), sect)
+  if err2 then
+    error(err2)
+  end
+
+  --- @type table[]
+  local ret = {}
+
+  for _, path in ipairs(paths) do
+    local pname, psect = parse_path(path)
+    ret[#ret + 1] = {
+      name = pname,
+      filename = ('man://%s(%s)'):format(pname, psect),
       cmd = '1',
     }
-  end, structured)
+  end
+
+  return ret
 end
 
--- Called when Nvim is invoked as $MANPAGER.
+--- Called when Nvim is invoked as $MANPAGER.
 function M.init_pager()
-  if getline(1):match('^%s*$') then
+  if fn.getline(1):match('^%s*$') then
     api.nvim_buf_set_lines(0, 0, 1, false, {})
   else
     vim.cmd('keepjumps 1')
@@ -670,9 +631,10 @@ function M.init_pager()
   highlight_man_page()
   -- Guess the ref from the heading (which is usually uppercase, so we cannot
   -- know the correct casing, cf. `man glDrawArraysInstanced`).
-  local ref = fn.substitute(matchstr(getline(1), [[^[^)]\+)]]) or '', ' ', '_', 'g')
-  local ok, res = pcall(extract_sect_and_name_ref, ref)
-  vim.b.man_sect = ok and res or ''
+  --- @type string
+  local ref = (fn.getline(1):match('^[^)]+%)') or ''):gsub(' ', '_')
+  local _, sect, err = pcall(parse_ref, ref)
+  vim.b.man_sect = err ~= nil and sect or ''
 
   if not fn.bufname('%'):match('man://') then -- Avoid duplicate buffers, E95.
     vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } })
@@ -681,50 +643,64 @@ function M.init_pager()
   set_options()
 end
 
----@param count integer
----@param args string[]
+--- Combine the name and sect into a manpage reference so that all
+--- verification/extraction can be kept in a single function.
+--- @param args string[]
+--- @return string? ref
+local function ref_from_args(args)
+  if #args <= 1 then
+    return args[1]
+  elseif args[1]:match('^%d$') or args[1]:match('^%d%a') or args[1]:match('^%a$') then
+    -- NB: Valid sections are not only digits, but also:
+    --  -  (see POSIX mans),
+    --  - and even  and  (see, for example, by tcl/tk)
+    -- NB2: don't optimize to :match("^%d"), as it will match manpages like
+    --    441toppm and others whose name starts with digit
+    local sect = args[1]
+    table.remove(args, 1)
+    local name = table.concat(args, ' ')
+    return ('%s(%s)'):format(name, sect)
+  end
+
+  return table.concat(args, ' ')
+end
+
+--- @param count integer
+--- @param args string[]
+--- @return string? err
 function M.open_page(count, smods, args)
-  local ref ---@type string
-  if #args == 0 then
+  local ref = ref_from_args(args)
+  if not ref then
     ref = vim.bo.filetype == 'man' and fn.expand('') or fn.expand('')
     if ref == '' then
-      man_error('no identifier under cursor')
-    end
-  elseif #args == 1 then
-    ref = args[1]
-  else
-    -- Combine the name and sect into a manpage reference so that all
-    -- verification/extraction can be kept in a single function.
-    if args[1]:match('^%d$') or args[1]:match('^%d%a') or args[1]:match('^%a$') then
-      -- NB: Valid sections are not only digits, but also:
-      --  -  (see POSIX mans),
-      --  - and even  and  (see, for example, by tcl/tk)
-      -- NB2: don't optimize to :match("^%d"), as it will match manpages like
-      --    441toppm and others whose name starts with digit
-      local sect = args[1]
-      table.remove(args, 1)
-      local name = table.concat(args, ' ')
-      ref = ('%s(%s)'):format(name, sect)
-    else
-      ref = table.concat(args, ' ')
+      return 'no identifier under cursor'
     end
   end
 
-  local sect, name = extract_sect_and_name_ref(ref)
+  local name, sect, err = parse_ref(ref)
+  if err then
+    return err
+  end
+  assert(name)
+
   if count >= 0 then
     sect = tostring(count)
   end
 
   -- Try both spaces and underscores, use the first that exists.
-  local path = M.find_path(sect, name)
-  if path == nil then
-    path = M.find_path(sect, spaces_to_underscores(name))
-    if path == nil then
-      man_error('no manual entry for ' .. name)
+  local path = M._find_path(name, sect)
+  if not path then
+    --- Replace spaces in a man page name with underscores
+    --- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)';
+    --- while editing SQL source code, it's nice to visually select 'CREATE TABLE'
+    --- and hit 'K', which requires this transformation
+    path = M._find_path(name:gsub('%s', '_'), sect)
+    if not path then
+      return 'no manual entry for ' .. name
     end
   end
 
-  sect, name = extract_sect_and_name_path(path)
+  name, sect = parse_path(path)
   local buf = api.nvim_get_current_buf()
   local save_tfu = vim.bo[buf].tagfunc
   vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag"
@@ -747,24 +723,51 @@ function M.open_page(count, smods, args)
 
   if not ok then
     error(ret)
-  else
-    set_options()
   end
+  set_options()
 
   vim.b.man_sect = sect
 end
 
--- Called when a man:// buffer is opened.
+--- Called when a man:// buffer is opened.
+--- @return string? err
 function M.read_page(ref)
-  local sect, name = extract_sect_and_name_ref(ref)
-  local path = M.find_path(sect, name)
-  if path == nil then
-    man_error('no manual entry for ' .. name)
+  local name, sect, err = parse_ref(ref)
+  if err then
+    return err
+  end
+
+  local path = M._find_path(name, sect)
+  if not path then
+    return 'no manual entry for ' .. name
   end
-  sect = extract_sect_and_name_path(path)
+
+  local _, sect1 = parse_path(path)
   local page = get_page(path)
-  vim.b.man_sect = sect
-  put_page(page)
+
+  vim.b.man_sect = sect1
+  vim.bo.modifiable = true
+  vim.bo.readonly = false
+  vim.bo.swapfile = false
+
+  api.nvim_buf_set_lines(0, 0, -1, false, vim.split(page, '\n'))
+
+  while fn.getline(1):match('^%s*$') do
+    api.nvim_buf_set_lines(0, 0, 1, false, {})
+  end
+  -- XXX: nroff justifies text by filling it with whitespace.  That interacts
+  -- badly with our use of $MANWIDTH=999.  Hack around this by using a fixed
+  -- size for those whitespace regions.
+  -- Use try/catch to avoid setting v:errmsg.
+  vim.cmd([[
+    try
+      keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
+    catch
+    endtry
+  ]])
+  vim.cmd('1') -- Move cursor to first line
+  highlight_man_page()
+  set_options()
 end
 
 function M.show_toc()
@@ -776,29 +779,18 @@ function M.show_toc()
     return
   end
 
-  ---@type {bufnr:integer, lnum:integer, text:string}[]
+  --- @type {bufnr:integer, lnum:integer, text:string}[]
   local toc = {}
 
   local lnum = 2
   local last_line = fn.line('$') - 1
-  local section_title_re = vim.regex([[^\%( \{3\}\)\=\S.*$]])
-  local flag_title_re = vim.regex([[^\s\+\%(+\|-\)\S\+]])
   while lnum and lnum < last_line do
-    local text = getline(lnum)
-    if section_title_re:match_str(text) then
-      -- if text is a section title
+    local text = fn.getline(lnum)
+    if text:match('^%s+[-+]%S') or text:match('^   %S') or text:match('^%S') then
       toc[#toc + 1] = {
         bufnr = bufnr,
         lnum = lnum,
-        text = text,
-      }
-    elseif flag_title_re:match_str(text) then
-      -- if text is a flag title. we strip whitespaces and prepend two
-      -- spaces to have a consistent format in the loclist.
-      toc[#toc + 1] = {
-        bufnr = bufnr,
-        lnum = lnum,
-        text = '  ' .. fn.substitute(text, [[^\s*\(.\{-}\)\s*$]], [[\1]], ''),
+        text = text:gsub('^%s+', ''):gsub('%s+$', ''),
       }
     end
     lnum = fn.nextnonblank(lnum + 1)
@@ -810,19 +802,4 @@ function M.show_toc()
   vim.w.qf_toc = bufname
 end
 
-local function init()
-  local path = get_path('', 'man', true)
-  local page ---@type string?
-  if path ~= nil then
-    -- Check for -l support.
-    page = get_page(path, true)
-  end
-
-  if page == '' or page == nil then
-    localfile_arg = false
-  end
-end
-
-init()
-
 return M
diff --git a/runtime/plugin/man.lua b/runtime/plugin/man.lua
index 512b1f63e8..d6d90208a2 100644
--- a/runtime/plugin/man.lua
+++ b/runtime/plugin/man.lua
@@ -8,9 +8,9 @@ vim.api.nvim_create_user_command('Man', function(params)
   if params.bang then
     man.init_pager()
   else
-    local ok, err = pcall(man.open_page, params.count, params.smods, params.fargs)
-    if not ok then
-      vim.notify(man.errormsg or err, vim.log.levels.ERROR)
+    local err = man.open_page(params.count, params.smods, params.fargs)
+    if err then
+      vim.notify('man.lua: ' .. err, vim.log.levels.ERROR)
     end
   end
 end, {
@@ -31,6 +31,9 @@ vim.api.nvim_create_autocmd('BufReadCmd', {
   pattern = 'man://*',
   nested = true,
   callback = function(params)
-    require('man').read_page(vim.fn.matchstr(params.match, 'man://\\zs.*'))
+    local err = require('man').read_page(assert(params.match:match('man://(.*)')))
+    if err then
+      vim.notify('man.lua: ' .. err, vim.log.levels.ERROR)
+    end
   end,
 })
diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua
index e3f3de6252..c1dbc6dac3 100644
--- a/test/functional/plugin/man_spec.lua
+++ b/test/functional/plugin/man_spec.lua
@@ -21,13 +21,12 @@ local function get_search_history(name)
     local man = require('man')
     local res = {}
     --- @diagnostic disable-next-line:duplicate-set-field
-    man.find_path = function(sect, name0)
+    man._find_path = function(name0, sect)
       table.insert(res, { sect, name0 })
       return nil
     end
-    local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args)
-    assert(not ok)
-    assert(rv and rv:match('no manual entry'))
+    local err = man.open_page(-1, { tab = 0 }, args)
+    assert(err and err:match('no manual entry'))
     return res
   end)
 end
@@ -225,7 +224,7 @@ describe(':Man', function()
     matches('^/.+', actual_file)
     local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' }
     matches(
-      ('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format(
+      ('Error detected while processing command line:\r\n' .. 'man.lua: no manual entry for %s'):format(
         pesc(actual_file)
       ),
       fn.system(args, { '' })
@@ -235,8 +234,8 @@ describe(':Man', function()
 
   it('tries variants with spaces, underscores #22503', function()
     eq({
-      { '', 'NAME WITH SPACES' },
-      { '', 'NAME_WITH_SPACES' },
+      { vim.NIL, 'NAME WITH SPACES' },
+      { vim.NIL, 'NAME_WITH_SPACES' },
     }, get_search_history('NAME WITH SPACES'))
     eq({
       { '3', 'some other man' },
@@ -255,8 +254,8 @@ describe(':Man', function()
       { 'n', 'some_other_man' },
     }, get_search_history('n some other man'))
     eq({
-      { '', '123some other man' },
-      { '', '123some_other_man' },
+      { vim.NIL, '123some other man' },
+      { vim.NIL, '123some_other_man' },
     }, get_search_history('123some other man'))
     eq({
       { '1', 'other_man' },
@@ -265,11 +264,10 @@ describe(':Man', function()
   end)
 
   it('can complete', function()
-    t.skip(t.is_os('mac') and t.is_arch('x86_64'), 'not supported on intel mac')
     eq(
       true,
       exec_lua(function()
-        return #require('man').man_complete('f', 'Man g') > 0
+        return #require('man').man_complete('f', 'Man f') > 0
       end)
     )
   end)
-- 
cgit 


From 3db3947b0ed256d498b09eef95c4969e28387e66 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Wed, 18 Dec 2024 11:41:05 -0600
Subject: fix(terminal): restore cursor from 'guicursor' on TermLeave (#31620)

Fixes: https://github.com/neovim/neovim/issues/31612
---
 src/nvim/terminal.c                      |  7 +--
 test/functional/terminal/cursor_spec.lua | 97 ++++++++++++++++++++------------
 2 files changed, 62 insertions(+), 42 deletions(-)

diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index fa08f3d6ca..969c539f97 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -641,9 +641,6 @@ bool terminal_enter(void)
   curwin->w_p_so = 0;
   curwin->w_p_siso = 0;
 
-  // Save the existing cursor entry since it may be modified by the application
-  cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM];
-
   // Update the cursor shape table and flush changes to the UI
   s->term->pending.cursor = true;
   refresh_cursor(s->term);
@@ -674,8 +671,8 @@ bool terminal_enter(void)
   RedrawingDisabled = s->save_rd;
   apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
 
-  shape_table[SHAPE_IDX_TERM] = save_cursorentry;
-  ui_mode_info_set();
+  // Restore the terminal cursor to what is set in 'guicursor'
+  (void)parse_shape_opt(SHAPE_CURSOR);
 
   if (save_curwin == curwin->handle) {  // Else: window was closed.
     curwin->w_p_cul = save_w_p_cul;
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index d4d08fb24c..657468c03e 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -15,9 +15,20 @@ local skip = t.skip
 describe(':terminal cursor', function()
   local screen
 
+  local terminal_mode_idx ---@type number
+
   before_each(function()
     clear()
     screen = tt.setup_screen()
+
+    if terminal_mode_idx == nil then
+      for i, v in ipairs(screen._mode_info) do
+        if v.name == 'terminal' then
+          terminal_mode_idx = i
+        end
+      end
+      assert(terminal_mode_idx)
+    end
   end)
 
   it('moves the screen cursor when focused', function()
@@ -143,13 +154,6 @@ describe(':terminal cursor', function()
 
   it('can be modified by application #3681', function()
     skip(is_os('win'), '#31587')
-    local idx ---@type number
-    for i, v in ipairs(screen._mode_info) do
-      if v.name == 'terminal' then
-        idx = i
-      end
-    end
-    assert(idx)
 
     local states = {
       [1] = { blink = true, shape = 'block' },
@@ -171,13 +175,13 @@ describe(':terminal cursor', function()
       ]],
         condition = function()
           if v.blink then
-            eq(500, screen._mode_info[idx].blinkon)
-            eq(500, screen._mode_info[idx].blinkoff)
+            eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+            eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
           else
-            eq(0, screen._mode_info[idx].blinkon)
-            eq(0, screen._mode_info[idx].blinkoff)
+            eq(0, screen._mode_info[terminal_mode_idx].blinkon)
+            eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
           end
-          eq(v.shape, screen._mode_info[idx].cursor_shape)
+          eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape)
         end,
       })
     end
@@ -191,20 +195,13 @@ describe(':terminal cursor', function()
     ]])
 
     -- Cursor returns to default on TermLeave
-    eq(500, screen._mode_info[idx].blinkon)
-    eq(500, screen._mode_info[idx].blinkoff)
-    eq('block', screen._mode_info[idx].cursor_shape)
+    eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+    eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
+    eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
   end)
 
   it('can be modified per terminal', function()
     skip(is_os('win'), '#31587')
-    local idx ---@type number
-    for i, v in ipairs(screen._mode_info) do
-      if v.name == 'terminal' then
-        idx = i
-      end
-    end
-    assert(idx)
 
     -- Set cursor to vertical bar with blink
     tt.feed_csi('5 q')
@@ -216,9 +213,9 @@ describe(':terminal cursor', function()
       {3:-- TERMINAL --}                                    |
     ]],
       condition = function()
-        eq(500, screen._mode_info[idx].blinkon)
-        eq(500, screen._mode_info[idx].blinkoff)
-        eq('vertical', screen._mode_info[idx].cursor_shape)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
+        eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
       end,
     })
 
@@ -231,9 +228,9 @@ describe(':terminal cursor', function()
       {3:-- TERMINAL --}                                    |
     ]],
       condition = function()
-        eq(500, screen._mode_info[idx].blinkon)
-        eq(500, screen._mode_info[idx].blinkoff)
-        eq('vertical', screen._mode_info[idx].cursor_shape)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
+        eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
       end,
     })
 
@@ -256,9 +253,9 @@ describe(':terminal cursor', function()
     ]],
       condition = function()
         -- New terminal, cursor resets to defaults
-        eq(500, screen._mode_info[idx].blinkon)
-        eq(500, screen._mode_info[idx].blinkoff)
-        eq('block', screen._mode_info[idx].cursor_shape)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
+        eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
       end,
     })
 
@@ -275,9 +272,9 @@ describe(':terminal cursor', function()
       {3:-- TERMINAL --}                                    |
     ]],
       condition = function()
-        eq(0, screen._mode_info[idx].blinkon)
-        eq(0, screen._mode_info[idx].blinkoff)
-        eq('horizontal', screen._mode_info[idx].cursor_shape)
+        eq(0, screen._mode_info[terminal_mode_idx].blinkon)
+        eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
+        eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape)
       end,
     })
 
@@ -294,9 +291,9 @@ describe(':terminal cursor', function()
       {3:-- TERMINAL --}                                    |
     ]],
       condition = function()
-        eq(500, screen._mode_info[idx].blinkon)
-        eq(500, screen._mode_info[idx].blinkoff)
-        eq('vertical', screen._mode_info[idx].cursor_shape)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+        eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
+        eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
       end,
     })
   end)
@@ -326,6 +323,32 @@ describe(':terminal cursor', function()
       {3:-- TERMINAL --}                                    |
     ]])
   end)
+
+  it('preserves guicursor value on TermLeave #31612', function()
+    eq(3, screen._mode_info[terminal_mode_idx].hl_id)
+
+    -- Change 'guicursor' while terminal mode is active
+    command('set guicursor+=t:Error')
+
+    local error_hl_id = call('hlID', 'Error')
+
+    screen:expect({
+      condition = function()
+        eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
+      end,
+    })
+
+    -- Exit terminal mode
+    feed([[]])
+
+    screen:expect([[
+      tty ready                                         |
+      ^                                                  |
+                                                        |*5
+    ]])
+
+    eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
+  end)
 end)
 
 describe('buffer cursor position is correct in terminal without number column', function()
-- 
cgit 


From 160cbd0ef4c905b849beb969e8b5a56064c7cea7 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Wed, 18 Dec 2024 20:06:16 +0100
Subject: test(cursor_spec): global highlight definitions (#31613)

---
 test/functional/terminal/cursor_spec.lua | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index 657468c03e..368afd6d36 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -373,12 +373,6 @@ describe('buffer cursor position is correct in terminal without number column',
     }, {
       cols = 70,
     })
-    screen:set_default_attr_ids({
-      [1] = { foreground = 253, background = 11 },
-      [2] = { reverse = true },
-      [3] = { bold = true },
-      [4] = { background = 11 },
-    })
     -- Also check for real cursor position, as it is used for stuff like input methods
     screen._handle_busy_start = function() end
     screen._handle_busy_stop = function() end
@@ -690,13 +684,6 @@ describe('buffer cursor position is correct in terminal with number column', fun
     }, {
       cols = 70,
     })
-    screen:set_default_attr_ids({
-      [1] = { foreground = 253, background = 11 },
-      [2] = { reverse = true },
-      [3] = { bold = true },
-      [4] = { background = 11 },
-      [7] = { foreground = 130 },
-    })
     -- Also check for real cursor position, as it is used for stuff like input methods
     screen._handle_busy_start = function() end
     screen._handle_busy_stop = function() end
-- 
cgit 


From 02bc40c19401ea9f7529cbc81bd18bd01c39bb3c Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Thu, 19 Dec 2024 07:59:03 +0800
Subject: vim-patch:9.1.0945: ComplMatchIns highlight doesn't end after
 inserted text (#31628)

Problem:  ComplMatchIns highlight doesn't end after inserted text.
Solution: Handle ComplMatchIns highlight more like search highlight.
          Fix off-by-one error. Handle deleting text properly.
          (zeertzjq)

closes: vim/vim#16244

https://github.com/vim/vim/commit/f25d8f9312a24da2727671560a865888812ab8d9
---
 runtime/colors/vim.lua                |   2 +-
 src/nvim/drawline.c                   |  26 ++++----
 src/nvim/highlight_group.c            |   2 +-
 src/nvim/insexpand.c                  |   5 +-
 test/functional/ui/mode_spec.lua      |   2 +-
 test/functional/ui/popupmenu_spec.lua | 110 ++++++++++++++++++++++++++++++++++
 test/old/testdir/test_indent.vim      |   2 +-
 test/old/testdir/test_popup.vim       |  58 +++++++++++++++++-
 8 files changed, 188 insertions(+), 19 deletions(-)

diff --git a/runtime/colors/vim.lua b/runtime/colors/vim.lua
index dd3e83c653..dd59e5075d 100644
--- a/runtime/colors/vim.lua
+++ b/runtime/colors/vim.lua
@@ -60,7 +60,7 @@ hi('PmenuMatch',     { link = 'Pmenu' })
 hi('PmenuMatchSel',  { link = 'PmenuSel' })
 hi('PmenuExtra',     { link = 'Pmenu' })
 hi('PmenuExtraSel',  { link = 'PmenuSel' })
-hi('ComplMatchIns',  { link = 'Normal' })
+hi('ComplMatchIns',  {})
 hi('Substitute',     { link = 'Search' })
 hi('Whitespace',     { link = 'NonText' })
 hi('MsgSeparator',   { link = 'StatusLine' })
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index a1d03486ff..35a41f840d 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -955,7 +955,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
   bool area_highlighting = false;       // Visual or incsearch highlighting in this line
   int vi_attr = 0;                      // attributes for Visual and incsearch highlighting
   int area_attr = 0;                    // attributes desired by highlighting
-  int search_attr = 0;                  // attributes desired by 'hlsearch'
+  int search_attr = 0;                  // attributes desired by 'hlsearch' or ComplMatchIns
   int vcol_save_attr = 0;               // saved attr for 'cursorcolumn'
   int decor_attr = 0;                   // attributes desired by syntax and extmarks
   bool has_syntax = false;              // this buffer has syntax highl.
@@ -969,7 +969,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
   int spell_attr = 0;                   // attributes desired by spelling
   int word_end = 0;                     // last byte with same spell_attr
   int cur_checked_col = 0;              // checked column for current line
-  bool extra_check = 0;                 // has syntax or linebreak
+  bool extra_check = false;             // has extra highlighting
   int multi_attr = 0;                   // attributes desired by multibyte
   int mb_l = 1;                         // multi-byte byte length
   int mb_c = 0;                         // decoded multi-byte character
@@ -1495,6 +1495,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
     ptr = line + v;  // "line" may have been updated
   }
 
+  if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
+    area_highlighting = true;
+  }
+
   win_line_start(wp, &wlv);
   bool draw_cols = true;
   int leftcols_width = 0;
@@ -1740,6 +1744,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
         if (*ptr == NUL) {
           has_match_conc = 0;
         }
+
+        // Check if ComplMatchIns highlight is needed.
+        if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
+          int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
+          if (ins_match_attr > 0) {
+            search_attr = hl_combine_attr(search_attr, ins_match_attr);
+          }
+        }
       }
 
       if (wlv.diff_hlf != (hlf_T)0) {
@@ -1787,16 +1799,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
       wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
     }
 
-    // Apply ComplMatchIns highlight if needed.
-    if (wlv.filler_todo <= 0
-        && (State & MODE_INSERT) && in_curline && ins_compl_active()) {
-      int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
-      if (ins_match_attr > 0) {
-        char_attr_pri = hl_combine_attr(char_attr_pri, ins_match_attr);
-        wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
-      }
-    }
-
     if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) {
       const int v = (int)(ptr - line);
       linenr_T lnume = lnum + foldinfo.fi_lines - 1;
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 636124a698..2ae15254ea 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -173,7 +173,7 @@ static const char *highlight_init_both[] = {
   "default link PmenuKind        Pmenu",
   "default link PmenuKindSel     PmenuSel",
   "default link PmenuSbar        Pmenu",
-  "default link ComplMatchIns    Normal",
+  "default link ComplMatchIns    NONE",
   "default link Substitute       Search",
   "default link StatusLineTerm   StatusLine",
   "default link StatusLineTermNC StatusLineNC",
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 5036ec5e45..872ed2b4c3 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -958,7 +958,7 @@ static void ins_compl_insert_bytes(char *p, int len)
   }
   assert(len >= 0);
   ins_bytes_len(p, (size_t)len);
-  compl_ins_end_col = curwin->w_cursor.col - 1;
+  compl_ins_end_col = curwin->w_cursor.col;
 }
 
 /// Checks if the column is within the currently inserted completion text
@@ -2147,6 +2147,8 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval)
       && pum_visible()) {
     word = xstrdup(compl_shown_match->cp_str);
     retval = true;
+    // May need to remove ComplMatchIns highlight.
+    redrawWinline(curwin, curwin->w_cursor.lnum);
   }
 
   // CTRL-E means completion is Ended, go back to the typed text.
@@ -3648,6 +3650,7 @@ void ins_compl_delete(bool new_leader)
       return;
     }
     backspace_until_column(col);
+    compl_ins_end_col = curwin->w_cursor.col;
   }
 
   // TODO(vim): is this sufficient for redrawing?  Redrawing everything
diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua
index 69b38f3ff3..01f4dda227 100644
--- a/test/functional/ui/mode_spec.lua
+++ b/test/functional/ui/mode_spec.lua
@@ -94,7 +94,7 @@ describe('ui mode_change event', function()
     }
   end)
 
-  -- oldtest: Test_indent_norm_with_gq()
+  -- oldtest: Test_mouse_shape_indent_norm_with_gq()
   it('is restored to Normal mode after "gq" indents using :normal #12309', function()
     screen:try_resize(60, 6)
     n.exec([[
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index a64fc99f26..75421c6998 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -1162,6 +1162,8 @@ describe('builtin popupmenu', function()
         [6] = { foreground = Screen.colors.White, background = Screen.colors.Red },
         [7] = { background = Screen.colors.Yellow }, -- Search
         [8] = { foreground = Screen.colors.Red },
+        [9] = { foreground = Screen.colors.Yellow, background = Screen.colors.Green },
+        [10] = { foreground = Screen.colors.White, background = Screen.colors.Green },
         ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey },
         kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 },
         xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey },
@@ -5625,6 +5627,114 @@ describe('builtin popupmenu', function()
           {2:-- INSERT --}                    |
         ]])
         feed('')
+
+        -- text after the inserted text shouldn't be highlighted
+        feed('0ea ')
+        screen:expect([[
+          αβγ {8:foo}^ foo                     |
+          {1:~  }{s: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 1 of 3}                 |
+        ]])
+        feed('')
+        screen:expect([[
+          αβγ ^ foo                        |
+          {1:~  }{n: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{8:Back at original}             |
+        ]])
+        feed('')
+        screen:expect([[
+          αβγ {8:你好}^ foo                    |
+          {1:~  }{n: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{s: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 3 of 3}                 |
+        ]])
+        feed('')
+        screen:expect([[
+          αβγ 你好^ foo                    |
+          {1:~                               }|*18
+          {2:-- INSERT --}                    |
+        ]])
+        feed('')
+      end)
+
+      -- oldtest: Test_pum_matchins_highlight_combine()
+      it('with ComplMatchIns, Normal and CursorLine highlights', function()
+        exec([[
+          func Omni_test(findstart, base)
+            if a:findstart
+              return col(".")
+            endif
+            return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+          endfunc
+          set omnifunc=Omni_test
+          hi Normal guibg=blue
+          hi CursorLine guibg=green guifg=white
+          set cursorline
+          call setline(1, 'aaa bbb')
+        ]])
+
+        -- when ComplMatchIns is not set, CursorLine applies normally
+        feed('0ea ')
+        screen:expect([[
+          {10:aaa foo^ bbb                     }|
+          {1:~  }{s: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 1 of 3}                 |
+        ]])
+        feed('')
+        screen:expect([[
+          {10:aaa ^ bbb                        }|
+          {1:~                               }|*18
+          {2:-- INSERT --}                    |
+        ]])
+        feed('')
+
+        -- when ComplMatchIns is set, it is applied over CursorLine
+        command('hi ComplMatchIns guifg=Yellow')
+        feed('0ea ')
+        screen:expect([[
+          {10:aaa }{9:foo}{10:^ bbb                     }|
+          {1:~  }{s: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 1 of 3}                 |
+        ]])
+        feed('')
+        screen:expect([[
+          {10:aaa ^ bbb                        }|
+          {1:~  }{n: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{n: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{8:Back at original}             |
+        ]])
+        feed('')
+        screen:expect([[
+          {10:aaa }{9:你好}{10:^ bbb                    }|
+          {1:~  }{n: foo            }{1:             }|
+          {1:~  }{n: bar            }{1:             }|
+          {1:~  }{s: 你好           }{1:             }|
+          {1:~                               }|*15
+          {2:-- }{5:match 3 of 3}                 |
+        ]])
+        feed('')
+        screen:expect([[
+          {10:aaa ^ bbb                        }|
+          {1:~                               }|*18
+          {2:-- INSERT --}                    |
+        ]])
+        feed('')
       end)
     end
   end
diff --git a/test/old/testdir/test_indent.vim b/test/old/testdir/test_indent.vim
index 36670a0472..630beed810 100644
--- a/test/old/testdir/test_indent.vim
+++ b/test/old/testdir/test_indent.vim
@@ -308,7 +308,7 @@ endfunc
 
 " Test that mouse shape is restored to Normal mode after using "gq" when
 " 'indentexpr' executes :normal.
-func Test_indent_norm_with_gq()
+func Test_mouse_shape_indent_norm_with_gq()
   CheckFeature mouseshape
   CheckCanRunGui
 
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index 28104bdff5..f16a897b07 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1747,13 +1747,67 @@ func Test_pum_matchins_highlight()
   call TermWait(buf)
   call term_sendkeys(buf, "Sαβγ \\\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_04', {})
-  call term_sendkeys(buf, "\\")
+  call term_sendkeys(buf, "\")
 
   " restore after cancel completion
   call TermWait(buf)
   call term_sendkeys(buf, "Sαβγ \\\")
   call VerifyScreenDump(buf, 'Test_pum_matchins_05', {})
-  call term_sendkeys(buf, "\\")
+  call term_sendkeys(buf, "\")
+
+  " text after the inserted text shouldn't be highlighted
+  call TermWait(buf)
+  call term_sendkeys(buf, "0ea \\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_07', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_08', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_09', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_10', {})
+  call term_sendkeys(buf, "\")
+
+  call StopVimInTerminal(buf)
+endfunc
+
+func Test_pum_matchins_highlight_combine()
+  CheckScreendump
+  let lines =<< trim END
+    func Omni_test(findstart, base)
+      if a:findstart
+        return col(".")
+      endif
+      return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+    endfunc
+    set omnifunc=Omni_test
+    hi Normal ctermbg=blue
+    hi CursorLine cterm=underline ctermbg=green
+    set cursorline
+    call setline(1, 'aaa bbb')
+  END
+  call writefile(lines, 'Xscript', 'D')
+  let buf = RunVimInTerminal('-S Xscript', {})
+
+  " when ComplMatchIns is not set, CursorLine applies normally
+  call term_sendkeys(buf, "0ea \\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_combine_01', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_combine_02', {})
+  call term_sendkeys(buf, "\\")
+
+  " when ComplMatchIns is set, it is applied over CursorLine
+  call TermWait(buf)
+  call term_sendkeys(buf, ":hi ComplMatchIns ctermbg=red ctermfg=yellow\")
+  call TermWait(buf)
+  call term_sendkeys(buf, "0ea \\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_combine_03', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_combine_04', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_combine_05', {})
+  call term_sendkeys(buf, "\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_combine_06', {})
+  call term_sendkeys(buf, "\")
 
   call StopVimInTerminal(buf)
 endfunc
-- 
cgit 


From 31d6885deba01fe0e7262dca58c94220c2fe32fc Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Thu, 19 Dec 2024 15:24:48 +0100
Subject: fix(messages): better formatting for :highlight with ext_messages
 #31627

Also avoid going down message callstack with empty message, and remove expected grid for some tests where it did not change, and we are just testing for expected messages.
---
 src/nvim/highlight_group.c           |  3 +--
 src/nvim/message.c                   |  9 +++++++--
 test/functional/ui/messages_spec.lua | 35 +++++++++--------------------------
 3 files changed, 17 insertions(+), 30 deletions(-)

diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 2ae15254ea..afdbd4a32f 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1887,8 +1887,7 @@ bool syn_list_header(const bool did_header, const int outlen, const int id, bool
     if (got_int) {
       return true;
     }
-    msg_outtrans(hl_table[id - 1].sg_name, 0, false);
-    name_col = msg_col;
+    msg_col = name_col = msg_outtrans(hl_table[id - 1].sg_name, 0, false);
     endcol = 15;
   } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) {
     msg_putchar(' ');
diff --git a/src/nvim/message.c b/src/nvim/message.c
index edb332a786..066aa6bc96 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2129,8 +2129,8 @@ void msg_puts_len(const char *const str, const ptrdiff_t len, int hl_id, bool hi
   // If redirection is on, also write to the redirection file.
   redir_write(str, len);
 
-  // Don't print anything when using ":silent cmd".
-  if (msg_silent != 0) {
+  // Don't print anything when using ":silent cmd" or empty message.
+  if (msg_silent != 0 || *str == NUL) {
     return;
   }
 
@@ -2238,6 +2238,11 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse
     size_t len = maxlen < 0 ? strlen(str) : strnlen(str, (size_t)maxlen);
     ga_concat_len(&msg_ext_last_chunk, str, len);
     msg_ext_cur_len += len;
+    // When message ends in newline, reset variables used to format message: msg_advance().
+    if (str[len - 1] == '\n') {
+      msg_ext_cur_len = 0;
+      msg_col = 0;
+    }
     return;
   }
 
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 287db81a12..6d8c6609c8 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -84,11 +84,6 @@ describe('ui/ext_messages', function()
     }
     feed('')
     screen:expect {
-      grid = [[
-      line 1                   |
-      line ^2                   |
-      {1:~                        }|*3
-    ]],
       messages = {
         {
           content = { { '\ntest\n[O]k: ', 6, 10 } },
@@ -160,11 +155,6 @@ describe('ui/ext_messages', function()
     -- kind=emsg after :throw
     feed(':throw "foo"')
     screen:expect {
-      grid = [[
-      l^ine 1                   |
-      line 2                   |
-      {1:~                        }|*3
-    ]],
       messages = {
         {
           content = { { 'Error detected while processing :', 9, 6 } },
@@ -214,27 +204,20 @@ describe('ui/ext_messages', function()
     })
 
     -- highlight
-    feed(':hi ErrorMsg')
+    feed(':filter character highlight')
     screen:expect({
-      grid = [[
-        ^line 1                   |
-        line 2                   |
-        {1:~                        }|*3
-      ]],
       messages = {
         {
           content = {
-            { '\nErrorMsg      ' },
-            { 'xxx', 9, 6 },
+            { '\n@character     ' },
+            { 'xxx', 26, 150 },
+            { ' ' },
+            { 'links to', 18, 5 },
+            { ' Character\n@character.special ' },
+            { 'xxx', 16, 151 },
             { ' ' },
-            { 'ctermfg=', 18, 5 },
-            { '15 ' },
-            { 'ctermbg=', 18, 5 },
-            { '1 ' },
-            { 'guifg=', 18, 5 },
-            { 'White ' },
-            { 'guibg=', 18, 5 },
-            { 'Red' },
+            { 'links to', 18, 5 },
+            { ' SpecialChar' },
           },
           kind = 'list_cmd',
         },
-- 
cgit 


From a5a4149e9754a96c063a357c18397aa7906edf53 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Thu, 19 Dec 2024 16:04:33 +0100
Subject: docs(api): specify when decor provider on_buf is called #31634

---
 runtime/doc/api.txt           | 4 ++--
 runtime/lua/vim/_meta/api.lua | 4 ++--
 src/nvim/api/extmark.c        | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 70fda5ce8a..95c8a6056f 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -2847,8 +2847,8 @@ nvim_set_decoration_provider({ns_id}, {opts})
                  • on_start: called first on each screen redraw >
                     ["start", tick]
 <
-                 • on_buf: called for each buffer being redrawn (before window
-                   callbacks) >
+                 • on_buf: called for each buffer being redrawn (once per
+                   edit, before window callbacks) >
                     ["buf", bufnr, tick]
 <
                  • on_win: called when starting to redraw a specific window. >
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 55274963ed..8d759278d0 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -2134,8 +2134,8 @@ function vim.api.nvim_set_current_win(window) end
 ---   ```
 ---     ["start", tick]
 ---   ```
---- - on_buf: called for each buffer being redrawn (before
----   window callbacks)
+--- - on_buf: called for each buffer being redrawn (once per edit,
+---   before window callbacks)
 ---   ```
 ---     ["buf", bufnr, tick]
 ---   ```
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index c94b8df9ea..55f537e9c9 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -1012,8 +1012,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
 ///               ```
 ///                 ["start", tick]
 ///               ```
-///             - on_buf: called for each buffer being redrawn (before
-///               window callbacks)
+///             - on_buf: called for each buffer being redrawn (once per edit,
+///               before window callbacks)
 ///               ```
 ///                 ["buf", bufnr, tick]
 ///               ```
-- 
cgit 


From 8ef41f590224dfeea2e51d9fec150e363fd72ee0 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Thu, 19 Dec 2024 07:07:04 -0800
Subject: feat(jobs): jobstart(…,{term=true}), deprecate termopen() #31343
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem:
`termopen` has long been a superficial wrapper around `jobstart`, and
has no real purpose. Also, `vim.system` and `nvim_open_term` presumably
will replace all features of `jobstart` and `termopen`, so centralizing
the logic will help with that.

Solution:
- Introduce `eval/deprecated.c`, where all deprecated eval funcs will live.
- Introduce "term" flag of `jobstart`.
- Deprecate `termopen`.
---
 contrib/gdb/neovim_gdb.vim                         |   6 +-
 runtime/doc/api.txt                                |   4 +-
 runtime/doc/builtin.txt                            |  35 +--
 runtime/doc/channel.txt                            |   4 +-
 runtime/doc/deprecated.txt                         | 305 ++++++++++-----------
 runtime/doc/news.txt                               |   1 +
 runtime/doc/terminal.txt                           |   2 +-
 runtime/lua/vim/_defaults.lua                      |   2 +-
 runtime/lua/vim/_meta/api.lua                      |   4 +-
 runtime/lua/vim/_meta/vimfn.lua                    |  28 +-
 .../pack/dist/opt/termdebug/plugin/termdebug.vim   |   8 +-
 src/nvim/api/buffer.c                              |   4 +-
 src/nvim/eval.c                                    |   2 +-
 src/nvim/eval.lua                                  |  28 +-
 src/nvim/eval/deprecated.c                         |  44 +++
 src/nvim/eval/deprecated.h                         |  10 +
 src/nvim/eval/funcs.c                              | 234 ++++++----------
 src/nvim/ex_docmd.c                                |   4 +-
 src/nvim/generators/gen_eval.lua                   |   1 +
 src/nvim/terminal.c                                |   2 +-
 test/functional/api/buffer_updates_spec.lua        |   3 +-
 test/functional/api/vim_spec.lua                   |   3 +-
 test/functional/core/job_spec.lua                  |  47 +++-
 test/functional/core/main_spec.lua                 |   3 +-
 test/functional/core/startup_spec.lua              |  29 +-
 test/functional/editor/mode_normal_spec.lua        |   8 +-
 .../ex_cmds/swapfile_preserve_recover_spec.lua     |  12 +-
 test/functional/legacy/window_cmd_spec.lua         |   2 +-
 test/functional/terminal/buffer_spec.lua           |  10 +-
 test/functional/terminal/channel_spec.lua          |  14 +-
 test/functional/terminal/clipboard_spec.lua        |   2 +-
 test/functional/terminal/cursor_spec.lua           |   2 +-
 test/functional/terminal/ex_terminal_spec.lua      |   4 +-
 test/functional/terminal/highlight_spec.lua        |  11 +-
 test/functional/terminal/scrollback_spec.lua       |   6 +-
 test/functional/terminal/tui_spec.lua              |   6 +-
 test/functional/testterm.lua                       |   2 +-
 test/functional/ui/hlstate_spec.lua                |   2 +-
 test/functional/ui/messages_spec.lua               |   2 +-
 test/functional/ui/title_spec.lua                  |   2 +-
 test/old/testdir/runnvim.vim                       |   3 +-
 41 files changed, 466 insertions(+), 435 deletions(-)
 create mode 100644 src/nvim/eval/deprecated.c
 create mode 100644 src/nvim/eval/deprecated.h

diff --git a/contrib/gdb/neovim_gdb.vim b/contrib/gdb/neovim_gdb.vim
index d61e7bc0cc..e0be6cb803 100644
--- a/contrib/gdb/neovim_gdb.vim
+++ b/contrib/gdb/neovim_gdb.vim
@@ -167,13 +167,15 @@ function! s:Spawn(server_cmd, client_cmd, server_addr, reconnect)
   if type(a:server_cmd) == type('')
     " spawn gdbserver in a vertical split
     let server = s:GdbServer.new(gdb)
-    vsp | enew | let gdb._server_id = termopen(a:server_cmd, server)
+    server.term = v:true
+    vsp | enew | let gdb._server_id = jobstart(a:server_cmd, server)
     let gdb._jump_window = 2
     let gdb._server_buf = bufnr('%')
   endif
   " go to the bottom window and spawn gdb client
   wincmd j
-  enew | let gdb._client_id = termopen(a:client_cmd, gdb)
+  gdb.term = v:true
+  enew | let gdb._client_id = jobstart(a:client_cmd, gdb)
   let gdb._client_buf = bufnr('%')
   tnoremap   :GdbContinuei
   tnoremap   :GdbNexti
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 95c8a6056f..c8e0dcd0c5 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -2170,12 +2170,12 @@ nvim_buf_call({buffer}, {fun})                               *nvim_buf_call()*
     This temporarily switches current buffer to "buffer". If the current
     window already shows "buffer", the window is not switched. If a window
     inside the current tabpage (including a float) already shows the buffer,
-    then one of these windows will be set as current window temporarily.
+    then one of those windows will be set as current window temporarily.
     Otherwise a temporary scratch window (called the "autocmd window" for
     historical reasons) will be used.
 
     This is useful e.g. to call Vimscript functions that only work with the
-    current buffer/window currently, like |termopen()|.
+    current buffer/window currently, like `jobstart(…, {'term': v:true})`.
 
     Attributes: ~
         Lua |vim.api| only
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 5f74080e0c..0e9c1c8512 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -5309,7 +5309,7 @@ jobresize({job}, {width}, {height})                                *jobresize()*
                   (`any`)
 
 jobstart({cmd} [, {opts}])                                          *jobstart()*
-		Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+		Note: Prefer |vim.system()| in Lua (unless using `rpc`, `pty`, or `term`).
 
 		Spawns {cmd} as a job.
 		If {cmd} is a List it runs directly (no 'shell').
@@ -5317,8 +5317,11 @@ jobstart({cmd} [, {opts}])                                          *jobstart()*
 		  call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
 <		(See |shell-unquoting| for details.)
 
-		Example: >vim
-		  call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+		Example: start a job and handle its output: >vim
+		  call jobstart(['nvim', '-h'], {'on_stdout':{j,d,e->append(line('.'),d)}})
+<
+		Example: start a job in a |terminal| connected to the current buffer: >vim
+		  call jobstart(['nvim', '-h'], {'term':v:true})
 <
 		Returns |job-id| on success, 0 on invalid arguments (or job
 		table is full), -1 if {cmd}[0] or 'shell' is not executable.
@@ -5383,6 +5386,10 @@ jobstart({cmd} [, {opts}])                                          *jobstart()*
 		  stdin:      (string) Either "pipe" (default) to connect the
 			      job's stdin to a channel or "null" to disconnect
 			      stdin.
+		  term:	    (boolean) Spawns {cmd} in a new pseudo-terminal session
+		          connected to the current (unmodified) buffer. Implies "pty".
+		          Default "height" and "width" are set to the current window
+		          dimensions. |jobstart()|. Defaults $TERM to "xterm-256color".
 		  width:      (number) Width of the `pty` terminal.
 
 		{opts} is passed as |self| dictionary to the callback; the
@@ -11168,28 +11175,6 @@ tempname()                                                          *tempname()*
                 Return: ~
                   (`string`)
 
-termopen({cmd} [, {opts}])                                          *termopen()*
-		Spawns {cmd} in a new pseudo-terminal session connected
-		to the current (unmodified) buffer. Parameters and behavior
-		are the same as |jobstart()| except "pty", "width", "height",
-		and "TERM" are ignored: "height" and "width" are taken from
-		the current window. Note that termopen() implies a "pty" arg
-		to jobstart(), and thus has the implications documented at
-		|jobstart()|.
-
-		Returns the same values as jobstart().
-
-		Terminal environment is initialized as in |jobstart-env|,
-		except $TERM is set to "xterm-256color". Full behavior is
-		described in |terminal|.
-
-                Parameters: ~
-                  • {cmd} (`string|string[]`)
-                  • {opts} (`table?`)
-
-                Return: ~
-                  (`integer`)
-
 test_garbagecollect_now()                            *test_garbagecollect_now()*
 		Like |garbagecollect()|, but executed right away.  This must
 		only be called directly to avoid any structure to exist
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 7184151cda..dc9b0735ea 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -20,8 +20,8 @@ There are several ways to open a channel:
 
   2. Through stdin, stdout and stderr of a process spawned by |jobstart()|.
 
-  3. Through the PTY master end of a PTY opened with
-     `jobstart(..., {'pty': v:true})` or |termopen()|.
+  3. Through the PTY master end of a PTY opened with |nvim_open_term()| or
+     `jobstart(…, {'pty': v:true})`.
 
   4. By connecting to a TCP/IP socket or named pipe with |sockconnect()|.
 
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index 592b6389c4..ec98cc844f 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -16,49 +16,34 @@ Deprecated features
 DEPRECATED IN 0.11					*deprecated-0.11*
 
 API
-- nvim_subscribe()	Plugins must maintain their own "multicast" channels list.
-- nvim_unsubscribe()	Plugins must maintain their own "multicast" channels list.
-
-LUA
-- vim.region()		Use |getregionpos()| instead.
-- *vim.highlight*	Renamed to |vim.hl|.
-- vim.validate(opts: table) Use form 1. See |vim.validate()|.
+• nvim_subscribe()	Plugins must maintain their own "multicast" channels list.
+• nvim_unsubscribe()	Plugins must maintain their own "multicast" channels list.
 
 DIAGNOSTICS
-- *vim.diagnostic.goto_next()*	Use |vim.diagnostic.jump()| with `{count=1, float=true}` instead.
-- *vim.diagnostic.goto_prev()*	Use |vim.diagnostic.jump()| with `{count=-1, float=true}` instead.
-- *vim.diagnostic.get_next_pos()*
-	Use the "lnum" and "col" fields from the return value of
-	|vim.diagnostic.get_next()| instead.
-- *vim.diagnostic.get_prev_pos()*
-	Use the "lnum" and "col" fields from the return value of
-	|vim.diagnostic.get_prev()| instead.
-- The "win_id" parameter used by various functions is deprecated in favor of
+• *vim.diagnostic.goto_next()*	Use |vim.diagnostic.jump()| with `{count=1, float=true}` instead.
+• *vim.diagnostic.goto_prev()*	Use |vim.diagnostic.jump()| with `{count=-1, float=true}` instead.
+• *vim.diagnostic.get_next_pos()* Use the "lnum" and "col" fields from the
+				return value of |vim.diagnostic.get_next()| instead.
+• *vim.diagnostic.get_prev_pos()* Use the "lnum" and "col" fields from the
+				return value of |vim.diagnostic.get_prev()| instead.
+• The "win_id" parameter used by various functions is deprecated in favor of
   "winid" |winid|
-- The "cursor_position" parameter of |vim.diagnostic.JumpOpts| is renamed to
-  "pos"
+• |vim.diagnostic.JumpOpts| renamed its "cursor_position" field to "pos".
 
 HIGHLIGHTS
-• *TermCursorNC*	As of Nvim 0.11, unfocused |terminal| windows no
-			longer have any cursor.
-
-TREESITTER
-• *TSNode:child_containing_descendant()*	Use
-  |TSNode:child_with_descendant()| instead; it is identical except that it can
-  return the descendant itself.
+• *TermCursorNC*	Unfocused |terminal| windows do not have a cursor.
 
 LSP
 • *vim.lsp.util.jump_to_location*	Use |vim.lsp.util.show_document()| with
-  `{focus=true}` instead.
+					`{focus=true}` instead.
 • *vim.lsp.buf.execute_command*		Use |Client:exec_cmd()| instead.
 • *vim.lsp.buf.completion*		Use |vim.lsp.completion.trigger()| instead.
 • vim.lsp.buf_request_all		The `error` key has been renamed to `err` inside
 					the result parameter of the handler.
 • *vim.lsp.with()*			Pass configuration to equivalent
 					functions in `vim.lsp.buf.*`.
-• |vim.lsp.handlers|
-	No longer support client to server response handlers. Only server to
-	client requests/notification handlers are supported.
+• |vim.lsp.handlers| Does not support client-to-server response handlers. Only
+  supports server-to-client requests/notification handlers.
 • *vim.lsp.handlers.signature_help()*	Use |vim.lsp.buf.signature_help()| instead.
 • `client.request()`			Use |Client:request()| instead.
 • `client.request_sync()`		Use |Client:request_sync()| instead.
@@ -70,6 +55,18 @@ LSP
 • `client.on_attach()`			Use |Client:on_attach()| instead.
 • `vim.lsp.start_client()`		Use |vim.lsp.start()| instead.
 
+LUA
+• vim.region()		Use |getregionpos()| instead.
+• *vim.highlight*	Renamed to |vim.hl|.
+• vim.validate(opts: table) Use form 1. See |vim.validate()|.
+
+TREESITTER
+• *TSNode:child_containing_descendant()* Use |TSNode:child_with_descendant()|
+  instead; it is identical except that it can return the descendant itself.
+
+VIMSCRIPT
+• *termopen()* Use |jobstart() with `{term: v:true}`.
+
 ------------------------------------------------------------------------------
 DEPRECATED IN 0.10					*deprecated-0.10*
 
@@ -130,198 +127,198 @@ TREESITTER
 DEPRECATED IN 0.9					*deprecated-0.9*
 
 API
-- *nvim_get_hl_by_name()*		Use |nvim_get_hl()| instead.
-- *nvim_get_hl_by_id()*			Use |nvim_get_hl()| instead.
+• *nvim_get_hl_by_name()*		Use |nvim_get_hl()| instead.
+• *nvim_get_hl_by_id()*			Use |nvim_get_hl()| instead.
 
 TREESITTER
-- *vim.treesitter.language.require_language()*	Use |vim.treesitter.language.add()| instead.
-- *vim.treesitter.get_node_at_pos()*		Use |vim.treesitter.get_node()| instead.
-- *vim.treesitter.get_node_at_cursor()*		Use |vim.treesitter.get_node()|
+• *vim.treesitter.language.require_language()*	Use |vim.treesitter.language.add()| instead.
+• *vim.treesitter.get_node_at_pos()*		Use |vim.treesitter.get_node()| instead.
+• *vim.treesitter.get_node_at_cursor()*		Use |vim.treesitter.get_node()|
 						and |TSNode:type()| instead.
 • The following top level Treesitter functions have been moved:
-  - *vim.treesitter.inspect_language()*    -> |vim.treesitter.language.inspect()|
-  - *vim.treesitter.get_query_files()*     -> |vim.treesitter.query.get_files()|
-  - *vim.treesitter.set_query()*           -> |vim.treesitter.query.set()|
-  - *vim.treesitter.query.set_query()*     -> |vim.treesitter.query.set()|
-  - *vim.treesitter.get_query()*           -> |vim.treesitter.query.get()|
-  - *vim.treesitter.query.get_query()*     -> |vim.treesitter.query.get()|
-  - *vim.treesitter.parse_query()*         -> |vim.treesitter.query.parse()|
-  - *vim.treesitter.query.parse_query()*   -> |vim.treesitter.query.parse()|
-  - *vim.treesitter.add_predicate()*       -> |vim.treesitter.query.add_predicate()|
-  - *vim.treesitter.add_directive()*       -> |vim.treesitter.query.add_directive()|
-  - *vim.treesitter.list_predicates()*     -> |vim.treesitter.query.list_predicates()|
-  - *vim.treesitter.list_directives()*     -> |vim.treesitter.query.list_directives()|
-  - *vim.treesitter.query.get_range()*     -> |vim.treesitter.get_range()|
-  - *vim.treesitter.query.get_node_text()* -> |vim.treesitter.get_node_text()|
+  • *vim.treesitter.inspect_language()*    -> |vim.treesitter.language.inspect()|
+  • *vim.treesitter.get_query_files()*     -> |vim.treesitter.query.get_files()|
+  • *vim.treesitter.set_query()*           -> |vim.treesitter.query.set()|
+  • *vim.treesitter.query.set_query()*     -> |vim.treesitter.query.set()|
+  • *vim.treesitter.get_query()*           -> |vim.treesitter.query.get()|
+  • *vim.treesitter.query.get_query()*     -> |vim.treesitter.query.get()|
+  • *vim.treesitter.parse_query()*         -> |vim.treesitter.query.parse()|
+  • *vim.treesitter.query.parse_query()*   -> |vim.treesitter.query.parse()|
+  • *vim.treesitter.add_predicate()*       -> |vim.treesitter.query.add_predicate()|
+  • *vim.treesitter.add_directive()*       -> |vim.treesitter.query.add_directive()|
+  • *vim.treesitter.list_predicates()*     -> |vim.treesitter.query.list_predicates()|
+  • *vim.treesitter.list_directives()*     -> |vim.treesitter.query.list_directives()|
+  • *vim.treesitter.query.get_range()*     -> |vim.treesitter.get_range()|
+  • *vim.treesitter.query.get_node_text()* -> |vim.treesitter.get_node_text()|
 
 LUA
-  - *nvim_exec()*				Use |nvim_exec2()| instead.
-  - *vim.pretty_print()*			Use |vim.print()| instead.
+  • *nvim_exec()*				Use |nvim_exec2()| instead.
+  • *vim.pretty_print()*			Use |vim.print()| instead.
 
 
 ------------------------------------------------------------------------------
 DEPRECATED IN 0.8 OR EARLIER
 
 API
-- *nvim_buf_clear_highlight()*	Use |nvim_buf_clear_namespace()| instead.
-- *nvim_buf_set_virtual_text()*	Use |nvim_buf_set_extmark()| instead.
-- *nvim_command_output()*	Use |nvim_exec2()| instead.
-- *nvim_execute_lua()*		Use |nvim_exec_lua()| instead.
-- *nvim_get_option_info()*	Use |nvim_get_option_info2()| instead.
+• *nvim_buf_clear_highlight()*	Use |nvim_buf_clear_namespace()| instead.
+• *nvim_buf_set_virtual_text()*	Use |nvim_buf_set_extmark()| instead.
+• *nvim_command_output()*	Use |nvim_exec2()| instead.
+• *nvim_execute_lua()*		Use |nvim_exec_lua()| instead.
+• *nvim_get_option_info()*	Use |nvim_get_option_info2()| instead.
 
 COMMANDS
-- *:rv* *:rviminfo*		Deprecated alias to |:rshada| command.
-- *:wv* *:wviminfo*		Deprecated alias to |:wshada| command.
+• *:rv* *:rviminfo*		Deprecated alias to |:rshada| command.
+• *:wv* *:wviminfo*		Deprecated alias to |:wshada| command.
 
 ENVIRONMENT VARIABLES
-- *$NVIM_LISTEN_ADDRESS*
-  - Deprecated way to:
-    - set the server name (use |--listen| or |serverstart()| instead)
-    - get the server name (use |v:servername| instead)
-    - detect a parent Nvim (use |$NVIM| instead)
-  - Ignored if --listen is given.
-  - Unset by |terminal| and |jobstart()| unless explicitly given by the "env"
+• *$NVIM_LISTEN_ADDRESS*
+  • Deprecated way to:
+    • set the server name (use |--listen| or |serverstart()| instead)
+    • get the server name (use |v:servername| instead)
+    • detect a parent Nvim (use |$NVIM| instead)
+  • Ignored if --listen is given.
+  • Unset by |terminal| and |jobstart()| unless explicitly given by the "env"
     option. Example: >vim
 	call jobstart(['foo'], { 'env': { 'NVIM_LISTEN_ADDRESS': v:servername  } })
 <
 
 EVENTS
-- *BufCreate*		Use |BufAdd| instead.
-- *EncodingChanged*	Never fired; 'encoding' is always "utf-8".
-- *FileEncoding*	Never fired; equivalent to |EncodingChanged|.
-- *GUIEnter*		Never fired; use |UIEnter| instead.
-- *GUIFailed*		Never fired.
+• *BufCreate*		Use |BufAdd| instead.
+• *EncodingChanged*	Never fired; 'encoding' is always "utf-8".
+• *FileEncoding*	Never fired; equivalent to |EncodingChanged|.
+• *GUIEnter*		Never fired; use |UIEnter| instead.
+• *GUIFailed*		Never fired.
 
 KEYCODES
-- **		Use  instead.
-- **		Use  instead.
-
-FUNCTIONS
-- *buffer_exists()*	Obsolete name for |bufexists()|.
-- *buffer_name()*	Obsolete name for |bufname()|.
-- *buffer_number()*	Obsolete name for |bufnr()|.
-- *file_readable()*	Obsolete name for |filereadable()|.
-- *highlight_exists()*	Obsolete name for |hlexists()|.
-- *highlightID()*	Obsolete name for |hlID()|.
-- *inputdialog()*	Use |input()| instead.
-- *jobclose()*		Obsolete name for |chanclose()|
-- *jobsend()*		Obsolete name for |chansend()|
-- *last_buffer_nr()*	Obsolete name for bufnr("$").
-- *rpcstart()*		Use |jobstart()| with `{'rpc': v:true}` instead.
-- *rpcstop()*		Use |jobstop()| instead to stop any job, or
-			`chanclose(id, "rpc")` to close RPC communication
-			without stopping the job. Use chanclose(id) to close
-			any socket.
+• **		Use  instead.
+• **		Use  instead.
 
 HIGHLIGHTS
-- *hl-VertSplit*	Use |hl-WinSeparator| instead.
+• *hl-VertSplit*	Use |hl-WinSeparator| instead.
 
 LSP DIAGNOSTICS
 For each of the functions below, use the corresponding function in
 |vim.diagnostic| instead (unless otherwise noted). For example, use
 |vim.diagnostic.get()| instead of |vim.lsp.diagnostic.get()|.
 
-- *vim.lsp.diagnostic.clear()*		Use |vim.diagnostic.hide()| instead.
-- *vim.lsp.diagnostic.disable()*          Use |vim.diagnostic.enable()| instead.
-- *vim.lsp.diagnostic.display()*	Use |vim.diagnostic.show()| instead.
-- *vim.lsp.diagnostic.enable()*
-- *vim.lsp.diagnostic.get()*
-- *vim.lsp.diagnostic.get_all()*	Use |vim.diagnostic.get()| instead.
-- *vim.lsp.diagnostic.get_count()*	Use |vim.diagnostic.count()| instead.
-- *vim.lsp.diagnostic.get_line_diagnostics()* Use |vim.diagnostic.get()| instead.
-- *vim.lsp.diagnostic.get_next()*
-- *vim.lsp.diagnostic.get_next_pos()*
-- *vim.lsp.diagnostic.get_prev()*
-- *vim.lsp.diagnostic.get_prev_pos()*
-- *vim.lsp.diagnostic.get_virtual_text_chunks_for_line()* No replacement. Use
+• *vim.lsp.diagnostic.clear()*		Use |vim.diagnostic.hide()| instead.
+• *vim.lsp.diagnostic.disable()*          Use |vim.diagnostic.enable()| instead.
+• *vim.lsp.diagnostic.display()*	Use |vim.diagnostic.show()| instead.
+• *vim.lsp.diagnostic.enable()*
+• *vim.lsp.diagnostic.get()*
+• *vim.lsp.diagnostic.get_all()*	Use |vim.diagnostic.get()| instead.
+• *vim.lsp.diagnostic.get_count()*	Use |vim.diagnostic.count()| instead.
+• *vim.lsp.diagnostic.get_line_diagnostics()* Use |vim.diagnostic.get()| instead.
+• *vim.lsp.diagnostic.get_next()*
+• *vim.lsp.diagnostic.get_next_pos()*
+• *vim.lsp.diagnostic.get_prev()*
+• *vim.lsp.diagnostic.get_prev_pos()*
+• *vim.lsp.diagnostic.get_virtual_text_chunks_for_line()* No replacement. Use
   options provided by |vim.diagnostic.config()| to customize virtual text.
-- *vim.lsp.diagnostic.goto_next()*
-- *vim.lsp.diagnostic.goto_prev()*
-- *vim.lsp.diagnostic.redraw()*		Use |vim.diagnostic.show()| instead.
-- *vim.lsp.diagnostic.reset()*
-- *vim.lsp.diagnostic.save()*		Use |vim.diagnostic.set()| instead.
-- *vim.lsp.diagnostic.set_loclist()*	Use |vim.diagnostic.setloclist()| instead.
-- *vim.lsp.diagnostic.set_qflist()*	Use |vim.diagnostic.setqflist()| instead.
-- *vim.lsp.diagnostic.show_line_diagnostics()* Use |vim.diagnostic.open_float()| instead.
-- *vim.lsp.diagnostic.show_position_diagnostics()* Use |vim.diagnostic.open_float()| instead.
+• *vim.lsp.diagnostic.goto_next()*
+• *vim.lsp.diagnostic.goto_prev()*
+• *vim.lsp.diagnostic.redraw()*		Use |vim.diagnostic.show()| instead.
+• *vim.lsp.diagnostic.reset()*
+• *vim.lsp.diagnostic.save()*		Use |vim.diagnostic.set()| instead.
+• *vim.lsp.diagnostic.set_loclist()*	Use |vim.diagnostic.setloclist()| instead.
+• *vim.lsp.diagnostic.set_qflist()*	Use |vim.diagnostic.setqflist()| instead.
+• *vim.lsp.diagnostic.show_line_diagnostics()* Use |vim.diagnostic.open_float()| instead.
+• *vim.lsp.diagnostic.show_position_diagnostics()* Use |vim.diagnostic.open_float()| instead.
 
 The following are deprecated without replacement. These functions are moved
 internally and are no longer exposed as part of the API. Instead, use
 |vim.diagnostic.config()| and |vim.diagnostic.show()|.
 
-- *vim.lsp.diagnostic.set_signs()*
-- *vim.lsp.diagnostic.set_underline()*
-- *vim.lsp.diagnostic.set_virtual_text()*
+• *vim.lsp.diagnostic.set_signs()*
+• *vim.lsp.diagnostic.set_underline()*
+• *vim.lsp.diagnostic.set_virtual_text()*
 
 LSP FUNCTIONS
-- *vim.lsp.buf.server_ready()*
+• *vim.lsp.buf.server_ready()*
   Use |LspAttach| instead, depending on your use-case. "Server ready" is not
   part of the LSP spec, so the Nvim LSP client cannot meaningfully implement
   it. "Ready" is ambiguous because:
-  - Language servers may finish analyzing the workspace, but edits can always
+  • Language servers may finish analyzing the workspace, but edits can always
     re-trigger analysis/builds.
-  - Language servers can serve some requests even while processing changes.
-- *vim.lsp.buf.range_code_action()*		Use |vim.lsp.buf.code_action()| with
+  • Language servers can serve some requests even while processing changes.
+• *vim.lsp.buf.range_code_action()*		Use |vim.lsp.buf.code_action()| with
 						the `range` parameter.
-- *vim.lsp.util.diagnostics_to_items()*		Use |vim.diagnostic.toqflist()| instead.
-- *vim.lsp.util.set_qflist()*			Use |setqflist()| instead.
-- *vim.lsp.util.set_loclist()*			Use |setloclist()| instead.
-- *vim.lsp.buf_get_clients()*			Use |vim.lsp.get_clients()| with
+• *vim.lsp.util.diagnostics_to_items()*		Use |vim.diagnostic.toqflist()| instead.
+• *vim.lsp.util.set_qflist()*			Use |setqflist()| instead.
+• *vim.lsp.util.set_loclist()*			Use |setloclist()| instead.
+• *vim.lsp.buf_get_clients()*			Use |vim.lsp.get_clients()| with
 						{buffer=bufnr} instead.
-- *vim.lsp.buf.formatting()*			Use |vim.lsp.buf.format()| with
+• *vim.lsp.buf.formatting()*			Use |vim.lsp.buf.format()| with
 						{async=true} instead.
-- *vim.lsp.buf.formatting_sync()*		Use |vim.lsp.buf.format()| with
+• *vim.lsp.buf.formatting_sync()*		Use |vim.lsp.buf.format()| with
 						{async=false} instead.
-- *vim.lsp.buf.range_formatting()*		Use |vim.lsp.formatexpr()|
+• *vim.lsp.buf.range_formatting()*		Use |vim.lsp.formatexpr()|
 						or |vim.lsp.buf.format()| instead.
 
 LUA
-- vim.register_keystroke_callback()	Use |vim.on_key()| instead.
+• vim.register_keystroke_callback()	Use |vim.on_key()| instead.
 
 NORMAL COMMANDS
-- *]f* *[f*		Same as "gf".
+• *]f* *[f*		Same as "gf".
 
 OPTIONS
-- *cpo-<* *:menu-* *:menu-special* *:map-* *:map-special*
+• *cpo-<* *:menu-* *:menu-special* *:map-* *:map-special*
   `<>` notation is always enabled.
-- *'fe'*		'fenc'+'enc' before Vim 6.0; no longer used.
-- *'highlight'* *'hl'*	Names of builtin |highlight-groups| cannot be changed.
-- *'langnoremap'*	Deprecated alias to 'nolangremap'.
-- 'sessionoptions'	Flags "unix", "slash" are ignored and always enabled.
-- *'vi'*
-- 'viewoptions'		Flags "unix", "slash" are ignored and always enabled.
-- *'viminfo'*		Deprecated alias to 'shada' option.
-- *'viminfofile'*	Deprecated alias to 'shadafile' option.
-- *'paste'* *'nopaste'*	Just Paste It.™  The 'paste' option is obsolete:
+• *'fe'*		'fenc'+'enc' before Vim 6.0; no longer used.
+• *'highlight'* *'hl'*	Names of builtin |highlight-groups| cannot be changed.
+• *'langnoremap'*	Deprecated alias to 'nolangremap'.
+• 'sessionoptions'	Flags "unix", "slash" are ignored and always enabled.
+• *'vi'*
+• 'viewoptions'		Flags "unix", "slash" are ignored and always enabled.
+• *'viminfo'*		Deprecated alias to 'shada' option.
+• *'viminfofile'*	Deprecated alias to 'shadafile' option.
+• *'paste'* *'nopaste'*	Just Paste It.™  The 'paste' option is obsolete:
 			|paste| is handled automatically when you paste text
 			using your terminal's or GUI's paste feature
 			(CTRL-SHIFT-v, CMD-v (macOS), middle-click, …).
 			Enables "paste mode":
-			  - Disables mappings in Insert, Cmdline mode.
-			  - Disables abbreviations.
-			  - Resets 'autoindent' 'expandtab' 'revins' 'ruler'
+			  • Disables mappings in Insert, Cmdline mode.
+			  • Disables abbreviations.
+			  • Resets 'autoindent' 'expandtab' 'revins' 'ruler'
 			    'showmatch' 'smartindent' 'smarttab' 'softtabstop'
 			    'textwidth' 'wrapmargin'.
-			  - Treats 'formatoptions' as empty.
-			  - Disables the effect of these options:
-			    - 'cindent'
-			    - 'indentexpr'
-			    - 'lisp'
+			  • Treats 'formatoptions' as empty.
+			  • Disables the effect of these options:
+			    • 'cindent'
+			    • 'indentexpr'
+			    • 'lisp'
 
 UI EXTENSIONS
-- *ui-wildmenu*		Use |ui-cmdline| with |ui-popupmenu| instead. Enabled
+• *ui-wildmenu*		Use |ui-cmdline| with |ui-popupmenu| instead. Enabled
 			by the `ext_wildmenu` |ui-option|. Emits these events:
-			- `["wildmenu_show", items]`
-			- `["wildmenu_select", selected]`
-			- `["wildmenu_hide"]`
-- *term_background*	Unused. The terminal background color is now detected
+			• `["wildmenu_show", items]`
+			• `["wildmenu_select", selected]`
+			• `["wildmenu_hide"]`
+• *term_background*	Unused. The terminal background color is now detected
 			by the Nvim core directly instead of the TUI.
 
 VARIABLES
-- *b:terminal_job_pid*	Use `jobpid(&channel)` instead.
-- *b:terminal_job_id*	Use `&channel` instead. To access in non-current buffer:
-			- Lua: `vim.bo[bufnr].channel`
-			- Vimscript: `getbufvar(bufnr, '&channel')`
+• *b:terminal_job_pid*	Use `jobpid(&channel)` instead.
+• *b:terminal_job_id*	Use `&channel` instead. To access in non-current buffer:
+			• Lua: `vim.bo[bufnr].channel`
+			• Vimscript: `getbufvar(bufnr, '&channel')`
+
+VIMSCRIPT
+• *buffer_exists()*	Obsolete name for |bufexists()|.
+• *buffer_name()*	Obsolete name for |bufname()|.
+• *buffer_number()*	Obsolete name for |bufnr()|.
+• *file_readable()*	Obsolete name for |filereadable()|.
+• *highlight_exists()*	Obsolete name for |hlexists()|.
+• *highlightID()*	Obsolete name for |hlID()|.
+• *inputdialog()*	Use |input()| instead.
+• *jobclose()*		Obsolete name for |chanclose()|
+• *jobsend()*		Obsolete name for |chansend()|
+• *last_buffer_nr()*	Obsolete name for bufnr("$").
+• *rpcstart()*		Use |jobstart()| with `{'rpc': v:true}` instead.
+• *rpcstop()*		Use |jobstop()| instead to stop any job, or
+			`chanclose(id, "rpc")` to close RPC communication
+			without stopping the job. Use chanclose(id) to close
+			any socket.
 
 
  vim:noet:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 172fa4625c..011970f5eb 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -294,6 +294,7 @@ TERMINAL
   means that the |TermCursorNC| highlight group is no longer supported: an
   unfocused terminal window will have no cursor at all (so there is nothing to
   highlight).
+• |jobstart()| gained the "term" flag.
 
 TREESITTER
 
diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt
index f9536c2f0c..ff8e3a976f 100644
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -26,7 +26,7 @@ Start						*terminal-start*
 There are several ways to create a terminal buffer:
 
 - Run the |:terminal| command.
-- Call the |nvim_open_term()| or |termopen()| function.
+- Call |nvim_open_term()| or `jobstart(…, {'term': v:true})`.
 - Edit a "term://" buffer. Examples: >vim
     :edit term://bash
     :vsplit term://top
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index 0b8a54e957..4216a2acb7 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -435,7 +435,7 @@ do
     group = nvim_terminal_augroup,
     desc = 'Treat term:// buffers as terminal buffers',
     nested = true,
-    command = "if !exists('b:term_title')|call termopen(matchstr(expand(\"\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'cwd': expand(get(matchlist(expand(\"\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})",
+    command = "if !exists('b:term_title')|call jobstart(matchstr(expand(\"\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'term': v:true, 'cwd': expand(get(matchlist(expand(\"\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})",
   })
 
   vim.api.nvim_create_autocmd({ 'TermClose' }, {
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 8d759278d0..bea6df43bb 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -272,12 +272,12 @@ function vim.api.nvim_buf_attach(buffer, send_buffer, opts) end
 --- This temporarily switches current buffer to "buffer".
 --- If the current window already shows "buffer", the window is not switched.
 --- If a window inside the current tabpage (including a float) already shows the
---- buffer, then one of these windows will be set as current window temporarily.
+--- buffer, then one of those windows will be set as current window temporarily.
 --- Otherwise a temporary scratch window (called the "autocmd window" for
 --- historical reasons) will be used.
 ---
 --- This is useful e.g. to call Vimscript functions that only work with the
---- current buffer/window currently, like `termopen()`.
+--- current buffer/window currently, like `jobstart(…, {'term': v:true})`.
 ---
 --- @param buffer integer Buffer handle, or 0 for current buffer
 --- @param fun function Function to call inside the buffer (currently Lua callable
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index cf1beda15f..b580357c85 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -4805,7 +4805,7 @@ function vim.fn.jobresize(job, width, height) end
 --- @return any
 function vim.fn.jobsend(...) end
 
---- Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+--- Note: Prefer |vim.system()| in Lua (unless using `rpc`, `pty`, or `term`).
 ---
 --- Spawns {cmd} as a job.
 --- If {cmd} is a List it runs directly (no 'shell').
@@ -4813,8 +4813,11 @@ function vim.fn.jobsend(...) end
 ---   call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
 --- <(See |shell-unquoting| for details.)
 ---
---- Example: >vim
----   call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+--- Example: start a job and handle its output: >vim
+---   call jobstart(['nvim', '-h'], {'on_stdout':{j,d,e->append(line('.'),d)}})
+--- <
+--- Example: start a job in a |terminal| connected to the current buffer: >vim
+---   call jobstart(['nvim', '-h'], {'term':v:true})
 --- <
 --- Returns |job-id| on success, 0 on invalid arguments (or job
 --- table is full), -1 if {cmd}[0] or 'shell' is not executable.
@@ -4879,6 +4882,10 @@ function vim.fn.jobsend(...) end
 ---   stdin:      (string) Either "pipe" (default) to connect the
 ---         job's stdin to a channel or "null" to disconnect
 ---         stdin.
+---   term:      (boolean) Spawns {cmd} in a new pseudo-terminal session
+---           connected to the current (unmodified) buffer. Implies "pty".
+---           Default "height" and "width" are set to the current window
+---           dimensions. |jobstart()|. Defaults $TERM to "xterm-256color".
 ---   width:      (number) Width of the `pty` terminal.
 ---
 --- {opts} is passed as |self| dictionary to the callback; the
@@ -10168,19 +10175,8 @@ function vim.fn.tanh(expr) end
 --- @return string
 function vim.fn.tempname() end
 
---- Spawns {cmd} in a new pseudo-terminal session connected
---- to the current (unmodified) buffer. Parameters and behavior
---- are the same as |jobstart()| except "pty", "width", "height",
---- and "TERM" are ignored: "height" and "width" are taken from
---- the current window. Note that termopen() implies a "pty" arg
---- to jobstart(), and thus has the implications documented at
---- |jobstart()|.
----
---- Returns the same values as jobstart().
----
---- Terminal environment is initialized as in |jobstart-env|,
---- except $TERM is set to "xterm-256color". Full behavior is
---- described in |terminal|.
+--- @deprecated
+--- Use |jobstart()| with `{term: v:true}` instead.
 ---
 --- @param cmd string|string[]
 --- @param opts? table
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index fd8c4b8817..b6da91a833 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -38,7 +38,7 @@
 " NEOVIM COMPATIBILITY
 "
 " The vim specific functionalities were replaced with neovim specific calls:
-" - term_start -> termopen
+" - term_start -> `jobstart(…, {'term': v:true})`
 " - term_sendkeys -> chansend
 " - term_getline -> getbufline
 " - job_info && term_getjob -> nvim_get_chan_info
@@ -251,7 +251,7 @@ endfunc
 " Open a terminal window without a job, to run the debugged program in.
 func s:StartDebug_term(dict)
   execute s:vertical ? 'vnew' : 'new'
-  let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
+  let s:pty_job_id = jobstart('tail -f /dev/null;#gdb program', {'term': v:true})
   if s:pty_job_id == 0
     call s:Echoerr('Invalid argument (or job table is full) while opening terminal window')
     return
@@ -323,7 +323,7 @@ func s:StartDebug_term(dict)
 
   execute 'new'
   " call ch_log($'executing "{join(gdb_cmd)}"')
-  let s:gdb_job_id = termopen(gdb_cmd, {'on_exit': function('s:EndTermDebug')})
+  let s:gdb_job_id = jobstart(gdb_cmd, {'term': v:true, 'on_exit': function('s:EndTermDebug')})
   if s:gdb_job_id == 0
     call s:Echoerr('Invalid argument (or job table is full) while opening gdb terminal window')
     exe 'bwipe! ' . s:ptybufnr
@@ -491,7 +491,7 @@ func s:StartDebug_prompt(dict)
     " Unix: Run the debugged program in a terminal window.  Open it below the
     " gdb window.
     belowright new
-    let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
+    let s:pty_job_id = jobstart('tail -f /dev/null;#gdb program', {'term': v:true})
     if s:pty_job_id == 0
       call s:Echoerr('Invalid argument (or job table is full) while opening terminal window')
       return
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 2b6aa8b371..6c40eb9b20 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1181,12 +1181,12 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Arena *arena,
 /// This temporarily switches current buffer to "buffer".
 /// If the current window already shows "buffer", the window is not switched.
 /// If a window inside the current tabpage (including a float) already shows the
-/// buffer, then one of these windows will be set as current window temporarily.
+/// buffer, then one of those windows will be set as current window temporarily.
 /// Otherwise a temporary scratch window (called the "autocmd window" for
 /// historical reasons) will be used.
 ///
 /// This is useful e.g. to call Vimscript functions that only work with the
-/// current buffer/window currently, like |termopen()|.
+/// current buffer/window currently, like `jobstart(…, {'term': v:true})`.
 ///
 /// @param buffer     Buffer handle, or 0 for current buffer
 /// @param fun        Function to call inside the buffer (currently Lua callable
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 47f0a13b29..8bdd8dad4c 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -8493,7 +8493,7 @@ char *do_string_sub(char *str, size_t len, char *pat, char *sub, typval_T *expr,
   return ret;
 }
 
-/// common code for getting job callbacks for jobstart, termopen and rpcstart
+/// Common code for getting job callbacks for `jobstart`.
 ///
 /// @return true/false on success/failure.
 bool common_job_callbacks(dict_T *vopts, CallbackReader *on_stdout, CallbackReader *on_stderr,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 9de189cc16..72dabd53e9 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -5945,7 +5945,7 @@ M.funcs = {
   jobstart = {
     args = { 1, 2 },
     desc = [=[
-      Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+      Note: Prefer |vim.system()| in Lua (unless using `rpc`, `pty`, or `term`).
 
       Spawns {cmd} as a job.
       If {cmd} is a List it runs directly (no 'shell').
@@ -5953,8 +5953,11 @@ M.funcs = {
         call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
       <(See |shell-unquoting| for details.)
 
-      Example: >vim
-        call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+      Example: start a job and handle its output: >vim
+        call jobstart(['nvim', '-h'], {'on_stdout':{j,d,e->append(line('.'),d)}})
+      <
+      Example: start a job in a |terminal| connected to the current buffer: >vim
+        call jobstart(['nvim', '-h'], {'term':v:true})
       <
       Returns |job-id| on success, 0 on invalid arguments (or job
       table is full), -1 if {cmd}[0] or 'shell' is not executable.
@@ -6019,6 +6022,10 @@ M.funcs = {
         stdin:      (string) Either "pipe" (default) to connect the
       	      job's stdin to a channel or "null" to disconnect
       	      stdin.
+        term:	    (boolean) Spawns {cmd} in a new pseudo-terminal session
+                connected to the current (unmodified) buffer. Implies "pty".
+                Default "height" and "width" are set to the current window
+                dimensions. |jobstart()|. Defaults $TERM to "xterm-256color".
         width:      (number) Width of the `pty` terminal.
 
       {opts} is passed as |self| dictionary to the callback; the
@@ -12271,21 +12278,10 @@ M.funcs = {
     signature = 'tempname()',
   },
   termopen = {
+    deprecated = true,
     args = { 1, 2 },
     desc = [=[
-      Spawns {cmd} in a new pseudo-terminal session connected
-      to the current (unmodified) buffer. Parameters and behavior
-      are the same as |jobstart()| except "pty", "width", "height",
-      and "TERM" are ignored: "height" and "width" are taken from
-      the current window. Note that termopen() implies a "pty" arg
-      to jobstart(), and thus has the implications documented at
-      |jobstart()|.
-
-      Returns the same values as jobstart().
-
-      Terminal environment is initialized as in |jobstart-env|,
-      except $TERM is set to "xterm-256color". Full behavior is
-      described in |terminal|.
+      Use |jobstart()| with `{term: v:true}` instead.
     ]=],
     name = 'termopen',
     params = { { 'cmd', 'string|string[]' }, { 'opts', 'table' } },
diff --git a/src/nvim/eval/deprecated.c b/src/nvim/eval/deprecated.c
new file mode 100644
index 0000000000..67c254dac9
--- /dev/null
+++ b/src/nvim/eval/deprecated.c
@@ -0,0 +1,44 @@
+#include                 // for true
+
+#include "nvim/errors.h"
+#include "nvim/eval/deprecated.h"
+#include "nvim/eval/funcs.h"
+#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/gettext_defs.h"      // for _
+#include "nvim/macros_defs.h"       // for S_LEN
+#include "nvim/message.h"           // for semsg
+#include "nvim/types_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/deprecated.c.generated.h"
+#endif
+
+/// "termopen(cmd[, cwd])" function
+void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+  if (check_secure()) {
+    return;
+  }
+
+  bool must_free = false;
+
+  if (argvars[1].v_type == VAR_UNKNOWN) {
+    must_free = true;
+    argvars[1].v_type = VAR_DICT;
+    argvars[1].vval.v_dict = tv_dict_alloc();
+  }
+
+  if (argvars[1].v_type != VAR_DICT) {
+    // Wrong argument types
+    semsg(_(e_invarg2), "expected dictionary");
+    return;
+  }
+
+  tv_dict_add_bool(argvars[1].vval.v_dict, S_LEN("term"), true);
+  f_jobstart(argvars, rettv, fptr);
+  if (must_free) {
+    tv_dict_free(argvars[1].vval.v_dict);
+  }
+}
diff --git a/src/nvim/eval/deprecated.h b/src/nvim/eval/deprecated.h
new file mode 100644
index 0000000000..b870403aa4
--- /dev/null
+++ b/src/nvim/eval/deprecated.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include                 // for true
+
+#include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
+#include "nvim/types_defs.h"  // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/deprecated.h.generated.h"
+#endif
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 768d7664f7..23bfcff406 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -38,6 +38,7 @@
 #include "nvim/eval.h"
 #include "nvim/eval/buffer.h"
 #include "nvim/eval/decode.h"
+#include "nvim/eval/deprecated.h"
 #include "nvim/eval/encode.h"
 #include "nvim/eval/executor.h"
 #include "nvim/eval/funcs.h"
@@ -3840,8 +3841,8 @@ static const char *required_env_vars[] = {
   NULL
 };
 
-static dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, const bool pty,
-                                  const char * const pty_term_name)
+dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, const bool pty,
+                           const char * const pty_term_name)
 {
   dict_T *env = tv_dict_alloc();
 
@@ -3933,7 +3934,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
 }
 
 /// "jobstart()" function
-static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 {
   rettv->v_type = VAR_NUMBER;
   rettv->vval.v_number = 0;
@@ -3942,9 +3943,9 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
     return;
   }
 
+  const char *cmd;
   bool executable = true;
-  char **argv = tv_to_argv(&argvars[0], NULL, &executable);
-  dict_T *env = NULL;
+  char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
   if (!argv) {
     rettv->vval.v_number = executable ? 0 : -1;
     return;  // Did error message in tv_to_argv.
@@ -3961,6 +3962,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   bool detach = false;
   bool rpc = false;
   bool pty = false;
+  bool term = false;
   bool clear_env = false;
   bool overlapped = false;
   ChannelStdinMode stdin_mode = kChannelStdinPipe;
@@ -3974,7 +3976,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 
     detach = tv_dict_get_number(job_opts, "detach") != 0;
     rpc = tv_dict_get_number(job_opts, "rpc") != 0;
-    pty = tv_dict_get_number(job_opts, "pty") != 0;
+    term = tv_dict_get_number(job_opts, "term") != 0;
+    pty = term || tv_dict_get_number(job_opts, "pty") != 0;
     clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
     overlapped = tv_dict_get_number(job_opts, "overlapped") != 0;
 
@@ -3989,6 +3992,14 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
       }
     }
 
+    dictitem_T *const job_term = tv_dict_find(job_opts, S_LEN("term"));
+    if (job_term && VAR_BOOL != job_term->di_tv.v_type) {
+      // Restrict "term" field to boolean, in case we want to allow buffer numbers in the future.
+      semsg(_(e_invarg2), "'term' must be Boolean");
+      shell_free_argv(argv);
+      return;
+    }
+
     if (pty && rpc) {
       semsg(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
       shell_free_argv(argv);
@@ -4032,24 +4043,87 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   uint16_t height = 0;
   char *term_name = NULL;
 
-  if (pty) {
-    width = (uint16_t)tv_dict_get_number(job_opts, "width");
-    height = (uint16_t)tv_dict_get_number(job_opts, "height");
-    // Legacy method, before env option existed, to specify $TERM.  No longer
-    // documented, but still usable to avoid breaking scripts.
-    term_name = tv_dict_get_string(job_opts, "TERM", false);
-    if (!term_name) {
-      term_name = "ansi";
+  if (term) {
+    if (text_locked()) {
+      text_locked_msg();
+      shell_free_argv(argv);
+      return;
     }
+    if (curbuf->b_changed) {
+      emsg(_("jobstart(...,{term=true}) requires unmodified buffer"));
+      shell_free_argv(argv);
+      return;
+    }
+    assert(!rpc);
+    term_name = "xterm-256color";
+    cwd = cwd ? cwd : ".";
+    overlapped = false;
+    detach = false;
+    stdin_mode = kChannelStdinPipe;
+    width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin));
+    height = (uint16_t)curwin->w_height_inner;
   }
 
-  env = create_environment(job_env, clear_env, pty, term_name);
+  if (pty) {
+    width = width ? width : (uint16_t)tv_dict_get_number(job_opts, "width");
+    height = height ? height : (uint16_t)tv_dict_get_number(job_opts, "height");
+    // Deprecated TERM field is from before `env` option existed.
+    term_name = term_name ? term_name : tv_dict_get_string(job_opts, "TERM", false);
+    term_name = term_name ? term_name : "ansi";
+  }
 
+  dict_T *env = create_environment(job_env, clear_env, pty, term_name);
   Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty,
                                     rpc, overlapped, detach, stdin_mode, cwd,
                                     width, height, env, &rettv->vval.v_number);
-  if (chan) {
+  if (!chan) {
+    return;
+  } else if (!term) {
     channel_create_event(chan, NULL);
+  } else {
+    if (rettv->vval.v_number <= 0) {
+      return;
+    }
+
+    int pid = chan->stream.pty.proc.pid;
+
+    // "./…" => "/home/foo/…"
+    vim_FullName(cwd, NameBuff, sizeof(NameBuff), false);
+    // "/home/foo/…" => "~/…"
+    size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true);
+    // Trim slash.
+    if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) {
+      IObuff[len - 1] = NUL;
+    }
+
+    if (len == 1 && IObuff[0] == '/') {
+      // Avoid ambiguity in the URI when CWD is root directory.
+      IObuff[1] = '.';
+      IObuff[2] = NUL;
+    }
+
+    // Terminal URI: "term://$CWD//$PID:$CMD"
+    snprintf(NameBuff, sizeof(NameBuff), "term://%s//%d:%s", IObuff, pid, cmd);
+    // Buffer has no terminal associated yet; unset 'swapfile' to ensure no swapfile is created.
+    curbuf->b_p_swf = false;
+
+    apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
+    setfname(curbuf, NameBuff, NULL, true);
+    apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
+
+    Error err = ERROR_INIT;
+    // Set (deprecated) buffer-local vars (prefer 'channel' buffer-local option).
+    dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
+                 INTEGER_OBJ((Integer)chan->id), false, false, NULL, &err);
+    api_clear_error(&err);
+    dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
+                 INTEGER_OBJ(pid), false, false, NULL, &err);
+    api_clear_error(&err);
+
+    channel_incref(chan);
+    channel_terminal_open(curbuf, chan);
+    channel_create_event(chan, NULL);
+    channel_decref(chan);
   }
 }
 
@@ -8133,134 +8207,6 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
            (char *)tag_pattern, (char *)fname);
 }
 
-/// "termopen(cmd[, cwd])" function
-static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
-  if (check_secure()) {
-    return;
-  }
-  if (text_locked()) {
-    text_locked_msg();
-    return;
-  }
-  if (curbuf->b_changed) {
-    emsg(_("Can only call this function in an unmodified buffer"));
-    return;
-  }
-
-  const char *cmd;
-  bool executable = true;
-  char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
-  if (!argv) {
-    rettv->vval.v_number = executable ? 0 : -1;
-    return;  // Did error message in tv_to_argv.
-  }
-
-  if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
-    // Wrong argument type
-    semsg(_(e_invarg2), "expected dictionary");
-    shell_free_argv(argv);
-    return;
-  }
-
-  CallbackReader on_stdout = CALLBACK_READER_INIT;
-  CallbackReader on_stderr = CALLBACK_READER_INIT;
-  Callback on_exit = CALLBACK_NONE;
-  dict_T *job_opts = NULL;
-  const char *cwd = ".";
-  dict_T *env = NULL;
-  const bool pty = true;
-  bool clear_env = false;
-  dictitem_T *job_env = NULL;
-
-  if (argvars[1].v_type == VAR_DICT) {
-    job_opts = argvars[1].vval.v_dict;
-
-    const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false);
-    if (new_cwd && *new_cwd != NUL) {
-      cwd = new_cwd;
-      // The new cwd must be a directory.
-      if (!os_isdir(cwd)) {
-        semsg(_(e_invarg2), "expected valid directory");
-        shell_free_argv(argv);
-        return;
-      }
-    }
-
-    job_env = tv_dict_find(job_opts, S_LEN("env"));
-    if (job_env && job_env->di_tv.v_type != VAR_DICT) {
-      semsg(_(e_invarg2), "env");
-      shell_free_argv(argv);
-      return;
-    }
-
-    clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
-
-    if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
-      shell_free_argv(argv);
-      return;
-    }
-  }
-
-  env = create_environment(job_env, clear_env, pty, "xterm-256color");
-
-  const bool rpc = false;
-  const bool overlapped = false;
-  const bool detach = false;
-  ChannelStdinMode stdin_mode = kChannelStdinPipe;
-  uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin));
-  Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit,
-                                    pty, rpc, overlapped, detach, stdin_mode,
-                                    cwd, term_width, (uint16_t)curwin->w_height_inner,
-                                    env, &rettv->vval.v_number);
-  if (rettv->vval.v_number <= 0) {
-    return;
-  }
-
-  int pid = chan->stream.pty.proc.pid;
-
-  // "./…" => "/home/foo/…"
-  vim_FullName(cwd, NameBuff, sizeof(NameBuff), false);
-  // "/home/foo/…" => "~/…"
-  size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true);
-  // Trim slash.
-  if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) {
-    IObuff[len - 1] = NUL;
-  }
-
-  if (len == 1 && IObuff[0] == '/') {
-    // Avoid ambiguity in the URI when CWD is root directory.
-    IObuff[1] = '.';
-    IObuff[2] = NUL;
-  }
-
-  // Terminal URI: "term://$CWD//$PID:$CMD"
-  snprintf(NameBuff, sizeof(NameBuff), "term://%s//%d:%s",
-           IObuff, pid, cmd);
-  // at this point the buffer has no terminal instance associated yet, so unset
-  // the 'swapfile' option to ensure no swap file will be created
-  curbuf->b_p_swf = false;
-
-  apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
-  setfname(curbuf, NameBuff, NULL, true);
-  apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
-
-  // Save the job id and pid in b:terminal_job_{id,pid}
-  Error err = ERROR_INIT;
-  // deprecated: use 'channel' buffer option
-  dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
-               INTEGER_OBJ((Integer)chan->id), false, false, NULL, &err);
-  api_clear_error(&err);
-  dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
-               INTEGER_OBJ(pid), false, false, NULL, &err);
-  api_clear_error(&err);
-
-  channel_incref(chan);
-  channel_terminal_open(curbuf, chan);
-  channel_create_event(chan, NULL);
-  channel_decref(chan);
-}
-
 /// "timer_info([timer])" function
 static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 {
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 052bf3b9f7..bd8623397a 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -7775,7 +7775,7 @@ static void ex_terminal(exarg_T *eap)
   if (*eap->arg != NUL) {  // Run {cmd} in 'shell'.
     char *name = vim_strsave_escaped(eap->arg, "\"\\");
     snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
-             " | call termopen(\"%s\")", name);
+             " | call jobstart(\"%s\",{'term':v:true})", name);
     xfree(name);
   } else {  // No {cmd}: run the job with tokenized 'shell'.
     if (*p_sh == NUL) {
@@ -7798,7 +7798,7 @@ static void ex_terminal(exarg_T *eap)
     shell_free_argv(argv);
 
     snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
-             " | call termopen([%s])", shell_argv + 1);
+             " | call jobstart([%s], {'term':v:true})", shell_argv + 1);
   }
 
   do_cmdline_cmd(ex_cmd);
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index 443c68e008..0b6ee6cb24 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -19,6 +19,7 @@ hashpipe:write([[
 #include "nvim/digraph.h"
 #include "nvim/eval.h"
 #include "nvim/eval/buffer.h"
+#include "nvim/eval/deprecated.h"
 #include "nvim/eval/fs.h"
 #include "nvim/eval/funcs.h"
 #include "nvim/eval/typval.h"
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 969c539f97..61eede5ae1 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -357,7 +357,7 @@ static void term_output_callback(const char *s, size_t len, void *user_data)
 
 /// Initializes terminal properties, and triggers TermOpen.
 ///
-/// The PTY process (TerminalOptions.data) was already started by termopen(),
+/// The PTY process (TerminalOptions.data) was already started by jobstart(),
 /// via ex_terminal() or the term:// BufReadCmd.
 ///
 /// @param buf Buffer used for presentation of the terminal.
diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua
index 527394bfd1..489f601d31 100644
--- a/test/functional/api/buffer_updates_spec.lua
+++ b/test/functional/api/buffer_updates_spec.lua
@@ -879,7 +879,8 @@ describe('API: buffer events:', function()
   it('when :terminal lines change', function()
     local buffer_lines = {}
     local expected_lines = {}
-    fn.termopen({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-n', '-c', 'set shortmess+=A' }, {
+    fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-n', '-c', 'set shortmess+=A' }, {
+      term = true,
       env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
     })
 
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 130be9987f..fbc9490df6 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -2704,7 +2704,8 @@ describe('API', function()
       -- :terminal with args + running process.
       command('enew')
       local progpath_esc = eval('shellescape(v:progpath)')
-      fn.termopen(('%s -u NONE -i NONE'):format(progpath_esc), {
+      fn.jobstart(('%s -u NONE -i NONE'):format(progpath_esc), {
+        term = true,
         env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
       })
       eq(-1, eval('jobwait([&channel], 0)[0]')) -- Running?
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index b4d80d4ed4..952437d80e 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -65,6 +65,39 @@ describe('jobs', function()
     ]])
   end)
 
+  it('validation', function()
+    matches(
+      "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set",
+      pcall_err(command, "call jobstart(['cat', '-'], { 'pty': v:true, 'rpc': v:true })")
+    )
+    matches(
+      'E475: Invalid argument: expected valid directory',
+      pcall_err(command, "call jobstart(['cat', '-'], { 'cwd': 9313843 })")
+    )
+    matches(
+      'E475: Invalid argument: expected valid directory',
+      pcall_err(command, "call jobstart(['cat', '-'], { 'cwd': 'bogusssssss/bogus' })")
+    )
+    matches(
+      "E475: Invalid argument: 'term' must be Boolean",
+      pcall_err(command, "call jobstart(['cat', '-'], { 'term': 'bogus' })")
+    )
+    matches(
+      "E475: Invalid argument: 'term' must be Boolean",
+      pcall_err(command, "call jobstart(['cat', '-'], { 'term': 1 })")
+    )
+    command('set modified')
+    matches(
+      vim.pesc('jobstart(...,{term=true}) requires unmodified buffer'),
+      pcall_err(command, "call jobstart(['cat', '-'], { 'term': v:true })")
+    )
+
+    -- Non-failure cases:
+    command('set nomodified')
+    command("call jobstart(['cat', '-'], { 'term': v:true })")
+    command("call jobstart(['cat', '-'], { 'term': v:false })")
+  end)
+
   it('must specify env option as a dict', function()
     command('let g:job_opts.env = v:true')
     local _, err = pcall(function()
@@ -969,13 +1002,6 @@ describe('jobs', function()
     eq({ 'notification', 'exit', { 0, 143 } }, next_msg())
   end)
 
-  it('cannot have both rpc and pty options', function()
-    command('let g:job_opts.pty = v:true')
-    command('let g:job_opts.rpc = v:true')
-    local _, err = pcall(command, "let j = jobstart(['cat', '-'], g:job_opts)")
-    matches("E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set", err)
-  end)
-
   it('does not crash when repeatedly failing to start shell', function()
     source([[
       set shell=nosuchshell
@@ -1230,7 +1256,7 @@ describe('pty process teardown', function()
   it('does not prevent/delay exit. #4798 #4900', function()
     skip(fn.executable('sleep') == 0, 'missing "sleep" command')
     -- Use a nested nvim (in :term) to test without --headless.
-    fn.termopen({
+    fn.jobstart({
       n.nvim_prog,
       '-u',
       'NONE',
@@ -1243,7 +1269,10 @@ describe('pty process teardown', function()
       '+terminal',
       '+!(sleep 300 &)',
       '+qa',
-    }, { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } })
+    }, {
+      term = true,
+      env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
+    })
 
     -- Exiting should terminate all descendants (PTY, its children, ...).
     screen:expect([[
diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index bfa5a0eb6a..ce4ba1905f 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -103,7 +103,8 @@ describe('command-line option', function()
 
       -- Need to explicitly pipe to stdin so that the embedded Nvim instance doesn't try to read
       -- data from the terminal #18181
-      fn.termopen(string.format([[echo "" | %s]], table.concat(args, ' ')), {
+      fn.jobstart(string.format([[echo "" | %s]], table.concat(args, ' ')), {
+        term = true,
         env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
       })
       screen:expect(
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index 319a037342..76b0755441 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -55,7 +55,10 @@ describe('startup', function()
     clear()
     local screen
     screen = Screen.new(84, 3)
-    fn.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' })
+    fn.jobstart(
+      { nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' },
+      { term = true }
+    )
     screen:expect([[
       ^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) |
                                                                                           |*2
@@ -98,7 +101,7 @@ describe('startup', function()
     screen = Screen.new(60, 7)
     -- not the same colors on windows for some reason
     screen._default_attr_ids = nil
-    local id = fn.termopen({
+    local id = fn.jobstart({
       nvim_prog,
       '-u',
       'NONE',
@@ -108,6 +111,7 @@ describe('startup', function()
       'set noruler',
       '-D',
     }, {
+      term = true,
       env = {
         VIMRUNTIME = os.getenv('VIMRUNTIME'),
       },
@@ -360,7 +364,7 @@ describe('startup', function()
       command([[set shellcmdflag=/s\ /c shellxquote=\"]])
     end
     -- Running in :terminal
-    fn.termopen({
+    fn.jobstart({
       nvim_prog,
       '-u',
       'NONE',
@@ -371,6 +375,7 @@ describe('startup', function()
       '-c',
       'echo has("ttyin") has("ttyout")',
     }, {
+      term = true,
       env = {
         VIMRUNTIME = os.getenv('VIMRUNTIME'),
       },
@@ -393,13 +398,14 @@ describe('startup', function()
       os.remove('Xtest_startup_ttyout')
     end)
     -- Running in :terminal
-    fn.termopen(
+    fn.jobstart(
       (
         [["%s" -u NONE -i NONE --cmd "%s"]]
         .. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
         .. [[ -c q | cat -v]]
       ):format(nvim_prog, nvim_set),
       {
+        term = true,
         env = {
           VIMRUNTIME = os.getenv('VIMRUNTIME'),
         },
@@ -424,7 +430,7 @@ describe('startup', function()
       os.remove('Xtest_startup_ttyout')
     end)
     -- Running in :terminal
-    fn.termopen(
+    fn.jobstart(
       (
         [[echo foo | ]] -- Input from a pipe.
         .. [["%s" -u NONE -i NONE --cmd "%s"]]
@@ -432,6 +438,7 @@ describe('startup', function()
         .. [[ -c q -- -]]
       ):format(nvim_prog, nvim_set),
       {
+        term = true,
         env = {
           VIMRUNTIME = os.getenv('VIMRUNTIME'),
         },
@@ -454,13 +461,14 @@ describe('startup', function()
       command([[set shellcmdflag=/s\ /c shellxquote=\"]])
     end
     -- Running in :terminal
-    fn.termopen(
+    fn.jobstart(
       (
         [[echo foo | ]]
         .. [["%s" -u NONE -i NONE --cmd "%s"]]
         .. [[ -c "echo has('ttyin') has('ttyout')"]]
       ):format(nvim_prog, nvim_set),
       {
+        term = true,
         env = {
           VIMRUNTIME = os.getenv('VIMRUNTIME'),
         },
@@ -614,7 +622,7 @@ describe('startup', function()
     local screen
     screen = Screen.new(60, 6)
     screen._default_attr_ids = nil
-    local id = fn.termopen({
+    local id = fn.jobstart({
       nvim_prog,
       '-u',
       'NONE',
@@ -625,6 +633,7 @@ describe('startup', function()
       '--cmd',
       'let g:foo = g:bar',
     }, {
+      term = true,
       env = {
         VIMRUNTIME = os.getenv('VIMRUNTIME'),
       },
@@ -1144,7 +1153,8 @@ describe('user config init', function()
 
         local screen = Screen.new(50, 8)
         screen._default_attr_ids = nil
-        fn.termopen({ nvim_prog }, {
+        fn.jobstart({ nvim_prog }, {
+          term = true,
           env = {
             VIMRUNTIME = os.getenv('VIMRUNTIME'),
           },
@@ -1418,7 +1428,7 @@ describe('inccommand on ex mode', function()
     clear()
     local screen
     screen = Screen.new(60, 10)
-    local id = fn.termopen({
+    local id = fn.jobstart({
       nvim_prog,
       '-u',
       'NONE',
@@ -1429,6 +1439,7 @@ describe('inccommand on ex mode', function()
       '-E',
       'test/README.md',
     }, {
+      term = true,
       env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
     })
     fn.chansend(id, '%s/N')
diff --git a/test/functional/editor/mode_normal_spec.lua b/test/functional/editor/mode_normal_spec.lua
index 5237f51237..353a261edb 100644
--- a/test/functional/editor/mode_normal_spec.lua
+++ b/test/functional/editor/mode_normal_spec.lua
@@ -26,10 +26,10 @@ describe('Normal mode', function()
 
   it('&showcmd does not crash with :startinsert #28419', function()
     local screen = Screen.new(60, 17)
-    fn.termopen(
-      { n.nvim_prog, '--clean', '--cmd', 'startinsert' },
-      { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } }
-    )
+    fn.jobstart({ n.nvim_prog, '--clean', '--cmd', 'startinsert' }, {
+      term = true,
+      env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
+    })
     screen:expect({
       grid = [[
         ^                                                            |
diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
index 2820cc9663..7234985009 100644
--- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
+++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
@@ -115,7 +115,8 @@ describe("preserve and (R)ecover with custom 'directory'", function()
     t.skip(t.is_os('win'))
     local screen0 = Screen.new()
     local child_server = new_pipename()
-    fn.termopen({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server }, {
+    fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server }, {
+      term = true,
       env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
     })
     screen0:expect({ any = pesc('[No Name]') }) -- Wait for the child process to start.
@@ -446,9 +447,10 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
   end)
 
   it('(Q)uit at first file argument', function()
-    local chan = fn.termopen(
+    local chan = fn.jobstart(
       { nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', init_dir, '--cmd', init_set, testfile },
       {
+        term = true,
         env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
       }
     )
@@ -468,7 +470,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
   end)
 
   it('(A)bort at second file argument with -p', function()
-    local chan = fn.termopen({
+    local chan = fn.jobstart({
       nvim_prog,
       '-u',
       'NONE',
@@ -482,6 +484,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
       otherfile,
       testfile,
     }, {
+      term = true,
       env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
     })
     retry(nil, nil, function()
@@ -508,7 +511,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
       second	%s	/^  \zssecond$/
       third	%s	/^  \zsthird$/]]):format(testfile, testfile, testfile)
     )
-    local chan = fn.termopen({
+    local chan = fn.jobstart({
       nvim_prog,
       '-u',
       'NONE',
@@ -522,6 +525,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
       'set tags=' .. otherfile,
       '-tsecond',
     }, {
+      term = true,
       env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
     })
     retry(nil, nil, function()
diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua
index d68c780f49..b58bf0bf43 100644
--- a/test/functional/legacy/window_cmd_spec.lua
+++ b/test/functional/legacy/window_cmd_spec.lua
@@ -120,7 +120,7 @@ describe('splitkeep', function()
             row = 0,
             col = 0,
           }))
-          vim.cmd("call termopen([&sh, &shcf, 'true'], { 'on_exit': 'C2' })")
+          vim.cmd("call jobstart([&sh, &shcf, 'true'], { 'term': v:true, 'on_exit': 'C2' })")
       end
     })]])
     feed('j')
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index edb4c928c1..e209ed9025 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -378,7 +378,7 @@ describe(':terminal buffer', function()
     }, exec_lua('return _G.input'))
   end)
 
-  it('no heap-buffer-overflow when using termopen(echo) #3161', function()
+  it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function()
     local testfilename = 'Xtestfile-functional-terminal-buffers_spec'
     write_file(testfilename, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa')
     finally(function()
@@ -387,8 +387,8 @@ describe(':terminal buffer', function()
     feed_command('edit ' .. testfilename)
     -- Move cursor away from the beginning of the line
     feed('$')
-    -- Let termopen() modify the buffer
-    feed_command('call termopen("echo")')
+    -- Let jobstart(…,{term=true}) modify the buffer
+    feed_command([[call jobstart("echo", {'term':v:true})]])
     assert_alive()
     feed_command('bdelete!')
   end)
@@ -411,7 +411,7 @@ describe(':terminal buffer', function()
 
   it('handles split UTF-8 sequences #16245', function()
     local screen = Screen.new(50, 7)
-    fn.termopen({ testprg('shell-test'), 'UTF-8' })
+    fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true })
     screen:expect([[
       ^å                                                 |
       ref: å̲                                            |
@@ -669,7 +669,7 @@ if is_os('win') then
   end)
 end
 
-describe('termopen()', function()
+describe('termopen() (deprecated alias to `jobstart(…,{term=true})`)', function()
   before_each(clear)
 
   it('disallowed when textlocked and in cmdwin buffer', function()
diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua
index 9912c1ff7b..bb97411f43 100644
--- a/test/functional/terminal/channel_spec.lua
+++ b/test/functional/terminal/channel_spec.lua
@@ -75,8 +75,8 @@ describe('terminal channel is closed and later released if', function()
     eq(chans - 1, eval('len(nvim_list_chans())'))
   end)
 
-  it('opened by termopen(), exited, and deleted by pressing a key', function()
-    command([[let id = termopen('echo')]])
+  it('opened by jobstart(…,{term=true}), exited, and deleted by pressing a key', function()
+    command([[let id = jobstart('echo',{'term':v:true})]])
     local chans = eval('len(nvim_list_chans())')
     -- wait for process to exit
     screen:expect({ any = '%[Process exited 0%]' })
@@ -96,8 +96,8 @@ describe('terminal channel is closed and later released if', function()
   end)
 
   -- This indirectly covers #16264
-  it('opened by termopen(), exited, and deleted by :bdelete', function()
-    command([[let id = termopen('echo')]])
+  it('opened by jobstart(…,{term=true}), exited, and deleted by :bdelete', function()
+    command([[let id = jobstart('echo', {'term':v:true})]])
     local chans = eval('len(nvim_list_chans())')
     -- wait for process to exit
     screen:expect({ any = '%[Process exited 0%]' })
@@ -124,7 +124,7 @@ it('chansend sends lines to terminal channel in proper order', function()
   screen._default_attr_ids = nil
   local shells = is_os('win') and { 'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop' } or { 'sh' }
   for _, sh in ipairs(shells) do
-    command([[let id = termopen(']] .. sh .. [[')]])
+    command([[let id = jobstart(']] .. sh .. [[', {'term':v:true})]])
     command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]])
     screen:expect {
       any = [[echo "hello".*echo "world"]],
@@ -149,7 +149,7 @@ describe('no crash when TermOpen autocommand', function()
     })
   end)
 
-  it('processes job exit event when using termopen()', function()
+  it('processes job exit event when using jobstart(…,{term=true})', function()
     command([[autocmd TermOpen * call input('')]])
     async_meths.nvim_command('terminal foobar')
     screen:expect {
@@ -179,7 +179,7 @@ describe('no crash when TermOpen autocommand', function()
     assert_alive()
   end)
 
-  it('wipes buffer and processes events when using termopen()', function()
+  it('wipes buffer and processes events when using jobstart(…,{term=true})', function()
     command([[autocmd TermOpen * bwipe! | call input('')]])
     async_meths.nvim_command('terminal foobar')
     screen:expect {
diff --git a/test/functional/terminal/clipboard_spec.lua b/test/functional/terminal/clipboard_spec.lua
index 4a1a0e29fd..f0ce407eaa 100644
--- a/test/functional/terminal/clipboard_spec.lua
+++ b/test/functional/terminal/clipboard_spec.lua
@@ -56,7 +56,7 @@ describe(':terminal', function()
       return string.format('\027]52;;%s\027\\', arg)
     end
 
-    fn.termopen({ testprg('shell-test'), '-t', osc52(encoded) })
+    fn.jobstart({ testprg('shell-test'), '-t', osc52(encoded) }, { term = true })
 
     retry(nil, 1000, function()
       eq(text, exec_lua([[ return vim.g.clipboard_data ]]))
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index 368afd6d36..8594a9ce16 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -239,7 +239,7 @@ describe(':terminal cursor', function()
     feed([[]])
     command('set statusline=~~~')
     command('new')
-    call('termopen', { testprg('tty-test') })
+    call('jobstart', { testprg('tty-test') }, { term = true })
     feed('i')
     screen:expect({
       grid = [[
diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua
index 5ebe7bd4fc..5224d322d3 100644
--- a/test/functional/terminal/ex_terminal_spec.lua
+++ b/test/functional/terminal/ex_terminal_spec.lua
@@ -175,7 +175,7 @@ local function test_terminal_with_fake_shell(backslash)
     api.nvim_set_option_value('shellxquote', '', {}) -- win: avoid extra quotes
   end)
 
-  it('with no argument, acts like termopen()', function()
+  it('with no argument, acts like jobstart(…,{term=true})', function()
     command('autocmd! nvim_terminal TermClose')
     feed_command('terminal')
     screen:expect([[
@@ -196,7 +196,7 @@ local function test_terminal_with_fake_shell(backslash)
     ]])
   end)
 
-  it("with no argument, but 'shell' has arguments, acts like termopen()", function()
+  it("with no argument, but 'shell' has arguments, acts like jobstart(…,{term=true})", function()
     api.nvim_set_option_value('shell', shell_path .. ' INTERACT', {})
     feed_command('terminal')
     screen:expect([[
diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua
index 5ed3c72b72..c43d139f70 100644
--- a/test/functional/terminal/highlight_spec.lua
+++ b/test/functional/terminal/highlight_spec.lua
@@ -33,7 +33,7 @@ describe(':terminal highlight', function()
       [12] = { bold = true, underdouble = true },
       [13] = { italic = true, undercurl = true },
     })
-    command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
+    command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test')))
     feed('i')
     screen:expect([[
       tty ready                                         |
@@ -150,7 +150,7 @@ it(':terminal highlight has lower precedence than editor #9964', function()
     },
   })
   -- Child nvim process in :terminal (with cterm colors).
-  fn.termopen({
+  fn.jobstart({
     nvim_prog_abs(),
     '-n',
     '-u',
@@ -163,6 +163,7 @@ it(':terminal highlight has lower precedence than editor #9964', function()
     '+norm! ichild nvim',
     '+norm! oline 2',
   }, {
+    term = true,
     env = {
       VIMRUNTIME = os.getenv('VIMRUNTIME'),
     },
@@ -200,7 +201,7 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi
     [4] = { background = Screen.colors.Grey90, reverse = true },
     [5] = { background = Screen.colors.Red },
   })
-  command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
+  command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test')))
   screen:expect([[
     ^tty ready                                         |
                                                       |*6
@@ -318,7 +319,7 @@ describe(':terminal highlight forwarding', function()
       [2] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } },
       [3] = { { foreground = tonumber('0xff8000') }, {} },
     })
-    command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
+    command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test')))
     feed('i')
     screen:expect([[
       tty ready                                         |
@@ -364,7 +365,7 @@ describe(':terminal highlight with custom palette', function()
       [9] = { bold = true },
     })
     api.nvim_set_var('terminal_color_3', '#123456')
-    command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
+    command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test')))
     feed('i')
     screen:expect([[
       tty ready                                         |
diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua
index 0de7873200..804c5367eb 100644
--- a/test/functional/terminal/scrollback_spec.lua
+++ b/test/functional/terminal/scrollback_spec.lua
@@ -355,7 +355,9 @@ describe(':terminal prints more lines than the screen height and exits', functio
   it('will push extra lines to scrollback', function()
     clear()
     local screen = Screen.new(30, 7, { rgb = false })
-    command(("call termopen(['%s', '10']) | startinsert"):format(testprg('tty-test')))
+    command(
+      ("call jobstart(['%s', '10'], {'term':v:true}) | startinsert"):format(testprg('tty-test'))
+    )
     screen:expect([[
       line6                         |
       line7                         |
@@ -623,7 +625,7 @@ describe('pending scrollback line handling', function()
       local bufnr = vim.api.nvim_create_buf(false, true)
       local args = ...
       vim.api.nvim_buf_call(bufnr, function()
-        vim.fn.termopen(args)
+        vim.fn.jobstart(args, { term = true })
       end)
       vim.api.nvim_win_set_buf(0, bufnr)
       vim.cmd('startinsert')
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 832bacb534..de92aefd5b 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -2114,7 +2114,7 @@ describe('TUI', function()
       [5] = { bold = true, reverse = true },
       [6] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen },
     })
-    fn.termopen({
+    fn.jobstart({
       nvim_prog,
       '--clean',
       '--cmd',
@@ -2124,6 +2124,7 @@ describe('TUI', function()
       '--cmd',
       'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile',
     }, {
+      term = true,
       env = {
         VIMRUNTIME = os.getenv('VIMRUNTIME'),
       },
@@ -2146,7 +2147,7 @@ describe('TUI', function()
   for _, guicolors in ipairs({ 'notermguicolors', 'termguicolors' }) do
     it('has no black flicker when clearing regions during startup with ' .. guicolors, function()
       local screen = Screen.new(50, 10)
-      fn.termopen({
+      fn.jobstart({
         nvim_prog,
         '--clean',
         '--cmd',
@@ -2154,6 +2155,7 @@ describe('TUI', function()
         '--cmd',
         'sleep 10',
       }, {
+        term = true,
         env = {
           VIMRUNTIME = os.getenv('VIMRUNTIME'),
         },
diff --git a/test/functional/testterm.lua b/test/functional/testterm.lua
index 7ae28dce69..17209d947e 100644
--- a/test/functional/testterm.lua
+++ b/test/functional/testterm.lua
@@ -141,7 +141,7 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts)
   })
 
   api.nvim_command('enew')
-  api.nvim_call_function('termopen', { cmd, env and { env = env } or nil })
+  api.nvim_call_function('jobstart', { cmd, { term = true, env = (env and env or nil) } })
   api.nvim_input('')
   local vim_errmsg = api.nvim_eval('v:errmsg')
   if vim_errmsg and '' ~= vim_errmsg then
diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua
index 745ad70efe..e10c79fa48 100644
--- a/test/functional/ui/hlstate_spec.lua
+++ b/test/functional/ui/hlstate_spec.lua
@@ -224,7 +224,7 @@ describe('ext_hlstate detailed highlights', function()
       [6] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { 5, 1 } },
       [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
     })
-    command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
+    command(("enew | call jobstart(['%s'],{'term':v:true})"):format(testprg('tty-test')))
     screen:expect([[
       ^tty ready                               |
                                               |
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 6d8c6609c8..d48d56aeb6 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -2062,7 +2062,7 @@ describe('ui/msg_puts_printf', function()
     )
 
     cmd = cmd .. '"' .. nvim_prog .. '" -u NONE -i NONE -Es -V1'
-    command([[call termopen(']] .. cmd .. [[')]])
+    command([[call jobstart(']] .. cmd .. [[',{'term':v:true})]])
     screen:expect([[
     ^Exモードに入ります。ノー |
     マルモードに戻るには "vis|
diff --git a/test/functional/ui/title_spec.lua b/test/functional/ui/title_spec.lua
index aa9ac3f5b5..2de1e71457 100644
--- a/test/functional/ui/title_spec.lua
+++ b/test/functional/ui/title_spec.lua
@@ -80,7 +80,7 @@ describe('title', function()
   it('is updated in Terminal mode', function()
     api.nvim_set_option_value('title', true, {})
     api.nvim_set_option_value('titlestring', '(%{mode(1)}) | nvim', {})
-    fn.termopen({ n.testprg('shell-test'), 'INTERACT' })
+    fn.jobstart({ n.testprg('shell-test'), 'INTERACT' }, { term = true })
     screen:expect(function()
       eq('(nt) | nvim', screen.title)
     end)
diff --git a/test/old/testdir/runnvim.vim b/test/old/testdir/runnvim.vim
index 578614c8a1..f5945aeaee 100644
--- a/test/old/testdir/runnvim.vim
+++ b/test/old/testdir/runnvim.vim
@@ -8,6 +8,7 @@ function s:logger.on_exit(id, data, event)
 endfunction
 
 let s:logger.env = #{VIMRUNTIME: $VIMRUNTIME}
+let s:logger.term = v:true
 
 " Replace non-printable chars by special sequence, or "<%x>".
 let s:escaped_char = {"\n": '\n', "\r": '\r', "\t": '\t'}
@@ -25,7 +26,7 @@ function Main()
   set lines=25
   set columns=80
   enew
-  let job = termopen(args, s:logger)
+  let job = jobstart(args, s:logger)
   let results = jobwait([job], 5 * 60 * 1000)
   " TODO(ZyX-I): Get colors
   let screen = getline(1, '$')
-- 
cgit 


From 889f9a0c5d1f3fe522af0d2b43a82317b0589b94 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 20 Dec 2024 08:04:46 +0800
Subject: vim-patch:9.1.0948: Missing cmdline completion for :pbuffer (#31645)

Problem:  Missing cmdline completion for :pbuffer.
Solution: Add cmdline completion for :pbuffer like :buffer.
          (zeertzjq)

fixes: vim/vim#16250
closes: vim/vim#16251

https://github.com/vim/vim/commit/3baf19a2b144b215c5b537c3c1b3b80a79b0fe99
---
 src/nvim/cmdexpand.c              |  1 +
 test/old/testdir/test_preview.vim | 11 +++++++++++
 2 files changed, 12 insertions(+)

diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index d977b20cc4..80ff5e057d 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -2001,6 +2001,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
     FALLTHROUGH;
   case CMD_buffer:
   case CMD_sbuffer:
+  case CMD_pbuffer:
   case CMD_checktime:
     xp->xp_context = EXPAND_BUFFERS;
     xp->xp_pattern = (char *)arg;
diff --git a/test/old/testdir/test_preview.vim b/test/old/testdir/test_preview.vim
index 424d93f54d..422c50ac77 100644
--- a/test/old/testdir/test_preview.vim
+++ b/test/old/testdir/test_preview.vim
@@ -47,12 +47,23 @@ func Test_window_preview_from_pbuffer()
   edit Xpreview.c
   const buf_num = bufnr('%')
   enew
+
+  call feedkeys(":pbuffer Xpre\\\"\", 'xt')
+  call assert_equal("\"pbuffer Xpreview.c", @:)
+
   call assert_equal(1, winnr('$'))
   exe 'pbuffer ' .  buf_num
   call assert_equal(2, winnr('$'))
   call assert_equal(0, &previewwindow)
 
   call s:goto_preview_and_close()
+
+  call assert_equal(1, winnr('$'))
+  pbuffer Xpreview.c
+  call assert_equal(2, winnr('$'))
+  call assert_equal(0, &previewwindow)
+
+  call s:goto_preview_and_close()
 endfunc
 
 func Test_window_preview_terminal()
-- 
cgit 


From 39781be14baff508efd4f99c11786c4228ea2c8d Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 20 Dec 2024 11:33:05 +0800
Subject: vim-patch:c363ca1: runtime(netrw): change indent size from 1 to 2
 (#31648)

closes: vim/vim#16248

https://github.com/vim/vim/commit/c363ca1ecd1f8db03663ef98dcf41eeacc3c22c7

Co-authored-by: Luca Saccarola 
---
 runtime/autoload/netrw.vim     | 16910 +++++++++++++++++++--------------------
 runtime/plugin/netrwPlugin.vim |   247 +-
 2 files changed, 8579 insertions(+), 8578 deletions(-)

diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index 116e105b57..b87eb85913 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -1,11 +1,11 @@
 " netrw.vim: Handles file transfer and remote directory listing across
 "            AUTOLOAD SECTION
 " Maintainer: This runtime file is looking for a new maintainer.
-" Date:		  May 03, 2023
-" Version:	173a
+" Date:           May 03, 2023
+" Version:      173a
 " Last Change: {{{1
-"   2023 Nov 21 by Vim Project: ignore wildignore when expanding $COMSPEC	(v173a)
-"   2023 Nov 22 by Vim Project: fix handling of very long filename on longlist style	(v173a)
+"   2023 Nov 21 by Vim Project: ignore wildignore when expanding $COMSPEC       (v173a)
+"   2023 Nov 22 by Vim Project: fix handling of very long filename on longlist style    (v173a)
 "   2024 Feb 19 by Vim Project: (announce adoption)
 "   2024 Feb 29 by Vim Project: handle symlinks in tree mode correctly
 "   2024 Apr 03 by Vim Project: detect filetypes for remote edited files
@@ -41,8 +41,9 @@
 "   2024 Dec 04 by Vim Project: do not detach for gvim (#16168)
 "   2024 Dec 08 by Vim Project: check the first arg of netrw_browsex_viewer for being executable (#16185)
 "   2024 Dec 12 by Vim Project: do not pollute the search history (#16206)
+"   2024 Dec 19 by Vim Project: change style (#16248)
 "   }}}
-" Former Maintainer:	Charles E Campbell
+" Former Maintainer:    Charles E Campbell
 " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
 " Copyright:    Copyright (C) 2016 Charles E. Campbell {{{1
 "               Permission is hereby granted to use and distribute this code,
@@ -72,15 +73,15 @@ endif
 " (netrw will benefit from vim's having patch#656, too)
 let s:needspatches=[1557,213]
 if exists("s:needspatches")
- for ptch in s:needspatches
-  if v:version < 704 || (v:version == 704 && !has("patch".ptch))
-   if !exists("s:needpatch{ptch}")
-    unsilent echomsg "***sorry*** this version of netrw requires vim v7.4 with patch#".ptch
-   endif
-   let s:needpatch{ptch}= 1
-   finish
-  endif
- endfor
+  for ptch in s:needspatches
+    if v:version < 704 || (v:version == 704 && !has("patch".ptch))
+      if !exists("s:needpatch{ptch}")
+        unsilent echomsg "***sorry*** this version of netrw requires vim v7.4 with patch#".ptch
+      endif
+      let s:needpatch{ptch}= 1
+      finish
+    endif
+  endfor
 endif
 
 let g:loaded_netrw = "v173"
@@ -105,124 +106,124 @@ setl cpo&vim
 "          (this function can optionally take a list of messages)
 "  Dec 2, 2019 : max errnum currently is 106
 fun! netrw#ErrorMsg(level,msg,errnum)
-"  call Dfunc("netrw#ErrorMsg(level=".a:level." msg<".a:msg."> errnum=".a:errnum.") g:netrw_use_errorwindow=".g:netrw_use_errorwindow)
+  "  call Dfunc("netrw#ErrorMsg(level=".a:level." msg<".a:msg."> errnum=".a:errnum.") g:netrw_use_errorwindow=".g:netrw_use_errorwindow)
 
   if a:level < g:netrw_errorlvl
-"   call Dret("netrw#ErrorMsg : suppressing level=".a:level." since g:netrw_errorlvl=".g:netrw_errorlvl)
-   return
+    "   call Dret("netrw#ErrorMsg : suppressing level=".a:level." since g:netrw_errorlvl=".g:netrw_errorlvl)
+    return
   endif
 
   if a:level == 1
-   let level= "**warning** (netrw) "
+    let level= "**warning** (netrw) "
   elseif a:level == 2
-   let level= "**error** (netrw) "
+    let level= "**error** (netrw) "
   else
-   let level= "**note** (netrw) "
+    let level= "**note** (netrw) "
   endif
-"  call Decho("level=".level,'~'.expand(""))
+  "  call Decho("level=".level,'~'.expand(""))
 
   if g:netrw_use_errorwindow == 2 && exists("*popup_atcursor")
-   " use popup window
-   if type(a:msg) == 3
-    let msg = [level]+a:msg
-   else
-    let msg= level.a:msg
-   endif
-   let s:popuperr_id  = popup_atcursor(msg,{})
-   let s:popuperr_text= ""
- elseif g:netrw_use_errorwindow
-   " (default) netrw creates a one-line window to show error/warning
-   " messages (reliably displayed)
-
-   " record current window number
-   let s:winBeforeErr= winnr()
-"   call Decho("s:winBeforeErr=".s:winBeforeErr,'~'.expand(""))
-
-   " getting messages out reliably is just plain difficult!
-   " This attempt splits the current window, creating a one line window.
-   if bufexists("NetrwMessage") && bufwinnr("NetrwMessage") > 0
-"    call Decho("write to NetrwMessage buffer",'~'.expand(""))
-    exe bufwinnr("NetrwMessage")."wincmd w"
-"    call Decho("setl ma noro",'~'.expand(""))
-    setl ma noro
+    " use popup window
     if type(a:msg) == 3
-     for msg in a:msg
-      NetrwKeepj call setline(line("$")+1,level.msg)
-     endfor
+      let msg = [level]+a:msg
     else
-     NetrwKeepj call setline(line("$")+1,level.a:msg)
-    endif
-    NetrwKeepj $
-   else
-"    call Decho("create a NetrwMessage buffer window",'~'.expand(""))
-    bo 1split
-    sil! call s:NetrwEnew()
-    sil! NetrwKeepj call s:NetrwOptionsSafe(1)
-    setl bt=nofile
-    NetrwKeepj file NetrwMessage
-"    call Decho("setl ma noro",'~'.expand(""))
-    setl ma noro
-    if type(a:msg) == 3
-     for msg in a:msg
-      NetrwKeepj call setline(line("$")+1,level.msg)
-     endfor
+      let msg= level.a:msg
+    endif
+    let s:popuperr_id  = popup_atcursor(msg,{})
+    let s:popuperr_text= ""
+  elseif g:netrw_use_errorwindow
+    " (default) netrw creates a one-line window to show error/warning
+    " messages (reliably displayed)
+
+    " record current window number
+    let s:winBeforeErr= winnr()
+    "   call Decho("s:winBeforeErr=".s:winBeforeErr,'~'.expand(""))
+
+    " getting messages out reliably is just plain difficult!
+    " This attempt splits the current window, creating a one line window.
+    if bufexists("NetrwMessage") && bufwinnr("NetrwMessage") > 0
+      "    call Decho("write to NetrwMessage buffer",'~'.expand(""))
+      exe bufwinnr("NetrwMessage")."wincmd w"
+      "    call Decho("setl ma noro",'~'.expand(""))
+      setl ma noro
+      if type(a:msg) == 3
+        for msg in a:msg
+          NetrwKeepj call setline(line("$")+1,level.msg)
+        endfor
+      else
+        NetrwKeepj call setline(line("$")+1,level.a:msg)
+      endif
+      NetrwKeepj $
     else
-     NetrwKeepj call setline(line("$"),level.a:msg)
-    endif
-    NetrwKeepj $
-   endif
-"   call Decho("wrote msg<".level.a:msg."> to NetrwMessage win#".winnr(),'~'.expand(""))
-   if &fo !~ '[ta]'
-    syn clear
-    syn match netrwMesgNote	"^\*\*note\*\*"
-    syn match netrwMesgWarning	"^\*\*warning\*\*"
-    syn match netrwMesgError	"^\*\*error\*\*"
-    hi link netrwMesgWarning WarningMsg
-    hi link netrwMesgError   Error
-   endif
-"   call Decho("setl noma ro bh=wipe",'~'.expand(""))
-   setl ro nomod noma bh=wipe
+      "    call Decho("create a NetrwMessage buffer window",'~'.expand(""))
+      bo 1split
+      sil! call s:NetrwEnew()
+      sil! NetrwKeepj call s:NetrwOptionsSafe(1)
+      setl bt=nofile
+      NetrwKeepj file NetrwMessage
+      "    call Decho("setl ma noro",'~'.expand(""))
+      setl ma noro
+      if type(a:msg) == 3
+        for msg in a:msg
+          NetrwKeepj call setline(line("$")+1,level.msg)
+        endfor
+      else
+        NetrwKeepj call setline(line("$"),level.a:msg)
+      endif
+      NetrwKeepj $
+    endif
+    "   call Decho("wrote msg<".level.a:msg."> to NetrwMessage win#".winnr(),'~'.expand(""))
+    if &fo !~ '[ta]'
+      syn clear
+      syn match netrwMesgNote     "^\*\*note\*\*"
+      syn match netrwMesgWarning  "^\*\*warning\*\*"
+      syn match netrwMesgError    "^\*\*error\*\*"
+      hi link netrwMesgWarning WarningMsg
+      hi link netrwMesgError   Error
+    endif
+    "   call Decho("setl noma ro bh=wipe",'~'.expand(""))
+    setl ro nomod noma bh=wipe
 
   else
-   " (optional) netrw will show messages using echomsg.  Even if the
-   " message doesn't appear, at least it'll be recallable via :messages
-"   redraw!
-   if a:level == s:WARNING
-    echohl WarningMsg
-   elseif a:level == s:ERROR
-    echohl Error
-   endif
-
-   if type(a:msg) == 3
-     for msg in a:msg
-      unsilent echomsg level.msg
-     endfor
-   else
-    unsilent echomsg level.a:msg
-   endif
-
-"   call Decho("echomsg ***netrw*** ".a:msg,'~'.expand(""))
-   echohl None
-  endif
-
-"  call Dret("netrw#ErrorMsg")
+    " (optional) netrw will show messages using echomsg.  Even if the
+    " message doesn't appear, at least it'll be recallable via :messages
+    "   redraw!
+    if a:level == s:WARNING
+      echohl WarningMsg
+    elseif a:level == s:ERROR
+      echohl Error
+    endif
+
+    if type(a:msg) == 3
+      for msg in a:msg
+        unsilent echomsg level.msg
+      endfor
+    else
+      unsilent echomsg level.a:msg
+    endif
+
+    "   call Decho("echomsg ***netrw*** ".a:msg,'~'.expand(""))
+    echohl None
+  endif
+
+  "  call Dret("netrw#ErrorMsg")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwInit: initializes variables if they haven't been defined {{{2
 "            Loosely,  varname = value.
 fun s:NetrwInit(varname,value)
-" call Decho("varname<".a:varname."> value=".a:value,'~'.expand(""))
+  " call Decho("varname<".a:varname."> value=".a:value,'~'.expand(""))
   if !exists(a:varname)
-   if type(a:value) == 0
-    exe "let ".a:varname."=".a:value
-   elseif type(a:value) == 1 && a:value =~ '^[{[]'
-    exe "let ".a:varname."=".a:value
-   elseif type(a:value) == 1
-    exe "let ".a:varname."="."'".a:value."'"
-   else
-    exe "let ".a:varname."=".a:value
-   endif
+    if type(a:value) == 0
+      exe "let ".a:varname."=".a:value
+    elseif type(a:value) == 1 && a:value =~ '^[{[]'
+      exe "let ".a:varname."=".a:value
+    elseif type(a:value) == 1
+      exe "let ".a:varname."="."'".a:value."'"
+    else
+      exe "let ".a:varname."=".a:value
+    endif
   endif
 endfun
 
@@ -230,11 +231,11 @@ endfun
 "  Netrw Constants: {{{2
 call s:NetrwInit("g:netrw_dirhistcnt",0)
 if !exists("s:LONGLIST")
- call s:NetrwInit("s:THINLIST",0)
- call s:NetrwInit("s:LONGLIST",1)
- call s:NetrwInit("s:WIDELIST",2)
- call s:NetrwInit("s:TREELIST",3)
- call s:NetrwInit("s:MAXLIST" ,4)
+  call s:NetrwInit("s:THINLIST",0)
+  call s:NetrwInit("s:LONGLIST",1)
+  call s:NetrwInit("s:WIDELIST",2)
+  call s:NetrwInit("s:TREELIST",3)
+  call s:NetrwInit("s:MAXLIST" ,4)
 endif
 
 let s:NOTE    = 0
@@ -256,54 +257,54 @@ if !exists("g:netrw_use_errorwindow")
 endif
 
 if !exists("g:netrw_dav_cmd")
- if executable("cadaver")
-  let g:netrw_dav_cmd	= "cadaver"
- elseif executable("curl")
-  let g:netrw_dav_cmd	= "curl"
- else
-  let g:netrw_dav_cmd   = ""
- endif
+  if executable("cadaver")
+    let g:netrw_dav_cmd   = "cadaver"
+  elseif executable("curl")
+    let g:netrw_dav_cmd   = "curl"
+  else
+    let g:netrw_dav_cmd   = ""
+  endif
 endif
 if !exists("g:netrw_fetch_cmd")
- if executable("fetch")
-  let g:netrw_fetch_cmd	= "fetch -o"
- else
-  let g:netrw_fetch_cmd	= ""
- endif
+  if executable("fetch")
+    let g:netrw_fetch_cmd = "fetch -o"
+  else
+    let g:netrw_fetch_cmd = ""
+  endif
 endif
 if !exists("g:netrw_file_cmd")
- if executable("elinks")
-  call s:NetrwInit("g:netrw_file_cmd","elinks")
- elseif executable("links")
-  call s:NetrwInit("g:netrw_file_cmd","links")
- endif
+  if executable("elinks")
+    call s:NetrwInit("g:netrw_file_cmd","elinks")
+  elseif executable("links")
+    call s:NetrwInit("g:netrw_file_cmd","links")
+  endif
 endif
 if !exists("g:netrw_ftp_cmd")
-  let g:netrw_ftp_cmd	= "ftp"
+  let g:netrw_ftp_cmd   = "ftp"
 endif
 let s:netrw_ftp_cmd= g:netrw_ftp_cmd
 if !exists("g:netrw_ftp_options")
- let g:netrw_ftp_options= "-i -n"
+  let g:netrw_ftp_options= "-i -n"
 endif
 if !exists("g:netrw_http_cmd")
- if executable("wget")
-  let g:netrw_http_cmd	= "wget"
-  call s:NetrwInit("g:netrw_http_xcmd","-q -O")
- elseif executable("curl")
-  let g:netrw_http_cmd	= "curl"
-  call s:NetrwInit("g:netrw_http_xcmd","-L -o")
- elseif executable("elinks")
-  let g:netrw_http_cmd = "elinks"
-  call s:NetrwInit("g:netrw_http_xcmd","-source >")
- elseif executable("fetch")
-  let g:netrw_http_cmd	= "fetch"
-  call s:NetrwInit("g:netrw_http_xcmd","-o")
- elseif executable("links")
-  let g:netrw_http_cmd = "links"
-  call s:NetrwInit("g:netrw_http_xcmd","-http.extra-header ".shellescape("Accept-Encoding: identity", 1)." -source >")
- else
-  let g:netrw_http_cmd	= ""
- endif
+  if executable("wget")
+    let g:netrw_http_cmd  = "wget"
+    call s:NetrwInit("g:netrw_http_xcmd","-q -O")
+  elseif executable("curl")
+    let g:netrw_http_cmd  = "curl"
+    call s:NetrwInit("g:netrw_http_xcmd","-L -o")
+  elseif executable("elinks")
+    let g:netrw_http_cmd = "elinks"
+    call s:NetrwInit("g:netrw_http_xcmd","-source >")
+  elseif executable("fetch")
+    let g:netrw_http_cmd  = "fetch"
+    call s:NetrwInit("g:netrw_http_xcmd","-o")
+  elseif executable("links")
+    let g:netrw_http_cmd = "links"
+    call s:NetrwInit("g:netrw_http_xcmd","-http.extra-header ".shellescape("Accept-Encoding: identity", 1)." -source >")
+  else
+    let g:netrw_http_cmd  = ""
+  endif
 endif
 call s:NetrwInit("g:netrw_http_put_cmd","curl -T")
 call s:NetrwInit("g:netrw_keepj","keepj")
@@ -311,37 +312,37 @@ call s:NetrwInit("g:netrw_rcp_cmd"  , "rcp")
 call s:NetrwInit("g:netrw_rsync_cmd", "rsync")
 call s:NetrwInit("g:netrw_rsync_sep", "/")
 if !exists("g:netrw_scp_cmd")
- if executable("scp")
-  call s:NetrwInit("g:netrw_scp_cmd" , "scp -q")
- elseif executable("pscp")
-  call s:NetrwInit("g:netrw_scp_cmd", 'pscp -q')
- else
-  call s:NetrwInit("g:netrw_scp_cmd" , "scp -q")
- endif
+  if executable("scp")
+    call s:NetrwInit("g:netrw_scp_cmd" , "scp -q")
+  elseif executable("pscp")
+    call s:NetrwInit("g:netrw_scp_cmd", 'pscp -q')
+  else
+    call s:NetrwInit("g:netrw_scp_cmd" , "scp -q")
+  endif
 endif
 call s:NetrwInit("g:netrw_sftp_cmd" , "sftp")
 call s:NetrwInit("g:netrw_ssh_cmd"  , "ssh")
 
 if has("win32")
-  \ && exists("g:netrw_use_nt_rcp")
-  \ && g:netrw_use_nt_rcp
-  \ && executable( $SystemRoot .'/system32/rcp.exe')
- let s:netrw_has_nt_rcp = 1
- let s:netrw_rcpmode    = '-b'
+      \ && exists("g:netrw_use_nt_rcp")
+      \ && g:netrw_use_nt_rcp
+      \ && executable( $SystemRoot .'/system32/rcp.exe')
+  let s:netrw_has_nt_rcp = 1
+  let s:netrw_rcpmode    = '-b'
 else
- let s:netrw_has_nt_rcp = 0
- let s:netrw_rcpmode    = ''
+  let s:netrw_has_nt_rcp = 0
+  let s:netrw_rcpmode    = ''
 endif
 
 " ---------------------------------------------------------------------
 " Default values for netrw's global variables {{{2
 " Cygwin Detection ------- {{{3
 if !exists("g:netrw_cygwin")
- if has("win32unix") && &shell =~ '\%(\\|\\)\%(\.exe\)\=$'
-  let g:netrw_cygwin= 1
- else
-  let g:netrw_cygwin= 0
- endif
+  if has("win32unix") && &shell =~ '\%(\\|\\)\%(\.exe\)\=$'
+    let g:netrw_cygwin= 1
+  else
+    let g:netrw_cygwin= 0
+  endif
 endif
 " Default values - a-c ---------- {{{3
 call s:NetrwInit("g:netrw_alto"        , &sb)
@@ -354,8 +355,8 @@ call s:NetrwInit("g:netrw_clipboard"   , 1)
 call s:NetrwInit("g:netrw_compress"    , "gzip")
 call s:NetrwInit("g:netrw_ctags"       , "ctags")
 if exists("g:netrw_cursorline") && !exists("g:netrw_cursor")
- call netrw#ErrorMsg(s:NOTE,'g:netrw_cursorline is deprecated; use g:netrw_cursor instead',77)
- let g:netrw_cursor= g:netrw_cursorline
+  call netrw#ErrorMsg(s:NOTE,'g:netrw_cursorline is deprecated; use g:netrw_cursor instead',77)
+  let g:netrw_cursor= g:netrw_cursorline
 endif
 call s:NetrwInit("g:netrw_cursor"      , 2)
 let s:netrw_usercul = &cursorline
@@ -366,156 +367,156 @@ call s:NetrwInit("g:netrw_cygdrive","/cygdrive")
 call s:NetrwInit("s:didstarstar",0)
 call s:NetrwInit("g:netrw_dirhistcnt"      , 0)
 let s:xz_opt = has('unix') ? "XZ_OPT=-T0" :
-		\ (has("win32") && &shell =~? '\vcmd(\.exe)?$' ?
-		\ "setx XZ_OPT=-T0 &&" : "")
+      \ (has("win32") && &shell =~? '\vcmd(\.exe)?$' ?
+      \ "setx XZ_OPT=-T0 &&" : "")
 call s:NetrwInit("g:netrw_decompress ", "{"
-            \ .."'.lz4':      'lz4 -d',"
-            \ .."'.lzo':      'lzop -d',"
-            \ .."'.lz':       'lzip -dk',"
-            \ .."'.7z':       '7za x',"
-            \ .."'.001':      '7za x',"
-            \ .."'.zip':      'unzip',"
-            \ .."'.bz':       'bunzip2 -k',"
-            \ .."'.bz2':      'bunzip2 -k',"
-            \ .."'.gz':       'gunzip -k',"
-            \ .."'.lzma':     'unlzma -T0 -k',"
-            \ .."'.xz':       'unxz -T0 -k',"
-            \ .."'.zst':      'zstd -T0 -d',"
-            \ .."'.Z':        'uncompress -k',"
-            \ .."'.tar':      'tar -xvf',"
-            \ .."'.tar.bz':   'tar -xvjf',"
-            \ .."'.tar.bz2':  'tar -xvjf',"
-            \ .."'.tbz':      'tar -xvjf',"
-            \ .."'.tbz2':     'tar -xvjf',"
-            \ .."'.tar.gz':   'tar -xvzf',"
-            \ .."'.tgz':      'tar -xvzf',"
-            \ .."'.tar.lzma': '"..s:xz_opt.." tar -xvf --lzma',"
-            \ .."'.tlz':      '"..s:xz_opt.." tar -xvf --lzma',"
-            \ .."'.tar.xz':   '"..s:xz_opt.." tar -xvfJ',"
-            \ .."'.txz':      '"..s:xz_opt.." tar -xvfJ',"
-            \ .."'.tar.zst':  '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd',"
-            \ .."'.tzst':     '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd',"
-            \ .."'.rar':      '"..(executable("unrar")?"unrar x -ad":"rar x -ad").."'"
-            \ .."}")
+      \ .."'.lz4':      'lz4 -d',"
+      \ .."'.lzo':      'lzop -d',"
+      \ .."'.lz':       'lzip -dk',"
+      \ .."'.7z':       '7za x',"
+      \ .."'.001':      '7za x',"
+      \ .."'.zip':      'unzip',"
+      \ .."'.bz':       'bunzip2 -k',"
+      \ .."'.bz2':      'bunzip2 -k',"
+      \ .."'.gz':       'gunzip -k',"
+      \ .."'.lzma':     'unlzma -T0 -k',"
+      \ .."'.xz':       'unxz -T0 -k',"
+      \ .."'.zst':      'zstd -T0 -d',"
+      \ .."'.Z':        'uncompress -k',"
+      \ .."'.tar':      'tar -xvf',"
+      \ .."'.tar.bz':   'tar -xvjf',"
+      \ .."'.tar.bz2':  'tar -xvjf',"
+      \ .."'.tbz':      'tar -xvjf',"
+      \ .."'.tbz2':     'tar -xvjf',"
+      \ .."'.tar.gz':   'tar -xvzf',"
+      \ .."'.tgz':      'tar -xvzf',"
+      \ .."'.tar.lzma': '"..s:xz_opt.." tar -xvf --lzma',"
+      \ .."'.tlz':      '"..s:xz_opt.." tar -xvf --lzma',"
+      \ .."'.tar.xz':   '"..s:xz_opt.." tar -xvfJ',"
+      \ .."'.txz':      '"..s:xz_opt.." tar -xvfJ',"
+      \ .."'.tar.zst':  '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd',"
+      \ .."'.tzst':     '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd',"
+      \ .."'.rar':      '"..(executable("unrar")?"unrar x -ad":"rar x -ad").."'"
+      \ .."}")
 unlet s:xz_opt
 call s:NetrwInit("g:netrw_dirhistmax"       , 10)
 call s:NetrwInit("g:netrw_fastbrowse"       , 1)
 call s:NetrwInit("g:netrw_ftp_browse_reject", '^total\s\+\d\+$\|^Trying\s\+\d\+.*$\|^KERBEROS_V\d rejected\|^Security extensions not\|No such file\|: connect to address [0-9a-fA-F:]*: No route to host$')
 if !exists("g:netrw_ftp_list_cmd")
- if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin)
-  let g:netrw_ftp_list_cmd     = "ls -lF"
-  let g:netrw_ftp_timelist_cmd = "ls -tlF"
-  let g:netrw_ftp_sizelist_cmd = "ls -slF"
- else
-  let g:netrw_ftp_list_cmd     = "dir"
-  let g:netrw_ftp_timelist_cmd = "dir"
-  let g:netrw_ftp_sizelist_cmd = "dir"
- endif
+  if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin)
+    let g:netrw_ftp_list_cmd     = "ls -lF"
+    let g:netrw_ftp_timelist_cmd = "ls -tlF"
+    let g:netrw_ftp_sizelist_cmd = "ls -slF"
+  else
+    let g:netrw_ftp_list_cmd     = "dir"
+    let g:netrw_ftp_timelist_cmd = "dir"
+    let g:netrw_ftp_sizelist_cmd = "dir"
+  endif
 endif
 call s:NetrwInit("g:netrw_ftpmode",'binary')
 " Default values - h-lh ---------- {{{3
 call s:NetrwInit("g:netrw_hide",1)
 if !exists("g:netrw_ignorenetrc")
- if &shell =~ '\c\<\%(cmd\|4nt\)\.exe$'
-  let g:netrw_ignorenetrc= 1
- else
-  let g:netrw_ignorenetrc= 0
- endif
+  if &shell =~ '\c\<\%(cmd\|4nt\)\.exe$'
+    let g:netrw_ignorenetrc= 1
+  else
+    let g:netrw_ignorenetrc= 0
+  endif
 endif
 call s:NetrwInit("g:netrw_keepdir",1)
 if !exists("g:netrw_list_cmd")
- if g:netrw_scp_cmd =~ '^pscp' && executable("pscp")
-  if exists("g:netrw_list_cmd_options")
-   let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME: ".g:netrw_list_cmd_options
-  else
-   let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME:"
-  endif
- elseif executable(g:netrw_ssh_cmd)
-  " provide a scp-based default listing command
-  if exists("g:netrw_list_cmd_options")
-   let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa ".g:netrw_list_cmd_options
+  if g:netrw_scp_cmd =~ '^pscp' && executable("pscp")
+    if exists("g:netrw_list_cmd_options")
+      let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME: ".g:netrw_list_cmd_options
+    else
+      let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME:"
+    endif
+  elseif executable(g:netrw_ssh_cmd)
+    " provide a scp-based default listing command
+    if exists("g:netrw_list_cmd_options")
+      let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa ".g:netrw_list_cmd_options
+    else
+      let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa"
+    endif
   else
-   let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa"
+    "  call Decho(g:netrw_ssh_cmd." is not executable",'~'.expand(""))
+    let g:netrw_list_cmd= ""
   endif
- else
-"  call Decho(g:netrw_ssh_cmd." is not executable",'~'.expand(""))
-  let g:netrw_list_cmd= ""
- endif
 endif
 call s:NetrwInit("g:netrw_list_hide","")
 " Default values - lh-lz ---------- {{{3
 if exists("g:netrw_local_copycmd")
- let g:netrw_localcopycmd= g:netrw_local_copycmd
- call netrw#ErrorMsg(s:NOTE,"g:netrw_local_copycmd is deprecated in favor of g:netrw_localcopycmd",84)
+  let g:netrw_localcopycmd= g:netrw_local_copycmd
+  call netrw#ErrorMsg(s:NOTE,"g:netrw_local_copycmd is deprecated in favor of g:netrw_localcopycmd",84)
 endif
 if !exists("g:netrw_localcmdshell")
- let g:netrw_localcmdshell= ""
+  let g:netrw_localcmdshell= ""
 endif
 if !exists("g:netrw_localcopycmd")
- if has("win32")
-  if g:netrw_cygwin
-   let g:netrw_localcopycmd= "cp"
+  if has("win32")
+    if g:netrw_cygwin
+      let g:netrw_localcopycmd= "cp"
+    else
+      let g:netrw_localcopycmd   = expand("$COMSPEC", v:true)
+      let g:netrw_localcopycmdopt= " /c copy"
+    endif
+  elseif has("unix") || has("macunix")
+    let g:netrw_localcopycmd= "cp"
   else
-   let g:netrw_localcopycmd   = expand("$COMSPEC", v:true)
-   let g:netrw_localcopycmdopt= " /c copy"
-  endif
- elseif has("unix") || has("macunix")
-  let g:netrw_localcopycmd= "cp"
- else
-  let g:netrw_localcopycmd= ""
- endif
+    let g:netrw_localcopycmd= ""
+  endif
 endif
 if !exists("g:netrw_localcopydircmd")
- if has("win32")
-  if g:netrw_cygwin
-   let g:netrw_localcopydircmd   = "cp"
-   let g:netrw_localcopydircmdopt= " -R"
+  if has("win32")
+    if g:netrw_cygwin
+      let g:netrw_localcopydircmd   = "cp"
+      let g:netrw_localcopydircmdopt= " -R"
+    else
+      let g:netrw_localcopydircmd   = expand("$COMSPEC", v:true)
+      let g:netrw_localcopydircmdopt= " /c xcopy /e /c /h /i /k"
+    endif
+  elseif has("unix")
+    let g:netrw_localcopydircmd   = "cp"
+    let g:netrw_localcopydircmdopt= " -R"
+  elseif has("macunix")
+    let g:netrw_localcopydircmd   = "cp"
+    let g:netrw_localcopydircmdopt= " -R"
   else
-   let g:netrw_localcopydircmd   = expand("$COMSPEC", v:true)
-   let g:netrw_localcopydircmdopt= " /c xcopy /e /c /h /i /k"
-  endif
- elseif has("unix")
-  let g:netrw_localcopydircmd   = "cp"
-  let g:netrw_localcopydircmdopt= " -R"
- elseif has("macunix")
-  let g:netrw_localcopydircmd   = "cp"
-  let g:netrw_localcopydircmdopt= " -R"
- else
-  let g:netrw_localcopydircmd= ""
- endif
+    let g:netrw_localcopydircmd= ""
+  endif
 endif
 if exists("g:netrw_local_mkdir")
- let g:netrw_localmkdir= g:netrw_local_mkdir
- call netrw#ErrorMsg(s:NOTE,"g:netrw_local_mkdir is deprecated in favor of g:netrw_localmkdir",87)
+  let g:netrw_localmkdir= g:netrw_local_mkdir
+  call netrw#ErrorMsg(s:NOTE,"g:netrw_local_mkdir is deprecated in favor of g:netrw_localmkdir",87)
 endif
 if has("win32")
   if g:netrw_cygwin
-   call s:NetrwInit("g:netrw_localmkdir","mkdir")
+    call s:NetrwInit("g:netrw_localmkdir","mkdir")
   else
-   let g:netrw_localmkdir   = expand("$COMSPEC", v:true)
-   let g:netrw_localmkdiropt= " /c mkdir"
+    let g:netrw_localmkdir   = expand("$COMSPEC", v:true)
+    let g:netrw_localmkdiropt= " /c mkdir"
   endif
 else
- call s:NetrwInit("g:netrw_localmkdir","mkdir")
+  call s:NetrwInit("g:netrw_localmkdir","mkdir")
 endif
 call s:NetrwInit("g:netrw_remote_mkdir","mkdir")
 if exists("g:netrw_local_movecmd")
- let g:netrw_localmovecmd= g:netrw_local_movecmd
- call netrw#ErrorMsg(s:NOTE,"g:netrw_local_movecmd is deprecated in favor of g:netrw_localmovecmd",88)
+  let g:netrw_localmovecmd= g:netrw_local_movecmd
+  call netrw#ErrorMsg(s:NOTE,"g:netrw_local_movecmd is deprecated in favor of g:netrw_localmovecmd",88)
 endif
 if !exists("g:netrw_localmovecmd")
- if has("win32")
-  if g:netrw_cygwin
-   let g:netrw_localmovecmd= "mv"
+  if has("win32")
+    if g:netrw_cygwin
+      let g:netrw_localmovecmd= "mv"
+    else
+      let g:netrw_localmovecmd   = expand("$COMSPEC", v:true)
+      let g:netrw_localmovecmdopt= " /c move"
+    endif
+  elseif has("unix") || has("macunix")
+    let g:netrw_localmovecmd= "mv"
   else
-   let g:netrw_localmovecmd   = expand("$COMSPEC", v:true)
-   let g:netrw_localmovecmdopt= " /c move"
-  endif
- elseif has("unix") || has("macunix")
-  let g:netrw_localmovecmd= "mv"
- else
-  let g:netrw_localmovecmd= ""
- endif
+    let g:netrw_localmovecmd= ""
+  endif
 endif
 " following serves as an example for how to insert a version&patch specific test
 "if v:version < 704 || (v:version == 704 && !has("patch1107"))
@@ -523,10 +524,10 @@ endif
 call s:NetrwInit("g:netrw_liststyle"  , s:THINLIST)
 " sanity checks
 if g:netrw_liststyle < 0 || g:netrw_liststyle >= s:MAXLIST
- let g:netrw_liststyle= s:THINLIST
+  let g:netrw_liststyle= s:THINLIST
 endif
 if g:netrw_liststyle == s:LONGLIST && g:netrw_scp_cmd !~ '^pscp'
- let g:netrw_list_cmd= g:netrw_list_cmd." -l"
+  let g:netrw_list_cmd= g:netrw_list_cmd." -l"
 endif
 " Default values - m-r ---------- {{{3
 call s:NetrwInit("g:netrw_markfileesc"   , '*./[\~')
@@ -536,11 +537,11 @@ call s:NetrwInit("g:netrw_mkdir_cmd"     , g:netrw_ssh_cmd." USEPORT HOSTNAME mk
 call s:NetrwInit("g:netrw_mousemaps"     , (exists("+mouse") && &mouse =~# '[anh]'))
 call s:NetrwInit("g:netrw_retmap"        , 0)
 if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin)
- call s:NetrwInit("g:netrw_chgperm"       , "chmod PERM FILENAME")
+  call s:NetrwInit("g:netrw_chgperm"       , "chmod PERM FILENAME")
 elseif has("win32")
- call s:NetrwInit("g:netrw_chgperm"       , "cacls FILENAME /e /p PERM")
+  call s:NetrwInit("g:netrw_chgperm"       , "cacls FILENAME /e /p PERM")
 else
- call s:NetrwInit("g:netrw_chgperm"       , "chmod PERM FILENAME")
+  call s:NetrwInit("g:netrw_chgperm"       , "chmod PERM FILENAME")
 endif
 call s:NetrwInit("g:netrw_preview"       , 0)
 call s:NetrwInit("g:netrw_scpport"       , "-P")
@@ -553,29 +554,29 @@ call s:NetrwInit("g:netrw_rmf_cmd"       , g:netrw_ssh_cmd." USEPORT HOSTNAME rm
 " Default values - q-s ---------- {{{3
 call s:NetrwInit("g:netrw_quickhelp",0)
 let s:QuickHelp= ["-:go up dir  D:delete  R:rename  s:sort-by  x:special",
-   \              "(create new)  %:file  d:directory",
-   \              "(windows split&open) o:horz  v:vert  p:preview",
-   \              "i:style  qf:file info  O:obtain  r:reverse",
-   \              "(marks)  mf:mark file  mt:set target  mm:move  mc:copy",
-   \              "(bookmarks)  mb:make  mB:delete  qb:list  gb:go to",
-   \              "(history)  qb:list  u:go up  U:go down",
-   \              "(targets)  mt:target Tb:use bookmark  Th:use history"]
+      \              "(create new)  %:file  d:directory",
+      \              "(windows split&open) o:horz  v:vert  p:preview",
+      \              "i:style  qf:file info  O:obtain  r:reverse",
+      \              "(marks)  mf:mark file  mt:set target  mm:move  mc:copy",
+      \              "(bookmarks)  mb:make  mB:delete  qb:list  gb:go to",
+      \              "(history)  qb:list  u:go up  U:go down",
+      \              "(targets)  mt:target Tb:use bookmark  Th:use history"]
 " g:netrw_sepchr: picking a character that doesn't appear in filenames that can be used to separate priority from filename
 call s:NetrwInit("g:netrw_sepchr"        , (&enc == "euc-jp")? "\" : "\")
 if !exists("g:netrw_keepj") || g:netrw_keepj == "keepj"
- call s:NetrwInit("s:netrw_silentxfer"    , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil keepj " : "keepj ")
+  call s:NetrwInit("s:netrw_silentxfer"    , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil keepj " : "keepj ")
 else
- call s:NetrwInit("s:netrw_silentxfer"    , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil " : " ")
+  call s:NetrwInit("s:netrw_silentxfer"    , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil " : " ")
 endif
 call s:NetrwInit("g:netrw_sort_by"       , "name") " alternatives: date                                      , size
 call s:NetrwInit("g:netrw_sort_options"  , "")
 call s:NetrwInit("g:netrw_sort_direction", "normal") " alternative: reverse  (z y x ...)
 if !exists("g:netrw_sort_sequence")
- if has("unix")
-  let g:netrw_sort_sequence= '[\/]$,\,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$'
- else
-  let g:netrw_sort_sequence= '[\/]$,\.h$,\.c$,\.cpp$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$'
- endif
+  if has("unix")
+    let g:netrw_sort_sequence= '[\/]$,\,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$'
+  else
+    let g:netrw_sort_sequence= '[\/]$,\.h$,\.c$,\.cpp$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$'
+  endif
 endif
 call s:NetrwInit("g:netrw_special_syntax"   , 0)
 call s:NetrwInit("g:netrw_ssh_browse_reject", '^total\s\+\d\+$')
@@ -584,15 +585,15 @@ call s:NetrwInit("g:netrw_sizestyle"        ,"b")
 " Default values - t-w ---------- {{{3
 call s:NetrwInit("g:netrw_timefmt","%c")
 if !exists("g:netrw_xstrlen")
- if exists("g:Align_xstrlen")
-  let g:netrw_xstrlen= g:Align_xstrlen
- elseif exists("g:drawit_xstrlen")
-  let g:netrw_xstrlen= g:drawit_xstrlen
- elseif &enc == "latin1" || !has("multi_byte")
-  let g:netrw_xstrlen= 0
- else
-  let g:netrw_xstrlen= 1
- endif
+  if exists("g:Align_xstrlen")
+    let g:netrw_xstrlen= g:Align_xstrlen
+  elseif exists("g:drawit_xstrlen")
+    let g:netrw_xstrlen= g:drawit_xstrlen
+  elseif &enc == "latin1" || !has("multi_byte")
+    let g:netrw_xstrlen= 0
+  else
+    let g:netrw_xstrlen= 1
+  endif
 endif
 call s:NetrwInit("g:NetrwTopLvlMenu","Netrw.")
 call s:NetrwInit("g:netrw_winsize",50)
@@ -602,50 +603,50 @@ if g:netrw_winsize > 100|let g:netrw_winsize= 100|endif
 " Default values for netrw's script variables: {{{2
 call s:NetrwInit("g:netrw_fname_escape",' ?&;%')
 if has("win32")
- call s:NetrwInit("g:netrw_glob_escape",'*?`{[]$')
+  call s:NetrwInit("g:netrw_glob_escape",'*?`{[]$')
 else
- call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\')
+  call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\')
 endif
 call s:NetrwInit("g:netrw_menu_escape",'.&? \')
 call s:NetrwInit("g:netrw_tmpfile_escape",' &;')
 call s:NetrwInit("s:netrw_map_escape","<|\n\r\\\\"")
 if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4')
- let s:treedepthstring= "│ "
+  let s:treedepthstring= "│ "
 else
- let s:treedepthstring= "| "
+  let s:treedepthstring= "| "
 endif
 call s:NetrwInit("s:netrw_posn",'{}')
 
 " BufEnter event ignored by decho when following variable is true
 "  Has a side effect that doau BufReadPost doesn't work, so
 "  files read by network transfer aren't appropriately highlighted.
-"let g:decho_bufenter = 1	"Decho
+"let g:decho_bufenter = 1       "Decho
 
 " ======================
 "  Netrw Initialization: {{{1
 " ======================
 if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on")
-" call Decho("installed beval events",'~'.expand(""))
- let &l:bexpr = "netrw#BalloonHelp()"
-" call Decho("&l:bexpr<".&l:bexpr."> buf#".bufnr())
- au FileType netrw	setl beval
- au WinLeave *		if &ft == "netrw" && exists("s:initbeval")|let &beval= s:initbeval|endif
- au VimEnter * 		let s:initbeval= &beval
-"else " Decho
-" if v:version < 700           | call Decho("did not install beval events: v:version=".v:version." < 700","~".expand(""))     | endif
+  " call Decho("installed beval events",'~'.expand(""))
+  let &l:bexpr = "netrw#BalloonHelp()"
+  " call Decho("&l:bexpr<".&l:bexpr."> buf#".bufnr())
+  au FileType netrw      setl beval
+  au WinLeave *          if &ft == "netrw" && exists("s:initbeval")|let &beval= s:initbeval|endif
+  au VimEnter *          let s:initbeval= &beval
+  "else " Decho
+  " if v:version < 700           | call Decho("did not install beval events: v:version=".v:version." < 700","~".expand(""))     | endif
 " if !has("balloon_eval")      | call Decho("did not install beval events: does not have balloon_eval","~".expand(""))        | endif
 " if exists("s:initbeval")     | call Decho("did not install beval events: s:initbeval exists","~".expand(""))                | endif
 " if exists("g:netrw_nobeval") | call Decho("did not install beval events: g:netrw_nobeval exists","~".expand(""))            | endif
 " if !has("syntax")            | call Decho("did not install beval events: does not have syntax highlighting","~".expand("")) | endif
 " if exists("g:syntax_on")     | call Decho("did not install beval events: g:syntax_on exists","~".expand(""))                | endif
 endif
-au WinEnter *	if &ft == "netrw"|call s:NetrwInsureWinVars()|endif
+au WinEnter *   if &ft == "netrw"|call s:NetrwInsureWinVars()|endif
 
 if g:netrw_keepj =~# "keepj"
- com! -nargs=*	NetrwKeepj	keepj 
+  com! -nargs=*  NetrwKeepj      keepj 
 else
- let g:netrw_keepj= ""
- com! -nargs=*	NetrwKeepj	
+  let g:netrw_keepj= ""
+  com! -nargs=*  NetrwKeepj      
 endif
 
 " ==============================
@@ -655,45 +656,45 @@ endif
 " ---------------------------------------------------------------------
 " netrw#BalloonHelp: {{{2
 if v:version >= 700 && has("balloon_eval") && has("syntax") && exists("g:syntax_on") && !exists("g:netrw_nobeval")
-" call Decho("loading netrw#BalloonHelp()",'~'.expand(""))
- fun! netrw#BalloonHelp()
-   if &ft != "netrw"
-    return ""
-   endif
-   if exists("s:popuperr_id") && popup_getpos(s:popuperr_id) != {}
-    " popup error window is still showing
-    " s:pouperr_id and s:popuperr_text are set up in netrw#ErrorMsg()
-    if exists("s:popuperr_text") && s:popuperr_text != "" && v:beval_text != s:popuperr_text
-     " text under mouse hasn't changed; only close window when it changes
-     call popup_close(s:popuperr_id)
-     unlet s:popuperr_text
+  " call Decho("loading netrw#BalloonHelp()",'~'.expand(""))
+  fun! netrw#BalloonHelp()
+    if &ft != "netrw"
+      return ""
+    endif
+    if exists("s:popuperr_id") && popup_getpos(s:popuperr_id) != {}
+      " popup error window is still showing
+      " s:pouperr_id and s:popuperr_text are set up in netrw#ErrorMsg()
+      if exists("s:popuperr_text") && s:popuperr_text != "" && v:beval_text != s:popuperr_text
+        " text under mouse hasn't changed; only close window when it changes
+        call popup_close(s:popuperr_id)
+        unlet s:popuperr_text
+      else
+        let s:popuperr_text= v:beval_text
+      endif
+      let mesg= ""
+    elseif !exists("w:netrw_bannercnt") || v:beval_lnum >= w:netrw_bannercnt || (exists("g:netrw_nobeval") && g:netrw_nobeval)
+      let mesg= ""
+    elseif     v:beval_text == "Netrw" || v:beval_text == "Directory" || v:beval_text == "Listing"
+      let mesg = "i: thin-long-wide-tree  gh: quick hide/unhide of dot-files   qf: quick file info  %:open new file"
+    elseif     getline(v:beval_lnum) =~ '^"\s*/'
+      let mesg = ": edit/enter   o: edit/enter in horiz window   t: edit/enter in new tab   v:edit/enter in vert window"
+    elseif     v:beval_text == "Sorted" || v:beval_text == "by"
+      let mesg = 's: sort by name, time, file size, extension   r: reverse sorting order   mt: mark target'
+    elseif v:beval_text == "Sort"   || v:beval_text == "sequence"
+      let mesg = "S: edit sorting sequence"
+    elseif v:beval_text == "Hiding" || v:beval_text == "Showing"
+      let mesg = "a: hiding-showing-all   ctrl-h: editing hiding list   mh: hide/show by suffix"
+    elseif v:beval_text == "Quick" || v:beval_text == "Help"
+      let mesg = "Help: press "
+    elseif v:beval_text == "Copy/Move" || v:beval_text == "Tgt"
+      let mesg = "mt: mark target   mc: copy marked file to target   mm: move marked file to target"
     else
-     let s:popuperr_text= v:beval_text
-    endif
-    let mesg= ""
-   elseif !exists("w:netrw_bannercnt") || v:beval_lnum >= w:netrw_bannercnt || (exists("g:netrw_nobeval") && g:netrw_nobeval)
-    let mesg= ""
-   elseif     v:beval_text == "Netrw" || v:beval_text == "Directory" || v:beval_text == "Listing"
-    let mesg = "i: thin-long-wide-tree  gh: quick hide/unhide of dot-files   qf: quick file info  %:open new file"
-   elseif     getline(v:beval_lnum) =~ '^"\s*/'
-    let mesg = ": edit/enter   o: edit/enter in horiz window   t: edit/enter in new tab   v:edit/enter in vert window"
-   elseif     v:beval_text == "Sorted" || v:beval_text == "by"
-    let mesg = 's: sort by name, time, file size, extension   r: reverse sorting order   mt: mark target'
-   elseif v:beval_text == "Sort"   || v:beval_text == "sequence"
-    let mesg = "S: edit sorting sequence"
-   elseif v:beval_text == "Hiding" || v:beval_text == "Showing"
-    let mesg = "a: hiding-showing-all   ctrl-h: editing hiding list   mh: hide/show by suffix"
-   elseif v:beval_text == "Quick" || v:beval_text == "Help"
-    let mesg = "Help: press "
-   elseif v:beval_text == "Copy/Move" || v:beval_text == "Tgt"
-    let mesg = "mt: mark target   mc: copy marked file to target   mm: move marked file to target"
-   else
-    let mesg= ""
-   endif
-   return mesg
- endfun
-"else " Decho
-" if v:version < 700            |call Decho("did not load netrw#BalloonHelp(): vim version ".v:version." < 700 -","~".expand(""))|endif
+      let mesg= ""
+    endif
+    return mesg
+  endfun
+  "else " Decho
+  " if v:version < 700            |call Decho("did not load netrw#BalloonHelp(): vim version ".v:version." < 700 -","~".expand(""))|endif
 " if !has("balloon_eval")       |call Decho("did not load netrw#BalloonHelp(): does not have balloon eval","~".expand(""))       |endif
 " if !has("syntax")             |call Decho("did not load netrw#BalloonHelp(): syntax disabled","~".expand(""))                  |endif
 " if !exists("g:syntax_on")     |call Decho("did not load netrw#BalloonHelp(): g:syntax_on n/a","~".expand(""))                  |endif
@@ -719,39 +720,39 @@ endif
 "                == 6: Texplore
 fun! netrw#Explore(indx,dosplit,style,...)
   if !exists("b:netrw_curdir")
-   let b:netrw_curdir= getcwd()
+    let b:netrw_curdir= getcwd()
   endif
 
   " record current file for Rexplore's benefit
   if &ft != "netrw"
-   let w:netrw_rexfile= expand("%:p")
+    let w:netrw_rexfile= expand("%:p")
   endif
 
   " record current directory
   let curdir     = simplify(b:netrw_curdir)
   let curfiledir = substitute(expand("%:p"),'^\(.*[/\\]\)[^/\\]*$','\1','e')
   if !exists("g:netrw_cygwin") && has("win32")
-   let curdir= substitute(curdir,'\','/','g')
+    let curdir= substitute(curdir,'\','/','g')
   endif
 
   " using completion, directories with spaces in their names (thanks, Bill Gates, for a truly dumb idea)
   " will end up with backslashes here.  Solution: strip off backslashes that precede white space and
   " try Explore again.
   if a:0 > 0
-   if a:1 =~ "\\\s" && !filereadable(s:NetrwFile(a:1)) && !isdirectory(s:NetrwFile(a:1))
-    let a1 = substitute(a:1, '\\\(\s\)', '\1', 'g')
-    if a1 != a:1
-      call netrw#Explore(a:indx, a:dosplit, a:style, a1)
-      return
+    if a:1 =~ "\\\s" && !filereadable(s:NetrwFile(a:1)) && !isdirectory(s:NetrwFile(a:1))
+      let a1 = substitute(a:1, '\\\(\s\)', '\1', 'g')
+      if a1 != a:1
+        call netrw#Explore(a:indx, a:dosplit, a:style, a1)
+        return
+      endif
     endif
-   endif
   endif
 
   " save registers
   if !has('nvim') && has("clipboard") && g:netrw_clipboard
-"   call Decho("(netrw#Explore) save @* and @+",'~'.expand(""))
-   sil! let keepregstar = @*
-   sil! let keepregplus = @+
+    "   call Decho("(netrw#Explore) save @* and @+",'~'.expand(""))
+    sil! let keepregstar = @*
+    sil! let keepregplus = @+
   endif
   sil! let keepregslash= @/
 
@@ -759,353 +760,353 @@ fun! netrw#Explore(indx,dosplit,style,...)
   " -or- file has been modified AND file not hidden when abandoned
   " -or- Texplore used
   if a:dosplit || (&modified && &hidden == 0 && &bufhidden != "hide") || a:style == 6
-   call s:SaveWinVars()
-   let winsz= g:netrw_winsize
-   if a:indx > 0
-    let winsz= a:indx
-   endif
-
-   if a:style == 0      " Explore, Sexplore
-    let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz
-    if winsz == 0|let winsz= ""|endif
-    exe "noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s"
+    call s:SaveWinVars()
+    let winsz= g:netrw_winsize
+    if a:indx > 0
+      let winsz= a:indx
+    endif
 
-   elseif a:style == 1  " Explore!, Sexplore!
-    let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz
-    if winsz == 0|let winsz= ""|endif
-    exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v"
+    if a:style == 0      " Explore, Sexplore
+      let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz
+      if winsz == 0|let winsz= ""|endif
+      exe "noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s"
 
-   elseif a:style == 2  " Hexplore
-    let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz
-    if winsz == 0|let winsz= ""|endif
-    exe "keepalt noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s"
+    elseif a:style == 1  " Explore!, Sexplore!
+      let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz
+      if winsz == 0|let winsz= ""|endif
+      exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v"
 
-   elseif a:style == 3  " Hexplore!
-    let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz
-    if winsz == 0|let winsz= ""|endif
-    exe "keepalt noswapfile ".(!g:netrw_alto ? "below " : "above ").winsz."wincmd s"
+    elseif a:style == 2  " Hexplore
+      let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz
+      if winsz == 0|let winsz= ""|endif
+      exe "keepalt noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s"
 
-   elseif a:style == 4  " Vexplore
-    let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz
-    if winsz == 0|let winsz= ""|endif
-    exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v"
+    elseif a:style == 3  " Hexplore!
+      let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz
+      if winsz == 0|let winsz= ""|endif
+      exe "keepalt noswapfile ".(!g:netrw_alto ? "below " : "above ").winsz."wincmd s"
 
-   elseif a:style == 5  " Vexplore!
-    let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz
-    if winsz == 0|let winsz= ""|endif
-    exe "keepalt noswapfile ".(!g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v"
+    elseif a:style == 4  " Vexplore
+      let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz
+      if winsz == 0|let winsz= ""|endif
+      exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v"
+
+    elseif a:style == 5  " Vexplore!
+      let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz
+      if winsz == 0|let winsz= ""|endif
+      exe "keepalt noswapfile ".(!g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v"
 
-   elseif a:style == 6  " Texplore
-    call s:SaveBufVars()
-    exe "keepalt tabnew ".fnameescape(curdir)
-    call s:RestoreBufVars()
-   endif
-   call s:RestoreWinVars()
+    elseif a:style == 6  " Texplore
+      call s:SaveBufVars()
+      exe "keepalt tabnew ".fnameescape(curdir)
+      call s:RestoreBufVars()
+    endif
+    call s:RestoreWinVars()
   endif
   NetrwKeepj norm! 0
 
   if a:0 > 0
-   if a:1 =~ '^\~' && (has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin))
-    let dirname= simplify(substitute(a:1,'\~',expand("$HOME"),''))
-   elseif a:1 == '.'
-    let dirname= simplify(exists("b:netrw_curdir")? b:netrw_curdir : getcwd())
-    if dirname !~ '/$'
-     let dirname= dirname."/"
-    endif
-   elseif a:1 =~ '\$'
-    let dirname= simplify(expand(a:1))
-   elseif a:1 !~ '^\*\{1,2}/' && a:1 !~ '^\a\{3,}://'
-    let dirname= simplify(a:1)
-   else
-    let dirname= a:1
-   endif
+    if a:1 =~ '^\~' && (has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin))
+      let dirname= simplify(substitute(a:1,'\~',expand("$HOME"),''))
+    elseif a:1 == '.'
+      let dirname= simplify(exists("b:netrw_curdir")? b:netrw_curdir : getcwd())
+      if dirname !~ '/$'
+        let dirname= dirname."/"
+      endif
+    elseif a:1 =~ '\$'
+      let dirname= simplify(expand(a:1))
+    elseif a:1 !~ '^\*\{1,2}/' && a:1 !~ '^\a\{3,}://'
+      let dirname= simplify(a:1)
+    else
+      let dirname= a:1
+    endif
   else
-   " clear explore
-   call s:NetrwClearExplore()
-   return
+    " clear explore
+    call s:NetrwClearExplore()
+    return
   endif
 
   if dirname =~ '\.\./\=$'
-   let dirname= simplify(fnamemodify(dirname,':p:h'))
+    let dirname= simplify(fnamemodify(dirname,':p:h'))
   elseif dirname =~ '\.\.' || dirname == '.'
-   let dirname= simplify(fnamemodify(dirname,':p'))
+    let dirname= simplify(fnamemodify(dirname,':p'))
   endif
 
   if dirname =~ '^\*//'
-   " starpat=1: Explore *//pattern   (current directory only search for files containing pattern)
-   let pattern= substitute(dirname,'^\*//\(.*\)$','\1','')
-   let starpat= 1
-   if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
+    " starpat=1: Explore *//pattern   (current directory only search for files containing pattern)
+    let pattern= substitute(dirname,'^\*//\(.*\)$','\1','')
+    let starpat= 1
+    if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
 
   elseif dirname =~ '^\*\*//'
-   " starpat=2: Explore **//pattern  (recursive descent search for files containing pattern)
-   let pattern= substitute(dirname,'^\*\*//','','')
-   let starpat= 2
+    " starpat=2: Explore **//pattern  (recursive descent search for files containing pattern)
+    let pattern= substitute(dirname,'^\*\*//','','')
+    let starpat= 2
 
   elseif dirname =~ '/\*\*/'
-   " handle .../**/.../filepat
-   let prefixdir= substitute(dirname,'^\(.\{-}\)\*\*.*$','\1','')
-   if prefixdir =~ '^/' || (prefixdir =~ '^\a:/' && has("win32"))
-    let b:netrw_curdir = prefixdir
-   else
-    let b:netrw_curdir= getcwd().'/'.prefixdir
-   endif
-   let dirname= substitute(dirname,'^.\{-}\(\*\*/.*\)$','\1','')
-   let starpat= 4
+    " handle .../**/.../filepat
+    let prefixdir= substitute(dirname,'^\(.\{-}\)\*\*.*$','\1','')
+    if prefixdir =~ '^/' || (prefixdir =~ '^\a:/' && has("win32"))
+      let b:netrw_curdir = prefixdir
+    else
+      let b:netrw_curdir= getcwd().'/'.prefixdir
+    endif
+    let dirname= substitute(dirname,'^.\{-}\(\*\*/.*\)$','\1','')
+    let starpat= 4
 
   elseif dirname =~ '^\*/'
-   " case starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
-   let starpat= 3
+    " case starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
+    let starpat= 3
 
   elseif dirname=~ '^\*\*/'
-   " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
-   let starpat= 4
+    " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
+    let starpat= 4
 
   else
-   let starpat= 0
+    let starpat= 0
   endif
 
   if starpat == 0 && a:indx >= 0
-   " [Explore Hexplore Vexplore Sexplore] [dirname]
-   if dirname == ""
-    let dirname= curfiledir
-   endif
-   if dirname =~# '^scp://' || dirname =~ '^ftp://'
-    call netrw#Nread(2,dirname)
-   else
+    " [Explore Hexplore Vexplore Sexplore] [dirname]
     if dirname == ""
-     let dirname= getcwd()
-    elseif has("win32") && !g:netrw_cygwin
-     " Windows : check for a drive specifier, or else for a remote share name ('\\Foo' or '//Foo',
-     " depending on whether backslashes have been converted to forward slashes by earlier code).
-     if dirname !~ '^[a-zA-Z]:' && dirname !~ '^\\\\\w\+' && dirname !~ '^//\w\+'
-      let dirname= b:netrw_curdir."/".dirname
-     endif
-    elseif dirname !~ '^/'
-     let dirname= b:netrw_curdir."/".dirname
-    endif
-    call netrw#LocalBrowseCheck(dirname)
-   endif
-   if exists("w:netrw_bannercnt")
-    " done to handle P08-Ingelrest. :Explore will _Always_ go to the line just after the banner.
-    " If one wants to return the same place in the netrw window, use :Rex instead.
-    exe w:netrw_bannercnt
-   endif
-
-
-  " starpat=1: Explore *//pattern  (current directory only search for files containing pattern)
-  " starpat=2: Explore **//pattern (recursive descent search for files containing pattern)
-  " starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
-  " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
-  elseif a:indx <= 0
-   " Nexplore, Pexplore, Explore: handle starpat
-   if !mapcheck("","n") && !mapcheck("","n") && exists("b:netrw_curdir")
-    let s:didstarstar= 1
-    nnoremap   	:Pexplore
-    nnoremap   	:Nexplore
-   endif
-
-   if has("path_extra")
-    if !exists("w:netrw_explore_indx")
-     let w:netrw_explore_indx= 0
+      let dirname= curfiledir
+    endif
+    if dirname =~# '^scp://' || dirname =~ '^ftp://'
+      call netrw#Nread(2,dirname)
+    else
+      if dirname == ""
+        let dirname= getcwd()
+      elseif has("win32") && !g:netrw_cygwin
+        " Windows : check for a drive specifier, or else for a remote share name ('\\Foo' or '//Foo',
+        " depending on whether backslashes have been converted to forward slashes by earlier code).
+        if dirname !~ '^[a-zA-Z]:' && dirname !~ '^\\\\\w\+' && dirname !~ '^//\w\+'
+          let dirname= b:netrw_curdir."/".dirname
+        endif
+      elseif dirname !~ '^/'
+        let dirname= b:netrw_curdir."/".dirname
+      endif
+      call netrw#LocalBrowseCheck(dirname)
+    endif
+    if exists("w:netrw_bannercnt")
+      " done to handle P08-Ingelrest. :Explore will _Always_ go to the line just after the banner.
+      " If one wants to return the same place in the netrw window, use :Rex instead.
+      exe w:netrw_bannercnt
     endif
 
-    let indx = a:indx
 
-    if indx == -1
-     " Nexplore
-     if !exists("w:netrw_explore_list") " sanity check
-      NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or  improperly; see help for netrw-starstar",40)
-      if !has('nvim') && has("clipboard") && g:netrw_clipboard
-       if @* != keepregstar | sil! let @* = keepregstar | endif
-       if @+ != keepregplus | sil! let @+ = keepregplus | endif
+    " starpat=1: Explore *//pattern  (current directory only search for files containing pattern)
+    " starpat=2: Explore **//pattern (recursive descent search for files containing pattern)
+    " starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
+    " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
+  elseif a:indx <= 0
+    " Nexplore, Pexplore, Explore: handle starpat
+    if !mapcheck("","n") && !mapcheck("","n") && exists("b:netrw_curdir")
+      let s:didstarstar= 1
+      nnoremap      :Pexplore
+      nnoremap    :Nexplore
+    endif
+
+    if has("path_extra")
+      if !exists("w:netrw_explore_indx")
+        let w:netrw_explore_indx= 0
       endif
-      sil! let @/ = keepregslash
-      return
-     endif
-     let indx= w:netrw_explore_indx
-     if indx < 0                        | let indx= 0                           | endif
-     if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif
-     let curfile= w:netrw_explore_list[indx]
-     while indx < w:netrw_explore_listlen && curfile == w:netrw_explore_list[indx]
-      let indx= indx + 1
-     endwhile
-     if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif
-
-    elseif indx == -2
-     " Pexplore
-     if !exists("w:netrw_explore_list") " sanity check
-      NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or  improperly; see help for netrw-starstar",41)
-      if !has('nvim') && has("clipboard") && g:netrw_clipboard
-       if @* != keepregstar | sil! let @* = keepregstar | endif
-       if @+ != keepregplus | sil! let @+ = keepregplus | endif
+
+      let indx = a:indx
+
+      if indx == -1
+        " Nexplore
+        if !exists("w:netrw_explore_list") " sanity check
+          NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or  improperly; see help for netrw-starstar",40)
+          if !has('nvim') && has("clipboard") && g:netrw_clipboard
+            if @* != keepregstar | sil! let @* = keepregstar | endif
+            if @+ != keepregplus | sil! let @+ = keepregplus | endif
+          endif
+          sil! let @/ = keepregslash
+          return
+        endif
+        let indx= w:netrw_explore_indx
+        if indx < 0                        | let indx= 0                           | endif
+        if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif
+        let curfile= w:netrw_explore_list[indx]
+        while indx < w:netrw_explore_listlen && curfile == w:netrw_explore_list[indx]
+          let indx= indx + 1
+        endwhile
+        if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif
+
+      elseif indx == -2
+        " Pexplore
+        if !exists("w:netrw_explore_list") " sanity check
+          NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or  improperly; see help for netrw-starstar",41)
+          if !has('nvim') && has("clipboard") && g:netrw_clipboard
+            if @* != keepregstar | sil! let @* = keepregstar | endif
+            if @+ != keepregplus | sil! let @+ = keepregplus | endif
+          endif
+          sil! let @/ = keepregslash
+          return
+        endif
+        let indx= w:netrw_explore_indx
+        if indx < 0                        | let indx= 0                           | endif
+        if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif
+        let curfile= w:netrw_explore_list[indx]
+        while indx >= 0 && curfile == w:netrw_explore_list[indx]
+          let indx= indx - 1
+        endwhile
+        if indx < 0                        | let indx= 0                           | endif
+
+      else
+        " Explore -- initialize
+        " build list of files to Explore with Nexplore/Pexplore
+        NetrwKeepj keepalt call s:NetrwClearExplore()
+        let w:netrw_explore_indx= 0
+        if !exists("b:netrw_curdir")
+          let b:netrw_curdir= getcwd()
+        endif
+
+        " switch on starpat to build the w:netrw_explore_list of files
+        if starpat == 1
+          " starpat=1: Explore *//pattern  (current directory only search for files containing pattern)
+          try
+            exe "NetrwKeepj noautocmd vimgrep /".pattern."/gj ".fnameescape(b:netrw_curdir)."/*"
+          catch /^Vim\%((\a\+)\)\=:E480/
+            keepalt call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pattern.">",76)
+            return
+          endtry
+          let w:netrw_explore_list = s:NetrwExploreListUniq(map(getqflist(),'bufname(v:val.bufnr)'))
+          if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
+
+        elseif starpat == 2
+          " starpat=2: Explore **//pattern (recursive descent search for files containing pattern)
+          try
+            exe "sil NetrwKeepj noautocmd keepalt vimgrep /".pattern."/gj "."**/*"
+          catch /^Vim\%((\a\+)\)\=:E480/
+            keepalt call netrw#ErrorMsg(s:WARNING,'no files matched pattern<'.pattern.'>',45)
+            if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
+            if !has('nvim') && has("clipboard") && g:netrw_clipboard
+              if @* != keepregstar | sil! let @* = keepregstar | endif
+              if @+ != keepregplus | sil! let @+ = keepregplus | endif
+            endif
+            sil! let @/ = keepregslash
+            return
+          endtry
+          let s:netrw_curdir       = b:netrw_curdir
+          let w:netrw_explore_list = getqflist()
+          let w:netrw_explore_list = s:NetrwExploreListUniq(map(w:netrw_explore_list,'s:netrw_curdir."/".bufname(v:val.bufnr)'))
+          if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
+
+        elseif starpat == 3
+          " starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
+          let filepat= substitute(dirname,'^\*/','','')
+          let filepat= substitute(filepat,'^[%#<]','\\&','')
+          let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".filepat),'\n'))
+          if &hls | let keepregslash= s:ExplorePatHls(filepat) | endif
+
+        elseif starpat == 4
+          " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
+          let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".dirname),'\n'))
+          if &hls | let keepregslash= s:ExplorePatHls(dirname) | endif
+        endif " switch on starpat to build w:netrw_explore_list
+
+        let w:netrw_explore_listlen = len(w:netrw_explore_list)
+
+        if w:netrw_explore_listlen == 0 || (w:netrw_explore_listlen == 1 && w:netrw_explore_list[0] =~ '\*\*\/')
+          keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no files matched",42)
+          if !has('nvim') && has("clipboard") && g:netrw_clipboard
+            if @* != keepregstar | sil! let @* = keepregstar | endif
+            if @+ != keepregplus | sil! let @+ = keepregplus | endif
+          endif
+          sil! let @/ = keepregslash
+          return
+        endif
+      endif  " if indx ... endif
+
+      " NetrwStatusLine support - for exploring support
+      let w:netrw_explore_indx= indx
+
+      " wrap the indx around, but issue a note
+      if indx >= w:netrw_explore_listlen || indx < 0
+        let indx                = (indx < 0)? ( w:netrw_explore_listlen - 1 ) : 0
+        let w:netrw_explore_indx= indx
+        keepalt NetrwKeepj call netrw#ErrorMsg(s:NOTE,"no more files match Explore pattern",43)
       endif
-      sil! let @/ = keepregslash
-      return
-     endif
-     let indx= w:netrw_explore_indx
-     if indx < 0                        | let indx= 0                           | endif
-     if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif
-     let curfile= w:netrw_explore_list[indx]
-     while indx >= 0 && curfile == w:netrw_explore_list[indx]
-      let indx= indx - 1
-     endwhile
-     if indx < 0                        | let indx= 0                           | endif
 
-    else
-     " Explore -- initialize
-     " build list of files to Explore with Nexplore/Pexplore
-     NetrwKeepj keepalt call s:NetrwClearExplore()
-     let w:netrw_explore_indx= 0
-     if !exists("b:netrw_curdir")
-      let b:netrw_curdir= getcwd()
-     endif
+      exe "let dirfile= w:netrw_explore_list[".indx."]"
+      let newdir= substitute(dirfile,'/[^/]*$','','e')
 
-     " switch on starpat to build the w:netrw_explore_list of files
-     if starpat == 1
-      " starpat=1: Explore *//pattern  (current directory only search for files containing pattern)
-      try
-       exe "NetrwKeepj noautocmd vimgrep /".pattern."/gj ".fnameescape(b:netrw_curdir)."/*"
-      catch /^Vim\%((\a\+)\)\=:E480/
-       keepalt call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pattern.">",76)
-       return
-      endtry
-      let w:netrw_explore_list = s:NetrwExploreListUniq(map(getqflist(),'bufname(v:val.bufnr)'))
-      if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
+      call netrw#LocalBrowseCheck(newdir)
+      if !exists("w:netrw_liststyle")
+        let w:netrw_liststyle= g:netrw_liststyle
+      endif
+      if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:LONGLIST
+        keepalt NetrwKeepj call search('^'.substitute(dirfile,"^.*/","","").'\>',"W")
+      else
+        keepalt NetrwKeepj call search('\<'.substitute(dirfile,"^.*/","","").'\>',"w")
+      endif
+      let w:netrw_explore_mtchcnt = indx + 1
+      let w:netrw_explore_bufnr   = bufnr("%")
+      let w:netrw_explore_line    = line(".")
+      keepalt NetrwKeepj call s:SetupNetrwStatusLine('%f %h%m%r%=%9*%{NetrwStatusLine()}')
 
-     elseif starpat == 2
-      " starpat=2: Explore **//pattern (recursive descent search for files containing pattern)
-      try
-       exe "sil NetrwKeepj noautocmd keepalt vimgrep /".pattern."/gj "."**/*"
-      catch /^Vim\%((\a\+)\)\=:E480/
-       keepalt call netrw#ErrorMsg(s:WARNING,'no files matched pattern<'.pattern.'>',45)
-       if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
-       if !has('nvim') && has("clipboard") && g:netrw_clipboard
-        if @* != keepregstar | sil! let @* = keepregstar | endif
-        if @+ != keepregplus | sil! let @+ = keepregplus | endif
-       endif
-       sil! let @/ = keepregslash
-       return
-      endtry
-      let s:netrw_curdir       = b:netrw_curdir
-      let w:netrw_explore_list = getqflist()
-      let w:netrw_explore_list = s:NetrwExploreListUniq(map(w:netrw_explore_list,'s:netrw_curdir."/".bufname(v:val.bufnr)'))
-      if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
-
-     elseif starpat == 3
-      " starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
-      let filepat= substitute(dirname,'^\*/','','')
-      let filepat= substitute(filepat,'^[%#<]','\\&','')
-      let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".filepat),'\n'))
-      if &hls | let keepregslash= s:ExplorePatHls(filepat) | endif
-
-     elseif starpat == 4
-      " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
-      let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".dirname),'\n'))
-      if &hls | let keepregslash= s:ExplorePatHls(dirname) | endif
-     endif " switch on starpat to build w:netrw_explore_list
-
-     let w:netrw_explore_listlen = len(w:netrw_explore_list)
-
-     if w:netrw_explore_listlen == 0 || (w:netrw_explore_listlen == 1 && w:netrw_explore_list[0] =~ '\*\*\/')
-      keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no files matched",42)
+    else
+      if !exists("g:netrw_quiet")
+        keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44)
+      endif
       if !has('nvim') && has("clipboard") && g:netrw_clipboard
         if @* != keepregstar | sil! let @* = keepregstar | endif
         if @+ != keepregplus | sil! let @+ = keepregplus | endif
       endif
       sil! let @/ = keepregslash
       return
-     endif
-    endif  " if indx ... endif
-
-    " NetrwStatusLine support - for exploring support
-    let w:netrw_explore_indx= indx
-
-    " wrap the indx around, but issue a note
-    if indx >= w:netrw_explore_listlen || indx < 0
-     let indx                = (indx < 0)? ( w:netrw_explore_listlen - 1 ) : 0
-     let w:netrw_explore_indx= indx
-     keepalt NetrwKeepj call netrw#ErrorMsg(s:NOTE,"no more files match Explore pattern",43)
     endif
 
-    exe "let dirfile= w:netrw_explore_list[".indx."]"
-    let newdir= substitute(dirfile,'/[^/]*$','','e')
-
-    call netrw#LocalBrowseCheck(newdir)
-    if !exists("w:netrw_liststyle")
-     let w:netrw_liststyle= g:netrw_liststyle
+  else
+    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && dirname =~ '/'
+      sil! unlet w:netrw_treedict
+      sil! unlet w:netrw_treetop
     endif
-    if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:LONGLIST
-     keepalt NetrwKeepj call search('^'.substitute(dirfile,"^.*/","","").'\>',"W")
+    let newdir= dirname
+    if !exists("b:netrw_curdir")
+      NetrwKeepj call netrw#LocalBrowseCheck(getcwd())
     else
-     keepalt NetrwKeepj call search('\<'.substitute(dirfile,"^.*/","","").'\>',"w")
-    endif
-    let w:netrw_explore_mtchcnt = indx + 1
-    let w:netrw_explore_bufnr   = bufnr("%")
-    let w:netrw_explore_line    = line(".")
-    keepalt NetrwKeepj call s:SetupNetrwStatusLine('%f %h%m%r%=%9*%{NetrwStatusLine()}')
-
-   else
-    if !exists("g:netrw_quiet")
-     keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44)
-    endif
-    if !has('nvim') && has("clipboard") && g:netrw_clipboard
-      if @* != keepregstar | sil! let @* = keepregstar | endif
-      if @+ != keepregplus | sil! let @+ = keepregplus | endif
+      NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,newdir,0))
     endif
-    sil! let @/ = keepregslash
-    return
-   endif
-
-  else
-   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && dirname =~ '/'
-    sil! unlet w:netrw_treedict
-    sil! unlet w:netrw_treetop
-   endif
-   let newdir= dirname
-   if !exists("b:netrw_curdir")
-    NetrwKeepj call netrw#LocalBrowseCheck(getcwd())
-   else
-    NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,newdir,0))
-   endif
   endif
 
   " visual display of **/ **// */ Exploration files
   if exists("w:netrw_explore_indx") && exists("b:netrw_curdir")
-   if !exists("s:explore_prvdir") || s:explore_prvdir != b:netrw_curdir
-    " only update match list when current directory isn't the same as before
-    let s:explore_prvdir = b:netrw_curdir
-    let s:explore_match  = ""
-    let dirlen           = strlen(b:netrw_curdir)
-    if b:netrw_curdir !~ '/$'
-     let dirlen= dirlen + 1
-    endif
-    let prvfname= ""
-    for fname in w:netrw_explore_list
-     if fname =~ '^'.b:netrw_curdir
-      if s:explore_match == ""
-       let s:explore_match= '\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>'
-      else
-       let s:explore_match= s:explore_match.'\|\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>'
+    if !exists("s:explore_prvdir") || s:explore_prvdir != b:netrw_curdir
+      " only update match list when current directory isn't the same as before
+      let s:explore_prvdir = b:netrw_curdir
+      let s:explore_match  = ""
+      let dirlen           = strlen(b:netrw_curdir)
+      if b:netrw_curdir !~ '/$'
+        let dirlen= dirlen + 1
       endif
-     elseif fname !~ '^/' && fname != prvfname
-      if s:explore_match == ""
-       let s:explore_match= '\<'.escape(fname,g:netrw_markfileesc).'\>'
-      else
-       let s:explore_match= s:explore_match.'\|\<'.escape(fname,g:netrw_markfileesc).'\>'
+      let prvfname= ""
+      for fname in w:netrw_explore_list
+        if fname =~ '^'.b:netrw_curdir
+          if s:explore_match == ""
+            let s:explore_match= '\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>'
+          else
+            let s:explore_match= s:explore_match.'\|\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>'
+          endif
+        elseif fname !~ '^/' && fname != prvfname
+          if s:explore_match == ""
+            let s:explore_match= '\<'.escape(fname,g:netrw_markfileesc).'\>'
+          else
+            let s:explore_match= s:explore_match.'\|\<'.escape(fname,g:netrw_markfileesc).'\>'
+          endif
+        endif
+        let prvfname= fname
+      endfor
+      if has("syntax") && exists("g:syntax_on") && g:syntax_on
+        exe "2match netrwMarkFile /".s:explore_match."/"
       endif
-     endif
-     let prvfname= fname
-    endfor
-    if has("syntax") && exists("g:syntax_on") && g:syntax_on
-     exe "2match netrwMarkFile /".s:explore_match."/"
     endif
-   endif
-   echo "==Pexplore  ==Nexplore"
+    echo "==Pexplore  ==Nexplore"
   else
-   2match none
-   if exists("s:explore_match")  | unlet s:explore_match  | endif
-   if exists("s:explore_prvdir") | unlet s:explore_prvdir | endif
+    2match none
+    if exists("s:explore_match")  | unlet s:explore_match  | endif
+    if exists("s:explore_prvdir") | unlet s:explore_prvdir | endif
   endif
 
   " since Explore may be used to initialize netrw's browser,
@@ -1113,8 +1114,8 @@ fun! netrw#Explore(indx,dosplit,style,...)
   " Consequently, set s:netrw_events to 2.
   let s:netrw_events= 2
   if !has('nvim') && has("clipboard") && g:netrw_clipboard
-   if @* != keepregstar | sil! let @* = keepregstar | endif
-   if @+ != keepregplus | sil! let @+ = keepregplus | endif
+    if @* != keepregstar | sil! let @* = keepregstar | endif
+    if @+ != keepregplus | sil! let @+ = keepregplus | endif
   endif
   sil! let @/ = keepregslash
 endfun
@@ -1127,93 +1128,93 @@ endfun
 "         s:lexplore_win  : window number of Lexplore window (serves to indicate which window is a Lexplore window)
 "         w:lexplore_buf  : buffer number of Lexplore window (serves to indicate which window is a Lexplore window)
 fun! netrw#Lexplore(count,rightside,...)
-"  call Dfunc("netrw#Lexplore(count=".a:count." rightside=".a:rightside.",...) a:0=".a:0." ft=".&ft)
+  "  call Dfunc("netrw#Lexplore(count=".a:count." rightside=".a:rightside.",...) a:0=".a:0." ft=".&ft)
   let curwin= winnr()
 
   if a:0 > 0 && a:1 != ""
-   " if a netrw window is already on the left-side of the tab
-   " and a directory has been specified, explore with that
-   " directory.
-   let a1 = expand(a:1)
-   exe "1wincmd w"
-   if &ft == "netrw"
-    exe "Explore ".fnameescape(a1)
-    exe curwin."wincmd w"
-    let s:lexplore_win= curwin
-    let w:lexplore_buf= bufnr("%")
-    if exists("t:netrw_lexposn")
-     unlet t:netrw_lexposn
+    " if a netrw window is already on the left-side of the tab
+    " and a directory has been specified, explore with that
+    " directory.
+    let a1 = expand(a:1)
+    exe "1wincmd w"
+    if &ft == "netrw"
+      exe "Explore ".fnameescape(a1)
+      exe curwin."wincmd w"
+      let s:lexplore_win= curwin
+      let w:lexplore_buf= bufnr("%")
+      if exists("t:netrw_lexposn")
+        unlet t:netrw_lexposn
+      endif
+      return
     endif
-    return
-   endif
-   exe curwin."wincmd w"
+    exe curwin."wincmd w"
   else
-   let a1= ""
+    let a1= ""
   endif
 
   if exists("t:netrw_lexbufnr")
-   " check if t:netrw_lexbufnr refers to a netrw window
-   let lexwinnr = bufwinnr(t:netrw_lexbufnr)
+    " check if t:netrw_lexbufnr refers to a netrw window
+    let lexwinnr = bufwinnr(t:netrw_lexbufnr)
   else
-   let lexwinnr= 0
+    let lexwinnr= 0
   endif
 
   if lexwinnr > 0
-   " close down netrw explorer window
-   exe lexwinnr."wincmd w"
-   let g:netrw_winsize = -winwidth(0)
-   let t:netrw_lexposn = winsaveview()
-   close
-   if lexwinnr < curwin
-    let curwin= curwin - 1
-   endif
-   if lexwinnr != curwin
-    exe curwin."wincmd w"
-   endif
-   unlet t:netrw_lexbufnr
-
-  else
-   " open netrw explorer window
-   exe "1wincmd w"
-   let keep_altv    = g:netrw_altv
-   let g:netrw_altv = 0
-   if a:count != 0
-    let netrw_winsize   = g:netrw_winsize
-    let g:netrw_winsize = a:count
-   endif
-   let curfile= expand("%")
-   exe (a:rightside? "botright" : "topleft")." vertical ".((g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize) . " new"
-   if a:0 > 0 && a1 != ""
-    call netrw#Explore(0,0,0,a1)
-    exe "Explore ".fnameescape(a1)
-   elseif curfile =~ '^\a\{3,}://'
-    call netrw#Explore(0,0,0,substitute(curfile,'[^/\\]*$','',''))
-   else
-    call netrw#Explore(0,0,0,".")
-   endif
-   if a:count != 0
-    let g:netrw_winsize = netrw_winsize
-   endif
-   setlocal winfixwidth
-   let g:netrw_altv     = keep_altv
-   let t:netrw_lexbufnr = bufnr("%")
-   " done to prevent build-up of hidden buffers due to quitting and re-invocation of :Lexplore.
-   " Since the intended use of :Lexplore is to have an always-present explorer window, the extra
-   " effort to prevent mis-use of :Lex is warranted.
-   set bh=wipe
-   if exists("t:netrw_lexposn")
-    call winrestview(t:netrw_lexposn)
-    unlet t:netrw_lexposn
-   endif
+    " close down netrw explorer window
+    exe lexwinnr."wincmd w"
+    let g:netrw_winsize = -winwidth(0)
+    let t:netrw_lexposn = winsaveview()
+    close
+    if lexwinnr < curwin
+      let curwin= curwin - 1
+    endif
+    if lexwinnr != curwin
+      exe curwin."wincmd w"
+    endif
+    unlet t:netrw_lexbufnr
+
+  else
+    " open netrw explorer window
+    exe "1wincmd w"
+    let keep_altv    = g:netrw_altv
+    let g:netrw_altv = 0
+    if a:count != 0
+      let netrw_winsize   = g:netrw_winsize
+      let g:netrw_winsize = a:count
+    endif
+    let curfile= expand("%")
+    exe (a:rightside? "botright" : "topleft")." vertical ".((g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize) . " new"
+    if a:0 > 0 && a1 != ""
+      call netrw#Explore(0,0,0,a1)
+      exe "Explore ".fnameescape(a1)
+    elseif curfile =~ '^\a\{3,}://'
+      call netrw#Explore(0,0,0,substitute(curfile,'[^/\\]*$','',''))
+    else
+      call netrw#Explore(0,0,0,".")
+    endif
+    if a:count != 0
+      let g:netrw_winsize = netrw_winsize
+    endif
+    setlocal winfixwidth
+    let g:netrw_altv     = keep_altv
+    let t:netrw_lexbufnr = bufnr("%")
+    " done to prevent build-up of hidden buffers due to quitting and re-invocation of :Lexplore.
+    " Since the intended use of :Lexplore is to have an always-present explorer window, the extra
+    " effort to prevent mis-use of :Lex is warranted.
+    set bh=wipe
+    if exists("t:netrw_lexposn")
+      call winrestview(t:netrw_lexposn)
+      unlet t:netrw_lexposn
+    endif
   endif
 
   " set up default window for editing via 
   if exists("g:netrw_chgwin") && g:netrw_chgwin == -1
-   if a:rightside
-    let g:netrw_chgwin= 1
-   else
-    let g:netrw_chgwin= 2
-   endif
+    if a:rightside
+      let g:netrw_chgwin= 1
+    else
+      let g:netrw_chgwin= 2
+    endif
   endif
 
 endfun
@@ -1223,73 +1224,73 @@ endfun
 " supports :NetrwClean  -- remove netrw from first directory on runtimepath
 "          :NetrwClean! -- remove netrw from all directories on runtimepath
 fun! netrw#Clean(sys)
-"  call Dfunc("netrw#Clean(sys=".a:sys.")")
+  "  call Dfunc("netrw#Clean(sys=".a:sys.")")
 
   if a:sys
-   let choice= confirm("Remove personal and system copies of netrw?","&Yes\n&No")
+    let choice= confirm("Remove personal and system copies of netrw?","&Yes\n&No")
   else
-   let choice= confirm("Remove personal copy of netrw?","&Yes\n&No")
+    let choice= confirm("Remove personal copy of netrw?","&Yes\n&No")
   endif
-"  call Decho("choice=".choice,'~'.expand(""))
+  "  call Decho("choice=".choice,'~'.expand(""))
   let diddel= 0
   let diddir= ""
 
   if choice == 1
-   for dir in split(&rtp,',')
-    if filereadable(dir."/plugin/netrwPlugin.vim")
-"     call Decho("removing netrw-related files from ".dir,'~'.expand(""))
-     if s:NetrwDelete(dir."/plugin/netrwPlugin.vim")        |call netrw#ErrorMsg(1,"unable to remove ".dir."/plugin/netrwPlugin.vim",55)        |endif
-     if s:NetrwDelete(dir."/autoload/netrwFileHandlers.vim")|call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwFileHandlers.vim",55)|endif
-     if s:NetrwDelete(dir."/autoload/netrwSettings.vim")    |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwSettings.vim",55)    |endif
-     if s:NetrwDelete(dir."/autoload/netrw.vim")            |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrw.vim",55)            |endif
-     if s:NetrwDelete(dir."/syntax/netrw.vim")              |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrw.vim",55)              |endif
-     if s:NetrwDelete(dir."/syntax/netrwlist.vim")          |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrwlist.vim",55)          |endif
-     let diddir= dir
-     let diddel= diddel + 1
-     if !a:sys|break|endif
-    endif
-   endfor
-  endif
-
-   echohl WarningMsg
+    for dir in split(&rtp,',')
+      if filereadable(dir."/plugin/netrwPlugin.vim")
+        "     call Decho("removing netrw-related files from ".dir,'~'.expand(""))
+        if s:NetrwDelete(dir."/plugin/netrwPlugin.vim")        |call netrw#ErrorMsg(1,"unable to remove ".dir."/plugin/netrwPlugin.vim",55)        |endif
+        if s:NetrwDelete(dir."/autoload/netrwFileHandlers.vim")|call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwFileHandlers.vim",55)|endif
+        if s:NetrwDelete(dir."/autoload/netrwSettings.vim")    |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwSettings.vim",55)    |endif
+        if s:NetrwDelete(dir."/autoload/netrw.vim")            |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrw.vim",55)            |endif
+        if s:NetrwDelete(dir."/syntax/netrw.vim")              |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrw.vim",55)              |endif
+        if s:NetrwDelete(dir."/syntax/netrwlist.vim")          |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrwlist.vim",55)          |endif
+        let diddir= dir
+        let diddel= diddel + 1
+        if !a:sys|break|endif
+      endif
+    endfor
+  endif
+
+  echohl WarningMsg
   if diddel == 0
-   echomsg "netrw is either not installed or not removable"
+    echomsg "netrw is either not installed or not removable"
   elseif diddel == 1
-   echomsg "removed one copy of netrw from <".diddir.">"
+    echomsg "removed one copy of netrw from <".diddir.">"
   else
-   echomsg "removed ".diddel." copies of netrw"
+    echomsg "removed ".diddel." copies of netrw"
   endif
-   echohl None
+  echohl None
 
-"  call Dret("netrw#Clean")
+  "  call Dret("netrw#Clean")
 endfun
 
 " ---------------------------------------------------------------------
 " netrw#MakeTgt: make a target out of the directory name provided {{{2
 fun! netrw#MakeTgt(dname)
-"  call Dfunc("netrw#MakeTgt(dname<".a:dname.">)")
-   " simplify the target (eg. /abc/def/../ghi -> /abc/ghi)
+  "  call Dfunc("netrw#MakeTgt(dname<".a:dname.">)")
+  " simplify the target (eg. /abc/def/../ghi -> /abc/ghi)
   let svpos               = winsaveview()
-"  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
+  "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
   let s:netrwmftgt_islocal= (a:dname !~ '^\a\{3,}://')
-"  call Decho("s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand(""))
+  "  call Decho("s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand(""))
   if s:netrwmftgt_islocal
-   let netrwmftgt= simplify(a:dname)
+    let netrwmftgt= simplify(a:dname)
   else
-   let netrwmftgt= a:dname
+    let netrwmftgt= a:dname
   endif
   if exists("s:netrwmftgt") && netrwmftgt == s:netrwmftgt
-   " re-selected target, so just clear it
-   unlet s:netrwmftgt s:netrwmftgt_islocal
+    " re-selected target, so just clear it
+    unlet s:netrwmftgt s:netrwmftgt_islocal
   else
-   let s:netrwmftgt= netrwmftgt
+    let s:netrwmftgt= netrwmftgt
   endif
   if g:netrw_fastbrowse <= 1
-   call s:NetrwRefresh((b:netrw_curdir !~ '\a\{3,}://'),b:netrw_curdir)
+    call s:NetrwRefresh((b:netrw_curdir !~ '\a\{3,}://'),b:netrw_curdir)
   endif
-"  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))"
+  "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))"
   call winrestview(svpos)
-"  call Dret("netrw#MakeTgt")
+  "  call Dret("netrw#MakeTgt")
 endfun
 
 " ---------------------------------------------------------------------
@@ -1300,250 +1301,250 @@ endfun
 "     fname  :   a filename or a list of filenames
 "     tgtdir :   optional place where files are to go  (not present, uses getcwd())
 fun! netrw#Obtain(islocal,fname,...)
-"  call Dfunc("netrw#Obtain(islocal=".a:islocal." fname<".((type(a:fname) == 1)? a:fname : string(a:fname)).">) a:0=".a:0)
+  "  call Dfunc("netrw#Obtain(islocal=".a:islocal." fname<".((type(a:fname) == 1)? a:fname : string(a:fname)).">) a:0=".a:0)
   " NetrwStatusLine support - for obtaining support
 
   if type(a:fname) == 1
-   let fnamelist= [ a:fname ]
+    let fnamelist= [ a:fname ]
   elseif type(a:fname) == 3
-   let fnamelist= a:fname
+    let fnamelist= a:fname
   else
-   call netrw#ErrorMsg(s:ERROR,"attempting to use NetrwObtain on something not a filename or a list",62)
-"   call Dret("netrw#Obtain")
-   return
+    call netrw#ErrorMsg(s:ERROR,"attempting to use NetrwObtain on something not a filename or a list",62)
+    "   call Dret("netrw#Obtain")
+    return
   endif
-"  call Decho("fnamelist<".string(fnamelist).">",'~'.expand(""))
+  "  call Decho("fnamelist<".string(fnamelist).">",'~'.expand(""))
   if a:0 > 0
-   let tgtdir= a:1
+    let tgtdir= a:1
   else
-   let tgtdir= getcwd()
+    let tgtdir= getcwd()
   endif
-"  call Decho("tgtdir<".tgtdir.">",'~'.expand(""))
+  "  call Decho("tgtdir<".tgtdir.">",'~'.expand(""))
 
   if exists("b:netrw_islocal") && b:netrw_islocal
-   " obtain a file from local b:netrw_curdir to (local) tgtdir
-"   call Decho("obtain a file from local ".b:netrw_curdir." to ".tgtdir,'~'.expand(""))
-   if exists("b:netrw_curdir") && getcwd() != b:netrw_curdir
-    let topath= s:ComposePath(tgtdir,"")
-    if has("win32")
-     " transfer files one at time
-"     call Decho("transfer files one at a time",'~'.expand(""))
-     for fname in fnamelist
-"      call Decho("system(".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath).")",'~'.expand(""))
-      call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".s:ShellEscape(fname)." ".s:ShellEscape(topath))
-      if v:shell_error != 0
-       call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80)
-"       call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath))
-       return
+    " obtain a file from local b:netrw_curdir to (local) tgtdir
+    "   call Decho("obtain a file from local ".b:netrw_curdir." to ".tgtdir,'~'.expand(""))
+    if exists("b:netrw_curdir") && getcwd() != b:netrw_curdir
+      let topath= s:ComposePath(tgtdir,"")
+      if has("win32")
+        " transfer files one at time
+        "     call Decho("transfer files one at a time",'~'.expand(""))
+        for fname in fnamelist
+          "      call Decho("system(".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath).")",'~'.expand(""))
+          call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".s:ShellEscape(fname)." ".s:ShellEscape(topath))
+          if v:shell_error != 0
+            call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80)
+            "       call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath))
+            return
+          endif
+        endfor
+      else
+        " transfer files with one command
+        "     call Decho("transfer files with one command",'~'.expand(""))
+        let filelist= join(map(deepcopy(fnamelist),"s:ShellEscape(v:val)"))
+        "     call Decho("system(".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath).")",'~'.expand(""))
+        call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".filelist." ".s:ShellEscape(topath))
+        if v:shell_error != 0
+          call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80)
+          "      call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath))
+          return
+        endif
       endif
-     endfor
+    elseif !exists("b:netrw_curdir")
+      call netrw#ErrorMsg(s:ERROR,"local browsing directory doesn't exist!",36)
     else
-     " transfer files with one command
-"     call Decho("transfer files with one command",'~'.expand(""))
-     let filelist= join(map(deepcopy(fnamelist),"s:ShellEscape(v:val)"))
-"     call Decho("system(".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath).")",'~'.expand(""))
-     call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".filelist." ".s:ShellEscape(topath))
-     if v:shell_error != 0
-      call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80)
-"      call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath))
-      return
-     endif
+      call netrw#ErrorMsg(s:WARNING,"local browsing directory and current directory are identical",37)
     endif
-   elseif !exists("b:netrw_curdir")
-    call netrw#ErrorMsg(s:ERROR,"local browsing directory doesn't exist!",36)
-   else
-    call netrw#ErrorMsg(s:WARNING,"local browsing directory and current directory are identical",37)
-   endif
 
   else
-   " obtain files from remote b:netrw_curdir to local tgtdir
-"   call Decho("obtain a file from remote ".b:netrw_curdir." to ".tgtdir,'~'.expand(""))
-   if type(a:fname) == 1
-    call s:SetupNetrwStatusLine('%f %h%m%r%=%9*Obtaining '.a:fname)
-   endif
-   call s:NetrwMethod(b:netrw_curdir)
-
-   if b:netrw_method == 4
-    " obtain file using scp
-"    call Decho("obtain via scp (method#4)",'~'.expand(""))
-    if exists("g:netrw_port") && g:netrw_port != ""
-     let useport= " ".g:netrw_scpport." ".g:netrw_port
-    else
-     let useport= ""
+    " obtain files from remote b:netrw_curdir to local tgtdir
+    "   call Decho("obtain a file from remote ".b:netrw_curdir." to ".tgtdir,'~'.expand(""))
+    if type(a:fname) == 1
+      call s:SetupNetrwStatusLine('%f %h%m%r%=%9*Obtaining '.a:fname)
     endif
-    if b:netrw_fname =~ '/'
-     let path= substitute(b:netrw_fname,'^\(.*/\).\{-}$','\1','')
-    else
-     let path= ""
-    endif
-    let filelist= join(map(deepcopy(fnamelist),'escape(s:ShellEscape(g:netrw_machine.":".path.v:val,1)," ")'))
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".filelist." ".s:ShellEscape(tgtdir,1))
-
-   elseif b:netrw_method == 2
-    " obtain file using ftp + .netrc
-"     call Decho("obtain via ftp+.netrc (method #2)",'~'.expand(""))
-     call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars()
-     let tmpbufnr= bufnr("%")
-     setl ff=unix
-     if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
-      NetrwKeepj put =g:netrw_ftpmode
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
+    call s:NetrwMethod(b:netrw_curdir)
 
-     if exists("b:netrw_fname") && b:netrw_fname != ""
-      call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
+    if b:netrw_method == 4
+      " obtain file using scp
+      "    call Decho("obtain via scp (method#4)",'~'.expand(""))
+      if exists("g:netrw_port") && g:netrw_port != ""
+        let useport= " ".g:netrw_scpport." ".g:netrw_port
+      else
+        let useport= ""
+      endif
+      if b:netrw_fname =~ '/'
+        let path= substitute(b:netrw_fname,'^\(.*/\).\{-}$','\1','')
+      else
+        let path= ""
+      endif
+      let filelist= join(map(deepcopy(fnamelist),'escape(s:ShellEscape(g:netrw_machine.":".path.v:val,1)," ")'))
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".filelist." ".s:ShellEscape(tgtdir,1))
+
+    elseif b:netrw_method == 2
+      " obtain file using ftp + .netrc
+      "     call Decho("obtain via ftp+.netrc (method #2)",'~'.expand(""))
+      call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars()
+      let tmpbufnr= bufnr("%")
+      setl ff=unix
+      if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
+        NetrwKeepj put =g:netrw_ftpmode
+        "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
 
-     if exists("g:netrw_ftpextracmd")
-      NetrwKeepj put =g:netrw_ftpextracmd
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
-     for fname in fnamelist
-      call setline(line("$")+1,'get "'.fname.'"')
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endfor
-     if exists("g:netrw_port") && g:netrw_port != ""
-      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
-     else
-      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
-     endif
-     " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-     if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
-      let debugkeep= &debug
-      setl debug=msg
-      call netrw#ErrorMsg(s:ERROR,getline(1),4)
-      let &debug= debugkeep
-     endif
-
-   elseif b:netrw_method == 3
-    " obtain with ftp + machine, id, passwd, and fname (ie. no .netrc)
-"    call Decho("obtain via ftp+mipf (method #3)",'~'.expand(""))
-    call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars()
-    let tmpbufnr= bufnr("%")
-    setl ff=unix
+      if exists("b:netrw_fname") && b:netrw_fname != ""
+        call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
+        "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
 
-    if exists("g:netrw_port") && g:netrw_port != ""
-     NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-    else
-     NetrwKeepj put ='open '.g:netrw_machine
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-    endif
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
+      for fname in fnamelist
+        call setline(line("$")+1,'get "'.fname.'"')
+        "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endfor
+      if exists("g:netrw_port") && g:netrw_port != ""
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
+      else
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
+      endif
+      " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+      if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
+        let debugkeep= &debug
+        setl debug=msg
+        call netrw#ErrorMsg(s:ERROR,getline(1),4)
+        let &debug= debugkeep
+      endif
 
-    if exists("g:netrw_uid") && g:netrw_uid != ""
-     if exists("g:netrw_ftp") && g:netrw_ftp == 1
-      NetrwKeepj put =g:netrw_uid
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-      if exists("s:netrw_passwd") && s:netrw_passwd != ""
-       NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
+    elseif b:netrw_method == 3
+      " obtain with ftp + machine, id, passwd, and fname (ie. no .netrc)
+      "    call Decho("obtain via ftp+mipf (method #3)",'~'.expand(""))
+      call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars()
+      let tmpbufnr= bufnr("%")
+      setl ff=unix
+
+      if exists("g:netrw_port") && g:netrw_port != ""
+        NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      else
+        NetrwKeepj put ='open '.g:netrw_machine
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
       endif
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     elseif exists("s:netrw_passwd")
-      NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
-    endif
 
-    if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
-     NetrwKeepj put =g:netrw_ftpmode
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-    endif
+      if exists("g:netrw_uid") && g:netrw_uid != ""
+        if exists("g:netrw_ftp") && g:netrw_ftp == 1
+          NetrwKeepj put =g:netrw_uid
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+          if exists("s:netrw_passwd") && s:netrw_passwd != ""
+            NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
+          endif
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        elseif exists("s:netrw_passwd")
+          NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endif
+      endif
 
-    if exists("b:netrw_fname") && b:netrw_fname != ""
-     NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-    endif
+      if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
+        NetrwKeepj put =g:netrw_ftpmode
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
 
-    if exists("g:netrw_ftpextracmd")
-     NetrwKeepj put =g:netrw_ftpextracmd
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-    endif
+      if exists("b:netrw_fname") && b:netrw_fname != ""
+        NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
 
-    if exists("g:netrw_ftpextracmd")
-     NetrwKeepj put =g:netrw_ftpextracmd
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-    endif
-    for fname in fnamelist
-     NetrwKeepj call setline(line("$")+1,'get "'.fname.'"')
-    endfor
-"    call Decho("filter input: ".getline('$'),'~'.expand(""))
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
 
-    " perform ftp:
-    " -i       : turns off interactive prompting from ftp
-    " -n  unix : DON'T use <.netrc>, even though it exists
-    " -n  win32: quit being obnoxious about password
-    "  Note: using "_dd to delete to the black hole register; avoids messing up @@
-    NetrwKeepj norm! 1G"_dd
-    call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
-    " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-    if getline(1) !~ "^$"
-"     call Decho("error<".getline(1).">",'~'.expand(""))
-     if !exists("g:netrw_quiet")
-      NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),5)
-     endif
-    endif
-
-   elseif b:netrw_method == 9
-    " obtain file using sftp
-"    call Decho("obtain via sftp (method #9)",'~'.expand(""))
-    if a:fname =~ '/'
-     let localfile= substitute(a:fname,'^.*/','','')
-    else
-     let localfile= a:fname
-    endif
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1).s:ShellEscape(localfile)." ".s:ShellEscape(tgtdir))
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      endif
+      for fname in fnamelist
+        NetrwKeepj call setline(line("$")+1,'get "'.fname.'"')
+      endfor
+      "    call Decho("filter input: ".getline('$'),'~'.expand(""))
+
+      " perform ftp:
+      " -i       : turns off interactive prompting from ftp
+      " -n  unix : DON'T use <.netrc>, even though it exists
+      " -n  win32: quit being obnoxious about password
+      "  Note: using "_dd to delete to the black hole register; avoids messing up @@
+      NetrwKeepj norm! 1G"_dd
+      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
+      " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+      if getline(1) !~ "^$"
+        "     call Decho("error<".getline(1).">",'~'.expand(""))
+        if !exists("g:netrw_quiet")
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),5)
+        endif
+      endif
 
-   elseif !exists("b:netrw_method") || b:netrw_method < 0
-    " probably a badly formed url; protocol not recognized
-"    call Dret("netrw#Obtain : unsupported method")
-    return
+    elseif b:netrw_method == 9
+      " obtain file using sftp
+      "    call Decho("obtain via sftp (method #9)",'~'.expand(""))
+      if a:fname =~ '/'
+        let localfile= substitute(a:fname,'^.*/','','')
+      else
+        let localfile= a:fname
+      endif
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1).s:ShellEscape(localfile)." ".s:ShellEscape(tgtdir))
 
-   else
-    " protocol recognized but not supported for Obtain (yet?)
-    if !exists("g:netrw_quiet")
-     NetrwKeepj call netrw#ErrorMsg(s:ERROR,"current protocol not supported for obtaining file",97)
+    elseif !exists("b:netrw_method") || b:netrw_method < 0
+      " probably a badly formed url; protocol not recognized
+      "    call Dret("netrw#Obtain : unsupported method")
+      return
+
+    else
+      " protocol recognized but not supported for Obtain (yet?)
+      if !exists("g:netrw_quiet")
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"current protocol not supported for obtaining file",97)
+      endif
+      "    call Dret("netrw#Obtain : current protocol not supported for obtaining file")
+      return
     endif
-"    call Dret("netrw#Obtain : current protocol not supported for obtaining file")
-    return
-   endif
 
-   " restore status line
-   if type(a:fname) == 1 && exists("s:netrw_users_stl")
-    NetrwKeepj call s:SetupNetrwStatusLine(s:netrw_users_stl)
-   endif
+    " restore status line
+    if type(a:fname) == 1 && exists("s:netrw_users_stl")
+      NetrwKeepj call s:SetupNetrwStatusLine(s:netrw_users_stl)
+    endif
 
   endif
 
   " cleanup
   if exists("tmpbufnr")
-   if bufnr("%") != tmpbufnr
-    exe tmpbufnr."bw!"
-   else
-    q!
-   endif
+    if bufnr("%") != tmpbufnr
+      exe tmpbufnr."bw!"
+    else
+      q!
+    endif
   endif
 
-"  call Dret("netrw#Obtain")
+  "  call Dret("netrw#Obtain")
 endfun
 
 " ---------------------------------------------------------------------
 " netrw#Nread: save position, call netrw#NetRead(), and restore position {{{2
 fun! netrw#Nread(mode,fname)
-"  call Dfunc("netrw#Nread(mode=".a:mode." fname<".a:fname.">)")
+  "  call Dfunc("netrw#Nread(mode=".a:mode." fname<".a:fname.">)")
   let svpos= winsaveview()
-"  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
+  "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
   call netrw#NetRead(a:mode,a:fname)
-"  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
+  "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
   call winrestview(svpos)
 
   if exists("w:netrw_liststyle") && w:netrw_liststyle != s:TREELIST
-   if exists("w:netrw_bannercnt")
-    " start with cursor just after the banner
-    exe w:netrw_bannercnt
-   endif
+    if exists("w:netrw_bannercnt")
+      " start with cursor just after the banner
+      exe w:netrw_bannercnt
+    endif
   endif
-"  call Dret("netrw#Nread")
+  "  call Dret("netrw#Nread")
 endfun
 
 " ------------------------------------------------------------------------
@@ -1558,21 +1559,21 @@ endfun
 "                - restore a user option when != safe option             (s:NetrwRestoreSetting)
 "             vt: (variable type) normally its either "w:" or "s:"
 fun! s:NetrwOptionsSave(vt)
-"  call Dfunc("s:NetrwOptionsSave(vt<".a:vt.">) win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%")).">"." winnr($)=".winnr("$")." mod=".&mod." ma=".&ma)
-"  call Decho(a:vt."netrw_optionsave".(exists("{a:vt}netrw_optionsave")? ("=".{a:vt}netrw_optionsave) : " doesn't exist"),'~'.expand(""))
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt." hid=".&hid,'~'.expand(""))
-"  call Decho("(s:NetrwOptionsSave) lines=".&lines)
+  "  call Dfunc("s:NetrwOptionsSave(vt<".a:vt.">) win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%")).">"." winnr($)=".winnr("$")." mod=".&mod." ma=".&ma)
+  "  call Decho(a:vt."netrw_optionsave".(exists("{a:vt}netrw_optionsave")? ("=".{a:vt}netrw_optionsave) : " doesn't exist"),'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt." hid=".&hid,'~'.expand(""))
+  "  call Decho("(s:NetrwOptionsSave) lines=".&lines)
 
   if !exists("{a:vt}netrw_optionsave")
-   let {a:vt}netrw_optionsave= 1
+    let {a:vt}netrw_optionsave= 1
   else
-"   call Dret("s:NetrwOptionsSave : options already saved")
-   return
+    "   call Dret("s:NetrwOptionsSave : options already saved")
+    return
   endif
-"  call Decho("prior to save: fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." diff=".&l:diff,'~'.expand(""))
+  "  call Decho("prior to save: fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." diff=".&l:diff,'~'.expand(""))
 
   " Save current settings and current directory
-"  call Decho("saving current settings and current directory",'~'.expand(""))
+  "  call Decho("saving current settings and current directory",'~'.expand(""))
   let s:yykeep          = @@
   if exists("&l:acd")|let {a:vt}netrw_acdkeep  = &l:acd|endif
   let {a:vt}netrw_aikeep    = &l:ai
@@ -1589,11 +1590,11 @@ fun! s:NetrwOptionsSave(vt)
   let {a:vt}netrw_cpokeep   = &l:cpo
   let {a:vt}netrw_cuckeep   = &l:cuc
   let {a:vt}netrw_culkeep   = &l:cul
-"  call Decho("(s:NetrwOptionsSave) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
+  "  call Decho("(s:NetrwOptionsSave) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
   let {a:vt}netrw_diffkeep  = &l:diff
   let {a:vt}netrw_fenkeep   = &l:fen
   if !exists("g:netrw_ffkeep") || g:netrw_ffkeep
-   let {a:vt}netrw_ffkeep    = &l:ff
+    let {a:vt}netrw_ffkeep    = &l:ff
   endif
   let {a:vt}netrw_fokeep    = &l:fo           " formatoptions
   let {a:vt}netrw_gdkeep    = &l:gd           " gdefault
@@ -1613,7 +1614,7 @@ fun! s:NetrwOptionsSave(vt)
   let {a:vt}netrw_selkeep   = &l:sel
   let {a:vt}netrw_spellkeep = &l:spell
   if !g:netrw_use_noswf
-   let {a:vt}netrw_swfkeep  = &l:swf
+    let {a:vt}netrw_swfkeep  = &l:swf
   endif
   let {a:vt}netrw_tskeep    = &l:ts
   let {a:vt}netrw_twkeep    = &l:tw           " textwidth
@@ -1622,20 +1623,20 @@ fun! s:NetrwOptionsSave(vt)
   let {a:vt}netrw_writekeep = &l:write
 
   " save a few selected netrw-related variables
-"  call Decho("saving a few selected netrw-related variables",'~'.expand(""))
+  "  call Decho("saving a few selected netrw-related variables",'~'.expand(""))
   if g:netrw_keepdir
-   let {a:vt}netrw_dirkeep  = getcwd()
-"   call Decho("saving to ".a:vt."netrw_dirkeep<".{a:vt}netrw_dirkeep.">",'~'.expand(""))
+    let {a:vt}netrw_dirkeep  = getcwd()
+    "   call Decho("saving to ".a:vt."netrw_dirkeep<".{a:vt}netrw_dirkeep.">",'~'.expand(""))
   endif
   if !has('nvim') && has("clipboard") && g:netrw_clipboard
-   sil! let {a:vt}netrw_starkeep = @*
-   sil! let {a:vt}netrw_pluskeep = @+
+    sil! let {a:vt}netrw_starkeep = @*
+    sil! let {a:vt}netrw_pluskeep = @+
   endif
   sil! let {a:vt}netrw_slashkeep= @/
 
-"  call Decho("(s:NetrwOptionsSave) lines=".&lines)
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand(""))
-"  call Dret("s:NetrwOptionsSave : tab#".tabpagenr()." win#".winnr())
+  "  call Decho("(s:NetrwOptionsSave) lines=".&lines)
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand(""))
+  "  call Dret("s:NetrwOptionsSave : tab#".tabpagenr()." win#".winnr())
 endfun
 
 " ---------------------------------------------------------------------
@@ -1643,25 +1644,25 @@ endfun
 "                     Use  s:NetrwSaveOptions() to save user settings
 "                     Use  s:NetrwOptionsRestore() to restore user settings
 fun! s:NetrwOptionsSafe(islocal)
-"  call Dfunc("s:NetrwOptionsSafe(islocal=".a:islocal.") win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%"))."> winnr($)=".winnr("$"))
-"  call Decho("win#".winnr()."'s ft=".&ft,'~'.expand(""))
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Dfunc("s:NetrwOptionsSafe(islocal=".a:islocal.") win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%"))."> winnr($)=".winnr("$"))
+  "  call Decho("win#".winnr()."'s ft=".&ft,'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
   if exists("+acd") | call s:NetrwSetSafeSetting("&l:acd",0)|endif
   call s:NetrwSetSafeSetting("&l:ai",0)
   call s:NetrwSetSafeSetting("&l:aw",0)
   call s:NetrwSetSafeSetting("&l:bl",0)
   call s:NetrwSetSafeSetting("&l:bomb",0)
   if a:islocal
-   call s:NetrwSetSafeSetting("&l:bt","nofile")
+    call s:NetrwSetSafeSetting("&l:bt","nofile")
   else
-   call s:NetrwSetSafeSetting("&l:bt","acwrite")
+    call s:NetrwSetSafeSetting("&l:bt","acwrite")
   endif
   call s:NetrwSetSafeSetting("&l:ci",0)
   call s:NetrwSetSafeSetting("&l:cin",0)
   if g:netrw_fastbrowse > a:islocal
-   call s:NetrwSetSafeSetting("&l:bh","hide")
+    call s:NetrwSetSafeSetting("&l:bh","hide")
   else
-   call s:NetrwSetSafeSetting("&l:bh","delete")
+    call s:NetrwSetSafeSetting("&l:bh","delete")
   endif
   call s:NetrwSetSafeSetting("&l:cino","")
   call s:NetrwSetSafeSetting("&l:com","")
@@ -1676,7 +1677,7 @@ fun! s:NetrwOptionsSafe(islocal)
   setl isk+=@ isk+=* isk+=/
   call s:NetrwSetSafeSetting("&l:magic",1)
   if g:netrw_use_noswf
-   call s:NetrwSetSafeSetting("swf",0)
+    call s:NetrwSetSafeSetting("swf",0)
   endif
   call s:NetrwSetSafeSetting("&l:report",10000)
   call s:NetrwSetSafeSetting("&l:sel","inclusive")
@@ -1690,41 +1691,41 @@ fun! s:NetrwOptionsSafe(islocal)
   call s:NetrwCursor(0)
 
   " allow the user to override safe options
-"  call Decho("ft<".&ft."> ei=".&ei,'~'.expand(""))
+  "  call Decho("ft<".&ft."> ei=".&ei,'~'.expand(""))
   if &ft == "netrw"
-"   call Decho("do any netrw FileType autocmds (doau FileType netrw)",'~'.expand(""))
-   keepalt NetrwKeepj doau FileType netrw
+    "   call Decho("do any netrw FileType autocmds (doau FileType netrw)",'~'.expand(""))
+    keepalt NetrwKeepj doau FileType netrw
   endif
 
-"  call Decho("fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." bh=".&l:bh." bt<".&bt.">",'~'.expand(""))
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"  call Dret("s:NetrwOptionsSafe")
+  "  call Decho("fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." bh=".&l:bh." bt<".&bt.">",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Dret("s:NetrwOptionsSafe")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwOptionsRestore: restore options (based on prior s:NetrwOptionsSave) {{{2
 fun! s:NetrwOptionsRestore(vt)
   if !exists("{a:vt}netrw_optionsave")
-   " filereadable() returns zero for remote files (e.g. scp://user@localhost//etc/fstab)
-   " Note: @ may not be in 'isfname', so '^\w\+://\f\+/' may not match
-   if filereadable(expand("%")) || expand("%") =~# '^\w\+://\f\+'
-    filetype detect
-   else
-    setl ft=netrw
-   endif
-   return
+    " filereadable() returns zero for remote files (e.g. scp://user@localhost//etc/fstab)
+    " Note: @ may not be in 'isfname', so '^\w\+://\f\+/' may not match
+    if filereadable(expand("%")) || expand("%") =~# '^\w\+://\f\+'
+      filetype detect
+    else
+      setl ft=netrw
+    endif
+    return
   endif
   unlet {a:vt}netrw_optionsave
 
   if exists("+acd")
-   if exists("{a:vt}netrw_acdkeep")
-    let curdir = getcwd()
-    let &l:acd = {a:vt}netrw_acdkeep
-    unlet {a:vt}netrw_acdkeep
-    if &l:acd
-     call s:NetrwLcd(curdir)
+    if exists("{a:vt}netrw_acdkeep")
+      let curdir = getcwd()
+      let &l:acd = {a:vt}netrw_acdkeep
+      unlet {a:vt}netrw_acdkeep
+      if &l:acd
+        call s:NetrwLcd(curdir)
+      endif
     endif
-   endif
   endif
   call s:NetrwRestoreSetting(a:vt."netrw_aikeep","&l:ai")
   call s:NetrwRestoreSetting(a:vt."netrw_awkeep","&l:aw")
@@ -1740,7 +1741,7 @@ fun! s:NetrwOptionsRestore(vt)
   call s:NetrwRestoreSetting(a:vt."netrw_diffkeep","&l:diff")
   call s:NetrwRestoreSetting(a:vt."netrw_fenkeep","&l:fen")
   if exists("g:netrw_ffkeep") && g:netrw_ffkeep
-   call s:NetrwRestoreSetting(a:vt."netrw_ffkeep")","&l:ff")
+    call s:NetrwRestoreSetting(a:vt."netrw_ffkeep")","&l:ff")
   endif
   call s:NetrwRestoreSetting(a:vt."netrw_fokeep"   ,"&l:fo")
   call s:NetrwRestoreSetting(a:vt."netrw_gdkeep"   ,"&l:gd")
@@ -1770,32 +1771,32 @@ fun! s:NetrwOptionsRestore(vt)
   call s:NetrwRestoreSetting(a:vt."netrw_tskeep","&l:ts")
 
   if exists("{a:vt}netrw_swfkeep")
-   if &directory == ""
-    " user hasn't specified a swapfile directory;
-    " netrw will temporarily set the swapfile directory
-    " to the current directory as returned by getcwd().
-    let &l:directory= getcwd()
-    sil! let &l:swf = {a:vt}netrw_swfkeep
-    setl directory=
-    unlet {a:vt}netrw_swfkeep
-   elseif &l:swf != {a:vt}netrw_swfkeep
-    if !g:netrw_use_noswf
-     " following line causes a Press ENTER in windows -- can't seem to work around it!!!
-     sil! let &l:swf= {a:vt}netrw_swfkeep
-    endif
-    unlet {a:vt}netrw_swfkeep
-   endif
+    if &directory == ""
+      " user hasn't specified a swapfile directory;
+      " netrw will temporarily set the swapfile directory
+      " to the current directory as returned by getcwd().
+      let &l:directory= getcwd()
+      sil! let &l:swf = {a:vt}netrw_swfkeep
+      setl directory=
+      unlet {a:vt}netrw_swfkeep
+    elseif &l:swf != {a:vt}netrw_swfkeep
+      if !g:netrw_use_noswf
+        " following line causes a Press ENTER in windows -- can't seem to work around it!!!
+        sil! let &l:swf= {a:vt}netrw_swfkeep
+      endif
+      unlet {a:vt}netrw_swfkeep
+    endif
   endif
   if exists("{a:vt}netrw_dirkeep") && isdirectory(s:NetrwFile({a:vt}netrw_dirkeep)) && g:netrw_keepdir
-   let dirkeep = substitute({a:vt}netrw_dirkeep,'\\','/','g')
-   if exists("{a:vt}netrw_dirkeep")
-    call s:NetrwLcd(dirkeep)
-    unlet {a:vt}netrw_dirkeep
-   endif
+    let dirkeep = substitute({a:vt}netrw_dirkeep,'\\','/','g')
+    if exists("{a:vt}netrw_dirkeep")
+      call s:NetrwLcd(dirkeep)
+      unlet {a:vt}netrw_dirkeep
+    endif
   endif
   if !has('nvim') && has("clipboard") && g:netrw_clipboard
-   call s:NetrwRestoreSetting(a:vt."netrw_starkeep","@*")
-   call s:NetrwRestoreSetting(a:vt."netrw_pluskeep","@+")
+    call s:NetrwRestoreSetting(a:vt."netrw_starkeep","@*")
+    call s:NetrwRestoreSetting(a:vt."netrw_pluskeep","@+")
   endif
   call s:NetrwRestoreSetting(a:vt."netrw_slashkeep","@/")
 
@@ -1803,7 +1804,7 @@ fun! s:NetrwOptionsRestore(vt)
   " were having their filetype detect-generated settings overwritten by
   " NetrwOptionRestore.
   if &ft != "netrw"
-   filetype detect
+    filetype detect
   endif
 endfun
 
@@ -1816,26 +1817,26 @@ endfun
 "                        Called from s:NetrwOptionsSafe
 "                          ex. call s:NetrwSetSafeSetting("&l:sel","inclusive")
 fun! s:NetrwSetSafeSetting(setting,safesetting)
-"  call Dfunc("s:NetrwSetSafeSetting(setting<".a:setting."> safesetting<".a:safesetting.">)")
+  "  call Dfunc("s:NetrwSetSafeSetting(setting<".a:setting."> safesetting<".a:safesetting.">)")
 
   if a:setting =~ '^&'
-"   call Decho("fyi: a:setting starts with &")
-   exe "let settingval= ".a:setting
-"   call Decho("fyi: settingval<".settingval.">")
-
-   if settingval != a:safesetting
-"    call Decho("set setting<".a:setting."> to option value<".a:safesetting.">")
-    if type(a:safesetting) == 0
-     exe "let ".a:setting."=".a:safesetting
-    elseif type(a:safesetting) == 1
-     exe "let ".a:setting."= '".a:safesetting."'"
-    else
-     call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:setting." with a safesetting of type#".type(a:safesetting),105)
+    "   call Decho("fyi: a:setting starts with &")
+    exe "let settingval= ".a:setting
+    "   call Decho("fyi: settingval<".settingval.">")
+
+    if settingval != a:safesetting
+      "    call Decho("set setting<".a:setting."> to option value<".a:safesetting.">")
+      if type(a:safesetting) == 0
+        exe "let ".a:setting."=".a:safesetting
+      elseif type(a:safesetting) == 1
+        exe "let ".a:setting."= '".a:safesetting."'"
+      else
+        call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:setting." with a safesetting of type#".type(a:safesetting),105)
+      endif
     endif
-   endif
   endif
 
-"  call Dret("s:NetrwSetSafeSetting")
+  "  call Dret("s:NetrwSetSafeSetting")
 endfun
 
 " ------------------------------------------------------------------------
@@ -1847,67 +1848,67 @@ endfun
 "                        Used by s:NetrwOptionsRestore() to restore each netrw-sensitive setting
 "                        keepvars are set up by s:NetrwOptionsSave
 fun! s:NetrwRestoreSetting(keepvar,setting)
-"""  call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)")
+  """  call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)")
 
   " typically called from s:NetrwOptionsRestore
   "   call s:NetrwRestoreSettings(keep-option-variable-name,'associated-option')
   "   ex. call s:NetrwRestoreSetting(a:vt."netrw_selkeep","&l:sel")
   "  Restores option (but only if different) from a:keepvar
   if exists(a:keepvar)
-   exe "let keepvarval= ".a:keepvar
-   exe "let setting= ".a:setting
-
-""   call Decho("fyi: a:keepvar<".a:keepvar."> exists")
-""   call Decho("fyi: keepvarval=".keepvarval)
-""   call Decho("fyi: a:setting<".a:setting."> setting<".setting.">")
-
-   if setting != keepvarval
-""    call Decho("restore setting<".a:setting."> (currently=".setting.") to keepvarval<".keepvarval.">")
-    if type(a:setting) == 0
-     exe "let ".a:setting."= ".keepvarval
-    elseif type(a:setting) == 1
-     exe "let ".a:setting."= '".substitute(keepvarval,"'","''","g")."'"
-    else
-     call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:keepvar." with a setting of type#".type(a:setting),105)
+    exe "let keepvarval= ".a:keepvar
+    exe "let setting= ".a:setting
+
+    ""   call Decho("fyi: a:keepvar<".a:keepvar."> exists")
+    ""   call Decho("fyi: keepvarval=".keepvarval)
+    ""   call Decho("fyi: a:setting<".a:setting."> setting<".setting.">")
+
+    if setting != keepvarval
+      ""    call Decho("restore setting<".a:setting."> (currently=".setting.") to keepvarval<".keepvarval.">")
+      if type(a:setting) == 0
+        exe "let ".a:setting."= ".keepvarval
+      elseif type(a:setting) == 1
+        exe "let ".a:setting."= '".substitute(keepvarval,"'","''","g")."'"
+      else
+        call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:keepvar." with a setting of type#".type(a:setting),105)
+      endif
     endif
-   endif
 
-   exe "unlet ".a:keepvar
+    exe "unlet ".a:keepvar
   endif
 
-""  call Dret("s:NetrwRestoreSetting")
+  ""  call Dret("s:NetrwRestoreSetting")
 endfun
 
 " ---------------------------------------------------------------------
 " NetrwStatusLine: {{{2
 fun! NetrwStatusLine()
 
-" vvv NetrwStatusLine() debugging vvv
-"  let g:stlmsg=""
-"  if !exists("w:netrw_explore_bufnr")
-"   let g:stlmsg="!X"
-"  elseif w:netrw_explore_bufnr != bufnr("%")
-"   let g:stlmsg="explore_bufnr!=".bufnr("%")
-"  endif
-"  if !exists("w:netrw_explore_line")
-"   let g:stlmsg=" !X"
-"  elseif w:netrw_explore_line != line(".")
-"   let g:stlmsg=" explore_line!={line(.)<".line(".").">"
-"  endif
-"  if !exists("w:netrw_explore_list")
-"   let g:stlmsg=" !X"
-"  endif
-" ^^^ NetrwStatusLine() debugging ^^^
+  " vvv NetrwStatusLine() debugging vvv
+  "  let g:stlmsg=""
+  "  if !exists("w:netrw_explore_bufnr")
+  "   let g:stlmsg="!X"
+  "  elseif w:netrw_explore_bufnr != bufnr("%")
+  "   let g:stlmsg="explore_bufnr!=".bufnr("%")
+  "  endif
+  "  if !exists("w:netrw_explore_line")
+  "   let g:stlmsg=" !X"
+  "  elseif w:netrw_explore_line != line(".")
+  "   let g:stlmsg=" explore_line!={line(.)<".line(".").">"
+  "  endif
+  "  if !exists("w:netrw_explore_list")
+  "   let g:stlmsg=" !X"
+  "  endif
+  " ^^^ NetrwStatusLine() debugging ^^^
 
   if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list")
-   " restore user's status line
-   let &l:stl      = s:netrw_users_stl
-   let &laststatus = s:netrw_users_ls
-   if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif
-   if exists("w:netrw_explore_line") |unlet w:netrw_explore_line |endif
-   return ""
+    " restore user's status line
+    let &l:stl      = s:netrw_users_stl
+    let &laststatus = s:netrw_users_ls
+    if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif
+    if exists("w:netrw_explore_line") |unlet w:netrw_explore_line |endif
+    return ""
   else
-   return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen
+    return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen
   endif
 endfun
 
@@ -1922,7 +1923,7 @@ endfun
 "         =2 replace with remote file
 "         =3 obtain file, but leave in temporary format
 fun! netrw#NetRead(mode,...)
-"  call Dfunc("netrw#NetRead(mode=".a:mode.",...) a:0=".a:0." ".g:loaded_netrw.((a:0 > 0)? " a:1<".a:1.">" : ""))
+  "  call Dfunc("netrw#NetRead(mode=".a:mode.",...) a:0=".a:0." ".g:loaded_netrw.((a:0 > 0)? " a:1<".a:1.">" : ""))
 
   " NetRead: save options {{{3
   call s:NetrwOptionsSave("w:")
@@ -1931,414 +1932,414 @@ fun! netrw#NetRead(mode,...)
   " NetrwSafeOptions sets a buffer up for a netrw listing, which includes buflisting off.
   " However, this setting is not wanted for a remote editing session.  The buffer should be "nofile", still.
   setl bl
-"  call Decho("buf#".bufnr("%")."<".bufname("%")."> bl=".&bl." bt=".&bt." bh=".&bh,'~'.expand(""))
+  "  call Decho("buf#".bufnr("%")."<".bufname("%")."> bl=".&bl." bt=".&bt." bh=".&bh,'~'.expand(""))
 
   " NetRead: interpret mode into a readcmd {{{3
   if     a:mode == 0 " read remote file before current line
-   let readcmd = "0r"
+    let readcmd = "0r"
   elseif a:mode == 1 " read file after current line
-   let readcmd = "r"
+    let readcmd = "r"
   elseif a:mode == 2 " replace with remote file
-   let readcmd = "%r"
+    let readcmd = "%r"
   elseif a:mode == 3 " skip read of file (leave as temporary)
-   let readcmd = "t"
+    let readcmd = "t"
   else
-   exe a:mode
-   let readcmd = "r"
+    exe a:mode
+    let readcmd = "r"
   endif
   let ichoice = (a:0 == 0)? 0 : 1
-"  call Decho("readcmd<".readcmd."> ichoice=".ichoice,'~'.expand(""))
+  "  call Decho("readcmd<".readcmd."> ichoice=".ichoice,'~'.expand(""))
 
   " NetRead: get temporary filename {{{3
   let tmpfile= s:GetTempfile("")
   if tmpfile == ""
-"   call Dret("netrw#NetRead : unable to get a tempfile!")
-   return
+    "   call Dret("netrw#NetRead : unable to get a tempfile!")
+    return
   endif
 
   while ichoice <= a:0
 
-   " attempt to repeat with previous host-file-etc
-   if exists("b:netrw_lastfile") && a:0 == 0
-"    call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand(""))
-    let choice = b:netrw_lastfile
-    let ichoice= ichoice + 1
+    " attempt to repeat with previous host-file-etc
+    if exists("b:netrw_lastfile") && a:0 == 0
+      "    call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand(""))
+      let choice = b:netrw_lastfile
+      let ichoice= ichoice + 1
 
-   else
-    exe "let choice= a:" . ichoice
-"    call Decho("no lastfile: choice<" . choice . ">",'~'.expand(""))
-
-    if match(choice,"?") == 0
-     " give help
-     echomsg 'NetRead Usage:'
-     echomsg ':Nread machine:path                         uses rcp'
-     echomsg ':Nread "machine path"                       uses ftp   with <.netrc>'
-     echomsg ':Nread "machine id password path"           uses ftp'
-     echomsg ':Nread dav://machine[:port]/path            uses cadaver'
-     echomsg ':Nread fetch://machine/path                 uses fetch'
-     echomsg ':Nread ftp://[user@]machine[:port]/path     uses ftp   autodetects <.netrc>'
-     echomsg ':Nread http://[user@]machine/path           uses http  wget'
-     echomsg ':Nread file:///path           		  uses elinks'
-     echomsg ':Nread https://[user@]machine/path          uses http  wget'
-     echomsg ':Nread rcp://[user@]machine/path            uses rcp'
-     echomsg ':Nread rsync://machine[:port]/path          uses rsync'
-     echomsg ':Nread scp://[user@]machine[[:#]port]/path  uses scp'
-     echomsg ':Nread sftp://[user@]machine[[:#]port]/path uses sftp'
-     sleep 4
-     break
-
-    elseif match(choice,'^"') != -1
-     " Reconstruct Choice if choice starts with '"'
-"     call Decho("reconstructing choice",'~'.expand(""))
-     if match(choice,'"$') != -1
-      " case "..."
-      let choice= strpart(choice,1,strlen(choice)-2)
-     else
-       "  case "... ... ..."
-      let choice      = strpart(choice,1,strlen(choice)-1)
-      let wholechoice = ""
-
-      while match(choice,'"$') == -1
-       let wholechoice = wholechoice . " " . choice
-       let ichoice     = ichoice + 1
-       if ichoice > a:0
-        if !exists("g:netrw_quiet")
-         call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",3)
+    else
+      exe "let choice= a:" . ichoice
+      "    call Decho("no lastfile: choice<" . choice . ">",'~'.expand(""))
+
+      if match(choice,"?") == 0
+        " give help
+        echomsg 'NetRead Usage:'
+        echomsg ':Nread machine:path                         uses rcp'
+        echomsg ':Nread "machine path"                       uses ftp   with <.netrc>'
+        echomsg ':Nread "machine id password path"           uses ftp'
+        echomsg ':Nread dav://machine[:port]/path            uses cadaver'
+        echomsg ':Nread fetch://machine/path                 uses fetch'
+        echomsg ':Nread ftp://[user@]machine[:port]/path     uses ftp   autodetects <.netrc>'
+        echomsg ':Nread http://[user@]machine/path           uses http  wget'
+        echomsg ':Nread file:///path                         uses elinks'
+        echomsg ':Nread https://[user@]machine/path          uses http  wget'
+        echomsg ':Nread rcp://[user@]machine/path            uses rcp'
+        echomsg ':Nread rsync://machine[:port]/path          uses rsync'
+        echomsg ':Nread scp://[user@]machine[[:#]port]/path  uses scp'
+        echomsg ':Nread sftp://[user@]machine[[:#]port]/path uses sftp'
+        sleep 4
+        break
+
+      elseif match(choice,'^"') != -1
+        " Reconstruct Choice if choice starts with '"'
+        "     call Decho("reconstructing choice",'~'.expand(""))
+        if match(choice,'"$') != -1
+          " case "..."
+          let choice= strpart(choice,1,strlen(choice)-2)
+        else
+          "  case "... ... ..."
+          let choice      = strpart(choice,1,strlen(choice)-1)
+          let wholechoice = ""
+
+          while match(choice,'"$') == -1
+            let wholechoice = wholechoice . " " . choice
+            let ichoice     = ichoice + 1
+            if ichoice > a:0
+              if !exists("g:netrw_quiet")
+                call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",3)
+              endif
+              "        call Dret("netrw#NetRead :2 getcwd<".getcwd().">")
+              return
+            endif
+            let choice= a:{ichoice}
+          endwhile
+          let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1)
         endif
-"        call Dret("netrw#NetRead :2 getcwd<".getcwd().">")
-        return
-       endif
-       let choice= a:{ichoice}
-      endwhile
-      let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1)
-     endif
+      endif
     endif
-   endif
 
-"   call Decho("choice<" . choice . ">",'~'.expand(""))
-   let ichoice= ichoice + 1
+    "   call Decho("choice<" . choice . ">",'~'.expand(""))
+    let ichoice= ichoice + 1
 
-   " NetRead: Determine method of read (ftp, rcp, etc) {{{3
-   call s:NetrwMethod(choice)
-   if !exists("b:netrw_method") || b:netrw_method < 0
-"    call Dret("netrw#NetRead : unsupported method")
-    return
-   endif
-   let tmpfile= s:GetTempfile(b:netrw_fname) " apply correct suffix
-
-   " Check whether or not NetrwBrowse() should be handling this request
-"   call Decho("checking if NetrwBrowse() should handle choice<".choice."> with netrw_list_cmd<".g:netrw_list_cmd.">",'~'.expand(""))
-   if choice =~ "^.*[\/]$" && b:netrw_method != 5 && choice !~ '^https\=://'
-"    call Decho("yes, choice matches '^.*[\/]$'",'~'.expand(""))
-    NetrwKeepj call s:NetrwBrowse(0,choice)
-"    call Dret("netrw#NetRead :3 getcwd<".getcwd().">")
-    return
-   endif
-
-   " ============
-   " NetRead: Perform Protocol-Based Read {{{3
-   " ===========================
-   if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1
-    echo "(netrw) Processing your read request..."
-   endif
-
-   ".........................................
-   " NetRead: (rcp)  NetRead Method #1 {{{3
-   if  b:netrw_method == 1 " read with rcp
-"    call Decho("read via rcp (method #1)",'~'.expand(""))
-   " ER: nothing done with g:netrw_uid yet?
-   " ER: on Win2K" rcp machine[.user]:file tmpfile
-   " ER: when machine contains '.' adding .user is required (use $USERNAME)
-   " ER: the tmpfile is full path: rcp sees C:\... as host C
-   if s:netrw_has_nt_rcp == 1
-    if exists("g:netrw_uid") &&	( g:netrw_uid != "" )
-     let uid_machine = g:netrw_machine .'.'. g:netrw_uid
-    else
-     " Any way needed it machine contains a '.'
-     let uid_machine = g:netrw_machine .'.'. $USERNAME
+    " NetRead: Determine method of read (ftp, rcp, etc) {{{3
+    call s:NetrwMethod(choice)
+    if !exists("b:netrw_method") || b:netrw_method < 0
+      "    call Dret("netrw#NetRead : unsupported method")
+      return
     endif
-   else
-    if exists("g:netrw_uid") &&	( g:netrw_uid != "" )
-     let uid_machine = g:netrw_uid .'@'. g:netrw_machine
-    else
-     let uid_machine = g:netrw_machine
-    endif
-   endif
-   call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
-   let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-   let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (ftp + <.netrc>)  NetRead Method #2 {{{3
-   elseif b:netrw_method  == 2		" read with ftp + <.netrc>
-"     call Decho("read via ftp+.netrc (method #2)",'~'.expand(""))
-     let netrw_fname= b:netrw_fname
-     NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars()
-     let filtbuf= bufnr("%")
-     setl ff=unix
-     NetrwKeepj put =g:netrw_ftpmode
-"     call Decho("filter input: ".getline(line("$")),'~'.expand(""))
-     if exists("g:netrw_ftpextracmd")
-      NetrwKeepj put =g:netrw_ftpextracmd
-"      call Decho("filter input: ".getline(line("$")),'~'.expand(""))
-     endif
-     call setline(line("$")+1,'get "'.netrw_fname.'" '.tmpfile)
-"     call Decho("filter input: ".getline(line("$")),'~'.expand(""))
-     if exists("g:netrw_port") && g:netrw_port != ""
-      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
-     else
-      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
-     endif
-     " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-     if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
-      let debugkeep = &debug
-      setl debug=msg
-      NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),4)
-      let &debug    = debugkeep
-     endif
-     call s:SaveBufVars()
-     keepj bd!
-     if bufname("%") == "" && getline("$") == "" && line('$') == 1
-      " needed when one sources a file in a nolbl setting window via ftp
-      q!
-     endif
-     call s:RestoreBufVars()
-     let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-     let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (ftp + machine,id,passwd,filename)  NetRead Method #3 {{{3
-   elseif b:netrw_method == 3		" read with ftp + machine, id, passwd, and fname
-    " Construct execution string (four lines) which will be passed through filter
-"    call Decho("read via ftp+mipf (method #3)",'~'.expand(""))
-    let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
-    NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars()
-    let filtbuf= bufnr("%")
-    setl ff=unix
-    if exists("g:netrw_port") && g:netrw_port != ""
-     NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-"     call Decho("filter input: ".getline('.'),'~'.expand(""))
-    else
-     NetrwKeepj put ='open '.g:netrw_machine
-"     call Decho("filter input: ".getline('.'),'~'.expand(""))
+    let tmpfile= s:GetTempfile(b:netrw_fname) " apply correct suffix
+
+    " Check whether or not NetrwBrowse() should be handling this request
+    "   call Decho("checking if NetrwBrowse() should handle choice<".choice."> with netrw_list_cmd<".g:netrw_list_cmd.">",'~'.expand(""))
+    if choice =~ "^.*[\/]$" && b:netrw_method != 5 && choice !~ '^https\=://'
+      "    call Decho("yes, choice matches '^.*[\/]$'",'~'.expand(""))
+      NetrwKeepj call s:NetrwBrowse(0,choice)
+      "    call Dret("netrw#NetRead :3 getcwd<".getcwd().">")
+      return
     endif
 
-    if exists("g:netrw_uid") && g:netrw_uid != ""
-     if exists("g:netrw_ftp") && g:netrw_ftp == 1
-      NetrwKeepj put =g:netrw_uid
-"       call Decho("filter input: ".getline('.'),'~'.expand(""))
-      if exists("s:netrw_passwd")
-       NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
+    " ============
+    " NetRead: Perform Protocol-Based Read {{{3
+    " ===========================
+    if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1
+      echo "(netrw) Processing your read request..."
+    endif
+
+    ".........................................
+    " NetRead: (rcp)  NetRead Method #1 {{{3
+    if  b:netrw_method == 1 " read with rcp
+      "    call Decho("read via rcp (method #1)",'~'.expand(""))
+      " ER: nothing done with g:netrw_uid yet?
+      " ER: on Win2K" rcp machine[.user]:file tmpfile
+      " ER: when machine contains '.' adding .user is required (use $USERNAME)
+      " ER: the tmpfile is full path: rcp sees C:\... as host C
+      if s:netrw_has_nt_rcp == 1
+        if exists("g:netrw_uid") && ( g:netrw_uid != "" )
+          let uid_machine = g:netrw_machine .'.'. g:netrw_uid
+        else
+          " Any way needed it machine contains a '.'
+          let uid_machine = g:netrw_machine .'.'. $USERNAME
+        endif
+      else
+        if exists("g:netrw_uid") && ( g:netrw_uid != "" )
+          let uid_machine = g:netrw_uid .'@'. g:netrw_machine
+        else
+          let uid_machine = g:netrw_machine
+        endif
+      endif
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
+      let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (ftp + <.netrc>)  NetRead Method #2 {{{3
+    elseif b:netrw_method  == 2          " read with ftp + <.netrc>
+      "     call Decho("read via ftp+.netrc (method #2)",'~'.expand(""))
+      let netrw_fname= b:netrw_fname
+      NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars()
+      let filtbuf= bufnr("%")
+      setl ff=unix
+      NetrwKeepj put =g:netrw_ftpmode
+      "     call Decho("filter input: ".getline(line("$")),'~'.expand(""))
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "      call Decho("filter input: ".getline(line("$")),'~'.expand(""))
+      endif
+      call setline(line("$")+1,'get "'.netrw_fname.'" '.tmpfile)
+      "     call Decho("filter input: ".getline(line("$")),'~'.expand(""))
+      if exists("g:netrw_port") && g:netrw_port != ""
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
+      else
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
+      endif
+      " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+      if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
+        let debugkeep = &debug
+        setl debug=msg
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),4)
+        let &debug    = debugkeep
+      endif
+      call s:SaveBufVars()
+      keepj bd!
+      if bufname("%") == "" && getline("$") == "" && line('$') == 1
+        " needed when one sources a file in a nolbl setting window via ftp
+        q!
+      endif
+      call s:RestoreBufVars()
+      let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (ftp + machine,id,passwd,filename)  NetRead Method #3 {{{3
+    elseif b:netrw_method == 3           " read with ftp + machine, id, passwd, and fname
+      " Construct execution string (four lines) which will be passed through filter
+      "    call Decho("read via ftp+mipf (method #3)",'~'.expand(""))
+      let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
+      NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars()
+      let filtbuf= bufnr("%")
+      setl ff=unix
+      if exists("g:netrw_port") && g:netrw_port != ""
+        NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      else
+        NetrwKeepj put ='open '.g:netrw_machine
+        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
       endif
-"      call Decho("filter input: ".getline('.'),'~'.expand(""))
-     elseif exists("s:netrw_passwd")
-      NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
-"      call Decho("filter input: ".getline('.'),'~'.expand(""))
-     endif
-    endif
 
-    if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
-     NetrwKeepj put =g:netrw_ftpmode
-"     call Decho("filter input: ".getline('.'),'~'.expand(""))
-    endif
-    if exists("g:netrw_ftpextracmd")
-     NetrwKeepj put =g:netrw_ftpextracmd
-"     call Decho("filter input: ".getline('.'),'~'.expand(""))
-    endif
-    NetrwKeepj put ='get \"'.netrw_fname.'\" '.tmpfile
-"    call Decho("filter input: ".getline('.'),'~'.expand(""))
+      if exists("g:netrw_uid") && g:netrw_uid != ""
+        if exists("g:netrw_ftp") && g:netrw_ftp == 1
+          NetrwKeepj put =g:netrw_uid
+          "       call Decho("filter input: ".getline('.'),'~'.expand(""))
+          if exists("s:netrw_passwd")
+            NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
+          endif
+          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+        elseif exists("s:netrw_passwd")
+          NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+        endif
+      endif
 
-    " perform ftp:
-    " -i       : turns off interactive prompting from ftp
-    " -n  unix : DON'T use <.netrc>, even though it exists
-    " -n  win32: quit being obnoxious about password
-    NetrwKeepj norm! 1G"_dd
-    call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
-    " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-    if getline(1) !~ "^$"
-"     call Decho("error<".getline(1).">",'~'.expand(""))
-     if !exists("g:netrw_quiet")
-      call netrw#ErrorMsg(s:ERROR,getline(1),5)
-     endif
-    endif
-    call s:SaveBufVars()|keepj bd!|call s:RestoreBufVars()
-    let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (scp) NetRead Method #4 {{{3
-   elseif     b:netrw_method  == 4	" read with scp
-"    call Decho("read via scp (method #4)",'~'.expand(""))
-    if exists("g:netrw_port") && g:netrw_port != ""
-     let useport= " ".g:netrw_scpport." ".g:netrw_port
-    else
-     let useport= ""
-    endif
-    " 'C' in 'C:\path\to\file' is handled as hostname on windows.
-    " This is workaround to avoid mis-handle windows local-path:
-    if g:netrw_scp_cmd =~ '^scp' && has("win32")
-      let tmpfile_get = substitute(tr(tmpfile, '\', '/'), '^\(\a\):[/\\]\(.*\)$', '/\1/\2', '')
-    else
-      let tmpfile_get = tmpfile
-    endif
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".escape(s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1),' ')." ".s:ShellEscape(tmpfile_get,1))
-    let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (http) NetRead Method #5 (wget) {{{3
-   elseif     b:netrw_method  == 5
-"    call Decho("read via http (method #5)",'~'.expand(""))
-    if g:netrw_http_cmd == ""
-     if !exists("g:netrw_quiet")
-      call netrw#ErrorMsg(s:ERROR,"neither the wget nor the fetch command is available",6)
-     endif
-"     call Dret("netrw#NetRead :4 getcwd<".getcwd().">")
-     return
-    endif
-
-    if match(b:netrw_fname,"#") == -1 || exists("g:netrw_http_xcmd")
-     " using g:netrw_http_cmd (usually elinks, links, curl, wget, or fetch)
-"     call Decho('using '.g:netrw_http_cmd.' (# not in b:netrw_fname<'.b:netrw_fname.">)",'~'.expand(""))
-     if exists("g:netrw_http_xcmd")
-      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".s:ShellEscape(tmpfile,1))
-     else
-      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1))
-     endif
-     let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
+        NetrwKeepj put =g:netrw_ftpmode
+        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      endif
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      endif
+      NetrwKeepj put ='get \"'.netrw_fname.'\" '.tmpfile
+      "    call Decho("filter input: ".getline('.'),'~'.expand(""))
+
+      " perform ftp:
+      " -i       : turns off interactive prompting from ftp
+      " -n  unix : DON'T use <.netrc>, even though it exists
+      " -n  win32: quit being obnoxious about password
+      NetrwKeepj norm! 1G"_dd
+      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
+      " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+      if getline(1) !~ "^$"
+        "     call Decho("error<".getline(1).">",'~'.expand(""))
+        if !exists("g:netrw_quiet")
+          call netrw#ErrorMsg(s:ERROR,getline(1),5)
+        endif
+      endif
+      call s:SaveBufVars()|keepj bd!|call s:RestoreBufVars()
+      let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (scp) NetRead Method #4 {{{3
+    elseif     b:netrw_method  == 4      " read with scp
+      "    call Decho("read via scp (method #4)",'~'.expand(""))
+      if exists("g:netrw_port") && g:netrw_port != ""
+        let useport= " ".g:netrw_scpport." ".g:netrw_port
+      else
+        let useport= ""
+      endif
+      " 'C' in 'C:\path\to\file' is handled as hostname on windows.
+      " This is workaround to avoid mis-handle windows local-path:
+      if g:netrw_scp_cmd =~ '^scp' && has("win32")
+        let tmpfile_get = substitute(tr(tmpfile, '\', '/'), '^\(\a\):[/\\]\(.*\)$', '/\1/\2', '')
+      else
+        let tmpfile_get = tmpfile
+      endif
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".escape(s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1),' ')." ".s:ShellEscape(tmpfile_get,1))
+      let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (http) NetRead Method #5 (wget) {{{3
+    elseif     b:netrw_method  == 5
+      "    call Decho("read via http (method #5)",'~'.expand(""))
+      if g:netrw_http_cmd == ""
+        if !exists("g:netrw_quiet")
+          call netrw#ErrorMsg(s:ERROR,"neither the wget nor the fetch command is available",6)
+        endif
+        "     call Dret("netrw#NetRead :4 getcwd<".getcwd().">")
+        return
+      endif
 
-    else
-     " wget/curl/fetch plus a jump to an in-page marker (ie. http://abc/def.html#aMarker)
-"     call Decho("wget/curl plus jump (# in b:netrw_fname<".b:netrw_fname.">)",'~'.expand(""))
-     let netrw_html= substitute(b:netrw_fname,"#.*$","","")
-     let netrw_tag = substitute(b:netrw_fname,"^.*#","","")
-"     call Decho("netrw_html<".netrw_html.">",'~'.expand(""))
-"     call Decho("netrw_tag <".netrw_tag.">",'~'.expand(""))
-     call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.netrw_html,1))
-     let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-"     call Decho('<\s*a\s*name=\s*"'.netrw_tag.'"/','~'.expand(""))
-     exe 'NetrwKeepj norm! 1G/<\s*a\s*name=\s*"'.netrw_tag.'"/'."\"
-    endif
-    let b:netrw_lastfile = choice
-"    call Decho("setl ro",'~'.expand(""))
-    setl ro nomod
-
-   ".........................................
-   " NetRead: (dav) NetRead Method #6 {{{3
-   elseif     b:netrw_method  == 6
-"    call Decho("read via cadaver (method #6)",'~'.expand(""))
-
-    if !executable(g:netrw_dav_cmd)
-     call netrw#ErrorMsg(s:ERROR,g:netrw_dav_cmd." is not executable",73)
-"     call Dret("netrw#NetRead : ".g:netrw_dav_cmd." not executable")
-     return
-    endif
-    if g:netrw_dav_cmd =~ "curl"
-     call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_dav_cmd." ".s:ShellEscape("dav://".g:netrw_machine.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
-    else
-     " Construct execution string (four lines) which will be passed through filter
-     let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
-     new
-     setl ff=unix
-     if exists("g:netrw_port") && g:netrw_port != ""
-      NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-     else
-      NetrwKeepj put ='open '.g:netrw_machine
-     endif
-     if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != ""
-      NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
-     endif
-     NetrwKeepj put ='get '.netrw_fname.' '.tmpfile
-     NetrwKeepj put ='quit'
-
-     " perform cadaver operation:
-     NetrwKeepj norm! 1G"_dd
-     call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd)
-     keepj bd!
-    endif
-    let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (rsync) NetRead Method #7 {{{3
-   elseif     b:netrw_method  == 7
-"    call Decho("read via rsync (method #7)",'~'.expand(""))
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
-    let result		 = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method)
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (fetch) NetRead Method #8 {{{3
-   "    fetch://[user@]host[:http]/path
-   elseif     b:netrw_method  == 8
-"    call Decho("read via fetch (method #8)",'~'.expand(""))
-    if g:netrw_fetch_cmd == ""
-     if !exists("g:netrw_quiet")
-      NetrwKeepj call netrw#ErrorMsg(s:ERROR,"fetch command not available",7)
-     endif
-"     call Dret("NetRead")
-     return
-    endif
-    if exists("g:netrw_option") && g:netrw_option =~ ":https\="
-     let netrw_option= "http"
-    else
-     let netrw_option= "ftp"
-    endif
-"    call Decho("read via fetch for ".netrw_option,'~'.expand(""))
+      if match(b:netrw_fname,"#") == -1 || exists("g:netrw_http_xcmd")
+        " using g:netrw_http_cmd (usually elinks, links, curl, wget, or fetch)
+        "     call Decho('using '.g:netrw_http_cmd.' (# not in b:netrw_fname<'.b:netrw_fname.">)",'~'.expand(""))
+        if exists("g:netrw_http_xcmd")
+          call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".s:ShellEscape(tmpfile,1))
+        else
+          call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1))
+        endif
+        let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+
+      else
+        " wget/curl/fetch plus a jump to an in-page marker (ie. http://abc/def.html#aMarker)
+        "     call Decho("wget/curl plus jump (# in b:netrw_fname<".b:netrw_fname.">)",'~'.expand(""))
+        let netrw_html= substitute(b:netrw_fname,"#.*$","","")
+        let netrw_tag = substitute(b:netrw_fname,"^.*#","","")
+        "     call Decho("netrw_html<".netrw_html.">",'~'.expand(""))
+        "     call Decho("netrw_tag <".netrw_tag.">",'~'.expand(""))
+        call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.netrw_html,1))
+        let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+        "     call Decho('<\s*a\s*name=\s*"'.netrw_tag.'"/','~'.expand(""))
+        exe 'NetrwKeepj norm! 1G/<\s*a\s*name=\s*"'.netrw_tag.'"/'."\"
+      endif
+      let b:netrw_lastfile = choice
+      "    call Decho("setl ro",'~'.expand(""))
+      setl ro nomod
+
+      ".........................................
+      " NetRead: (dav) NetRead Method #6 {{{3
+    elseif     b:netrw_method  == 6
+      "    call Decho("read via cadaver (method #6)",'~'.expand(""))
+
+      if !executable(g:netrw_dav_cmd)
+        call netrw#ErrorMsg(s:ERROR,g:netrw_dav_cmd." is not executable",73)
+        "     call Dret("netrw#NetRead : ".g:netrw_dav_cmd." not executable")
+        return
+      endif
+      if g:netrw_dav_cmd =~ "curl"
+        call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_dav_cmd." ".s:ShellEscape("dav://".g:netrw_machine.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
+      else
+        " Construct execution string (four lines) which will be passed through filter
+        let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
+        new
+        setl ff=unix
+        if exists("g:netrw_port") && g:netrw_port != ""
+          NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+        else
+          NetrwKeepj put ='open '.g:netrw_machine
+        endif
+        if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != ""
+          NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
+        endif
+        NetrwKeepj put ='get '.netrw_fname.' '.tmpfile
+        NetrwKeepj put ='quit'
 
-    if exists("g:netrw_uid") && g:netrw_uid != "" && exists("s:netrw_passwd") && s:netrw_passwd != ""
-     call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1))
+        " perform cadaver operation:
+        NetrwKeepj norm! 1G"_dd
+        call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd)
+        keepj bd!
+      endif
+      let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (rsync) NetRead Method #7 {{{3
+    elseif     b:netrw_method  == 7
+      "    call Decho("read via rsync (method #7)",'~'.expand(""))
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
+      let result           = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (fetch) NetRead Method #8 {{{3
+      "    fetch://[user@]host[:http]/path
+    elseif     b:netrw_method  == 8
+      "    call Decho("read via fetch (method #8)",'~'.expand(""))
+      if g:netrw_fetch_cmd == ""
+        if !exists("g:netrw_quiet")
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"fetch command not available",7)
+        endif
+        "     call Dret("NetRead")
+        return
+      endif
+      if exists("g:netrw_option") && g:netrw_option =~ ":https\="
+        let netrw_option= "http"
+      else
+        let netrw_option= "ftp"
+      endif
+      "    call Decho("read via fetch for ".netrw_option,'~'.expand(""))
+
+      if exists("g:netrw_uid") && g:netrw_uid != "" && exists("s:netrw_passwd") && s:netrw_passwd != ""
+        call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1))
+      else
+        call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1))
+      endif
+
+      let result          = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+      "    call Decho("setl ro",'~'.expand(""))
+      setl ro nomod
+
+      ".........................................
+      " NetRead: (sftp) NetRead Method #9 {{{3
+    elseif     b:netrw_method  == 9
+      "    call Decho("read via sftp (method #9)",'~'.expand(""))
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
+      let result          = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: (file) NetRead Method #10 {{{3
+    elseif      b:netrw_method == 10 && exists("g:netrw_file_cmd")
+      "   "    call Decho("read via ".b:netrw_file_cmd." (method #10)",'~'.expand(""))
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_file_cmd." ".s:ShellEscape(b:netrw_fname,1)." ".tmpfile)
+      let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetRead: Complain {{{3
     else
-     call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1))
-    endif
-
-    let result		= s:NetrwGetFile(readcmd,tmpfile, b:netrw_method)
-    let b:netrw_lastfile = choice
-"    call Decho("setl ro",'~'.expand(""))
-    setl ro nomod
-
-   ".........................................
-   " NetRead: (sftp) NetRead Method #9 {{{3
-   elseif     b:netrw_method  == 9
-"    call Decho("read via sftp (method #9)",'~'.expand(""))
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
-    let result		= s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: (file) NetRead Method #10 {{{3
-  elseif      b:netrw_method == 10 && exists("g:netrw_file_cmd")
-"   "    call Decho("read via ".b:netrw_file_cmd." (method #10)",'~'.expand(""))
-   call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_file_cmd." ".s:ShellEscape(b:netrw_fname,1)." ".tmpfile)
-   let result		= s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
-   let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetRead: Complain {{{3
-   else
-    call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",8)
-   endif
+      call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",8)
+    endif
   endwhile
 
   " NetRead: cleanup {{{3
   if exists("b:netrw_method")
-"   call Decho("cleanup b:netrw_method and b:netrw_fname",'~'.expand(""))
-   unlet b:netrw_method
-   unlet b:netrw_fname
+    "   call Decho("cleanup b:netrw_method and b:netrw_fname",'~'.expand(""))
+    unlet b:netrw_method
+    unlet b:netrw_fname
   endif
   if s:FileReadable(tmpfile) && tmpfile !~ '.tar.bz2$' && tmpfile !~ '.tar.gz$' && tmpfile !~ '.zip' && tmpfile !~ '.tar' && readcmd != 't' && tmpfile !~ '.tar.xz$' && tmpfile !~ '.txz'
-"   call Decho("cleanup by deleting tmpfile<".tmpfile.">",'~'.expand(""))
-   NetrwKeepj call s:NetrwDelete(tmpfile)
+    "   call Decho("cleanup by deleting tmpfile<".tmpfile.">",'~'.expand(""))
+    NetrwKeepj call s:NetrwDelete(tmpfile)
   endif
   NetrwKeepj call s:NetrwOptionsRestore("w:")
 
-"  call Dret("netrw#NetRead :5 getcwd<".getcwd().">")
+  "  call Dret("netrw#NetRead :5 getcwd<".getcwd().">")
 endfun
 
 " ------------------------------------------------------------------------
 " netrw#NetWrite: responsible for writing a file over the net {{{2
 fun! netrw#NetWrite(...) range
-"  call Dfunc("netrw#NetWrite(a:0=".a:0.") ".g:loaded_netrw)
+  "  call Dfunc("netrw#NetWrite(a:0=".a:0.") ".g:loaded_netrw)
 
   " NetWrite: option handling {{{3
   let mod= 0
@@ -2348,366 +2349,366 @@ fun! netrw#NetWrite(...) range
   " NetWrite: Get Temporary Filename {{{3
   let tmpfile= s:GetTempfile("")
   if tmpfile == ""
-"   call Dret("netrw#NetWrite : unable to get a tempfile!")
-   return
+    "   call Dret("netrw#NetWrite : unable to get a tempfile!")
+    return
   endif
 
   if a:0 == 0
-   let ichoice = 0
+    let ichoice = 0
   else
-   let ichoice = 1
+    let ichoice = 1
   endif
 
   let curbufname= expand("%")
-"  call Decho("curbufname<".curbufname.">",'~'.expand(""))
+  "  call Decho("curbufname<".curbufname.">",'~'.expand(""))
   if &binary
-   " For binary writes, always write entire file.
-   " (line numbers don't really make sense for that).
-   " Also supports the writing of tar and zip files.
-"   call Decho("(write entire file) sil exe w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand(""))
-   exe "sil NetrwKeepj w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
+    " For binary writes, always write entire file.
+    " (line numbers don't really make sense for that).
+    " Also supports the writing of tar and zip files.
+    "   call Decho("(write entire file) sil exe w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand(""))
+    exe "sil NetrwKeepj w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
   elseif g:netrw_cygwin
-   " write (selected portion of) file to temporary
-   let cygtmpfile= substitute(tmpfile,g:netrw_cygdrive.'/\(.\)','\1:','')
-"   call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile),'~'.expand(""))
-   exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile)
+    " write (selected portion of) file to temporary
+    let cygtmpfile= substitute(tmpfile,g:netrw_cygdrive.'/\(.\)','\1:','')
+    "   call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile),'~'.expand(""))
+    exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile)
   else
-   " write (selected portion of) file to temporary
-"   call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand(""))
-   exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
+    " write (selected portion of) file to temporary
+    "   call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand(""))
+    exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
   endif
 
   if curbufname == ""
-   " when the file is [No Name], and one attempts to Nwrite it, the buffer takes
-   " on the temporary file's name.  Deletion of the temporary file during
-   " cleanup then causes an error message.
-   0file!
+    " when the file is [No Name], and one attempts to Nwrite it, the buffer takes
+    " on the temporary file's name.  Deletion of the temporary file during
+    " cleanup then causes an error message.
+    0file!
   endif
 
   " NetWrite: while choice loop: {{{3
   while ichoice <= a:0
 
-   " Process arguments: {{{4
-   " attempt to repeat with previous host-file-etc
-   if exists("b:netrw_lastfile") && a:0 == 0
-"    call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand(""))
-    let choice = b:netrw_lastfile
-    let ichoice= ichoice + 1
-   else
-    exe "let choice= a:" . ichoice
-
-    " Reconstruct Choice when choice starts with '"'
-    if match(choice,"?") == 0
-     echomsg 'NetWrite Usage:"'
-     echomsg ':Nwrite machine:path                        uses rcp'
-     echomsg ':Nwrite "machine path"                      uses ftp with <.netrc>'
-     echomsg ':Nwrite "machine id password path"          uses ftp'
-     echomsg ':Nwrite dav://[user@]machine/path           uses cadaver'
-     echomsg ':Nwrite fetch://[user@]machine/path         uses fetch'
-     echomsg ':Nwrite ftp://machine[#port]/path           uses ftp  (autodetects <.netrc>)'
-     echomsg ':Nwrite rcp://machine/path                  uses rcp'
-     echomsg ':Nwrite rsync://[user@]machine/path         uses rsync'
-     echomsg ':Nwrite scp://[user@]machine[[:#]port]/path uses scp'
-     echomsg ':Nwrite sftp://[user@]machine/path          uses sftp'
-     sleep 4
-     break
-
-    elseif match(choice,"^\"") != -1
-     if match(choice,"\"$") != -1
-       " case "..."
-      let choice=strpart(choice,1,strlen(choice)-2)
-     else
-      "  case "... ... ..."
-      let choice      = strpart(choice,1,strlen(choice)-1)
-      let wholechoice = ""
-
-      while match(choice,"\"$") == -1
-       let wholechoice= wholechoice . " " . choice
-       let ichoice    = ichoice + 1
-       if choice > a:0
-        if !exists("g:netrw_quiet")
-         call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",13)
-        endif
-"        call Dret("netrw#NetWrite")
-        return
-       endif
-       let choice= a:{ichoice}
-      endwhile
-      let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1)
-     endif
-    endif
-   endif
-   let ichoice= ichoice + 1
-"   call Decho("choice<" . choice . "> ichoice=".ichoice,'~'.expand(""))
-
-   " Determine method of write (ftp, rcp, etc) {{{4
-   NetrwKeepj call s:NetrwMethod(choice)
-   if !exists("b:netrw_method") || b:netrw_method < 0
-"    call Dfunc("netrw#NetWrite : unsupported method")
-    return
-   endif
-
-   " =============
-   " NetWrite: Perform Protocol-Based Write {{{3
-   " ============================
-   if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1
-    echo "(netrw) Processing your write request..."
-"    call Decho("Processing your write request...",'~'.expand(""))
-   endif
-
-   ".........................................
-   " NetWrite: (rcp) NetWrite Method #1 {{{3
-   if  b:netrw_method == 1
-"    call Decho("write via rcp (method #1)",'~'.expand(""))
-    if s:netrw_has_nt_rcp == 1
-     if exists("g:netrw_uid") &&  ( g:netrw_uid != "" )
-      let uid_machine = g:netrw_machine .'.'. g:netrw_uid
-     else
-      let uid_machine = g:netrw_machine .'.'. $USERNAME
-     endif
+    " Process arguments: {{{4
+    " attempt to repeat with previous host-file-etc
+    if exists("b:netrw_lastfile") && a:0 == 0
+      "    call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand(""))
+      let choice = b:netrw_lastfile
+      let ichoice= ichoice + 1
     else
-     if exists("g:netrw_uid") &&  ( g:netrw_uid != "" )
-      let uid_machine = g:netrw_uid .'@'. g:netrw_machine
-     else
-      let uid_machine = g:netrw_machine
-     endif
-    endif
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1))
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetWrite: (ftp + <.netrc>) NetWrite Method #2 {{{3
-   elseif b:netrw_method == 2
-"    call Decho("write via ftp+.netrc (method #2)",'~'.expand(""))
-    let netrw_fname = b:netrw_fname
-
-    " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
-    let bhkeep      = &l:bh
-    let curbuf      = bufnr("%")
-    setl bh=hide
-    keepj keepalt enew
+      exe "let choice= a:" . ichoice
+
+      " Reconstruct Choice when choice starts with '"'
+      if match(choice,"?") == 0
+        echomsg 'NetWrite Usage:"'
+        echomsg ':Nwrite machine:path                        uses rcp'
+        echomsg ':Nwrite "machine path"                      uses ftp with <.netrc>'
+        echomsg ':Nwrite "machine id password path"          uses ftp'
+        echomsg ':Nwrite dav://[user@]machine/path           uses cadaver'
+        echomsg ':Nwrite fetch://[user@]machine/path         uses fetch'
+        echomsg ':Nwrite ftp://machine[#port]/path           uses ftp  (autodetects <.netrc>)'
+        echomsg ':Nwrite rcp://machine/path                  uses rcp'
+        echomsg ':Nwrite rsync://[user@]machine/path         uses rsync'
+        echomsg ':Nwrite scp://[user@]machine[[:#]port]/path uses scp'
+        echomsg ':Nwrite sftp://[user@]machine/path          uses sftp'
+        sleep 4
+        break
 
-"    call Decho("filter input window#".winnr(),'~'.expand(""))
-    setl ff=unix
-    NetrwKeepj put =g:netrw_ftpmode
-"    call Decho("filter input: ".getline('$'),'~'.expand(""))
-    if exists("g:netrw_ftpextracmd")
-     NetrwKeepj put =g:netrw_ftpextracmd
-"     call Decho("filter input: ".getline("$"),'~'.expand(""))
+      elseif match(choice,"^\"") != -1
+        if match(choice,"\"$") != -1
+          " case "..."
+          let choice=strpart(choice,1,strlen(choice)-2)
+        else
+          "  case "... ... ..."
+          let choice      = strpart(choice,1,strlen(choice)-1)
+          let wholechoice = ""
+
+          while match(choice,"\"$") == -1
+            let wholechoice= wholechoice . " " . choice
+            let ichoice    = ichoice + 1
+            if choice > a:0
+              if !exists("g:netrw_quiet")
+                call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",13)
+              endif
+              "        call Dret("netrw#NetWrite")
+              return
+            endif
+            let choice= a:{ichoice}
+          endwhile
+          let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1)
+        endif
+      endif
     endif
-    NetrwKeepj call setline(line("$")+1,'put "'.tmpfile.'" "'.netrw_fname.'"')
-"    call Decho("filter input: ".getline("$"),'~'.expand(""))
-    if exists("g:netrw_port") && g:netrw_port != ""
-     call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
-    else
-"     call Decho("filter input window#".winnr(),'~'.expand(""))
-     call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
-    endif
-    " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-    if getline(1) !~ "^$"
-     if !exists("g:netrw_quiet")
-      NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),14)
-     endif
-     let mod=1
-    endif
-
-    " remove enew buffer (quietly)
-    let filtbuf= bufnr("%")
-    exe curbuf."b!"
-    let &l:bh            = bhkeep
-    exe filtbuf."bw!"
-
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetWrite: (ftp + machine, id, passwd, filename) NetWrite Method #3 {{{3
-   elseif b:netrw_method == 3
-    " Construct execution string (three or more lines) which will be passed through filter
-"    call Decho("read via ftp+mipf (method #3)",'~'.expand(""))
-    let netrw_fname = b:netrw_fname
-    let bhkeep      = &l:bh
-
-    " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
-    let curbuf      = bufnr("%")
-    setl bh=hide
-    keepj keepalt enew
-    setl ff=unix
+    let ichoice= ichoice + 1
+    "   call Decho("choice<" . choice . "> ichoice=".ichoice,'~'.expand(""))
 
-    if exists("g:netrw_port") && g:netrw_port != ""
-     NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-"     call Decho("filter input: ".getline('.'),'~'.expand(""))
-    else
-     NetrwKeepj put ='open '.g:netrw_machine
-"     call Decho("filter input: ".getline('.'),'~'.expand(""))
-    endif
-    if exists("g:netrw_uid") && g:netrw_uid != ""
-     if exists("g:netrw_ftp") && g:netrw_ftp == 1
-      NetrwKeepj put =g:netrw_uid
-"      call Decho("filter input: ".getline('.'),'~'.expand(""))
-      if exists("s:netrw_passwd") && s:netrw_passwd != ""
-       NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
-      endif
-"      call Decho("filter input: ".getline('.'),'~'.expand(""))
-     elseif exists("s:netrw_passwd") && s:netrw_passwd != ""
-      NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
-"      call Decho("filter input: ".getline('.'),'~'.expand(""))
-     endif
-    endif
-    NetrwKeepj put =g:netrw_ftpmode
-"    call Decho("filter input: ".getline('$'),'~'.expand(""))
-    if exists("g:netrw_ftpextracmd")
-     NetrwKeepj put =g:netrw_ftpextracmd
-"     call Decho("filter input: ".getline("$"),'~'.expand(""))
+    " Determine method of write (ftp, rcp, etc) {{{4
+    NetrwKeepj call s:NetrwMethod(choice)
+    if !exists("b:netrw_method") || b:netrw_method < 0
+      "    call Dfunc("netrw#NetWrite : unsupported method")
+      return
     endif
-    NetrwKeepj put ='put \"'.tmpfile.'\" \"'.netrw_fname.'\"'
-"    call Decho("filter input: ".getline('.'),'~'.expand(""))
-    " save choice/id/password for future use
-    let b:netrw_lastfile = choice
 
-    " perform ftp:
-    " -i       : turns off interactive prompting from ftp
-    " -n  unix : DON'T use <.netrc>, even though it exists
-    " -n  win32: quit being obnoxious about password
-    NetrwKeepj norm! 1G"_dd
-    call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
-    " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-    if getline(1) !~ "^$"
-     if  !exists("g:netrw_quiet")
-      call netrw#ErrorMsg(s:ERROR,getline(1),15)
-     endif
-     let mod=1
-    endif
-
-    " remove enew buffer (quietly)
-    let filtbuf= bufnr("%")
-    exe curbuf."b!"
-    let &l:bh= bhkeep
-    exe filtbuf."bw!"
-
-   ".........................................
-   " NetWrite: (scp) NetWrite Method #4 {{{3
-   elseif     b:netrw_method == 4
-"    call Decho("write via scp (method #4)",'~'.expand(""))
-    if exists("g:netrw_port") && g:netrw_port != ""
-     let useport= " ".g:netrw_scpport." ".fnameescape(g:netrw_port)
-    else
-     let useport= ""
-    endif
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1))
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetWrite: (http) NetWrite Method #5 {{{3
-   elseif     b:netrw_method == 5
-"    call Decho("write via http (method #5)",'~'.expand(""))
-    let curl= substitute(g:netrw_http_put_cmd,'\s\+.*$',"","")
-    if executable(curl)
-     let url= g:netrw_choice
-     call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(url,1) )
-    elseif !exists("g:netrw_quiet")
-     call netrw#ErrorMsg(s:ERROR,"can't write to http using <".g:netrw_http_put_cmd.">",16)
+    " =============
+    " NetWrite: Perform Protocol-Based Write {{{3
+    " ============================
+    if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1
+      echo "(netrw) Processing your write request..."
+      "    call Decho("Processing your write request...",'~'.expand(""))
     endif
 
-   ".........................................
-   " NetWrite: (dav) NetWrite Method #6 (cadaver) {{{3
-   elseif     b:netrw_method == 6
-"    call Decho("write via cadaver (method #6)",'~'.expand(""))
+    ".........................................
+    " NetWrite: (rcp) NetWrite Method #1 {{{3
+    if  b:netrw_method == 1
+      "    call Decho("write via rcp (method #1)",'~'.expand(""))
+      if s:netrw_has_nt_rcp == 1
+        if exists("g:netrw_uid") &&  ( g:netrw_uid != "" )
+          let uid_machine = g:netrw_machine .'.'. g:netrw_uid
+        else
+          let uid_machine = g:netrw_machine .'.'. $USERNAME
+        endif
+      else
+        if exists("g:netrw_uid") &&  ( g:netrw_uid != "" )
+          let uid_machine = g:netrw_uid .'@'. g:netrw_machine
+        else
+          let uid_machine = g:netrw_machine
+        endif
+      endif
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1))
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetWrite: (ftp + <.netrc>) NetWrite Method #2 {{{3
+    elseif b:netrw_method == 2
+      "    call Decho("write via ftp+.netrc (method #2)",'~'.expand(""))
+      let netrw_fname = b:netrw_fname
+
+      " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
+      let bhkeep      = &l:bh
+      let curbuf      = bufnr("%")
+      setl bh=hide
+      keepj keepalt enew
+
+      "    call Decho("filter input window#".winnr(),'~'.expand(""))
+      setl ff=unix
+      NetrwKeepj put =g:netrw_ftpmode
+      "    call Decho("filter input: ".getline('$'),'~'.expand(""))
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "     call Decho("filter input: ".getline("$"),'~'.expand(""))
+      endif
+      NetrwKeepj call setline(line("$")+1,'put "'.tmpfile.'" "'.netrw_fname.'"')
+      "    call Decho("filter input: ".getline("$"),'~'.expand(""))
+      if exists("g:netrw_port") && g:netrw_port != ""
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
+      else
+        "     call Decho("filter input window#".winnr(),'~'.expand(""))
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
+      endif
+      " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+      if getline(1) !~ "^$"
+        if !exists("g:netrw_quiet")
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),14)
+        endif
+        let mod=1
+      endif
+
+      " remove enew buffer (quietly)
+      let filtbuf= bufnr("%")
+      exe curbuf."b!"
+      let &l:bh            = bhkeep
+      exe filtbuf."bw!"
 
-    " Construct execution string (four lines) which will be passed through filter
-    let netrw_fname = escape(b:netrw_fname,g:netrw_fname_escape)
-    let bhkeep      = &l:bh
+      let b:netrw_lastfile = choice
 
-    " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
-    let curbuf      = bufnr("%")
-    setl bh=hide
-    keepj keepalt enew
+      ".........................................
+      " NetWrite: (ftp + machine, id, passwd, filename) NetWrite Method #3 {{{3
+    elseif b:netrw_method == 3
+      " Construct execution string (three or more lines) which will be passed through filter
+      "    call Decho("read via ftp+mipf (method #3)",'~'.expand(""))
+      let netrw_fname = b:netrw_fname
+      let bhkeep      = &l:bh
+
+      " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
+      let curbuf      = bufnr("%")
+      setl bh=hide
+      keepj keepalt enew
+      setl ff=unix
+
+      if exists("g:netrw_port") && g:netrw_port != ""
+        NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      else
+        NetrwKeepj put ='open '.g:netrw_machine
+        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      endif
+      if exists("g:netrw_uid") && g:netrw_uid != ""
+        if exists("g:netrw_ftp") && g:netrw_ftp == 1
+          NetrwKeepj put =g:netrw_uid
+          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+          if exists("s:netrw_passwd") && s:netrw_passwd != ""
+            NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
+          endif
+          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+        elseif exists("s:netrw_passwd") && s:netrw_passwd != ""
+          NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+        endif
+      endif
+      NetrwKeepj put =g:netrw_ftpmode
+      "    call Decho("filter input: ".getline('$'),'~'.expand(""))
+      if exists("g:netrw_ftpextracmd")
+        NetrwKeepj put =g:netrw_ftpextracmd
+        "     call Decho("filter input: ".getline("$"),'~'.expand(""))
+      endif
+      NetrwKeepj put ='put \"'.tmpfile.'\" \"'.netrw_fname.'\"'
+      "    call Decho("filter input: ".getline('.'),'~'.expand(""))
+      " save choice/id/password for future use
+      let b:netrw_lastfile = choice
+
+      " perform ftp:
+      " -i       : turns off interactive prompting from ftp
+      " -n  unix : DON'T use <.netrc>, even though it exists
+      " -n  win32: quit being obnoxious about password
+      NetrwKeepj norm! 1G"_dd
+      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
+      " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+      if getline(1) !~ "^$"
+        if  !exists("g:netrw_quiet")
+          call netrw#ErrorMsg(s:ERROR,getline(1),15)
+        endif
+        let mod=1
+      endif
 
-    setl ff=unix
-    if exists("g:netrw_port") && g:netrw_port != ""
-     NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-    else
-     NetrwKeepj put ='open '.g:netrw_machine
-    endif
-    if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != ""
-     NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
-    endif
-    NetrwKeepj put ='put '.tmpfile.' '.netrw_fname
-
-    " perform cadaver operation:
-    NetrwKeepj norm! 1G"_dd
-    call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd)
-
-    " remove enew buffer (quietly)
-    let filtbuf= bufnr("%")
-    exe curbuf."b!"
-    let &l:bh            = bhkeep
-    exe filtbuf."bw!"
-
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetWrite: (rsync) NetWrite Method #7 {{{3
-   elseif     b:netrw_method == 7
-"    call Decho("write via rsync (method #7)",'~'.expand(""))
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1))
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetWrite: (sftp) NetWrite Method #9 {{{3
-   elseif     b:netrw_method == 9
-"    call Decho("write via sftp (method #9)",'~'.expand(""))
-    let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
-    if exists("g:netrw_uid") &&  ( g:netrw_uid != "" )
-     let uid_machine = g:netrw_uid .'@'. g:netrw_machine
-    else
-     let uid_machine = g:netrw_machine
-    endif
+      " remove enew buffer (quietly)
+      let filtbuf= bufnr("%")
+      exe curbuf."b!"
+      let &l:bh= bhkeep
+      exe filtbuf."bw!"
+
+      ".........................................
+      " NetWrite: (scp) NetWrite Method #4 {{{3
+    elseif     b:netrw_method == 4
+      "    call Decho("write via scp (method #4)",'~'.expand(""))
+      if exists("g:netrw_port") && g:netrw_port != ""
+        let useport= " ".g:netrw_scpport." ".fnameescape(g:netrw_port)
+      else
+        let useport= ""
+      endif
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1))
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetWrite: (http) NetWrite Method #5 {{{3
+    elseif     b:netrw_method == 5
+      "    call Decho("write via http (method #5)",'~'.expand(""))
+      let curl= substitute(g:netrw_http_put_cmd,'\s\+.*$',"","")
+      if executable(curl)
+        let url= g:netrw_choice
+        call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(url,1) )
+      elseif !exists("g:netrw_quiet")
+        call netrw#ErrorMsg(s:ERROR,"can't write to http using <".g:netrw_http_put_cmd.">",16)
+      endif
 
-    " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
-    let bhkeep = &l:bh
-    let curbuf = bufnr("%")
-    setl bh=hide
-    keepj keepalt enew
+      ".........................................
+      " NetWrite: (dav) NetWrite Method #6 (cadaver) {{{3
+    elseif     b:netrw_method == 6
+      "    call Decho("write via cadaver (method #6)",'~'.expand(""))
 
-    setl ff=unix
-    call setline(1,'put "'.escape(tmpfile,'\').'" '.netrw_fname)
-"    call Decho("filter input: ".getline('.'),'~'.expand(""))
-    let sftpcmd= substitute(g:netrw_sftp_cmd,"%TEMPFILE%",escape(tmpfile,'\'),"g")
-    call s:NetrwExe(s:netrw_silentxfer."%!".sftpcmd.' '.s:ShellEscape(uid_machine,1))
-    let filtbuf= bufnr("%")
-    exe curbuf."b!"
-    let &l:bh            = bhkeep
-    exe filtbuf."bw!"
-    let b:netrw_lastfile = choice
-
-   ".........................................
-   " NetWrite: Complain {{{3
-   else
-    call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",17)
-    let leavemod= 1
-   endif
+      " Construct execution string (four lines) which will be passed through filter
+      let netrw_fname = escape(b:netrw_fname,g:netrw_fname_escape)
+      let bhkeep      = &l:bh
+
+      " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
+      let curbuf      = bufnr("%")
+      setl bh=hide
+      keepj keepalt enew
+
+      setl ff=unix
+      if exists("g:netrw_port") && g:netrw_port != ""
+        NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+      else
+        NetrwKeepj put ='open '.g:netrw_machine
+      endif
+      if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != ""
+        NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
+      endif
+      NetrwKeepj put ='put '.tmpfile.' '.netrw_fname
+
+      " perform cadaver operation:
+      NetrwKeepj norm! 1G"_dd
+      call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd)
+
+      " remove enew buffer (quietly)
+      let filtbuf= bufnr("%")
+      exe curbuf."b!"
+      let &l:bh            = bhkeep
+      exe filtbuf."bw!"
+
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetWrite: (rsync) NetWrite Method #7 {{{3
+    elseif     b:netrw_method == 7
+      "    call Decho("write via rsync (method #7)",'~'.expand(""))
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1))
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetWrite: (sftp) NetWrite Method #9 {{{3
+    elseif     b:netrw_method == 9
+      "    call Decho("write via sftp (method #9)",'~'.expand(""))
+      let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
+      if exists("g:netrw_uid") &&  ( g:netrw_uid != "" )
+        let uid_machine = g:netrw_uid .'@'. g:netrw_machine
+      else
+        let uid_machine = g:netrw_machine
+      endif
+
+      " formerly just a "new...bd!", that changed the window sizes when equalalways.  Using enew workaround instead
+      let bhkeep = &l:bh
+      let curbuf = bufnr("%")
+      setl bh=hide
+      keepj keepalt enew
+
+      setl ff=unix
+      call setline(1,'put "'.escape(tmpfile,'\').'" '.netrw_fname)
+      "    call Decho("filter input: ".getline('.'),'~'.expand(""))
+      let sftpcmd= substitute(g:netrw_sftp_cmd,"%TEMPFILE%",escape(tmpfile,'\'),"g")
+      call s:NetrwExe(s:netrw_silentxfer."%!".sftpcmd.' '.s:ShellEscape(uid_machine,1))
+      let filtbuf= bufnr("%")
+      exe curbuf."b!"
+      let &l:bh            = bhkeep
+      exe filtbuf."bw!"
+      let b:netrw_lastfile = choice
+
+      ".........................................
+      " NetWrite: Complain {{{3
+    else
+      call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",17)
+      let leavemod= 1
+    endif
   endwhile
 
   " NetWrite: Cleanup: {{{3
-"  call Decho("cleanup",'~'.expand(""))
+  "  call Decho("cleanup",'~'.expand(""))
   if s:FileReadable(tmpfile)
-"   call Decho("tmpfile<".tmpfile."> readable, will now delete it",'~'.expand(""))
-   call s:NetrwDelete(tmpfile)
+    "   call Decho("tmpfile<".tmpfile."> readable, will now delete it",'~'.expand(""))
+    call s:NetrwDelete(tmpfile)
   endif
   call s:NetrwOptionsRestore("w:")
 
   if a:firstline == 1 && a:lastline == line("$")
-   " restore modifiability; usually equivalent to set nomod
-   let &l:mod= mod
-"   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    " restore modifiability; usually equivalent to set nomod
+    let &l:mod= mod
+    "   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
   elseif !exists("leavemod")
-   " indicate that the buffer has not been modified since last written
-"   call Decho("set nomod",'~'.expand(""))
-   setl nomod
-"   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    " indicate that the buffer has not been modified since last written
+    "   call Decho("set nomod",'~'.expand(""))
+    setl nomod
+    "   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
   endif
 
-"  call Dret("netrw#NetWrite")
+  "  call Dret("netrw#NetWrite")
 endfun
 
 " ---------------------------------------------------------------------
@@ -2716,39 +2717,39 @@ endfun
 "              then sources that file,
 "              then removes that file.
 fun! netrw#NetSource(...)
-"  call Dfunc("netrw#NetSource() a:0=".a:0)
+  "  call Dfunc("netrw#NetSource() a:0=".a:0)
   if a:0 > 0 && a:1 == '?'
-   " give help
-   echomsg 'NetSource Usage:'
-   echomsg ':Nsource dav://machine[:port]/path            uses cadaver'
-   echomsg ':Nsource fetch://machine/path                 uses fetch'
-   echomsg ':Nsource ftp://[user@]machine[:port]/path     uses ftp   autodetects <.netrc>'
-   echomsg ':Nsource http[s]://[user@]machine/path        uses http  wget'
-   echomsg ':Nsource rcp://[user@]machine/path            uses rcp'
-   echomsg ':Nsource rsync://machine[:port]/path          uses rsync'
-   echomsg ':Nsource scp://[user@]machine[[:#]port]/path  uses scp'
-   echomsg ':Nsource sftp://[user@]machine[[:#]port]/path uses sftp'
-   sleep 4
-  else
-   let i= 1
-   while i <= a:0
-    call netrw#NetRead(3,a:{i})
-"    call Decho("s:netread_tmpfile<".s:netrw_tmpfile.">",'~'.expand(""))
-    if s:FileReadable(s:netrw_tmpfile)
-"     call Decho("exe so ".fnameescape(s:netrw_tmpfile),'~'.expand(""))
-     exe "so ".fnameescape(s:netrw_tmpfile)
-"     call Decho("delete(".s:netrw_tmpfile.")",'~'.expand(""))
-     if delete(s:netrw_tmpfile)
-      call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".s:netrw_tmpfile.">!",103)
-     endif
-     unlet s:netrw_tmpfile
-    else
-     call netrw#ErrorMsg(s:ERROR,"unable to source <".a:{i}.">!",48)
-    endif
-    let i= i + 1
-   endwhile
+    " give help
+    echomsg 'NetSource Usage:'
+    echomsg ':Nsource dav://machine[:port]/path            uses cadaver'
+    echomsg ':Nsource fetch://machine/path                 uses fetch'
+    echomsg ':Nsource ftp://[user@]machine[:port]/path     uses ftp   autodetects <.netrc>'
+    echomsg ':Nsource http[s]://[user@]machine/path        uses http  wget'
+    echomsg ':Nsource rcp://[user@]machine/path            uses rcp'
+    echomsg ':Nsource rsync://machine[:port]/path          uses rsync'
+    echomsg ':Nsource scp://[user@]machine[[:#]port]/path  uses scp'
+    echomsg ':Nsource sftp://[user@]machine[[:#]port]/path uses sftp'
+    sleep 4
+  else
+    let i= 1
+    while i <= a:0
+      call netrw#NetRead(3,a:{i})
+      "    call Decho("s:netread_tmpfile<".s:netrw_tmpfile.">",'~'.expand(""))
+      if s:FileReadable(s:netrw_tmpfile)
+        "     call Decho("exe so ".fnameescape(s:netrw_tmpfile),'~'.expand(""))
+        exe "so ".fnameescape(s:netrw_tmpfile)
+        "     call Decho("delete(".s:netrw_tmpfile.")",'~'.expand(""))
+        if delete(s:netrw_tmpfile)
+          call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".s:netrw_tmpfile.">!",103)
+        endif
+        unlet s:netrw_tmpfile
+      else
+        call netrw#ErrorMsg(s:ERROR,"unable to source <".a:{i}.">!",48)
+      endif
+      let i= i + 1
+    endwhile
   endif
-"  call Dret("netrw#NetSource")
+  "  call Dret("netrw#NetSource")
 endfun
 
 " ---------------------------------------------------------------------
@@ -2760,31 +2761,31 @@ fun! netrw#SetTreetop(iscmd,...)
   " iscmd==1: netrw#SetTreetop called using :Ntree from the command line
   " clear out the current tree
   if exists("w:netrw_treetop")
-   let inittreetop= w:netrw_treetop
-   unlet w:netrw_treetop
+    let inittreetop= w:netrw_treetop
+    unlet w:netrw_treetop
   endif
   if exists("w:netrw_treedict")
-   unlet w:netrw_treedict
+    unlet w:netrw_treedict
   endif
 
   if (a:iscmd == 0 || a:1 == "") && exists("inittreetop")
-   let treedir         = s:NetrwTreePath(inittreetop)
-  else
-   if isdirectory(s:NetrwFile(a:1))
-    let treedir         = a:1
-    let s:netrw_treetop = treedir
-   elseif exists("b:netrw_curdir") && (isdirectory(s:NetrwFile(b:netrw_curdir."/".a:1)) || a:1 =~ '^\a\{3,}://')
-    let treedir         = b:netrw_curdir."/".a:1
-    let s:netrw_treetop = treedir
-   else
-    " normally the cursor is left in the message window.
-    " However, here this results in the directory being listed in the message window, which is not wanted.
-    let netrwbuf= bufnr("%")
-    call netrw#ErrorMsg(s:ERROR,"sorry, ".a:1." doesn't seem to be a directory!",95)
-    exe bufwinnr(netrwbuf)."wincmd w"
-    let treedir         = "."
-    let s:netrw_treetop = getcwd()
-   endif
+    let treedir         = s:NetrwTreePath(inittreetop)
+  else
+    if isdirectory(s:NetrwFile(a:1))
+      let treedir         = a:1
+      let s:netrw_treetop = treedir
+    elseif exists("b:netrw_curdir") && (isdirectory(s:NetrwFile(b:netrw_curdir."/".a:1)) || a:1 =~ '^\a\{3,}://')
+      let treedir         = b:netrw_curdir."/".a:1
+      let s:netrw_treetop = treedir
+    else
+      " normally the cursor is left in the message window.
+      " However, here this results in the directory being listed in the message window, which is not wanted.
+      let netrwbuf= bufnr("%")
+      call netrw#ErrorMsg(s:ERROR,"sorry, ".a:1." doesn't seem to be a directory!",95)
+      exe bufwinnr(netrwbuf)."wincmd w"
+      let treedir         = "."
+      let s:netrw_treetop = getcwd()
+    endif
   endif
 
   " determine if treedir is remote or local
@@ -2792,9 +2793,9 @@ fun! netrw#SetTreetop(iscmd,...)
 
   " browse the resulting directory
   if islocal
-   call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(islocal,treedir,0))
+    call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(islocal,treedir,0))
   else
-   call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,treedir,0))
+    call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,treedir,0))
   endif
 
 endfun
@@ -2806,125 +2807,125 @@ endfun
 "            == r  : read file after current line
 "            == t  : leave file in temporary form (ie. don't read into buffer)
 fun! s:NetrwGetFile(readcmd, tfile, method)
-"  call Dfunc("NetrwGetFile(readcmd<".a:readcmd.">,tfile<".a:tfile."> method<".a:method.">)")
+  "  call Dfunc("NetrwGetFile(readcmd<".a:readcmd.">,tfile<".a:tfile."> method<".a:method.">)")
 
   " readcmd=='t': simply do nothing
   if a:readcmd == 't'
-"   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-"   call Dret("NetrwGetFile : skip read of tfile<".a:tfile.">")
-   return
+    "   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    "   call Dret("NetrwGetFile : skip read of tfile<".a:tfile.">")
+    return
   endif
 
   " get name of remote filename (ie. url and all)
   let rfile= bufname("%")
-"  call Decho("rfile<".rfile.">",'~'.expand(""))
+  "  call Decho("rfile<".rfile.">",'~'.expand(""))
 
   if exists("*NetReadFixup")
-   " for the use of NetReadFixup (not otherwise used internally)
-   let line2= line("$")
+    " for the use of NetReadFixup (not otherwise used internally)
+    let line2= line("$")
   endif
 
   if a:readcmd[0] == '%'
-  " get file into buffer
-"   call Decho("get file into buffer",'~'.expand(""))
-
-   " rename the current buffer to the temp file (ie. tfile)
-   if g:netrw_cygwin
-    let tfile= substitute(a:tfile,g:netrw_cygdrive.'/\(.\)','\1:','')
-   else
-    let tfile= a:tfile
-   endif
-   call s:NetrwBufRename(tfile)
-
-   " edit temporary file (ie. read the temporary file in)
-   if     rfile =~ '\.zip$'
-"    call Decho("handling remote zip file with zip#Browse(tfile<".tfile.">)",'~'.expand(""))
-    call zip#Browse(tfile)
-   elseif rfile =~ '\.tar$'
-"    call Decho("handling remote tar file with tar#Browse(tfile<".tfile.">)",'~'.expand(""))
-    call tar#Browse(tfile)
-   elseif rfile =~ '\.tar\.gz$'
-"    call Decho("handling remote gzip-compressed tar file",'~'.expand(""))
-    call tar#Browse(tfile)
-   elseif rfile =~ '\.tar\.bz2$'
-"    call Decho("handling remote bz2-compressed tar file",'~'.expand(""))
-    call tar#Browse(tfile)
-   elseif rfile =~ '\.tar\.xz$'
-"    call Decho("handling remote xz-compressed tar file",'~'.expand(""))
-    call tar#Browse(tfile)
-   elseif rfile =~ '\.txz$'
-"    call Decho("handling remote xz-compressed tar file (.txz)",'~'.expand(""))
-    call tar#Browse(tfile)
-   else
-"    call Decho("edit temporary file",'~'.expand(""))
-    NetrwKeepj e!
-   endif
-
-   " rename buffer back to remote filename
-   call s:NetrwBufRename(rfile)
-
-   " Jan 19, 2022: COMBAK -- bram problem with https://github.com/vim/vim/pull/9554.diff filetype
-   " Detect filetype of local version of remote file.
-   " Note that isk must not include a "/" for scripts.vim
-   " to process this detection correctly.
-"   call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand(""))
-"   call Decho("..did_filetype()=".did_filetype())
-"   setl ft=
-"   call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
-   let iskkeep= &isk
-   setl isk-=/
-   filetype detect
-"   call Decho("..local filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
-   let &l:isk= iskkeep
-"   call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)")
-   let line1 = 1
-   let line2 = line("$")
+    " get file into buffer
+    "   call Decho("get file into buffer",'~'.expand(""))
+
+    " rename the current buffer to the temp file (ie. tfile)
+    if g:netrw_cygwin
+      let tfile= substitute(a:tfile,g:netrw_cygdrive.'/\(.\)','\1:','')
+    else
+      let tfile= a:tfile
+    endif
+    call s:NetrwBufRename(tfile)
+
+    " edit temporary file (ie. read the temporary file in)
+    if     rfile =~ '\.zip$'
+      "    call Decho("handling remote zip file with zip#Browse(tfile<".tfile.">)",'~'.expand(""))
+      call zip#Browse(tfile)
+    elseif rfile =~ '\.tar$'
+      "    call Decho("handling remote tar file with tar#Browse(tfile<".tfile.">)",'~'.expand(""))
+      call tar#Browse(tfile)
+    elseif rfile =~ '\.tar\.gz$'
+      "    call Decho("handling remote gzip-compressed tar file",'~'.expand(""))
+      call tar#Browse(tfile)
+    elseif rfile =~ '\.tar\.bz2$'
+      "    call Decho("handling remote bz2-compressed tar file",'~'.expand(""))
+      call tar#Browse(tfile)
+    elseif rfile =~ '\.tar\.xz$'
+      "    call Decho("handling remote xz-compressed tar file",'~'.expand(""))
+      call tar#Browse(tfile)
+    elseif rfile =~ '\.txz$'
+      "    call Decho("handling remote xz-compressed tar file (.txz)",'~'.expand(""))
+      call tar#Browse(tfile)
+    else
+      "    call Decho("edit temporary file",'~'.expand(""))
+      NetrwKeepj e!
+    endif
+
+    " rename buffer back to remote filename
+    call s:NetrwBufRename(rfile)
+
+    " Jan 19, 2022: COMBAK -- bram problem with https://github.com/vim/vim/pull/9554.diff filetype
+    " Detect filetype of local version of remote file.
+    " Note that isk must not include a "/" for scripts.vim
+    " to process this detection correctly.
+    "   call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand(""))
+    "   call Decho("..did_filetype()=".did_filetype())
+    "   setl ft=
+    "   call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
+    let iskkeep= &isk
+    setl isk-=/
+    filetype detect
+    "   call Decho("..local filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
+    let &l:isk= iskkeep
+    "   call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)")
+    let line1 = 1
+    let line2 = line("$")
 
   elseif !&ma
-   " attempting to read a file after the current line in the file, but the buffer is not modifiable
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"attempt to read<".a:tfile."> into a non-modifiable buffer!",94)
-"   call Dret("NetrwGetFile : attempt to read<".a:tfile."> into a non-modifiable buffer!")
-   return
+    " attempting to read a file after the current line in the file, but the buffer is not modifiable
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"attempt to read<".a:tfile."> into a non-modifiable buffer!",94)
+    "   call Dret("NetrwGetFile : attempt to read<".a:tfile."> into a non-modifiable buffer!")
+    return
 
   elseif s:FileReadable(a:tfile)
-   " read file after current line
-"   call Decho("read file<".a:tfile."> after current line",'~'.expand(""))
-   let curline = line(".")
-   let lastline= line("$")
-"   call Decho("exe<".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile).">  line#".curline,'~'.expand(""))
-   exe "NetrwKeepj ".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)
-   let line1= curline + 1
-   let line2= line("$") - lastline + 1
-
-  else
-   " not readable
-"   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-"   call Decho("tfile<".a:tfile."> not readable",'~'.expand(""))
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"file <".a:tfile."> not readable",9)
-"   call Dret("NetrwGetFile : tfile<".a:tfile."> not readable")
-   return
+    " read file after current line
+    "   call Decho("read file<".a:tfile."> after current line",'~'.expand(""))
+    let curline = line(".")
+    let lastline= line("$")
+    "   call Decho("exe<".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile).">  line#".curline,'~'.expand(""))
+    exe "NetrwKeepj ".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)
+    let line1= curline + 1
+    let line2= line("$") - lastline + 1
+
+  else
+    " not readable
+    "   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    "   call Decho("tfile<".a:tfile."> not readable",'~'.expand(""))
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"file <".a:tfile."> not readable",9)
+    "   call Dret("NetrwGetFile : tfile<".a:tfile."> not readable")
+    return
   endif
 
   " User-provided (ie. optional) fix-it-up command
   if exists("*NetReadFixup")
-"   call Decho("calling NetReadFixup(method<".a:method."> line1=".line1." line2=".line2.")",'~'.expand(""))
-   NetrwKeepj call NetReadFixup(a:method, line1, line2)
-"  else " Decho
-"   call Decho("NetReadFixup() not called, doesn't exist  (line1=".line1." line2=".line2.")",'~'.expand(""))
+    "   call Decho("calling NetReadFixup(method<".a:method."> line1=".line1." line2=".line2.")",'~'.expand(""))
+    NetrwKeepj call NetReadFixup(a:method, line1, line2)
+    "  else " Decho
+    "   call Decho("NetReadFixup() not called, doesn't exist  (line1=".line1." line2=".line2.")",'~'.expand(""))
   endif
 
   if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
-   " update the Buffers menu
-   NetrwKeepj call s:UpdateBuffersMenu()
+    " update the Buffers menu
+    NetrwKeepj call s:UpdateBuffersMenu()
   endif
 
-"  call Decho("readcmd<".a:readcmd."> cmdarg<".v:cmdarg."> tfile<".a:tfile."> readable=".s:FileReadable(a:tfile),'~'.expand(""))
+  "  call Decho("readcmd<".a:readcmd."> cmdarg<".v:cmdarg."> tfile<".a:tfile."> readable=".s:FileReadable(a:tfile),'~'.expand(""))
 
- " make sure file is being displayed
-"  redraw!
+  " make sure file is being displayed
+  "  redraw!
 
-"  call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-"  call Dret("NetrwGetFile")
+  "  call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+  "  call Dret("NetrwGetFile")
 endfun
 
 " ------------------------------------------------------------------------
@@ -2934,44 +2935,44 @@ endfun
 " Output:
 "  b:netrw_method= 1: rcp
 "                  2: ftp + <.netrc>
-"	           3: ftp + machine, id, password, and [path]filename
-"	           4: scp
-"	           5: http[s] (wget)
-"	           6: dav
-"	           7: rsync
-"	           8: fetch
-"	           9: sftp
-"	          10: file
+"                  3: ftp + machine, id, password, and [path]filename
+"                  4: scp
+"                  5: http[s] (wget)
+"                  6: dav
+"                  7: rsync
+"                  8: fetch
+"                  9: sftp
+"                 10: file
 "  g:netrw_machine= hostname
 "  b:netrw_fname  = filename
 "  g:netrw_port   = optional port number (for ftp)
 "  g:netrw_choice = copy of input url (choice)
 fun! s:NetrwMethod(choice)
-"   call Dfunc("s:NetrwMethod(a:choice<".a:choice.">)")
+  "   call Dfunc("s:NetrwMethod(a:choice<".a:choice.">)")
 
-   " sanity check: choice should have at least three slashes in it
-   if strlen(substitute(a:choice,'[^/]','','g')) < 3
+  " sanity check: choice should have at least three slashes in it
+  if strlen(substitute(a:choice,'[^/]','','g')) < 3
     call netrw#ErrorMsg(s:ERROR,"not a netrw-style url; netrw uses protocol://[user@]hostname[:port]/[path])",78)
     let b:netrw_method = -1
-"    call Dret("s:NetrwMethod : incorrect url format<".a:choice.">")
+    "    call Dret("s:NetrwMethod : incorrect url format<".a:choice.">")
     return
-   endif
+  endif
 
-   " record current g:netrw_machine, if any
-   " curmachine used if protocol == ftp and no .netrc
-   if exists("g:netrw_machine")
+  " record current g:netrw_machine, if any
+  " curmachine used if protocol == ftp and no .netrc
+  if exists("g:netrw_machine")
     let curmachine= g:netrw_machine
-"    call Decho("curmachine<".curmachine.">",'~'.expand(""))
-   else
+    "    call Decho("curmachine<".curmachine.">",'~'.expand(""))
+  else
     let curmachine= "N O T A HOST"
-   endif
-   if exists("g:netrw_port")
+  endif
+  if exists("g:netrw_port")
     let netrw_port= g:netrw_port
-   endif
+  endif
 
-   " insure that netrw_ftp_cmd starts off every method determination
-   " with the current g:netrw_ftp_cmd
-   let s:netrw_ftp_cmd= g:netrw_ftp_cmd
+  " insure that netrw_ftp_cmd starts off every method determination
+  " with the current g:netrw_ftp_cmd
+  let s:netrw_ftp_cmd= g:netrw_ftp_cmd
 
   " initialization
   let b:netrw_method  = 0
@@ -2981,18 +2982,18 @@ fun! s:NetrwMethod(choice)
   let g:netrw_choice  = a:choice
 
   " Patterns:
-  " mipf     : a:machine a:id password filename	     Use ftp
-  " mf	    : a:machine filename		     Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd
+  " mipf     : a:machine a:id password filename      Use ftp
+  " mf      : a:machine filename                     Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd
   " ftpurm   : ftp://[user@]host[[#:]port]/filename  Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd
-  " rcpurm   : rcp://[user@]host/filename	     Use rcp
-  " rcphf    : [user@]host:filename		     Use rcp
+  " rcpurm   : rcp://[user@]host/filename            Use rcp
+  " rcphf    : [user@]host:filename                  Use rcp
   " scpurm   : scp://[user@]host[[#:]port]/filename  Use scp
-  " httpurm  : http[s]://[user@]host/filename	     Use wget
+  " httpurm  : http[s]://[user@]host/filename        Use wget
   " davurm   : dav[s]://host[:port]/path             Use cadaver/curl
   " rsyncurm : rsync://host[:port]/path              Use rsync
   " fetchurm : fetch://[user@]host[:http]/filename   Use fetch (defaults to ftp, override for http)
   " sftpurm  : sftp://[user@]host/filename  Use scp
-  " fileurm  : file://[user@]host/filename	     Use elinks or links
+  " fileurm  : file://[user@]host/filename           Use elinks or links
   let mipf     = '^\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)$'
   let mf       = '^\(\S\+\)\s\+\(\S\+\)$'
   let ftpurm   = '^ftp://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\([#:]\d\+\)\=/\(.*\)$'
@@ -3006,306 +3007,306 @@ fun! s:NetrwMethod(choice)
   let sftpurm  = '^sftp://\([^/]\{-}\)/\(.*\)\=$'
   let fileurm  = '^file\=://\(.*\)$'
 
-"  call Decho("determine method:",'~'.expand(""))
+  "  call Decho("determine method:",'~'.expand(""))
   " Determine Method
   " Method#1: rcp://user@hostname/...path-to-file {{{3
   if match(a:choice,rcpurm) == 0
-"   call Decho("rcp://...",'~'.expand(""))
-   let b:netrw_method  = 1
-   let userid          = substitute(a:choice,rcpurm,'\1',"")
-   let g:netrw_machine = substitute(a:choice,rcpurm,'\2',"")
-   let b:netrw_fname   = substitute(a:choice,rcpurm,'\3',"")
-   if userid != ""
-    let g:netrw_uid= userid
-   endif
-
-  " Method#4: scp://user@hostname/...path-to-file {{{3
+    "   call Decho("rcp://...",'~'.expand(""))
+    let b:netrw_method  = 1
+    let userid          = substitute(a:choice,rcpurm,'\1',"")
+    let g:netrw_machine = substitute(a:choice,rcpurm,'\2',"")
+    let b:netrw_fname   = substitute(a:choice,rcpurm,'\3',"")
+    if userid != ""
+      let g:netrw_uid= userid
+    endif
+
+    " Method#4: scp://user@hostname/...path-to-file {{{3
   elseif match(a:choice,scpurm) == 0
-"   call Decho("scp://...",'~'.expand(""))
-   let b:netrw_method  = 4
-   let g:netrw_machine = substitute(a:choice,scpurm,'\1',"")
-   let g:netrw_port    = substitute(a:choice,scpurm,'\2',"")
-   let b:netrw_fname   = substitute(a:choice,scpurm,'\3',"")
+    "   call Decho("scp://...",'~'.expand(""))
+    let b:netrw_method  = 4
+    let g:netrw_machine = substitute(a:choice,scpurm,'\1',"")
+    let g:netrw_port    = substitute(a:choice,scpurm,'\2',"")
+    let b:netrw_fname   = substitute(a:choice,scpurm,'\3',"")
 
-  " Method#5: http[s]://user@hostname/...path-to-file {{{3
+    " Method#5: http[s]://user@hostname/...path-to-file {{{3
   elseif match(a:choice,httpurm) == 0
-"   call Decho("http[s]://...",'~'.expand(""))
-   let b:netrw_method = 5
-   let g:netrw_machine= substitute(a:choice,httpurm,'\1',"")
-   let b:netrw_fname  = substitute(a:choice,httpurm,'\2',"")
-   let b:netrw_http   = (a:choice =~ '^https:')? "https" : "http"
+    "   call Decho("http[s]://...",'~'.expand(""))
+    let b:netrw_method = 5
+    let g:netrw_machine= substitute(a:choice,httpurm,'\1',"")
+    let b:netrw_fname  = substitute(a:choice,httpurm,'\2',"")
+    let b:netrw_http   = (a:choice =~ '^https:')? "https" : "http"
 
-  " Method#6: dav://hostname[:port]/..path-to-file.. {{{3
+    " Method#6: dav://hostname[:port]/..path-to-file.. {{{3
   elseif match(a:choice,davurm) == 0
-"   call Decho("dav://...",'~'.expand(""))
-   let b:netrw_method= 6
-   if a:choice =~ 'davs:'
-    let g:netrw_machine= 'https://'.substitute(a:choice,davurm,'\1/\2',"")
-   else
-    let g:netrw_machine= 'http://'.substitute(a:choice,davurm,'\1/\2',"")
-   endif
-   let b:netrw_fname  = substitute(a:choice,davurm,'\3',"")
-
-   " Method#7: rsync://user@hostname/...path-to-file {{{3
+    "   call Decho("dav://...",'~'.expand(""))
+    let b:netrw_method= 6
+    if a:choice =~ 'davs:'
+      let g:netrw_machine= 'https://'.substitute(a:choice,davurm,'\1/\2',"")
+    else
+      let g:netrw_machine= 'http://'.substitute(a:choice,davurm,'\1/\2',"")
+    endif
+    let b:netrw_fname  = substitute(a:choice,davurm,'\3',"")
+
+    " Method#7: rsync://user@hostname/...path-to-file {{{3
   elseif match(a:choice,rsyncurm) == 0
-"   call Decho("rsync://...",'~'.expand(""))
-   let b:netrw_method = 7
-   let g:netrw_machine= substitute(a:choice,rsyncurm,'\1',"")
-   let b:netrw_fname  = substitute(a:choice,rsyncurm,'\2',"")
+    "   call Decho("rsync://...",'~'.expand(""))
+    let b:netrw_method = 7
+    let g:netrw_machine= substitute(a:choice,rsyncurm,'\1',"")
+    let b:netrw_fname  = substitute(a:choice,rsyncurm,'\2',"")
 
-   " Methods 2,3: ftp://[user@]hostname[[:#]port]/...path-to-file {{{3
+    " Methods 2,3: ftp://[user@]hostname[[:#]port]/...path-to-file {{{3
   elseif match(a:choice,ftpurm) == 0
-"   call Decho("ftp://...",'~'.expand(""))
-   let userid	      = substitute(a:choice,ftpurm,'\2',"")
-   let g:netrw_machine= substitute(a:choice,ftpurm,'\3',"")
-   let g:netrw_port   = substitute(a:choice,ftpurm,'\4',"")
-   let b:netrw_fname  = substitute(a:choice,ftpurm,'\5',"")
-"   call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand(""))
-   if userid != ""
-    let g:netrw_uid= userid
-   endif
-
-   if curmachine != g:netrw_machine
-    if exists("s:netrw_hup[".g:netrw_machine."]")
-     call NetUserPass("ftp:".g:netrw_machine)
-    elseif exists("s:netrw_passwd")
-     " if there's a change in hostname, require password re-entry
-     unlet s:netrw_passwd
-    endif
-    if exists("netrw_port")
-     unlet netrw_port
-    endif
-   endif
-
-   if exists("g:netrw_uid") && exists("s:netrw_passwd")
-    let b:netrw_method = 3
-   else
-    let host= substitute(g:netrw_machine,'\..*$','','')
-    if exists("s:netrw_hup[host]")
-     call NetUserPass("ftp:".host)
-
-    elseif has("win32") && s:netrw_ftp_cmd =~# '-[sS]:'
-"     call Decho("has -s: : s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand(""))
-"     call Decho("          g:netrw_ftp_cmd<".g:netrw_ftp_cmd.">",'~'.expand(""))
-     if g:netrw_ftp_cmd =~# '-[sS]:\S*MACHINE\>'
-      let s:netrw_ftp_cmd= substitute(g:netrw_ftp_cmd,'\',g:netrw_machine,'')
-"      call Decho("s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand(""))
-     endif
-     let b:netrw_method= 2
-    elseif s:FileReadable(expand("$HOME/.netrc")) && !g:netrw_ignorenetrc
-"     call Decho("using <".expand("$HOME/.netrc")."> (readable)",'~'.expand(""))
-     let b:netrw_method= 2
+    "   call Decho("ftp://...",'~'.expand(""))
+    let userid         = substitute(a:choice,ftpurm,'\2',"")
+    let g:netrw_machine= substitute(a:choice,ftpurm,'\3',"")
+    let g:netrw_port   = substitute(a:choice,ftpurm,'\4',"")
+    let b:netrw_fname  = substitute(a:choice,ftpurm,'\5',"")
+    "   call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand(""))
+    if userid != ""
+      let g:netrw_uid= userid
+    endif
+
+    if curmachine != g:netrw_machine
+      if exists("s:netrw_hup[".g:netrw_machine."]")
+        call NetUserPass("ftp:".g:netrw_machine)
+      elseif exists("s:netrw_passwd")
+        " if there's a change in hostname, require password re-entry
+        unlet s:netrw_passwd
+      endif
+      if exists("netrw_port")
+        unlet netrw_port
+      endif
+    endif
+
+    if exists("g:netrw_uid") && exists("s:netrw_passwd")
+      let b:netrw_method = 3
     else
-     if !exists("g:netrw_uid") || g:netrw_uid == ""
-      call NetUserPass()
-     elseif !exists("s:netrw_passwd") || s:netrw_passwd == ""
-      call NetUserPass(g:netrw_uid)
-    " else just use current g:netrw_uid and s:netrw_passwd
-     endif
-     let b:netrw_method= 3
+      let host= substitute(g:netrw_machine,'\..*$','','')
+      if exists("s:netrw_hup[host]")
+        call NetUserPass("ftp:".host)
+
+      elseif has("win32") && s:netrw_ftp_cmd =~# '-[sS]:'
+        "     call Decho("has -s: : s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand(""))
+        "     call Decho("          g:netrw_ftp_cmd<".g:netrw_ftp_cmd.">",'~'.expand(""))
+        if g:netrw_ftp_cmd =~# '-[sS]:\S*MACHINE\>'
+          let s:netrw_ftp_cmd= substitute(g:netrw_ftp_cmd,'\',g:netrw_machine,'')
+          "      call Decho("s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand(""))
+        endif
+        let b:netrw_method= 2
+      elseif s:FileReadable(expand("$HOME/.netrc")) && !g:netrw_ignorenetrc
+        "     call Decho("using <".expand("$HOME/.netrc")."> (readable)",'~'.expand(""))
+        let b:netrw_method= 2
+      else
+        if !exists("g:netrw_uid") || g:netrw_uid == ""
+          call NetUserPass()
+        elseif !exists("s:netrw_passwd") || s:netrw_passwd == ""
+          call NetUserPass(g:netrw_uid)
+          " else just use current g:netrw_uid and s:netrw_passwd
+        endif
+        let b:netrw_method= 3
+      endif
     endif
-   endif
 
-  " Method#8: fetch {{{3
+    " Method#8: fetch {{{3
   elseif match(a:choice,fetchurm) == 0
-"   call Decho("fetch://...",'~'.expand(""))
-   let b:netrw_method = 8
-   let g:netrw_userid = substitute(a:choice,fetchurm,'\2',"")
-   let g:netrw_machine= substitute(a:choice,fetchurm,'\3',"")
-   let b:netrw_option = substitute(a:choice,fetchurm,'\4',"")
-   let b:netrw_fname  = substitute(a:choice,fetchurm,'\5',"")
-
-   " Method#3: Issue an ftp : "machine id password [path/]filename" {{{3
+    "   call Decho("fetch://...",'~'.expand(""))
+    let b:netrw_method = 8
+    let g:netrw_userid = substitute(a:choice,fetchurm,'\2',"")
+    let g:netrw_machine= substitute(a:choice,fetchurm,'\3',"")
+    let b:netrw_option = substitute(a:choice,fetchurm,'\4',"")
+    let b:netrw_fname  = substitute(a:choice,fetchurm,'\5',"")
+
+    " Method#3: Issue an ftp : "machine id password [path/]filename" {{{3
   elseif match(a:choice,mipf) == 0
-"   call Decho("(ftp) host id pass file",'~'.expand(""))
-   let b:netrw_method  = 3
-   let g:netrw_machine = substitute(a:choice,mipf,'\1',"")
-   let g:netrw_uid     = substitute(a:choice,mipf,'\2',"")
-   let s:netrw_passwd  = substitute(a:choice,mipf,'\3',"")
-   let b:netrw_fname   = substitute(a:choice,mipf,'\4',"")
-   call NetUserPass(g:netrw_machine,g:netrw_uid,s:netrw_passwd)
-
-  " Method#3: Issue an ftp: "hostname [path/]filename" {{{3
-  elseif match(a:choice,mf) == 0
-"   call Decho("(ftp) host file",'~'.expand(""))
-   if exists("g:netrw_uid") && exists("s:netrw_passwd")
+    "   call Decho("(ftp) host id pass file",'~'.expand(""))
     let b:netrw_method  = 3
-    let g:netrw_machine = substitute(a:choice,mf,'\1',"")
-    let b:netrw_fname   = substitute(a:choice,mf,'\2',"")
+    let g:netrw_machine = substitute(a:choice,mipf,'\1',"")
+    let g:netrw_uid     = substitute(a:choice,mipf,'\2',"")
+    let s:netrw_passwd  = substitute(a:choice,mipf,'\3',"")
+    let b:netrw_fname   = substitute(a:choice,mipf,'\4',"")
+    call NetUserPass(g:netrw_machine,g:netrw_uid,s:netrw_passwd)
+
+    " Method#3: Issue an ftp: "hostname [path/]filename" {{{3
+  elseif match(a:choice,mf) == 0
+    "   call Decho("(ftp) host file",'~'.expand(""))
+    if exists("g:netrw_uid") && exists("s:netrw_passwd")
+      let b:netrw_method  = 3
+      let g:netrw_machine = substitute(a:choice,mf,'\1',"")
+      let b:netrw_fname   = substitute(a:choice,mf,'\2',"")
 
-   elseif s:FileReadable(expand("$HOME/.netrc"))
-    let b:netrw_method  = 2
-    let g:netrw_machine = substitute(a:choice,mf,'\1',"")
-    let b:netrw_fname   = substitute(a:choice,mf,'\2',"")
-   endif
+    elseif s:FileReadable(expand("$HOME/.netrc"))
+      let b:netrw_method  = 2
+      let g:netrw_machine = substitute(a:choice,mf,'\1',"")
+      let b:netrw_fname   = substitute(a:choice,mf,'\2',"")
+    endif
 
-  " Method#9: sftp://user@hostname/...path-to-file {{{3
+    " Method#9: sftp://user@hostname/...path-to-file {{{3
   elseif match(a:choice,sftpurm) == 0
-"   call Decho("sftp://...",'~'.expand(""))
-   let b:netrw_method = 9
-   let g:netrw_machine= substitute(a:choice,sftpurm,'\1',"")
-   let b:netrw_fname  = substitute(a:choice,sftpurm,'\2',"")
+    "   call Decho("sftp://...",'~'.expand(""))
+    let b:netrw_method = 9
+    let g:netrw_machine= substitute(a:choice,sftpurm,'\1',"")
+    let b:netrw_fname  = substitute(a:choice,sftpurm,'\2',"")
 
-  " Method#1: Issue an rcp: hostname:filename"  (this one should be last) {{{3
+    " Method#1: Issue an rcp: hostname:filename"  (this one should be last) {{{3
   elseif match(a:choice,rcphf) == 0
-"   call Decho("(rcp) [user@]host:file) rcphf<".rcphf.">",'~'.expand(""))
-   let b:netrw_method  = 1
-   let userid          = substitute(a:choice,rcphf,'\2',"")
-   let g:netrw_machine = substitute(a:choice,rcphf,'\3',"")
-   let b:netrw_fname   = substitute(a:choice,rcphf,'\4',"")
-"   call Decho('\1<'.substitute(a:choice,rcphf,'\1',"").">",'~'.expand(""))
-"   call Decho('\2<'.substitute(a:choice,rcphf,'\2',"").">",'~'.expand(""))
-"   call Decho('\3<'.substitute(a:choice,rcphf,'\3',"").">",'~'.expand(""))
-"   call Decho('\4<'.substitute(a:choice,rcphf,'\4',"").">",'~'.expand(""))
-   if userid != ""
-    let g:netrw_uid= userid
-   endif
-
-   " Method#10: file://user@hostname/...path-to-file {{{3
+    "   call Decho("(rcp) [user@]host:file) rcphf<".rcphf.">",'~'.expand(""))
+    let b:netrw_method  = 1
+    let userid          = substitute(a:choice,rcphf,'\2',"")
+    let g:netrw_machine = substitute(a:choice,rcphf,'\3',"")
+    let b:netrw_fname   = substitute(a:choice,rcphf,'\4',"")
+    "   call Decho('\1<'.substitute(a:choice,rcphf,'\1',"").">",'~'.expand(""))
+    "   call Decho('\2<'.substitute(a:choice,rcphf,'\2',"").">",'~'.expand(""))
+    "   call Decho('\3<'.substitute(a:choice,rcphf,'\3',"").">",'~'.expand(""))
+    "   call Decho('\4<'.substitute(a:choice,rcphf,'\4',"").">",'~'.expand(""))
+    if userid != ""
+      let g:netrw_uid= userid
+    endif
+
+    " Method#10: file://user@hostname/...path-to-file {{{3
   elseif match(a:choice,fileurm) == 0 && exists("g:netrw_file_cmd")
-"   call Decho("http[s]://...",'~'.expand(""))
-   let b:netrw_method = 10
-   let b:netrw_fname  = substitute(a:choice,fileurm,'\1',"")
-"   call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">",'~'.expand(""))
+    "   call Decho("http[s]://...",'~'.expand(""))
+    let b:netrw_method = 10
+    let b:netrw_fname  = substitute(a:choice,fileurm,'\1',"")
+    "   call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">",'~'.expand(""))
 
-  " Cannot Determine Method {{{3
+    " Cannot Determine Method {{{3
   else
-   if !exists("g:netrw_quiet")
-    call netrw#ErrorMsg(s:WARNING,"cannot determine method (format: protocol://[user@]hostname[:port]/[path])",45)
-   endif
-   let b:netrw_method  = -1
+    if !exists("g:netrw_quiet")
+      call netrw#ErrorMsg(s:WARNING,"cannot determine method (format: protocol://[user@]hostname[:port]/[path])",45)
+    endif
+    let b:netrw_method  = -1
   endif
   "}}}3
 
   if g:netrw_port != ""
-   " remove any leading [:#] from port number
-   let g:netrw_port = substitute(g:netrw_port,'[#:]\+','','')
+    " remove any leading [:#] from port number
+    let g:netrw_port = substitute(g:netrw_port,'[#:]\+','','')
   elseif exists("netrw_port")
-   " retain port number as implicit for subsequent ftp operations
-   let g:netrw_port= netrw_port
-  endif
-
-"  call Decho("a:choice       <".a:choice.">",'~'.expand(""))
-"  call Decho("b:netrw_method <".b:netrw_method.">",'~'.expand(""))
-"  call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand(""))
-"  call Decho("g:netrw_port   <".g:netrw_port.">",'~'.expand(""))
-"  if exists("g:netrw_uid")		"Decho
-"   call Decho("g:netrw_uid    <".g:netrw_uid.">",'~'.expand(""))
-"  endif					"Decho
-"  if exists("s:netrw_passwd")		"Decho
-"   call Decho("s:netrw_passwd <".s:netrw_passwd.">",'~'.expand(""))
-"  endif					"Decho
-"  call Decho("b:netrw_fname  <".b:netrw_fname.">",'~'.expand(""))
-"  call Dret("s:NetrwMethod : b:netrw_method=".b:netrw_method." g:netrw_port=".g:netrw_port)
+    " retain port number as implicit for subsequent ftp operations
+    let g:netrw_port= netrw_port
+  endif
+
+  "  call Decho("a:choice       <".a:choice.">",'~'.expand(""))
+  "  call Decho("b:netrw_method <".b:netrw_method.">",'~'.expand(""))
+  "  call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand(""))
+  "  call Decho("g:netrw_port   <".g:netrw_port.">",'~'.expand(""))
+  "  if exists("g:netrw_uid")             "Decho
+  "   call Decho("g:netrw_uid    <".g:netrw_uid.">",'~'.expand(""))
+  "  endif                                        "Decho
+  "  if exists("s:netrw_passwd")          "Decho
+  "   call Decho("s:netrw_passwd <".s:netrw_passwd.">",'~'.expand(""))
+  "  endif                                        "Decho
+  "  call Decho("b:netrw_fname  <".b:netrw_fname.">",'~'.expand(""))
+  "  call Dret("s:NetrwMethod : b:netrw_method=".b:netrw_method." g:netrw_port=".g:netrw_port)
 endfun
 
 " ---------------------------------------------------------------------
 " NetUserPass: set username and password for subsequent ftp transfer {{{2
-"   Usage:  :call NetUserPass()		               -- will prompt for userid and password
-"	    :call NetUserPass("uid")	               -- will prompt for password
-"	    :call NetUserPass("uid","password")        -- sets global userid and password
-"	    :call NetUserPass("ftp:host")              -- looks up userid and password using hup dictionary
-"	    :call NetUserPass("host","uid","password") -- sets hup dictionary with host, userid, password
+"   Usage:  :call NetUserPass()                        -- will prompt for userid and password
+"           :call NetUserPass("uid")                   -- will prompt for password
+"           :call NetUserPass("uid","password")        -- sets global userid and password
+"           :call NetUserPass("ftp:host")              -- looks up userid and password using hup dictionary
+"           :call NetUserPass("host","uid","password") -- sets hup dictionary with host, userid, password
 fun! NetUserPass(...)
 
-" call Dfunc("NetUserPass() a:0=".a:0)
-
- if !exists('s:netrw_hup')
-  let s:netrw_hup= {}
- endif
-
- if a:0 == 0
-  " case: no input arguments
-
-  " change host and username if not previously entered; get new password
-  if !exists("g:netrw_machine")
-   let g:netrw_machine= input('Enter hostname: ')
-  endif
-  if !exists("g:netrw_uid") || g:netrw_uid == ""
-   " get username (user-id) via prompt
-   let g:netrw_uid= input('Enter username: ')
-  endif
-  " get password via prompting
-  let s:netrw_passwd= inputsecret("Enter Password: ")
-
-  " set up hup database
-  let host = substitute(g:netrw_machine,'\..*$','','')
-  if !exists('s:netrw_hup[host]')
-   let s:netrw_hup[host]= {}
-  endif
-  let s:netrw_hup[host].uid    = g:netrw_uid
-  let s:netrw_hup[host].passwd = s:netrw_passwd
-
- elseif a:0 == 1
-  " case: one input argument
-
-  if a:1 =~ '^ftp:'
-   " get host from ftp:... url
-   " access userid and password from hup (host-user-passwd) dictionary
-"   call Decho("case a:0=1: a:1<".a:1."> (get host from ftp:... url)",'~'.expand(""))
-   let host = substitute(a:1,'^ftp:','','')
-   let host = substitute(host,'\..*','','')
-   if exists("s:netrw_hup[host]")
-    let g:netrw_uid    = s:netrw_hup[host].uid
-    let s:netrw_passwd = s:netrw_hup[host].passwd
-"    call Decho("get s:netrw_hup[".host."].uid   <".s:netrw_hup[host].uid.">",'~'.expand(""))
-"    call Decho("get s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand(""))
-   else
-    let g:netrw_uid    = input("Enter UserId: ")
-    let s:netrw_passwd = inputsecret("Enter Password: ")
-   endif
+  " call Dfunc("NetUserPass() a:0=".a:0)
+
+  if !exists('s:netrw_hup')
+    let s:netrw_hup= {}
+  endif
+
+  if a:0 == 0
+    " case: no input arguments
+
+    " change host and username if not previously entered; get new password
+    if !exists("g:netrw_machine")
+      let g:netrw_machine= input('Enter hostname: ')
+    endif
+    if !exists("g:netrw_uid") || g:netrw_uid == ""
+      " get username (user-id) via prompt
+      let g:netrw_uid= input('Enter username: ')
+    endif
+    " get password via prompting
+    let s:netrw_passwd= inputsecret("Enter Password: ")
+
+    " set up hup database
+    let host = substitute(g:netrw_machine,'\..*$','','')
+    if !exists('s:netrw_hup[host]')
+      let s:netrw_hup[host]= {}
+    endif
+    let s:netrw_hup[host].uid    = g:netrw_uid
+    let s:netrw_hup[host].passwd = s:netrw_passwd
+
+  elseif a:0 == 1
+    " case: one input argument
+
+    if a:1 =~ '^ftp:'
+      " get host from ftp:... url
+      " access userid and password from hup (host-user-passwd) dictionary
+      "   call Decho("case a:0=1: a:1<".a:1."> (get host from ftp:... url)",'~'.expand(""))
+      let host = substitute(a:1,'^ftp:','','')
+      let host = substitute(host,'\..*','','')
+      if exists("s:netrw_hup[host]")
+        let g:netrw_uid    = s:netrw_hup[host].uid
+        let s:netrw_passwd = s:netrw_hup[host].passwd
+        "    call Decho("get s:netrw_hup[".host."].uid   <".s:netrw_hup[host].uid.">",'~'.expand(""))
+        "    call Decho("get s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand(""))
+      else
+        let g:netrw_uid    = input("Enter UserId: ")
+        let s:netrw_passwd = inputsecret("Enter Password: ")
+      endif
 
-  else
-   " case: one input argument, not an url.  Using it as a new user-id.
-"   call Decho("case a:0=1: a:1<".a:1."> (get host from input argument, not an url)",'~'.expand(""))
-   if exists("g:netrw_machine")
-    if g:netrw_machine =~ '[0-9.]\+'
-     let host= g:netrw_machine
     else
-     let host= substitute(g:netrw_machine,'\..*$','','')
-    endif
-   else
-    let g:netrw_machine= input('Enter hostname: ')
-   endif
-   let g:netrw_uid = a:1
-"   call Decho("set g:netrw_uid= <".g:netrw_uid.">",'~'.expand(""))
-   if exists("g:netrw_passwd")
-    " ask for password if one not previously entered
-    let s:netrw_passwd= g:netrw_passwd
-   else
-    let s:netrw_passwd = inputsecret("Enter Password: ")
-   endif
-  endif
-
-"  call Decho("host<".host.">",'~'.expand(""))
-  if exists("host")
-   if !exists('s:netrw_hup[host]')
-    let s:netrw_hup[host]= {}
-   endif
-   let s:netrw_hup[host].uid    = g:netrw_uid
-   let s:netrw_hup[host].passwd = s:netrw_passwd
-  endif
-
- elseif a:0 == 2
-  let g:netrw_uid    = a:1
-  let s:netrw_passwd = a:2
-
- elseif a:0 == 3
-  " enter hostname, user-id, and password into the hup dictionary
-  let host = substitute(a:1,'^\a\+:','','')
-  let host = substitute(host,'\..*$','','')
-  if !exists('s:netrw_hup[host]')
-   let s:netrw_hup[host]= {}
-  endif
-  let s:netrw_hup[host].uid    = a:2
-  let s:netrw_hup[host].passwd = a:3
-  let g:netrw_uid              = s:netrw_hup[host].uid
-  let s:netrw_passwd           = s:netrw_hup[host].passwd
-"  call Decho("set s:netrw_hup[".host."].uid   <".s:netrw_hup[host].uid.">",'~'.expand(""))
-"  call Decho("set s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand(""))
- endif
-
-" call Dret("NetUserPass : uid<".g:netrw_uid."> passwd<".s:netrw_passwd.">")
+      " case: one input argument, not an url.  Using it as a new user-id.
+      "   call Decho("case a:0=1: a:1<".a:1."> (get host from input argument, not an url)",'~'.expand(""))
+      if exists("g:netrw_machine")
+        if g:netrw_machine =~ '[0-9.]\+'
+          let host= g:netrw_machine
+        else
+          let host= substitute(g:netrw_machine,'\..*$','','')
+        endif
+      else
+        let g:netrw_machine= input('Enter hostname: ')
+      endif
+      let g:netrw_uid = a:1
+      "   call Decho("set g:netrw_uid= <".g:netrw_uid.">",'~'.expand(""))
+      if exists("g:netrw_passwd")
+        " ask for password if one not previously entered
+        let s:netrw_passwd= g:netrw_passwd
+      else
+        let s:netrw_passwd = inputsecret("Enter Password: ")
+      endif
+    endif
+
+    "  call Decho("host<".host.">",'~'.expand(""))
+    if exists("host")
+      if !exists('s:netrw_hup[host]')
+        let s:netrw_hup[host]= {}
+      endif
+      let s:netrw_hup[host].uid    = g:netrw_uid
+      let s:netrw_hup[host].passwd = s:netrw_passwd
+    endif
+
+  elseif a:0 == 2
+    let g:netrw_uid    = a:1
+    let s:netrw_passwd = a:2
+
+  elseif a:0 == 3
+    " enter hostname, user-id, and password into the hup dictionary
+    let host = substitute(a:1,'^\a\+:','','')
+    let host = substitute(host,'\..*$','','')
+    if !exists('s:netrw_hup[host]')
+      let s:netrw_hup[host]= {}
+    endif
+    let s:netrw_hup[host].uid    = a:2
+    let s:netrw_hup[host].passwd = a:3
+    let g:netrw_uid              = s:netrw_hup[host].uid
+    let s:netrw_passwd           = s:netrw_hup[host].passwd
+    "  call Decho("set s:netrw_hup[".host."].uid   <".s:netrw_hup[host].uid.">",'~'.expand(""))
+    "  call Decho("set s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand(""))
+  endif
+
+  " call Dret("NetUserPass : uid<".g:netrw_uid."> passwd<".s:netrw_passwd.">")
 endfun
 
 " =================================
@@ -3315,13 +3316,13 @@ endfun
 " ---------------------------------------------------------------------
 " s:ExplorePatHls: converts an Explore pattern into a regular expression search pattern {{{2
 fun! s:ExplorePatHls(pattern)
-"  call Dfunc("s:ExplorePatHls(pattern<".a:pattern.">)")
+  "  call Dfunc("s:ExplorePatHls(pattern<".a:pattern.">)")
   let repat= substitute(a:pattern,'^**/\{1,2}','','')
-"  call Decho("repat<".repat.">",'~'.expand(""))
+  "  call Decho("repat<".repat.">",'~'.expand(""))
   let repat= escape(repat,'][.\')
-"  call Decho("repat<".repat.">",'~'.expand(""))
+  "  call Decho("repat<".repat.">",'~'.expand(""))
   let repat= '\<'.substitute(repat,'\*','\\(\\S\\+ \\)*\\S\\+','g').'\>'
-"  call Dret("s:ExplorePatHls repat<".repat.">")
+  "  call Dret("s:ExplorePatHls repat<".repat.">")
   return repat
 endfun
 
@@ -3335,185 +3336,185 @@ endfun
 "    5: (user: )    go down (next)     directory, using history
 "    6: (user: )   delete bookmark
 fun! s:NetrwBookHistHandler(chg,curdir)
-"  call Dfunc("s:NetrwBookHistHandler(chg=".a:chg." curdir<".a:curdir.">) cnt=".v:count." histcnt=".g:netrw_dirhistcnt." histmax=".g:netrw_dirhistmax)
+  "  call Dfunc("s:NetrwBookHistHandler(chg=".a:chg." curdir<".a:curdir.">) cnt=".v:count." histcnt=".g:netrw_dirhistcnt." histmax=".g:netrw_dirhistmax)
   if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0
-"   "  call Dret("s:NetrwBookHistHandler - suppressed due to g:netrw_dirhistmax")
-   return
+    "   "  call Dret("s:NetrwBookHistHandler - suppressed due to g:netrw_dirhistmax")
+    return
   endif
 
   let ykeep    = @@
   let curbufnr = bufnr("%")
 
   if a:chg == 0
-   " bookmark the current directory
-"   call Decho("(user: ) bookmark the current directory",'~'.expand(""))
-   if exists("s:netrwmarkfilelist_{curbufnr}")
-    call s:NetrwBookmark(0)
-    echo "bookmarked marked files"
-   else
-    call s:MakeBookmark(a:curdir)
-    echo "bookmarked the current directory"
-   endif
-
-   try
-    call s:NetrwBookHistSave()
-   catch
-   endtry
+    " bookmark the current directory
+    "   call Decho("(user: ) bookmark the current directory",'~'.expand(""))
+    if exists("s:netrwmarkfilelist_{curbufnr}")
+      call s:NetrwBookmark(0)
+      echo "bookmarked marked files"
+    else
+      call s:MakeBookmark(a: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(""))
-   if exists("g:netrw_bookmarklist[v:count-1]")
-"    call Decho("(user: <".v:count."gb>) bookmarklist=".string(g:netrw_bookmarklist),'~'.expand(""))
-    exe "NetrwKeepj e ".fnameescape(g:netrw_bookmarklist[v:count-1])
-   else
-    echomsg "Sorry, bookmark#".v:count." doesn't exist!"
-   endif
+    " change to the bookmarked directory
+    "   call Decho("(user: <".v:count."gb>) change to the bookmarked directory",'~'.expand(""))
+    if exists("g:netrw_bookmarklist[v:count-1]")
+      "    call Decho("(user: <".v:count."gb>) bookmarklist=".string(g:netrw_bookmarklist),'~'.expand(""))
+      exe "NetrwKeepj e ".fnameescape(g:netrw_bookmarklist[v:count-1])
+    else
+      echomsg "Sorry, bookmark#".v:count." doesn't exist!"
+    endif
 
   elseif a:chg == 2
-"   redraw!
-   let didwork= 0
-   " list user's bookmarks
-"   call Decho("(user: ) list user's bookmarks",'~'.expand(""))
-   if exists("g:netrw_bookmarklist")
-"    call Decho('list '.len(g:netrw_bookmarklist).' bookmarks','~'.expand(""))
-    let cnt= 1
-    for bmd in g:netrw_bookmarklist
-"     call Decho("Netrw Bookmark#".cnt.": ".g:netrw_bookmarklist[cnt-1],'~'.expand(""))
-     echo printf("Netrw Bookmark#%-2d: %s",cnt,g:netrw_bookmarklist[cnt-1])
-     let didwork = 1
-     let cnt     = cnt + 1
-    endfor
-   endif
-
-   " list directory history
-   " Note: history is saved only when PerformListing is done;
-   "       ie. when netrw can re-use a netrw buffer, the current directory is not saved in the history.
-   let cnt     = g:netrw_dirhistcnt
-   let first   = 1
-   let histcnt = 0
-   if g:netrw_dirhistmax > 0
-    while ( first || cnt != g:netrw_dirhistcnt )
-"    call Decho("first=".first." cnt=".cnt." dirhistcnt=".g:netrw_dirhistcnt,'~'.expand(""))
-     if exists("g:netrw_dirhist_{cnt}")
-"     call Decho("Netrw  History#".histcnt.": ".g:netrw_dirhist_{cnt},'~'.expand(""))
-      echo printf("Netrw  History#%-2d: %s",histcnt,g:netrw_dirhist_{cnt})
-      let didwork= 1
-     endif
-     let histcnt = histcnt + 1
-     let first   = 0
-     let cnt     = ( cnt - 1 ) % g:netrw_dirhistmax
-     if cnt < 0
-      let cnt= cnt + g:netrw_dirhistmax
-     endif
-    endwhile
-   else
-    let g:netrw_dirhistcnt= 0
-   endif
-   if didwork
-    call inputsave()|call input("Press  to continue")|call inputrestore()
-   endif
-
-  elseif a:chg == 3
-   " saves most recently visited directories (when they differ)
-"   call Decho("(browsing) record curdir history",'~'.expand(""))
-   if !exists("g:netrw_dirhistcnt") || !exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") || g:netrw_dirhist_{g:netrw_dirhistcnt} != a:curdir
-    if g:netrw_dirhistmax > 0
-     let g:netrw_dirhistcnt                   = ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax
-     let g:netrw_dirhist_{g:netrw_dirhistcnt} = a:curdir
+    "   redraw!
+    let didwork= 0
+    " list user's bookmarks
+    "   call Decho("(user: ) list user's bookmarks",'~'.expand(""))
+    if exists("g:netrw_bookmarklist")
+      "    call Decho('list '.len(g:netrw_bookmarklist).' bookmarks','~'.expand(""))
+      let cnt= 1
+      for bmd in g:netrw_bookmarklist
+        "     call Decho("Netrw Bookmark#".cnt.": ".g:netrw_bookmarklist[cnt-1],'~'.expand(""))
+        echo printf("Netrw Bookmark#%-2d: %s",cnt,g:netrw_bookmarklist[cnt-1])
+        let didwork = 1
+        let cnt     = cnt + 1
+      endfor
     endif
-"    call Decho("save dirhist#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand(""))
-   endif
 
-  elseif a:chg == 4
-   " u: change to the previous directory stored on the history list
-"   call Decho("(user: ) chg to prev dir from history",'~'.expand(""))
-   if g:netrw_dirhistmax > 0
-    let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - v:count1 ) % g:netrw_dirhistmax
-    if g:netrw_dirhistcnt < 0
-     let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax
-    endif
-   else
-    let g:netrw_dirhistcnt= 0
-   endif
-   if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}")
-"    call Decho("changedir u#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand(""))
-    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
-     setl ma noro
-"     call Decho("setl ma noro",'~'.expand(""))
-     sil! NetrwKeepj %d _
-     setl nomod
-"     call Decho("setl nomod",'~'.expand(""))
-"     call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-    endif
-"    call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand(""))
-    exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt})
-   else
+    " list directory history
+    " Note: history is saved only when PerformListing is done;
+    "       ie. when netrw can re-use a netrw buffer, the current directory is not saved in the history.
+    let cnt     = g:netrw_dirhistcnt
+    let first   = 1
+    let histcnt = 0
     if g:netrw_dirhistmax > 0
-     let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + v:count1 ) % g:netrw_dirhistmax
+      while ( first || cnt != g:netrw_dirhistcnt )
+        "    call Decho("first=".first." cnt=".cnt." dirhistcnt=".g:netrw_dirhistcnt,'~'.expand(""))
+        if exists("g:netrw_dirhist_{cnt}")
+          "     call Decho("Netrw  History#".histcnt.": ".g:netrw_dirhist_{cnt},'~'.expand(""))
+          echo printf("Netrw  History#%-2d: %s",histcnt,g:netrw_dirhist_{cnt})
+          let didwork= 1
+        endif
+        let histcnt = histcnt + 1
+        let first   = 0
+        let cnt     = ( cnt - 1 ) % g:netrw_dirhistmax
+        if cnt < 0
+          let cnt= cnt + g:netrw_dirhistmax
+        endif
+      endwhile
     else
-     let g:netrw_dirhistcnt= 0
+      let g:netrw_dirhistcnt= 0
+    endif
+    if didwork
+      call inputsave()|call input("Press  to continue")|call inputrestore()
     endif
-    echo "Sorry, no predecessor directory exists yet"
-   endif
 
-  elseif a:chg == 5
-   " U: change to the subsequent directory stored on the history list
-"   call Decho("(user: ) chg to next dir from history",'~'.expand(""))
-   if g:netrw_dirhistmax > 0
-    let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax
+  elseif a:chg == 3
+    " saves most recently visited directories (when they differ)
+    "   call Decho("(browsing) record curdir history",'~'.expand(""))
+    if !exists("g:netrw_dirhistcnt") || !exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") || g:netrw_dirhist_{g:netrw_dirhistcnt} != a:curdir
+      if g:netrw_dirhistmax > 0
+        let g:netrw_dirhistcnt                   = ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax
+        let g:netrw_dirhist_{g:netrw_dirhistcnt} = a:curdir
+      endif
+      "    call Decho("save dirhist#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand(""))
+    endif
+
+  elseif a:chg == 4
+    " u: change to the previous directory stored on the history list
+    "   call Decho("(user: ) chg to prev dir from history",'~'.expand(""))
+    if g:netrw_dirhistmax > 0
+      let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - v:count1 ) % g:netrw_dirhistmax
+      if g:netrw_dirhistcnt < 0
+        let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax
+      endif
+    else
+      let g:netrw_dirhistcnt= 0
+    endif
     if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}")
-"    call Decho("changedir U#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand(""))
-     if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
-"      call Decho("setl ma noro",'~'.expand(""))
-      setl ma noro
-      sil! NetrwKeepj %d _
-"      call Decho("removed all lines from buffer (%d)",'~'.expand(""))
-"      call Decho("setl nomod",'~'.expand(""))
-      setl nomod
-"      call Decho("(set nomod)  ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-     endif
-"    call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand(""))
-     exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt})
+      "    call Decho("changedir u#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand(""))
+      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
+        setl ma noro
+        "     call Decho("setl ma noro",'~'.expand(""))
+        sil! NetrwKeepj %d _
+        setl nomod
+        "     call Decho("setl nomod",'~'.expand(""))
+        "     call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+      endif
+      "    call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand(""))
+      exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt})
+    else
+      if g:netrw_dirhistmax > 0
+        let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + v:count1 ) % g:netrw_dirhistmax
+      else
+        let g:netrw_dirhistcnt= 0
+      endif
+      echo "Sorry, no predecessor directory exists yet"
+    endif
+
+  elseif a:chg == 5
+    " U: change to the subsequent directory stored on the history list
+    "   call Decho("(user: ) chg to next dir from history",'~'.expand(""))
+    if g:netrw_dirhistmax > 0
+      let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax
+      if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}")
+        "    call Decho("changedir U#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand(""))
+        if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
+          "      call Decho("setl ma noro",'~'.expand(""))
+          setl ma noro
+          sil! NetrwKeepj %d _
+          "      call Decho("removed all lines from buffer (%d)",'~'.expand(""))
+          "      call Decho("setl nomod",'~'.expand(""))
+          setl nomod
+          "      call Decho("(set nomod)  ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+        endif
+        "    call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand(""))
+        exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt})
+      else
+        let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - 1 ) % g:netrw_dirhistmax
+        if g:netrw_dirhistcnt < 0
+          let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax
+        endif
+        echo "Sorry, no successor directory exists yet"
+      endif
     else
-     let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - 1 ) % g:netrw_dirhistmax
-     if g:netrw_dirhistcnt < 0
-      let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax
-     endif
-     echo "Sorry, no successor directory exists yet"
+      let g:netrw_dirhistcnt= 0
+      echo "Sorry, no successor directory exists yet (g:netrw_dirhistmax is ".g:netrw_dirhistmax.")"
     endif
-   else
-    let g:netrw_dirhistcnt= 0
-    echo "Sorry, no successor directory exists yet (g:netrw_dirhistmax is ".g:netrw_dirhistmax.")"
-   endif
 
   elseif a:chg == 6
-"   call Decho("(user: ) delete bookmark'd directory",'~'.expand(""))
-   if exists("s:netrwmarkfilelist_{curbufnr}")
-    call s:NetrwBookmark(1)
-    echo "removed marked files from bookmarks"
-   else
-    " delete the v:count'th bookmark
-    let iremove = v:count
-    let dremove = g:netrw_bookmarklist[iremove - 1]
-"    call Decho("delete bookmark#".iremove."<".g:netrw_bookmarklist[iremove - 1].">",'~'.expand(""))
-    call s:MergeBookmarks()
-"    call Decho("remove g:netrw_bookmarklist[".(iremove-1)."]<".g:netrw_bookmarklist[(iremove-1)].">",'~'.expand(""))
-    NetrwKeepj call remove(g:netrw_bookmarklist,iremove-1)
-    echo "removed ".dremove." from g:netrw_bookmarklist"
-"    call Decho("g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand(""))
-   endif
-"   call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand(""))
-
-   try
-    call s:NetrwBookHistSave()
-   catch
-   endtry
+    "   call Decho("(user: ) delete bookmark'd directory",'~'.expand(""))
+    if exists("s:netrwmarkfilelist_{curbufnr}")
+      call s:NetrwBookmark(1)
+      echo "removed marked files from bookmarks"
+    else
+      " delete the v:count'th bookmark
+      let iremove = v:count
+      let dremove = g:netrw_bookmarklist[iremove - 1]
+      "    call Decho("delete bookmark#".iremove."<".g:netrw_bookmarklist[iremove - 1].">",'~'.expand(""))
+      call s:MergeBookmarks()
+      "    call Decho("remove g:netrw_bookmarklist[".(iremove-1)."]<".g:netrw_bookmarklist[(iremove-1)].">",'~'.expand(""))
+      NetrwKeepj call remove(g:netrw_bookmarklist,iremove-1)
+      echo "removed ".dremove." from g:netrw_bookmarklist"
+      "    call Decho("g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand(""))
+    endif
+    "   call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand(""))
+
+    try
+      call s:NetrwBookHistSave()
+    catch
+    endtry
   endif
   call s:NetrwBookmarkMenu()
   call s:NetrwTgtMenu()
   let @@= ykeep
-"  call Dret("s:NetrwBookHistHandler")
+  "  call Dret("s:NetrwBookHistHandler")
 endfun
 
 " ---------------------------------------------------------------------
@@ -3521,38 +3522,38 @@ endfun
 "  Will source the history file (.netrwhist) only if the g:netrw_disthistmax is > 0.
 "                      Sister function: s:NetrwBookHistSave()
 fun! s:NetrwBookHistRead()
-"  call Dfunc("s:NetrwBookHistRead()")
+  "  call Dfunc("s:NetrwBookHistRead()")
   if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0
-"   call Dret("s:NetrwBookHistRead - nothing read (suppressed due to dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a").")")
-   return
+    "   call Dret("s:NetrwBookHistRead - nothing read (suppressed due to dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a").")")
+    return
   endif
   let ykeep= @@
 
   " read bookmarks
   if !exists("s:netrw_initbookhist")
-   let home    = s:NetrwHome()
-   let savefile= home."/.netrwbook"
-   if filereadable(s:NetrwFile(savefile))
-"    call Decho("sourcing .netrwbook",'~'.expand(""))
-    exe "keepalt NetrwKeepj so ".savefile
-   endif
-
-   " read history
-   if g:netrw_dirhistmax > 0
-    let savefile= home."/.netrwhist"
+    let home    = s:NetrwHome()
+    let savefile= home."/.netrwbook"
     if filereadable(s:NetrwFile(savefile))
-"    call Decho("sourcing .netrwhist",'~'.expand(""))
-     exe "keepalt NetrwKeepj so ".savefile
+      "    call Decho("sourcing .netrwbook",'~'.expand(""))
+      exe "keepalt NetrwKeepj so ".savefile
+    endif
+
+    " read history
+    if g:netrw_dirhistmax > 0
+      let savefile= home."/.netrwhist"
+      if filereadable(s:NetrwFile(savefile))
+        "    call Decho("sourcing .netrwhist",'~'.expand(""))
+        exe "keepalt NetrwKeepj so ".savefile
+      endif
+      let s:netrw_initbookhist= 1
+      au VimLeave * call s:NetrwBookHistSave()
     endif
-    let s:netrw_initbookhist= 1
-    au VimLeave * call s:NetrwBookHistSave()
-   endif
   endif
 
   let @@= ykeep
-"  call Decho("dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a"),'~'.expand(""))
-"  call Decho("dirhistcnt=".(exists("g:netrw_dirhistcnt")? g:netrw_dirhistcnt : "n/a"),'~'.expand(""))
-"  call Dret("s:NetrwBookHistRead")
+  "  call Decho("dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a"),'~'.expand(""))
+  "  call Decho("dirhistcnt=".(exists("g:netrw_dirhistcnt")? g:netrw_dirhistcnt : "n/a"),'~'.expand(""))
+  "  call Dret("s:NetrwBookHistRead")
 endfun
 
 " ---------------------------------------------------------------------
@@ -3564,23 +3565,23 @@ endfun
 "                      will be saved.
 "                      (s:NetrwBookHistHandler(3,...) used to record history)
 fun! s:NetrwBookHistSave()
-"  call Dfunc("s:NetrwBookHistSave() dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt)
+  "  call Dfunc("s:NetrwBookHistSave() dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt)
   if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0
-"   call Dret("s:NetrwBookHistSave : nothing saved (dirhistmax=".g:netrw_dirhistmax.")")
-   return
+    "   call Dret("s:NetrwBookHistSave : nothing saved (dirhistmax=".g:netrw_dirhistmax.")")
+    return
   endif
 
   let savefile= s:NetrwHome()."/.netrwhist"
-"  call Decho("savefile<".savefile.">",'~'.expand(""))
+  "  call Decho("savefile<".savefile.">",'~'.expand(""))
   1split
 
   " setting up a new buffer which will become .netrwhist
   call s:NetrwEnew()
-"  call Decho("case g:netrw_use_noswf=".g:netrw_use_noswf.(exists("+acd")? " +acd" : " -acd"),'~'.expand(""))
+  "  call Decho("case g:netrw_use_noswf=".g:netrw_use_noswf.(exists("+acd")? " +acd" : " -acd"),'~'.expand(""))
   if g:netrw_use_noswf
-   setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000 noswf
+    setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000 noswf
   else
-   setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000
+    setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000
   endif
   setl nocin noai noci magic nospell nohid wig= noaw
   setl ma noro write
@@ -3590,52 +3591,52 @@ fun! s:NetrwBookHistSave()
   " rename enew'd file: .netrwhist -- no attempt to merge
   " record dirhistmax and current dirhistcnt
   " save history
-"  call Decho("saving history: dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt." lastline=".line("$"),'~'.expand(""))
+  "  call Decho("saving history: dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt." lastline=".line("$"),'~'.expand(""))
   sil! keepalt file .netrwhist
   call setline(1,"let g:netrw_dirhistmax  =".g:netrw_dirhistmax)
   call setline(2,"let g:netrw_dirhistcnt =".g:netrw_dirhistcnt)
   if g:netrw_dirhistmax > 0
-   let lastline = line("$")
-   let cnt      = g:netrw_dirhistcnt
-   let first    = 1
-   while ( first || cnt != g:netrw_dirhistcnt )
-    let lastline= lastline + 1
-    if exists("g:netrw_dirhist_{cnt}")
-     call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'")
-"     call Decho("..".lastline.'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'",'~'.expand(""))
-    endif
-    let first   = 0
-    let cnt     = ( cnt - 1 ) % g:netrw_dirhistmax
-    if cnt < 0
-     let cnt= cnt + g:netrw_dirhistmax
-    endif
-   endwhile
-   exe "sil! w! ".savefile
-"   call Decho("exe sil! w! ".savefile,'~'.expand(""))
+    let lastline = line("$")
+    let cnt      = g:netrw_dirhistcnt
+    let first    = 1
+    while ( first || cnt != g:netrw_dirhistcnt )
+      let lastline= lastline + 1
+      if exists("g:netrw_dirhist_{cnt}")
+        call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'")
+        "     call Decho("..".lastline.'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'",'~'.expand(""))
+      endif
+      let first   = 0
+      let cnt     = ( cnt - 1 ) % g:netrw_dirhistmax
+      if cnt < 0
+        let cnt= cnt + g:netrw_dirhistmax
+      endif
+    endwhile
+    exe "sil! w! ".savefile
+    "   call Decho("exe sil! w! ".savefile,'~'.expand(""))
   endif
 
   " save bookmarks
   sil NetrwKeepj %d _
   if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != []
-"   call Decho("saving bookmarks",'~'.expand(""))
-   " merge and write .netrwbook
-   let savefile= s:NetrwHome()."/.netrwbook"
-
-   if filereadable(s:NetrwFile(savefile))
-    let booklist= deepcopy(g:netrw_bookmarklist)
-    exe "sil NetrwKeepj keepalt so ".savefile
-    for bdm in booklist
-     if index(g:netrw_bookmarklist,bdm) == -1
-      call add(g:netrw_bookmarklist,bdm)
-     endif
-    endfor
-    call sort(g:netrw_bookmarklist)
-   endif
+    "   call Decho("saving bookmarks",'~'.expand(""))
+    " merge and write .netrwbook
+    let savefile= s:NetrwHome()."/.netrwbook"
+
+    if filereadable(s:NetrwFile(savefile))
+      let booklist= deepcopy(g:netrw_bookmarklist)
+      exe "sil NetrwKeepj keepalt so ".savefile
+      for bdm in booklist
+        if index(g:netrw_bookmarklist,bdm) == -1
+          call add(g:netrw_bookmarklist,bdm)
+        endif
+      endfor
+      call sort(g:netrw_bookmarklist)
+    endif
 
-   " construct and save .netrwbook
-   call setline(1,"let g:netrw_bookmarklist= ".string(g:netrw_bookmarklist))
-   exe "sil! w! ".savefile
-"   call Decho("exe sil! w! ".savefile,'~'.expand(""))
+    " construct and save .netrwbook
+    call setline(1,"let g:netrw_bookmarklist= ".string(g:netrw_bookmarklist))
+    exe "sil! w! ".savefile
+    "   call Decho("exe sil! w! ".savefile,'~'.expand(""))
   endif
 
   " cleanup -- remove buffer used to construct history
@@ -3643,7 +3644,7 @@ fun! s:NetrwBookHistSave()
   q!
   exe "keepalt ".bgone."bwipe!"
 
-"  call Dret("s:NetrwBookHistSave")
+  "  call Dret("s:NetrwBookHistSave")
 endfun
 
 " ---------------------------------------------------------------------
@@ -3658,38 +3659,38 @@ fun! s:NetrwBrowse(islocal,dirname)
   " save alternate-file's filename if w:netrw_rexlocal doesn't exist
   " This is useful when one edits a local file, then :e ., then :Rex
   if a:islocal && !exists("w:netrw_rexfile") && bufname("#") != ""
-   let w:netrw_rexfile= bufname("#")
+    let w:netrw_rexfile= bufname("#")
   endif
 
   " s:NetrwBrowse : initialize history {{{3
   if !exists("s:netrw_initbookhist")
-   NetrwKeepj call s:NetrwBookHistRead()
+    NetrwKeepj call s:NetrwBookHistRead()
   endif
 
   " s:NetrwBrowse : simplify the dirname (especially for ".."s in dirnames) {{{3
   if a:dirname !~ '^\a\{3,}://'
-   let dirname= simplify(a:dirname)
+    let dirname= simplify(a:dirname)
   else
-   let dirname= a:dirname
+    let dirname= a:dirname
   endif
 
   " repoint t:netrw_lexbufnr if appropriate
   if exists("t:netrw_lexbufnr") && bufnr("%") == t:netrw_lexbufnr
-   let repointlexbufnr= 1
+    let repointlexbufnr= 1
   endif
 
   " s:NetrwBrowse : sanity checks: {{{3
   if exists("s:netrw_skipbrowse")
-   unlet s:netrw_skipbrowse
-   return
+    unlet s:netrw_skipbrowse
+    return
   endif
   if !exists("*shellescape")
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing shellescape()",69)
-   return
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing shellescape()",69)
+    return
   endif
   if !exists("*fnameescape")
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing fnameescape()",70)
-   return
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing fnameescape()",70)
+    return
   endif
 
   " s:NetrwBrowse : save options: {{{3
@@ -3697,56 +3698,56 @@ fun! s:NetrwBrowse(islocal,dirname)
 
   " s:NetrwBrowse : re-instate any marked files {{{3
   if has("syntax") && exists("g:syntax_on") && g:syntax_on
-   if exists("s:netrwmarkfilelist_{bufnr('%')}")
-    exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
-   endif
+    if exists("s:netrwmarkfilelist_{bufnr('%')}")
+      exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
+    endif
   endif
 
   if a:islocal && exists("w:netrw_acdkeep") && w:netrw_acdkeep
-   " s:NetrwBrowse : set up "safe" options for local directory/file {{{3
-   if s:NetrwLcd(dirname)
-    return
-   endif
+    " s:NetrwBrowse : set up "safe" options for local directory/file {{{3
+    if s:NetrwLcd(dirname)
+      return
+    endif
 
   elseif !a:islocal && dirname !~ '[\/]$' && dirname !~ '^"'
-   " s:NetrwBrowse :  remote regular file handler {{{3
-   if bufname(dirname) != ""
-    exe "NetrwKeepj b ".bufname(dirname)
-   else
-    " attempt transfer of remote regular file
-
-    " remove any filetype indicator from end of dirname, except for the
-    " "this is a directory" indicator (/).
-    " There shouldn't be one of those here, anyway.
-    let path= substitute(dirname,'[*=@|]\r\=$','','e')
-    call s:RemotePathAnalysis(dirname)
-
-    " s:NetrwBrowse : remote-read the requested file into current buffer {{{3
-    call s:NetrwEnew(dirname)
-    call s:NetrwOptionsSafe(a:islocal)
-    setl ma noro
-    let b:netrw_curdir = dirname
-    let url            = s:method."://".((s:user == "")? "" : s:user."@").s:machine.(s:port ? ":".s:port : "")."/".s:path
-    call s:NetrwBufRename(url)
-    exe "sil! NetrwKeepj keepalt doau BufReadPre ".fnameescape(s:fname)
-    sil call netrw#NetRead(2,url)
-    " netrw.vim and tar.vim have already handled decompression of the tarball; avoiding gzip.vim error
-    if s:path =~ '.bz2'
-     exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.bz2$','',''))
-    elseif s:path =~ '.gz'
-     exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.gz$','',''))
-    elseif s:path =~ '.gz'
-     exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.txz$','',''))
+    " s:NetrwBrowse :  remote regular file handler {{{3
+    if bufname(dirname) != ""
+      exe "NetrwKeepj b ".bufname(dirname)
     else
-     exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(s:fname)
+      " attempt transfer of remote regular file
+
+      " remove any filetype indicator from end of dirname, except for the
+      " "this is a directory" indicator (/).
+      " There shouldn't be one of those here, anyway.
+      let path= substitute(dirname,'[*=@|]\r\=$','','e')
+      call s:RemotePathAnalysis(dirname)
+
+      " s:NetrwBrowse : remote-read the requested file into current buffer {{{3
+      call s:NetrwEnew(dirname)
+      call s:NetrwOptionsSafe(a:islocal)
+      setl ma noro
+      let b:netrw_curdir = dirname
+      let url            = s:method."://".((s:user == "")? "" : s:user."@").s:machine.(s:port ? ":".s:port : "")."/".s:path
+      call s:NetrwBufRename(url)
+      exe "sil! NetrwKeepj keepalt doau BufReadPre ".fnameescape(s:fname)
+      sil call netrw#NetRead(2,url)
+      " netrw.vim and tar.vim have already handled decompression of the tarball; avoiding gzip.vim error
+      if s:path =~ '.bz2'
+        exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.bz2$','',''))
+      elseif s:path =~ '.gz'
+        exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.gz$','',''))
+      elseif s:path =~ '.gz'
+        exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.txz$','',''))
+      else
+        exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(s:fname)
+      endif
     endif
-   endif
 
-   " s:NetrwBrowse : save certain window-oriented variables into buffer-oriented variables {{{3
-   call s:SetBufWinVars()
-   call s:NetrwOptionsRestore("w:")
-   setl ma nomod noro
-   return
+    " s:NetrwBrowse : save certain window-oriented variables into buffer-oriented variables {{{3
+    call s:SetBufWinVars()
+    call s:NetrwOptionsRestore("w:")
+    setl ma nomod noro
+    return
   endif
 
   " use buffer-oriented WinVars if buffer variables exist but associated window variables don't {{{3
@@ -3768,89 +3769,89 @@ fun! s:NetrwBrowse(islocal,dirname)
   let prevbufnr = bufnr('%')
   let reusing= s:NetrwGetBuffer(a:islocal,dirname)
   if exists("s:rexposn_".prevbufnr)
-   let s:rexposn_{bufnr('%')} = s:rexposn_{prevbufnr}
+    let s:rexposn_{bufnr('%')} = s:rexposn_{prevbufnr}
   endif
 
   " maintain markfile highlighting
   if has("syntax") && exists("g:syntax_on") && g:syntax_on
-   if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != ""
-    exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
-   else
-    2match none
-   endif
+    if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != ""
+      exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
+    else
+      2match none
+    endif
   endif
   if reusing && line("$") > 1
-   call s:NetrwOptionsRestore("w:")
-   setl noma nomod nowrap
-   return
+    call s:NetrwOptionsRestore("w:")
+    setl noma nomod nowrap
+    return
   endif
 
   " set b:netrw_curdir to the new directory name {{{3
   let b:netrw_curdir= dirname
   if b:netrw_curdir =~ '[/\\]$'
-   let b:netrw_curdir= substitute(b:netrw_curdir,'[/\\]$','','e')
+    let b:netrw_curdir= substitute(b:netrw_curdir,'[/\\]$','','e')
   endif
   if b:netrw_curdir =~ '\a:$' && has("win32")
-   let b:netrw_curdir= b:netrw_curdir."/"
+    let b:netrw_curdir= b:netrw_curdir."/"
   endif
   if b:netrw_curdir == ''
-   if has("amiga")
-    " On the Amiga, the empty string connotes the current directory
-    let b:netrw_curdir= getcwd()
-   else
-    " under unix, when the root directory is encountered, the result
-    " from the preceding substitute is an empty string.
-    let b:netrw_curdir= '/'
-   endif
+    if has("amiga")
+      " On the Amiga, the empty string connotes the current directory
+      let b:netrw_curdir= getcwd()
+    else
+      " under unix, when the root directory is encountered, the result
+      " from the preceding substitute is an empty string.
+      let b:netrw_curdir= '/'
+    endif
   endif
   if !a:islocal && b:netrw_curdir !~ '/$'
-   let b:netrw_curdir= b:netrw_curdir.'/'
+    let b:netrw_curdir= b:netrw_curdir.'/'
   endif
 
   " ------------
   " (local only) {{{3
   " ------------
   if a:islocal
-   " Set up ShellCmdPost handling.  Append current buffer to browselist
-   call s:LocalFastBrowser()
+    " Set up ShellCmdPost handling.  Append current buffer to browselist
+    call s:LocalFastBrowser()
 
-  " handle g:netrw_keepdir: set vim's current directory to netrw's notion of the current directory {{{3
-   if !g:netrw_keepdir
-    if !exists("&l:acd") || !&l:acd
-     if s:NetrwLcd(b:netrw_curdir)
-      return
-     endif
+    " handle g:netrw_keepdir: set vim's current directory to netrw's notion of the current directory {{{3
+    if !g:netrw_keepdir
+      if !exists("&l:acd") || !&l:acd
+        if s:NetrwLcd(b:netrw_curdir)
+          return
+        endif
+      endif
     endif
-   endif
 
-  " --------------------------------
-  " remote handling: {{{3
-  " --------------------------------
+    " --------------------------------
+    " remote handling: {{{3
+    " --------------------------------
   else
 
-   " analyze dirname and g:netrw_list_cmd {{{3
-   if dirname =~# "^NetrwTreeListing\>"
-    let dirname= b:netrw_curdir
-   elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
-    let dirname= substitute(b:netrw_curdir,'\\','/','g')
-    if dirname !~ '/$'
-     let dirname= dirname.'/'
-    endif
-    let b:netrw_curdir = dirname
-   else
-    let dirname = substitute(dirname,'\\','/','g')
-   endif
-
-   let dirpat  = '^\(\w\{-}\)://\(\w\+@\)\=\([^/]\+\)/\(.*\)$'
-   if dirname !~ dirpat
-    if !exists("g:netrw_quiet")
-     NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw doesn't understand your dirname<".dirname.">",20)
+    " analyze dirname and g:netrw_list_cmd {{{3
+    if dirname =~# "^NetrwTreeListing\>"
+      let dirname= b:netrw_curdir
+    elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
+      let dirname= substitute(b:netrw_curdir,'\\','/','g')
+      if dirname !~ '/$'
+        let dirname= dirname.'/'
+      endif
+      let b:netrw_curdir = dirname
+    else
+      let dirname = substitute(dirname,'\\','/','g')
     endif
-    NetrwKeepj call s:NetrwOptionsRestore("w:")
-    setl noma nomod nowrap
-    return
-   endif
-   let b:netrw_curdir= dirname
+
+    let dirpat  = '^\(\w\{-}\)://\(\w\+@\)\=\([^/]\+\)/\(.*\)$'
+    if dirname !~ dirpat
+      if !exists("g:netrw_quiet")
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw doesn't understand your dirname<".dirname.">",20)
+      endif
+      NetrwKeepj call s:NetrwOptionsRestore("w:")
+      setl noma nomod nowrap
+      return
+    endif
+    let b:netrw_curdir= dirname
   endif  " (additional remote handling)
 
   " -------------------------------
@@ -3866,26 +3867,26 @@ fun! s:NetrwBrowse(islocal,dirname)
   " If there is a rexposn: restore position with rexposn
   " Otherwise            : set rexposn
   if exists("s:rexposn_".bufnr("%"))
-   NetrwKeepj call winrestview(s:rexposn_{bufnr('%')})
-   if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt
-    NetrwKeepj exe w:netrw_bannercnt
-   endif
+    NetrwKeepj call winrestview(s:rexposn_{bufnr('%')})
+    if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt
+      NetrwKeepj exe w:netrw_bannercnt
+    endif
   else
-   NetrwKeepj call s:SetRexDir(a:islocal,b:netrw_curdir)
+    NetrwKeepj call s:SetRexDir(a:islocal,b:netrw_curdir)
   endif
   if v:version >= 700 && has("balloon_eval") && &beval == 0 && &l:bexpr == "" && !exists("g:netrw_nobeval")
-   let &l:bexpr= "netrw#BalloonHelp()"
-   setl beval
+    let &l:bexpr= "netrw#BalloonHelp()"
+    setl beval
   endif
 
   " repoint t:netrw_lexbufnr if appropriate
   if exists("repointlexbufnr")
-   let t:netrw_lexbufnr= bufnr("%")
+    let t:netrw_lexbufnr= bufnr("%")
   endif
 
   " restore position
   if reusing
-   call winrestview(svpos)
+    call winrestview(svpos)
   endif
 
   " The s:LocalBrowseRefresh() function is called by an autocmd
@@ -3901,125 +3902,125 @@ endfun
 " directory is used.
 " Returns a path to the file specified by a:fname
 fun! s:NetrwFile(fname)
-"  "" call Dfunc("s:NetrwFile(fname<".a:fname.">) win#".winnr())
-"  "" call Decho("g:netrw_keepdir  =".(exists("g:netrw_keepdir")?   g:netrw_keepdir   : 'n/a'),'~'.expand(""))
-"  "" call Decho("g:netrw_cygwin   =".(exists("g:netrw_cygwin")?    g:netrw_cygwin    : 'n/a'),'~'.expand(""))
-"  "" call Decho("g:netrw_liststyle=".(exists("g:netrw_liststyle")? g:netrw_liststyle : 'n/a'),'~'.expand(""))
-"  "" call Decho("w:netrw_liststyle=".(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand(""))
+  "  "" call Dfunc("s:NetrwFile(fname<".a:fname.">) win#".winnr())
+  "  "" call Decho("g:netrw_keepdir  =".(exists("g:netrw_keepdir")?   g:netrw_keepdir   : 'n/a'),'~'.expand(""))
+  "  "" call Decho("g:netrw_cygwin   =".(exists("g:netrw_cygwin")?    g:netrw_cygwin    : 'n/a'),'~'.expand(""))
+  "  "" call Decho("g:netrw_liststyle=".(exists("g:netrw_liststyle")? g:netrw_liststyle : 'n/a'),'~'.expand(""))
+  "  "" call Decho("w:netrw_liststyle=".(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand(""))
 
   " clean up any leading treedepthstring
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-   let fname= substitute(a:fname,'^'.s:treedepthstring.'\+','','')
-"   "" call Decho("clean up any leading treedepthstring: fname<".fname.">",'~'.expand(""))
+    let fname= substitute(a:fname,'^'.s:treedepthstring.'\+','','')
+    "   "" call Decho("clean up any leading treedepthstring: fname<".fname.">",'~'.expand(""))
   else
-   let fname= a:fname
+    let fname= a:fname
   endif
 
   if g:netrw_keepdir
-   " vim's idea of the current directory possibly may differ from netrw's
-   if !exists("b:netrw_curdir")
-    let b:netrw_curdir= getcwd()
-   endif
+    " vim's idea of the current directory possibly may differ from netrw's
+    if !exists("b:netrw_curdir")
+      let b:netrw_curdir= getcwd()
+    endif
+
+    if !exists("g:netrw_cygwin") && has("win32")
+      if fname =~ '^\' || fname =~ '^\a:\'
+        " windows, but full path given
+        let ret= fname
+        "     "" call Decho("windows+full path: isdirectory(".fname.")",'~'.expand(""))
+      else
+        " windows, relative path given
+        let ret= s:ComposePath(b:netrw_curdir,fname)
+        "     "" call Decho("windows+rltv path: isdirectory(".fname.")",'~'.expand(""))
+      endif
 
-   if !exists("g:netrw_cygwin") && has("win32")
-    if fname =~ '^\' || fname =~ '^\a:\'
-     " windows, but full path given
-     let ret= fname
-"     "" call Decho("windows+full path: isdirectory(".fname.")",'~'.expand(""))
+    elseif fname =~ '^/'
+      " not windows, full path given
+      let ret= fname
+      "    "" call Decho("unix+full path: isdirectory(".fname.")",'~'.expand(""))
     else
-     " windows, relative path given
-     let ret= s:ComposePath(b:netrw_curdir,fname)
-"     "" call Decho("windows+rltv path: isdirectory(".fname.")",'~'.expand(""))
+      " not windows, relative path given
+      let ret= s:ComposePath(b:netrw_curdir,fname)
+      "    "" call Decho("unix+rltv path: isdirectory(".fname.")",'~'.expand(""))
     endif
-
-   elseif fname =~ '^/'
-    " not windows, full path given
-    let ret= fname
-"    "" call Decho("unix+full path: isdirectory(".fname.")",'~'.expand(""))
-   else
-    " not windows, relative path given
-    let ret= s:ComposePath(b:netrw_curdir,fname)
-"    "" call Decho("unix+rltv path: isdirectory(".fname.")",'~'.expand(""))
-   endif
   else
-   " vim and netrw agree on the current directory
-   let ret= fname
-"   "" call Decho("vim and netrw agree on current directory (g:netrw_keepdir=".g:netrw_keepdir.")",'~'.expand(""))
-"   "" call Decho("vim   directory: ".getcwd(),'~'.expand(""))
-"   "" call Decho("netrw directory: ".(exists("b:netrw_curdir")? b:netrw_curdir : 'n/a'),'~'.expand(""))
+    " vim and netrw agree on the current directory
+    let ret= fname
+    "   "" call Decho("vim and netrw agree on current directory (g:netrw_keepdir=".g:netrw_keepdir.")",'~'.expand(""))
+    "   "" call Decho("vim   directory: ".getcwd(),'~'.expand(""))
+    "   "" call Decho("netrw directory: ".(exists("b:netrw_curdir")? b:netrw_curdir : 'n/a'),'~'.expand(""))
   endif
 
-"  "" call Dret("s:NetrwFile ".ret)
+  "  "" call Dret("s:NetrwFile ".ret)
   return ret
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwFileInfo: supports qf (query for file information) {{{2
 fun! s:NetrwFileInfo(islocal,fname)
-"  call Dfunc("s:NetrwFileInfo(islocal=".a:islocal." fname<".a:fname.">) b:netrw_curdir<".b:netrw_curdir.">")
+  "  call Dfunc("s:NetrwFileInfo(islocal=".a:islocal." fname<".a:fname.">) b:netrw_curdir<".b:netrw_curdir.">")
   let ykeep= @@
   if a:islocal
-   let lsopt= "-lsad"
-   if g:netrw_sizestyle =~# 'H'
-    let lsopt= "-lsadh"
-   elseif g:netrw_sizestyle =~# 'h'
-    let lsopt= "-lsadh --si"
-   endif
-"   call Decho("(s:NetrwFileInfo) lsopt<".lsopt.">")
-   if (has("unix") || has("macunix")) && executable("/bin/ls")
-
-    if getline(".") == "../"
-     echo system("/bin/ls ".lsopt." ".s:ShellEscape(".."))
-"     call Decho("#1: echo system(/bin/ls -lsad ".s:ShellEscape(..).")",'~'.expand(""))
-
-    elseif w:netrw_liststyle == s:TREELIST && getline(".") !~ '^'.s:treedepthstring
-     echo system("/bin/ls ".lsopt." ".s:ShellEscape(b:netrw_curdir))
-"     call Decho("#2: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir).")",'~'.expand(""))
+    let lsopt= "-lsad"
+    if g:netrw_sizestyle =~# 'H'
+      let lsopt= "-lsadh"
+    elseif g:netrw_sizestyle =~# 'h'
+      let lsopt= "-lsadh --si"
+    endif
+    "   call Decho("(s:NetrwFileInfo) lsopt<".lsopt.">")
+    if (has("unix") || has("macunix")) && executable("/bin/ls")
 
-    elseif exists("b:netrw_curdir")
-      echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,a:fname)))
-"      call Decho("#3: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir.a:fname).")",'~'.expand(""))
+      if getline(".") == "../"
+        echo system("/bin/ls ".lsopt." ".s:ShellEscape(".."))
+        "     call Decho("#1: echo system(/bin/ls -lsad ".s:ShellEscape(..).")",'~'.expand(""))
 
+      elseif w:netrw_liststyle == s:TREELIST && getline(".") !~ '^'.s:treedepthstring
+        echo system("/bin/ls ".lsopt." ".s:ShellEscape(b:netrw_curdir))
+        "     call Decho("#2: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir).")",'~'.expand(""))
+
+      elseif exists("b:netrw_curdir")
+        echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,a:fname)))
+        "      call Decho("#3: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir.a:fname).")",'~'.expand(""))
+
+      else
+        "     call Decho('using ls '.a:fname." using cwd<".getcwd().">",'~'.expand(""))
+        echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:NetrwFile(a:fname)))
+        "     call Decho("#5: echo system(/bin/ls -lsad ".s:ShellEscape(a:fname).")",'~'.expand(""))
+      endif
     else
-"     call Decho('using ls '.a:fname." using cwd<".getcwd().">",'~'.expand(""))
-     echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:NetrwFile(a:fname)))
-"     call Decho("#5: echo system(/bin/ls -lsad ".s:ShellEscape(a:fname).")",'~'.expand(""))
-    endif
-   else
-    " use vim functions to return information about file below cursor
-"    call Decho("using vim functions to query for file info",'~'.expand(""))
-    if !isdirectory(s:NetrwFile(a:fname)) && !filereadable(s:NetrwFile(a:fname)) && a:fname =~ '[*@/]'
-     let fname= substitute(a:fname,".$","","")
-    else
-     let fname= a:fname
-    endif
-    let t  = getftime(s:NetrwFile(fname))
-    let sz = getfsize(s:NetrwFile(fname))
-    if g:netrw_sizestyle =~# "[hH]"
-     let sz= s:NetrwHumanReadable(sz)
+      " use vim functions to return information about file below cursor
+      "    call Decho("using vim functions to query for file info",'~'.expand(""))
+      if !isdirectory(s:NetrwFile(a:fname)) && !filereadable(s:NetrwFile(a:fname)) && a:fname =~ '[*@/]'
+        let fname= substitute(a:fname,".$","","")
+      else
+        let fname= a:fname
+      endif
+      let t  = getftime(s:NetrwFile(fname))
+      let sz = getfsize(s:NetrwFile(fname))
+      if g:netrw_sizestyle =~# "[hH]"
+        let sz= s:NetrwHumanReadable(sz)
+      endif
+      echo a:fname.":  ".sz."  ".strftime(g:netrw_timefmt,getftime(s:NetrwFile(fname)))
+      "    call Decho("fname.":  ".sz."  ".strftime(g:netrw_timefmt,getftime(fname)),'~'.expand(""))
     endif
-    echo a:fname.":  ".sz."  ".strftime(g:netrw_timefmt,getftime(s:NetrwFile(fname)))
-"    call Decho("fname.":  ".sz."  ".strftime(g:netrw_timefmt,getftime(fname)),'~'.expand(""))
-   endif
   else
-   echo "sorry, \"qf\" not supported yet for remote files"
+    echo "sorry, \"qf\" not supported yet for remote files"
   endif
   let @@= ykeep
-"  call Dret("s:NetrwFileInfo")
+  "  call Dret("s:NetrwFileInfo")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwFullPath: returns the full path to a directory and/or file {{{2
 fun! s:NetrwFullPath(filename)
-"  " call Dfunc("s:NetrwFullPath(filename<".a:filename.">)")
+  "  " call Dfunc("s:NetrwFullPath(filename<".a:filename.">)")
   let filename= a:filename
   if filename !~ '^/'
-   let filename= resolve(getcwd().'/'.filename)
+    let filename= resolve(getcwd().'/'.filename)
   endif
   if filename != "/" && filename =~ '/$'
-   let filename= substitute(filename,'/$','','')
+    let filename= substitute(filename,'/$','','')
   endif
-"  " call Dret("s:NetrwFullPath <".filename.">")
+  "  " call Dret("s:NetrwFullPath <".filename.">")
   return filename
 endfun
 
@@ -4029,165 +4030,165 @@ endfun
 "           1=re-used buffer (buffer not cleared)
 "  Nov 09, 2020: tst952 shows that when user does :set hidden that NetrwGetBuffer will come up with a [No Name] buffer (hid fix)
 fun! s:NetrwGetBuffer(islocal,dirname)
-"  call Dfunc("s:NetrwGetBuffer(islocal=".a:islocal." dirname<".a:dirname.">) liststyle=".g:netrw_liststyle)
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." hid=".&hid,'~'.expand(""))
-"  call Decho("netrwbuf dictionary=".(exists("s:netrwbuf")? string(s:netrwbuf) : 'n/a'),'~'.expand(""))
-"  call Dredir("ls!","s:NetrwGetBuffer")
+  "  call Dfunc("s:NetrwGetBuffer(islocal=".a:islocal." dirname<".a:dirname.">) liststyle=".g:netrw_liststyle)
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." hid=".&hid,'~'.expand(""))
+  "  call Decho("netrwbuf dictionary=".(exists("s:netrwbuf")? string(s:netrwbuf) : 'n/a'),'~'.expand(""))
+  "  call Dredir("ls!","s:NetrwGetBuffer")
   let dirname= a:dirname
 
   " re-use buffer if possible {{{3
-"  call Decho("--re-use a buffer if possible--",'~'.expand(""))
+  "  call Decho("--re-use a buffer if possible--",'~'.expand(""))
   if !exists("s:netrwbuf")
-"   call Decho("  s:netrwbuf initialized to {}",'~'.expand(""))
-   let s:netrwbuf= {}
+    "   call Decho("  s:netrwbuf initialized to {}",'~'.expand(""))
+    let s:netrwbuf= {}
   endif
-"  call Decho("  s:netrwbuf         =".string(s:netrwbuf),'~'.expand(""))
-"  call Decho("  w:netrw_liststyle  =".(exists("w:netrw_liststyle")? w:netrw_liststyle : "n/a"),'~'.expand(""))
+  "  call Decho("  s:netrwbuf         =".string(s:netrwbuf),'~'.expand(""))
+  "  call Decho("  w:netrw_liststyle  =".(exists("w:netrw_liststyle")? w:netrw_liststyle : "n/a"),'~'.expand(""))
 
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-   let bufnum = -1
+    let bufnum = -1
 
-   if !empty(s:netrwbuf) && has_key(s:netrwbuf,s:NetrwFullPath(dirname))
-    if has_key(s:netrwbuf,"NetrwTreeListing")
-     let bufnum= s:netrwbuf["NetrwTreeListing"]
+    if !empty(s:netrwbuf) && has_key(s:netrwbuf,s:NetrwFullPath(dirname))
+      if has_key(s:netrwbuf,"NetrwTreeListing")
+        let bufnum= s:netrwbuf["NetrwTreeListing"]
+      else
+        let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)]
+      endif
+      "    call Decho("  NetrwTreeListing: bufnum#".bufnum,'~'.expand(""))
+      if !bufexists(bufnum)
+        call remove(s:netrwbuf,"NetrwTreeListing")
+        let bufnum= -1
+      endif
+    elseif bufnr("NetrwTreeListing") != -1
+      let bufnum= bufnr("NetrwTreeListing")
+      "    call Decho("  NetrwTreeListing".": bufnum#".bufnum,'~'.expand(""))
     else
-     let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)]
-    endif
-"    call Decho("  NetrwTreeListing: bufnum#".bufnum,'~'.expand(""))
-    if !bufexists(bufnum)
-     call remove(s:netrwbuf,"NetrwTreeListing")
-     let bufnum= -1
+      "    call Decho("  did not find a NetrwTreeListing buffer",'~'.expand(""))
+      let bufnum= -1
     endif
-   elseif bufnr("NetrwTreeListing") != -1
-    let bufnum= bufnr("NetrwTreeListing")
-"    call Decho("  NetrwTreeListing".": bufnum#".bufnum,'~'.expand(""))
-   else
-"    call Decho("  did not find a NetrwTreeListing buffer",'~'.expand(""))
-     let bufnum= -1
-   endif
 
   elseif has_key(s:netrwbuf,s:NetrwFullPath(dirname))
-   let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)]
-"   call Decho("  lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnum,'~'.expand(""))
-   if !bufexists(bufnum)
-    call remove(s:netrwbuf,s:NetrwFullPath(dirname))
-    let bufnum= -1
-   endif
+    let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)]
+    "   call Decho("  lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnum,'~'.expand(""))
+    if !bufexists(bufnum)
+      call remove(s:netrwbuf,s:NetrwFullPath(dirname))
+      let bufnum= -1
+    endif
 
   else
-"   call Decho("  lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."] not a key",'~'.expand(""))
-   let bufnum= -1
+    "   call Decho("  lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."] not a key",'~'.expand(""))
+    let bufnum= -1
   endif
-"  call Decho("  bufnum#".bufnum,'~'.expand(""))
+  "  call Decho("  bufnum#".bufnum,'~'.expand(""))
 
   " highjack the current buffer
   "   IF the buffer already has the desired name
   "   AND it is empty
   let curbuf = bufname("%")
   if curbuf == '.'
-   let curbuf = getcwd()
-  endif
-"  call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)")
-"  call Decho("deciding if netrw may highjack the current buffer#".bufnr("%")."<".curbuf.">",'~'.expand(""))
-"  call Decho("..dirname<".dirname.">  IF dirname == bufname",'~'.expand(""))
-"  call Decho("..curbuf<".curbuf.">",'~'.expand(""))
-"  call Decho("..line($)=".line("$")." AND this is 1",'~'.expand(""))
-"  call Decho("..getline(%)<".getline("%").">  AND this line is empty",'~'.expand(""))
+    let curbuf = getcwd()
+  endif
+  "  call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)")
+  "  call Decho("deciding if netrw may highjack the current buffer#".bufnr("%")."<".curbuf.">",'~'.expand(""))
+  "  call Decho("..dirname<".dirname.">  IF dirname == bufname",'~'.expand(""))
+  "  call Decho("..curbuf<".curbuf.">",'~'.expand(""))
+  "  call Decho("..line($)=".line("$")." AND this is 1",'~'.expand(""))
+  "  call Decho("..getline(%)<".getline("%").">  AND this line is empty",'~'.expand(""))
   if dirname == curbuf && line("$") == 1 && getline("%") == ""
-"   call Dret("s:NetrwGetBuffer 0 : highjacking buffer#".bufnr("%"))
-   return 0
+    "   call Dret("s:NetrwGetBuffer 0 : highjacking buffer#".bufnr("%"))
+    return 0
   else  " DEBUG
-"   call Decho("..did NOT highjack buffer",'~'.expand(""))
+    "   call Decho("..did NOT highjack buffer",'~'.expand(""))
   endif
   " Aug 14, 2021: was thinking about looking for a [No Name] buffer here and using it, but that might cause problems
 
   " get enew buffer and name it -or- re-use buffer {{{3
   if bufnum < 0      " get enew buffer and name it
-"   call Decho("--get enew buffer and name it  (bufnum#".bufnum."<0 OR bufexists(".bufnum.")=".bufexists(bufnum)."==0)",'~'.expand(""))
-   call s:NetrwEnew(dirname)
-"   call Decho("  got enew buffer#".bufnr("%")." (altbuf<".expand("#").">)",'~'.expand(""))
-   " name the buffer
-   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-    " Got enew buffer; transform into a NetrwTreeListing
-"    call Decho("--transform enew buffer#".bufnr("%")." into a NetrwTreeListing --",'~'.expand(""))
-    let w:netrw_treebufnr = bufnr("%")
-    call s:NetrwBufRename("NetrwTreeListing")
-    if g:netrw_use_noswf
-     setl nobl bt=nofile noswf
+    "   call Decho("--get enew buffer and name it  (bufnum#".bufnum."<0 OR bufexists(".bufnum.")=".bufexists(bufnum)."==0)",'~'.expand(""))
+    call s:NetrwEnew(dirname)
+    "   call Decho("  got enew buffer#".bufnr("%")." (altbuf<".expand("#").">)",'~'.expand(""))
+    " name the buffer
+    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+      " Got enew buffer; transform into a NetrwTreeListing
+      "    call Decho("--transform enew buffer#".bufnr("%")." into a NetrwTreeListing --",'~'.expand(""))
+      let w:netrw_treebufnr = bufnr("%")
+      call s:NetrwBufRename("NetrwTreeListing")
+      if g:netrw_use_noswf
+        setl nobl bt=nofile noswf
+      else
+        setl nobl bt=nofile
+      endif
+      nnoremap   [[       :sil call TreeListMove('[[')
+      nnoremap   ]]       :sil call TreeListMove(']]')
+      nnoremap   []       :sil call TreeListMove('[]')
+      nnoremap   ][       :sil call TreeListMove('][')
+      "    call Decho("  tree listing bufnr=".w:netrw_treebufnr,'~'.expand(""))
     else
-     setl nobl bt=nofile
-    endif
-    nnoremap   [[       :sil call TreeListMove('[[')
-    nnoremap   ]]       :sil call TreeListMove(']]')
-    nnoremap   []       :sil call TreeListMove('[]')
-    nnoremap   ][       :sil call TreeListMove('][')
-"    call Decho("  tree listing bufnr=".w:netrw_treebufnr,'~'.expand(""))
-   else
-    call s:NetrwBufRename(dirname)
-    " enter the new buffer into the s:netrwbuf dictionary
-    let s:netrwbuf[s:NetrwFullPath(dirname)]= bufnr("%")
-"    call Decho("update netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnr("%"),'~'.expand(""))
-"    call Decho("netrwbuf dictionary=".string(s:netrwbuf),'~'.expand(""))
-   endif
-"   call Decho("  named enew buffer#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
+      call s:NetrwBufRename(dirname)
+      " enter the new buffer into the s:netrwbuf dictionary
+      let s:netrwbuf[s:NetrwFullPath(dirname)]= bufnr("%")
+      "    call Decho("update netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnr("%"),'~'.expand(""))
+      "    call Decho("netrwbuf dictionary=".string(s:netrwbuf),'~'.expand(""))
+    endif
+    "   call Decho("  named enew buffer#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
 
   else " Re-use the buffer
-"   call Decho("--re-use buffer#".bufnum." (bufnum#".bufnum.">=0 AND bufexists(".bufnum.")=".bufexists(bufnum)."!=0)",'~'.expand(""))
-   " ignore all events
-   let eikeep= &ei
-   setl ei=all
-
-   if &ft == "netrw"
-"    call Decho("buffer type is netrw; not using keepalt with b ".bufnum)
-    exe "sil! NetrwKeepj noswapfile b ".bufnum
-"    call Dredir("ls!","one")
-   else
-"    call Decho("buffer type is not netrw; using keepalt with b ".bufnum)
-    call s:NetrwEditBuf(bufnum)
-"    call Dredir("ls!","two")
-   endif
-"   call Decho("  line($)=".line("$"),'~'.expand(""))
-   if bufname("%") == '.'
-    call s:NetrwBufRename(getcwd())
-   endif
-
-   " restore ei
-   let &ei= eikeep
-
-   if line("$") <= 1 && getline(1) == ""
-    " empty buffer
-    NetrwKeepj call s:NetrwListSettings(a:islocal)
-"    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"    call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but its empty, so refresh it")
-    return 0
+    "   call Decho("--re-use buffer#".bufnum." (bufnum#".bufnum.">=0 AND bufexists(".bufnum.")=".bufexists(bufnum)."!=0)",'~'.expand(""))
+    " ignore all events
+    let eikeep= &ei
+    setl ei=all
 
-   elseif g:netrw_fastbrowse == 0 || (a:islocal && g:netrw_fastbrowse == 1)
-"    call Decho("g:netrw_fastbrowse=".g:netrw_fastbrowse." a:islocal=".a:islocal.": clear buffer",'~'.expand(""))
-    NetrwKeepj call s:NetrwListSettings(a:islocal)
-    sil NetrwKeepj %d _
-"    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"    call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but refreshing due to g:netrw_fastbrowse=".g:netrw_fastbrowse)
-    return 0
+    if &ft == "netrw"
+      "    call Decho("buffer type is netrw; not using keepalt with b ".bufnum)
+      exe "sil! NetrwKeepj noswapfile b ".bufnum
+      "    call Dredir("ls!","one")
+    else
+      "    call Decho("buffer type is not netrw; using keepalt with b ".bufnum)
+      call s:NetrwEditBuf(bufnum)
+      "    call Dredir("ls!","two")
+    endif
+    "   call Decho("  line($)=".line("$"),'~'.expand(""))
+    if bufname("%") == '.'
+      call s:NetrwBufRename(getcwd())
+    endif
 
-   elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-"    call Decho("--re-use tree listing--",'~'.expand(""))
-"    call Decho("  clear buffer<".expand("%")."> with :%d",'~'.expand(""))
-    setl ma
-    sil NetrwKeepj %d _
-    NetrwKeepj call s:NetrwListSettings(a:islocal)
-"    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"    call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but treelist mode always needs a refresh")
-    return 0
+    " restore ei
+    let &ei= eikeep
+
+    if line("$") <= 1 && getline(1) == ""
+      " empty buffer
+      NetrwKeepj call s:NetrwListSettings(a:islocal)
+      "    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+      "    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+      "    call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but its empty, so refresh it")
+      return 0
+
+    elseif g:netrw_fastbrowse == 0 || (a:islocal && g:netrw_fastbrowse == 1)
+      "    call Decho("g:netrw_fastbrowse=".g:netrw_fastbrowse." a:islocal=".a:islocal.": clear buffer",'~'.expand(""))
+      NetrwKeepj call s:NetrwListSettings(a:islocal)
+      sil NetrwKeepj %d _
+      "    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+      "    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+      "    call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but refreshing due to g:netrw_fastbrowse=".g:netrw_fastbrowse)
+      return 0
+
+    elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+      "    call Decho("--re-use tree listing--",'~'.expand(""))
+      "    call Decho("  clear buffer<".expand("%")."> with :%d",'~'.expand(""))
+      setl ma
+      sil NetrwKeepj %d _
+      NetrwKeepj call s:NetrwListSettings(a:islocal)
+      "    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+      "    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+      "    call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but treelist mode always needs a refresh")
+      return 0
 
-   else
-"    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"    call Dret("s:NetrwGetBuffer 1")
-    return 1
-   endif
+    else
+      "    call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+      "    call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+      "    call Dret("s:NetrwGetBuffer 1")
+      return 1
+    endif
   endif
 
   " do netrw settings: make this buffer not-a-file, modifiable, not line-numbered, etc {{{3
@@ -4195,19 +4196,19 @@ fun! s:NetrwGetBuffer(islocal,dirname)
   "  slow   0         D      D      Deleting a buffer implies it will not be re-used (slow)
   "  med    1         D      H
   "  fast   2         H      H
-"  call Decho("--do netrw settings: make this buffer#".bufnr("%")." not-a-file, modifiable, not line-numbered, etc--",'~'.expand(""))
+  "  call Decho("--do netrw settings: make this buffer#".bufnr("%")." not-a-file, modifiable, not line-numbered, etc--",'~'.expand(""))
   let fname= expand("%")
   NetrwKeepj call s:NetrwListSettings(a:islocal)
   call s:NetrwBufRename(fname)
 
   " delete all lines from buffer {{{3
-"  call Decho("--delete all lines from buffer--",'~'.expand(""))
-"  call Decho("  clear buffer<".expand("%")."> with :%d",'~'.expand(""))
+  "  call Decho("--delete all lines from buffer--",'~'.expand(""))
+  "  call Decho("  clear buffer<".expand("%")."> with :%d",'~'.expand(""))
   sil! keepalt NetrwKeepj %d _
 
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"  call Dret("s:NetrwGetBuffer 0")
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+  "  call Dret("s:NetrwGetBuffer 0")
   return 0
 endfun
 
@@ -4216,23 +4217,23 @@ endfun
 "   Change backslashes to forward slashes, if any.
 "   If doesc is true, escape certain troublesome characters
 fun! s:NetrwGetcwd(doesc)
-"  call Dfunc("NetrwGetcwd(doesc=".a:doesc.")")
+  "  call Dfunc("NetrwGetcwd(doesc=".a:doesc.")")
   let curdir= substitute(getcwd(),'\\','/','ge')
   if curdir !~ '[\/]$'
-   let curdir= curdir.'/'
+    let curdir= curdir.'/'
   endif
   if a:doesc
-   let curdir= fnameescape(curdir)
+    let curdir= fnameescape(curdir)
   endif
-"  call Dret("NetrwGetcwd <".curdir.">")
+  "  call Dret("NetrwGetcwd <".curdir.">")
   return curdir
 endfun
 
 " ---------------------------------------------------------------------
 "  s:NetrwGetWord: it gets the directory/file named under the cursor {{{2
 fun! s:NetrwGetWord()
-"  call Dfunc("s:NetrwGetWord() liststyle=".s:ShowStyle()." virtcol=".virtcol("."))
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+  "  call Dfunc("s:NetrwGetWord() liststyle=".s:ShowStyle()." virtcol=".virtcol("."))
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
   let keepsol= &l:sol
   setl nosol
 
@@ -4240,114 +4241,114 @@ fun! s:NetrwGetWord()
 
   " insure that w:netrw_liststyle is set up
   if !exists("w:netrw_liststyle")
-   if exists("g:netrw_liststyle")
-    let w:netrw_liststyle= g:netrw_liststyle
-   else
-    let w:netrw_liststyle= s:THINLIST
-   endif
-"   call Decho("w:netrw_liststyle=".w:netrw_liststyle,'~'.expand(""))
+    if exists("g:netrw_liststyle")
+      let w:netrw_liststyle= g:netrw_liststyle
+    else
+      let w:netrw_liststyle= s:THINLIST
+    endif
+    "   call Decho("w:netrw_liststyle=".w:netrw_liststyle,'~'.expand(""))
   endif
 
   if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt
-   " Active Banner support
-"   call Decho("active banner handling",'~'.expand(""))
-   NetrwKeepj norm! 0
-   let dirname= "./"
-   let curline= getline('.')
-
-   if curline =~# '"\s*Sorted by\s'
-    NetrwKeepj norm! "_s
-    let s:netrw_skipbrowse= 1
-    echo 'Pressing "s" also works'
-
-   elseif curline =~# '"\s*Sort sequence:'
-    let s:netrw_skipbrowse= 1
-    echo 'Press "S" to edit sorting sequence'
-
-   elseif curline =~# '"\s*Quick Help:'
-    NetrwKeepj norm! ?
-    let s:netrw_skipbrowse= 1
-
-   elseif curline =~# '"\s*\%(Hiding\|Showing\):'
-    NetrwKeepj norm! a
-    let s:netrw_skipbrowse= 1
-    echo 'Pressing "a" also works'
-
-   elseif line("$") > w:netrw_bannercnt
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt
-   endif
+    " Active Banner support
+    "   call Decho("active banner handling",'~'.expand(""))
+    NetrwKeepj norm! 0
+    let dirname= "./"
+    let curline= getline('.')
+
+    if curline =~# '"\s*Sorted by\s'
+      NetrwKeepj norm! "_s
+      let s:netrw_skipbrowse= 1
+      echo 'Pressing "s" also works'
+
+    elseif curline =~# '"\s*Sort sequence:'
+      let s:netrw_skipbrowse= 1
+      echo 'Press "S" to edit sorting sequence'
+
+    elseif curline =~# '"\s*Quick Help:'
+      NetrwKeepj norm! ?
+      let s:netrw_skipbrowse= 1
+
+    elseif curline =~# '"\s*\%(Hiding\|Showing\):'
+      NetrwKeepj norm! a
+      let s:netrw_skipbrowse= 1
+      echo 'Pressing "a" also works'
+
+    elseif line("$") > w:netrw_bannercnt
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt
+    endif
 
   elseif w:netrw_liststyle == s:THINLIST
-"   call Decho("thin column handling",'~'.expand(""))
-   NetrwKeepj norm! 0
-   let dirname= substitute(getline('.'),'\t -->.*$','','')
+    "   call Decho("thin column handling",'~'.expand(""))
+    NetrwKeepj norm! 0
+    let dirname= substitute(getline('.'),'\t -->.*$','','')
 
   elseif w:netrw_liststyle == s:LONGLIST
-"   call Decho("long column handling",'~'.expand(""))
-   NetrwKeepj norm! 0
-   let dirname= substitute(getline('.'),'^\(\%(\S\+ \)*\S\+\).\{-}$','\1','e')
+    "   call Decho("long column handling",'~'.expand(""))
+    NetrwKeepj norm! 0
+    let dirname= substitute(getline('.'),'^\(\%(\S\+ \)*\S\+\).\{-}$','\1','e')
 
   elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-"   call Decho("treelist handling",'~'.expand(""))
-   let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e')
-   let dirname= substitute(dirname,'\t -->.*$','','')
+    "   call Decho("treelist handling",'~'.expand(""))
+    let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e')
+    let dirname= substitute(dirname,'\t -->.*$','','')
 
   else
-"   call Decho("obtain word from wide listing",'~'.expand(""))
-   let dirname= getline('.')
+    "   call Decho("obtain word from wide listing",'~'.expand(""))
+    let dirname= getline('.')
 
-   if !exists("b:netrw_cpf")
-    let b:netrw_cpf= 0
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
+    if !exists("b:netrw_cpf")
+      let b:netrw_cpf= 0
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
     call histdel("/",-1)
-"   "call Decho("computed cpf=".b:netrw_cpf,'~'.expand(""))
-   endif
-
-"   call Decho("buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
-   let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf
-"   call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart."  bannercnt=".w:netrw_bannercnt,'~'.expand(""))
-"   call Decho("1: dirname<".dirname.">",'~'.expand(""))
-   if filestart == 0
+    "   "call Decho("computed cpf=".b:netrw_cpf,'~'.expand(""))
+  endif
+
+  "   call Decho("buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
+  let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf
+  "   call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart."  bannercnt=".w:netrw_bannercnt,'~'.expand(""))
+  "   call Decho("1: dirname<".dirname.">",'~'.expand(""))
+  if filestart == 0
     NetrwKeepj norm! 0ma
-   else
+  else
     call cursor(line("."),filestart+1)
     NetrwKeepj norm! ma
-   endif
-
-   let dict={}
-   " save the unnamed register and register 0-9 and a
-   let dict.a=[getreg('a'), getregtype('a')]
-   for i in range(0, 9)
-     let dict[i] = [getreg(i), getregtype(i)]
-   endfor
-   let dict.unnamed = [getreg(''), getregtype('')]
-
-   let eofname= filestart + b:netrw_cpf + 1
-   if eofname <= col("$")
+  endif
+
+  let dict={}
+  " save the unnamed register and register 0-9 and a
+  let dict.a=[getreg('a'), getregtype('a')]
+  for i in range(0, 9)
+    let dict[i] = [getreg(i), getregtype(i)]
+  endfor
+  let dict.unnamed = [getreg(''), getregtype('')]
+
+  let eofname= filestart + b:netrw_cpf + 1
+  if eofname <= col("$")
     call cursor(line("."),filestart+b:netrw_cpf+1)
     NetrwKeepj norm! "ay`a
-   else
+  else
     NetrwKeepj norm! "ay$
-   endif
+  endif
 
-   let dirname = @a
-   call s:RestoreRegister(dict)
+  let dirname = @a
+  call s:RestoreRegister(dict)
 
-"   call Decho("2: dirname<".dirname.">",'~'.expand(""))
-   let dirname= substitute(dirname,'\s\+$','','e')
-"   call Decho("3: dirname<".dirname.">",'~'.expand(""))
-  endif
+  "   call Decho("2: dirname<".dirname.">",'~'.expand(""))
+  let dirname= substitute(dirname,'\s\+$','','e')
+  "   call Decho("3: dirname<".dirname.">",'~'.expand(""))
+endif
 
-  " symlinks are indicated by a trailing "@".  Remove it before further processing.
-  let dirname= substitute(dirname,"@$","","")
+" symlinks are indicated by a trailing "@".  Remove it before further processing.
+let dirname= substitute(dirname,"@$","","")
 
-  " executables are indicated by a trailing "*".  Remove it before further processing.
-  let dirname= substitute(dirname,"\*$","","")
+" executables are indicated by a trailing "*".  Remove it before further processing.
+let dirname= substitute(dirname,"\*$","","")
 
-  let &l:sol= keepsol
+let &l:sol= keepsol
 
 "  call Dret("s:NetrwGetWord <".dirname.">")
-  return dirname
+return dirname
 endfun
 
 " ---------------------------------------------------------------------
@@ -4355,27 +4356,27 @@ endfun
 "                      g:netrw_bufsettings will be used after the listing is produced.
 "                      Called by s:NetrwGetBuffer()
 fun! s:NetrwListSettings(islocal)
-"  call Dfunc("s:NetrwListSettings(islocal=".a:islocal.")")
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Dfunc("s:NetrwListSettings(islocal=".a:islocal.")")
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
   let fname= bufname("%")
-"  "  call Decho("setl bt=nofile nobl ma nonu nowrap noro nornu",'~'.expand(""))
+  "  "  call Decho("setl bt=nofile nobl ma nonu nowrap noro nornu",'~'.expand(""))
   "              nobl noma nomod nonu noma nowrap ro   nornu  (std g:netrw_bufsettings)
   setl bt=nofile nobl ma         nonu      nowrap noro nornu
   call s:NetrwBufRename(fname)
   if g:netrw_use_noswf
-   setl noswf
+    setl noswf
   endif
-"  call Dredir("ls!","s:NetrwListSettings")
-"  call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand(""))
+  "  call Dredir("ls!","s:NetrwListSettings")
+  "  call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand(""))
   exe "setl ts=".(g:netrw_maxfilenamelen+1)
   setl isk+=.,~,-
   if g:netrw_fastbrowse > a:islocal
-   setl bh=hide
+    setl bh=hide
   else
-   setl bh=delete
+    setl bh=delete
   endif
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"  call Dret("s:NetrwListSettings")
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Dret("s:NetrwListSettings")
 endfun
 
 " ---------------------------------------------------------------------
@@ -4391,29 +4392,29 @@ fun! s:NetrwListStyle(islocal)
 
   " repoint t:netrw_lexbufnr if appropriate
   if exists("t:netrw_lexbufnr") && bufnr("%") == t:netrw_lexbufnr
-   let repointlexbufnr= 1
+    let repointlexbufnr= 1
   endif
 
   if w:netrw_liststyle == s:THINLIST
-   " use one column listing
-   let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
+    " use one column listing
+    let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
 
   elseif w:netrw_liststyle == s:LONGLIST
-   " use long list
-   let g:netrw_list_cmd = g:netrw_list_cmd." -l"
+    " use long list
+    let g:netrw_list_cmd = g:netrw_list_cmd." -l"
 
   elseif w:netrw_liststyle == s:WIDELIST
-   " give wide list
-   let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
+    " give wide list
+    let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
 
   elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-   let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
+    let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
 
   else
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"bad value for g:netrw_liststyle (=".w:netrw_liststyle.")",46)
-   let g:netrw_liststyle = s:THINLIST
-   let w:netrw_liststyle = g:netrw_liststyle
-   let g:netrw_list_cmd  = substitute(g:netrw_list_cmd,' -l','','ge')
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"bad value for g:netrw_liststyle (=".w:netrw_liststyle.")",46)
+    let g:netrw_liststyle = s:THINLIST
+    let w:netrw_liststyle = g:netrw_liststyle
+    let g:netrw_list_cmd  = substitute(g:netrw_list_cmd,' -l','','ge')
   endif
   setl ma noro
 
@@ -4428,11 +4429,11 @@ fun! s:NetrwListStyle(islocal)
 
   " repoint t:netrw_lexbufnr if appropriate
   if exists("repointlexbufnr")
-   let t:netrw_lexbufnr= bufnr("%")
+    let t:netrw_lexbufnr= bufnr("%")
   endif
 
   " restore position; keep cursor on the filename
-"  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
+  "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
   NetrwKeepj call winrestview(svpos)
   let @@= ykeep
 
@@ -4451,16 +4452,16 @@ fun! s:NetrwBannerCtrl(islocal)
 
   " keep cursor on the filename
   if g:netrw_banner && exists("w:netrw_bannercnt") && line(".") >= w:netrw_bannercnt
-   let fname= s:NetrwGetWord()
-   sil NetrwKeepj $
-   let result= search('\%(^\%(|\+\s\)\=\|\s\{2,}\)\zs'.escape(fname,'.\[]*$^').'\%(\s\{2,}\|$\)','bc')
-" "  call Decho("search result=".result." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'N/A'),'~'.expand(""))
-   if result <= 0 && exists("w:netrw_bannercnt")
-    exe "NetrwKeepj ".w:netrw_bannercnt
-   endif
+    let fname= s:NetrwGetWord()
+    sil NetrwKeepj $
+    let result= search('\%(^\%(|\+\s\)\=\|\s\{2,}\)\zs'.escape(fname,'.\[]*$^').'\%(\s\{2,}\|$\)','bc')
+    " "  call Decho("search result=".result." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'N/A'),'~'.expand(""))
+    if result <= 0 && exists("w:netrw_bannercnt")
+      exe "NetrwKeepj ".w:netrw_bannercnt
+    endif
   endif
   let @@= ykeep
-"  call Dret("s:NetrwBannerCtrl : g:netrw_banner=".g:netrw_banner)
+  "  call Dret("s:NetrwBannerCtrl : g:netrw_banner=".g:netrw_banner)
 endfun
 
 " ---------------------------------------------------------------------
@@ -4476,53 +4477,53 @@ endfun
 "  With bang: deletes files/directories from Netrw's bookmark system
 fun! s:NetrwBookmark(del,...)
   if a:0 == 0
-   if &ft == "netrw"
-    let curbufnr = bufnr("%")
+    if &ft == "netrw"
+      let curbufnr = bufnr("%")
+
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+        " for every filename in the marked list
+        let svpos  = winsaveview()
+        let islocal= expand("%") !~ '^\a\{3,}://'
+        for fname in s:netrwmarkfilelist_{curbufnr}
+          if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
+        endfor
+        let curdir  = exists("b:netrw_curdir")? b:netrw_curdir : getcwd()
+        call s:NetrwUnmarkList(curbufnr,curdir)
+        NetrwKeepj call s:NetrwRefresh(islocal,s:NetrwBrowseChgDir(islocal,'./',0))
+        NetrwKeepj call winrestview(svpos)
+      else
+        let fname= s:NetrwGetWord()
+        if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
+      endif
 
-    if exists("s:netrwmarkfilelist_{curbufnr}")
-     " for every filename in the marked list
-     let svpos  = winsaveview()
-     let islocal= expand("%") !~ '^\a\{3,}://'
-     for fname in s:netrwmarkfilelist_{curbufnr}
-      if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
-     endfor
-     let curdir  = exists("b:netrw_curdir")? b:netrw_curdir : getcwd()
-     call s:NetrwUnmarkList(curbufnr,curdir)
-     NetrwKeepj call s:NetrwRefresh(islocal,s:NetrwBrowseChgDir(islocal,'./',0))
-     NetrwKeepj call winrestview(svpos)
     else
-     let fname= s:NetrwGetWord()
-     if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
+      " bookmark currently open file
+      let fname= expand("%")
+      if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
     endif
 
-   else
-    " bookmark currently open file
-    let fname= expand("%")
-    if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
-   endif
-
   else
-   " bookmark specified files
-   "  attempts to infer if working remote or local
-   "  by deciding if the current file begins with an url
-   "  Globbing cannot be done remotely.
-   let islocal= expand("%") !~ '^\a\{3,}://'
-   let i = 1
-   while i <= a:0
-    if islocal
-     if v:version > 704 || (v:version == 704 && has("patch656"))
-      let mbfiles= glob(fnameescape(a:{i}),0,1,1)
-     else
-      let mbfiles= glob(fnameescape(a:{i}),0,1)
-     endif
-    else
-     let mbfiles= [a:{i}]
-    endif
-    for mbfile in mbfiles
-     if a:del|call s:DeleteBookmark(mbfile)|else|call s:MakeBookmark(mbfile)|endif
-    endfor
-    let i= i + 1
-   endwhile
+    " bookmark specified files
+    "  attempts to infer if working remote or local
+    "  by deciding if the current file begins with an url
+    "  Globbing cannot be done remotely.
+    let islocal= expand("%") !~ '^\a\{3,}://'
+    let i = 1
+    while i <= a:0
+      if islocal
+        if v:version > 704 || (v:version == 704 && has("patch656"))
+          let mbfiles= glob(fnameescape(a:{i}),0,1,1)
+        else
+          let mbfiles= glob(fnameescape(a:{i}),0,1)
+        endif
+      else
+        let mbfiles= [a:{i}]
+      endif
+      for mbfile in mbfiles
+        if a:del|call s:DeleteBookmark(mbfile)|else|call s:MakeBookmark(mbfile)|endif
+      endfor
+      let i= i + 1
+    endwhile
   endif
 
   " update the menu
@@ -4536,61 +4537,61 @@ endfun
 "                      (see s:NetrwMenu())
 fun! s:NetrwBookmarkMenu()
   if !exists("s:netrw_menucnt")
-   return
+    return
   endif
-"  call Dfunc("NetrwBookmarkMenu()  histcnt=".g:netrw_dirhistcnt." menucnt=".s:netrw_menucnt)
+  "  call Dfunc("NetrwBookmarkMenu()  histcnt=".g:netrw_dirhistcnt." menucnt=".s:netrw_menucnt)
 
   " the following test assures that gvim is running, has menus available, and has menus enabled.
   if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
-   if exists("g:NetrwTopLvlMenu")
-"    call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand(""))
-    exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks'
-    exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete'
-   endif
-   if !exists("s:netrw_initbookhist")
-    call s:NetrwBookHistRead()
-   endif
-
-   " show bookmarked places
-   if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0
-    let cnt= 1
-    for bmd in g:netrw_bookmarklist
-"     call Decho('sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmark.'.bmd.'	:e '.bmd,'~'.expand(""))
-     let bmd= escape(bmd,g:netrw_menu_escape)
-
-     " show bookmarks for goto menu
-     exe 'sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks.'.bmd.'	:e '.bmd."\"
-
-     " show bookmarks for deletion menu
-     exe 'sil! menu '.g:NetrwMenuPriority.".8.2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete.'.bmd.'	'.cnt."mB"
-     let cnt= cnt + 1
-    endfor
+    if exists("g:NetrwTopLvlMenu")
+      "    call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand(""))
+      exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks'
+      exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete'
+    endif
+    if !exists("s:netrw_initbookhist")
+      call s:NetrwBookHistRead()
+    endif
 
-   endif
+    " show bookmarked places
+    if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0
+      let cnt= 1
+      for bmd in g:netrw_bookmarklist
+        "     call Decho('sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmark.'.bmd.'     :e '.bmd,'~'.expand(""))
+        let bmd= escape(bmd,g:netrw_menu_escape)
 
-   " show directory browsing history
-   if g:netrw_dirhistmax > 0
-    let cnt     = g:netrw_dirhistcnt
-    let first   = 1
-    let histcnt = 0
-    while ( first || cnt != g:netrw_dirhistcnt )
-     let histcnt  = histcnt + 1
-     let priority = g:netrw_dirhistcnt + histcnt
-     if exists("g:netrw_dirhist_{cnt}")
-      let histdir= escape(g:netrw_dirhist_{cnt},g:netrw_menu_escape)
-"     call Decho('sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.'	:e '.histdir,'~'.expand(""))
-      exe 'sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.'	:e '.histdir."\"
-     endif
-     let first = 0
-     let cnt   = ( cnt - 1 ) % g:netrw_dirhistmax
-     if cnt < 0
-      let cnt= cnt + g:netrw_dirhistmax
-     endif
-    endwhile
-   endif
+        " show bookmarks for goto menu
+        exe 'sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks.'.bmd.'    :e '.bmd."\"
+
+        " show bookmarks for deletion menu
+        exe 'sil! menu '.g:NetrwMenuPriority.".8.2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete.'.bmd.'   '.cnt."mB"
+        let cnt= cnt + 1
+      endfor
+
+    endif
+
+    " show directory browsing history
+    if g:netrw_dirhistmax > 0
+      let cnt     = g:netrw_dirhistcnt
+      let first   = 1
+      let histcnt = 0
+      while ( first || cnt != g:netrw_dirhistcnt )
+        let histcnt  = histcnt + 1
+        let priority = g:netrw_dirhistcnt + histcnt
+        if exists("g:netrw_dirhist_{cnt}")
+          let histdir= escape(g:netrw_dirhist_{cnt},g:netrw_menu_escape)
+          "     call Decho('sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.'     :e '.histdir,'~'.expand(""))
+          exe 'sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.'    :e '.histdir."\"
+        endif
+        let first = 0
+        let cnt   = ( cnt - 1 ) % g:netrw_dirhistmax
+        if cnt < 0
+          let cnt= cnt + g:netrw_dirhistmax
+        endif
+      endwhile
+    endif
 
   endif
-"  call Dret("NetrwBookmarkMenu")
+  "  call Dret("NetrwBookmarkMenu")
 endfun
 
 " ---------------------------------------------------------------------
@@ -4604,10 +4605,10 @@ endfun
 fun! s:NetrwBrowseChgDir(islocal,newdir,cursor,...)
   let ykeep= @@
   if !exists("b:netrw_curdir")
-   " Don't try to change-directory: this can happen, for example, when netrw#ErrorMsg has been called
-   " and the current window is the NetrwMessage window.
-   let @@= ykeep
-   return
+    " Don't try to change-directory: this can happen, for example, when netrw#ErrorMsg has been called
+    " and the current window is the NetrwMessage window.
+    let @@= ykeep
+    return
   endif
 
   " NetrwBrowseChgDir; save options and initialize {{{3
@@ -4617,314 +4618,314 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,cursor,...)
 
   let newdir = a:newdir
   if a:cursor && exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop")
-   " dirname is the path to the word under the cursor
-   let dirname = s:NetrwTreePath(w:netrw_treetop)
-   " newdir resolves to a directory and points to a directory in dirname
-   " /tmp/test/folder_symlink/ -> /tmp/test/original_folder/
-   if a:islocal && fnamemodify(dirname, ':t') == newdir && isdirectory(resolve(dirname)) && resolve(dirname) == resolve(newdir)
-    let dirname = fnamemodify(resolve(dirname), ':p:h:h')
-    let newdir = fnamemodify(resolve(newdir), ':t')
-   endif
-   " Remove trailing "/"
-   let dirname = substitute(dirname, "/$", "", "")
-
-   " If the word under the cursor is a directory (except for ../), NetrwTreePath
-   " returns the full path, including the word under the cursor, remove it
-   if newdir =~ "/$" && newdir != "../"
-    let dirname = fnamemodify(dirname, ":h")
-   endif
+    " dirname is the path to the word under the cursor
+    let dirname = s:NetrwTreePath(w:netrw_treetop)
+    " newdir resolves to a directory and points to a directory in dirname
+    " /tmp/test/folder_symlink/ -> /tmp/test/original_folder/
+    if a:islocal && fnamemodify(dirname, ':t') == newdir && isdirectory(resolve(dirname)) && resolve(dirname) == resolve(newdir)
+      let dirname = fnamemodify(resolve(dirname), ':p:h:h')
+      let newdir = fnamemodify(resolve(newdir), ':t')
+    endif
+    " Remove trailing "/"
+    let dirname = substitute(dirname, "/$", "", "")
+
+    " If the word under the cursor is a directory (except for ../), NetrwTreePath
+    " returns the full path, including the word under the cursor, remove it
+    if newdir =~ "/$" && newdir != "../"
+      let dirname = fnamemodify(dirname, ":h")
+    endif
   else
-   let dirname = b:netrw_curdir
+    let dirname = b:netrw_curdir
   endif
   if has("win32")
-   let dirname = substitute(dirname,'\\','/','ge')
+    let dirname = substitute(dirname,'\\','/','ge')
   endif
   let dolockout = 0
   let dorestore = 1
 
   " ignore s when done in the banner
   if g:netrw_banner
-   if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt && line("$") >= w:netrw_bannercnt
-    if getline(".") =~# 'Quick Help'
-     let g:netrw_quickhelp= (g:netrw_quickhelp + 1)%len(s:QuickHelp)
-     setl ma noro nowrap
-     NetrwKeepj call setline(line('.'),'"   Quick Help: :help  '.s:QuickHelp[g:netrw_quickhelp])
-     setl noma nomod nowrap
-     NetrwKeepj call s:NetrwOptionsRestore("s:")
+    if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt && line("$") >= w:netrw_bannercnt
+      if getline(".") =~# 'Quick Help'
+        let g:netrw_quickhelp= (g:netrw_quickhelp + 1)%len(s:QuickHelp)
+        setl ma noro nowrap
+        NetrwKeepj call setline(line('.'),'"   Quick Help: :help  '.s:QuickHelp[g:netrw_quickhelp])
+        setl noma nomod nowrap
+        NetrwKeepj call s:NetrwOptionsRestore("s:")
+      endif
     endif
-   endif
   endif
 
   " set up o/s-dependent directory recognition pattern
   if has("amiga")
-   let dirpat= '[\/:]$'
+    let dirpat= '[\/:]$'
   else
-   let dirpat= '[\/]$'
+    let dirpat= '[\/]$'
   endif
 
   if dirname !~ dirpat
-   " apparently vim is "recognizing" that it is in a directory and
-   " is removing the trailing "/".  Bad idea, so let's put it back.
-   let dirname= dirname.'/'
+    " apparently vim is "recognizing" that it is in a directory and
+    " is removing the trailing "/".  Bad idea, so let's put it back.
+    let dirname= dirname.'/'
   endif
 
   if newdir !~ dirpat && !(a:islocal && isdirectory(s:NetrwFile(s:ComposePath(dirname,newdir))))
-   " ------------------------------
-   " NetrwBrowseChgDir: edit a file {{{3
-   " ------------------------------
+    " ------------------------------
+    " NetrwBrowseChgDir: edit a file {{{3
+    " ------------------------------
 
-   " save position for benefit of Rexplore
-   let s:rexposn_{bufnr("%")}= winsaveview()
+    " save position for benefit of Rexplore
+    let s:rexposn_{bufnr("%")}= winsaveview()
 
-   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") && newdir !~ '^\(/\|\a:\)'
-    if dirname =~ '/$'
-     let dirname= dirname.newdir
+    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") && newdir !~ '^\(/\|\a:\)'
+      if dirname =~ '/$'
+        let dirname= dirname.newdir
+      else
+        let dirname= dirname."/".newdir
+      endif
+    elseif newdir =~ '^\(/\|\a:\)'
+      let dirname= newdir
     else
-     let dirname= dirname."/".newdir
-    endif
-   elseif newdir =~ '^\(/\|\a:\)'
-    let dirname= newdir
-   else
-    let dirname= s:ComposePath(dirname,newdir)
-   endif
-   " this lets netrw#BrowseX avoid the edit
-   if a:0 < 1
-    NetrwKeepj call s:NetrwOptionsRestore("s:")
-    let curdir= b:netrw_curdir
-    if !exists("s:didsplit")
-     if type(g:netrw_browse_split) == 3
-      " open file in server
-      " Note that g:netrw_browse_split is a List: [servername,tabnr,winnr]
-      call s:NetrwServerEdit(a:islocal,dirname)
-      return
+      let dirname= s:ComposePath(dirname,newdir)
+    endif
+    " this lets netrw#BrowseX avoid the edit
+    if a:0 < 1
+      NetrwKeepj call s:NetrwOptionsRestore("s:")
+      let curdir= b:netrw_curdir
+      if !exists("s:didsplit")
+        if type(g:netrw_browse_split) == 3
+          " open file in server
+          " Note that g:netrw_browse_split is a List: [servername,tabnr,winnr]
+          call s:NetrwServerEdit(a:islocal,dirname)
+          return
+
+        elseif g:netrw_browse_split == 1
+          " horizontally splitting the window first
+          let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
+          exe "keepalt ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
+          if !&ea
+            keepalt wincmd _
+          endif
+          call s:SetRexDir(a:islocal,curdir)
+
+        elseif g:netrw_browse_split == 2
+          " vertically splitting the window first
+          let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
+          exe "keepalt ".(g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
+          if !&ea
+            keepalt wincmd |
+          endif
+          call s:SetRexDir(a:islocal,curdir)
+
+        elseif g:netrw_browse_split == 3
+          " open file in new tab
+          keepalt tabnew
+          if !exists("b:netrw_curdir")
+            let b:netrw_curdir= getcwd()
+          endif
+          call s:SetRexDir(a:islocal,curdir)
+
+        elseif g:netrw_browse_split == 4
+          " act like "P" (ie. open previous window)
+          if s:NetrwPrevWinOpen(2) == 3
+            let @@= ykeep
+            return
+          endif
+          call s:SetRexDir(a:islocal,curdir)
+
+        else
+          " handling a file, didn't split, so remove menu
+          call s:NetrwMenu(0)
+          " optional change to window
+          if g:netrw_chgwin >= 1
+            if winnr("$")+1 == g:netrw_chgwin
+              " if g:netrw_chgwin is set to one more than the last window, then
+              " vertically split the last window to make that window available.
+              let curwin= winnr()
+              exe "NetrwKeepj keepalt ".winnr("$")."wincmd w"
+              vs
+              exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd ".curwin
+            endif
+            exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
+          endif
+          call s:SetRexDir(a:islocal,curdir)
+        endif
 
-     elseif g:netrw_browse_split == 1
-      " horizontally splitting the window first
-      let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
-      exe "keepalt ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
-      if !&ea
-       keepalt wincmd _
-      endif
-      call s:SetRexDir(a:islocal,curdir)
-
-     elseif g:netrw_browse_split == 2
-      " vertically splitting the window first
-      let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
-      exe "keepalt ".(g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
-      if !&ea
-       keepalt wincmd |
-      endif
-      call s:SetRexDir(a:islocal,curdir)
-
-     elseif g:netrw_browse_split == 3
-      " open file in new tab
-      keepalt tabnew
-      if !exists("b:netrw_curdir")
-       let b:netrw_curdir= getcwd()
-      endif
-      call s:SetRexDir(a:islocal,curdir)
-
-     elseif g:netrw_browse_split == 4
-      " act like "P" (ie. open previous window)
-      if s:NetrwPrevWinOpen(2) == 3
-       let @@= ykeep
-       return
-      endif
-      call s:SetRexDir(a:islocal,curdir)
-
-     else
-      " handling a file, didn't split, so remove menu
-      call s:NetrwMenu(0)
-      " optional change to window
-      if g:netrw_chgwin >= 1
-       if winnr("$")+1 == g:netrw_chgwin
-       " if g:netrw_chgwin is set to one more than the last window, then
-       " vertically split the last window to make that window available.
-       let curwin= winnr()
-       exe "NetrwKeepj keepalt ".winnr("$")."wincmd w"
-       vs
-       exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd ".curwin
-       endif
-       exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
-      endif
-      call s:SetRexDir(a:islocal,curdir)
-     endif
-
-    endif
-
-    " the point where netrw actually edits the (local) file
-    " if its local only: LocalBrowseCheck() doesn't edit a file, but NetrwBrowse() will
-    " use keepalt to support  :e #  to return to a directory listing
-    if !&mod
-     " if e the new file would fail due to &mod, then don't change any of the flags
-     let dolockout= 1
-    endif
-    if a:islocal
-     " some like c-^ to return to the last edited file
-     " others like c-^ to return to the netrw buffer
-     " Apr 30, 2020: used to have e! here.  That can cause loss of a modified file,
-     " so emit error E37 instead.
-     call s:NetrwEditFile("e","",dirname)
-     call s:NetrwCursor(1)
-     if &hidden || &bufhidden == "hide"
-      " file came from vim's hidden storage.  Don't "restore" options with it.
-      let dorestore= 0
-     endif
-    else
-    endif
+      endif
 
-    " handle g:Netrw_funcref -- call external-to-netrw functions
-    "   This code will handle g:Netrw_funcref as an individual function reference
-    "   or as a list of function references.  It will ignore anything that's not
-    "   a function reference.  See  :help Funcref  for information about function references.
-    if exists("g:Netrw_funcref")
-     if type(g:Netrw_funcref) == 2
-      NetrwKeepj call g:Netrw_funcref()
-     elseif type(g:Netrw_funcref) == 3
-      for Fncref in g:Netrw_funcref
-       if type(Fncref) == 2
-        NetrwKeepj call Fncref()
-       endif
-      endfor
-     endif
+      " the point where netrw actually edits the (local) file
+      " if its local only: LocalBrowseCheck() doesn't edit a file, but NetrwBrowse() will
+      " use keepalt to support  :e #  to return to a directory listing
+      if !&mod
+        " if e the new file would fail due to &mod, then don't change any of the flags
+        let dolockout= 1
+      endif
+      if a:islocal
+        " some like c-^ to return to the last edited file
+        " others like c-^ to return to the netrw buffer
+        " Apr 30, 2020: used to have e! here.  That can cause loss of a modified file,
+        " so emit error E37 instead.
+        call s:NetrwEditFile("e","",dirname)
+        call s:NetrwCursor(1)
+        if &hidden || &bufhidden == "hide"
+          " file came from vim's hidden storage.  Don't "restore" options with it.
+          let dorestore= 0
+        endif
+      else
+      endif
+
+      " handle g:Netrw_funcref -- call external-to-netrw functions
+      "   This code will handle g:Netrw_funcref as an individual function reference
+      "   or as a list of function references.  It will ignore anything that's not
+      "   a function reference.  See  :help Funcref  for information about function references.
+      if exists("g:Netrw_funcref")
+        if type(g:Netrw_funcref) == 2
+          NetrwKeepj call g:Netrw_funcref()
+        elseif type(g:Netrw_funcref) == 3
+          for Fncref in g:Netrw_funcref
+            if type(Fncref) == 2
+              NetrwKeepj call Fncref()
+            endif
+          endfor
+        endif
+      endif
     endif
-   endif
 
   elseif newdir =~ '^/'
-   " ----------------------------------------------------
-   " NetrwBrowseChgDir: just go to the new directory spec {{{3
-   " ----------------------------------------------------
-   let dirname = newdir
-   NetrwKeepj call s:SetRexDir(a:islocal,dirname)
-   NetrwKeepj call s:NetrwOptionsRestore("s:")
-   norm! m`
+    " ----------------------------------------------------
+    " NetrwBrowseChgDir: just go to the new directory spec {{{3
+    " ----------------------------------------------------
+    let dirname = newdir
+    NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+    NetrwKeepj call s:NetrwOptionsRestore("s:")
+    norm! m`
 
   elseif newdir == './'
-   " ---------------------------------------------
-   " NetrwBrowseChgDir: refresh the directory list {{{3
-   " ---------------------------------------------
-   NetrwKeepj call s:SetRexDir(a:islocal,dirname)
-   norm! m`
+    " ---------------------------------------------
+    " NetrwBrowseChgDir: refresh the directory list {{{3
+    " ---------------------------------------------
+    NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+    norm! m`
 
   elseif newdir == '../'
-   " --------------------------------------
-   " NetrwBrowseChgDir: go up one directory {{{3
-   " --------------------------------------
-
-   if w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-    " force a refresh
-    setl noro ma
-    NetrwKeepj %d _
-   endif
+    " --------------------------------------
+    " NetrwBrowseChgDir: go up one directory {{{3
+    " --------------------------------------
 
-   if has("amiga")
-    " amiga
-    if a:islocal
-     let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+$\)','\1','')
-     let dirname= substitute(dirname,'/$','','')
-    else
-     let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+/$\)','\1','')
+    if w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
+      " force a refresh
+      setl noro ma
+      NetrwKeepj %d _
     endif
 
-   elseif !g:netrw_cygwin && has("win32")
-    " windows
-    if a:islocal
-     let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','')
-     if dirname == ""
-      let dirname= '/'
-     endif
-    else
-     let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','')
-    endif
-    if dirname =~ '^\a:$'
-     let dirname= dirname.'/'
-    endif
+    if has("amiga")
+      " amiga
+      if a:islocal
+        let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+$\)','\1','')
+        let dirname= substitute(dirname,'/$','','')
+      else
+        let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+/$\)','\1','')
+      endif
+
+    elseif !g:netrw_cygwin && has("win32")
+      " windows
+      if a:islocal
+        let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','')
+        if dirname == ""
+          let dirname= '/'
+        endif
+      else
+        let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','')
+      endif
+      if dirname =~ '^\a:$'
+        let dirname= dirname.'/'
+      endif
 
-   else
-    " unix or cygwin
-    if a:islocal
-     let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','')
-     if dirname == ""
-      let dirname= '/'
-     endif
     else
-     let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','')
+      " unix or cygwin
+      if a:islocal
+        let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','')
+        if dirname == ""
+          let dirname= '/'
+        endif
+      else
+        let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','')
+      endif
     endif
-   endif
-   NetrwKeepj call s:SetRexDir(a:islocal,dirname)
-   norm! m`
+    NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+    norm! m`
 
   elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-   " --------------------------------------
-   " NetrwBrowseChgDir: Handle Tree Listing {{{3
-   " --------------------------------------
-   " force a refresh (for TREELIST, NetrwTreeDir() will force the refresh)
-   setl noro ma
-   if !(exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir"))
-    NetrwKeepj %d _
-   endif
-   let treedir      = s:NetrwTreeDir(a:islocal)
-   let s:treecurpos = winsaveview()
-   let haskey       = 0
-
-   " search treedict for tree dir as-is
-   if has_key(w:netrw_treedict,treedir)
-    let haskey= 1
-   else
-   endif
-
-   " search treedict for treedir with a [/@] appended
-   if !haskey && treedir !~ '[/@]$'
-    if has_key(w:netrw_treedict,treedir."/")
-     let treedir= treedir."/"
-     let haskey = 1
-    else
+    " --------------------------------------
+    " NetrwBrowseChgDir: Handle Tree Listing {{{3
+    " --------------------------------------
+    " force a refresh (for TREELIST, NetrwTreeDir() will force the refresh)
+    setl noro ma
+    if !(exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir"))
+      NetrwKeepj %d _
     endif
-   endif
+    let treedir      = s:NetrwTreeDir(a:islocal)
+    let s:treecurpos = winsaveview()
+    let haskey       = 0
 
-   " search treedict for treedir with any trailing / elided
-   if !haskey && treedir =~ '/$'
-    let treedir= substitute(treedir,'/$','','')
+    " search treedict for tree dir as-is
     if has_key(w:netrw_treedict,treedir)
-     let haskey = 1
+      let haskey= 1
     else
     endif
-   endif
 
-   if haskey
-    " close tree listing for selected subdirectory
-    call remove(w:netrw_treedict,treedir)
-    let dirname= w:netrw_treetop
-   else
-    " go down one directory
-    let dirname= substitute(treedir,'/*$','/','')
-   endif
-   NetrwKeepj call s:SetRexDir(a:islocal,dirname)
-   let s:treeforceredraw = 1
+    " search treedict for treedir with a [/@] appended
+    if !haskey && treedir !~ '[/@]$'
+      if has_key(w:netrw_treedict,treedir."/")
+        let treedir= treedir."/"
+        let haskey = 1
+      else
+      endif
+    endif
+
+    " search treedict for treedir with any trailing / elided
+    if !haskey && treedir =~ '/$'
+      let treedir= substitute(treedir,'/$','','')
+      if has_key(w:netrw_treedict,treedir)
+        let haskey = 1
+      else
+      endif
+    endif
+
+    if haskey
+      " close tree listing for selected subdirectory
+      call remove(w:netrw_treedict,treedir)
+      let dirname= w:netrw_treetop
+    else
+      " go down one directory
+      let dirname= substitute(treedir,'/*$','/','')
+    endif
+    NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+    let s:treeforceredraw = 1
 
   else
-   " ----------------------------------------
-   " NetrwBrowseChgDir: Go down one directory {{{3
-   " ----------------------------------------
-   let dirname    = s:ComposePath(dirname,newdir)
-   NetrwKeepj call s:SetRexDir(a:islocal,dirname)
-   norm! m`
+    " ----------------------------------------
+    " NetrwBrowseChgDir: Go down one directory {{{3
+    " ----------------------------------------
+    let dirname    = s:ComposePath(dirname,newdir)
+    NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+    norm! m`
   endif
 
- " --------------------------------------
- " NetrwBrowseChgDir: Restore and Cleanup {{{3
- " --------------------------------------
+  " --------------------------------------
+  " NetrwBrowseChgDir: Restore and Cleanup {{{3
+  " --------------------------------------
   if dorestore
-   " dorestore is zero'd when a local file was hidden or bufhidden;
-   " in such a case, we want to keep whatever settings it may have.
-   NetrwKeepj call s:NetrwOptionsRestore("s:")
+    " dorestore is zero'd when a local file was hidden or bufhidden;
+    " in such a case, we want to keep whatever settings it may have.
+    NetrwKeepj call s:NetrwOptionsRestore("s:")
   endif
   if dolockout && dorestore
-   if filewritable(dirname)
-    setl ma noro nomod
-   else
-    setl ma ro nomod
-   endif
+    if filewritable(dirname)
+      setl ma noro nomod
+    else
+      setl ma ro nomod
+    endif
   endif
   call s:RestorePosn(s:netrw_posn)
   let @@= ykeep
@@ -4938,62 +4939,62 @@ endfun
 "    for tree, keeps cursor on current filename
 fun! s:NetrwBrowseUpDir(islocal)
   if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt-1
-   " this test needed because occasionally this function seems to be incorrectly called
-   " when multiple leftmouse clicks are taken when atop the one line help in the banner.
-   " I'm allowing the very bottom line to permit a "-" exit so that one may escape empty
-   " directories.
-   return
+    " this test needed because occasionally this function seems to be incorrectly called
+    " when multiple leftmouse clicks are taken when atop the one line help in the banner.
+    " I'm allowing the very bottom line to permit a "-" exit so that one may escape empty
+    " directories.
+    return
   endif
 
   norm! 0
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-   let curline= getline(".")
-   let swwline= winline() - 1
-   if exists("w:netrw_treetop")
-    let b:netrw_curdir= w:netrw_treetop
-   elseif exists("b:netrw_curdir")
-    let w:netrw_treetop= b:netrw_curdir
-   else
-    let w:netrw_treetop= getcwd()
-    let b:netrw_curdir = w:netrw_treetop
-   endif
-   let curfile = getline(".")
-   let curpath = s:NetrwTreePath(w:netrw_treetop)
-   if a:islocal
-    call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0))
-   else
-    call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0))
-   endif
-   if w:netrw_treetop == '/'
-     keepj call search('^\M'.curfile,"w")
-   elseif curfile == '../'
-     keepj call search('^\M'.curfile,"wb")
-   else
-    while 1
-     keepj call search('^\M'.s:treedepthstring.curfile,"wb")
-     let treepath= s:NetrwTreePath(w:netrw_treetop)
-     if treepath == curpath
-      break
-     endif
-    endwhile
-   endif
+    let curline= getline(".")
+    let swwline= winline() - 1
+    if exists("w:netrw_treetop")
+      let b:netrw_curdir= w:netrw_treetop
+    elseif exists("b:netrw_curdir")
+      let w:netrw_treetop= b:netrw_curdir
+    else
+      let w:netrw_treetop= getcwd()
+      let b:netrw_curdir = w:netrw_treetop
+    endif
+    let curfile = getline(".")
+    let curpath = s:NetrwTreePath(w:netrw_treetop)
+    if a:islocal
+      call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0))
+    else
+      call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0))
+    endif
+    if w:netrw_treetop == '/'
+      keepj call search('^\M'.curfile,"w")
+    elseif curfile == '../'
+      keepj call search('^\M'.curfile,"wb")
+    else
+      while 1
+        keepj call search('^\M'.s:treedepthstring.curfile,"wb")
+        let treepath= s:NetrwTreePath(w:netrw_treetop)
+        if treepath == curpath
+          break
+        endif
+      endwhile
+    endif
 
   else
-   call s:SavePosn(s:netrw_posn)
-   if exists("b:netrw_curdir")
-    let curdir= b:netrw_curdir
-   else
-    let curdir= expand(getcwd())
-   endif
-   if a:islocal
-    call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0))
-   else
-    call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0))
-   endif
-   call s:RestorePosn(s:netrw_posn)
-   let curdir= substitute(curdir,'^.*[\/]','','')
-   let curdir= '\<'. escape(curdir, '~'). '/'
-   call search(curdir,'wc')
+    call s:SavePosn(s:netrw_posn)
+    if exists("b:netrw_curdir")
+      let curdir= b:netrw_curdir
+    else
+      let curdir= expand(getcwd())
+    endif
+    if a:islocal
+      call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0))
+    else
+      call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0))
+    endif
+    call s:RestorePosn(s:netrw_posn)
+    let curdir= substitute(curdir,'^.*[\/]','','')
+    let curdir= '\<'. escape(curdir, '~'). '/'
+    call search(curdir,'wc')
   endif
 endfun
 
@@ -5011,65 +5012,65 @@ func s:redir()
 endfunc
 
 if has('unix')
- if has('win32unix')
-  " Cygwin provides cygstart
-  if executable('cygstart')
-   fun! netrw#Launch(args)
-     exe 'silent ! cygstart --hide' a:args s:redir() | redraw!
-   endfun
-  elseif !empty($MSYSTEM) && executable('start')
-   " MSYS2/Git Bash comes by default without cygstart; see
-   " https://www.msys2.org/wiki/How-does-MSYS2-differ-from-Cygwin
-   " Instead it provides /usr/bin/start script running `cmd.exe //c start`
-   " Adding "" //b` sets void title, hides cmd window and blocks path conversion
-   " of /b to \b\ " by MSYS2; see https://www.msys2.org/docs/filesystem-paths/
-   fun! netrw#Launch(args)
-     exe 'silent !start "" //b' a:args s:redir() | redraw!
-   endfun
-  else
-   " imitate /usr/bin/start script for other environments and hope for the best
-   fun! netrw#Launch(args)
-     exe 'silent !cmd //c start "" //b' a:args s:redir() | redraw!
-   endfun
+  if has('win32unix')
+    " Cygwin provides cygstart
+    if executable('cygstart')
+      fun! netrw#Launch(args)
+        exe 'silent ! cygstart --hide' a:args s:redir() | redraw!
+      endfun
+    elseif !empty($MSYSTEM) && executable('start')
+      " MSYS2/Git Bash comes by default without cygstart; see
+      " https://www.msys2.org/wiki/How-does-MSYS2-differ-from-Cygwin
+      " Instead it provides /usr/bin/start script running `cmd.exe //c start`
+      " Adding "" //b` sets void title, hides cmd window and blocks path conversion
+      " of /b to \b\ " by MSYS2; see https://www.msys2.org/docs/filesystem-paths/
+      fun! netrw#Launch(args)
+        exe 'silent !start "" //b' a:args s:redir() | redraw!
+      endfun
+    else
+      " imitate /usr/bin/start script for other environments and hope for the best
+      fun! netrw#Launch(args)
+        exe 'silent !cmd //c start "" //b' a:args s:redir() | redraw!
+      endfun
+    endif
+  elseif exists('$WSL_DISTRO_NAME') " use cmd.exe to start GUI apps in WSL
+    fun! netrw#Launch(args)
+      let args = a:args
+      exe 'silent !' ..
+            \ ((args =~? '\v<\f+\.(exe|com|bat|cmd)>') ?
+            \ 'cmd.exe /c start /b ' .. args :
+            \ 'nohup ' .. args .. ' ' .. s:redir() .. ' &')
+            \ | redraw!
+    endfun
+  else
+    fun! netrw#Launch(args)
+      exe ':silent ! nohup' a:args s:redir() (has('gui_running') ? '' : '&') | redraw!
+    endfun
   endif
- elseif exists('$WSL_DISTRO_NAME') " use cmd.exe to start GUI apps in WSL
-  fun! netrw#Launch(args)
-    let args = a:args
-    exe 'silent !' ..
-      \ ((args =~? '\v<\f+\.(exe|com|bat|cmd)>') ?
-      \ 'cmd.exe /c start /b ' .. args :
-      \ 'nohup ' .. args .. ' ' .. s:redir() .. ' &')
-      \ | redraw!
-  endfun
- else
+elseif has('win32')
   fun! netrw#Launch(args)
-    exe ':silent ! nohup' a:args s:redir() (has('gui_running') ? '' : '&') | redraw!
+    exe 'silent !' .. (&shell =~? '\' ? '' : 'cmd.exe /c')
+          \ 'start "" /b' a:args s:redir() | redraw!
   endfun
- endif
-elseif has('win32')
- fun! netrw#Launch(args)
-   exe 'silent !' .. (&shell =~? '\' ? '' : 'cmd.exe /c')
-     \ 'start "" /b' a:args s:redir() | redraw!
- endfun
 else
- fun! netrw#Launch(dummy)
-   echom 'No common launcher found'
- endfun
+  fun! netrw#Launch(dummy)
+    echom 'No common launcher found'
+  endfun
 endif
 
 " Git Bash
 if has('win32unix')
-   " (cyg)start suffices
-   let s:os_viewer = ''
-" Windows / WSL
+  " (cyg)start suffices
+  let s:os_viewer = ''
+  " Windows / WSL
 elseif executable('explorer.exe')
-   let s:os_viewer = 'explorer.exe'
-" Linux / BSD
+  let s:os_viewer = 'explorer.exe'
+  " Linux / BSD
 elseif executable('xdg-open')
-   let s:os_viewer = 'xdg-open'
-" MacOS
+  let s:os_viewer = 'xdg-open'
+  " MacOS
 elseif executable('open')
-   let s:os_viewer = 'open'
+  let s:os_viewer = 'open'
 endif
 
 fun! s:viewer()
@@ -5107,3025 +5108,3024 @@ endfun
 
 fun! netrw#Open(file) abort
   call netrw#Launch(s:viewer() .. ' ' .. shellescape(a:file, 1))
-endf
+  endf
 
-if !exists('g:netrw_regex_url')
-  let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}'
-endif
-
-" ---------------------------------------------------------------------
-" netrw#BrowseX:  (implements "x" and "gx") executes a special "viewer" script or program for the {{{2
-"              given filename; typically this means given their extension.
-"              0=local, 1=remote
-fun! netrw#BrowseX(fname,remote)
-  if a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$'
-   " remote directory, not a webpage access, looks like an attempt to do a directory listing
-   norm! gf
+  if !exists('g:netrw_regex_url')
+    let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}'
   endif
 
-  if exists("g:netrw_browsex_viewer") && exists("g:netrw_browsex_support_remote") && !g:netrw_browsex_support_remote
-    let remote = a:remote
-  else
-    let remote = 0
-  endif
+  " ---------------------------------------------------------------------
+  " netrw#BrowseX:  (implements "x" and "gx") executes a special "viewer" script or program for the {{{2
+  "              given filename; typically this means given their extension.
+  "              0=local, 1=remote
+  fun! netrw#BrowseX(fname,remote)
+    if a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$'
+      " remote directory, not a webpage access, looks like an attempt to do a directory listing
+      norm! gf
+    endif
 
-  let ykeep      = @@
-  let screenposn = winsaveview()
+    if exists("g:netrw_browsex_viewer") && exists("g:netrw_browsex_support_remote") && !g:netrw_browsex_support_remote
+      let remote = a:remote
+    else
+      let remote = 0
+    endif
+
+    let ykeep      = @@
+    let screenposn = winsaveview()
+
+    " need to save and restore aw setting as gx can invoke this function from non-netrw buffers
+    let awkeep     = &aw
+    set noaw
+
+    " special core dump handler
+    if a:fname =~ '/core\(\.\d\+\)\=$'
+      if exists("g:Netrw_corehandler")
+        if type(g:Netrw_corehandler) == 2
+          " g:Netrw_corehandler is a function reference (see :help Funcref)
+          call g:Netrw_corehandler(s:NetrwFile(a:fname))
+        elseif type(g:Netrw_corehandler) == 3
+          " g:Netrw_corehandler is a List of function references (see :help Funcref)
+          for Fncref in g:Netrw_corehandler
+            if type(Fncref) == 2
+              call Fncref(a:fname)
+            endif
+          endfor
+        endif
+        call winrestview(screenposn)
+        let @@= ykeep
+        let &aw= awkeep
+        return
+      endif
+    endif
+
+    " set up the filename
+    " (lower case the extension, make a local copy of a remote file)
+    let exten= substitute(a:fname,'.*\.\(.\{-}\)','\1','e')
+    if has("win32")
+      let exten= substitute(exten,'^.*$','\L&\E','')
+    endif
+    if exten =~ "[\\/]"
+      let exten= ""
+    endif
+
+    if remote == 1
+      " create a local copy
+      setl bh=delete
+      call netrw#NetRead(3,a:fname)
+      " attempt to rename tempfile
+      let basename= substitute(a:fname,'^\(.*\)/\(.*\)\.\([^.]*\)$','\2','')
+      let newname = substitute(s:netrw_tmpfile,'^\(.*\)/\(.*\)\.\([^.]*\)$','\1/'.basename.'.\3','')
+      if s:netrw_tmpfile != newname && newname != ""
+        if rename(s:netrw_tmpfile,newname) == 0
+          " renaming succeeded
+          let fname= newname
+        else
+          " renaming failed
+          let fname= s:netrw_tmpfile
+        endif
+      else
+        let fname= s:netrw_tmpfile
+      endif
+    else
+      let fname= a:fname
+      " special ~ handler for local
+      if fname =~ '^\~' && expand("$HOME") != ""
+        let fname= s:NetrwFile(substitute(fname,'^\~',expand("$HOME"),''))
+      endif
+    endif
+
+    " although shellescape(..., 1) is used in netrw#Open(), it's insufficient
+    call netrw#Open(escape(fname, '#%'))
 
-  " need to save and restore aw setting as gx can invoke this function from non-netrw buffers
-  let awkeep     = &aw
-  set noaw
+    " cleanup: remove temporary file,
+    "          delete current buffer if success with handler,
+    "          return to prior buffer (directory listing)
+    "          Feb 12, 2008: had to de-activate removal of
+    "          temporary file because it wasn't getting seen.
+    "  if remote == 1 && fname != a:fname
+    "   call s:NetrwDelete(fname)
+    "  endif
 
-  " special core dump handler
-  if a:fname =~ '/core\(\.\d\+\)\=$'
-   if exists("g:Netrw_corehandler")
-    if type(g:Netrw_corehandler) == 2
-     " g:Netrw_corehandler is a function reference (see :help Funcref)
-     call g:Netrw_corehandler(s:NetrwFile(a:fname))
-    elseif type(g:Netrw_corehandler) == 3
-     " g:Netrw_corehandler is a List of function references (see :help Funcref)
-     for Fncref in g:Netrw_corehandler
-      if type(Fncref) == 2
-       call Fncref(a:fname)
+    if remote == 1
+      setl bh=delete bt=nofile
+      if g:netrw_use_noswf
+        setl noswf
       endif
-     endfor
+      exe "sil! NetrwKeepj norm! \"
     endif
     call winrestview(screenposn)
-    let @@= ykeep
+    let @@ = ykeep
     let &aw= awkeep
-    return
-   endif
-  endif
+  endfun
 
-  " set up the filename
-  " (lower case the extension, make a local copy of a remote file)
-  let exten= substitute(a:fname,'.*\.\(.\{-}\)','\1','e')
-  if has("win32")
-   let exten= substitute(exten,'^.*$','\L&\E','')
-  endif
-  if exten =~ "[\\/]"
-   let exten= ""
-  endif
-
-  if remote == 1
-   " create a local copy
-   setl bh=delete
-   call netrw#NetRead(3,a:fname)
-   " attempt to rename tempfile
-   let basename= substitute(a:fname,'^\(.*\)/\(.*\)\.\([^.]*\)$','\2','')
-   let newname = substitute(s:netrw_tmpfile,'^\(.*\)/\(.*\)\.\([^.]*\)$','\1/'.basename.'.\3','')
-   if s:netrw_tmpfile != newname && newname != ""
-    if rename(s:netrw_tmpfile,newname) == 0
-     " renaming succeeded
-     let fname= newname
+  " ---------------------------------------------------------------------
+  " netrw#GX: gets word under cursor for gx support {{{2
+  "           See also: netrw#BrowseXVis
+  "                     netrw#BrowseX
+  fun! netrw#GX()
+    "  call Dfunc("netrw#GX()")
+    if &ft == "netrw"
+      let fname= s:NetrwGetWord()
     else
-     " renaming failed
-     let fname= s:netrw_tmpfile
+      let fname= exists("g:netrw_gx")? expand(g:netrw_gx) : s:GetURL()
     endif
-   else
-    let fname= s:netrw_tmpfile
-   endif
-  else
-   let fname= a:fname
-   " special ~ handler for local
-   if fname =~ '^\~' && expand("$HOME") != ""
-    let fname= s:NetrwFile(substitute(fname,'^\~',expand("$HOME"),''))
-   endif
-  endif
-
-  " although shellescape(..., 1) is used in netrw#Open(), it's insufficient
-  call netrw#Open(escape(fname, '#%'))
-
-  " cleanup: remove temporary file,
-  "          delete current buffer if success with handler,
-  "          return to prior buffer (directory listing)
-  "          Feb 12, 2008: had to de-activate removal of
-  "          temporary file because it wasn't getting seen.
-"  if remote == 1 && fname != a:fname
-"   call s:NetrwDelete(fname)
-"  endif
-
-  if remote == 1
-   setl bh=delete bt=nofile
-   if g:netrw_use_noswf
-    setl noswf
-   endif
-   exe "sil! NetrwKeepj norm! \"
-  endif
-  call winrestview(screenposn)
-  let @@ = ykeep
-  let &aw= awkeep
-endfun
-
-" ---------------------------------------------------------------------
-" netrw#GX: gets word under cursor for gx support {{{2
-"           See also: netrw#BrowseXVis
-"                     netrw#BrowseX
-fun! netrw#GX()
-"  call Dfunc("netrw#GX()")
-  if &ft == "netrw"
-   let fname= s:NetrwGetWord()
-  else
-   let fname= exists("g:netrw_gx")? expand(g:netrw_gx) : s:GetURL()
-  endif
-"  call Dret("netrw#GX <".fname.">")
-  return fname
-endfun
+    "  call Dret("netrw#GX <".fname.">")
+    return fname
+  endfun
 
-fun! s:GetURL() abort
-   let URL = ''
-   if exists('*Netrw_get_URL_' .. &filetype)
+  fun! s:GetURL() abort
+    let URL = ''
+    if exists('*Netrw_get_URL_' .. &filetype)
       let URL = call('Netrw_get_URL_' .. &filetype, [])
-   endif
-   if !empty(URL) | return URL | endif
-  " URLs end in letter, digit or forward slash
-  let URL = matchstr(expand(""), '\<' .. g:netrw_regex_url .. '\ze[^A-Za-z0-9/]*$')
-  if !empty(URL) | return URL | endif
-
-  " Is it a file in the current work dir ...
-  let file = expand("")
-  if filereadable(file) | return file | endif
-  " ... or in that of the current buffer?
-  let path = fnamemodify(expand('%'), ':p')
-  if isdirectory(path)
-    let dir = path
-  elseif filereadable(path)
-    let dir = fnamemodify(path, ':h')
-  endif
-  if exists('dir') && filereadable(dir..'/'..file) | return dir..'/'..file | endif
+    endif
+    if !empty(URL) | return URL | endif
+    " URLs end in letter, digit or forward slash
+    let URL = matchstr(expand(""), '\<' .. g:netrw_regex_url .. '\ze[^A-Za-z0-9/]*$')
+    if !empty(URL) | return URL | endif
+
+    " Is it a file in the current work dir ...
+    let file = expand("")
+    if filereadable(file) | return file | endif
+    " ... or in that of the current buffer?
+    let path = fnamemodify(expand('%'), ':p')
+    if isdirectory(path)
+      let dir = path
+    elseif filereadable(path)
+      let dir = fnamemodify(path, ':h')
+    endif
+    if exists('dir') && filereadable(dir..'/'..file) | return dir..'/'..file | endif
+
+    return ''
+    endf
+
+    " ---------------------------------------------------------------------
+    " netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2
+    fun! netrw#BrowseXVis()
+      let dict={}
+      let dict.a=[getreg('a'), getregtype('a')]
+      norm! gv"ay
+      let gxfile= @a
+      call s:RestoreRegister(dict)
+      call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile))
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwBufRename: renames a buffer without the side effect of retaining an unlisted buffer having the old name {{{2
+    "                   Using the file command on a "[No Name]" buffer does not seem to cause the old "[No Name]" buffer
+    "                   to become an unlisted buffer, so in that case don't bwipe it.
+    fun! s:NetrwBufRename(newname)
+      "  call Dfunc("s:NetrwBufRename(newname<".a:newname.">) buf(%)#".bufnr("%")."<".bufname(bufnr("%")).">")
+      "  call Dredir("ls!","s:NetrwBufRename (before rename)")
+      let oldbufname= bufname(bufnr("%"))
+      "  call Decho("buf#".bufnr("%").": oldbufname<".oldbufname.">",'~'.expand(""))
+
+      if oldbufname != a:newname
+        "   call Decho("do buffer rename: oldbufname<".oldbufname."> ≠ a:newname<".a:newname.">",'~'.expand(""))
+        let b:junk= 1
+        "   call Decho("rename buffer: sil! keepj keepalt file ".fnameescape(a:newname),'~'.expand(""))
+        exe 'sil! keepj keepalt file '.fnameescape(a:newname)
+        "   call Dredir("ls!","s:NetrwBufRename (before bwipe)~".expand(""))
+        let oldbufnr= bufnr(oldbufname)
+        "   call Decho("oldbufname<".oldbufname."> oldbufnr#".oldbufnr,'~'.expand(""))
+        "   call Decho("bufnr(%)=".bufnr("%"),'~'.expand(""))
+        if oldbufname != "" && oldbufnr != -1 && oldbufnr != bufnr("%")
+          "    call Decho("bwipe ".oldbufnr,'~'.expand(""))
+          exe "bwipe! ".oldbufnr
+          "   else " Decho
+          "    call Decho("did *not* bwipe buf#".oldbufnr,'~'.expand(""))
+          "    call Decho("..reason: if oldbufname<".oldbufname."> is empty",'~'.expand(""))"
+          "    call Decho("..reason: if oldbufnr#".oldbufnr." is -1",'~'.expand(""))"
+          "    call Decho("..reason: if oldbufnr#".oldbufnr." != bufnr(%)#".bufnr("%"),'~'.expand(""))"
+        endif
+        "   call Dredir("ls!","s:NetrwBufRename (after rename)")
+        "  else " Decho
+        "   call Decho("oldbufname<".oldbufname."> == a:newname: did *not* rename",'~'.expand(""))
+      endif
 
-  return ''
-endf
+      "  call Dret("s:NetrwBufRename : buf#".bufnr("%").": oldname<".oldbufname."> newname<".a:newname."> expand(%)<".expand("%").">")
+    endfun
 
-" ---------------------------------------------------------------------
-" netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2
-fun! netrw#BrowseXVis()
-  let dict={}
-  let dict.a=[getreg('a'), getregtype('a')]
-  norm! gv"ay
-  let gxfile= @a
-  call s:RestoreRegister(dict)
-  call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile))
-endfun
+    " ---------------------------------------------------------------------
+    " netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2
+    fun! netrw#CheckIfRemote(...)
+      "  call Dfunc("netrw#CheckIfRemote() a:0=".a:0)
+      if a:0 > 0
+        let curfile= a:1
+      else
+        let curfile= expand("%")
+      endif
+      " Ignore terminal buffers
+      if &buftype ==# 'terminal'
+        return 0
+      endif
+      "  call Decho("curfile<".curfile.">")
+      if curfile =~ '^\a\{3,}://'
+        "   call Dret("netrw#CheckIfRemote 1")
+        return 1
+      else
+        "   call Dret("netrw#CheckIfRemote 0")
+        return 0
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwChgPerm: (implements "gp") change file permission {{{2
+    fun! s:NetrwChgPerm(islocal,curdir)
+      let ykeep  = @@
+      call inputsave()
+      let newperm= input("Enter new permission: ")
+      call inputrestore()
+      let chgperm= substitute(g:netrw_chgperm,'\',s:ShellEscape(expand("")),'')
+      let chgperm= substitute(chgperm,'\',s:ShellEscape(newperm),'')
+      call system(chgperm)
+      if v:shell_error != 0
+        NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("")."> seems to have failed",75)
+      endif
+      if a:islocal
+        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      endif
+      let @@= ykeep
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:CheckIfKde: checks if kdeinit is running {{{2
+    "    Returns 0: kdeinit not running
+    "            1: kdeinit is  running
+    fun! s:CheckIfKde()
+      "  call Dfunc("s:CheckIfKde()")
+      " seems kde systems often have gnome-open due to dependencies, even though
+      " gnome-open's subsidiary display tools are largely absent.  Kde systems
+      " usually have "kdeinit" running, though...  (tnx Mikolaj Machowski)
+      if !exists("s:haskdeinit")
+        if has("unix") && executable("ps") && !has("win32unix")
+          let s:haskdeinit= system("ps -e") =~ '\"))
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwBufRename: renames a buffer without the side effect of retaining an unlisted buffer having the old name {{{2
-"                   Using the file command on a "[No Name]" buffer does not seem to cause the old "[No Name]" buffer
-"                   to become an unlisted buffer, so in that case don't bwipe it.
-fun! s:NetrwBufRename(newname)
-"  call Dfunc("s:NetrwBufRename(newname<".a:newname.">) buf(%)#".bufnr("%")."<".bufname(bufnr("%")).">")
-"  call Dredir("ls!","s:NetrwBufRename (before rename)")
-  let oldbufname= bufname(bufnr("%"))
-"  call Decho("buf#".bufnr("%").": oldbufname<".oldbufname.">",'~'.expand(""))
-
-  if oldbufname != a:newname
-"   call Decho("do buffer rename: oldbufname<".oldbufname."> ≠ a:newname<".a:newname.">",'~'.expand(""))
-   let b:junk= 1
-"   call Decho("rename buffer: sil! keepj keepalt file ".fnameescape(a:newname),'~'.expand(""))
-   exe 'sil! keepj keepalt file '.fnameescape(a:newname)
-"   call Dredir("ls!","s:NetrwBufRename (before bwipe)~".expand(""))
-   let oldbufnr= bufnr(oldbufname)
-"   call Decho("oldbufname<".oldbufname."> oldbufnr#".oldbufnr,'~'.expand(""))
-"   call Decho("bufnr(%)=".bufnr("%"),'~'.expand(""))
-   if oldbufname != "" && oldbufnr != -1 && oldbufnr != bufnr("%")
-"    call Decho("bwipe ".oldbufnr,'~'.expand(""))
-    exe "bwipe! ".oldbufnr
-"   else " Decho
-"    call Decho("did *not* bwipe buf#".oldbufnr,'~'.expand(""))
-"    call Decho("..reason: if oldbufname<".oldbufname."> is empty",'~'.expand(""))"
-"    call Decho("..reason: if oldbufnr#".oldbufnr." is -1",'~'.expand(""))"
-"    call Decho("..reason: if oldbufnr#".oldbufnr." != bufnr(%)#".bufnr("%"),'~'.expand(""))"
-   endif
-"   call Dredir("ls!","s:NetrwBufRename (after rename)")
-"  else " Decho
-"   call Decho("oldbufname<".oldbufname."> == a:newname: did *not* rename",'~'.expand(""))
-  endif
-
-"  call Dret("s:NetrwBufRename : buf#".bufnr("%").": oldname<".oldbufname."> newname<".a:newname."> expand(%)<".expand("%").">")
-endfun
+      "  call Dret("s:CheckIfKde ".s:haskdeinit)
+      return s:haskdeinit
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwClearExplore: clear explore variables (if any) {{{2
+    fun! s:NetrwClearExplore()
+      "  call Dfunc("s:NetrwClearExplore()")
+      2match none
+      if exists("s:explore_match")        |unlet s:explore_match        |endif
+      if exists("s:explore_indx")         |unlet s:explore_indx         |endif
+      if exists("s:netrw_explore_prvdir") |unlet s:netrw_explore_prvdir |endif
+      if exists("s:dirstarstar")          |unlet s:dirstarstar          |endif
+      if exists("s:explore_prvdir")       |unlet s:explore_prvdir       |endif
+      if exists("w:netrw_explore_indx")   |unlet w:netrw_explore_indx   |endif
+      if exists("w:netrw_explore_listlen")|unlet w:netrw_explore_listlen|endif
+      if exists("w:netrw_explore_list")   |unlet w:netrw_explore_list   |endif
+      if exists("w:netrw_explore_bufnr")  |unlet w:netrw_explore_bufnr  |endif
+      "   redraw!
+      "  call Dret("s:NetrwClearExplore")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2
+    fun! s:NetrwEditBuf(bufnum)
+      "  call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)")
+      if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
+        "   call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum))
+        exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)
+      else
+        "   call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum))
+        exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)
+      endif
+      "  call Dret("s:NetrwEditBuf")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2
+    "    NetrwKeepj [keepalt]   
+    fun! s:NetrwEditFile(cmd,opt,fname)
+      "  call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">)  ft<".&ft.">")
+      if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
+        "   call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname))
+        exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)
+      else
+        "   call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname))
+        if a:cmd =~# 'e\%[new]!' && !&hidden && getbufvar(bufname('%'), '&modified', 0)
+          call setbufvar(bufname('%'), '&bufhidden', 'hide')
+        endif
+        exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)
+      endif
+      "  call Dret("s:NetrwEditFile")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwExploreListUniq: {{{2
+    fun! s:NetrwExploreListUniq(explist)
+      " this assumes that the list is already sorted
+      let newexplist= []
+      for member in a:explist
+        if !exists("uniqmember") || member != uniqmember
+          let uniqmember = member
+          let newexplist = newexplist + [ member ]
+        endif
+      endfor
+      return newexplist
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwForceChgDir: (gd support) Force treatment as a directory {{{2
+    fun! s:NetrwForceChgDir(islocal,newdir)
+      let ykeep= @@
+      if a:newdir !~ '/$'
+        " ok, looks like force is needed to get directory-style treatment
+        if a:newdir =~ '@$'
+          let newdir= substitute(a:newdir,'@$','/','')
+        elseif a:newdir =~ '[*=|\\]$'
+          let newdir= substitute(a:newdir,'.$','/','')
+        else
+          let newdir= a:newdir.'/'
+        endif
+      else
+        " should already be getting treatment as a directory
+        let newdir= a:newdir
+      endif
+      let newdir= s:NetrwBrowseChgDir(a:islocal,newdir,0)
+      call s:NetrwBrowse(a:islocal,newdir)
+      let @@= ykeep
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwGlob: does glob() if local, remote listing otherwise {{{2
+    "     direntry: this is the name of the directory.  Will be fnameescape'd to prevent wildcard handling by glob()
+    "     expr    : this is the expression to follow the directory.  Will use s:ComposePath()
+    "     pare    =1: remove the current directory from the resulting glob() filelist
+    "             =0: leave  the current directory   in the resulting glob() filelist
+    fun! s:NetrwGlob(direntry,expr,pare)
+      "  call Dfunc("s:NetrwGlob(direntry<".a:direntry."> expr<".a:expr."> pare=".a:pare.")")
+      if netrw#CheckIfRemote()
+        keepalt 1sp
+        keepalt enew
+        let keep_liststyle    = w:netrw_liststyle
+        let w:netrw_liststyle = s:THINLIST
+        if s:NetrwRemoteListing() == 0
+          keepj keepalt %s@/@@
+          let filelist= getline(1,$)
+          q!
+        else
+          " remote listing error -- leave treedict unchanged
+          let filelist= w:netrw_treedict[a:direntry]
+        endif
+        let w:netrw_liststyle= keep_liststyle
+      else
+        let path= s:ComposePath(fnameescape(a:direntry), a:expr)
+        if has("win32")
+          " escape [ so it is not detected as wildcard character, see :h wildcard
+          let path= substitute(path, '[', '[[]', 'g')
+        endif
+        if v:version > 704 || (v:version == 704 && has("patch656"))
+          let filelist= glob(path,0,1,1)
+        else
+          let filelist= glob(path,0,1)
+        endif
+        if a:pare
+          let filelist= map(filelist,'substitute(v:val, "^.*/", "", "")')
+        endif
+      endif
+      return filelist
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwForceFile: (gf support) Force treatment as a file {{{2
+    fun! s:NetrwForceFile(islocal,newfile)
+      if a:newfile =~ '[/@*=|\\]$'
+        let newfile= substitute(a:newfile,'.$','','')
+      else
+        let newfile= a:newfile
+      endif
+      if a:islocal
+        call s:NetrwBrowseChgDir(a:islocal,newfile,0)
+      else
+        call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,newfile,0))
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwHide: this function is invoked by the "a" map for browsing {{{2
+    "          and switches the hiding mode.  The actual hiding is done by
+    "          s:NetrwListHide().
+    "             g:netrw_hide= 0: show all
+    "                           1: show not-hidden files
+    "                           2: show hidden files only
+    fun! s:NetrwHide(islocal)
+      let ykeep= @@
+      let svpos= winsaveview()
+
+      if exists("s:netrwmarkfilelist_{bufnr('%')}")
+
+        " hide the files in the markfile list
+        for fname in s:netrwmarkfilelist_{bufnr("%")}
+          if match(g:netrw_list_hide,'\<'.fname.'\>') != -1
+            " remove fname from hiding list
+            let g:netrw_list_hide= substitute(g:netrw_list_hide,'..\<'.escape(fname,g:netrw_fname_escape).'\>..','','')
+            let g:netrw_list_hide= substitute(g:netrw_list_hide,',,',',','g')
+            let g:netrw_list_hide= substitute(g:netrw_list_hide,'^,\|,$','','')
+          else
+            " append fname to hiding list
+            if exists("g:netrw_list_hide") && g:netrw_list_hide != ""
+              let g:netrw_list_hide= g:netrw_list_hide.',\<'.escape(fname,g:netrw_fname_escape).'\>'
+            else
+              let g:netrw_list_hide= '\<'.escape(fname,g:netrw_fname_escape).'\>'
+            endif
+          endif
+        endfor
+        NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+        let g:netrw_hide= 1
 
-" ---------------------------------------------------------------------
-" netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2
-fun! netrw#CheckIfRemote(...)
-"  call Dfunc("netrw#CheckIfRemote() a:0=".a:0)
-  if a:0 > 0
-   let curfile= a:1
-  else
-   let curfile= expand("%")
-  endif
+      else
 
-  " Ignore terminal buffers
-  if &buftype ==# 'terminal'
-    return 0
-  endif
-"  call Decho("curfile<".curfile.">")
-  if curfile =~ '^\a\{3,}://'
-"   call Dret("netrw#CheckIfRemote 1")
-   return 1
-  else
-"   call Dret("netrw#CheckIfRemote 0")
-   return 0
-  endif
-endfun
+        " switch between show-all/show-not-hidden/show-hidden
+        let g:netrw_hide=(g:netrw_hide+1)%3
+        exe "NetrwKeepj norm! 0"
+        if g:netrw_hide && g:netrw_list_hide == ""
+          NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49)
+          let @@= ykeep
+          return
+        endif
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwChgPerm: (implements "gp") change file permission {{{2
-fun! s:NetrwChgPerm(islocal,curdir)
-  let ykeep  = @@
-  call inputsave()
-  let newperm= input("Enter new permission: ")
-  call inputrestore()
-  let chgperm= substitute(g:netrw_chgperm,'\',s:ShellEscape(expand("")),'')
-  let chgperm= substitute(chgperm,'\',s:ShellEscape(newperm),'')
-  call system(chgperm)
-  if v:shell_error != 0
-   NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("")."> seems to have failed",75)
-  endif
-  if a:islocal
-   NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  endif
-  let @@= ykeep
-endfun
+      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      NetrwKeepj call winrestview(svpos)
+      let @@= ykeep
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwHideEdit: allows user to edit the file/directory hiding list {{{2
+    fun! s:NetrwHideEdit(islocal)
+      let ykeep= @@
+      " save current cursor position
+      let svpos= winsaveview()
+
+      " get new hiding list from user
+      call inputsave()
+      let newhide= input("Edit Hiding List: ",g:netrw_list_hide)
+      call inputrestore()
+      let g:netrw_list_hide= newhide
+
+      " refresh the listing
+      sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./",0))
+
+      " restore cursor position
+      call winrestview(svpos)
+      let @@= ykeep
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwHidden: invoked by "gh" {{{2
+    fun! s:NetrwHidden(islocal)
+      let ykeep= @@
+      "  save current position
+      let svpos  = winsaveview()
+
+      if g:netrw_list_hide =~ '\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+'
+        " remove .file pattern from hiding list
+        let g:netrw_list_hide= substitute(g:netrw_list_hide,'\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+','','')
+      elseif s:Strlen(g:netrw_list_hide) >= 1
+        let g:netrw_list_hide= g:netrw_list_hide . ',\(^\|\s\s\)\zs\.\S\+'
+      else
+        let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+'
+      endif
+      if g:netrw_list_hide =~ '^,'
+        let g:netrw_list_hide= strpart(g:netrw_list_hide,1)
+      endif
 
-" ---------------------------------------------------------------------
-" s:CheckIfKde: checks if kdeinit is running {{{2
-"    Returns 0: kdeinit not running
-"            1: kdeinit is  running
-fun! s:CheckIfKde()
-"  call Dfunc("s:CheckIfKde()")
-  " seems kde systems often have gnome-open due to dependencies, even though
-  " gnome-open's subsidiary display tools are largely absent.  Kde systems
-  " usually have "kdeinit" running, though...  (tnx Mikolaj Machowski)
-  if !exists("s:haskdeinit")
-   if has("unix") && executable("ps") && !has("win32unix")
-    let s:haskdeinit= system("ps -e") =~ '\"))
-  endif
-
-"  call Dret("s:CheckIfKde ".s:haskdeinit)
-  return s:haskdeinit
-endfun
+      " refresh screen and return to saved position
+      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      NetrwKeepj call winrestview(svpos)
+      let @@= ykeep
+    endfun
 
-" ---------------------------------------------------------------------
-" s:NetrwClearExplore: clear explore variables (if any) {{{2
-fun! s:NetrwClearExplore()
-"  call Dfunc("s:NetrwClearExplore()")
-  2match none
-  if exists("s:explore_match")        |unlet s:explore_match        |endif
-  if exists("s:explore_indx")         |unlet s:explore_indx         |endif
-  if exists("s:netrw_explore_prvdir") |unlet s:netrw_explore_prvdir |endif
-  if exists("s:dirstarstar")          |unlet s:dirstarstar          |endif
-  if exists("s:explore_prvdir")       |unlet s:explore_prvdir       |endif
-  if exists("w:netrw_explore_indx")   |unlet w:netrw_explore_indx   |endif
-  if exists("w:netrw_explore_listlen")|unlet w:netrw_explore_listlen|endif
-  if exists("w:netrw_explore_list")   |unlet w:netrw_explore_list   |endif
-  if exists("w:netrw_explore_bufnr")  |unlet w:netrw_explore_bufnr  |endif
-"   redraw!
-"  call Dret("s:NetrwClearExplore")
-endfun
+    " ---------------------------------------------------------------------
+    "  s:NetrwHome: this function determines a "home" for saving bookmarks and history {{{2
+    fun! s:NetrwHome()
+      if exists("g:netrw_home")
+        let home= expand(g:netrw_home)
+      else
+        let home = stdpath('data')
+      endif
+      " insure that the home directory exists
+      if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home))
+        "   call Decho("insure that the home<".home."> directory exists")
+        if exists("g:netrw_mkdir")
+          "    call Decho("call system(".g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)).")")
+          call system(g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)))
+        else
+          "    call Decho("mkdir(".home.")")
+          call mkdir(home)
+        endif
+      endif
+      let g:netrw_home= home
+      return home
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwLeftmouse: handles the  when in a netrw browsing window {{{2
+    fun! s:NetrwLeftmouse(islocal)
+      if exists("s:netrwdrag")
+        return
+      endif
+      if &ft != "netrw"
+        return
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2
-fun! s:NetrwEditBuf(bufnum)
-"  call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)")
-  if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
-"   call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum))
-   exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)
-  else
-"   call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum))
-   exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)
-  endif
-"  call Dret("s:NetrwEditBuf")
-endfun
+      let ykeep= @@
+      " check if the status bar was clicked on instead of a file/directory name
+      while getchar(0) != 0
+        "clear the input stream
+      endwhile
+      call feedkeys("\")
+      let c          = getchar()
+      let mouse_lnum = v:mouse_lnum
+      let wlastline  = line('w$')
+      let lastline   = line('$')
+      if mouse_lnum >= wlastline + 1 || v:mouse_win != winnr()
+        " appears to be a status bar leftmouse click
+        let @@= ykeep
+        return
+      endif
+      " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode
+      " Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar
+      " without this test when its disabled.
+      " May 26, 2014: edit file, :Lex, resize window -- causes refresh.  Reinstated a modified test.  See if problems develop.
+      if v:mouse_col > virtcol('.')
+        let @@= ykeep
+        return
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2
-"    NetrwKeepj [keepalt]   
-fun! s:NetrwEditFile(cmd,opt,fname)
-"  call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">)  ft<".&ft.">")
-  if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
-"   call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname))
-   exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)
-  else
-"   call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname))
-    if a:cmd =~# 'e\%[new]!' && !&hidden && getbufvar(bufname('%'), '&modified', 0)
-      call setbufvar(bufname('%'), '&bufhidden', 'hide')
-    endif
-   exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)
-  endif
-"  call Dret("s:NetrwEditFile")
-endfun
+      if a:islocal
+        if exists("b:netrw_curdir")
+          NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
+        endif
+      else
+        if exists("b:netrw_curdir")
+          NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+        endif
+      endif
+      let @@= ykeep
+    endfun
 
-" ---------------------------------------------------------------------
-" s:NetrwExploreListUniq: {{{2
-fun! s:NetrwExploreListUniq(explist)
-  " this assumes that the list is already sorted
-  let newexplist= []
-  for member in a:explist
-   if !exists("uniqmember") || member != uniqmember
-    let uniqmember = member
-    let newexplist = newexplist + [ member ]
-   endif
-  endfor
-  return newexplist
-endfun
+    " ---------------------------------------------------------------------
+    " s:NetrwCLeftmouse: used to select a file/directory for a target {{{2
+    fun! s:NetrwCLeftmouse(islocal)
+      if &ft != "netrw"
+        return
+      endif
+      call s:NetrwMarkFileTgt(a:islocal)
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER  (implements ){{{2
+    "   a:islocal=0 :  not used, remote
+    "   a:islocal=1 :  not used, local
+    "   a:islocal=2 :      used, remote
+    "   a:islocal=3 :      used, local
+    fun! s:NetrwServerEdit(islocal,fname)
+      "  call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)")
+      let islocal = a:islocal%2      " =0: remote           =1: local
+      let ctrlr   = a:islocal >= 2   " =0:  not used   =1:  used
+
+      if (islocal && isdirectory(s:NetrwFile(a:fname))) || (!islocal && a:fname =~ '/$')
+        " handle directories in the local window -- not in the remote vim server
+        " user must have closed the NETRWSERVER window.  Treat as normal editing from netrw.
+        let g:netrw_browse_split= 0
+        if exists("s:netrw_browse_split") && exists("s:netrw_browse_split_".winnr())
+          let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
+          unlet s:netrw_browse_split_{winnr()}
+        endif
+        call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
+        return
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwForceChgDir: (gd support) Force treatment as a directory {{{2
-fun! s:NetrwForceChgDir(islocal,newdir)
-  let ykeep= @@
-  if a:newdir !~ '/$'
-   " ok, looks like force is needed to get directory-style treatment
-   if a:newdir =~ '@$'
-    let newdir= substitute(a:newdir,'@$','/','')
-   elseif a:newdir =~ '[*=|\\]$'
-    let newdir= substitute(a:newdir,'.$','/','')
-   else
-    let newdir= a:newdir.'/'
-   endif
-  else
-   " should already be getting treatment as a directory
-   let newdir= a:newdir
-  endif
-  let newdir= s:NetrwBrowseChgDir(a:islocal,newdir,0)
-  call s:NetrwBrowse(a:islocal,newdir)
-  let @@= ykeep
-endfun
+      if has("clientserver") && executable("gvim")
+
+        if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3
+          let srvrname = g:netrw_browse_split[0]
+          let tabnum   = g:netrw_browse_split[1]
+          let winnum   = g:netrw_browse_split[2]
+
+          if serverlist() !~ '\<'.srvrname.'\>'
+            if !ctrlr
+              " user must have closed the server window and the user did not use , but
+              " used something like .
+              if exists("g:netrw_browse_split")
+                unlet g:netrw_browse_split
+              endif
+              let g:netrw_browse_split= 0
+              if exists("s:netrw_browse_split_".winnr())
+                let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
+              endif
+              call s:NetrwBrowseChgDir(islocal,a:fname,0)
+              return
+
+            elseif has("win32") && executable("start")
+              " start up remote netrw server under windows
+              call system("start gvim --servername ".srvrname)
+
+            else
+              " start up remote netrw server under linux
+              call system("gvim --servername ".srvrname)
+            endif
+          endif
+
+          call remote_send(srvrname,":tabn ".tabnum."\")
+          call remote_send(srvrname,":".winnum."wincmd w\")
+          call remote_send(srvrname,":e ".fnameescape(s:NetrwFile(a:fname))."\")
+        else
+
+          if serverlist() !~ '\<'.g:netrw_servername.'\>'
+
+            if !ctrlr
+              if exists("g:netrw_browse_split")
+                unlet g:netrw_browse_split
+              endif
+              let g:netrw_browse_split= 0
+              call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
+              return
+
+            else
+              if has("win32") && executable("start")
+                " start up remote netrw server under windows
+                call system("start gvim --servername ".g:netrw_servername)
+              else
+                " start up remote netrw server under linux
+                call system("gvim --servername ".g:netrw_servername)
+              endif
+            endif
+          endif
+
+          while 1
+            try
+              call remote_send(g:netrw_servername,":e ".fnameescape(s:NetrwFile(a:fname))."\")
+              break
+            catch /^Vim\%((\a\+)\)\=:E241/
+              sleep 200m
+            endtry
+          endwhile
+
+          if exists("g:netrw_browse_split")
+            if type(g:netrw_browse_split) != 3
+              let s:netrw_browse_split_{winnr()}= g:netrw_browse_split
+            endif
+            unlet g:netrw_browse_split
+          endif
+          let g:netrw_browse_split= [g:netrw_servername,1,1]
+        endif
 
-" ---------------------------------------------------------------------
-" s:NetrwGlob: does glob() if local, remote listing otherwise {{{2
-"     direntry: this is the name of the directory.  Will be fnameescape'd to prevent wildcard handling by glob()
-"     expr    : this is the expression to follow the directory.  Will use s:ComposePath()
-"     pare    =1: remove the current directory from the resulting glob() filelist
-"             =0: leave  the current directory   in the resulting glob() filelist
-fun! s:NetrwGlob(direntry,expr,pare)
-"  call Dfunc("s:NetrwGlob(direntry<".a:direntry."> expr<".a:expr."> pare=".a:pare.")")
-  if netrw#CheckIfRemote()
-   keepalt 1sp
-   keepalt enew
-   let keep_liststyle    = w:netrw_liststyle
-   let w:netrw_liststyle = s:THINLIST
-   if s:NetrwRemoteListing() == 0
-    keepj keepalt %s@/@@
-    let filelist= getline(1,$)
-    q!
-   else
-    " remote listing error -- leave treedict unchanged
-    let filelist= w:netrw_treedict[a:direntry]
-   endif
-   let w:netrw_liststyle= keep_liststyle
-  else
-   let path= s:ComposePath(fnameescape(a:direntry), a:expr)
-    if has("win32")
-     " escape [ so it is not detected as wildcard character, see :h wildcard
-     let path= substitute(path, '[', '[[]', 'g')
-    endif
-    if v:version > 704 || (v:version == 704 && has("patch656"))
-     let filelist= glob(path,0,1,1)
-    else
-     let filelist= glob(path,0,1)
-    endif
-    if a:pare
-     let filelist= map(filelist,'substitute(v:val, "^.*/", "", "")')
-    endif
-  endif
-  return filelist
-endfun
+      else
+        call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use ",98)
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwForceFile: (gf support) Force treatment as a file {{{2
-fun! s:NetrwForceFile(islocal,newfile)
-  if a:newfile =~ '[/@*=|\\]$'
-   let newfile= substitute(a:newfile,'.$','','')
-  else
-   let newfile= a:newfile
-  endif
-  if a:islocal
-   call s:NetrwBrowseChgDir(a:islocal,newfile,0)
-  else
-   call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,newfile,0))
-  endif
-endfun
+    endfun
 
-" ---------------------------------------------------------------------
-" s:NetrwHide: this function is invoked by the "a" map for browsing {{{2
-"          and switches the hiding mode.  The actual hiding is done by
-"          s:NetrwListHide().
-"             g:netrw_hide= 0: show all
-"                           1: show not-hidden files
-"                           2: show hidden files only
-fun! s:NetrwHide(islocal)
-  let ykeep= @@
-  let svpos= winsaveview()
+    " ---------------------------------------------------------------------
+    " s:NetrwSLeftmouse: marks the file under the cursor.  May be dragged to select additional files {{{2
+    fun! s:NetrwSLeftmouse(islocal)
+      if &ft != "netrw"
+        return
+      endif
+      "  call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")")
+
+      let s:ngw= s:NetrwGetWord()
+      call s:NetrwMarkFile(a:islocal,s:ngw)
+
+      "  call Dret("s:NetrwSLeftmouse")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwSLeftdrag: invoked via a shift-leftmouse and dragging {{{2
+    "                   Used to mark multiple files.
+    fun! s:NetrwSLeftdrag(islocal)
+      "  call Dfunc("s:NetrwSLeftdrag(islocal=".a:islocal.")")
+      if !exists("s:netrwdrag")
+        let s:netrwdrag = winnr()
+        if a:islocal
+          nno   :call NetrwSLeftrelease(1)
+        else
+          nno   :call NetrwSLeftrelease(0)
+        endif
+      endif
+      let ngw = s:NetrwGetWord()
+      if !exists("s:ngw") || s:ngw != ngw
+        call s:NetrwMarkFile(a:islocal,ngw)
+      endif
+      let s:ngw= ngw
+      "  call Dret("s:NetrwSLeftdrag : s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwSLeftrelease: terminates shift-leftmouse dragging {{{2
+    fun! s:NetrwSLeftrelease(islocal)
+      "  call Dfunc("s:NetrwSLeftrelease(islocal=".a:islocal.") s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
+      if exists("s:netrwdrag")
+        nunmap 
+        let ngw = s:NetrwGetWord()
+        if !exists("s:ngw") || s:ngw != ngw
+          call s:NetrwMarkFile(a:islocal,ngw)
+        endif
+        if exists("s:ngw")
+          unlet s:ngw
+        endif
+        unlet s:netrwdrag
+      endif
+      "  call Dret("s:NetrwSLeftrelease")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwListHide: uses [range]g~...~d to delete files that match       {{{2
+    "                  comma-separated patterns given in g:netrw_list_hide
+    fun! s:NetrwListHide()
+      "  call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
+      "  call Decho("initial: ".string(getline(w:netrw_bannercnt,'$')))
+      let ykeep= @@
+
+      " find a character not in the "hide" string to use as a separator for :g and :v commands
+      " How-it-works: take the hiding command, convert it into a range.
+      " Duplicate characters don't matter.
+      " Remove all such characters from the '/~@#...890' string.
+      " Use the first character left as a separator character.
+      "  call Decho("find a character not in the hide string to use as a separator",'~'.expand(""))
+      let listhide= g:netrw_list_hide
+      let sep     = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1)
+      "  call Decho("sep<".sep.">  (sep not in hide string)",'~'.expand(""))
+
+      while listhide != ""
+        if listhide =~ ','
+          let hide     = substitute(listhide,',.*$','','e')
+          let listhide = substitute(listhide,'^.\{-},\(.*\)$','\1','e')
+        else
+          let hide     = listhide
+          let listhide = ""
+        endif
+        "   call Decho("..extracted pattern from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand(""))
+        if g:netrw_sort_by =~ '^[ts]'
+          if hide =~ '^\^'
+            "     call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand(""))
+            let hide= substitute(hide,'^\^','^\(\\d\\+/\)','')
+          elseif hide =~ '^\\(\^'
+            let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','')
+          endif
+          "    call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand(""))
+        endif
 
-  if exists("s:netrwmarkfilelist_{bufnr('%')}")
+        " Prune the list by hiding any files which match
+        "   call Decho("..prune the list by hiding any files which ".((g:netrw_hide == 1)? "" : "don't")."match hide<".hide.">")
+        if g:netrw_hide == 1
+          "    call Decho("..hiding<".hide.">",'~'.expand(""))
+          exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d'
+        elseif g:netrw_hide == 2
+          "    call Decho("..showing<".hide.">",'~'.expand(""))
+          exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @'
+        endif
+        "   call Decho("..result: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
+      endwhile
 
-   " hide the files in the markfile list
-   for fname in s:netrwmarkfilelist_{bufnr("%")}
-    if match(g:netrw_list_hide,'\<'.fname.'\>') != -1
-     " remove fname from hiding list
-     let g:netrw_list_hide= substitute(g:netrw_list_hide,'..\<'.escape(fname,g:netrw_fname_escape).'\>..','','')
-     let g:netrw_list_hide= substitute(g:netrw_list_hide,',,',',','g')
-     let g:netrw_list_hide= substitute(g:netrw_list_hide,'^,\|,$','','')
-    else
-     " append fname to hiding list
-     if exists("g:netrw_list_hide") && g:netrw_list_hide != ""
-      let g:netrw_list_hide= g:netrw_list_hide.',\<'.escape(fname,g:netrw_fname_escape).'\>'
-     else
-      let g:netrw_list_hide= '\<'.escape(fname,g:netrw_fname_escape).'\>'
-     endif
-    endif
-   endfor
-   NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
-   let g:netrw_hide= 1
+      if g:netrw_hide == 2
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d'
+        "   call Decho("..v KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e'
+        "   call Decho("..g KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
+      endif
 
-  else
+      " remove any blank lines that have somehow remained.
+      " This seems to happen under Windows.
+      exe 'sil! NetrwKeepj 1,$g@^\s*$@d'
 
-   " switch between show-all/show-not-hidden/show-hidden
-   let g:netrw_hide=(g:netrw_hide+1)%3
-   exe "NetrwKeepj norm! 0"
-   if g:netrw_hide && g:netrw_list_hide == ""
-    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49)
-    let @@= ykeep
-    return
-   endif
-  endif
+      let @@= ykeep
+      "  call Dret("s:NetrwListHide")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2
+    "                 implements the "d" mapping.
+    fun! s:NetrwMakeDir(usrhost)
+
+      let ykeep= @@
+      " get name of new directory from user.  A bare  will skip.
+      " if its currently a directory, also request will be skipped, but with
+      " a message.
+      call inputsave()
+      let newdirname= input("Please give directory name: ")
+      call inputrestore()
+
+      if newdirname == ""
+        let @@= ykeep
+        return
+      endif
 
-  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  NetrwKeepj call winrestview(svpos)
-  let @@= ykeep
-endfun
+      if a:usrhost == ""
+
+        " Local mkdir:
+        " sanity checks
+        let fullnewdir= b:netrw_curdir.'/'.newdirname
+        if isdirectory(s:NetrwFile(fullnewdir))
+          if !exists("g:netrw_quiet")
+            NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24)
+          endif
+          let @@= ykeep
+          return
+        endif
+        if s:FileReadable(fullnewdir)
+          if !exists("g:netrw_quiet")
+            NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25)
+          endif
+          let @@= ykeep
+          return
+        endif
 
-" ---------------------------------------------------------------------
-" s:NetrwHideEdit: allows user to edit the file/directory hiding list {{{2
-fun! s:NetrwHideEdit(islocal)
-  let ykeep= @@
-  " save current cursor position
-  let svpos= winsaveview()
+        " requested new local directory is neither a pre-existing file or
+        " directory, so make it!
+        if exists("*mkdir")
+          if has("unix")
+            call mkdir(fullnewdir,"p",xor(0777, system("umask")))
+          else
+            call mkdir(fullnewdir,"p")
+          endif
+        else
+          let netrw_origdir= s:NetrwGetcwd(1)
+          if s:NetrwLcd(b:netrw_curdir)
+            return
+          endif
+          call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(newdirname,1))
+          if v:shell_error != 0
+            let @@= ykeep
+            call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
+            return
+          endif
+          if !g:netrw_keepdir
+            if s:NetrwLcd(netrw_origdir)
+              return
+            endif
+          endif
+        endif
 
-  " get new hiding list from user
-  call inputsave()
-  let newhide= input("Edit Hiding List: ",g:netrw_list_hide)
-  call inputrestore()
-  let g:netrw_list_hide= newhide
+        if v:shell_error == 0
+          " refresh listing
+          let svpos= winsaveview()
+          call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
+          call winrestview(svpos)
+        elseif !exists("g:netrw_quiet")
+          call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",26)
+        endif
 
-  " refresh the listing
-  sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./",0))
+      elseif !exists("b:netrw_method") || b:netrw_method == 4
+        " Remote mkdir:  using ssh
+        let mkdircmd  = s:MakeSshCmd(g:netrw_mkdir_cmd)
+        let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname
+        call s:NetrwExe("sil! !".mkdircmd." ".s:ShellEscape(newdirname,1))
+        if v:shell_error == 0
+          " refresh listing
+          let svpos= winsaveview()
+          NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
+          NetrwKeepj call winrestview(svpos)
+        elseif !exists("g:netrw_quiet")
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27)
+        endif
 
-  " restore cursor position
-  call winrestview(svpos)
-  let @@= ykeep
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwHidden: invoked by "gh" {{{2
-fun! s:NetrwHidden(islocal)
-  let ykeep= @@
-  "  save current position
-  let svpos  = winsaveview()
-
-  if g:netrw_list_hide =~ '\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+'
-   " remove .file pattern from hiding list
-   let g:netrw_list_hide= substitute(g:netrw_list_hide,'\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+','','')
-  elseif s:Strlen(g:netrw_list_hide) >= 1
-   let g:netrw_list_hide= g:netrw_list_hide . ',\(^\|\s\s\)\zs\.\S\+'
-  else
-   let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+'
-  endif
-  if g:netrw_list_hide =~ '^,'
-   let g:netrw_list_hide= strpart(g:netrw_list_hide,1)
-  endif
-
-  " refresh screen and return to saved position
-  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  NetrwKeepj call winrestview(svpos)
-  let @@= ykeep
-endfun
-
-" ---------------------------------------------------------------------
-"  s:NetrwHome: this function determines a "home" for saving bookmarks and history {{{2
-fun! s:NetrwHome()
-  if exists("g:netrw_home")
-   let home= expand(g:netrw_home)
-  else
-   let home = stdpath('data')
-  endif
-  " insure that the home directory exists
-  if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home))
-"   call Decho("insure that the home<".home."> directory exists")
-   if exists("g:netrw_mkdir")
-"    call Decho("call system(".g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)).")")
-    call system(g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)))
-   else
-"    call Decho("mkdir(".home.")")
-    call mkdir(home)
-   endif
-  endif
-  let g:netrw_home= home
-  return home
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwLeftmouse: handles the  when in a netrw browsing window {{{2
-fun! s:NetrwLeftmouse(islocal)
-  if exists("s:netrwdrag")
-   return
-  endif
-  if &ft != "netrw"
-   return
-  endif
-
-  let ykeep= @@
-  " check if the status bar was clicked on instead of a file/directory name
-  while getchar(0) != 0
-   "clear the input stream
-  endwhile
-  call feedkeys("\")
-  let c          = getchar()
-  let mouse_lnum = v:mouse_lnum
-  let wlastline  = line('w$')
-  let lastline   = line('$')
-  if mouse_lnum >= wlastline + 1 || v:mouse_win != winnr()
-   " appears to be a status bar leftmouse click
-   let @@= ykeep
-   return
-  endif
-   " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode
-   " Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar
-   " without this test when its disabled.
-   " May 26, 2014: edit file, :Lex, resize window -- causes refresh.  Reinstated a modified test.  See if problems develop.
-   if v:mouse_col > virtcol('.')
-    let @@= ykeep
-    return
-   endif
+      elseif b:netrw_method == 2
+        " Remote mkdir:  using ftp+.netrc
+        let svpos= winsaveview()
+        if exists("b:netrw_fname")
+          let remotepath= b:netrw_fname
+        else
+          let remotepath= ""
+        endif
+        call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
+        NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
+        NetrwKeepj call winrestview(svpos)
+
+      elseif b:netrw_method == 3
+        " Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc)
+        let svpos= winsaveview()
+        if exists("b:netrw_fname")
+          let remotepath= b:netrw_fname
+        else
+          let remotepath= ""
+        endif
+        call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
+        NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
+        NetrwKeepj call winrestview(svpos)
+      endif
 
-  if a:islocal
-   if exists("b:netrw_curdir")
-    NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
-   endif
-  else
-   if exists("b:netrw_curdir")
-    NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
-   endif
-  endif
-  let @@= ykeep
-endfun
+      let @@= ykeep
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:TreeSqueezeDir: allows a shift-cr (gvim only) to squeeze the current tree-listing directory {{{2
+    fun! s:TreeSqueezeDir(islocal)
+      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
+        " its a tree-listing style
+        let curdepth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e')
+        let stopline = (exists("w:netrw_bannercnt")? (w:netrw_bannercnt + 1) : 1)
+        let depth    = strchars(substitute(curdepth,' ','','g'))
+        let srch     = -1
+        if depth >= 2
+          NetrwKeepj norm! 0
+          let curdepthm1= substitute(curdepth,'^'.s:treedepthstring,'','')
+          let srch      = search('^'.curdepthm1.'\%('.s:treedepthstring.'\)\@!','bW',stopline)
+        elseif depth == 1
+          NetrwKeepj norm! 0
+          let treedepthchr= substitute(s:treedepthstring,' ','','')
+          let srch        = search('^[^'.treedepthchr.']','bW',stopline)
+        endif
+        if srch > 0
+          call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,s:NetrwGetWord(),1))
+          exe srch
+        endif
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMaps: {{{2
+    fun! s:NetrwMaps(islocal)
+
+      " mouse  maps: {{{3
+      if g:netrw_mousemaps && g:netrw_retmap
+        "   call Decho("set up Rexplore 2-leftmouse",'~'.expand(""))
+        if !hasmapto("NetrwReturn")
+          if maparg("<2-leftmouse>","n") == "" || maparg("<2-leftmouse>","n") =~ '^-$'
+            nmap   <2-leftmouse>       NetrwReturn
+          elseif maparg("","n") == ""
+            nmap          NetrwReturn
+          endif
+        endif
+        nno  NetrwReturn       :Rexplore
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwCLeftmouse: used to select a file/directory for a target {{{2
-fun! s:NetrwCLeftmouse(islocal)
-  if &ft != "netrw"
-   return
-  endif
-  call s:NetrwMarkFileTgt(a:islocal)
-endfun
+      " generate default  maps {{{3
+      if !hasmapto('NetrwHide')              |nmap    a       NetrwHide_a|endif
+      if !hasmapto('NetrwBrowseUpDir')       |nmap    -       NetrwBrowseUpDir|endif
+      if !hasmapto('NetrwOpenFile')          |nmap    %       NetrwOpenFile|endif
+      if !hasmapto('NetrwBadd_cb')           |nmap    cb      NetrwBadd_cb|endif
+      if !hasmapto('NetrwBadd_cB')           |nmap    cB      NetrwBadd_cB|endif
+      if !hasmapto('NetrwLcd')               |nmap    cd      NetrwLcd|endif
+      if !hasmapto('NetrwSetChgwin')         |nmap    C       NetrwSetChgwin|endif
+      if !hasmapto('NetrwRefresh')           |nmap       NetrwRefresh|endif
+      if !hasmapto('NetrwLocalBrowseCheck')  |nmap        NetrwLocalBrowseCheck|endif
+      if !hasmapto('NetrwServerEdit')        |nmap       NetrwServerEdit|endif
+      if !hasmapto('NetrwMakeDir')           |nmap    d       NetrwMakeDir|endif
+      if !hasmapto('NetrwBookHistHandler_gb')|nmap    gb      NetrwBookHistHandler_gb|endif
 
-" ---------------------------------------------------------------------
-" s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER  (implements ){{{2
-"   a:islocal=0 :  not used, remote
-"   a:islocal=1 :  not used, local
-"   a:islocal=2 :      used, remote
-"   a:islocal=3 :      used, local
-fun! s:NetrwServerEdit(islocal,fname)
-"  call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)")
-  let islocal = a:islocal%2      " =0: remote           =1: local
-  let ctrlr   = a:islocal >= 2   " =0:  not used   =1:  used
-
-  if (islocal && isdirectory(s:NetrwFile(a:fname))) || (!islocal && a:fname =~ '/$')
-   " handle directories in the local window -- not in the remote vim server
-   " user must have closed the NETRWSERVER window.  Treat as normal editing from netrw.
-   let g:netrw_browse_split= 0
-   if exists("s:netrw_browse_split") && exists("s:netrw_browse_split_".winnr())
-    let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
-    unlet s:netrw_browse_split_{winnr()}
-   endif
-   call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
-   return
-  endif
-
-  if has("clientserver") && executable("gvim")
-
-    if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3
-     let srvrname = g:netrw_browse_split[0]
-     let tabnum   = g:netrw_browse_split[1]
-     let winnum   = g:netrw_browse_split[2]
-
-     if serverlist() !~ '\<'.srvrname.'\>'
-      if !ctrlr
-       " user must have closed the server window and the user did not use , but
-       " used something like .
-       if exists("g:netrw_browse_split")
-        unlet g:netrw_browse_split
-       endif
-       let g:netrw_browse_split= 0
-       if exists("s:netrw_browse_split_".winnr())
-        let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
-       endif
-       call s:NetrwBrowseChgDir(islocal,a:fname,0)
-       return
-
-      elseif has("win32") && executable("start")
-       " start up remote netrw server under windows
-       call system("start gvim --servername ".srvrname)
+      if a:islocal
+        " local normal-mode maps {{{3
+        nnoremap   NetrwHide_a                 :call NetrwHide(1)
+        nnoremap   NetrwBrowseUpDir            :call NetrwBrowseUpDir(1)
+        nnoremap   NetrwOpenFile               :call NetrwOpenFile(1)
+        nnoremap   NetrwBadd_cb                :call NetrwBadd(1,0)
+        nnoremap   NetrwBadd_cB                :call NetrwBadd(1,1)
+        nnoremap   NetrwLcd                    :call NetrwLcd(b:netrw_curdir)
+        nnoremap   NetrwSetChgwin              :call NetrwSetChgwin()
+        nnoremap   NetrwLocalBrowseCheck       :call netrw#LocalBrowseCheck(NetrwBrowseChgDir(1,NetrwGetWord(),1))
+        nnoremap   NetrwServerEdit             :call NetrwServerEdit(3,NetrwGetWord())
+        nnoremap   NetrwMakeDir                :call NetrwMakeDir("")
+        nnoremap   NetrwBookHistHandler_gb     :call NetrwBookHistHandler(1,b:netrw_curdir)
+        " ---------------------------------------------------------------------
+        nnoremap    gd       :call NetrwForceChgDir(1,NetrwGetWord())
+        nnoremap    gf       :call NetrwForceFile(1,NetrwGetWord())
+        nnoremap    gh       :call NetrwHidden(1)
+        nnoremap    gn       :call netrw#SetTreetop(0,NetrwGetWord())
+        nnoremap    gp       :call NetrwChgPerm(1,b:netrw_curdir)
+        nnoremap    I        :call NetrwBannerCtrl(1)
+        nnoremap    i        :call NetrwListStyle(1)
+        nnoremap    ma       :call NetrwMarkFileArgList(1,0)
+        nnoremap    mA       :call NetrwMarkFileArgList(1,1)
+        nnoremap    mb       :call NetrwBookHistHandler(0,b:netrw_curdir)
+        nnoremap    mB       :call NetrwBookHistHandler(6,b:netrw_curdir)
+        nnoremap    mc       :call NetrwMarkFileCopy(1)
+        nnoremap    md       :call NetrwMarkFileDiff(1)
+        nnoremap    me       :call NetrwMarkFileEdit(1)
+        nnoremap    mf       :call NetrwMarkFile(1,NetrwGetWord())
+        nnoremap    mF       :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+        nnoremap    mg       :call NetrwMarkFileGrep(1)
+        nnoremap    mh       :call NetrwMarkHideSfx(1)
+        nnoremap    mm       :call NetrwMarkFileMove(1)
+        " nnoremap    mp       :call NetrwMarkFilePrint(1)
+        nnoremap    mr       :call NetrwMarkFileRegexp(1)
+        nnoremap    ms       :call NetrwMarkFileSource(1)
+        nnoremap    mT       :call NetrwMarkFileTag(1)
+        nnoremap    mt       :call NetrwMarkFileTgt(1)
+        nnoremap    mu       :call NetrwUnMarkFile(1)
+        nnoremap    mv       :call NetrwMarkFileVimCmd(1)
+        nnoremap    mx       :call NetrwMarkFileExe(1,0)
+        nnoremap    mX       :call NetrwMarkFileExe(1,1)
+        nnoremap    mz       :call NetrwMarkFileCompress(1)
+        nnoremap    O        :call NetrwObtain(1)
+        nnoremap    o        :call NetrwSplit(3)
+        nnoremap    p        :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
+        nnoremap    P        :call NetrwPrevWinOpen(1)
+        nnoremap    qb       :call NetrwBookHistHandler(2,b:netrw_curdir)
+        nnoremap    qf       :call NetrwFileInfo(1,NetrwGetWord())
+        nnoremap    qF       :call NetrwMarkFileQFEL(1,getqflist())
+        nnoremap    qL       :call NetrwMarkFileQFEL(1,getloclist(v:count))
+        nnoremap    s        :call NetrwSortStyle(1)
+        nnoremap    S        :call NetSortSequence(1)
+        nnoremap    Tb       :call NetrwSetTgt(1,'b',v:count1)
+        nnoremap    t        :call NetrwSplit(4)
+        nnoremap    Th       :call NetrwSetTgt(1,'h',v:count)
+        nnoremap    u        :call NetrwBookHistHandler(4,expand("%"))
+        nnoremap    U        :call NetrwBookHistHandler(5,expand("%"))
+        nnoremap    v        :call NetrwSplit(5)
+        nnoremap    x        :call netrw#BrowseX(NetrwBrowseChgDir(1,NetrwGetWord(),1,0),0)"
+        nnoremap    X        :call NetrwLocalExecute(expand(""))"
+
+        nnoremap    r        :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwRefresh(1,NetrwBrowseChgDir(1,'./',0))
+        if !hasmapto('NetrwHideEdit')
+          nmap    NetrwHideEdit
+        endif
+        nnoremap   NetrwHideEdit               :call NetrwHideEdit(1)
+        if !hasmapto('NetrwRefresh')
+          nmap    NetrwRefresh
+        endif
+        nnoremap   NetrwRefresh                :call NetrwRefresh(1,NetrwBrowseChgDir(1,(exists("w:netrw_liststyle") && exists("w:netrw_treetop") && w:netrw_liststyle == 3)? w:netrw_treetop : './',0))
+        if s:didstarstar || !mapcheck("","n")
+          nnoremap    :Nexplore
+        endif
+        if s:didstarstar || !mapcheck("","n")
+          nnoremap      :Pexplore
+        endif
+        if !hasmapto('NetrwTreeSqueeze')
+          nmap                          NetrwTreeSqueeze
+        endif
+        nnoremap   NetrwTreeSqueeze            :call TreeSqueezeDir(1)
+        let mapsafecurdir = escape(b:netrw_curdir, s:netrw_map_escape)
+        if g:netrw_mousemaps == 1
+          nmap                                     NetrwLeftmouse
+          nmap                                   NetrwCLeftmouse
+          nmap                                   NetrwMiddlemouse
+          nmap                                   NetrwSLeftmouse
+          nmap                                    NetrwSLeftdrag
+          nmap                        <2-leftmouse>           Netrw2Leftmouse
+          imap                                     ILeftmouse
+          imap                                   IMiddlemouse
+          nno                 NetrwLeftmouse    :exec "norm! \leftmouse>"call NetrwLeftmouse(1)
+          nno                 NetrwCLeftmouse   :exec "norm! \leftmouse>"call NetrwCLeftmouse(1)
+          nno                 NetrwMiddlemouse  :exec "norm! \leftmouse>"call NetrwPrevWinOpen(1)
+          nno                 NetrwSLeftmouse   :exec "norm! \leftmouse>"call NetrwSLeftmouse(1)
+          nno                 NetrwSLeftdrag    :exec "norm! \leftmouse>"call NetrwSLeftdrag(1)
+          nmap                Netrw2Leftmouse   -
+          exe 'nnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
+          exe 'vnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
+        endif
+        exe 'nnoremap           :call NetrwLocalRm("'.mapsafecurdir.'")'
+        exe 'nnoremap    D           :call NetrwLocalRm("'.mapsafecurdir.'")'
+        exe 'nnoremap    R           :call NetrwLocalRename("'.mapsafecurdir.'")'
+        exe 'nnoremap    d           :call NetrwMakeDir("")'
+        exe 'vnoremap           :call NetrwLocalRm("'.mapsafecurdir.'")'
+        exe 'vnoremap    D           :call NetrwLocalRm("'.mapsafecurdir.'")'
+        exe 'vnoremap    R           :call NetrwLocalRename("'.mapsafecurdir.'")'
+        nnoremap                         :he netrw-quickhelp
+
+        " support user-specified maps
+        call netrw#UserMaps(1)
 
       else
-       " start up remote netrw server under linux
-       call system("gvim --servername ".srvrname)
+        " remote normal-mode maps {{{3
+        call s:RemotePathAnalysis(b:netrw_curdir)
+        nnoremap   NetrwHide_a                 :call NetrwHide(0)
+        nnoremap   NetrwBrowseUpDir            :call NetrwBrowseUpDir(0)
+        nnoremap   NetrwOpenFile               :call NetrwOpenFile(0)
+        nnoremap   NetrwBadd_cb                :call NetrwBadd(0,0)
+        nnoremap   NetrwBadd_cB                :call NetrwBadd(0,1)
+        nnoremap   NetrwLcd                    :call NetrwLcd(b:netrw_curdir)
+        nnoremap   NetrwSetChgwin              :call NetrwSetChgwin()
+        nnoremap   NetrwRefresh                :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
+        nnoremap   NetrwLocalBrowseCheck       :call NetrwBrowse(0,NetrwBrowseChgDir(0,NetrwGetWord(),1))
+        nnoremap   NetrwServerEdit             :call NetrwServerEdit(2,NetrwGetWord())
+        nnoremap   NetrwBookHistHandler_gb     :call NetrwBookHistHandler(1,b:netrw_curdir)
+        " ---------------------------------------------------------------------
+        nnoremap    gd       :call NetrwForceChgDir(0,NetrwGetWord())
+        nnoremap    gf       :call NetrwForceFile(0,NetrwGetWord())
+        nnoremap    gh       :call NetrwHidden(0)
+        nnoremap    gp       :call NetrwChgPerm(0,b:netrw_curdir)
+        nnoremap    I        :call NetrwBannerCtrl(1)
+        nnoremap    i        :call NetrwListStyle(0)
+        nnoremap    ma       :call NetrwMarkFileArgList(0,0)
+        nnoremap    mA       :call NetrwMarkFileArgList(0,1)
+        nnoremap    mb       :call NetrwBookHistHandler(0,b:netrw_curdir)
+        nnoremap    mB       :call NetrwBookHistHandler(6,b:netrw_curdir)
+        nnoremap    mc       :call NetrwMarkFileCopy(0)
+        nnoremap    md       :call NetrwMarkFileDiff(0)
+        nnoremap    me       :call NetrwMarkFileEdit(0)
+        nnoremap    mf       :call NetrwMarkFile(0,NetrwGetWord())
+        nnoremap    mF       :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+        nnoremap    mg       :call NetrwMarkFileGrep(0)
+        nnoremap    mh       :call NetrwMarkHideSfx(0)
+        nnoremap    mm       :call NetrwMarkFileMove(0)
+        " nnoremap    mp       :call NetrwMarkFilePrint(0)
+        nnoremap    mr       :call NetrwMarkFileRegexp(0)
+        nnoremap    ms       :call NetrwMarkFileSource(0)
+        nnoremap    mT       :call NetrwMarkFileTag(0)
+        nnoremap    mt       :call NetrwMarkFileTgt(0)
+        nnoremap    mu       :call NetrwUnMarkFile(0)
+        nnoremap    mv       :call NetrwMarkFileVimCmd(0)
+        nnoremap    mx       :call NetrwMarkFileExe(0,0)
+        nnoremap    mX       :call NetrwMarkFileExe(0,1)
+        nnoremap    mz       :call NetrwMarkFileCompress(0)
+        nnoremap    O        :call NetrwObtain(0)
+        nnoremap    o        :call NetrwSplit(0)
+        nnoremap    p        :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
+        nnoremap    P        :call NetrwPrevWinOpen(0)
+        nnoremap    qb       :call NetrwBookHistHandler(2,b:netrw_curdir)
+        nnoremap    qf       :call NetrwFileInfo(0,NetrwGetWord())
+        nnoremap    qF       :call NetrwMarkFileQFEL(0,getqflist())
+        nnoremap    qL       :call NetrwMarkFileQFEL(0,getloclist(v:count))
+        nnoremap    r        :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwBrowse(0,NetrwBrowseChgDir(0,'./',0))
+        nnoremap    s        :call NetrwSortStyle(0)
+        nnoremap    S        :call NetSortSequence(0)
+        nnoremap    Tb       :call NetrwSetTgt(0,'b',v:count1)
+        nnoremap    t        :call NetrwSplit(1)
+        nnoremap    Th       :call NetrwSetTgt(0,'h',v:count)
+        nnoremap    u        :call NetrwBookHistHandler(4,b:netrw_curdir)
+        nnoremap    U        :call NetrwBookHistHandler(5,b:netrw_curdir)
+        nnoremap    v        :call NetrwSplit(2)
+        nnoremap    x        :call netrw#BrowseX(NetrwBrowseChgDir(0,NetrwGetWord(),1),1)
+        nmap                gx       x
+        if !hasmapto('NetrwHideEdit')
+          nmap   NetrwHideEdit
+        endif
+        nnoremap   NetrwHideEdit       :call NetrwHideEdit(0)
+        if !hasmapto('NetrwRefresh')
+          nmap   NetrwRefresh
+        endif
+        if !hasmapto('NetrwTreeSqueeze')
+          nmap          NetrwTreeSqueeze
+        endif
+        nnoremap   NetrwTreeSqueeze    :call TreeSqueezeDir(0)
+
+        let mapsafepath     = escape(s:path, s:netrw_map_escape)
+        let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape)
+
+        nnoremap   NetrwRefresh        :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
+        if g:netrw_mousemaps == 1
+          nmap             NetrwLeftmouse
+          nno                 NetrwLeftmouse    :exec "norm! \leftmouse>"call NetrwLeftmouse(0)
+          nmap           NetrwCLeftmouse
+          nno                 NetrwCLeftmouse   :exec "norm! \leftmouse>"call NetrwCLeftmouse(0)
+          nmap           NetrwSLeftmouse
+          nno                 NetrwSLeftmouse   :exec "norm! \leftmouse>"call NetrwSLeftmouse(0)
+          nmap            NetrwSLeftdrag
+          nno                 NetrwSLeftdrag    :exec "norm! \leftmouse>"call NetrwSLeftdrag(0)
+          nmap                   NetrwMiddlemouse
+          nno                            NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(0)
+          nmap  <2-leftmouse>         Netrw2Leftmouse
+          nmap                Netrw2Leftmouse   -
+          imap             ILeftmouse
+          imap           IMiddlemouse
+          imap           ISLeftmouse
+          exe 'nnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+          exe 'vnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+        endif
+        exe 'nnoremap           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+        exe 'nnoremap    d           :call NetrwMakeDir("'.mapsafeusermach.'")'
+        exe 'nnoremap    D           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+        exe 'nnoremap    R           :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
+        exe 'vnoremap           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+        exe 'vnoremap    D           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+        exe 'vnoremap    R           :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
+        nnoremap                         :he netrw-quickhelp
+
+        " support user-specified maps
+        call netrw#UserMaps(0)
+      endif " }}}3
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwCommands: set up commands                              {{{2
+    "  If -buffer, the command is only available from within netrw buffers
+    "  Otherwise, the command is available from any window, so long as netrw
+    "  has been used at least once in the session.
+    fun! s:NetrwCommands(islocal)
+      "  call Dfunc("s:NetrwCommands(islocal=".a:islocal.")")
+
+      com! -nargs=* -complete=file -bang    NetrwMB call s:NetrwBookmark(0,)
+      com! -nargs=*                         NetrwC  call s:NetrwSetChgwin()
+      com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"win#".winnr()." not a former netrw window",79)|endif
+      if a:islocal
+        com! -buffer -nargs=+ -complete=file MF      call s:NetrwMarkFiles(1,)
+      else
+        com! -buffer -nargs=+ -complete=file MF      call s:NetrwMarkFiles(0,)
       endif
-     endif
-
-     call remote_send(srvrname,":tabn ".tabnum."\")
-     call remote_send(srvrname,":".winnum."wincmd w\")
-     call remote_send(srvrname,":e ".fnameescape(s:NetrwFile(a:fname))."\")
-    else
-
-     if serverlist() !~ '\<'.g:netrw_servername.'\>'
-
-      if !ctrlr
-       if exists("g:netrw_browse_split")
-        unlet g:netrw_browse_split
-       endif
-       let g:netrw_browse_split= 0
-       call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
-       return
-
+      com! -buffer -nargs=? -complete=file  MT      call s:NetrwMarkTarget()
+
+      "  call Dret("s:NetrwCommands")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFiles: apply s:NetrwMarkFile() to named file(s) {{{2
+    "                   glob()ing only works with local files
+    fun! s:NetrwMarkFiles(islocal,...)
+      "  call Dfunc("s:NetrwMarkFiles(islocal=".a:islocal."...) a:0=".a:0)
+      let curdir = s:NetrwGetCurdir(a:islocal)
+      let i      = 1
+      while i <= a:0
+        if a:islocal
+          if v:version > 704 || (v:version == 704 && has("patch656"))
+            let mffiles= glob(a:{i},0,1,1)
+          else
+            let mffiles= glob(a:{i},0,1)
+          endif
+        else
+          let mffiles= [a:{i}]
+        endif
+        "   call Decho("mffiles".string(mffiles),'~'.expand(""))
+        for mffile in mffiles
+          "    call Decho("mffile<".mffile.">",'~'.expand(""))
+          call s:NetrwMarkFile(a:islocal,mffile)
+        endfor
+        let i= i + 1
+      endwhile
+      "  call Dret("s:NetrwMarkFiles")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkTarget: implements :MT (mark target) {{{2
+    fun! s:NetrwMarkTarget(...)
+      if a:0 == 0 || (a:0 == 1 && a:1 == "")
+        let curdir = s:NetrwGetCurdir(1)
+        let tgt    = b:netrw_curdir
       else
-       if has("win32") && executable("start")
-        " start up remote netrw server under windows
-        call system("start gvim --servername ".g:netrw_servername)
-       else
-        " start up remote netrw server under linux
-        call system("gvim --servername ".g:netrw_servername)
-       endif
+        let curdir = s:NetrwGetCurdir((a:1 =~ '^\a\{3,}://')? 0 : 1)
+        let tgt    = a:1
       endif
-     endif
-
-     while 1
-      try
-       call remote_send(g:netrw_servername,":e ".fnameescape(s:NetrwFile(a:fname))."\")
-       break
-      catch /^Vim\%((\a\+)\)\=:E241/
-       sleep 200m
-      endtry
-     endwhile
-
-     if exists("g:netrw_browse_split")
-      if type(g:netrw_browse_split) != 3
-        let s:netrw_browse_split_{winnr()}= g:netrw_browse_split
-       endif
-      unlet g:netrw_browse_split
-     endif
-     let g:netrw_browse_split= [g:netrw_servername,1,1]
-    endif
-
-   else
-    call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use ",98)
-   endif
-
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwSLeftmouse: marks the file under the cursor.  May be dragged to select additional files {{{2
-fun! s:NetrwSLeftmouse(islocal)
-  if &ft != "netrw"
-   return
-  endif
-"  call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")")
-
-  let s:ngw= s:NetrwGetWord()
-  call s:NetrwMarkFile(a:islocal,s:ngw)
-
-"  call Dret("s:NetrwSLeftmouse")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwSLeftdrag: invoked via a shift-leftmouse and dragging {{{2
-"                   Used to mark multiple files.
-fun! s:NetrwSLeftdrag(islocal)
-"  call Dfunc("s:NetrwSLeftdrag(islocal=".a:islocal.")")
-  if !exists("s:netrwdrag")
-   let s:netrwdrag = winnr()
-   if a:islocal
-    nno   :call NetrwSLeftrelease(1)
-   else
-    nno   :call NetrwSLeftrelease(0)
-   endif
-  endif
-  let ngw = s:NetrwGetWord()
-  if !exists("s:ngw") || s:ngw != ngw
-   call s:NetrwMarkFile(a:islocal,ngw)
-  endif
-  let s:ngw= ngw
-"  call Dret("s:NetrwSLeftdrag : s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwSLeftrelease: terminates shift-leftmouse dragging {{{2
-fun! s:NetrwSLeftrelease(islocal)
-"  call Dfunc("s:NetrwSLeftrelease(islocal=".a:islocal.") s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
-  if exists("s:netrwdrag")
-   nunmap 
-   let ngw = s:NetrwGetWord()
-   if !exists("s:ngw") || s:ngw != ngw
-    call s:NetrwMarkFile(a:islocal,ngw)
-   endif
-   if exists("s:ngw")
-    unlet s:ngw
-   endif
-   unlet s:netrwdrag
-  endif
-"  call Dret("s:NetrwSLeftrelease")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwListHide: uses [range]g~...~d to delete files that match       {{{2
-"                  comma-separated patterns given in g:netrw_list_hide
-fun! s:NetrwListHide()
-"  call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
-"  call Decho("initial: ".string(getline(w:netrw_bannercnt,'$')))
-  let ykeep= @@
-
-  " find a character not in the "hide" string to use as a separator for :g and :v commands
-  " How-it-works: take the hiding command, convert it into a range.
-  " Duplicate characters don't matter.
-  " Remove all such characters from the '/~@#...890' string.
-  " Use the first character left as a separator character.
-"  call Decho("find a character not in the hide string to use as a separator",'~'.expand(""))
-  let listhide= g:netrw_list_hide
-  let sep     = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1)
-"  call Decho("sep<".sep.">  (sep not in hide string)",'~'.expand(""))
-
-  while listhide != ""
-   if listhide =~ ','
-    let hide     = substitute(listhide,',.*$','','e')
-    let listhide = substitute(listhide,'^.\{-},\(.*\)$','\1','e')
-   else
-    let hide     = listhide
-    let listhide = ""
-   endif
-"   call Decho("..extracted pattern from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand(""))
-   if g:netrw_sort_by =~ '^[ts]'
-    if hide =~ '^\^'
-"     call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand(""))
-     let hide= substitute(hide,'^\^','^\(\\d\\+/\)','')
-    elseif hide =~ '^\\(\^'
-     let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','')
-    endif
-"    call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand(""))
-   endif
-
-   " Prune the list by hiding any files which match
-"   call Decho("..prune the list by hiding any files which ".((g:netrw_hide == 1)? "" : "don't")."match hide<".hide.">")
-   if g:netrw_hide == 1
-"    call Decho("..hiding<".hide.">",'~'.expand(""))
-    exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d'
-   elseif g:netrw_hide == 2
-"    call Decho("..showing<".hide.">",'~'.expand(""))
-    exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @'
-   endif
-"   call Decho("..result: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
-  endwhile
-
-  if g:netrw_hide == 2
-   exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d'
-"   call Decho("..v KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
-   exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e'
-"   call Decho("..g KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
-  endif
-
-  " remove any blank lines that have somehow remained.
-  " This seems to happen under Windows.
-  exe 'sil! NetrwKeepj 1,$g@^\s*$@d'
-
-  let @@= ykeep
-"  call Dret("s:NetrwListHide")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2
-"                 implements the "d" mapping.
-fun! s:NetrwMakeDir(usrhost)
-
-  let ykeep= @@
-  " get name of new directory from user.  A bare  will skip.
-  " if its currently a directory, also request will be skipped, but with
-  " a message.
-  call inputsave()
-  let newdirname= input("Please give directory name: ")
-  call inputrestore()
-
-  if newdirname == ""
-   let @@= ykeep
-   return
-  endif
-
-  if a:usrhost == ""
-
-   " Local mkdir:
-   " sanity checks
-   let fullnewdir= b:netrw_curdir.'/'.newdirname
-   if isdirectory(s:NetrwFile(fullnewdir))
-    if !exists("g:netrw_quiet")
-     NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24)
-    endif
-    let @@= ykeep
-    return
-   endif
-   if s:FileReadable(fullnewdir)
-    if !exists("g:netrw_quiet")
-     NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25)
-    endif
-    let @@= ykeep
-    return
-   endif
-
-   " requested new local directory is neither a pre-existing file or
-   " directory, so make it!
-   if exists("*mkdir")
-    if has("unix")
-     call mkdir(fullnewdir,"p",xor(0777, system("umask")))
-    else
-     call mkdir(fullnewdir,"p")
-    endif
-   else
-    let netrw_origdir= s:NetrwGetcwd(1)
-    if s:NetrwLcd(b:netrw_curdir)
-     return
-    endif
-    call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(newdirname,1))
-    if v:shell_error != 0
-     let @@= ykeep
-     call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
-     return
-    endif
-    if !g:netrw_keepdir
-     if s:NetrwLcd(netrw_origdir)
-      return
-     endif
-    endif
-   endif
-
-   if v:shell_error == 0
-    " refresh listing
-    let svpos= winsaveview()
-    call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
-    call winrestview(svpos)
-   elseif !exists("g:netrw_quiet")
-    call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",26)
-   endif
-
-  elseif !exists("b:netrw_method") || b:netrw_method == 4
-   " Remote mkdir:  using ssh
-   let mkdircmd  = s:MakeSshCmd(g:netrw_mkdir_cmd)
-   let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname
-   call s:NetrwExe("sil! !".mkdircmd." ".s:ShellEscape(newdirname,1))
-   if v:shell_error == 0
-    " refresh listing
-    let svpos= winsaveview()
-    NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
-    NetrwKeepj call winrestview(svpos)
-   elseif !exists("g:netrw_quiet")
-    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27)
-   endif
-
-  elseif b:netrw_method == 2
-   " Remote mkdir:  using ftp+.netrc
-   let svpos= winsaveview()
-   if exists("b:netrw_fname")
-    let remotepath= b:netrw_fname
-   else
-    let remotepath= ""
-   endif
-   call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
-   NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
-   NetrwKeepj call winrestview(svpos)
-
-  elseif b:netrw_method == 3
-   " Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc)
-   let svpos= winsaveview()
-   if exists("b:netrw_fname")
-    let remotepath= b:netrw_fname
-   else
-    let remotepath= ""
-   endif
-   call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
-   NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
-   NetrwKeepj call winrestview(svpos)
-  endif
-
-  let @@= ykeep
-endfun
-
-" ---------------------------------------------------------------------
-" s:TreeSqueezeDir: allows a shift-cr (gvim only) to squeeze the current tree-listing directory {{{2
-fun! s:TreeSqueezeDir(islocal)
-  if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-   " its a tree-listing style
-   let curdepth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e')
-   let stopline = (exists("w:netrw_bannercnt")? (w:netrw_bannercnt + 1) : 1)
-   let depth    = strchars(substitute(curdepth,' ','','g'))
-   let srch     = -1
-   if depth >= 2
-    NetrwKeepj norm! 0
-    let curdepthm1= substitute(curdepth,'^'.s:treedepthstring,'','')
-    let srch      = search('^'.curdepthm1.'\%('.s:treedepthstring.'\)\@!','bW',stopline)
-   elseif depth == 1
-    NetrwKeepj norm! 0
-    let treedepthchr= substitute(s:treedepthstring,' ','','')
-    let srch        = search('^[^'.treedepthchr.']','bW',stopline)
-   endif
-   if srch > 0
-    call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,s:NetrwGetWord(),1))
-    exe srch
-   endif
-  endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMaps: {{{2
-fun! s:NetrwMaps(islocal)
-
-  " mouse  maps: {{{3
-  if g:netrw_mousemaps && g:netrw_retmap
-"   call Decho("set up Rexplore 2-leftmouse",'~'.expand(""))
-   if !hasmapto("NetrwReturn")
-    if maparg("<2-leftmouse>","n") == "" || maparg("<2-leftmouse>","n") =~ '^-$'
-     nmap   <2-leftmouse>	NetrwReturn
-    elseif maparg("","n") == ""
-     nmap   	NetrwReturn
-    endif
-   endif
-   nno  NetrwReturn	:Rexplore
-  endif
-
-  " generate default  maps {{{3
-  if !hasmapto('NetrwHide')              |nmap    a	NetrwHide_a|endif
-  if !hasmapto('NetrwBrowseUpDir')       |nmap    -	NetrwBrowseUpDir|endif
-  if !hasmapto('NetrwOpenFile')          |nmap    %	NetrwOpenFile|endif
-  if !hasmapto('NetrwBadd_cb')           |nmap    cb	NetrwBadd_cb|endif
-  if !hasmapto('NetrwBadd_cB')           |nmap    cB	NetrwBadd_cB|endif
-  if !hasmapto('NetrwLcd')               |nmap    cd	NetrwLcd|endif
-  if !hasmapto('NetrwSetChgwin')         |nmap    C	NetrwSetChgwin|endif
-  if !hasmapto('NetrwRefresh')           |nmap    	NetrwRefresh|endif
-  if !hasmapto('NetrwLocalBrowseCheck')  |nmap    	NetrwLocalBrowseCheck|endif
-  if !hasmapto('NetrwServerEdit')        |nmap    	NetrwServerEdit|endif
-  if !hasmapto('NetrwMakeDir')           |nmap    d	NetrwMakeDir|endif
-  if !hasmapto('NetrwBookHistHandler_gb')|nmap    gb	NetrwBookHistHandler_gb|endif
-
-  if a:islocal
-   " local normal-mode maps {{{3
-   nnoremap   NetrwHide_a			:call NetrwHide(1)
-   nnoremap   NetrwBrowseUpDir		:call NetrwBrowseUpDir(1)
-   nnoremap   NetrwOpenFile		:call NetrwOpenFile(1)
-   nnoremap   NetrwBadd_cb		:call NetrwBadd(1,0)
-   nnoremap   NetrwBadd_cB		:call NetrwBadd(1,1)
-   nnoremap   NetrwLcd			:call NetrwLcd(b:netrw_curdir)
-   nnoremap   NetrwSetChgwin		:call NetrwSetChgwin()
-   nnoremap   NetrwLocalBrowseCheck	:call netrw#LocalBrowseCheck(NetrwBrowseChgDir(1,NetrwGetWord(),1))
-   nnoremap   NetrwServerEdit		:call NetrwServerEdit(3,NetrwGetWord())
-   nnoremap   NetrwMakeDir		:call NetrwMakeDir("")
-   nnoremap   NetrwBookHistHandler_gb	:call NetrwBookHistHandler(1,b:netrw_curdir)
-" ---------------------------------------------------------------------
-   nnoremap    gd	:call NetrwForceChgDir(1,NetrwGetWord())
-   nnoremap    gf	:call NetrwForceFile(1,NetrwGetWord())
-   nnoremap    gh	:call NetrwHidden(1)
-   nnoremap    gn	:call netrw#SetTreetop(0,NetrwGetWord())
-   nnoremap    gp	:call NetrwChgPerm(1,b:netrw_curdir)
-   nnoremap    I	:call NetrwBannerCtrl(1)
-   nnoremap    i	:call NetrwListStyle(1)
-   nnoremap    ma	:call NetrwMarkFileArgList(1,0)
-   nnoremap    mA	:call NetrwMarkFileArgList(1,1)
-   nnoremap    mb	:call NetrwBookHistHandler(0,b:netrw_curdir)
-   nnoremap    mB	:call NetrwBookHistHandler(6,b:netrw_curdir)
-   nnoremap    mc	:call NetrwMarkFileCopy(1)
-   nnoremap    md	:call NetrwMarkFileDiff(1)
-   nnoremap    me	:call NetrwMarkFileEdit(1)
-   nnoremap    mf	:call NetrwMarkFile(1,NetrwGetWord())
-   nnoremap    mF	:call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
-   nnoremap    mg	:call NetrwMarkFileGrep(1)
-   nnoremap    mh	:call NetrwMarkHideSfx(1)
-   nnoremap    mm	:call NetrwMarkFileMove(1)
-   "nnoremap    mp	:call NetrwMarkFilePrint(1)
-   nnoremap    mr	:call NetrwMarkFileRegexp(1)
-   nnoremap    ms	:call NetrwMarkFileSource(1)
-   nnoremap    mT	:call NetrwMarkFileTag(1)
-   nnoremap    mt	:call NetrwMarkFileTgt(1)
-   nnoremap    mu	:call NetrwUnMarkFile(1)
-   nnoremap    mv	:call NetrwMarkFileVimCmd(1)
-   nnoremap    mx	:call NetrwMarkFileExe(1,0)
-   nnoremap    mX	:call NetrwMarkFileExe(1,1)
-   nnoremap    mz	:call NetrwMarkFileCompress(1)
-   nnoremap    O	:call NetrwObtain(1)
-   nnoremap    o	:call NetrwSplit(3)
-   nnoremap    p	:call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
-   nnoremap    P	:call NetrwPrevWinOpen(1)
-   nnoremap    qb	:call NetrwBookHistHandler(2,b:netrw_curdir)
-   nnoremap    qf	:call NetrwFileInfo(1,NetrwGetWord())
-   nnoremap    qF	:call NetrwMarkFileQFEL(1,getqflist())
-   nnoremap    qL	:call NetrwMarkFileQFEL(1,getloclist(v:count))
-   nnoremap    s	:call NetrwSortStyle(1)
-   nnoremap    S	:call NetSortSequence(1)
-   nnoremap    Tb	:call NetrwSetTgt(1,'b',v:count1)
-   nnoremap    t	:call NetrwSplit(4)
-   nnoremap    Th	:call NetrwSetTgt(1,'h',v:count)
-   nnoremap    u	:call NetrwBookHistHandler(4,expand("%"))
-   nnoremap    U	:call NetrwBookHistHandler(5,expand("%"))
-   nnoremap    v	:call NetrwSplit(5)
-   nnoremap    x	:call netrw#BrowseX(NetrwBrowseChgDir(1,NetrwGetWord(),1,0),0)"
-   nnoremap    X	:call NetrwLocalExecute(expand(""))"
-
-   nnoremap    r	:let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwRefresh(1,NetrwBrowseChgDir(1,'./',0))
-   if !hasmapto('NetrwHideEdit')
-    nmap    NetrwHideEdit
-   endif
-   nnoremap   NetrwHideEdit		:call NetrwHideEdit(1)
-   if !hasmapto('NetrwRefresh')
-    nmap    NetrwRefresh
-   endif
-   nnoremap   NetrwRefresh		:call NetrwRefresh(1,NetrwBrowseChgDir(1,(exists("w:netrw_liststyle") && exists("w:netrw_treetop") && w:netrw_liststyle == 3)? w:netrw_treetop : './',0))
-   if s:didstarstar || !mapcheck("","n")
-    nnoremap   	:Nexplore
-   endif
-   if s:didstarstar || !mapcheck("","n")
-    nnoremap   	:Pexplore
-   endif
-   if !hasmapto('NetrwTreeSqueeze')
-    nmap    			NetrwTreeSqueeze
-   endif
-   nnoremap   NetrwTreeSqueeze		:call TreeSqueezeDir(1)
-   let mapsafecurdir = escape(b:netrw_curdir, s:netrw_map_escape)
-   if g:netrw_mousemaps == 1
-    nmap 			   		NetrwLeftmouse
-    nmap 					NetrwCLeftmouse
-    nmap 					NetrwMiddlemouse
-    nmap 					NetrwSLeftmouse
-    nmap 					NetrwSLeftdrag
-    nmap 			<2-leftmouse>		Netrw2Leftmouse
-    imap 					ILeftmouse
-    imap 					IMiddlemouse
-    nno   		NetrwLeftmouse	:exec "norm! \leftmouse>"call NetrwLeftmouse(1)
-    nno   		NetrwCLeftmouse	:exec "norm! \leftmouse>"call NetrwCLeftmouse(1)
-    nno   		NetrwMiddlemouse	:exec "norm! \leftmouse>"call NetrwPrevWinOpen(1)
-    nno   		NetrwSLeftmouse 	:exec "norm! \leftmouse>"call NetrwSLeftmouse(1)
-    nno   		NetrwSLeftdrag	:exec "norm! \leftmouse>"call NetrwSLeftdrag(1)
-    nmap  		Netrw2Leftmouse	-
-    exe 'nnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
-    exe 'vnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
-   endif
-   exe 'nnoremap    	:call NetrwLocalRm("'.mapsafecurdir.'")'
-   exe 'nnoremap    D		:call NetrwLocalRm("'.mapsafecurdir.'")'
-   exe 'nnoremap    R		:call NetrwLocalRename("'.mapsafecurdir.'")'
-   exe 'nnoremap    d		:call NetrwMakeDir("")'
-   exe 'vnoremap    	:call NetrwLocalRm("'.mapsafecurdir.'")'
-   exe 'vnoremap    D		:call NetrwLocalRm("'.mapsafecurdir.'")'
-   exe 'vnoremap    R		:call NetrwLocalRename("'.mapsafecurdir.'")'
-   nnoremap  			:he netrw-quickhelp
-
-   " support user-specified maps
-   call netrw#UserMaps(1)
-
-  else
-   " remote normal-mode maps {{{3
-   call s:RemotePathAnalysis(b:netrw_curdir)
-   nnoremap   NetrwHide_a			:call NetrwHide(0)
-   nnoremap   NetrwBrowseUpDir		:call NetrwBrowseUpDir(0)
-   nnoremap   NetrwOpenFile		:call NetrwOpenFile(0)
-   nnoremap   NetrwBadd_cb		:call NetrwBadd(0,0)
-   nnoremap   NetrwBadd_cB		:call NetrwBadd(0,1)
-   nnoremap   NetrwLcd			:call NetrwLcd(b:netrw_curdir)
-   nnoremap   NetrwSetChgwin		:call NetrwSetChgwin()
-   nnoremap   NetrwRefresh		:call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
-   nnoremap   NetrwLocalBrowseCheck	:call NetrwBrowse(0,NetrwBrowseChgDir(0,NetrwGetWord(),1))
-   nnoremap   NetrwServerEdit		:call NetrwServerEdit(2,NetrwGetWord())
-   nnoremap   NetrwBookHistHandler_gb	:call NetrwBookHistHandler(1,b:netrw_curdir)
-" ---------------------------------------------------------------------
-   nnoremap    gd	:call NetrwForceChgDir(0,NetrwGetWord())
-   nnoremap    gf	:call NetrwForceFile(0,NetrwGetWord())
-   nnoremap    gh	:call NetrwHidden(0)
-   nnoremap    gp	:call NetrwChgPerm(0,b:netrw_curdir)
-   nnoremap    I	:call NetrwBannerCtrl(1)
-   nnoremap    i	:call NetrwListStyle(0)
-   nnoremap    ma	:call NetrwMarkFileArgList(0,0)
-   nnoremap    mA	:call NetrwMarkFileArgList(0,1)
-   nnoremap    mb	:call NetrwBookHistHandler(0,b:netrw_curdir)
-   nnoremap    mB	:call NetrwBookHistHandler(6,b:netrw_curdir)
-   nnoremap    mc	:call NetrwMarkFileCopy(0)
-   nnoremap    md	:call NetrwMarkFileDiff(0)
-   nnoremap    me	:call NetrwMarkFileEdit(0)
-   nnoremap    mf	:call NetrwMarkFile(0,NetrwGetWord())
-   nnoremap    mF	:call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
-   nnoremap    mg	:call NetrwMarkFileGrep(0)
-   nnoremap    mh	:call NetrwMarkHideSfx(0)
-   nnoremap    mm	:call NetrwMarkFileMove(0)
-   "nnoremap    mp	:call NetrwMarkFilePrint(0)
-   nnoremap    mr	:call NetrwMarkFileRegexp(0)
-   nnoremap    ms	:call NetrwMarkFileSource(0)
-   nnoremap    mT	:call NetrwMarkFileTag(0)
-   nnoremap    mt	:call NetrwMarkFileTgt(0)
-   nnoremap    mu	:call NetrwUnMarkFile(0)
-   nnoremap    mv	:call NetrwMarkFileVimCmd(0)
-   nnoremap    mx	:call NetrwMarkFileExe(0,0)
-   nnoremap    mX	:call NetrwMarkFileExe(0,1)
-   nnoremap    mz	:call NetrwMarkFileCompress(0)
-   nnoremap    O	:call NetrwObtain(0)
-   nnoremap    o	:call NetrwSplit(0)
-   nnoremap    p	:call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
-   nnoremap    P	:call NetrwPrevWinOpen(0)
-   nnoremap    qb	:call NetrwBookHistHandler(2,b:netrw_curdir)
-   nnoremap    qf	:call NetrwFileInfo(0,NetrwGetWord())
-   nnoremap    qF	:call NetrwMarkFileQFEL(0,getqflist())
-   nnoremap    qL	:call NetrwMarkFileQFEL(0,getloclist(v:count))
-   nnoremap    r	:let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwBrowse(0,NetrwBrowseChgDir(0,'./',0))
-   nnoremap    s	:call NetrwSortStyle(0)
-   nnoremap    S	:call NetSortSequence(0)
-   nnoremap    Tb	:call NetrwSetTgt(0,'b',v:count1)
-   nnoremap    t	:call NetrwSplit(1)
-   nnoremap    Th	:call NetrwSetTgt(0,'h',v:count)
-   nnoremap    u	:call NetrwBookHistHandler(4,b:netrw_curdir)
-   nnoremap    U	:call NetrwBookHistHandler(5,b:netrw_curdir)
-   nnoremap    v	:call NetrwSplit(2)
-   nnoremap    x	:call netrw#BrowseX(NetrwBrowseChgDir(0,NetrwGetWord(),1),1)
-   nmap                gx	x
-   if !hasmapto('NetrwHideEdit')
-    nmap   NetrwHideEdit
-   endif
-   nnoremap   NetrwHideEdit	:call NetrwHideEdit(0)
-   if !hasmapto('NetrwRefresh')
-    nmap   NetrwRefresh
-   endif
-   if !hasmapto('NetrwTreeSqueeze')
-    nmap    	NetrwTreeSqueeze
-   endif
-   nnoremap   NetrwTreeSqueeze	:call TreeSqueezeDir(0)
-
-   let mapsafepath     = escape(s:path, s:netrw_map_escape)
-   let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape)
-
-   nnoremap   NetrwRefresh	:call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
-   if g:netrw_mousemaps == 1
-    nmap  		NetrwLeftmouse
-    nno   		NetrwLeftmouse	:exec "norm! \leftmouse>"call NetrwLeftmouse(0)
-    nmap  		NetrwCLeftmouse
-    nno   		NetrwCLeftmouse	:exec "norm! \leftmouse>"call NetrwCLeftmouse(0)
-    nmap  		NetrwSLeftmouse
-    nno   		NetrwSLeftmouse 	:exec "norm! \leftmouse>"call NetrwSLeftmouse(0)
-    nmap  		NetrwSLeftdrag
-    nno   		NetrwSLeftdrag	:exec "norm! \leftmouse>"call NetrwSLeftdrag(0)
-    nmap 			NetrwMiddlemouse
-    nno   				NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(0)
-    nmap  <2-leftmouse>		Netrw2Leftmouse
-    nmap  		Netrw2Leftmouse	-
-    imap  		ILeftmouse
-    imap  		IMiddlemouse
-    imap  		ISLeftmouse
-    exe 'nnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-    exe 'vnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-   endif
-   exe 'nnoremap    	:call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-   exe 'nnoremap    d		:call NetrwMakeDir("'.mapsafeusermach.'")'
-   exe 'nnoremap    D		:call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-   exe 'nnoremap    R		:call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
-   exe 'vnoremap    	:call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-   exe 'vnoremap    D		:call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-   exe 'vnoremap    R		:call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
-   nnoremap  			:he netrw-quickhelp
-
-   " support user-specified maps
-   call netrw#UserMaps(0)
-  endif " }}}3
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwCommands: set up commands 				{{{2
-"  If -buffer, the command is only available from within netrw buffers
-"  Otherwise, the command is available from any window, so long as netrw
-"  has been used at least once in the session.
-fun! s:NetrwCommands(islocal)
-"  call Dfunc("s:NetrwCommands(islocal=".a:islocal.")")
-
-  com! -nargs=* -complete=file -bang	NetrwMB	call s:NetrwBookmark(0,)
-  com! -nargs=*			    	NetrwC	call s:NetrwSetChgwin()
-  com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"win#".winnr()." not a former netrw window",79)|endif
-  if a:islocal
-   com! -buffer -nargs=+ -complete=file	MF	call s:NetrwMarkFiles(1,)
-  else
-   com! -buffer -nargs=+ -complete=file	MF	call s:NetrwMarkFiles(0,)
-  endif
-  com! -buffer -nargs=? -complete=file	MT	call s:NetrwMarkTarget()
-
-"  call Dret("s:NetrwCommands")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFiles: apply s:NetrwMarkFile() to named file(s) {{{2
-"                   glob()ing only works with local files
-fun! s:NetrwMarkFiles(islocal,...)
-"  call Dfunc("s:NetrwMarkFiles(islocal=".a:islocal."...) a:0=".a:0)
-  let curdir = s:NetrwGetCurdir(a:islocal)
-  let i      = 1
-  while i <= a:0
-   if a:islocal
-    if v:version > 704 || (v:version == 704 && has("patch656"))
-     let mffiles= glob(a:{i},0,1,1)
-    else
-     let mffiles= glob(a:{i},0,1)
-    endif
-   else
-    let mffiles= [a:{i}]
-   endif
-"   call Decho("mffiles".string(mffiles),'~'.expand(""))
-   for mffile in mffiles
-"    call Decho("mffile<".mffile.">",'~'.expand(""))
-    call s:NetrwMarkFile(a:islocal,mffile)
-   endfor
-   let i= i + 1
-  endwhile
-"  call Dret("s:NetrwMarkFiles")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkTarget: implements :MT (mark target) {{{2
-fun! s:NetrwMarkTarget(...)
-  if a:0 == 0 || (a:0 == 1 && a:1 == "")
-   let curdir = s:NetrwGetCurdir(1)
-   let tgt    = b:netrw_curdir
-  else
-   let curdir = s:NetrwGetCurdir((a:1 =~ '^\a\{3,}://')? 0 : 1)
-   let tgt    = a:1
-  endif
-  let s:netrwmftgt         = tgt
-  let s:netrwmftgt_islocal = tgt !~ '^\a\{3,}://'
-  let curislocal           = b:netrw_curdir !~ '^\a\{3,}://'
-  let svpos                = winsaveview()
-  call s:NetrwRefresh(curislocal,s:NetrwBrowseChgDir(curislocal,'./',0))
-  call winrestview(svpos)
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFile: (invoked by mf) This function is used to both {{{2
-"                  mark and unmark files.  If a markfile list exists,
-"                  then the rename and delete functions will use it instead
-"                  of whatever may happen to be under the cursor at that
-"                  moment.  When the mouse and gui are available,
-"                  shift-leftmouse may also be used to mark files.
-"
-"  Creates two lists
-"    s:netrwmarkfilelist    -- holds complete paths to all marked files
-"    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
-"
-"  Creates a marked file match string
-"    s:netrwmarfilemtch_#   -- used with 2match to display marked files
-"
-"  Creates a buffer version of islocal
-"    b:netrw_islocal
-fun! s:NetrwMarkFile(islocal,fname)
-"  call Dfunc("s:NetrwMarkFile(islocal=".a:islocal." fname<".a:fname.">)")
-"  call Decho("bufnr(%)=".bufnr("%").": ".bufname("%"),'~'.expand(""))
-
-  " sanity check
-  if empty(a:fname)
-"   call Dret("s:NetrwMarkFile : empty fname")
-   return
-  endif
-  let curdir = s:NetrwGetCurdir(a:islocal)
+      let s:netrwmftgt         = tgt
+      let s:netrwmftgt_islocal = tgt !~ '^\a\{3,}://'
+      let curislocal           = b:netrw_curdir !~ '^\a\{3,}://'
+      let svpos                = winsaveview()
+      call s:NetrwRefresh(curislocal,s:NetrwBrowseChgDir(curislocal,'./',0))
+      call winrestview(svpos)
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFile: (invoked by mf) This function is used to both {{{2
+    "                  mark and unmark files.  If a markfile list exists,
+    "                  then the rename and delete functions will use it instead
+    "                  of whatever may happen to be under the cursor at that
+    "                  moment.  When the mouse and gui are available,
+    "                  shift-leftmouse may also be used to mark files.
+    "
+    "  Creates two lists
+    "    s:netrwmarkfilelist    -- holds complete paths to all marked files
+    "    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
+    "
+    "  Creates a marked file match string
+    "    s:netrwmarfilemtch_#   -- used with 2match to display marked files
+    "
+    "  Creates a buffer version of islocal
+    "    b:netrw_islocal
+    fun! s:NetrwMarkFile(islocal,fname)
+      "  call Dfunc("s:NetrwMarkFile(islocal=".a:islocal." fname<".a:fname.">)")
+      "  call Decho("bufnr(%)=".bufnr("%").": ".bufname("%"),'~'.expand(""))
+
+      " sanity check
+      if empty(a:fname)
+        "   call Dret("s:NetrwMarkFile : empty fname")
+        return
+      endif
+      let curdir = s:NetrwGetCurdir(a:islocal)
 
-  let ykeep   = @@
-  let curbufnr= bufnr("%")
-  let leader= '\%(^\|\s\)\zs'
-  if a:fname =~ '\a$'
-   let trailer = '\>[@=|\/\*]\=\ze\%(  \|\t\|$\)'
-  else
-   let trailer = '[@=|\/\*]\=\ze\%(  \|\t\|$\)'
-  endif
-
-  if exists("s:netrwmarkfilelist_".curbufnr)
-   " markfile list pre-exists
-"   call Decho("case s:netrwmarkfilelist_".curbufnr." already exists",'~'.expand(""))
-"   call Decho("starting s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-"   call Decho("starting s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
-   let b:netrw_islocal= a:islocal
-
-   if index(s:netrwmarkfilelist_{curbufnr},a:fname) == -1
-    " append filename to buffer's markfilelist
-"    call Decho("append filename<".a:fname."> to local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-    call add(s:netrwmarkfilelist_{curbufnr},a:fname)
-    let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(a:fname,g:netrw_markfileesc).trailer
-
-   else
-    " remove filename from buffer's markfilelist
-"    call Decho("remove filename<".a:fname."> from local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-    call filter(s:netrwmarkfilelist_{curbufnr},'v:val != a:fname')
-    if s:netrwmarkfilelist_{curbufnr} == []
-     " local markfilelist is empty; remove it entirely
-"     call Decho("markfile list now empty",'~'.expand(""))
-     call s:NetrwUnmarkList(curbufnr,curdir)
-    else
-     " rebuild match list to display markings correctly
-"     call Decho("rebuild s:netrwmarkfilemtch_".curbufnr,'~'.expand(""))
-     let s:netrwmarkfilemtch_{curbufnr}= ""
-     let first                         = 1
-     for fname in s:netrwmarkfilelist_{curbufnr}
-      if first
-       let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.leader.escape(fname,g:netrw_markfileesc).trailer
+      let ykeep   = @@
+      let curbufnr= bufnr("%")
+      let leader= '\%(^\|\s\)\zs'
+      if a:fname =~ '\a$'
+        let trailer = '\>[@=|\/\*]\=\ze\%(  \|\t\|$\)'
       else
-       let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(fname,g:netrw_markfileesc).trailer
+        let trailer = '[@=|\/\*]\=\ze\%(  \|\t\|$\)'
       endif
-      let first= 0
-     endfor
-"     call Decho("ending s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-    endif
-   endif
-
-  else
-   " initialize new markfilelist
-"   call Decho("case: initialize new markfilelist",'~'.expand(""))
-
-"   call Decho("add fname<".a:fname."> to new markfilelist_".curbufnr,'~'.expand(""))
-   let s:netrwmarkfilelist_{curbufnr}= []
-   call add(s:netrwmarkfilelist_{curbufnr},substitute(a:fname,'[|@]$','',''))
-"   call Decho("ending s:netrwmarkfilelist_{curbufnr}<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-
-   " build initial markfile matching pattern
-   if a:fname =~ '/$'
-    let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc)
-   else
-    let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc).trailer
-   endif
-"   call Decho("ending s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
-  endif
-
-  " handle global markfilelist
-  if exists("s:netrwmarkfilelist")
-   let dname= s:ComposePath(b:netrw_curdir,a:fname)
-   if index(s:netrwmarkfilelist,dname) == -1
-    " append new filename to global markfilelist
-    call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
-"    call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
-   else
-    " remove new filename from global markfilelist
-"    call Decho("remove new filename from global s:markfilelist",'~'.expand(""))
-"    call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand(""))
-    call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"')
-"    call Decho("..ending s:netrwmarkfilelist  <".string(s:netrwmarkfilelist).">",'~'.expand(""))
-    if s:netrwmarkfilelist == []
-"     call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand(""))
-     unlet s:netrwmarkfilelist
-    endif
-   endif
-  else
-   " initialize new global-directory markfilelist
-   let s:netrwmarkfilelist= []
-   call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
-"   call Decho("init s:netrwmarkfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
-  endif
-
-  " set up 2match'ing to netrwmarkfilemtch_# list
-  if has("syntax") && exists("g:syntax_on") && g:syntax_on
-   if exists("s:netrwmarkfilemtch_{curbufnr}") && s:netrwmarkfilemtch_{curbufnr} != ""
-" "   call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/",'~'.expand(""))
-    if exists("g:did_drchip_netrwlist_syntax")
-     exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/"
-    endif
-   else
-" "   call Decho("2match none",'~'.expand(""))
-    2match none
-   endif
-  endif
-  let @@= ykeep
-"  call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand(""))
-"  call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist").">  (buf#".curbufnr."list)")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileArgList: ma: move the marked file list to the argument list (tomflist=0) {{{2
-"                         mA: move the argument list to marked file list     (tomflist=1)
-"                            Uses the global marked file list
-fun! s:NetrwMarkFileArgList(islocal,tomflist)
-  let svpos    = winsaveview()
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
-
-  if a:tomflist
-   " mA: move argument list to marked file list
-   while argc()
-    let fname= argv(0)
-    exe "argdel ".fnameescape(fname)
-    call s:NetrwMarkFile(a:islocal,fname)
-   endwhile
-
-  else
-   " ma: move marked file list to argument list
-   if exists("s:netrwmarkfilelist")
-
-    " for every filename in the marked list
-    for fname in s:netrwmarkfilelist
-     exe "argadd ".fnameescape(fname)
-    endfor	" for every file in the marked list
-
-    " unmark list and refresh
-    call s:NetrwUnmarkList(curbufnr,curdir)
-    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-    NetrwKeepj call winrestview(svpos)
-   endif
-  endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileCompress: (invoked by mz) This function is used to {{{2
-"                          compress/decompress files using the programs
-"                          in g:netrw_compress and g:netrw_uncompress,
-"                          using g:netrw_compress_suffix to know which to
-"                          do.  By default:
-"                            g:netrw_compress        = "gzip"
-"                            g:netrw_decompress      = { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf", ".xz" : "unxz"}
-fun! s:NetrwMarkFileCompress(islocal)
-  let svpos    = winsaveview()
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
 
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-   return
-  endif
-
-  if exists("s:netrwmarkfilelist_{curbufnr}") && exists("g:netrw_compress") && exists("g:netrw_decompress")
-
-   " for every filename in the marked list
-   for fname in s:netrwmarkfilelist_{curbufnr}
-    let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','')
-    if exists("g:netrw_decompress['".sfx."']")
-     " fname has a suffix indicating that its compressed; apply associated decompression routine
-     let exe= g:netrw_decompress[sfx]
-     let exe= netrw#WinPath(exe)
-     if a:islocal
-      if g:netrw_keepdir
-       let fname= s:ShellEscape(s:ComposePath(curdir,fname))
-      endif
-      call system(exe." ".fname)
-      if v:shell_error
-       NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50)
-      endif
-     else
-      let fname= s:ShellEscape(b:netrw_curdir.fname,1)
-      NetrwKeepj call s:RemoteSystem(exe." ".fname)
-     endif
-
-    endif
-    unlet sfx
-
-    if exists("exe")
-     unlet exe
-    elseif a:islocal
-     " fname not a compressed file, so compress it
-     call system(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,fname)))
-     if v:shell_error
-      call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_compress<".g:netrw_compress."> to something that works",104)
-     endif
-    else
-     " fname not a compressed file, so compress it
-     NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(fname))
-    endif
-   endfor	" for every file in the marked list
-
-   call s:NetrwUnmarkList(curbufnr,curdir)
-   NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   NetrwKeepj call winrestview(svpos)
-  endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileCopy: (invoked by mc) copy marked files to target {{{2
-"                      If no marked files, then set up directory as the
-"                      target.  Currently does not support copying entire
-"                      directories.  Uses the local-buffer marked file list.
-"                      Returns 1=success  (used by NetrwMarkFileMove())
-"                              0=failure
-fun! s:NetrwMarkFileCopy(islocal,...)
-"  call Dfunc("s:NetrwMarkFileCopy(islocal=".a:islocal.") target<".(exists("s:netrwmftgt")? s:netrwmftgt : '---')."> a:0=".a:0)
-
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
-  if b:netrw_curdir !~ '/$'
-   if !exists("b:netrw_curdir")
-    let b:netrw_curdir= curdir
-   endif
-   let b:netrw_curdir= b:netrw_curdir."/"
-  endif
+      if exists("s:netrwmarkfilelist_".curbufnr)
+        " markfile list pre-exists
+        "   call Decho("case s:netrwmarkfilelist_".curbufnr." already exists",'~'.expand(""))
+        "   call Decho("starting s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+        "   call Decho("starting s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
+        let b:netrw_islocal= a:islocal
+
+        if index(s:netrwmarkfilelist_{curbufnr},a:fname) == -1
+          " append filename to buffer's markfilelist
+          "    call Decho("append filename<".a:fname."> to local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+          call add(s:netrwmarkfilelist_{curbufnr},a:fname)
+          let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(a:fname,g:netrw_markfileesc).trailer
+
+        else
+          " remove filename from buffer's markfilelist
+          "    call Decho("remove filename<".a:fname."> from local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+          call filter(s:netrwmarkfilelist_{curbufnr},'v:val != a:fname')
+          if s:netrwmarkfilelist_{curbufnr} == []
+            " local markfilelist is empty; remove it entirely
+            "     call Decho("markfile list now empty",'~'.expand(""))
+            call s:NetrwUnmarkList(curbufnr,curdir)
+          else
+            " rebuild match list to display markings correctly
+            "     call Decho("rebuild s:netrwmarkfilemtch_".curbufnr,'~'.expand(""))
+            let s:netrwmarkfilemtch_{curbufnr}= ""
+            let first                         = 1
+            for fname in s:netrwmarkfilelist_{curbufnr}
+              if first
+                let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.leader.escape(fname,g:netrw_markfileesc).trailer
+              else
+                let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(fname,g:netrw_markfileesc).trailer
+              endif
+              let first= 0
+            endfor
+            "     call Decho("ending s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+          endif
+        endif
 
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-"   call Dret("s:NetrwMarkFileCopy")
-   return
-  endif
-"  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-
-  if !exists("s:netrwmftgt")
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67)
-"   call Dret("s:NetrwMarkFileCopy 0")
-   return 0
-  endif
-"  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
-
-  if a:islocal &&  s:netrwmftgt_islocal
-   " Copy marked files, local directory to local directory
-"   call Decho("copy from local to local",'~'.expand(""))
-   if !executable(g:netrw_localcopycmd)
-    call netrw#ErrorMsg(s:ERROR,"g:netrw_localcopycmd<".g:netrw_localcopycmd."> not executable on your system, aborting",91)
-"    call Dfunc("s:NetrwMarkFileMove : g:netrw_localcopycmd<".g:netrw_localcopycmd."> n/a!")
-    return
-   endif
-
-   " copy marked files while within the same directory (ie. allow renaming)
-   if s:StripTrailingSlash(simplify(s:netrwmftgt)) == s:StripTrailingSlash(simplify(b:netrw_curdir))
-    if len(s:netrwmarkfilelist_{bufnr('%')}) == 1
-     " only one marked file
-"     call Decho("case: only one marked file",'~'.expand(""))
-     let args    = s:ShellEscape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0])
-     let oldname = s:netrwmarkfilelist_{bufnr('%')}[0]
-    elseif a:0 == 1
-"     call Decho("case: handling one input argument",'~'.expand(""))
-     " this happens when the next case was used to recursively call s:NetrwMarkFileCopy()
-     let args    = s:ShellEscape(b:netrw_curdir.a:1)
-     let oldname = a:1
-    else
-     " copy multiple marked files inside the same directory
-"     call Decho("case: handling a multiple marked files",'~'.expand(""))
-     let s:recursive= 1
-     for oldname in s:netrwmarkfilelist_{bufnr("%")}
-      let ret= s:NetrwMarkFileCopy(a:islocal,oldname)
-      if ret == 0
-       break
-      endif
-     endfor
-     unlet s:recursive
-     call s:NetrwUnmarkList(curbufnr,curdir)
-"     call Dret("s:NetrwMarkFileCopy ".ret)
-     return ret
-    endif
-
-    call inputsave()
-    let newname= input("Copy ".oldname." to : ",oldname,"file")
-    call inputrestore()
-    if newname == ""
-"     call Dret("s:NetrwMarkFileCopy 0")
-     return 0
-    endif
-    let args= s:ShellEscape(oldname)
-    let tgt = s:ShellEscape(s:netrwmftgt.'/'.newname)
-   else
-    let args= join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"s:ShellEscape(b:netrw_curdir.\"/\".v:val)"))
-    let tgt = s:ShellEscape(s:netrwmftgt)
-   endif
-   if !g:netrw_cygwin && has("win32")
-    let args= substitute(args,'/','\\','g')
-    let tgt = substitute(tgt, '/','\\','g')
-   endif
-   if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif
-   if tgt  =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif
-   if args =~ '//'|let args= substitute(args,'//','/','g')|endif
-   if tgt  =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif
-"   call Decho("args   <".args.">",'~'.expand(""))
-"   call Decho("tgt    <".tgt.">",'~'.expand(""))
-   if isdirectory(s:NetrwFile(args))
-"    call Decho("args<".args."> is a directory",'~'.expand(""))
-    let copycmd= g:netrw_localcopydircmd
-"    call Decho("using copydircmd<".copycmd.">",'~'.expand(""))
-    if !g:netrw_cygwin && has("win32")
-     " window's xcopy doesn't copy a directory to a target properly.  Instead, it copies a directory's
-     " contents to a target.  One must append the source directory name to the target to get xcopy to
-     " do the right thing.
-     let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','')
-"     call Decho("modified tgt for xcopy",'~'.expand(""))
-    endif
-   else
-    let copycmd= g:netrw_localcopycmd
-   endif
-   if g:netrw_localcopycmd =~ '\s'
-    let copycmd     = substitute(copycmd,'\s.*$','','')
-    let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','')
-    let copycmd     = netrw#WinPath(copycmd).copycmdargs
-   else
-    let copycmd = netrw#WinPath(copycmd)
-   endif
-"   call Decho("args   <".args.">",'~'.expand(""))
-"   call Decho("tgt    <".tgt.">",'~'.expand(""))
-"   call Decho("copycmd<".copycmd.">",'~'.expand(""))
-"   call Decho("system(".copycmd." '".args."' '".tgt."')",'~'.expand(""))
-   call system(copycmd.g:netrw_localcopycmdopt." '".args."' '".tgt."'")
-   if v:shell_error != 0
-    if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir
-     call netrw#ErrorMsg(s:ERROR,"copy failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",101)
-    else
-     call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80)
-    endif
-"    call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".s:ShellEscape(s:netrwmftgt))
-    return 0
-   endif
-
-  elseif  a:islocal && !s:netrwmftgt_islocal
-   " Copy marked files, local directory to remote directory
-"   call Decho("copy from local to remote",'~'.expand(""))
-   NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
-
-  elseif !a:islocal &&  s:netrwmftgt_islocal
-   " Copy marked files, remote directory to local directory
-"   call Decho("copy from remote to local",'~'.expand(""))
-   NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
-
-  elseif !a:islocal && !s:netrwmftgt_islocal
-   " Copy marked files, remote directory to remote directory
-"   call Decho("copy from remote to remote",'~'.expand(""))
-   let curdir = getcwd()
-   let tmpdir = s:GetTempfile("")
-   if tmpdir !~ '/'
-    let tmpdir= curdir."/".tmpdir
-   endif
-   if exists("*mkdir")
-    call mkdir(tmpdir)
-   else
-    call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(tmpdir,1))
-    if v:shell_error != 0
-     call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
-"     call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.s:ShellEscape(tmpdir,1) )
-     return
-    endif
-   endif
-   if isdirectory(s:NetrwFile(tmpdir))
-    if s:NetrwLcd(tmpdir)
-"     call Dret("s:NetrwMarkFileCopy : lcd failure")
-     return
-    endif
-    NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir)
-    let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")')
-    NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt)
-    if getcwd() == tmpdir
-     for fname in s:netrwmarkfilelist_{bufnr('%')}
-      NetrwKeepj call s:NetrwDelete(fname)
-     endfor
-     if s:NetrwLcd(curdir)
-"      call Dret("s:NetrwMarkFileCopy : lcd failure")
-      return
-     endif
-     if delete(tmpdir,"d")
-      call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103)
-     endif
-    else
-     if s:NetrwLcd(curdir)
-"      call Dret("s:NetrwMarkFileCopy : lcd failure")
-      return
-     endif
-    endif
-   endif
-  endif
+      else
+        " initialize new markfilelist
+        "   call Decho("case: initialize new markfilelist",'~'.expand(""))
+
+        "   call Decho("add fname<".a:fname."> to new markfilelist_".curbufnr,'~'.expand(""))
+        let s:netrwmarkfilelist_{curbufnr}= []
+        call add(s:netrwmarkfilelist_{curbufnr},substitute(a:fname,'[|@]$','',''))
+        "   call Decho("ending s:netrwmarkfilelist_{curbufnr}<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+
+        " build initial markfile matching pattern
+        if a:fname =~ '/$'
+          let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc)
+        else
+          let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc).trailer
+        endif
+        "   call Decho("ending s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
+      endif
 
-  " -------
-  " cleanup
-  " -------
-"  call Decho("cleanup",'~'.expand(""))
-  " remove markings from local buffer
-  call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
-"  call Decho(" g:netrw_fastbrowse  =".g:netrw_fastbrowse,'~'.expand(""))
-"  call Decho(" s:netrwmftgt        =".s:netrwmftgt,'~'.expand(""))
-"  call Decho(" s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand(""))
-"  call Decho(" curdir              =".curdir,'~'.expand(""))
-"  call Decho(" a:islocal           =".a:islocal,'~'.expand(""))
-"  call Decho(" curbufnr            =".curbufnr,'~'.expand(""))
-  if exists("s:recursive")
-"   call Decho(" s:recursive         =".s:recursive,'~'.expand(""))
-  else
-"   call Decho(" s:recursive         =n/a",'~'.expand(""))
-  endif
-  " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium)
-  if g:netrw_fastbrowse <= 1
-   NetrwKeepj call s:LocalBrowseRefresh()
-  else
-   " refresh local and targets for fast browsing
-   if !exists("s:recursive")
-    " remove markings from local buffer
-"    call Decho(" remove markings from local buffer",'~'.expand(""))
-    NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir)
-   endif
-
-   " refresh buffers
-   if s:netrwmftgt_islocal
-"    call Decho(" refresh s:netrwmftgt=".s:netrwmftgt,'~'.expand(""))
-    NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
-   endif
-   if a:islocal && s:netrwmftgt != curdir
-"    call Decho(" refresh curdir=".curdir,'~'.expand(""))
-    NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir)
-   endif
-  endif
-
-"  call Dret("s:NetrwMarkFileCopy 1")
-  return 1
-endfun
+      " handle global markfilelist
+      if exists("s:netrwmarkfilelist")
+        let dname= s:ComposePath(b:netrw_curdir,a:fname)
+        if index(s:netrwmarkfilelist,dname) == -1
+          " append new filename to global markfilelist
+          call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
+          "    call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
+        else
+          " remove new filename from global markfilelist
+          "    call Decho("remove new filename from global s:markfilelist",'~'.expand(""))
+          "    call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand(""))
+          call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"')
+          "    call Decho("..ending s:netrwmarkfilelist  <".string(s:netrwmarkfilelist).">",'~'.expand(""))
+          if s:netrwmarkfilelist == []
+            "     call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand(""))
+            unlet s:netrwmarkfilelist
+          endif
+        endif
+      else
+        " initialize new global-directory markfilelist
+        let s:netrwmarkfilelist= []
+        call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
+        "   call Decho("init s:netrwmarkfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileDiff: (invoked by md) This function is used to {{{2
-"                      invoke vim's diff mode on the marked files.
-"                      Either two or three files can be so handled.
-"                      Uses the global marked file list.
-fun! s:NetrwMarkFileDiff(islocal)
-"  call Dfunc("s:NetrwMarkFileDiff(islocal=".a:islocal.") b:netrw_curdir<".b:netrw_curdir.">")
-  let curbufnr= bufnr("%")
+      " set up 2match'ing to netrwmarkfilemtch_# list
+      if has("syntax") && exists("g:syntax_on") && g:syntax_on
+        if exists("s:netrwmarkfilemtch_{curbufnr}") && s:netrwmarkfilemtch_{curbufnr} != ""
+          " "   call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/",'~'.expand(""))
+          if exists("g:did_drchip_netrwlist_syntax")
+            exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/"
+          endif
+        else
+          " "   call Decho("2match none",'~'.expand(""))
+          2match none
+        endif
+      endif
+      let @@= ykeep
+      "  call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand(""))
+      "  call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist").">  (buf#".curbufnr."list)")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileArgList: ma: move the marked file list to the argument list (tomflist=0) {{{2
+    "                         mA: move the argument list to marked file list     (tomflist=1)
+    "                            Uses the global marked file list
+    fun! s:NetrwMarkFileArgList(islocal,tomflist)
+      let svpos    = winsaveview()
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+
+      if a:tomflist
+        " mA: move argument list to marked file list
+        while argc()
+          let fname= argv(0)
+          exe "argdel ".fnameescape(fname)
+          call s:NetrwMarkFile(a:islocal,fname)
+        endwhile
 
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-"   call Dret("s:NetrwMarkFileDiff")
-   return
-  endif
-  let curdir= s:NetrwGetCurdir(a:islocal)
-"  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-
-  if exists("s:netrwmarkfilelist_{".curbufnr."}")
-   let cnt    = 0
-   for fname in s:netrwmarkfilelist
-    let cnt= cnt + 1
-    if cnt == 1
-"     call Decho("diffthis: fname<".fname.">",'~'.expand(""))
-     exe "NetrwKeepj e ".fnameescape(fname)
-     diffthis
-    elseif cnt == 2 || cnt == 3
-     below vsplit
-"     call Decho("diffthis: ".fname,'~'.expand(""))
-     exe "NetrwKeepj e ".fnameescape(fname)
-     diffthis
-    else
-     break
-    endif
-   endfor
-   call s:NetrwUnmarkList(curbufnr,curdir)
-  endif
+      else
+        " ma: move marked file list to argument list
+        if exists("s:netrwmarkfilelist")
+
+          " for every filename in the marked list
+          for fname in s:netrwmarkfilelist
+            exe "argadd ".fnameescape(fname)
+          endfor      " for every file in the marked list
+
+          " unmark list and refresh
+          call s:NetrwUnmarkList(curbufnr,curdir)
+          NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+          NetrwKeepj call winrestview(svpos)
+        endif
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileCompress: (invoked by mz) This function is used to {{{2
+    "                          compress/decompress files using the programs
+    "                          in g:netrw_compress and g:netrw_uncompress,
+    "                          using g:netrw_compress_suffix to know which to
+    "                          do.  By default:
+    "                            g:netrw_compress        = "gzip"
+    "                            g:netrw_decompress      = { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf", ".xz" : "unxz"}
+    fun! s:NetrwMarkFileCompress(islocal)
+      let svpos    = winsaveview()
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        return
+      endif
 
-"  call Dret("s:NetrwMarkFileDiff")
-endfun
+      if exists("s:netrwmarkfilelist_{curbufnr}") && exists("g:netrw_compress") && exists("g:netrw_decompress")
+
+        " for every filename in the marked list
+        for fname in s:netrwmarkfilelist_{curbufnr}
+          let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','')
+          if exists("g:netrw_decompress['".sfx."']")
+            " fname has a suffix indicating that its compressed; apply associated decompression routine
+            let exe= g:netrw_decompress[sfx]
+            let exe= netrw#WinPath(exe)
+            if a:islocal
+              if g:netrw_keepdir
+                let fname= s:ShellEscape(s:ComposePath(curdir,fname))
+              endif
+              call system(exe." ".fname)
+              if v:shell_error
+                NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50)
+              endif
+            else
+              let fname= s:ShellEscape(b:netrw_curdir.fname,1)
+              NetrwKeepj call s:RemoteSystem(exe." ".fname)
+            endif
+
+          endif
+          unlet sfx
+
+          if exists("exe")
+            unlet exe
+          elseif a:islocal
+            " fname not a compressed file, so compress it
+            call system(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,fname)))
+            if v:shell_error
+              call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_compress<".g:netrw_compress."> to something that works",104)
+            endif
+          else
+            " fname not a compressed file, so compress it
+            NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(fname))
+          endif
+        endfor       " for every file in the marked list
+
+        call s:NetrwUnmarkList(curbufnr,curdir)
+        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        NetrwKeepj call winrestview(svpos)
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileCopy: (invoked by mc) copy marked files to target {{{2
+    "                      If no marked files, then set up directory as the
+    "                      target.  Currently does not support copying entire
+    "                      directories.  Uses the local-buffer marked file list.
+    "                      Returns 1=success  (used by NetrwMarkFileMove())
+    "                              0=failure
+    fun! s:NetrwMarkFileCopy(islocal,...)
+      "  call Dfunc("s:NetrwMarkFileCopy(islocal=".a:islocal.") target<".(exists("s:netrwmftgt")? s:netrwmftgt : '---')."> a:0=".a:0)
+
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+      if b:netrw_curdir !~ '/$'
+        if !exists("b:netrw_curdir")
+          let b:netrw_curdir= curdir
+        endif
+        let b:netrw_curdir= b:netrw_curdir."/"
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileEdit: (invoked by me) put marked files on arg list and start editing them {{{2
-"                       Uses global markfilelist
-fun! s:NetrwMarkFileEdit(islocal)
-"  call Dfunc("s:NetrwMarkFileEdit(islocal=".a:islocal.")")
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        "   call Dret("s:NetrwMarkFileCopy")
+        return
+      endif
+      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
 
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
+      if !exists("s:netrwmftgt")
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67)
+        "   call Dret("s:NetrwMarkFileCopy 0")
+        return 0
+      endif
+      "  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+
+      if a:islocal &&  s:netrwmftgt_islocal
+        " Copy marked files, local directory to local directory
+        "   call Decho("copy from local to local",'~'.expand(""))
+        if !executable(g:netrw_localcopycmd)
+          call netrw#ErrorMsg(s:ERROR,"g:netrw_localcopycmd<".g:netrw_localcopycmd."> not executable on your system, aborting",91)
+          "    call Dfunc("s:NetrwMarkFileMove : g:netrw_localcopycmd<".g:netrw_localcopycmd."> n/a!")
+          return
+        endif
 
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-"   call Dret("s:NetrwMarkFileEdit")
-   return
-  endif
-"  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+        " copy marked files while within the same directory (ie. allow renaming)
+        if s:StripTrailingSlash(simplify(s:netrwmftgt)) == s:StripTrailingSlash(simplify(b:netrw_curdir))
+          if len(s:netrwmarkfilelist_{bufnr('%')}) == 1
+            " only one marked file
+            "     call Decho("case: only one marked file",'~'.expand(""))
+            let args    = s:ShellEscape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0])
+            let oldname = s:netrwmarkfilelist_{bufnr('%')}[0]
+          elseif a:0 == 1
+            "     call Decho("case: handling one input argument",'~'.expand(""))
+            " this happens when the next case was used to recursively call s:NetrwMarkFileCopy()
+            let args    = s:ShellEscape(b:netrw_curdir.a:1)
+            let oldname = a:1
+          else
+            " copy multiple marked files inside the same directory
+            "     call Decho("case: handling a multiple marked files",'~'.expand(""))
+            let s:recursive= 1
+            for oldname in s:netrwmarkfilelist_{bufnr("%")}
+              let ret= s:NetrwMarkFileCopy(a:islocal,oldname)
+              if ret == 0
+                break
+              endif
+            endfor
+            unlet s:recursive
+            call s:NetrwUnmarkList(curbufnr,curdir)
+            "     call Dret("s:NetrwMarkFileCopy ".ret)
+            return ret
+          endif
+
+          call inputsave()
+          let newname= input("Copy ".oldname." to : ",oldname,"file")
+          call inputrestore()
+          if newname == ""
+            "     call Dret("s:NetrwMarkFileCopy 0")
+            return 0
+          endif
+          let args= s:ShellEscape(oldname)
+          let tgt = s:ShellEscape(s:netrwmftgt.'/'.newname)
+        else
+          let args= join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"s:ShellEscape(b:netrw_curdir.\"/\".v:val)"))
+          let tgt = s:ShellEscape(s:netrwmftgt)
+        endif
+        if !g:netrw_cygwin && has("win32")
+          let args= substitute(args,'/','\\','g')
+          let tgt = substitute(tgt, '/','\\','g')
+        endif
+        if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif
+        if tgt  =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif
+        if args =~ '//'|let args= substitute(args,'//','/','g')|endif
+        if tgt  =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif
+        "   call Decho("args   <".args.">",'~'.expand(""))
+        "   call Decho("tgt    <".tgt.">",'~'.expand(""))
+        if isdirectory(s:NetrwFile(args))
+          "    call Decho("args<".args."> is a directory",'~'.expand(""))
+          let copycmd= g:netrw_localcopydircmd
+          "    call Decho("using copydircmd<".copycmd.">",'~'.expand(""))
+          if !g:netrw_cygwin && has("win32")
+            " window's xcopy doesn't copy a directory to a target properly.  Instead, it copies a directory's
+            " contents to a target.  One must append the source directory name to the target to get xcopy to
+            " do the right thing.
+            let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','')
+            "     call Decho("modified tgt for xcopy",'~'.expand(""))
+          endif
+        else
+          let copycmd= g:netrw_localcopycmd
+        endif
+        if g:netrw_localcopycmd =~ '\s'
+          let copycmd     = substitute(copycmd,'\s.*$','','')
+          let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','')
+          let copycmd     = netrw#WinPath(copycmd).copycmdargs
+        else
+          let copycmd = netrw#WinPath(copycmd)
+        endif
+        "   call Decho("args   <".args.">",'~'.expand(""))
+        "   call Decho("tgt    <".tgt.">",'~'.expand(""))
+        "   call Decho("copycmd<".copycmd.">",'~'.expand(""))
+        "   call Decho("system(".copycmd." '".args."' '".tgt."')",'~'.expand(""))
+        call system(copycmd.g:netrw_localcopycmdopt." '".args."' '".tgt."'")
+        if v:shell_error != 0
+          if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir
+            call netrw#ErrorMsg(s:ERROR,"copy failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",101)
+          else
+            call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80)
+          endif
+          "    call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".s:ShellEscape(s:netrwmftgt))
+          return 0
+        endif
 
-  if exists("s:netrwmarkfilelist_{curbufnr}")
-   call s:SetRexDir(a:islocal,curdir)
-   let flist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
-   " unmark markedfile list
-"   call s:NetrwUnmarkList(curbufnr,curdir)
-   call s:NetrwUnmarkAll()
-"   call Decho("exe sil args ".flist,'~'.expand(""))
-   exe "sil args ".flist
-  endif
-  echo "(use :bn, :bp to navigate files; :Rex to return)"
+      elseif  a:islocal && !s:netrwmftgt_islocal
+        " Copy marked files, local directory to remote directory
+        "   call Decho("copy from local to remote",'~'.expand(""))
+        NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
+
+      elseif !a:islocal &&  s:netrwmftgt_islocal
+        " Copy marked files, remote directory to local directory
+        "   call Decho("copy from remote to local",'~'.expand(""))
+        NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
+
+      elseif !a:islocal && !s:netrwmftgt_islocal
+        " Copy marked files, remote directory to remote directory
+        "   call Decho("copy from remote to remote",'~'.expand(""))
+        let curdir = getcwd()
+        let tmpdir = s:GetTempfile("")
+        if tmpdir !~ '/'
+          let tmpdir= curdir."/".tmpdir
+        endif
+        if exists("*mkdir")
+          call mkdir(tmpdir)
+        else
+          call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(tmpdir,1))
+          if v:shell_error != 0
+            call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
+            "     call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.s:ShellEscape(tmpdir,1) )
+            return
+          endif
+        endif
+        if isdirectory(s:NetrwFile(tmpdir))
+          if s:NetrwLcd(tmpdir)
+            "     call Dret("s:NetrwMarkFileCopy : lcd failure")
+            return
+          endif
+          NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir)
+          let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")')
+          NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt)
+          if getcwd() == tmpdir
+            for fname in s:netrwmarkfilelist_{bufnr('%')}
+              NetrwKeepj call s:NetrwDelete(fname)
+            endfor
+            if s:NetrwLcd(curdir)
+              "      call Dret("s:NetrwMarkFileCopy : lcd failure")
+              return
+            endif
+            if delete(tmpdir,"d")
+              call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103)
+            endif
+          else
+            if s:NetrwLcd(curdir)
+              "      call Dret("s:NetrwMarkFileCopy : lcd failure")
+              return
+            endif
+          endif
+        endif
+      endif
 
-"  call Dret("s:NetrwMarkFileEdit")
-endfun
+      " -------
+      " cleanup
+      " -------
+      "  call Decho("cleanup",'~'.expand(""))
+      " remove markings from local buffer
+      call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
+      "  call Decho(" g:netrw_fastbrowse  =".g:netrw_fastbrowse,'~'.expand(""))
+      "  call Decho(" s:netrwmftgt        =".s:netrwmftgt,'~'.expand(""))
+      "  call Decho(" s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand(""))
+      "  call Decho(" curdir              =".curdir,'~'.expand(""))
+      "  call Decho(" a:islocal           =".a:islocal,'~'.expand(""))
+      "  call Decho(" curbufnr            =".curbufnr,'~'.expand(""))
+      if exists("s:recursive")
+        "   call Decho(" s:recursive         =".s:recursive,'~'.expand(""))
+      else
+        "   call Decho(" s:recursive         =n/a",'~'.expand(""))
+      endif
+      " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium)
+      if g:netrw_fastbrowse <= 1
+        NetrwKeepj call s:LocalBrowseRefresh()
+      else
+        " refresh local and targets for fast browsing
+        if !exists("s:recursive")
+          " remove markings from local buffer
+          "    call Decho(" remove markings from local buffer",'~'.expand(""))
+          NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir)
+        endif
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileQFEL: convert a quickfix-error or location list into a marked file list {{{2
-fun! s:NetrwMarkFileQFEL(islocal,qfel)
-"  call Dfunc("s:NetrwMarkFileQFEL(islocal=".a:islocal.",qfel)")
-  call s:NetrwUnmarkAll()
-  let curbufnr= bufnr("%")
-
-  if !empty(a:qfel)
-   for entry in a:qfel
-    let bufnmbr= entry["bufnr"]
-"    call Decho("bufname(".bufnmbr.")<".bufname(bufnmbr)."> line#".entry["lnum"]." text=".entry["text"],'~'.expand(""))
-    if !exists("s:netrwmarkfilelist_{curbufnr}")
-"     call Decho("case: no marked file list",'~'.expand(""))
-     call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
-    elseif index(s:netrwmarkfilelist_{curbufnr},bufname(bufnmbr)) == -1
-     " s:NetrwMarkFile will remove duplicate entries from the marked file list.
-     " So, this test lets two or more hits on the same pattern to be ignored.
-"     call Decho("case: ".bufname(bufnmbr)." not currently in marked file list",'~'.expand(""))
-     call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
-    else
-"     call Decho("case: ".bufname(bufnmbr)." already in marked file list",'~'.expand(""))
-    endif
-   endfor
-   echo "(use me to edit marked files)"
-  else
-   call netrw#ErrorMsg(s:WARNING,"can't convert quickfix error list; its empty!",92)
-  endif
+        " refresh buffers
+        if s:netrwmftgt_islocal
+          "    call Decho(" refresh s:netrwmftgt=".s:netrwmftgt,'~'.expand(""))
+          NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
+        endif
+        if a:islocal && s:netrwmftgt != curdir
+          "    call Decho(" refresh curdir=".curdir,'~'.expand(""))
+          NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir)
+        endif
+      endif
 
-"  call Dret("s:NetrwMarkFileQFEL")
-endfun
+      "  call Dret("s:NetrwMarkFileCopy 1")
+      return 1
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileDiff: (invoked by md) This function is used to {{{2
+    "                      invoke vim's diff mode on the marked files.
+    "                      Either two or three files can be so handled.
+    "                      Uses the global marked file list.
+    fun! s:NetrwMarkFileDiff(islocal)
+      "  call Dfunc("s:NetrwMarkFileDiff(islocal=".a:islocal.") b:netrw_curdir<".b:netrw_curdir.">")
+      let curbufnr= bufnr("%")
+
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        "   call Dret("s:NetrwMarkFileDiff")
+        return
+      endif
+      let curdir= s:NetrwGetCurdir(a:islocal)
+      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+
+      if exists("s:netrwmarkfilelist_{".curbufnr."}")
+        let cnt    = 0
+        for fname in s:netrwmarkfilelist
+          let cnt= cnt + 1
+          if cnt == 1
+            "     call Decho("diffthis: fname<".fname.">",'~'.expand(""))
+            exe "NetrwKeepj e ".fnameescape(fname)
+            diffthis
+          elseif cnt == 2 || cnt == 3
+            below vsplit
+            "     call Decho("diffthis: ".fname,'~'.expand(""))
+            exe "NetrwKeepj e ".fnameescape(fname)
+            diffthis
+          else
+            break
+          endif
+        endfor
+        call s:NetrwUnmarkList(curbufnr,curdir)
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2
-"                     mx enbloc=0: Uses the local marked-file list, applies command to each file individually
-"                     mX enbloc=1: Uses the global marked-file list, applies command to entire list
-fun! s:NetrwMarkFileExe(islocal,enbloc)
-  let svpos    = winsaveview()
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
+      "  call Dret("s:NetrwMarkFileDiff")
+    endfun
 
-  if a:enbloc == 0
-   " individually apply command to files, one at a time
-    " sanity check
-    if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-     NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-     return
-    endif
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileEdit: (invoked by me) put marked files on arg list and start editing them {{{2
+    "                       Uses global markfilelist
+    fun! s:NetrwMarkFileEdit(islocal)
+      "  call Dfunc("s:NetrwMarkFileEdit(islocal=".a:islocal.")")
 
-    if exists("s:netrwmarkfilelist_{curbufnr}")
-     " get the command
-     call inputsave()
-     let cmd= input("Enter command: ","","file")
-     call inputrestore()
-     if cmd == ""
-      return
-     endif
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
 
-     " apply command to marked files, individually.  Substitute: filename -> %
-     " If no %, then append a space and the filename to the command
-     for fname in s:netrwmarkfilelist_{curbufnr}
-      if a:islocal
-       if g:netrw_keepdir
-        let fname= s:ShellEscape(netrw#WinPath(s:ComposePath(curdir,fname)))
-       endif
-      else
-       let fname= s:ShellEscape(netrw#WinPath(b:netrw_curdir.fname))
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        "   call Dret("s:NetrwMarkFileEdit")
+        return
       endif
-      if cmd =~ '%'
-       let xcmd= substitute(cmd,'%',fname,'g')
-      else
-       let xcmd= cmd.' '.fname
+      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+        call s:SetRexDir(a:islocal,curdir)
+        let flist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
+        " unmark markedfile list
+        "   call s:NetrwUnmarkList(curbufnr,curdir)
+        call s:NetrwUnmarkAll()
+        "   call Decho("exe sil args ".flist,'~'.expand(""))
+        exe "sil args ".flist
       endif
-      if a:islocal
-       let ret= system(xcmd)
+      echo "(use :bn, :bp to navigate files; :Rex to return)"
+
+      "  call Dret("s:NetrwMarkFileEdit")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileQFEL: convert a quickfix-error or location list into a marked file list {{{2
+    fun! s:NetrwMarkFileQFEL(islocal,qfel)
+      "  call Dfunc("s:NetrwMarkFileQFEL(islocal=".a:islocal.",qfel)")
+      call s:NetrwUnmarkAll()
+      let curbufnr= bufnr("%")
+
+      if !empty(a:qfel)
+        for entry in a:qfel
+          let bufnmbr= entry["bufnr"]
+          "    call Decho("bufname(".bufnmbr.")<".bufname(bufnmbr)."> line#".entry["lnum"]." text=".entry["text"],'~'.expand(""))
+          if !exists("s:netrwmarkfilelist_{curbufnr}")
+            "     call Decho("case: no marked file list",'~'.expand(""))
+            call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
+          elseif index(s:netrwmarkfilelist_{curbufnr},bufname(bufnmbr)) == -1
+            " s:NetrwMarkFile will remove duplicate entries from the marked file list.
+            " So, this test lets two or more hits on the same pattern to be ignored.
+            "     call Decho("case: ".bufname(bufnmbr)." not currently in marked file list",'~'.expand(""))
+            call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
+          else
+            "     call Decho("case: ".bufname(bufnmbr)." already in marked file list",'~'.expand(""))
+          endif
+        endfor
+        echo "(use me to edit marked files)"
       else
-       let ret= s:RemoteSystem(xcmd)
+        call netrw#ErrorMsg(s:WARNING,"can't convert quickfix error list; its empty!",92)
       endif
-      if v:shell_error < 0
-       NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
-       break
-      else
-       if ret !=# ''
-        echo "\n"
-        " skip trailing new line
-        echo ret[0:-2]
-       else
-        echo ret
-       endif
-      endif
-     endfor
-
-   " unmark marked file list
-   call s:NetrwUnmarkList(curbufnr,curdir)
-
-   " refresh the listing
-   NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   NetrwKeepj call winrestview(svpos)
-  else
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-  endif
-
- else " apply command to global list of files, en bloc
-
-  call inputsave()
-  let cmd= input("Enter command: ","","file")
-  call inputrestore()
-  if cmd == ""
-   return
-  endif
-  if cmd =~ '%'
-   let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' '),'g')
-  else
-   let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' ')
-  endif
-  if a:islocal
-   call system(cmd)
-   if v:shell_error < 0
-    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
-   endif
-  else
-   let ret= s:RemoteSystem(cmd)
-  endif
-  call s:NetrwUnmarkAll()
-
-  " refresh the listing
-  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  NetrwKeepj call winrestview(svpos)
-
- endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
-"                  as the marked file(s) (toggles suffix presence)
-"                  Uses the local marked file list.
-fun! s:NetrwMarkHideSfx(islocal)
-  let svpos    = winsaveview()
-  let curbufnr = bufnr("%")
-
-  " s:netrwmarkfilelist_{curbufnr}: the List of marked files
-  if exists("s:netrwmarkfilelist_{curbufnr}")
-
-   for fname in s:netrwmarkfilelist_{curbufnr}
-     " construct suffix pattern
-     if fname =~ '\.'
-      let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
-     else
-      let sfxpat= '^\%(\%(\.\)\@!.\)*$'
-     endif
-     " determine if its in the hiding list or not
-     let inhidelist= 0
-     if g:netrw_list_hide != ""
-      let itemnum = 0
-      let hidelist= split(g:netrw_list_hide,',')
-      for hidepat in hidelist
-       if sfxpat == hidepat
-        let inhidelist= 1
-        break
-       endif
-       let itemnum= itemnum + 1
-      endfor
-     endif
-     if inhidelist
-      " remove sfxpat from list
-      call remove(hidelist,itemnum)
-      let g:netrw_list_hide= join(hidelist,",")
-     elseif g:netrw_list_hide != ""
-      " append sfxpat to non-empty list
-      let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
-     else
-      " set hiding list to sfxpat
-      let g:netrw_list_hide= sfxpat
-     endif
-    endfor
-
-   " refresh the listing
-   NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   NetrwKeepj call winrestview(svpos)
-  else
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-  endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2
-"                     Uses the local marked-file list.
-fun! s:NetrwMarkFileVimCmd(islocal)
-  let svpos    = winsaveview()
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
-
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-   return
-  endif
-
-  if exists("s:netrwmarkfilelist_{curbufnr}")
-   " get the command
-   call inputsave()
-   let cmd= input("Enter vim command: ","","file")
-   call inputrestore()
-   if cmd == ""
-    return
-   endif
-
-   " apply command to marked files.  Substitute: filename -> %
-   " If no %, then append a space and the filename to the command
-   for fname in s:netrwmarkfilelist_{curbufnr}
-    if a:islocal
-     1split
-     exe "sil! NetrwKeepj keepalt e ".fnameescape(fname)
-     exe cmd
-     exe "sil! keepalt wq!"
-    else
-     echo "sorry, \"mv\" not supported yet for remote files"
-    endif
-   endfor
-
-   " unmark marked file list
-   call s:NetrwUnmarkList(curbufnr,curdir)
-
-   " refresh the listing
-   NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   NetrwKeepj call winrestview(svpos)
-  else
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-  endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
-"                  as the marked file(s) (toggles suffix presence)
-"                  Uses the local marked file list.
-fun! s:NetrwMarkHideSfx(islocal)
-  let svpos    = winsaveview()
-  let curbufnr = bufnr("%")
-
-  " s:netrwmarkfilelist_{curbufnr}: the List of marked files
-  if exists("s:netrwmarkfilelist_{curbufnr}")
-
-   for fname in s:netrwmarkfilelist_{curbufnr}
-     " construct suffix pattern
-     if fname =~ '\.'
-      let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
-     else
-      let sfxpat= '^\%(\%(\.\)\@!.\)*$'
-     endif
-     " determine if its in the hiding list or not
-     let inhidelist= 0
-     if g:netrw_list_hide != ""
-      let itemnum = 0
-      let hidelist= split(g:netrw_list_hide,',')
-      for hidepat in hidelist
-       if sfxpat == hidepat
-        let inhidelist= 1
-        break
-       endif
-       let itemnum= itemnum + 1
-      endfor
-     endif
-     if inhidelist
-      " remove sfxpat from list
-      call remove(hidelist,itemnum)
-      let g:netrw_list_hide= join(hidelist,",")
-     elseif g:netrw_list_hide != ""
-      " append sfxpat to non-empty list
-      let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
-     else
-      " set hiding list to sfxpat
-      let g:netrw_list_hide= sfxpat
-     endif
-    endfor
-
-   " refresh the listing
-   NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   NetrwKeepj call winrestview(svpos)
-  else
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-  endif
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileGrep: (invoked by mg) This function applies vimgrep to marked files {{{2
-"                     Uses the global markfilelist
-fun! s:NetrwMarkFileGrep(islocal)
-"  call Dfunc("s:NetrwMarkFileGrep(islocal=".a:islocal.")")
-  let svpos    = winsaveview()
-"  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
-  let curbufnr = bufnr("%")
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-
-  if exists("s:netrwmarkfilelist")
-"   call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand(""))
-   let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
-"   call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand(""))"
-   call s:NetrwUnmarkAll()
-  else
-"   call Decho('no marked files, using "*"','~'.expand(""))
-   let netrwmarkfilelist= "*"
-  endif
-
-  " ask user for pattern
-"  call Decho("ask user for search pattern",'~'.expand(""))
-  call inputsave()
-  let pat= input("Enter pattern: ","")
-  call inputrestore()
-  let patbang = ""
-  if pat =~ '^!'
-   let patbang = "!"
-   let pat     = strpart(pat,2)
-  endif
-  if pat =~ '^\i'
-   let pat    = escape(pat,'/')
-   let pat    = '/'.pat.'/'
-  else
-   let nonisi = pat[0]
-  endif
-
-  " use vimgrep for both local and remote
-"  call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist,'~'.expand(""))
-  try
-   exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist
-  catch /^Vim\%((\a\+)\)\=:E480/
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76)
-"   call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">")
-   return
-  endtry
-  echo "(use :cn, :cp to navigate, :Rex to return)"
-
-  2match none
-"  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
-  NetrwKeepj call winrestview(svpos)
-
-  if exists("nonisi")
-   " original, user-supplied pattern did not begin with a character from isident
-"   call Decho("looking for trailing nonisi<".nonisi."> followed by a j, gj, or jg",'~'.expand(""))
-   if pat =~# nonisi.'j$\|'.nonisi.'gj$\|'.nonisi.'jg$'
-    call s:NetrwMarkFileQFEL(a:islocal,getqflist())
-   endif
-  endif
-
-"  call Dret("s:NetrwMarkFileGrep")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileMove: (invoked by mm) execute arbitrary command on marked files, one at a time {{{2
-"                      uses the global marked file list
-"                      s:netrwmfloc= 0: target directory is remote
-"                                  = 1: target directory is local
-fun! s:NetrwMarkFileMove(islocal)
-"  call Dfunc("s:NetrwMarkFileMove(islocal=".a:islocal.")")
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
-
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-"   call Dret("s:NetrwMarkFileMove")
-   return
-  endif
-"  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-
-  if !exists("s:netrwmftgt")
-   NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67)
-"   call Dret("s:NetrwMarkFileCopy 0")
-   return 0
-  endif
-"  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
-
-  if      a:islocal &&  s:netrwmftgt_islocal
-   " move: local -> local
-"   call Decho("move from local to local",'~'.expand(""))
-"   call Decho("local to local move",'~'.expand(""))
-   if !executable(g:netrw_localmovecmd)
-    call netrw#ErrorMsg(s:ERROR,"g:netrw_localmovecmd<".g:netrw_localmovecmd."> not executable on your system, aborting",90)
-"    call Dfunc("s:NetrwMarkFileMove : g:netrw_localmovecmd<".g:netrw_localmovecmd."> n/a!")
-    return
-   endif
-   let tgt = s:ShellEscape(s:netrwmftgt)
-"   call Decho("tgt<".tgt.">",'~'.expand(""))
-   if !g:netrw_cygwin && has("win32")
-    let tgt= substitute(tgt, '/','\\','g')
-"    call Decho("windows exception: tgt<".tgt.">",'~'.expand(""))
-    if g:netrw_localmovecmd =~ '\s'
-     let movecmd     = substitute(g:netrw_localmovecmd,'\s.*$','','')
-     let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','')
-     let movecmd     = netrw#WinPath(movecmd).movecmdargs
-"     call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand(""))
-    else
-     let movecmd = netrw#WinPath(g:netrw_localmovecmd)
-"     call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand(""))
-    endif
-   else
-    let movecmd = netrw#WinPath(g:netrw_localmovecmd)
-"    call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand(""))
-   endif
-   for fname in s:netrwmarkfilelist_{bufnr("%")}
-    if g:netrw_keepdir
-    " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1
-     let fname= b:netrw_curdir."/".fname
-    endif
-    if !g:netrw_cygwin && has("win32")
-     let fname= substitute(fname,'/','\\','g')
-    endif
-"    call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand(""))
-    let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt)
-    if v:shell_error != 0
-     if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir
-      call netrw#ErrorMsg(s:ERROR,"move failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",100)
-     else
-      call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localmovecmd<".g:netrw_localmovecmd.">; it doesn't work!",54)
-     endif
-     break
-    endif
-   endfor
-
-  elseif  a:islocal && !s:netrwmftgt_islocal
-   " move: local -> remote
-"   call Decho("move from local to remote",'~'.expand(""))
-"   call Decho("copy",'~'.expand(""))
-   let mflist= s:netrwmarkfilelist_{bufnr("%")}
-   NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
-"   call Decho("remove",'~'.expand(""))
-   for fname in mflist
-    let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
-    let ok        = s:NetrwLocalRmFile(b:netrw_curdir,barefname,1)
-   endfor
-   unlet mflist
-
-  elseif !a:islocal &&  s:netrwmftgt_islocal
-   " move: remote -> local
-"   call Decho("move from remote to local",'~'.expand(""))
-"   call Decho("copy",'~'.expand(""))
-   let mflist= s:netrwmarkfilelist_{bufnr("%")}
-   NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
-"   call Decho("remove",'~'.expand(""))
-   for fname in mflist
-    let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
-    let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
-   endfor
-   unlet mflist
-
-  elseif !a:islocal && !s:netrwmftgt_islocal
-   " move: remote -> remote
-"   call Decho("move from remote to remote",'~'.expand(""))
-"   call Decho("copy",'~'.expand(""))
-   let mflist= s:netrwmarkfilelist_{bufnr("%")}
-   NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
-"   call Decho("remove",'~'.expand(""))
-   for fname in mflist
-    let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
-    let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
-   endfor
-   unlet mflist
-  endif
-
-  " -------
-  " cleanup
-  " -------
-"  call Decho("cleanup",'~'.expand(""))
-
-  " remove markings from local buffer
-  call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
-
-  " refresh buffers
-  if !s:netrwmftgt_islocal
-"   call Decho("refresh netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
-   NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
-  endif
-  if a:islocal
-"   call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">",'~'.expand(""))
-   NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir)
-  endif
-  if g:netrw_fastbrowse <= 1
-"   call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh",'~'.expand(""))
-   NetrwKeepj call s:LocalBrowseRefresh()
-  endif
-
-"  call Dret("s:NetrwMarkFileMove")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2
-"                       using the hardcopy command.  Local marked-file list only.
-fun! s:NetrwMarkFilePrint(islocal)
-"  call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")")
-  let curbufnr= bufnr("%")
-
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-"   call Dret("s:NetrwMarkFilePrint")
-   return
-  endif
-"  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-  let curdir= s:NetrwGetCurdir(a:islocal)
-
-  if exists("s:netrwmarkfilelist_{curbufnr}")
-   let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr}
-   call s:NetrwUnmarkList(curbufnr,curdir)
-   for fname in netrwmarkfilelist
-    if a:islocal
-     if g:netrw_keepdir
-      let fname= s:ComposePath(curdir,fname)
-     endif
-    else
-     let fname= curdir.fname
-    endif
-    1split
-    " the autocmds will handle both local and remote files
-"    call Decho("exe sil e ".escape(fname,' '),'~'.expand(""))
-    exe "sil NetrwKeepj e ".fnameescape(fname)
-"    call Decho("hardcopy",'~'.expand(""))
-    hardcopy
-    q
-   endfor
-   2match none
-  endif
-"  call Dret("s:NetrwMarkFilePrint")
-endfun
-
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2
-"                        files when given a regexp (for which a prompt is
-"                        issued) (matches to name of files).
-fun! s:NetrwMarkFileRegexp(islocal)
-"  call Dfunc("s:NetrwMarkFileRegexp(islocal=".a:islocal.")")
-
-  " get the regular expression
-  call inputsave()
-  let regexp= input("Enter regexp: ","","file")
-  call inputrestore()
 
-  if a:islocal
-   let curdir= s:NetrwGetCurdir(a:islocal)
-"   call Decho("curdir<".fnameescape(curdir).">")
-   " get the matching list of files using local glob()
-"   call Decho("handle local regexp",'~'.expand(""))
-   let dirname = escape(b:netrw_curdir,g:netrw_glob_escape)
-   if v:version > 704 || (v:version == 704 && has("patch656"))
-    let filelist= glob(s:ComposePath(dirname,regexp),0,1,1)
-   else
-    let files   = glob(s:ComposePath(dirname,regexp),0,0)
-    let filelist= split(files,"\n")
-   endif
-"   call Decho("files<".string(filelist).">",'~'.expand(""))
-
-  " mark the list of files
-  for fname in filelist
-   if fname =~ '^'.fnameescape(curdir)
-"    call Decho("fname<".substitute(fname,'^'.fnameescape(curdir).'/','','').">",'~'.expand(""))
-    NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^'.fnameescape(curdir).'/','',''))
-   else
-"    call Decho("fname<".fname.">",'~'.expand(""))
-    NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','',''))
-   endif
-  endfor
+      "  call Dret("s:NetrwMarkFileQFEL")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2
+    "                     mx enbloc=0: Uses the local marked-file list, applies command to each file individually
+    "                     mX enbloc=1: Uses the global marked-file list, applies command to entire list
+    fun! s:NetrwMarkFileExe(islocal,enbloc)
+      let svpos    = winsaveview()
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+
+      if a:enbloc == 0
+        " individually apply command to files, one at a time
+        " sanity check
+        if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+          NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+          return
+        endif
 
-  else
-"   call Decho("handle remote regexp",'~'.expand(""))
-
-   " convert displayed listing into a filelist
-   let eikeep = &ei
-   let areg   = @a
-   sil NetrwKeepj %y a
-   setl ei=all ma
-"   call Decho("setl ei=all ma",'~'.expand(""))
-   1split
-   NetrwKeepj call s:NetrwEnew()
-   NetrwKeepj call s:NetrwOptionsSafe(a:islocal)
-   sil NetrwKeepj norm! "ap
-   NetrwKeepj 2
-   let bannercnt= search('^" =====','W')
-   exe "sil NetrwKeepj 1,".bannercnt."d"
-   setl bt=nofile
-   if     g:netrw_liststyle == s:LONGLIST
-    sil NetrwKeepj %s/\s\{2,}\S.*$//e
-    call histdel("/",-1)
-   elseif g:netrw_liststyle == s:WIDELIST
-    sil NetrwKeepj %s/\s\{2,}/\r/ge
-    call histdel("/",-1)
-   elseif g:netrw_liststyle == s:TREELIST
-    exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e'
-    sil! NetrwKeepj g/^ .*$/d
-    call histdel("/",-1)
-    call histdel("/",-1)
-   endif
-   " convert regexp into the more usual glob-style format
-   let regexp= substitute(regexp,'\*','.*','g')
-"   call Decho("regexp<".regexp.">",'~'.expand(""))
-   exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d"
-   call histdel("/",-1)
-   let filelist= getline(1,line("$"))
-   q!
-   for filename in filelist
-    NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','',''))
-   endfor
-   unlet filelist
-   let @a  = areg
-   let &ei = eikeep
-  endif
-  echo "  (use me to edit marked files)"
-
-"  call Dret("s:NetrwMarkFileRegexp")
-endfun
+        if exists("s:netrwmarkfilelist_{curbufnr}")
+          " get the command
+          call inputsave()
+          let cmd= input("Enter command: ","","file")
+          call inputrestore()
+          if cmd == ""
+            return
+          endif
+
+          " apply command to marked files, individually.  Substitute: filename -> %
+          " If no %, then append a space and the filename to the command
+          for fname in s:netrwmarkfilelist_{curbufnr}
+            if a:islocal
+              if g:netrw_keepdir
+                let fname= s:ShellEscape(netrw#WinPath(s:ComposePath(curdir,fname)))
+              endif
+            else
+              let fname= s:ShellEscape(netrw#WinPath(b:netrw_curdir.fname))
+            endif
+            if cmd =~ '%'
+              let xcmd= substitute(cmd,'%',fname,'g')
+            else
+              let xcmd= cmd.' '.fname
+            endif
+            if a:islocal
+              let ret= system(xcmd)
+            else
+              let ret= s:RemoteSystem(xcmd)
+            endif
+            if v:shell_error < 0
+              NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
+              break
+            else
+              if ret !=# ''
+                echo "\n"
+                " skip trailing new line
+                echo ret[0:-2]
+              else
+                echo ret
+              endif
+            endif
+          endfor
+
+          " unmark marked file list
+          call s:NetrwUnmarkList(curbufnr,curdir)
+
+          " refresh the listing
+          NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+          NetrwKeepj call winrestview(svpos)
+        else
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+        endif
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileSource: (invoked by ms) This function sources marked files {{{2
-"                        Uses the local marked file list.
-fun! s:NetrwMarkFileSource(islocal)
-"  call Dfunc("s:NetrwMarkFileSource(islocal=".a:islocal.")")
-  let curbufnr= bufnr("%")
+      else " apply command to global list of files, en bloc
 
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-"   call Dret("s:NetrwMarkFileSource")
-   return
-  endif
-"  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-  let curdir= s:NetrwGetCurdir(a:islocal)
-
-  if exists("s:netrwmarkfilelist_{curbufnr}")
-   let netrwmarkfilelist = s:netrwmarkfilelist_{bufnr("%")}
-   call s:NetrwUnmarkList(curbufnr,curdir)
-   for fname in netrwmarkfilelist
-    if a:islocal
-     if g:netrw_keepdir
-      let fname= s:ComposePath(curdir,fname)
-     endif
-    else
-     let fname= curdir.fname
-    endif
-    " the autocmds will handle sourcing both local and remote files
-"    call Decho("exe so ".fnameescape(fname),'~'.expand(""))
-    exe "so ".fnameescape(fname)
-   endfor
-   2match none
-  endif
-"  call Dret("s:NetrwMarkFileSource")
-endfun
+        call inputsave()
+        let cmd= input("Enter command: ","","file")
+        call inputrestore()
+        if cmd == ""
+          return
+        endif
+        if cmd =~ '%'
+          let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' '),'g')
+        else
+          let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' ')
+        endif
+        if a:islocal
+          call system(cmd)
+          if v:shell_error < 0
+            NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
+          endif
+        else
+          let ret= s:RemoteSystem(cmd)
+        endif
+        call s:NetrwUnmarkAll()
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileTag: (invoked by mT) This function applies g:netrw_ctags to marked files {{{2
-"                     Uses the global markfilelist
-fun! s:NetrwMarkFileTag(islocal)
-  let svpos    = winsaveview()
-  let curdir   = s:NetrwGetCurdir(a:islocal)
-  let curbufnr = bufnr("%")
+        " refresh the listing
+        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        NetrwKeepj call winrestview(svpos)
 
-  " sanity check
-  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-   NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-   return
-  endif
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
+    "                  as the marked file(s) (toggles suffix presence)
+    "                  Uses the local marked file list.
+    fun! s:NetrwMarkHideSfx(islocal)
+      let svpos    = winsaveview()
+      let curbufnr = bufnr("%")
+
+      " s:netrwmarkfilelist_{curbufnr}: the List of marked files
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+
+        for fname in s:netrwmarkfilelist_{curbufnr}
+          " construct suffix pattern
+          if fname =~ '\.'
+            let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
+          else
+            let sfxpat= '^\%(\%(\.\)\@!.\)*$'
+          endif
+          " determine if its in the hiding list or not
+          let inhidelist= 0
+          if g:netrw_list_hide != ""
+            let itemnum = 0
+            let hidelist= split(g:netrw_list_hide,',')
+            for hidepat in hidelist
+              if sfxpat == hidepat
+                let inhidelist= 1
+                break
+              endif
+              let itemnum= itemnum + 1
+            endfor
+          endif
+          if inhidelist
+            " remove sfxpat from list
+            call remove(hidelist,itemnum)
+            let g:netrw_list_hide= join(hidelist,",")
+          elseif g:netrw_list_hide != ""
+            " append sfxpat to non-empty list
+            let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
+          else
+            " set hiding list to sfxpat
+            let g:netrw_list_hide= sfxpat
+          endif
+        endfor
+
+        " refresh the listing
+        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        NetrwKeepj call winrestview(svpos)
+      else
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2
+    "                     Uses the local marked-file list.
+    fun! s:NetrwMarkFileVimCmd(islocal)
+      let svpos    = winsaveview()
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        return
+      endif
 
-  if exists("s:netrwmarkfilelist")
-   let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "s:ShellEscape(v:val,".!a:islocal.")"))
-   call s:NetrwUnmarkAll()
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+        " get the command
+        call inputsave()
+        let cmd= input("Enter vim command: ","","file")
+        call inputrestore()
+        if cmd == ""
+          return
+        endif
 
-   if a:islocal
+        " apply command to marked files.  Substitute: filename -> %
+        " If no %, then append a space and the filename to the command
+        for fname in s:netrwmarkfilelist_{curbufnr}
+          if a:islocal
+            1split
+            exe "sil! NetrwKeepj keepalt e ".fnameescape(fname)
+            exe cmd
+            exe "sil! keepalt wq!"
+          else
+            echo "sorry, \"mv\" not supported yet for remote files"
+          endif
+        endfor
+
+        " unmark marked file list
+        call s:NetrwUnmarkList(curbufnr,curdir)
+
+        " refresh the listing
+        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        NetrwKeepj call winrestview(svpos)
+      else
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
+    "                  as the marked file(s) (toggles suffix presence)
+    "                  Uses the local marked file list.
+    fun! s:NetrwMarkHideSfx(islocal)
+      let svpos    = winsaveview()
+      let curbufnr = bufnr("%")
+
+      " s:netrwmarkfilelist_{curbufnr}: the List of marked files
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+
+        for fname in s:netrwmarkfilelist_{curbufnr}
+          " construct suffix pattern
+          if fname =~ '\.'
+            let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
+          else
+            let sfxpat= '^\%(\%(\.\)\@!.\)*$'
+          endif
+          " determine if its in the hiding list or not
+          let inhidelist= 0
+          if g:netrw_list_hide != ""
+            let itemnum = 0
+            let hidelist= split(g:netrw_list_hide,',')
+            for hidepat in hidelist
+              if sfxpat == hidepat
+                let inhidelist= 1
+                break
+              endif
+              let itemnum= itemnum + 1
+            endfor
+          endif
+          if inhidelist
+            " remove sfxpat from list
+            call remove(hidelist,itemnum)
+            let g:netrw_list_hide= join(hidelist,",")
+          elseif g:netrw_list_hide != ""
+            " append sfxpat to non-empty list
+            let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
+          else
+            " set hiding list to sfxpat
+            let g:netrw_list_hide= sfxpat
+          endif
+        endfor
+
+        " refresh the listing
+        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        NetrwKeepj call winrestview(svpos)
+      else
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileGrep: (invoked by mg) This function applies vimgrep to marked files {{{2
+    "                     Uses the global markfilelist
+    fun! s:NetrwMarkFileGrep(islocal)
+      "  call Dfunc("s:NetrwMarkFileGrep(islocal=".a:islocal.")")
+      let svpos    = winsaveview()
+      "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
+      let curbufnr = bufnr("%")
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+
+      if exists("s:netrwmarkfilelist")
+        "   call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand(""))
+        let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
+        "   call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand(""))"
+        call s:NetrwUnmarkAll()
+      else
+        "   call Decho('no marked files, using "*"','~'.expand(""))
+        let netrwmarkfilelist= "*"
+      endif
 
-    call system(g:netrw_ctags." ".netrwmarkfilelist)
-    if v:shell_error
-     call netrw#ErrorMsg(s:ERROR,"g:netrw_ctags<".g:netrw_ctags."> is not executable!",51)
-    endif
+      " ask user for pattern
+      "  call Decho("ask user for search pattern",'~'.expand(""))
+      call inputsave()
+      let pat= input("Enter pattern: ","")
+      call inputrestore()
+      let patbang = ""
+      if pat =~ '^!'
+        let patbang = "!"
+        let pat     = strpart(pat,2)
+      endif
+      if pat =~ '^\i'
+        let pat    = escape(pat,'/')
+        let pat    = '/'.pat.'/'
+      else
+        let nonisi = pat[0]
+      endif
 
-   else
-    let cmd   = s:RemoteSystem(g:netrw_ctags." ".netrwmarkfilelist)
-    call netrw#Obtain(a:islocal,"tags")
-    let curdir= b:netrw_curdir
-    1split
-    NetrwKeepj e tags
-    let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','')
-    exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e'
-    call histdel("/",-1)
-    wq!
-   endif
-   2match none
-   call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   call winrestview(svpos)
-  endif
-endfun
+      " use vimgrep for both local and remote
+      "  call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist,'~'.expand(""))
+      try
+        exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist
+      catch /^Vim\%((\a\+)\)\=:E480/
+        NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76)
+        "   call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">")
+        return
+      endtry
+      echo "(use :cn, :cp to navigate, :Rex to return)"
 
-" ---------------------------------------------------------------------
-" s:NetrwMarkFileTgt:  (invoked by mt) This function sets up a marked file target {{{2
-"   Sets up two variables,
-"     s:netrwmftgt         : holds the target directory
-"     s:netrwmftgt_islocal : 0=target directory is remote
-"                            1=target directory is local
-fun! s:NetrwMarkFileTgt(islocal)
-  let svpos  = winsaveview()
-  let curdir = s:NetrwGetCurdir(a:islocal)
-  let hadtgt = exists("s:netrwmftgt")
-  if !exists("w:netrw_bannercnt")
-   let w:netrw_bannercnt= b:netrw_bannercnt
-  endif
+      2match none
+      "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
+      NetrwKeepj call winrestview(svpos)
 
-  " set up target
-  if line(".") < w:netrw_bannercnt
-   " if cursor in banner region, use b:netrw_curdir for the target unless its already the target
-   if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") && s:netrwmftgt == b:netrw_curdir
-    unlet s:netrwmftgt s:netrwmftgt_islocal
-    if g:netrw_fastbrowse <= 1
-     call s:LocalBrowseRefresh()
-    endif
-    call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-    call winrestview(svpos)
-    return
-   else
-    let s:netrwmftgt= b:netrw_curdir
-   endif
+      if exists("nonisi")
+        " original, user-supplied pattern did not begin with a character from isident
+        "   call Decho("looking for trailing nonisi<".nonisi."> followed by a j, gj, or jg",'~'.expand(""))
+        if pat =~# nonisi.'j$\|'.nonisi.'gj$\|'.nonisi.'jg$'
+          call s:NetrwMarkFileQFEL(a:islocal,getqflist())
+        endif
+      endif
 
-  else
-   " get word under cursor.
-   "  * If directory, use it for the target.
-   "  * If file, use b:netrw_curdir for the target
-   let curword= s:NetrwGetWord()
-   let tgtdir = s:ComposePath(curdir,curword)
-   if a:islocal && isdirectory(s:NetrwFile(tgtdir))
-    let s:netrwmftgt = tgtdir
-   elseif !a:islocal && tgtdir =~ '/$'
-    let s:netrwmftgt = tgtdir
-   else
-    let s:netrwmftgt = curdir
-   endif
-  endif
-  if a:islocal
-   " simplify the target (eg. /abc/def/../ghi -> /abc/ghi)
-   let s:netrwmftgt= simplify(s:netrwmftgt)
-  endif
-  if g:netrw_cygwin
-   let s:netrwmftgt= substitute(system("cygpath ".s:ShellEscape(s:netrwmftgt)),'\n$','','')
-   let s:netrwmftgt= substitute(s:netrwmftgt,'\n$','','')
-  endif
-  let s:netrwmftgt_islocal= a:islocal
+      "  call Dret("s:NetrwMarkFileGrep")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileMove: (invoked by mm) execute arbitrary command on marked files, one at a time {{{2
+    "                      uses the global marked file list
+    "                      s:netrwmfloc= 0: target directory is remote
+    "                                  = 1: target directory is local
+    fun! s:NetrwMarkFileMove(islocal)
+      "  call Dfunc("s:NetrwMarkFileMove(islocal=".a:islocal.")")
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        "   call Dret("s:NetrwMarkFileMove")
+        return
+      endif
+      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
 
-  " need to do refresh so that the banner will be updated
-  "  s:LocalBrowseRefresh handles all local-browsing buffers when not fast browsing
-  if g:netrw_fastbrowse <= 1
-   call s:LocalBrowseRefresh()
-  endif
-"  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-   call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,w:netrw_treetop,0))
-  else
-   call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  endif
-  call winrestview(svpos)
-  if !hadtgt
-   sil! NetrwKeepj norm! j
-  endif
-endfun
+      if !exists("s:netrwmftgt")
+        NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67)
+        "   call Dret("s:NetrwMarkFileCopy 0")
+        return 0
+      endif
+      "  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+
+      if      a:islocal &&  s:netrwmftgt_islocal
+        " move: local -> local
+        "   call Decho("move from local to local",'~'.expand(""))
+        "   call Decho("local to local move",'~'.expand(""))
+        if !executable(g:netrw_localmovecmd)
+          call netrw#ErrorMsg(s:ERROR,"g:netrw_localmovecmd<".g:netrw_localmovecmd."> not executable on your system, aborting",90)
+          "    call Dfunc("s:NetrwMarkFileMove : g:netrw_localmovecmd<".g:netrw_localmovecmd."> n/a!")
+          return
+        endif
+        let tgt = s:ShellEscape(s:netrwmftgt)
+        "   call Decho("tgt<".tgt.">",'~'.expand(""))
+        if !g:netrw_cygwin && has("win32")
+          let tgt= substitute(tgt, '/','\\','g')
+          "    call Decho("windows exception: tgt<".tgt.">",'~'.expand(""))
+          if g:netrw_localmovecmd =~ '\s'
+            let movecmd     = substitute(g:netrw_localmovecmd,'\s.*$','','')
+            let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','')
+            let movecmd     = netrw#WinPath(movecmd).movecmdargs
+            "     call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand(""))
+          else
+            let movecmd = netrw#WinPath(g:netrw_localmovecmd)
+            "     call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand(""))
+          endif
+        else
+          let movecmd = netrw#WinPath(g:netrw_localmovecmd)
+          "    call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand(""))
+        endif
+        for fname in s:netrwmarkfilelist_{bufnr("%")}
+          if g:netrw_keepdir
+            " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1
+            let fname= b:netrw_curdir."/".fname
+          endif
+          if !g:netrw_cygwin && has("win32")
+            let fname= substitute(fname,'/','\\','g')
+          endif
+          "    call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand(""))
+          let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt)
+          if v:shell_error != 0
+            if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir
+              call netrw#ErrorMsg(s:ERROR,"move failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",100)
+            else
+              call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localmovecmd<".g:netrw_localmovecmd.">; it doesn't work!",54)
+            endif
+            break
+          endif
+        endfor
+
+      elseif  a:islocal && !s:netrwmftgt_islocal
+        " move: local -> remote
+        "   call Decho("move from local to remote",'~'.expand(""))
+        "   call Decho("copy",'~'.expand(""))
+        let mflist= s:netrwmarkfilelist_{bufnr("%")}
+        NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
+        "   call Decho("remove",'~'.expand(""))
+        for fname in mflist
+          let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
+          let ok        = s:NetrwLocalRmFile(b:netrw_curdir,barefname,1)
+        endfor
+        unlet mflist
+
+      elseif !a:islocal &&  s:netrwmftgt_islocal
+        " move: remote -> local
+        "   call Decho("move from remote to local",'~'.expand(""))
+        "   call Decho("copy",'~'.expand(""))
+        let mflist= s:netrwmarkfilelist_{bufnr("%")}
+        NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
+        "   call Decho("remove",'~'.expand(""))
+        for fname in mflist
+          let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
+          let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
+        endfor
+        unlet mflist
+
+      elseif !a:islocal && !s:netrwmftgt_islocal
+        " move: remote -> remote
+        "   call Decho("move from remote to remote",'~'.expand(""))
+        "   call Decho("copy",'~'.expand(""))
+        let mflist= s:netrwmarkfilelist_{bufnr("%")}
+        NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
+        "   call Decho("remove",'~'.expand(""))
+        for fname in mflist
+          let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
+          let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
+        endfor
+        unlet mflist
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwGetCurdir: gets current directory and sets up b:netrw_curdir if necessary {{{2
-fun! s:NetrwGetCurdir(islocal)
-"  call Dfunc("s:NetrwGetCurdir(islocal=".a:islocal.")")
+      " -------
+      " cleanup
+      " -------
+      "  call Decho("cleanup",'~'.expand(""))
 
-  if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-   let b:netrw_curdir = s:NetrwTreePath(w:netrw_treetop)
-"   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used s:NetrwTreeDir)",'~'.expand(""))
-  elseif !exists("b:netrw_curdir")
-   let b:netrw_curdir= getcwd()
-"   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used getcwd)",'~'.expand(""))
-  endif
+      " remove markings from local buffer
+      call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
 
-"  call Decho("b:netrw_curdir<".b:netrw_curdir."> ".((b:netrw_curdir !~ '\<\a\{3,}://')? "does not match" : "matches")." url pattern",'~'.expand(""))
-  if b:netrw_curdir !~ '\<\a\{3,}://'
-   let curdir= b:netrw_curdir
-"   call Decho("g:netrw_keepdir=".g:netrw_keepdir,'~'.expand(""))
-   if g:netrw_keepdir == 0
-    call s:NetrwLcd(curdir)
-   endif
-  endif
+      " refresh buffers
+      if !s:netrwmftgt_islocal
+        "   call Decho("refresh netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+        NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
+      endif
+      if a:islocal
+        "   call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">",'~'.expand(""))
+        NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir)
+      endif
+      if g:netrw_fastbrowse <= 1
+        "   call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh",'~'.expand(""))
+        NetrwKeepj call s:LocalBrowseRefresh()
+      endif
 
-"  call Dret("s:NetrwGetCurdir <".curdir.">")
-  return b:netrw_curdir
-endfun
+      "  call Dret("s:NetrwMarkFileMove")
+    endfun
 
-" ---------------------------------------------------------------------
-" s:NetrwOpenFile: query user for a filename and open it {{{2
-fun! s:NetrwOpenFile(islocal)
-"  call Dfunc("s:NetrwOpenFile(islocal=".a:islocal.")")
-  let ykeep= @@
-  call inputsave()
-  let fname= input("Enter filename: ")
-  call inputrestore()
-"  call Decho("(s:NetrwOpenFile) fname<".fname.">",'~'.expand(""))
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2
+    "                       using the hardcopy command.  Local marked-file list only.
+    fun! s:NetrwMarkFilePrint(islocal)
+      "  call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")")
+      let curbufnr= bufnr("%")
 
-  " determine if Lexplore is in use
-  if exists("t:netrw_lexbufnr")
-   " check if t:netrw_lexbufnr refers to a netrw window
-"   call Decho("(s:netrwOpenFile) ..t:netrw_lexbufnr=".t:netrw_lexbufnr,'~'.expand(""))
-   let lexwinnr = bufwinnr(t:netrw_lexbufnr)
-   if lexwinnr != -1 && exists("g:netrw_chgwin") && g:netrw_chgwin != -1
-"    call Decho("(s:netrwOpenFile) ..Lexplore in use",'~'.expand(""))
-    exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
-    exe "NetrwKeepj e ".fnameescape(fname)
-    let @@= ykeep
-"    call Dret("s:NetrwOpenFile : creating a file with Lexplore mode")
-   endif
-  endif
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        "   call Dret("s:NetrwMarkFilePrint")
+        return
+      endif
+      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+      let curdir= s:NetrwGetCurdir(a:islocal)
+
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+        let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr}
+        call s:NetrwUnmarkList(curbufnr,curdir)
+        for fname in netrwmarkfilelist
+          if a:islocal
+            if g:netrw_keepdir
+              let fname= s:ComposePath(curdir,fname)
+            endif
+          else
+            let fname= curdir.fname
+          endif
+          1split
+          " the autocmds will handle both local and remote files
+          "    call Decho("exe sil e ".escape(fname,' '),'~'.expand(""))
+          exe "sil NetrwKeepj e ".fnameescape(fname)
+          "    call Decho("hardcopy",'~'.expand(""))
+          hardcopy
+          q
+        endfor
+        2match none
+      endif
+      "  call Dret("s:NetrwMarkFilePrint")
+    endfun
 
-  " Does the filename contain a path?
-  if fname !~ '[/\\]'
-   if exists("b:netrw_curdir")
-    if exists("g:netrw_quiet")
-     let netrw_quiet_keep = g:netrw_quiet
-    endif
-    let g:netrw_quiet = 1
-    " save position for benefit of Rexplore
-    let s:rexposn_{bufnr("%")}= winsaveview()
-"    call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand(""))
-    if b:netrw_curdir =~ '/$'
-     exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname)
-    else
-     exe "e ".fnameescape(b:netrw_curdir."/".fname)
-    endif
-    if exists("netrw_quiet_keep")
-     let g:netrw_quiet= netrw_quiet_keep
-    else
-     unlet g:netrw_quiet
-    endif
-   endif
-  else
-   exe "NetrwKeepj e ".fnameescape(fname)
-  endif
-  let @@= ykeep
-"  call Dret("s:NetrwOpenFile")
-endfun
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2
+    "                        files when given a regexp (for which a prompt is
+    "                        issued) (matches to name of files).
+    fun! s:NetrwMarkFileRegexp(islocal)
+      "  call Dfunc("s:NetrwMarkFileRegexp(islocal=".a:islocal.")")
 
-" ---------------------------------------------------------------------
-" netrw#Shrink: shrinks/expands a netrw or Lexplorer window {{{2
-"               For the mapping to this function be made via
-"               netrwPlugin, you'll need to have had
-"               g:netrw_usetab set to non-zero.
-fun! netrw#Shrink()
-"  call Dfunc("netrw#Shrink() ft<".&ft."> winwidth=".winwidth(0)." lexbuf#".((exists("t:netrw_lexbufnr"))? t:netrw_lexbufnr : 'n/a'))
-  let curwin  = winnr()
-  let wiwkeep = &wiw
-  set wiw=1
+      " get the regular expression
+      call inputsave()
+      let regexp= input("Enter regexp: ","","file")
+      call inputrestore()
 
-  if &ft == "netrw"
-   if winwidth(0) > g:netrw_wiw
-    let t:netrw_winwidth= winwidth(0)
-    exe "vert resize ".g:netrw_wiw
-    wincmd l
-    if winnr() == curwin
-     wincmd h
-    endif
-"    call Decho("vert resize 0",'~'.expand(""))
-   else
-    exe "vert resize ".t:netrw_winwidth
-"    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
-   endif
-
-  elseif exists("t:netrw_lexbufnr")
-   exe bufwinnr(t:netrw_lexbufnr)."wincmd w"
-   if     winwidth(bufwinnr(t:netrw_lexbufnr)) >  g:netrw_wiw
-    let t:netrw_winwidth= winwidth(0)
-    exe "vert resize ".g:netrw_wiw
-    wincmd l
-    if winnr() == curwin
-     wincmd h
-    endif
-"    call Decho("vert resize 0",'~'.expand(""))
-   elseif winwidth(bufwinnr(t:netrw_lexbufnr)) >= 0
-    exe "vert resize ".t:netrw_winwidth
-"    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
-   else
-    call netrw#Lexplore(0,0)
-   endif
+      if a:islocal
+        let curdir= s:NetrwGetCurdir(a:islocal)
+        "   call Decho("curdir<".fnameescape(curdir).">")
+        " get the matching list of files using local glob()
+        "   call Decho("handle local regexp",'~'.expand(""))
+        let dirname = escape(b:netrw_curdir,g:netrw_glob_escape)
+        if v:version > 704 || (v:version == 704 && has("patch656"))
+          let filelist= glob(s:ComposePath(dirname,regexp),0,1,1)
+        else
+          let files   = glob(s:ComposePath(dirname,regexp),0,0)
+          let filelist= split(files,"\n")
+        endif
+        "   call Decho("files<".string(filelist).">",'~'.expand(""))
+
+        " mark the list of files
+        for fname in filelist
+          if fname =~ '^'.fnameescape(curdir)
+            "    call Decho("fname<".substitute(fname,'^'.fnameescape(curdir).'/','','').">",'~'.expand(""))
+            NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^'.fnameescape(curdir).'/','',''))
+          else
+            "    call Decho("fname<".fname.">",'~'.expand(""))
+            NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','',''))
+          endif
+        endfor
 
-  else
-   call netrw#Lexplore(0,0)
-  endif
-  let wiw= wiwkeep
+      else
+        "   call Decho("handle remote regexp",'~'.expand(""))
+
+        " convert displayed listing into a filelist
+        let eikeep = &ei
+        let areg   = @a
+        sil NetrwKeepj %y a
+        setl ei=all ma
+        "   call Decho("setl ei=all ma",'~'.expand(""))
+        1split
+        NetrwKeepj call s:NetrwEnew()
+        NetrwKeepj call s:NetrwOptionsSafe(a:islocal)
+        sil NetrwKeepj norm! "ap
+        NetrwKeepj 2
+        let bannercnt= search('^" =====','W')
+        exe "sil NetrwKeepj 1,".bannercnt."d"
+        setl bt=nofile
+        if     g:netrw_liststyle == s:LONGLIST
+          sil NetrwKeepj %s/\s\{2,}\S.*$//e
+          call histdel("/",-1)
+        elseif g:netrw_liststyle == s:WIDELIST
+          sil NetrwKeepj %s/\s\{2,}/\r/ge
+          call histdel("/",-1)
+        elseif g:netrw_liststyle == s:TREELIST
+          exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e'
+          sil! NetrwKeepj g/^ .*$/d
+          call histdel("/",-1)
+          call histdel("/",-1)
+        endif
+        " convert regexp into the more usual glob-style format
+        let regexp= substitute(regexp,'\*','.*','g')
+        "   call Decho("regexp<".regexp.">",'~'.expand(""))
+        exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d"
+        call histdel("/",-1)
+        let filelist= getline(1,line("$"))
+        q!
+        for filename in filelist
+          NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','',''))
+        endfor
+        unlet filelist
+        let @a  = areg
+        let &ei = eikeep
+      endif
+      echo "  (use me to edit marked files)"
+
+      "  call Dret("s:NetrwMarkFileRegexp")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileSource: (invoked by ms) This function sources marked files {{{2
+    "                        Uses the local marked file list.
+    fun! s:NetrwMarkFileSource(islocal)
+      "  call Dfunc("s:NetrwMarkFileSource(islocal=".a:islocal.")")
+      let curbufnr= bufnr("%")
+
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        "   call Dret("s:NetrwMarkFileSource")
+        return
+      endif
+      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+      let curdir= s:NetrwGetCurdir(a:islocal)
+
+      if exists("s:netrwmarkfilelist_{curbufnr}")
+        let netrwmarkfilelist = s:netrwmarkfilelist_{bufnr("%")}
+        call s:NetrwUnmarkList(curbufnr,curdir)
+        for fname in netrwmarkfilelist
+          if a:islocal
+            if g:netrw_keepdir
+              let fname= s:ComposePath(curdir,fname)
+            endif
+          else
+            let fname= curdir.fname
+          endif
+          " the autocmds will handle sourcing both local and remote files
+          "    call Decho("exe so ".fnameescape(fname),'~'.expand(""))
+          exe "so ".fnameescape(fname)
+        endfor
+        2match none
+      endif
+      "  call Dret("s:NetrwMarkFileSource")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileTag: (invoked by mT) This function applies g:netrw_ctags to marked files {{{2
+    "                     Uses the global markfilelist
+    fun! s:NetrwMarkFileTag(islocal)
+      let svpos    = winsaveview()
+      let curdir   = s:NetrwGetCurdir(a:islocal)
+      let curbufnr = bufnr("%")
+
+      " sanity check
+      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+        return
+      endif
 
-"  call Dret("netrw#Shrink")
-endfun
+      if exists("s:netrwmarkfilelist")
+        let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "s:ShellEscape(v:val,".!a:islocal.")"))
+        call s:NetrwUnmarkAll()
+
+        if a:islocal
+
+          call system(g:netrw_ctags." ".netrwmarkfilelist)
+          if v:shell_error
+            call netrw#ErrorMsg(s:ERROR,"g:netrw_ctags<".g:netrw_ctags."> is not executable!",51)
+          endif
+
+        else
+          let cmd   = s:RemoteSystem(g:netrw_ctags." ".netrwmarkfilelist)
+          call netrw#Obtain(a:islocal,"tags")
+          let curdir= b:netrw_curdir
+          1split
+          NetrwKeepj e tags
+          let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','')
+          exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e'
+          call histdel("/",-1)
+          wq!
+        endif
+        2match none
+        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        call winrestview(svpos)
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwMarkFileTgt:  (invoked by mt) This function sets up a marked file target {{{2
+    "   Sets up two variables,
+    "     s:netrwmftgt         : holds the target directory
+    "     s:netrwmftgt_islocal : 0=target directory is remote
+    "                            1=target directory is local
+    fun! s:NetrwMarkFileTgt(islocal)
+      let svpos  = winsaveview()
+      let curdir = s:NetrwGetCurdir(a:islocal)
+      let hadtgt = exists("s:netrwmftgt")
+      if !exists("w:netrw_bannercnt")
+        let w:netrw_bannercnt= b:netrw_bannercnt
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetSortSequence: allows user to edit the sorting sequence {{{2
-fun! s:NetSortSequence(islocal)
-  let ykeep= @@
-  let svpos= winsaveview()
-  call inputsave()
-  let newsortseq= input("Edit Sorting Sequence: ",g:netrw_sort_sequence)
-  call inputrestore()
+      " set up target
+      if line(".") < w:netrw_bannercnt
+        " if cursor in banner region, use b:netrw_curdir for the target unless its already the target
+        if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") && s:netrwmftgt == b:netrw_curdir
+          unlet s:netrwmftgt s:netrwmftgt_islocal
+          if g:netrw_fastbrowse <= 1
+            call s:LocalBrowseRefresh()
+          endif
+          call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+          call winrestview(svpos)
+          return
+        else
+          let s:netrwmftgt= b:netrw_curdir
+        endif
 
-  " refresh the listing
-  let g:netrw_sort_sequence= newsortseq
-  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-  NetrwKeepj call winrestview(svpos)
-  let @@= ykeep
-endfun
+      else
+        " get word under cursor.
+        "  * If directory, use it for the target.
+        "  * If file, use b:netrw_curdir for the target
+        let curword= s:NetrwGetWord()
+        let tgtdir = s:ComposePath(curdir,curword)
+        if a:islocal && isdirectory(s:NetrwFile(tgtdir))
+          let s:netrwmftgt = tgtdir
+        elseif !a:islocal && tgtdir =~ '/$'
+          let s:netrwmftgt = tgtdir
+        else
+          let s:netrwmftgt = curdir
+        endif
+      endif
+      if a:islocal
+        " simplify the target (eg. /abc/def/../ghi -> /abc/ghi)
+        let s:netrwmftgt= simplify(s:netrwmftgt)
+      endif
+      if g:netrw_cygwin
+        let s:netrwmftgt= substitute(system("cygpath ".s:ShellEscape(s:netrwmftgt)),'\n$','','')
+        let s:netrwmftgt= substitute(s:netrwmftgt,'\n$','','')
+      endif
+      let s:netrwmftgt_islocal= a:islocal
 
-" ---------------------------------------------------------------------
-" s:NetrwUnmarkList: delete local marked file list and remove their contents from the global marked-file list {{{2
-"   User access provided by the  mapping. (see :help netrw-mF)
-"   Used by many MarkFile functions.
-fun! s:NetrwUnmarkList(curbufnr,curdir)
-"  call Dfunc("s:NetrwUnmarkList(curbufnr=".a:curbufnr." curdir<".a:curdir.">)")
-
-  "  remove all files in local marked-file list from global list
-  if exists("s:netrwmarkfilelist")
-   for mfile in s:netrwmarkfilelist_{a:curbufnr}
-    let dfile = s:ComposePath(a:curdir,mfile)       " prepend directory to mfile
-    let idx   = index(s:netrwmarkfilelist,dfile)    " get index in list of dfile
-    call remove(s:netrwmarkfilelist,idx)            " remove from global list
-   endfor
-   if s:netrwmarkfilelist == []
-    unlet s:netrwmarkfilelist
-   endif
-
-   " getting rid of the local marked-file lists is easy
-   unlet s:netrwmarkfilelist_{a:curbufnr}
-  endif
-  if exists("s:netrwmarkfilemtch_{a:curbufnr}")
-   unlet s:netrwmarkfilemtch_{a:curbufnr}
-  endif
-  2match none
-"  call Dret("s:NetrwUnmarkList")
-endfun
+      " need to do refresh so that the banner will be updated
+      "  s:LocalBrowseRefresh handles all local-browsing buffers when not fast browsing
+      if g:netrw_fastbrowse <= 1
+        call s:LocalBrowseRefresh()
+      endif
+      "  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,w:netrw_treetop,0))
+      else
+        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      endif
+      call winrestview(svpos)
+      if !hadtgt
+        sil! NetrwKeepj norm! j
+      endif
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwGetCurdir: gets current directory and sets up b:netrw_curdir if necessary {{{2
+    fun! s:NetrwGetCurdir(islocal)
+      "  call Dfunc("s:NetrwGetCurdir(islocal=".a:islocal.")")
+
+      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+        let b:netrw_curdir = s:NetrwTreePath(w:netrw_treetop)
+        "   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used s:NetrwTreeDir)",'~'.expand(""))
+      elseif !exists("b:netrw_curdir")
+        let b:netrw_curdir= getcwd()
+        "   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used getcwd)",'~'.expand(""))
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwUnmarkAll: remove the global marked file list and all local ones {{{2
-fun! s:NetrwUnmarkAll()
-"  call Dfunc("s:NetrwUnmarkAll()")
-  if exists("s:netrwmarkfilelist")
-   unlet s:netrwmarkfilelist
-  endif
-  sil call s:NetrwUnmarkAll2()
-  2match none
-"  call Dret("s:NetrwUnmarkAll")
-endfun
+      "  call Decho("b:netrw_curdir<".b:netrw_curdir."> ".((b:netrw_curdir !~ '\<\a\{3,}://')? "does not match" : "matches")." url pattern",'~'.expand(""))
+      if b:netrw_curdir !~ '\<\a\{3,}://'
+        let curdir= b:netrw_curdir
+        "   call Decho("g:netrw_keepdir=".g:netrw_keepdir,'~'.expand(""))
+        if g:netrw_keepdir == 0
+          call s:NetrwLcd(curdir)
+        endif
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwUnmarkAll2: unmark all files from all buffers {{{2
-fun! s:NetrwUnmarkAll2()
-"  call Dfunc("s:NetrwUnmarkAll2()")
-  redir => netrwmarkfilelist_let
-  let
-  redir END
-  let netrwmarkfilelist_list= split(netrwmarkfilelist_let,'\n')          " convert let string into a let list
-  call filter(netrwmarkfilelist_list,"v:val =~ '^s:netrwmarkfilelist_'") " retain only those vars that start as s:netrwmarkfilelist_
-  call map(netrwmarkfilelist_list,"substitute(v:val,'\\s.*$','','')")    " remove what the entries are equal to
-  for flist in netrwmarkfilelist_list
-   let curbufnr= substitute(flist,'s:netrwmarkfilelist_','','')
-   unlet s:netrwmarkfilelist_{curbufnr}
-   unlet s:netrwmarkfilemtch_{curbufnr}
-  endfor
-"  call Dret("s:NetrwUnmarkAll2")
-endfun
+      "  call Dret("s:NetrwGetCurdir <".curdir.">")
+      return b:netrw_curdir
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwOpenFile: query user for a filename and open it {{{2
+    fun! s:NetrwOpenFile(islocal)
+      "  call Dfunc("s:NetrwOpenFile(islocal=".a:islocal.")")
+      let ykeep= @@
+      call inputsave()
+      let fname= input("Enter filename: ")
+      call inputrestore()
+      "  call Decho("(s:NetrwOpenFile) fname<".fname.">",'~'.expand(""))
+
+      " determine if Lexplore is in use
+      if exists("t:netrw_lexbufnr")
+        " check if t:netrw_lexbufnr refers to a netrw window
+        "   call Decho("(s:netrwOpenFile) ..t:netrw_lexbufnr=".t:netrw_lexbufnr,'~'.expand(""))
+        let lexwinnr = bufwinnr(t:netrw_lexbufnr)
+        if lexwinnr != -1 && exists("g:netrw_chgwin") && g:netrw_chgwin != -1
+          "    call Decho("(s:netrwOpenFile) ..Lexplore in use",'~'.expand(""))
+          exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
+          exe "NetrwKeepj e ".fnameescape(fname)
+          let @@= ykeep
+          "    call Dret("s:NetrwOpenFile : creating a file with Lexplore mode")
+        endif
+      endif
 
-" ---------------------------------------------------------------------
-" s:NetrwUnMarkFile: called via mu map; unmarks *all* marked files, both global and buffer-local {{{2
-"
-" Marked files are in two types of lists:
-"    s:netrwmarkfilelist    -- holds complete paths to all marked files
-"    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
-"
-" Marked files suitable for use with 2match are in:
-"    s:netrwmarkfilemtch_#   -- used with 2match to display marked files
-fun! s:NetrwUnMarkFile(islocal)
-  let svpos    = winsaveview()
-  let curbufnr = bufnr("%")
+      " Does the filename contain a path?
+      if fname !~ '[/\\]'
+        if exists("b:netrw_curdir")
+          if exists("g:netrw_quiet")
+            let netrw_quiet_keep = g:netrw_quiet
+          endif
+          let g:netrw_quiet = 1
+          " save position for benefit of Rexplore
+          let s:rexposn_{bufnr("%")}= winsaveview()
+          "    call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand(""))
+          if b:netrw_curdir =~ '/$'
+            exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname)
+          else
+            exe "e ".fnameescape(b:netrw_curdir."/".fname)
+          endif
+          if exists("netrw_quiet_keep")
+            let g:netrw_quiet= netrw_quiet_keep
+          else
+            unlet g:netrw_quiet
+          endif
+        endif
+      else
+        exe "NetrwKeepj e ".fnameescape(fname)
+      endif
+      let @@= ykeep
+      "  call Dret("s:NetrwOpenFile")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " netrw#Shrink: shrinks/expands a netrw or Lexplorer window {{{2
+    "               For the mapping to this function be made via
+    "               netrwPlugin, you'll need to have had
+    "               g:netrw_usetab set to non-zero.
+    fun! netrw#Shrink()
+      "  call Dfunc("netrw#Shrink() ft<".&ft."> winwidth=".winwidth(0)." lexbuf#".((exists("t:netrw_lexbufnr"))? t:netrw_lexbufnr : 'n/a'))
+      let curwin  = winnr()
+      let wiwkeep = &wiw
+      set wiw=1
+
+      if &ft == "netrw"
+        if winwidth(0) > g:netrw_wiw
+          let t:netrw_winwidth= winwidth(0)
+          exe "vert resize ".g:netrw_wiw
+          wincmd l
+          if winnr() == curwin
+            wincmd h
+          endif
+          "    call Decho("vert resize 0",'~'.expand(""))
+        else
+          exe "vert resize ".t:netrw_winwidth
+          "    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
+        endif
 
-  " unmark marked file list
-  " (although I expect s:NetrwUpload() to do it, I'm just making sure)
-  if exists("s:netrwmarkfilelist")
-"   "   call Decho("unlet'ing: s:netrwmarkfilelist",'~'.expand(""))
-   unlet s:netrwmarkfilelist
-  endif
+      elseif exists("t:netrw_lexbufnr")
+        exe bufwinnr(t:netrw_lexbufnr)."wincmd w"
+        if     winwidth(bufwinnr(t:netrw_lexbufnr)) >  g:netrw_wiw
+          let t:netrw_winwidth= winwidth(0)
+          exe "vert resize ".g:netrw_wiw
+          wincmd l
+          if winnr() == curwin
+            wincmd h
+          endif
+          "    call Decho("vert resize 0",'~'.expand(""))
+        elseif winwidth(bufwinnr(t:netrw_lexbufnr)) >= 0
+          exe "vert resize ".t:netrw_winwidth
+          "    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
+        else
+          call netrw#Lexplore(0,0)
+        endif
 
-  let ibuf= 1
-  while ibuf < bufnr("$")
-   if exists("s:netrwmarkfilelist_".ibuf)
-    unlet s:netrwmarkfilelist_{ibuf}
-    unlet s:netrwmarkfilemtch_{ibuf}
-   endif
-   let ibuf = ibuf + 1
-  endwhile
-  2match none
+      else
+        call netrw#Lexplore(0,0)
+      endif
+      let wiw= wiwkeep
+
+      "  call Dret("netrw#Shrink")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetSortSequence: allows user to edit the sorting sequence {{{2
+    fun! s:NetSortSequence(islocal)
+      let ykeep= @@
+      let svpos= winsaveview()
+      call inputsave()
+      let newsortseq= input("Edit Sorting Sequence: ",g:netrw_sort_sequence)
+      call inputrestore()
+
+      " refresh the listing
+      let g:netrw_sort_sequence= newsortseq
+      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      NetrwKeepj call winrestview(svpos)
+      let @@= ykeep
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwUnmarkList: delete local marked file list and remove their contents from the global marked-file list {{{2
+    "   User access provided by the  mapping. (see :help netrw-mF)
+    "   Used by many MarkFile functions.
+    fun! s:NetrwUnmarkList(curbufnr,curdir)
+      "  call Dfunc("s:NetrwUnmarkList(curbufnr=".a:curbufnr." curdir<".a:curdir.">)")
+
+      "  remove all files in local marked-file list from global list
+      if exists("s:netrwmarkfilelist")
+        for mfile in s:netrwmarkfilelist_{a:curbufnr}
+          let dfile = s:ComposePath(a:curdir,mfile)       " prepend directory to mfile
+          let idx   = index(s:netrwmarkfilelist,dfile)    " get index in list of dfile
+          call remove(s:netrwmarkfilelist,idx)            " remove from global list
+        endfor
+        if s:netrwmarkfilelist == []
+          unlet s:netrwmarkfilelist
+        endif
 
-"  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-call winrestview(svpos)
-endfun
+        " getting rid of the local marked-file lists is easy
+        unlet s:netrwmarkfilelist_{a:curbufnr}
+      endif
+      if exists("s:netrwmarkfilemtch_{a:curbufnr}")
+        unlet s:netrwmarkfilemtch_{a:curbufnr}
+      endif
+      2match none
+      "  call Dret("s:NetrwUnmarkList")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwUnmarkAll: remove the global marked file list and all local ones {{{2
+    fun! s:NetrwUnmarkAll()
+      "  call Dfunc("s:NetrwUnmarkAll()")
+      if exists("s:netrwmarkfilelist")
+        unlet s:netrwmarkfilelist
+      endif
+      sil call s:NetrwUnmarkAll2()
+      2match none
+      "  call Dret("s:NetrwUnmarkAll")
+    endfun
+
+    " ---------------------------------------------------------------------
+    " s:NetrwUnmarkAll2: unmark all files from all buffers {{{2
+    fun! s:NetrwUnmarkAll2()
+      "  call Dfunc("s:NetrwUnmarkAll2()")
+      redir => netrwmarkfilelist_let
+      let
+      redir END
+      let netrwmarkfilelist_list= split(netrwmarkfilelist_let,'\n')          " convert let string into a let list
+      call filter(netrwmarkfilelist_list,"v:val =~ '^s:netrwmarkfilelist_'") " retain only those vars that start as s:netrwmarkfilelist_
+      call map(netrwmarkfilelist_list,"substitute(v:val,'\\s.*$','','')")    " remove what the entries are equal to
+      for flist in netrwmarkfilelist_list
+        let curbufnr= substitute(flist,'s:netrwmarkfilelist_','','')
+        unlet s:netrwmarkfilelist_{curbufnr}
+        unlet s:netrwmarkfilemtch_{curbufnr}
+      endfor
+      "  call Dret("s:NetrwUnmarkAll2")
+    endfun
 
-" ---------------------------------------------------------------------
-" s:NetrwMenu: generates the menu for gvim and netrw {{{2
-fun! s:NetrwMenu(domenu)
+    " ---------------------------------------------------------------------
+    " s:NetrwUnMarkFile: called via mu map; unmarks *all* marked files, both global and buffer-local {{{2
+    "
+    " Marked files are in two types of lists:
+    "    s:netrwmarkfilelist    -- holds complete paths to all marked files
+    "    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
+    "
+    " Marked files suitable for use with 2match are in:
+    "    s:netrwmarkfilemtch_#   -- used with 2match to display marked files
+    fun! s:NetrwUnMarkFile(islocal)
+      let svpos    = winsaveview()
+      let curbufnr = bufnr("%")
+
+      " unmark marked file list
+      " (although I expect s:NetrwUpload() to do it, I'm just making sure)
+      if exists("s:netrwmarkfilelist")
+        "   "   call Decho("unlet'ing: s:netrwmarkfilelist",'~'.expand(""))
+        unlet s:netrwmarkfilelist
+      endif
 
-  if !exists("g:NetrwMenuPriority")
-   let g:NetrwMenuPriority= 80
-  endif
+      let ibuf= 1
+      while ibuf < bufnr("$")
+        if exists("s:netrwmarkfilelist_".ibuf)
+          unlet s:netrwmarkfilelist_{ibuf}
+          unlet s:netrwmarkfilemtch_{ibuf}
+        endif
+        let ibuf = ibuf + 1
+      endwhile
+      2match none
 
-  if has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
-"   call Dfunc("NetrwMenu(domenu=".a:domenu.")")
+      "  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      call winrestview(svpos)
+    endfun
 
-   if !exists("s:netrw_menu_enabled") && a:domenu
-"    call Decho("initialize menu",'~'.expand(""))
-    let s:netrw_menu_enabled= 1
-    exe 'sil! menu '.g:NetrwMenuPriority.'.1      '.g:NetrwTopLvlMenu.'Help	'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.5      '.g:NetrwTopLvlMenu.'-Sep1-	:'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.6      '.g:NetrwTopLvlMenu.'Go\ Up\ Directory-	-'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.7      '.g:NetrwTopLvlMenu.'Apply\ Special\ Viewerx	x'
-    if g:netrw_dirhistmax > 0
-     exe 'sil! menu '.g:NetrwMenuPriority.'.8.1   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Current\ Directorymb	mb'
-     exe 'sil! menu '.g:NetrwMenuPriority.'.8.4   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Prev\ Dir\ (History)u	u'
-     exe 'sil! menu '.g:NetrwMenuPriority.'.8.5   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Next\ Dir\ (History)U	U'
-     exe 'sil! menu '.g:NetrwMenuPriority.'.8.6   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Listqb	qb'
-    else
-     exe 'sil! menu '.g:NetrwMenuPriority.'.8     '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History	:echo "(disabled)"'."\"
-    endif
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.1    '.g:NetrwTopLvlMenu.'Browsing\ Control.Horizontal\ Splito	o'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.2    '.g:NetrwTopLvlMenu.'Browsing\ Control.Vertical\ Splitv	v'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.3    '.g:NetrwTopLvlMenu.'Browsing\ Control.New\ Tabt	t'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.4    '.g:NetrwTopLvlMenu.'Browsing\ Control.Previewp	p'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.5    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ File\ Hiding\ List'."	\'"
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.6    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ Sorting\ SequenceS	S'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.7    '.g:NetrwTopLvlMenu.'Browsing\ Control.Quick\ Hide/Unhide\ Dot\ Files'."gh	gh"
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.8    '.g:NetrwTopLvlMenu.'Browsing\ Control.Refresh\ Listing'."	\"
-    exe 'sil! menu '.g:NetrwMenuPriority.'.9.9    '.g:NetrwTopLvlMenu.'Browsing\ Control.Settings/Options:NetrwSettings	'.":NetrwSettings\"
-    exe 'sil! menu '.g:NetrwMenuPriority.'.10     '.g:NetrwTopLvlMenu.'Delete\ File/DirectoryD	D'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Create\ New\ File%	%'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Current\ Window	'."\"
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.2   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directoryp	p'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.3   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ WindowP	P'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.4   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Windowo	o'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tabt	t'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Windowv	v'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.1   '.g:NetrwTopLvlMenu.'Explore.Directory\ Name	:Explore '
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only):Explore\ */	:Explore */'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (+subdirs):Explore\ **/	:Explore **/'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.3   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (curdir\ only):Explore\ *//	:Explore *//'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (+subdirs):Explore\ **//	:Explore **//'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Next\ Match:Nexplore	:Nexplore'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Prev\ Match:Pexplore	:Pexplore'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.13     '.g:NetrwTopLvlMenu.'Make\ Subdirectoryd	d'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.1   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Filemf	mf'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.2   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Files\ by\ Regexpmr	mr'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.3   '.g:NetrwTopLvlMenu.'Marked\ Files.Hide-Show-List\ Controla	a'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.4   '.g:NetrwTopLvlMenu.'Marked\ Files.Copy\ To\ Targetmc	mc'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.5   '.g:NetrwTopLvlMenu.'Marked\ Files.DeleteD	D'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.6   '.g:NetrwTopLvlMenu.'Marked\ Files.Diffmd	md'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.7   '.g:NetrwTopLvlMenu.'Marked\ Files.Editme	me'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.8   '.g:NetrwTopLvlMenu.'Marked\ Files.Exe\ Cmdmx	mx'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.9   '.g:NetrwTopLvlMenu.'Marked\ Files.Move\ To\ Targetmm	mm'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.10  '.g:NetrwTopLvlMenu.'Marked\ Files.ObtainO	O'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.11  '.g:NetrwTopLvlMenu.'Marked\ Files.Printmp	mp'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.12  '.g:NetrwTopLvlMenu.'Marked\ Files.ReplaceR	R'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.13  '.g:NetrwTopLvlMenu.'Marked\ Files.Set\ Targetmt	mt'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.14  '.g:NetrwTopLvlMenu.'Marked\ Files.TagmT	mT'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.14.15  '.g:NetrwTopLvlMenu.'Marked\ Files.Zip/Unzip/Compress/Uncompressmz	mz'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.15     '.g:NetrwTopLvlMenu.'Obtain\ FileO	O'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.thini	:let w:netrw_liststyle=0'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.longi	:let w:netrw_liststyle=1'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.widei	:let w:netrw_liststyle=2'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.treei	:let w:netrw_liststyle=3'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.1 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Show\ Alla	:let g:netrw_hide=0'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.3 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Normala	:let g:netrw_hide=1'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.2 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Hidden\ Onlya	:let g:netrw_hide=2'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.3   '.g:NetrwTopLvlMenu.'Style.Reverse\ Sorting\ Order'."r	r"
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.1 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Names       :let g:netrw_sort_by="name"'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.2 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Times       :let g:netrw_sort_by="time"'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Sizes       :let g:netrw_sort_by="size"'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Extens      :let g:netrw_sort_by="exten"'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.17     '.g:NetrwTopLvlMenu.'Rename\ File/DirectoryR	R'
-    exe 'sil! menu '.g:NetrwMenuPriority.'.18     '.g:NetrwTopLvlMenu.'Set\ Current\ Directoryc	c'
-    let s:netrw_menucnt= 28
-    call s:NetrwBookmarkMenu() " provide some history!  uses priorities 2,3, reserves 4, 8.2.x
-    call s:NetrwTgtMenu()      " let bookmarks and history be easy targets
-
-   elseif !a:domenu
-    let s:netrwcnt = 0
-    let curwin     = winnr()
-    windo if getline(2) =~# "Netrw" | let s:netrwcnt= s:netrwcnt + 1 | endif
-    exe curwin."wincmd w"
+    " ---------------------------------------------------------------------
+    " s:NetrwMenu: generates the menu for gvim and netrw {{{2
+    fun! s:NetrwMenu(domenu)
+
+      if !exists("g:NetrwMenuPriority")
+        let g:NetrwMenuPriority= 80
+      endif
 
-    if s:netrwcnt <= 1
-"     call Decho("clear menus",'~'.expand(""))
-     exe 'sil! unmenu '.g:NetrwTopLvlMenu
-"     call Decho('exe sil! unmenu '.g:NetrwTopLvlMenu.'*','~'.expand(""))
-     sil! unlet s:netrw_menu_enabled
+      if has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
+        "   call Dfunc("NetrwMenu(domenu=".a:domenu.")")
+
+        if !exists("s:netrw_menu_enabled") && a:domenu
+          "    call Decho("initialize menu",'~'.expand(""))
+          let s:netrw_menu_enabled= 1
+          exe 'sil! menu '.g:NetrwMenuPriority.'.1      '.g:NetrwTopLvlMenu.'Help    '
+          exe 'sil! menu '.g:NetrwMenuPriority.'.5      '.g:NetrwTopLvlMenu.'-Sep1-   :'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.6      '.g:NetrwTopLvlMenu.'Go\ Up\ Directory-  -'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.7      '.g:NetrwTopLvlMenu.'Apply\ Special\ Viewerx     x'
+          if g:netrw_dirhistmax > 0
+            exe 'sil! menu '.g:NetrwMenuPriority.'.8.1   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Current\ Directorymb      mb'
+            exe 'sil! menu '.g:NetrwMenuPriority.'.8.4   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Prev\ Dir\ (History)u u'
+            exe 'sil! menu '.g:NetrwMenuPriority.'.8.5   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Next\ Dir\ (History)U U'
+            exe 'sil! menu '.g:NetrwMenuPriority.'.8.6   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Listqb      qb'
+          else
+            exe 'sil! menu '.g:NetrwMenuPriority.'.8     '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History  :echo "(disabled)"'."\"
+          endif
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.1    '.g:NetrwTopLvlMenu.'Browsing\ Control.Horizontal\ Splito        o'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.2    '.g:NetrwTopLvlMenu.'Browsing\ Control.Vertical\ Splitv  v'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.3    '.g:NetrwTopLvlMenu.'Browsing\ Control.New\ Tabt t'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.4    '.g:NetrwTopLvlMenu.'Browsing\ Control.Previewp  p'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.5    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ File\ Hiding\ List'."       \'"
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.6    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ Sorting\ SequenceS  S'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.7    '.g:NetrwTopLvlMenu.'Browsing\ Control.Quick\ Hide/Unhide\ Dot\ Files'."gh       gh"
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.8    '.g:NetrwTopLvlMenu.'Browsing\ Control.Refresh\ Listing'."       \"
+          exe 'sil! menu '.g:NetrwMenuPriority.'.9.9    '.g:NetrwTopLvlMenu.'Browsing\ Control.Settings/Options:NetrwSettings    '.":NetrwSettings\"
+          exe 'sil! menu '.g:NetrwMenuPriority.'.10     '.g:NetrwTopLvlMenu.'Delete\ File/DirectoryD     D'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Create\ New\ File%   %'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Current\ Window      '."\"
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.2   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directoryp     p'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.3   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ WindowP        P'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.4   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Windowo     o'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tabt        t'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Windowv   v'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.1   '.g:NetrwTopLvlMenu.'Explore.Directory\ Name  :Explore '
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only):Explore\ */    :Explore */'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (+subdirs):Explore\ **/       :Explore **/'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.3   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (curdir\ only):Explore\ *//     :Explore *//'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (+subdirs):Explore\ **//        :Explore **//'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Next\ Match:Nexplore        :Nexplore'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Prev\ Match:Pexplore        :Pexplore'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.13     '.g:NetrwTopLvlMenu.'Make\ Subdirectoryd d'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.1   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Filemf  mf'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.2   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Files\ by\ Regexpmr     mr'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.3   '.g:NetrwTopLvlMenu.'Marked\ Files.Hide-Show-List\ Controla      a'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.4   '.g:NetrwTopLvlMenu.'Marked\ Files.Copy\ To\ Targetmc    mc'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.5   '.g:NetrwTopLvlMenu.'Marked\ Files.DeleteD       D'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.6   '.g:NetrwTopLvlMenu.'Marked\ Files.Diffmd        md'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.7   '.g:NetrwTopLvlMenu.'Marked\ Files.Editme        me'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.8   '.g:NetrwTopLvlMenu.'Marked\ Files.Exe\ Cmdmx    mx'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.9   '.g:NetrwTopLvlMenu.'Marked\ Files.Move\ To\ Targetmm    mm'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.10  '.g:NetrwTopLvlMenu.'Marked\ Files.ObtainO       O'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.11  '.g:NetrwTopLvlMenu.'Marked\ Files.Printmp       mp'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.12  '.g:NetrwTopLvlMenu.'Marked\ Files.ReplaceR      R'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.13  '.g:NetrwTopLvlMenu.'Marked\ Files.Set\ Targetmt mt'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.14  '.g:NetrwTopLvlMenu.'Marked\ Files.TagmT mT'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.14.15  '.g:NetrwTopLvlMenu.'Marked\ Files.Zip/Unzip/Compress/Uncompressmz       mz'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.15     '.g:NetrwTopLvlMenu.'Obtain\ FileO       O'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.thini :let w:netrw_liststyle=0'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.longi :let w:netrw_liststyle=1'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.widei :let w:netrw_liststyle=2'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.treei :let w:netrw_liststyle=3'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.1 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Show\ Alla   :let g:netrw_hide=0'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.3 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Normala      :let g:netrw_hide=1'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.2 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Hidden\ Onlya        :let g:netrw_hide=2'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.3   '.g:NetrwTopLvlMenu.'Style.Reverse\ Sorting\ Order'."r   r"
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.1 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Names       :let g:netrw_sort_by="name"'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.2 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Times       :let g:netrw_sort_by="time"'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Sizes       :let g:netrw_sort_by="size"'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Extens      :let g:netrw_sort_by="exten"'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.17     '.g:NetrwTopLvlMenu.'Rename\ File/DirectoryR     R'
+          exe 'sil! menu '.g:NetrwMenuPriority.'.18     '.g:NetrwTopLvlMenu.'Set\ Current\ Directoryc    c'
+          let s:netrw_menucnt= 28
+          call s:NetrwBookmarkMenu() " provide some history!  uses priorities 2,3, reserves 4, 8.2.x
+          call s:NetrwTgtMenu()      " let bookmarks and history be easy targets
+
+        elseif !a:domenu
+          let s:netrwcnt = 0
+          let curwin     = winnr()
+          windo if getline(2) =~# "Netrw" | let s:netrwcnt= s:netrwcnt + 1 | endif
+        exe curwin."wincmd w"
+
+        if s:netrwcnt <= 1
+          "     call Decho("clear menus",'~'.expand(""))
+          exe 'sil! unmenu '.g:NetrwTopLvlMenu
+          "     call Decho('exe sil! unmenu '.g:NetrwTopLvlMenu.'*','~'.expand(""))
+          sil! unlet s:netrw_menu_enabled
+        endif
+      endif
+      "   call Dret("NetrwMenu")
+      return
     endif
-   endif
-"   call Dret("NetrwMenu")
-   return
-  endif
 
-endfun
+  endfun
 
-" ---------------------------------------------------------------------
-" s:NetrwObtain: obtain file under cursor or from markfile list {{{2
-"                Used by the O maps (as NetrwObtain())
-fun! s:NetrwObtain(islocal)
-"  call Dfunc("NetrwObtain(islocal=".a:islocal.")")
+  " ---------------------------------------------------------------------
+  " s:NetrwObtain: obtain file under cursor or from markfile list {{{2
+  "                Used by the O maps (as NetrwObtain())
+  fun! s:NetrwObtain(islocal)
+    "  call Dfunc("NetrwObtain(islocal=".a:islocal.")")
+
+    let ykeep= @@
+    if exists("s:netrwmarkfilelist_{bufnr('%')}")
+      let islocal= s:netrwmarkfilelist_{bufnr('%')}[1] !~ '^\a\{3,}://'
+      call netrw#Obtain(islocal,s:netrwmarkfilelist_{bufnr('%')})
+      call s:NetrwUnmarkList(bufnr('%'),b:netrw_curdir)
+    else
+      call netrw#Obtain(a:islocal,s:NetrwGetWord())
+    endif
+    let @@= ykeep
 
-  let ykeep= @@
-  if exists("s:netrwmarkfilelist_{bufnr('%')}")
-   let islocal= s:netrwmarkfilelist_{bufnr('%')}[1] !~ '^\a\{3,}://'
-   call netrw#Obtain(islocal,s:netrwmarkfilelist_{bufnr('%')})
-   call s:NetrwUnmarkList(bufnr('%'),b:netrw_curdir)
-  else
-   call netrw#Obtain(a:islocal,s:NetrwGetWord())
-  endif
-  let @@= ykeep
+    "  call Dret("NetrwObtain")
+  endfun
 
-"  call Dret("NetrwObtain")
-endfun
+  " ---------------------------------------------------------------------
+  " s:NetrwPrevWinOpen: open file/directory in previous window.  {{{2
+  "   If there's only one window, then the window will first be split.
+  "   Returns:
+  "     choice = 0 : didn't have to choose
+  "     choice = 1 : saved modified file in window first
+  "     choice = 2 : didn't save modified file, opened window
+  "     choice = 3 : cancel open
+  fun! s:NetrwPrevWinOpen(islocal)
+    let ykeep= @@
+    " grab a copy of the b:netrw_curdir to pass it along to newly split windows
+    let curdir = b:netrw_curdir
+
+    " get last window number and the word currently under the cursor
+    let origwin   = winnr()
+    let lastwinnr = winnr("$")
+    let curword      = s:NetrwGetWord()
+    let choice       = 0
+    let s:prevwinopen= 1  " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen)
+    let s:treedir = s:NetrwTreeDir(a:islocal)
+    let curdir    = s:treedir
+
+    let didsplit = 0
+    if lastwinnr == 1
+      " if only one window, open a new one first
+      " g:netrw_preview=0: preview window shown in a horizontally split window
+      " g:netrw_preview=1: preview window shown in a vertically   split window
+      if g:netrw_preview
+        " vertically split preview window
+        let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
+        exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
+      else
+        " horizontally split preview window
+        let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
+        exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
+      endif
+      let didsplit = 1
 
-" ---------------------------------------------------------------------
-" s:NetrwPrevWinOpen: open file/directory in previous window.  {{{2
-"   If there's only one window, then the window will first be split.
-"   Returns:
-"     choice = 0 : didn't have to choose
-"     choice = 1 : saved modified file in window first
-"     choice = 2 : didn't save modified file, opened window
-"     choice = 3 : cancel open
-fun! s:NetrwPrevWinOpen(islocal)
-  let ykeep= @@
-  " grab a copy of the b:netrw_curdir to pass it along to newly split windows
-  let curdir = b:netrw_curdir
-
-  " get last window number and the word currently under the cursor
-  let origwin   = winnr()
-  let lastwinnr = winnr("$")
-  let curword      = s:NetrwGetWord()
-  let choice       = 0
-  let s:prevwinopen= 1	" lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen)
-  let s:treedir = s:NetrwTreeDir(a:islocal)
-  let curdir    = s:treedir
-
-  let didsplit = 0
-  if lastwinnr == 1
-   " if only one window, open a new one first
-   " g:netrw_preview=0: preview window shown in a horizontally split window
-   " g:netrw_preview=1: preview window shown in a vertically   split window
-   if g:netrw_preview
-    " vertically split preview window
-    let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
-    exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
-   else
-    " horizontally split preview window
-    let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
-    exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
-   endif
-   let didsplit = 1
+    else
+      NetrwKeepj call s:SaveBufVars()
+      let eikeep= &ei
+      setl ei=all
+      wincmd p
+
+      if exists("s:lexplore_win") && s:lexplore_win == winnr()
+        " whoops -- user trying to open file in the Lexplore window.
+        " Use Lexplore's opening-file window instead.
+        "    exe g:netrw_chgwin."wincmd w"
+        wincmd p
+        call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+      endif
 
-  else
-   NetrwKeepj call s:SaveBufVars()
-   let eikeep= &ei
-   setl ei=all
-   wincmd p
-
-   if exists("s:lexplore_win") && s:lexplore_win == winnr()
-    " whoops -- user trying to open file in the Lexplore window.
-    " Use Lexplore's opening-file window instead.
-"    exe g:netrw_chgwin."wincmd w"
-     wincmd p
-     call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
-   endif
-
-   " prevwinnr: the window number of the "prev" window
-   " prevbufnr: the buffer number of the buffer in the "prev" window
-   " bnrcnt   : the qty of windows open on the "prev" buffer
-   let prevwinnr   = winnr()
-   let prevbufnr   = bufnr("%")
-   let prevbufname = bufname("%")
-   let prevmod     = &mod
-   let bnrcnt      = 0
-   NetrwKeepj call s:RestoreBufVars()
-
-   " if the previous window's buffer has been changed (ie. its modified flag is set),
-   " and it doesn't appear in any other extant window, then ask the
-   " user if s/he wants to abandon modifications therein.
-   if prevmod
-    windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif
-    exe prevwinnr."wincmd w"
-
-    if bnrcnt == 1 && &hidden == 0
-     " only one copy of the modified buffer in a window, and
-     " hidden not set, so overwriting will lose the modified file.  Ask first...
-     let choice = confirm("Save modified buffer<".prevbufname."> first?","&Yes\n&No\n&Cancel")
-     let &ei= eikeep
-
-     if choice == 1
-      " Yes -- write file & then browse
-      let v:errmsg= ""
-      sil w
-      if v:errmsg != ""
-       call netrw#ErrorMsg(s:ERROR,"unable to write <".(exists("prevbufname")? prevbufname : 'n/a').">!",30)
-       exe origwin."wincmd w"
-       let &ei = eikeep
-       let @@  = ykeep
-       return choice
-      endif
-
-     elseif choice == 2
-      " No -- don't worry about changed file, just browse anyway
-      echomsg "**note** changes to ".prevbufname." abandoned"
-
-     else
-      " Cancel -- don't do this
-      exe origwin."wincmd w"
-      let &ei= eikeep
-      let @@ = ykeep
-      return choice
-     endif
+      " prevwinnr: the window number of the "prev" window
+      " prevbufnr: the buffer number of the buffer in the "prev" window
+      " bnrcnt   : the qty of windows open on the "prev" buffer
+      let prevwinnr   = winnr()
+      let prevbufnr   = bufnr("%")
+      let prevbufname = bufname("%")
+      let prevmod     = &mod
+      let bnrcnt      = 0
+      NetrwKeepj call s:RestoreBufVars()
+
+      " if the previous window's buffer has been changed (ie. its modified flag is set),
+      " and it doesn't appear in any other extant window, then ask the
+      " user if s/he wants to abandon modifications therein.
+      if prevmod
+        windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif
+      exe prevwinnr."wincmd w"
+
+      if bnrcnt == 1 && &hidden == 0
+        " only one copy of the modified buffer in a window, and
+        " hidden not set, so overwriting will lose the modified file.  Ask first...
+        let choice = confirm("Save modified buffer<".prevbufname."> first?","&Yes\n&No\n&Cancel")
+        let &ei= eikeep
+
+        if choice == 1
+          " Yes -- write file & then browse
+          let v:errmsg= ""
+          sil w
+          if v:errmsg != ""
+            call netrw#ErrorMsg(s:ERROR,"unable to write <".(exists("prevbufname")? prevbufname : 'n/a').">!",30)
+            exe origwin."wincmd w"
+            let &ei = eikeep
+            let @@  = ykeep
+            return choice
+          endif
+
+        elseif choice == 2
+          " No -- don't worry about changed file, just browse anyway
+          echomsg "**note** changes to ".prevbufname." abandoned"
+
+        else
+          " Cancel -- don't do this
+          exe origwin."wincmd w"
+          let &ei= eikeep
+          let @@ = ykeep
+          return choice
+        endif
+      endif
     endif
-   endif
-   let &ei= eikeep
+    let &ei= eikeep
   endif
 
   " restore b:netrw_curdir (window split/enew may have lost it)
   let b:netrw_curdir= curdir
   if a:islocal < 2
-   if a:islocal
-    call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(a:islocal,curword,0))
-   else
-    call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,curword,0))
-   endif
+    if a:islocal
+      call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(a:islocal,curword,0))
+    else
+      call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,curword,0))
+    endif
   endif
   let @@= ykeep
   return choice
@@ -8137,296 +8137,296 @@ endfun
 "                call s:NetrwUpload(filename, target)
 "                call s:NetrwUpload(filename, target, fromdirectory)
 fun! s:NetrwUpload(fname,tgt,...)
-"  call Dfunc("s:NetrwUpload(fname<".((type(a:fname) == 1)? a:fname : string(a:fname))."> tgt<".a:tgt.">) a:0=".a:0)
+  "  call Dfunc("s:NetrwUpload(fname<".((type(a:fname) == 1)? a:fname : string(a:fname))."> tgt<".a:tgt.">) a:0=".a:0)
 
   if a:tgt =~ '^\a\{3,}://'
-   let tgtdir= substitute(a:tgt,'^\a\{3,}://[^/]\+/\(.\{-}\)$','\1','')
+    let tgtdir= substitute(a:tgt,'^\a\{3,}://[^/]\+/\(.\{-}\)$','\1','')
   else
-   let tgtdir= substitute(a:tgt,'^\(.*\)/[^/]*$','\1','')
+    let tgtdir= substitute(a:tgt,'^\(.*\)/[^/]*$','\1','')
   endif
-"  call Decho("tgtdir<".tgtdir.">",'~'.expand(""))
+  "  call Decho("tgtdir<".tgtdir.">",'~'.expand(""))
 
   if a:0 > 0
-   let fromdir= a:1
+    let fromdir= a:1
   else
-   let fromdir= getcwd()
+    let fromdir= getcwd()
   endif
-"  call Decho("fromdir<".fromdir.">",'~'.expand(""))
+  "  call Decho("fromdir<".fromdir.">",'~'.expand(""))
 
   if type(a:fname) == 1
-   " handle uploading a single file using NetWrite
-"   call Decho("handle uploading a single file via NetWrite",'~'.expand(""))
-   1split
-"   call Decho("exe e ".fnameescape(s:NetrwFile(a:fname)),'~'.expand(""))
-   exe "NetrwKeepj e ".fnameescape(s:NetrwFile(a:fname))
-"   call Decho("now locally editing<".expand("%").">, has ".line("$")." lines",'~'.expand(""))
-   if a:tgt =~ '/$'
-    let wfname= substitute(a:fname,'^.*/','','')
-"    call Decho("exe w! ".fnameescape(wfname),'~'.expand(""))
-    exe "w! ".fnameescape(a:tgt.wfname)
-   else
-"    call Decho("writing local->remote: exe w ".fnameescape(a:tgt),'~'.expand(""))
-    exe "w ".fnameescape(a:tgt)
-"    call Decho("done writing local->remote",'~'.expand(""))
-   endif
-   q!
-
-  elseif type(a:fname) == 3
-   " handle uploading a list of files via scp
-"   call Decho("handle uploading a list of files via scp",'~'.expand(""))
-   let curdir= getcwd()
-   if a:tgt =~ '^scp:'
-    if s:NetrwLcd(fromdir)
-"     call Dret("s:NetrwUpload : lcd failure")
-     return
-    endif
-    let filelist= deepcopy(s:netrwmarkfilelist_{bufnr('%')})
-    let args    = join(map(filelist,"s:ShellEscape(v:val, 1)"))
-    if exists("g:netrw_port") && g:netrw_port != ""
-     let useport= " ".g:netrw_scpport." ".g:netrw_port
+    " handle uploading a single file using NetWrite
+    "   call Decho("handle uploading a single file via NetWrite",'~'.expand(""))
+    1split
+    "   call Decho("exe e ".fnameescape(s:NetrwFile(a:fname)),'~'.expand(""))
+    exe "NetrwKeepj e ".fnameescape(s:NetrwFile(a:fname))
+    "   call Decho("now locally editing<".expand("%").">, has ".line("$")." lines",'~'.expand(""))
+    if a:tgt =~ '/$'
+      let wfname= substitute(a:fname,'^.*/','','')
+      "    call Decho("exe w! ".fnameescape(wfname),'~'.expand(""))
+      exe "w! ".fnameescape(a:tgt.wfname)
     else
-     let useport= ""
-    endif
-    let machine = substitute(a:tgt,'^scp://\([^/:]\+\).*$','\1','')
-    let tgt     = substitute(a:tgt,'^scp://[^/]\+/\(.*\)$','\1','')
-    call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".args." ".s:ShellEscape(machine.":".tgt,1))
-    if s:NetrwLcd(curdir)
-"     call Dret("s:NetrwUpload : lcd failure")
-     return
+      "    call Decho("writing local->remote: exe w ".fnameescape(a:tgt),'~'.expand(""))
+      exe "w ".fnameescape(a:tgt)
+      "    call Decho("done writing local->remote",'~'.expand(""))
     endif
+    q!
 
-   elseif a:tgt =~ '^ftp:'
-    call s:NetrwMethod(a:tgt)
+  elseif type(a:fname) == 3
+    " handle uploading a list of files via scp
+    "   call Decho("handle uploading a list of files via scp",'~'.expand(""))
+    let curdir= getcwd()
+    if a:tgt =~ '^scp:'
+      if s:NetrwLcd(fromdir)
+        "     call Dret("s:NetrwUpload : lcd failure")
+        return
+      endif
+      let filelist= deepcopy(s:netrwmarkfilelist_{bufnr('%')})
+      let args    = join(map(filelist,"s:ShellEscape(v:val, 1)"))
+      if exists("g:netrw_port") && g:netrw_port != ""
+        let useport= " ".g:netrw_scpport." ".g:netrw_port
+      else
+        let useport= ""
+      endif
+      let machine = substitute(a:tgt,'^scp://\([^/:]\+\).*$','\1','')
+      let tgt     = substitute(a:tgt,'^scp://[^/]\+/\(.*\)$','\1','')
+      call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".args." ".s:ShellEscape(machine.":".tgt,1))
+      if s:NetrwLcd(curdir)
+        "     call Dret("s:NetrwUpload : lcd failure")
+        return
+      endif
 
-    if b:netrw_method == 2
-     " handle uploading a list of files via ftp+.netrc
-     let netrw_fname = b:netrw_fname
-     sil NetrwKeepj new
-"     call Decho("filter input window#".winnr(),'~'.expand(""))
+    elseif a:tgt =~ '^ftp:'
+      call s:NetrwMethod(a:tgt)
 
-     NetrwKeepj put =g:netrw_ftpmode
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      if b:netrw_method == 2
+        " handle uploading a list of files via ftp+.netrc
+        let netrw_fname = b:netrw_fname
+        sil NetrwKeepj new
+        "     call Decho("filter input window#".winnr(),'~'.expand(""))
 
-     if exists("g:netrw_ftpextracmd")
-      NetrwKeepj put =g:netrw_ftpextracmd
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
-
-     NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"')
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-
-     if tgtdir == ""
-      let tgtdir= '/'
-     endif
-     NetrwKeepj call setline(line("$")+1,'cd "'.tgtdir.'"')
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
-
-     for fname in a:fname
-      NetrwKeepj call setline(line("$")+1,'put "'.s:NetrwFile(fname).'"')
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endfor
-
-     if exists("g:netrw_port") && g:netrw_port != ""
-      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
-     else
-"      call Decho("filter input window#".winnr(),'~'.expand(""))
-      call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
-     endif
-     " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-     sil NetrwKeepj g/Local directory now/d
-     call histdel("/",-1)
-     if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
-      call netrw#ErrorMsg(s:ERROR,getline(1),14)
-     else
-      bw!|q
-     endif
+        NetrwKeepj put =g:netrw_ftpmode
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
 
-    elseif b:netrw_method == 3
-     " upload with ftp + machine, id, passwd, and fname (ie. no .netrc)
-     let netrw_fname= b:netrw_fname
-     NetrwKeepj call s:SaveBufVars()|sil NetrwKeepj new|NetrwKeepj call s:RestoreBufVars()
-     let tmpbufnr= bufnr("%")
-     setl ff=unix
+        if exists("g:netrw_ftpextracmd")
+          NetrwKeepj put =g:netrw_ftpextracmd
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endif
 
-     if exists("g:netrw_port") && g:netrw_port != ""
-      NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     else
-      NetrwKeepj put ='open '.g:netrw_machine
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
+        NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"')
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
 
-     if exists("g:netrw_uid") && g:netrw_uid != ""
-      if exists("g:netrw_ftp") && g:netrw_ftp == 1
-       NetrwKeepj put =g:netrw_uid
-"       call Decho("filter input: ".getline('$'),'~'.expand(""))
-       if exists("s:netrw_passwd")
-        NetrwKeepj call setline(line("$")+1,'"'.s:netrw_passwd.'"')
-       endif
-"       call Decho("filter input: ".getline('$'),'~'.expand(""))
-      elseif exists("s:netrw_passwd")
-       NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
-"       call Decho("filter input: ".getline('$'),'~'.expand(""))
-      endif
-     endif
+        if tgtdir == ""
+          let tgtdir= '/'
+        endif
+        NetrwKeepj call setline(line("$")+1,'cd "'.tgtdir.'"')
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+
+        for fname in a:fname
+          NetrwKeepj call setline(line("$")+1,'put "'.s:NetrwFile(fname).'"')
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endfor
+
+        if exists("g:netrw_port") && g:netrw_port != ""
+          call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1))
+        else
+          "      call Decho("filter input window#".winnr(),'~'.expand(""))
+          call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1))
+        endif
+        " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+        sil NetrwKeepj g/Local directory now/d
+        call histdel("/",-1)
+        if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
+          call netrw#ErrorMsg(s:ERROR,getline(1),14)
+        else
+          bw!|q
+        endif
+
+      elseif b:netrw_method == 3
+        " upload with ftp + machine, id, passwd, and fname (ie. no .netrc)
+        let netrw_fname= b:netrw_fname
+        NetrwKeepj call s:SaveBufVars()|sil NetrwKeepj new|NetrwKeepj call s:RestoreBufVars()
+        let tmpbufnr= bufnr("%")
+        setl ff=unix
+
+        if exists("g:netrw_port") && g:netrw_port != ""
+          NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        else
+          NetrwKeepj put ='open '.g:netrw_machine
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endif
 
-     NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"')
-"     call Decho("filter input: ".getline('$'),'~'.expand(""))
+        if exists("g:netrw_uid") && g:netrw_uid != ""
+          if exists("g:netrw_ftp") && g:netrw_ftp == 1
+            NetrwKeepj put =g:netrw_uid
+            "       call Decho("filter input: ".getline('$'),'~'.expand(""))
+            if exists("s:netrw_passwd")
+              NetrwKeepj call setline(line("$")+1,'"'.s:netrw_passwd.'"')
+            endif
+            "       call Decho("filter input: ".getline('$'),'~'.expand(""))
+          elseif exists("s:netrw_passwd")
+            NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+            "       call Decho("filter input: ".getline('$'),'~'.expand(""))
+          endif
+        endif
 
-     if exists("b:netrw_fname") && b:netrw_fname != ""
-      NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
+        NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"')
+        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
 
-     if exists("g:netrw_ftpextracmd")
-      NetrwKeepj put =g:netrw_ftpextracmd
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endif
-
-     for fname in a:fname
-      NetrwKeepj call setline(line("$")+1,'put "'.fname.'"')
-"      call Decho("filter input: ".getline('$'),'~'.expand(""))
-     endfor
-
-     " perform ftp:
-     " -i       : turns off interactive prompting from ftp
-     " -n  unix : DON'T use <.netrc>, even though it exists
-     " -n  win32: quit being obnoxious about password
-     NetrwKeepj norm! 1G"_dd
-     call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
-     " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
-     sil NetrwKeepj g/Local directory now/d
-     call histdel("/",-1)
-     if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
-      let debugkeep= &debug
-      setl debug=msg
-      call netrw#ErrorMsg(s:ERROR,getline(1),15)
-      let &debug = debugkeep
-      let mod    = 1
-     else
-      bw!|q
-     endif
-    elseif !exists("b:netrw_method") || b:netrw_method < 0
-"     call Dret("s:#NetrwUpload : unsupported method")
-     return
+        if exists("b:netrw_fname") && b:netrw_fname != ""
+          NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endif
+
+        if exists("g:netrw_ftpextracmd")
+          NetrwKeepj put =g:netrw_ftpextracmd
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endif
+
+        for fname in a:fname
+          NetrwKeepj call setline(line("$")+1,'put "'.fname.'"')
+          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        endfor
+
+        " perform ftp:
+        " -i       : turns off interactive prompting from ftp
+        " -n  unix : DON'T use <.netrc>, even though it exists
+        " -n  win32: quit being obnoxious about password
+        NetrwKeepj norm! 1G"_dd
+        call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
+        " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
+        sil NetrwKeepj g/Local directory now/d
+        call histdel("/",-1)
+        if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
+          let debugkeep= &debug
+          setl debug=msg
+          call netrw#ErrorMsg(s:ERROR,getline(1),15)
+          let &debug = debugkeep
+          let mod    = 1
+        else
+          bw!|q
+        endif
+      elseif !exists("b:netrw_method") || b:netrw_method < 0
+        "     call Dret("s:#NetrwUpload : unsupported method")
+        return
+      endif
+    else
+      call netrw#ErrorMsg(s:ERROR,"can't obtain files with protocol from<".a:tgt.">",63)
     endif
-   else
-    call netrw#ErrorMsg(s:ERROR,"can't obtain files with protocol from<".a:tgt.">",63)
-   endif
   endif
 
-"  call Dret("s:NetrwUpload")
+  "  call Dret("s:NetrwUpload")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwPreview: supports netrw's "p" map {{{2
 fun! s:NetrwPreview(path) range
-"  call Dfunc("NetrwPreview(path<".a:path.">)")
-"  call Decho("g:netrw_alto   =".(exists("g:netrw_alto")?    g:netrw_alto    : 'n/a'),'~'.expand(""))
-"  call Decho("g:netrw_preview=".(exists("g:netrw_preview")? g:netrw_preview : 'n/a'),'~'.expand(""))
+  "  call Dfunc("NetrwPreview(path<".a:path.">)")
+  "  call Decho("g:netrw_alto   =".(exists("g:netrw_alto")?    g:netrw_alto    : 'n/a'),'~'.expand(""))
+  "  call Decho("g:netrw_preview=".(exists("g:netrw_preview")? g:netrw_preview : 'n/a'),'~'.expand(""))
   let ykeep= @@
   NetrwKeepj call s:NetrwOptionsSave("s:")
   if a:path !~ '^\*\{1,2}/' && a:path !~ '^\a\{3,}://'
-   NetrwKeepj call s:NetrwOptionsSafe(1)
+    NetrwKeepj call s:NetrwOptionsSafe(1)
   else
-   NetrwKeepj call s:NetrwOptionsSafe(0)
+    NetrwKeepj call s:NetrwOptionsSafe(0)
   endif
   if has("quickfix")
-"   call Decho("has quickfix",'~'.expand(""))
-   if !isdirectory(s:NetrwFile(a:path))
-"    call Decho("good; not previewing a directory",'~'.expand(""))
-    if g:netrw_preview
-     " vertical split
-     let pvhkeep = &pvh
-     let winsz   = (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
-     let &pvh    = winwidth(0) - winsz
-"     call Decho("g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily)  g:netrw_winsize=".g:netrw_winsize,'~'.expand(""))
-    else
-     " horizontal split
-     let pvhkeep = &pvh
-     let winsz   = (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
-     let &pvh    = winheight(0) - winsz
-"     call Decho("!g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily)  g:netrw_winsize=".g:netrw_winsize,'~'.expand(""))
-    endif
-    " g:netrw_preview   g:netrw_alto
-    "    1 : vert        1: top       -- preview window is vertically   split off and on the left
-    "    1 : vert        0: bot       -- preview window is vertically   split off and on the right
-    "    0 :             1: top       -- preview window is horizontally split off and on the top
-    "    0 :             0: bot       -- preview window is horizontally split off and on the bottom
-    "
-    " Note that the file being previewed is already known to not be a directory, hence we can avoid doing a LocalBrowseCheck() check via
-    " the BufEnter event set up in netrwPlugin.vim
-"    call Decho("exe ".(g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path),'~'.expand(""))
-    let eikeep = &ei
-    set ei=BufEnter
-    exe (g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path)
-    let &ei= eikeep
-"    call Decho("winnr($)=".winnr("$"),'~'.expand(""))
-    if exists("pvhkeep")
-     let &pvh= pvhkeep
+    "   call Decho("has quickfix",'~'.expand(""))
+    if !isdirectory(s:NetrwFile(a:path))
+      "    call Decho("good; not previewing a directory",'~'.expand(""))
+      if g:netrw_preview
+        " vertical split
+        let pvhkeep = &pvh
+        let winsz   = (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
+        let &pvh    = winwidth(0) - winsz
+        "     call Decho("g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily)  g:netrw_winsize=".g:netrw_winsize,'~'.expand(""))
+      else
+        " horizontal split
+        let pvhkeep = &pvh
+        let winsz   = (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
+        let &pvh    = winheight(0) - winsz
+        "     call Decho("!g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily)  g:netrw_winsize=".g:netrw_winsize,'~'.expand(""))
+      endif
+      " g:netrw_preview   g:netrw_alto
+      "    1 : vert        1: top       -- preview window is vertically   split off and on the left
+      "    1 : vert        0: bot       -- preview window is vertically   split off and on the right
+      "    0 :             1: top       -- preview window is horizontally split off and on the top
+      "    0 :             0: bot       -- preview window is horizontally split off and on the bottom
+      "
+      " Note that the file being previewed is already known to not be a directory, hence we can avoid doing a LocalBrowseCheck() check via
+      " the BufEnter event set up in netrwPlugin.vim
+      "    call Decho("exe ".(g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path),'~'.expand(""))
+      let eikeep = &ei
+      set ei=BufEnter
+      exe (g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path)
+      let &ei= eikeep
+      "    call Decho("winnr($)=".winnr("$"),'~'.expand(""))
+      if exists("pvhkeep")
+        let &pvh= pvhkeep
+      endif
+    elseif !exists("g:netrw_quiet")
+      NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, cannot preview a directory such as <".a:path.">",38)
     endif
-   elseif !exists("g:netrw_quiet")
-    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, cannot preview a directory such as <".a:path.">",38)
-   endif
   elseif !exists("g:netrw_quiet")
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, to preview your vim needs the quickfix feature compiled in",39)
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, to preview your vim needs the quickfix feature compiled in",39)
   endif
   NetrwKeepj call s:NetrwOptionsRestore("s:")
   let @@= ykeep
-"  call Dret("NetrwPreview")
+  "  call Dret("NetrwPreview")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwRefresh: {{{2
 fun! s:NetrwRefresh(islocal,dirname)
-"  call Dfunc("s:NetrwRefresh(islocal<".a:islocal.">,dirname=".a:dirname.") g:netrw_hide=".g:netrw_hide." g:netrw_sort_direction=".g:netrw_sort_direction)
+  "  call Dfunc("s:NetrwRefresh(islocal<".a:islocal.">,dirname=".a:dirname.") g:netrw_hide=".g:netrw_hide." g:netrw_sort_direction=".g:netrw_sort_direction)
   " at the current time (Mar 19, 2007) all calls to NetrwRefresh() call NetrwBrowseChgDir() first.
   setl ma noro
-"  call Decho("setl ma noro",'~'.expand(""))
-"  call Decho("clear buffer<".expand("%")."> with :%d",'~'.expand(""))
+  "  call Decho("setl ma noro",'~'.expand(""))
+  "  call Decho("clear buffer<".expand("%")."> with :%d",'~'.expand(""))
   let ykeep      = @@
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-   if !exists("w:netrw_treetop")
-    if exists("b:netrw_curdir")
-     let w:netrw_treetop= b:netrw_curdir
-    else
-     let w:netrw_treetop= getcwd()
+    if !exists("w:netrw_treetop")
+      if exists("b:netrw_curdir")
+        let w:netrw_treetop= b:netrw_curdir
+      else
+        let w:netrw_treetop= getcwd()
+      endif
     endif
-   endif
-   NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop)
+    NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop)
   endif
 
   " save the cursor position before refresh.
   let screenposn = winsaveview()
-"  call Decho("saving posn to screenposn<".string(screenposn).">",'~'.expand(""))
+  "  call Decho("saving posn to screenposn<".string(screenposn).">",'~'.expand(""))
 
-"  call Decho("win#".winnr().": ".winheight(0)."x".winwidth(0)." curfile<".expand("%").">",'~'.expand(""))
-"  call Decho("clearing buffer prior to refresh",'~'.expand(""))
+  "  call Decho("win#".winnr().": ".winheight(0)."x".winwidth(0)." curfile<".expand("%").">",'~'.expand(""))
+  "  call Decho("clearing buffer prior to refresh",'~'.expand(""))
   sil! NetrwKeepj %d _
   if a:islocal
-   NetrwKeepj call netrw#LocalBrowseCheck(a:dirname)
+    NetrwKeepj call netrw#LocalBrowseCheck(a:dirname)
   else
-   NetrwKeepj call s:NetrwBrowse(a:islocal,a:dirname)
+    NetrwKeepj call s:NetrwBrowse(a:islocal,a:dirname)
   endif
 
   " restore position
-"  call Decho("restoring posn to screenposn<".string(screenposn).">",'~'.expand(""))
+  "  call Decho("restoring posn to screenposn<".string(screenposn).">",'~'.expand(""))
   NetrwKeepj call winrestview(screenposn)
 
   " restore file marks
   if has("syntax") && exists("g:syntax_on") && g:syntax_on
-   if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != ""
-" "   call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/",'~'.expand(""))
-    exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
-   else
-" "   call Decho("2match none  (bufnr(%)=".bufnr("%")."<".bufname("%").">)",'~'.expand(""))
-    2match none
-   endif
- endif
+    if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != ""
+      " "   call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/",'~'.expand(""))
+      exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
+    else
+      " "   call Decho("2match none  (bufnr(%)=".bufnr("%")."<".bufname("%").">)",'~'.expand(""))
+      2match none
+    endif
+  endif
 
-"  restore
+  "  restore
   let @@= ykeep
-"  call Dret("s:NetrwRefresh")
+  "  call Dret("s:NetrwRefresh")
 endfun
 
 " ---------------------------------------------------------------------
@@ -8435,23 +8435,23 @@ endfun
 "                    Interfaces to s:NetrwRefresh() and s:LocalBrowseRefresh()
 fun! s:NetrwRefreshDir(islocal,dirname)
   if g:netrw_fastbrowse == 0
-   " slowest mode (keep buffers refreshed, local or remote)
-   let tgtwin= bufwinnr(a:dirname)
-
-   if tgtwin > 0
-    " tgtwin is being displayed, so refresh it
-    let curwin= winnr()
-    exe tgtwin."wincmd w"
-    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-    exe curwin."wincmd w"
+    " slowest mode (keep buffers refreshed, local or remote)
+    let tgtwin= bufwinnr(a:dirname)
+
+    if tgtwin > 0
+      " tgtwin is being displayed, so refresh it
+      let curwin= winnr()
+      exe tgtwin."wincmd w"
+      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      exe curwin."wincmd w"
 
-   elseif bufnr(a:dirname) > 0
-    let bn= bufnr(a:dirname)
-    exe "sil keepj bd ".bn
-   endif
+    elseif bufnr(a:dirname) > 0
+      let bn= bufnr(a:dirname)
+      exe "sil keepj bd ".bn
+    endif
 
   elseif g:netrw_fastbrowse <= 1
-   NetrwKeepj call s:LocalBrowseRefresh()
+    NetrwKeepj call s:LocalBrowseRefresh()
   endif
 endfun
 
@@ -8461,21 +8461,21 @@ endfun
 " Supports   [count]C  where the count, if present, is used to specify
 " a window to use for editing via the  mapping.
 fun! s:NetrwSetChgwin(...)
-"  call Dfunc("s:NetrwSetChgwin() v:count=".v:count)
+  "  call Dfunc("s:NetrwSetChgwin() v:count=".v:count)
   if a:0 > 0
-"   call Decho("a:1<".a:1.">",'~'.expand(""))
-   if a:1 == ""    " :NetrwC win#
-    let g:netrw_chgwin= winnr()
-   else              " :NetrwC
-    let g:netrw_chgwin= a:1
-   endif
+    "   call Decho("a:1<".a:1.">",'~'.expand(""))
+    if a:1 == ""    " :NetrwC win#
+      let g:netrw_chgwin= winnr()
+    else              " :NetrwC
+      let g:netrw_chgwin= a:1
+    endif
   elseif v:count > 0 " [count]C
-   let g:netrw_chgwin= v:count
+    let g:netrw_chgwin= v:count
   else               " C
-   let g:netrw_chgwin= winnr()
+    let g:netrw_chgwin= winnr()
   endif
   echo "editing window now set to window#".g:netrw_chgwin
-"  call Dret("s:NetrwSetChgwin : g:netrw_chgwin=".g:netrw_chgwin)
+  "  call Dret("s:NetrwSetChgwin : g:netrw_chgwin=".g:netrw_chgwin)
 endfun
 
 " ---------------------------------------------------------------------
@@ -8485,58 +8485,58 @@ endfun
 "          "files" that satisfy each pattern, putting the priority / in
 "          front.  An "*" pattern handles the default priority.
 fun! s:NetrwSetSort()
-"  call Dfunc("SetSort() bannercnt=".w:netrw_bannercnt)
+  "  call Dfunc("SetSort() bannercnt=".w:netrw_bannercnt)
   let ykeep= @@
   if w:netrw_liststyle == s:LONGLIST
-   let seqlist  = substitute(g:netrw_sort_sequence,'\$','\\%(\t\\|\$\\)','ge')
+    let seqlist  = substitute(g:netrw_sort_sequence,'\$','\\%(\t\\|\$\\)','ge')
   else
-   let seqlist  = g:netrw_sort_sequence
+    let seqlist  = g:netrw_sort_sequence
   endif
   " sanity check -- insure that * appears somewhere
   if seqlist == ""
-   let seqlist= '*'
+    let seqlist= '*'
   elseif seqlist !~ '\*'
-   let seqlist= seqlist.',*'
+    let seqlist= seqlist.',*'
   endif
   let priority = 1
   while seqlist != ""
-   if seqlist =~ ','
-    let seq     = substitute(seqlist,',.*$','','e')
-    let seqlist = substitute(seqlist,'^.\{-},\(.*\)$','\1','e')
-   else
-    let seq     = seqlist
-    let seqlist = ""
-   endif
-   if priority < 10
-    let spriority= "00".priority.g:netrw_sepchr
-   elseif priority < 100
-    let spriority= "0".priority.g:netrw_sepchr
-   else
-    let spriority= priority.g:netrw_sepchr
-   endif
-"   call Decho("priority=".priority." spriority<".spriority."> seq<".seq."> seqlist<".seqlist.">",'~'.expand(""))
-
-   " sanity check
-   if w:netrw_bannercnt > line("$")
-    " apparently no files were left after a Hiding pattern was used
-"    call Dret("SetSort : no files left after hiding")
-    return
-   endif
-   if seq == '*'
-    let starpriority= spriority
-   else
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/'.seq.'/s/^/'.spriority.'/'
-    call histdel("/",-1)
-    " sometimes multiple sorting patterns will match the same file or directory.
-    " The following substitute is intended to remove the excess matches.
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^\d\{3}'.g:netrw_sepchr.'\d\{3}\//s/^\d\{3}'.g:netrw_sepchr.'\(\d\{3}\/\).\@=/\1/e'
-    NetrwKeepj call histdel("/",-1)
-   endif
-   let priority = priority + 1
+    if seqlist =~ ','
+      let seq     = substitute(seqlist,',.*$','','e')
+      let seqlist = substitute(seqlist,'^.\{-},\(.*\)$','\1','e')
+    else
+      let seq     = seqlist
+      let seqlist = ""
+    endif
+    if priority < 10
+      let spriority= "00".priority.g:netrw_sepchr
+    elseif priority < 100
+      let spriority= "0".priority.g:netrw_sepchr
+    else
+      let spriority= priority.g:netrw_sepchr
+    endif
+    "   call Decho("priority=".priority." spriority<".spriority."> seq<".seq."> seqlist<".seqlist.">",'~'.expand(""))
+
+    " sanity check
+    if w:netrw_bannercnt > line("$")
+      " apparently no files were left after a Hiding pattern was used
+      "    call Dret("SetSort : no files left after hiding")
+      return
+    endif
+    if seq == '*'
+      let starpriority= spriority
+    else
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/'.seq.'/s/^/'.spriority.'/'
+      call histdel("/",-1)
+      " sometimes multiple sorting patterns will match the same file or directory.
+      " The following substitute is intended to remove the excess matches.
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^\d\{3}'.g:netrw_sepchr.'\d\{3}\//s/^\d\{3}'.g:netrw_sepchr.'\(\d\{3}\/\).\@=/\1/e'
+      NetrwKeepj call histdel("/",-1)
+    endif
+    let priority = priority + 1
   endwhile
   if exists("starpriority")
-   exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v/^\d\{3}'.g:netrw_sepchr.'/s/^/'.starpriority.'/e'
-   NetrwKeepj call histdel("/",-1)
+    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v/^\d\{3}'.g:netrw_sepchr.'/s/^/'.starpriority.'/e'
+    NetrwKeepj call histdel("/",-1)
   endif
 
   " Following line associated with priority -- items that satisfy a priority
@@ -8549,7 +8549,7 @@ fun! s:NetrwSetSort()
   NetrwKeepj call histdel("/",-1)
   let @@= ykeep
 
-"  call Dret("SetSort")
+  "  call Dret("SetSort")
 endfun
 
 " ---------------------------------------------------------------------
@@ -8558,35 +8558,35 @@ endfun
 "               [count]Th  (bookhist)
 "               See :help netrw-qb for how to make the choice.
 fun! s:NetrwSetTgt(islocal,bookhist,choice)
-"  call Dfunc("s:NetrwSetTgt(islocal=".a:islocal." bookhist<".a:bookhist."> choice#".a:choice.")")
+  "  call Dfunc("s:NetrwSetTgt(islocal=".a:islocal." bookhist<".a:bookhist."> choice#".a:choice.")")
 
   if     a:bookhist == 'b'
-   " supports choosing a bookmark as a target using a qb-generated list
-   let choice= a:choice - 1
-   if exists("g:netrw_bookmarklist[".choice."]")
-    call netrw#MakeTgt(g:netrw_bookmarklist[choice])
-   else
-    echomsg "Sorry, bookmark#".a:choice." doesn't exist!"
-   endif
+    " supports choosing a bookmark as a target using a qb-generated list
+    let choice= a:choice - 1
+    if exists("g:netrw_bookmarklist[".choice."]")
+      call netrw#MakeTgt(g:netrw_bookmarklist[choice])
+    else
+      echomsg "Sorry, bookmark#".a:choice." doesn't exist!"
+    endif
 
   elseif a:bookhist == 'h'
-   " supports choosing a history stack entry as a target using a qb-generated list
-   let choice= (a:choice % g:netrw_dirhistmax) + 1
-   if exists("g:netrw_dirhist_".choice)
-    let histentry = g:netrw_dirhist_{choice}
-    call netrw#MakeTgt(histentry)
-   else
-    echomsg "Sorry, history#".a:choice." not available!"
-   endif
+    " supports choosing a history stack entry as a target using a qb-generated list
+    let choice= (a:choice % g:netrw_dirhistmax) + 1
+    if exists("g:netrw_dirhist_".choice)
+      let histentry = g:netrw_dirhist_{choice}
+      call netrw#MakeTgt(histentry)
+    else
+      echomsg "Sorry, history#".a:choice." not available!"
+    endif
   endif
 
   " refresh the display
   if !exists("b:netrw_curdir")
-   let b:netrw_curdir= getcwd()
+    let b:netrw_curdir= getcwd()
   endif
   call s:NetrwRefresh(a:islocal,b:netrw_curdir)
 
-"  call Dret("s:NetrwSetTgt")
+  "  call Dret("s:NetrwSetTgt")
 endfun
 
 " =====================================================================
@@ -8615,83 +8615,83 @@ fun! s:NetrwSplit(mode)
   call s:SaveWinVars()
 
   if a:mode == 0
-   " remote and o
-   let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
-   if winsz == 0|let winsz= ""|endif
-   exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
-   let s:didsplit= 1
-   NetrwKeepj call s:RestoreWinVars()
-   NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
-   unlet s:didsplit
+    " remote and o
+    let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
+    if winsz == 0|let winsz= ""|endif
+    exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
+    let s:didsplit= 1
+    NetrwKeepj call s:RestoreWinVars()
+    NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+    unlet s:didsplit
 
   elseif a:mode == 1
-   " remote and t
-   let newdir  = s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)
-   tabnew
-   let s:didsplit= 1
-   NetrwKeepj call s:RestoreWinVars()
-   NetrwKeepj call s:NetrwBrowse(0,newdir)
-   unlet s:didsplit
+    " remote and t
+    let newdir  = s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)
+    tabnew
+    let s:didsplit= 1
+    NetrwKeepj call s:RestoreWinVars()
+    NetrwKeepj call s:NetrwBrowse(0,newdir)
+    unlet s:didsplit
 
   elseif a:mode == 2
-   " remote and v
-   let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
-   if winsz == 0|let winsz= ""|endif
-   exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v"
-   let s:didsplit= 1
-   NetrwKeepj call s:RestoreWinVars()
-   NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
-   unlet s:didsplit
+    " remote and v
+    let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
+    if winsz == 0|let winsz= ""|endif
+    exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v"
+    let s:didsplit= 1
+    NetrwKeepj call s:RestoreWinVars()
+    NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+    unlet s:didsplit
 
   elseif a:mode == 3
-   " local and o
-   let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
-   if winsz == 0|let winsz= ""|endif
-   exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
-   let s:didsplit= 1
-   NetrwKeepj call s:RestoreWinVars()
-   NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
-   unlet s:didsplit
+    " local and o
+    let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
+    if winsz == 0|let winsz= ""|endif
+    exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
+    let s:didsplit= 1
+    NetrwKeepj call s:RestoreWinVars()
+    NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
+    unlet s:didsplit
 
   elseif a:mode == 4
-   " local and t
-   let cursorword  = s:NetrwGetWord()
-   let eikeep      = &ei
-   let netrw_winnr = winnr()
-   let netrw_line  = line(".")
-   let netrw_col   = virtcol(".")
-   NetrwKeepj norm! H0
-   let netrw_hline = line(".")
-   setl ei=all
-   exe "NetrwKeepj norm! ".netrw_hline."G0z\"
-   exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\"
-   let &ei          = eikeep
-   let netrw_curdir = s:NetrwTreeDir(0)
-   tabnew
-   let b:netrw_curdir = netrw_curdir
-   let s:didsplit     = 1
-   NetrwKeepj call s:RestoreWinVars()
-   NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,cursorword,0))
-   if &ft == "netrw"
+    " local and t
+    let cursorword  = s:NetrwGetWord()
+    let eikeep      = &ei
+    let netrw_winnr = winnr()
+    let netrw_line  = line(".")
+    let netrw_col   = virtcol(".")
+    NetrwKeepj norm! H0
+    let netrw_hline = line(".")
     setl ei=all
     exe "NetrwKeepj norm! ".netrw_hline."G0z\"
     exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\"
-    let &ei= eikeep
-   endif
-   unlet s:didsplit
+    let &ei          = eikeep
+    let netrw_curdir = s:NetrwTreeDir(0)
+    tabnew
+    let b:netrw_curdir = netrw_curdir
+    let s:didsplit     = 1
+    NetrwKeepj call s:RestoreWinVars()
+    NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,cursorword,0))
+    if &ft == "netrw"
+      setl ei=all
+      exe "NetrwKeepj norm! ".netrw_hline."G0z\"
+      exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\"
+      let &ei= eikeep
+    endif
+    unlet s:didsplit
 
   elseif a:mode == 5
-   " local and v
-   let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
-   if winsz == 0|let winsz= ""|endif
-   exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v"
-   let s:didsplit= 1
-   NetrwKeepj call s:RestoreWinVars()
-   NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
-   unlet s:didsplit
+    " local and v
+    let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
+    if winsz == 0|let winsz= ""|endif
+    exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v"
+    let s:didsplit= 1
+    NetrwKeepj call s:RestoreWinVars()
+    NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
+    unlet s:didsplit
 
   else
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"(NetrwSplit) unsupported mode=".a:mode,45)
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"(NetrwSplit) unsupported mode=".a:mode,45)
   endif
 
   let @@= ykeep
@@ -8701,63 +8701,63 @@ endfun
 " s:NetrwTgtMenu: {{{2
 fun! s:NetrwTgtMenu()
   if !exists("s:netrw_menucnt")
-   return
+    return
   endif
-"  call Dfunc("s:NetrwTgtMenu()")
+  "  call Dfunc("s:NetrwTgtMenu()")
 
   " the following test assures that gvim is running, has menus available, and has menus enabled.
   if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
-   if exists("g:NetrwTopLvlMenu")
-"    call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand(""))
-    exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Targets'
-   endif
-   if !exists("s:netrw_initbookhist")
-    call s:NetrwBookHistRead()
-   endif
-
-   " try to cull duplicate entries
-   let tgtdict={}
-
-   " target bookmarked places
-   if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0
-"    call Decho("installing bookmarks as easy targets",'~'.expand(""))
-    let cnt= 1
-    for bmd in g:netrw_bookmarklist
-     if has_key(tgtdict,bmd)
-      let cnt= cnt + 1
-      continue
-     endif
-     let tgtdict[bmd]= cnt
-     let ebmd= escape(bmd,g:netrw_menu_escape)
-     " show bookmarks for goto menu
-"     call Decho("menu: Targets: ".bmd,'~'.expand(""))
-     exe 'sil! menu  '.g:NetrwMenuPriority.".19.1.".cnt." ".g:NetrwTopLvlMenu.'Targets.'.ebmd."	:call netrw#MakeTgt('".bmd."')\"
-     let cnt= cnt + 1
-    endfor
-   endif
-
-   " target directory browsing history
-   if exists("g:netrw_dirhistmax") && g:netrw_dirhistmax > 0
-"    call Decho("installing history as easy targets (histmax=".g:netrw_dirhistmax.")",'~'.expand(""))
-    let histcnt = 1
-    while histcnt <= g:netrw_dirhistmax
-     let priority = g:netrw_dirhistcnt + histcnt
-     if exists("g:netrw_dirhist_{histcnt}")
-      let histentry  = g:netrw_dirhist_{histcnt}
-      if has_key(tgtdict,histentry)
-       let histcnt = histcnt + 1
-       continue
-      endif
-      let tgtdict[histentry] = histcnt
-      let ehistentry         = escape(histentry,g:netrw_menu_escape)
-"      call Decho("menu: Targets: ".histentry,'~'.expand(""))
-      exe 'sil! menu  '.g:NetrwMenuPriority.".19.2.".priority." ".g:NetrwTopLvlMenu.'Targets.'.ehistentry."	:call netrw#MakeTgt('".histentry."')\"
-     endif
-     let histcnt = histcnt + 1
-    endwhile
-   endif
+    if exists("g:NetrwTopLvlMenu")
+      "    call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand(""))
+      exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Targets'
+    endif
+    if !exists("s:netrw_initbookhist")
+      call s:NetrwBookHistRead()
+    endif
+
+    " try to cull duplicate entries
+    let tgtdict={}
+
+    " target bookmarked places
+    if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0
+      "    call Decho("installing bookmarks as easy targets",'~'.expand(""))
+      let cnt= 1
+      for bmd in g:netrw_bookmarklist
+        if has_key(tgtdict,bmd)
+          let cnt= cnt + 1
+          continue
+        endif
+        let tgtdict[bmd]= cnt
+        let ebmd= escape(bmd,g:netrw_menu_escape)
+        " show bookmarks for goto menu
+        "     call Decho("menu: Targets: ".bmd,'~'.expand(""))
+        exe 'sil! menu  '.g:NetrwMenuPriority.".19.1.".cnt." ".g:NetrwTopLvlMenu.'Targets.'.ebmd." :call netrw#MakeTgt('".bmd."')\"
+        let cnt= cnt + 1
+      endfor
+    endif
+
+    " target directory browsing history
+    if exists("g:netrw_dirhistmax") && g:netrw_dirhistmax > 0
+      "    call Decho("installing history as easy targets (histmax=".g:netrw_dirhistmax.")",'~'.expand(""))
+      let histcnt = 1
+      while histcnt <= g:netrw_dirhistmax
+        let priority = g:netrw_dirhistcnt + histcnt
+        if exists("g:netrw_dirhist_{histcnt}")
+          let histentry  = g:netrw_dirhist_{histcnt}
+          if has_key(tgtdict,histentry)
+            let histcnt = histcnt + 1
+            continue
+          endif
+          let tgtdict[histentry] = histcnt
+          let ehistentry         = escape(histentry,g:netrw_menu_escape)
+          "      call Decho("menu: Targets: ".histentry,'~'.expand(""))
+          exe 'sil! menu  '.g:NetrwMenuPriority.".19.2.".priority." ".g:NetrwTopLvlMenu.'Targets.'.ehistentry."     :call netrw#MakeTgt('".histentry."')\"
+        endif
+        let histcnt = histcnt + 1
+      endwhile
+    endif
   endif
-"  call Dret("s:NetrwTgtMenu")
+  "  call Dret("s:NetrwTgtMenu")
 endfun
 
 " ---------------------------------------------------------------------
@@ -8766,44 +8766,44 @@ endfun
 fun! s:NetrwTreeDir(islocal)
 
   if exists("s:treedir") && exists("s:prevwinopen")
-   " s:NetrwPrevWinOpen opens a "previous" window -- and thus needs to and does call s:NetrwTreeDir early
-   let treedir= s:treedir
-   unlet s:treedir
-   unlet s:prevwinopen
-   return treedir
+    " s:NetrwPrevWinOpen opens a "previous" window -- and thus needs to and does call s:NetrwTreeDir early
+    let treedir= s:treedir
+    unlet s:treedir
+    unlet s:prevwinopen
+    return treedir
   endif
   if exists("s:prevwinopen")
-   unlet s:prevwinopen
+    unlet s:prevwinopen
   endif
 
   if !exists("b:netrw_curdir") || b:netrw_curdir == ""
-   let b:netrw_curdir= getcwd()
+    let b:netrw_curdir= getcwd()
   endif
   let treedir = b:netrw_curdir
   let s:treecurpos= winsaveview()
 
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
 
-   " extract tree directory if on a line specifying a subdirectory (ie. ends with "/")
-   let curline= substitute(getline('.'),"\t -->.*$",'','')
-   if curline =~ '/$'
-    let treedir= substitute(getline('.'),'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
-   elseif curline =~ '@$'
-    let potentialdir= resolve(s:NetrwTreePath(w:netrw_treetop))
-   else
-    let treedir= ""
-   endif
+    " extract tree directory if on a line specifying a subdirectory (ie. ends with "/")
+    let curline= substitute(getline('.'),"\t -->.*$",'','')
+    if curline =~ '/$'
+      let treedir= substitute(getline('.'),'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
+    elseif curline =~ '@$'
+      let potentialdir= resolve(s:NetrwTreePath(w:netrw_treetop))
+    else
+      let treedir= ""
+    endif
 
-   " detect user attempting to close treeroot
-   if curline !~ '^'.s:treedepthstring && getline('.') != '..'
-    " now force a refresh
-    sil! NetrwKeepj %d _
-    return b:netrw_curdir
-   endif
+    " detect user attempting to close treeroot
+    if curline !~ '^'.s:treedepthstring && getline('.') != '..'
+      " now force a refresh
+      sil! NetrwKeepj %d _
+      return b:netrw_curdir
+    endif
 
-   " COMBAK: a symbolic link may point anywhere -- so it will be used to start a new treetop
-"   if a:islocal && curline =~ '@$' && isdirectory(s:NetrwFile(potentialdir))
-"    let newdir          = w:netrw_treetop.'/'.potentialdir
+    " COMBAK: a symbolic link may point anywhere -- so it will be used to start a new treetop
+    "   if a:islocal && curline =~ '@$' && isdirectory(s:NetrwFile(potentialdir))
+    "    let newdir          = w:netrw_treetop.'/'.potentialdir
     if a:islocal && curline =~ '@$'
       if isdirectory(s:NetrwFile(potentialdir))
         let treedir = potentialdir
@@ -8828,18 +8828,18 @@ fun! s:NetrwTreeDisplay(dir,depth)
 
   " install ../ and shortdir
   if a:depth == ""
-   call setline(line("$")+1,'../')
+    call setline(line("$")+1,'../')
   endif
   if a:dir =~ '^\a\{3,}://'
-   if a:dir == w:netrw_treetop
-    let shortdir= a:dir
-   else
-    let shortdir= substitute(a:dir,'^.*/\([^/]\+\)/$','\1/','e')
-   endif
-   call setline(line("$")+1,a:depth.shortdir)
+    if a:dir == w:netrw_treetop
+      let shortdir= a:dir
+    else
+      let shortdir= substitute(a:dir,'^.*/\([^/]\+\)/$','\1/','e')
+    endif
+    call setline(line("$")+1,a:depth.shortdir)
   else
-   let shortdir= substitute(a:dir,'^.*/','','e')
-   call setline(line("$")+1,a:depth.shortdir.'/')
+    let shortdir= substitute(a:dir,'^.*/','','e')
+    call setline(line("$")+1,a:depth.shortdir.'/')
   endif
   " append a / to dir if its missing one
   let dir= a:dir
@@ -8849,49 +8849,49 @@ fun! s:NetrwTreeDisplay(dir,depth)
 
   " implement g:netrw_hide for tree listings (uses g:netrw_list_hide)
   if     g:netrw_hide == 1
-   " hide given patterns
-   let listhide= split(g:netrw_list_hide,',')
-   for pat in listhide
-    call filter(w:netrw_treedict[dir],'v:val !~ "'.escape(pat,'\\').'"')
-   endfor
+    " hide given patterns
+    let listhide= split(g:netrw_list_hide,',')
+    for pat in listhide
+      call filter(w:netrw_treedict[dir],'v:val !~ "'.escape(pat,'\\').'"')
+    endfor
 
   elseif g:netrw_hide == 2
-   " show given patterns (only)
-   let listhide= split(g:netrw_list_hide,',')
-   let entries=[]
-   for entry in w:netrw_treedict[dir]
-    for pat in listhide
-     if entry =~ pat
-      call add(entries,entry)
-      break
-     endif
+    " show given patterns (only)
+    let listhide= split(g:netrw_list_hide,',')
+    let entries=[]
+    for entry in w:netrw_treedict[dir]
+      for pat in listhide
+        if entry =~ pat
+          call add(entries,entry)
+          break
+        endif
+      endfor
     endfor
-   endfor
-   let w:netrw_treedict[dir]= entries
+    let w:netrw_treedict[dir]= entries
   endif
   if depth != ""
-   " always remove "." and ".." entries when there's depth
-   call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\.$"')
-   call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\./$"')
-   call filter(w:netrw_treedict[dir],'v:val !~ "\\.$"')
-   call filter(w:netrw_treedict[dir],'v:val !~ "\\./$"')
+    " always remove "." and ".." entries when there's depth
+    call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\.$"')
+    call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\./$"')
+    call filter(w:netrw_treedict[dir],'v:val !~ "\\.$"')
+    call filter(w:netrw_treedict[dir],'v:val !~ "\\./$"')
   endif
 
   for entry in w:netrw_treedict[dir]
-   if dir =~ '/$'
-    let direntry= substitute(dir.entry,'[@/]$','','e')
-   else
-    let direntry= substitute(dir.'/'.entry,'[@/]$','','e')
-   endif
-   if entry =~ '/$' && has_key(w:netrw_treedict,direntry)
-    NetrwKeepj call s:NetrwTreeDisplay(direntry,depth)
-   elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/')
-    NetrwKeepj call s:NetrwTreeDisplay(direntry.'/',depth)
-   elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@')
-    NetrwKeepj call s:NetrwTreeDisplay(direntry.'@',depth)
-   else
-    sil! NetrwKeepj call setline(line("$")+1,depth.entry)
-   endif
+    if dir =~ '/$'
+      let direntry= substitute(dir.entry,'[@/]$','','e')
+    else
+      let direntry= substitute(dir.'/'.entry,'[@/]$','','e')
+    endif
+    if entry =~ '/$' && has_key(w:netrw_treedict,direntry)
+      NetrwKeepj call s:NetrwTreeDisplay(direntry,depth)
+    elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/')
+      NetrwKeepj call s:NetrwTreeDisplay(direntry.'/',depth)
+    elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@')
+      NetrwKeepj call s:NetrwTreeDisplay(direntry.'@',depth)
+    else
+      sil! NetrwKeepj call setline(line("$")+1,depth.entry)
+    endif
   endfor
 endfun
 
@@ -8899,29 +8899,29 @@ endfun
 " s:NetrwRefreshTreeDict: updates the contents information for a tree (w:netrw_treedict) {{{2
 fun! s:NetrwRefreshTreeDict(dir)
   if !exists("w:netrw_treedict")
-   return
+    return
   endif
 
   for entry in w:netrw_treedict[a:dir]
-   let direntry= substitute(a:dir.'/'.entry,'[@/]$','','e')
+    let direntry= substitute(a:dir.'/'.entry,'[@/]$','','e')
 
-   if entry =~ '/$' && has_key(w:netrw_treedict,direntry)
-    NetrwKeepj call s:NetrwRefreshTreeDict(direntry)
-    let filelist = s:NetrwLocalListingList(direntry,0)
-    let w:netrw_treedict[direntry] = sort(filelist)
+    if entry =~ '/$' && has_key(w:netrw_treedict,direntry)
+      NetrwKeepj call s:NetrwRefreshTreeDict(direntry)
+      let filelist = s:NetrwLocalListingList(direntry,0)
+      let w:netrw_treedict[direntry] = sort(filelist)
 
-   elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/')
-    NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/')
-    let filelist = s:NetrwLocalListingList(direntry.'/',0)
-    let w:netrw_treedict[direntry] = sort(filelist)
+    elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/')
+      NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/')
+      let filelist = s:NetrwLocalListingList(direntry.'/',0)
+      let w:netrw_treedict[direntry] = sort(filelist)
 
-   elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@')
-    NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/')
-    let liststar   = s:NetrwGlob(direntry.'/','*',1)
-    let listdotstar= s:NetrwGlob(direntry.'/','.*',1)
+    elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@')
+      NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/')
+      let liststar   = s:NetrwGlob(direntry.'/','*',1)
+      let listdotstar= s:NetrwGlob(direntry.'/','.*',1)
 
-   else
-   endif
+    else
+    endif
   endfor
 endfun
 
@@ -8931,51 +8931,51 @@ endfun
 fun! s:NetrwTreeListing(dirname)
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
 
-   " update the treetop
-   if !exists("w:netrw_treetop")
-    let w:netrw_treetop= a:dirname
-    let s:netrw_treetop= w:netrw_treetop
-   " use \V in case the directory contains specials chars like '$' or '~'
-   elseif (w:netrw_treetop =~ ('^'.'\V'.a:dirname) && s:Strlen(a:dirname) < s:Strlen(w:netrw_treetop))
-    \ || a:dirname !~ ('^'.'\V'.w:netrw_treetop)
-    let w:netrw_treetop= a:dirname
-    let s:netrw_treetop= w:netrw_treetop
-   endif
-   if exists("w:netrw_treetop")
-    let s:netrw_treetop= w:netrw_treetop
-   else
-    let w:netrw_treetop= getcwd()
-    let s:netrw_treetop= w:netrw_treetop
-   endif
-
-   if !exists("w:netrw_treedict")
-    " insure that we have a treedict, albeit empty
-    let w:netrw_treedict= {}
-   endif
-
-   " update the dictionary for the current directory
-   exe "sil! NetrwKeepj keepp ".w:netrw_bannercnt.',$g@^\.\.\=/$@d _'
-   let w:netrw_treedict[a:dirname]= getline(w:netrw_bannercnt,line("$"))
-   exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _"
-
-   " if past banner, record word
-   if exists("w:netrw_bannercnt") && line(".") > w:netrw_bannercnt
-    let fname= expand("")
-   else
-    let fname= ""
-   endif
-
-   " display from treetop on down
-   NetrwKeepj call s:NetrwTreeDisplay(w:netrw_treetop,"")
-
-   " remove any blank line remaining as line#1 (happens in treelisting mode with banner suppressed)
-   while getline(1) =~ '^\s*$' && byte2line(1) > 0
-    1d
-   endwhile
-
-   exe "setl ".g:netrw_bufsettings
-
-   return
+    " update the treetop
+    if !exists("w:netrw_treetop")
+      let w:netrw_treetop= a:dirname
+      let s:netrw_treetop= w:netrw_treetop
+      " use \V in case the directory contains specials chars like '$' or '~'
+    elseif (w:netrw_treetop =~ ('^'.'\V'.a:dirname) && s:Strlen(a:dirname) < s:Strlen(w:netrw_treetop))
+          \ || a:dirname !~ ('^'.'\V'.w:netrw_treetop)
+      let w:netrw_treetop= a:dirname
+      let s:netrw_treetop= w:netrw_treetop
+    endif
+    if exists("w:netrw_treetop")
+      let s:netrw_treetop= w:netrw_treetop
+    else
+      let w:netrw_treetop= getcwd()
+      let s:netrw_treetop= w:netrw_treetop
+    endif
+
+    if !exists("w:netrw_treedict")
+      " insure that we have a treedict, albeit empty
+      let w:netrw_treedict= {}
+    endif
+
+    " update the dictionary for the current directory
+    exe "sil! NetrwKeepj keepp ".w:netrw_bannercnt.',$g@^\.\.\=/$@d _'
+    let w:netrw_treedict[a:dirname]= getline(w:netrw_bannercnt,line("$"))
+    exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _"
+
+    " if past banner, record word
+    if exists("w:netrw_bannercnt") && line(".") > w:netrw_bannercnt
+      let fname= expand("")
+    else
+      let fname= ""
+    endif
+
+    " display from treetop on down
+    NetrwKeepj call s:NetrwTreeDisplay(w:netrw_treetop,"")
+
+    " remove any blank line remaining as line#1 (happens in treelisting mode with banner suppressed)
+    while getline(1) =~ '^\s*$' && byte2line(1) > 0
+      1d
+    endwhile
+
+    exe "setl ".g:netrw_bufsettings
+
+    return
   endif
 endfun
 
@@ -8985,58 +8985,58 @@ endfun
 "                  user of the function ( netrw#SetTreetop() )
 "                  wipes that out prior to calling this function
 fun! s:NetrwTreePath(treetop)
-"  call Dfunc("s:NetrwTreePath(treetop<".a:treetop.">) line#".line(".")."<".getline(".").">")
+  "  call Dfunc("s:NetrwTreePath(treetop<".a:treetop.">) line#".line(".")."<".getline(".").">")
   if line(".") < w:netrw_bannercnt + 2
-   let treedir= a:treetop
-   if treedir !~ '/$'
-    let treedir= treedir.'/'
-   endif
-"   call Dret("s:NetrwTreePath ".treedir." : line#".line(".")." ≤ ".(w:netrw_bannercnt+2))
-   return treedir
+    let treedir= a:treetop
+    if treedir !~ '/$'
+      let treedir= treedir.'/'
+    endif
+    "   call Dret("s:NetrwTreePath ".treedir." : line#".line(".")." ≤ ".(w:netrw_bannercnt+2))
+    return treedir
   endif
 
   let svpos = winsaveview()
-"  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
+  "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
   let depth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e')
-"  call Decho("depth<".depth."> 1st subst",'~'.expand(""))
+  "  call Decho("depth<".depth."> 1st subst",'~'.expand(""))
   let depth = substitute(depth,'^'.s:treedepthstring,'','')
-"  call Decho("depth<".depth."> 2nd subst (first depth removed)",'~'.expand(""))
+  "  call Decho("depth<".depth."> 2nd subst (first depth removed)",'~'.expand(""))
   let curline= getline('.')
-"  call Decho("curline<".curline.'>','~'.expand(""))
+  "  call Decho("curline<".curline.'>','~'.expand(""))
   if curline =~ '/$'
-"   call Decho("extract tree directory from current line",'~'.expand(""))
-   let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
-"   call Decho("treedir<".treedir.">",'~'.expand(""))
+    "   call Decho("extract tree directory from current line",'~'.expand(""))
+    let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
+    "   call Decho("treedir<".treedir.">",'~'.expand(""))
   elseif curline =~ '@\s\+-->'
-"   call Decho("extract tree directory using symbolic link",'~'.expand(""))
-   let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
-   let treedir= substitute(treedir,'@\s\+-->.*$','','e')
-"   call Decho("treedir<".treedir.">",'~'.expand(""))
+    "   call Decho("extract tree directory using symbolic link",'~'.expand(""))
+    let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
+    let treedir= substitute(treedir,'@\s\+-->.*$','','e')
+    "   call Decho("treedir<".treedir.">",'~'.expand(""))
   else
-"   call Decho("do not extract tree directory from current line and set treedir to empty",'~'.expand(""))
-   let treedir= ""
+    "   call Decho("do not extract tree directory from current line and set treedir to empty",'~'.expand(""))
+    let treedir= ""
   endif
   " construct treedir by searching backwards at correct depth
-"  call Decho("construct treedir by searching backwards for correct depth",'~'.expand(""))
-"  call Decho("initial      treedir<".treedir."> depth<".depth.">",'~'.expand(""))
+  "  call Decho("construct treedir by searching backwards for correct depth",'~'.expand(""))
+  "  call Decho("initial      treedir<".treedir."> depth<".depth.">",'~'.expand(""))
   while depth != "" && search('^'.depth.'[^'.s:treedepthstring.'].\{-}/$','bW')
-   let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e')
-   let treedir= dirname.treedir
-   let depth  = substitute(depth,'^'.s:treedepthstring,'','')
-"   call Decho("constructing treedir<".treedir.">: dirname<".dirname."> while depth<".depth.">",'~'.expand(""))
+    let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e')
+    let treedir= dirname.treedir
+    let depth  = substitute(depth,'^'.s:treedepthstring,'','')
+    "   call Decho("constructing treedir<".treedir.">: dirname<".dirname."> while depth<".depth.">",'~'.expand(""))
   endwhile
-"  call Decho("treedir#1<".treedir.">",'~'.expand(""))
+  "  call Decho("treedir#1<".treedir.">",'~'.expand(""))
   if a:treetop =~ '/$'
-   let treedir= a:treetop.treedir
+    let treedir= a:treetop.treedir
   else
-   let treedir= a:treetop.'/'.treedir
+    let treedir= a:treetop.'/'.treedir
   endif
-"  call Decho("treedir#2<".treedir.">",'~'.expand(""))
+  "  call Decho("treedir#2<".treedir.">",'~'.expand(""))
   let treedir= substitute(treedir,'//$','/','')
-"  call Decho("treedir#3<".treedir.">",'~'.expand(""))
-"  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))"
+  "  call Decho("treedir#3<".treedir.">",'~'.expand(""))
+  "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))"
   call winrestview(svpos)
-"  call Dret("s:NetrwTreePath <".treedir.">")
+  "  call Dret("s:NetrwTreePath <".treedir.">")
   return treedir
 endfun
 
@@ -9045,119 +9045,119 @@ endfun
 fun! s:NetrwWideListing()
 
   if w:netrw_liststyle == s:WIDELIST
-"   call Dfunc("NetrwWideListing() w:netrw_liststyle=".w:netrw_liststyle.' fo='.&fo.' l:fo='.&l:fo)
-   " look for longest filename (cpf=characters per filename)
-   " cpf: characters per filename
-   " fpl: filenames per line
-   " fpc: filenames per column
-   setl ma noro
-   let dict={}
-   " save the unnamed register and register 0-9 and a
-   let dict.a=[getreg('a'), getregtype('a')]
-   for i in range(0, 9)
-     let dict[i] = [getreg(i), getregtype(i)]
-   endfor
-   let dict.unnamed = [getreg(''), getregtype('')]
-"   call Decho("setl ma noro",'~'.expand(""))
-   let b:netrw_cpf= 0
-   if line("$") >= w:netrw_bannercnt
-    " determine the maximum filename size; use that to set cpf
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
+    "   call Dfunc("NetrwWideListing() w:netrw_liststyle=".w:netrw_liststyle.' fo='.&fo.' l:fo='.&l:fo)
+    " look for longest filename (cpf=characters per filename)
+    " cpf: characters per filename
+    " fpl: filenames per line
+    " fpc: filenames per column
+    setl ma noro
+    let dict={}
+    " save the unnamed register and register 0-9 and a
+    let dict.a=[getreg('a'), getregtype('a')]
+    for i in range(0, 9)
+      let dict[i] = [getreg(i), getregtype(i)]
+    endfor
+    let dict.unnamed = [getreg(''), getregtype('')]
+    "   call Decho("setl ma noro",'~'.expand(""))
+    let b:netrw_cpf= 0
+    if line("$") >= w:netrw_bannercnt
+      " determine the maximum filename size; use that to set cpf
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
     NetrwKeepj call histdel("/",-1)
-   else
+  else
     " restore stored registers
     call s:RestoreRegister(dict)
-"    call Dret("NetrwWideListing")
+    "    call Dret("NetrwWideListing")
     return
-   endif
-   " allow for two spaces to separate columns
-   let b:netrw_cpf= b:netrw_cpf + 2
-"   call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand(""))
-
-   " determine qty files per line (fpl)
-   let w:netrw_fpl= winwidth(0)/b:netrw_cpf
-   if w:netrw_fpl <= 0
+  endif
+  " allow for two spaces to separate columns
+  let b:netrw_cpf= b:netrw_cpf + 2
+  "   call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand(""))
+
+  " determine qty files per line (fpl)
+  let w:netrw_fpl= winwidth(0)/b:netrw_cpf
+  if w:netrw_fpl <= 0
     let w:netrw_fpl= 1
-   endif
-"   call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl,'~'.expand(""))
-
-   " make wide display
-   "   fpc: files per column of wide listing
-   exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'S",submatch(0)),"\\")/'
-   NetrwKeepj call histdel("/",-1)
-   let fpc         = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl
-   let newcolstart = w:netrw_bannercnt + fpc
-   let newcolend   = newcolstart + fpc - 1
-"   call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand(""))
-   if !has('nvim') && has("clipboard") && g:netrw_clipboard
-"    call Decho("(s:NetrwWideListing) save @* and @+",'~'.expand(""))
+  endif
+  "   call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl,'~'.expand(""))
+
+  " make wide display
+  "   fpc: files per column of wide listing
+  exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'S",submatch(0)),"\\")/'
+  NetrwKeepj call histdel("/",-1)
+  let fpc         = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl
+  let newcolstart = w:netrw_bannercnt + fpc
+  let newcolend   = newcolstart + fpc - 1
+  "   call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand(""))
+  if !has('nvim') && has("clipboard") && g:netrw_clipboard
+    "    call Decho("(s:NetrwWideListing) save @* and @+",'~'.expand(""))
     sil! let keepregstar = @*
     sil! let keepregplus = @+
-   endif
-   while line("$") >= newcolstart
+  endif
+  while line("$") >= newcolstart
     if newcolend > line("$") | let newcolend= line("$") | endif
     let newcolqty= newcolend - newcolstart
     exe newcolstart
     " COMBAK: both of the visual-mode using lines below are problematic vis-a-vis @*
     if newcolqty == 0
-     exe "sil! NetrwKeepj norm! 0\$h\"ax".w:netrw_bannercnt."G$\"ap"
+      exe "sil! NetrwKeepj norm! 0\$h\"ax".w:netrw_bannercnt."G$\"ap"
     else
-     exe "sil! NetrwKeepj norm! 0\".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap'
+      exe "sil! NetrwKeepj norm! 0\".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap'
     endif
     exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _'
     exe 'sil! NetrwKeepj '.w:netrw_bannercnt
-   endwhile
-   if !has('nvim') && has("clipboard")
-"    call Decho("(s:NetrwWideListing) restore @* and @+",'~'.expand(""))
+  endwhile
+  if !has('nvim') && has("clipboard")
+    "    call Decho("(s:NetrwWideListing) restore @* and @+",'~'.expand(""))
     if @* != keepregstar | sil! let @* = keepregstar | endif
     if @+ != keepregplus | sil! let @+ = keepregplus | endif
-   endif
-   exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e'
-   NetrwKeepj call histdel("/",-1)
-   exe 'nno   w	:call search(''^.\\|\s\s\zs\S'',''W'')'."\"
-   exe 'nno   b	:call search(''^.\\|\s\s\zs\S'',''bW'')'."\"
-"   call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand(""))
-   exe "setl ".g:netrw_bufsettings
-   call s:RestoreRegister(dict)
-"   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-"   call Dret("NetrwWideListing")
-   return
-  else
-   if hasmapto("w","n")
+  endif
+  exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e'
+  NetrwKeepj call histdel("/",-1)
+  exe 'nno   w :call search(''^.\\|\s\s\zs\S'',''W'')'."\"
+  exe 'nno   b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\"
+  "   call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand(""))
+  exe "setl ".g:netrw_bufsettings
+  call s:RestoreRegister(dict)
+  "   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+  "   call Dret("NetrwWideListing")
+  return
+else
+  if hasmapto("w","n")
     sil! nunmap  w
-   endif
-   if hasmapto("b","n")
+  endif
+  if hasmapto("b","n")
     sil! nunmap  b
-   endif
   endif
+endif
 endfun
 
 " ---------------------------------------------------------------------
 " s:PerformListing: {{{2
 fun! s:PerformListing(islocal)
-"  call Dfunc("s:PerformListing(islocal=".a:islocal.")")
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
-"  call Decho("settings: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (enter)"." ei<".&ei.">",'~'.expand(""))
+  "  call Dfunc("s:PerformListing(islocal=".a:islocal.")")
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
+  "  call Decho("settings: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (enter)"." ei<".&ei.">",'~'.expand(""))
   sil! NetrwKeepj %d _
-"  call DechoBuf(bufnr("%"))
+  "  call DechoBuf(bufnr("%"))
 
   " set up syntax highlighting {{{3
-"  call Decho("--set up syntax highlighting (ie. setl ft=netrw)",'~'.expand(""))
+  "  call Decho("--set up syntax highlighting (ie. setl ft=netrw)",'~'.expand(""))
   sil! setl ft=netrw
 
   NetrwKeepj call s:NetrwOptionsSafe(a:islocal)
   setl noro ma
-"  call Decho("setl noro ma bh=".&bh,'~'.expand(""))
+  "  call Decho("setl noro ma bh=".&bh,'~'.expand(""))
 
-"  if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1	" Decho
-"   call Decho("Processing your browsing request...",'~'.expand(""))
-"  endif								" Decho
+  "  if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1       " Decho
+  "   call Decho("Processing your browsing request...",'~'.expand(""))
+  "  endif                                                                " Decho
 
-"  call Decho('w:netrw_liststyle='.(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand(""))
+  "  call Decho('w:netrw_liststyle='.(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand(""))
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-   " force a refresh for tree listings
-"   call Decho("force refresh for treelisting: clear buffer<".expand("%")."> with :%d",'~'.expand(""))
-   sil! NetrwKeepj %d _
+    " force a refresh for tree listings
+    "   call Decho("force refresh for treelisting: clear buffer<".expand("%")."> with :%d",'~'.expand(""))
+    sil! NetrwKeepj %d _
   endif
 
   " save current directory on directory history list
@@ -9165,305 +9165,305 @@ fun! s:PerformListing(islocal)
 
   " Set up the banner {{{3
   if g:netrw_banner
-"   call Decho("--set up banner",'~'.expand(""))
-   NetrwKeepj call setline(1,'" ============================================================================')
-   if exists("g:netrw_pchk")
-    " this undocumented option allows pchk to run with different versions of netrw without causing spurious
-    " failure detections.
-    NetrwKeepj call setline(2,'" Netrw Directory Listing')
-   else
-    NetrwKeepj call setline(2,'" Netrw Directory Listing                                        (netrw '.g:loaded_netrw.')')
-   endif
-   if exists("g:netrw_pchk")
-    let curdir= substitute(b:netrw_curdir,expand("$HOME"),'~','')
-   else
-    let curdir= b:netrw_curdir
-   endif
-   if exists("g:netrw_bannerbackslash") && g:netrw_bannerbackslash
-    NetrwKeepj call setline(3,'"   '.substitute(curdir,'/','\\','g'))
-   else
-    NetrwKeepj call setline(3,'"   '.curdir)
-   endif
-   let w:netrw_bannercnt= 3
-   NetrwKeepj exe "sil! NetrwKeepj ".w:netrw_bannercnt
+    "   call Decho("--set up banner",'~'.expand(""))
+    NetrwKeepj call setline(1,'" ============================================================================')
+    if exists("g:netrw_pchk")
+      " this undocumented option allows pchk to run with different versions of netrw without causing spurious
+      " failure detections.
+      NetrwKeepj call setline(2,'" Netrw Directory Listing')
+    else
+      NetrwKeepj call setline(2,'" Netrw Directory Listing                                        (netrw '.g:loaded_netrw.')')
+    endif
+    if exists("g:netrw_pchk")
+      let curdir= substitute(b:netrw_curdir,expand("$HOME"),'~','')
+    else
+      let curdir= b:netrw_curdir
+    endif
+    if exists("g:netrw_bannerbackslash") && g:netrw_bannerbackslash
+      NetrwKeepj call setline(3,'"   '.substitute(curdir,'/','\\','g'))
+    else
+      NetrwKeepj call setline(3,'"   '.curdir)
+    endif
+    let w:netrw_bannercnt= 3
+    NetrwKeepj exe "sil! NetrwKeepj ".w:netrw_bannercnt
   else
-"   call Decho("--no banner",'~'.expand(""))
-   NetrwKeepj 1
-   let w:netrw_bannercnt= 1
+    "   call Decho("--no banner",'~'.expand(""))
+    NetrwKeepj 1
+    let w:netrw_bannercnt= 1
   endif
-"  call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." win#".winnr(),'~'.expand(""))
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
+  "  call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." win#".winnr(),'~'.expand(""))
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
 
   " construct sortby string: [name|time|size|exten] [reversed]
   let sortby= g:netrw_sort_by
   if g:netrw_sort_direction =~# "^r"
-   let sortby= sortby." reversed"
+    let sortby= sortby." reversed"
   endif
 
   " Sorted by... {{{3
   if g:netrw_banner
-"   call Decho("--handle specified sorting: g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand(""))
-   if g:netrw_sort_by =~# "^n"
-"   call Decho("directories will be sorted by name",'~'.expand(""))
-    " sorted by name (also includes the sorting sequence in the banner)
-    NetrwKeepj put ='\"   Sorted by      '.sortby
-    NetrwKeepj put ='\"   Sort sequence: '.g:netrw_sort_sequence
-    let w:netrw_bannercnt= w:netrw_bannercnt + 2
-   else
-"   call Decho("directories will be sorted by size or time",'~'.expand(""))
-    " sorted by time, size, exten
-    NetrwKeepj put ='\"   Sorted by '.sortby
-    let w:netrw_bannercnt= w:netrw_bannercnt + 1
-   endif
-   exe "sil! NetrwKeepj ".w:netrw_bannercnt
-"  else " Decho
-"   call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+    "   call Decho("--handle specified sorting: g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand(""))
+    if g:netrw_sort_by =~# "^n"
+      "   call Decho("directories will be sorted by name",'~'.expand(""))
+      " sorted by name (also includes the sorting sequence in the banner)
+      NetrwKeepj put ='\"   Sorted by      '.sortby
+      NetrwKeepj put ='\"   Sort sequence: '.g:netrw_sort_sequence
+      let w:netrw_bannercnt= w:netrw_bannercnt + 2
+    else
+      "   call Decho("directories will be sorted by size or time",'~'.expand(""))
+      " sorted by time, size, exten
+      NetrwKeepj put ='\"   Sorted by '.sortby
+      let w:netrw_bannercnt= w:netrw_bannercnt + 1
+    endif
+    exe "sil! NetrwKeepj ".w:netrw_bannercnt
+    "  else " Decho
+    "   call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
   endif
 
   " show copy/move target, if any {{{3
   if g:netrw_banner
-   if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal")
-"    call Decho("--show copy/move target<".s:netrwmftgt.">",'~'.expand(""))
-    NetrwKeepj put =''
-    if s:netrwmftgt_islocal
-     sil! NetrwKeepj call setline(line("."),'"   Copy/Move Tgt: '.s:netrwmftgt.' (local)')
+    if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal")
+      "    call Decho("--show copy/move target<".s:netrwmftgt.">",'~'.expand(""))
+      NetrwKeepj put =''
+      if s:netrwmftgt_islocal
+        sil! NetrwKeepj call setline(line("."),'"   Copy/Move Tgt: '.s:netrwmftgt.' (local)')
+      else
+        sil! NetrwKeepj call setline(line("."),'"   Copy/Move Tgt: '.s:netrwmftgt.' (remote)')
+      endif
+      let w:netrw_bannercnt= w:netrw_bannercnt + 1
     else
-     sil! NetrwKeepj call setline(line("."),'"   Copy/Move Tgt: '.s:netrwmftgt.' (remote)')
+      "    call Decho("s:netrwmftgt does not exist, don't make Copy/Move Tgt",'~'.expand(""))
     endif
-    let w:netrw_bannercnt= w:netrw_bannercnt + 1
-   else
-"    call Decho("s:netrwmftgt does not exist, don't make Copy/Move Tgt",'~'.expand(""))
-   endif
-   exe "sil! NetrwKeepj ".w:netrw_bannercnt
+    exe "sil! NetrwKeepj ".w:netrw_bannercnt
   endif
 
   " Hiding...  -or-  Showing... {{{3
   if g:netrw_banner
-"   call Decho("--handle hiding/showing in banner (g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">)",'~'.expand(""))
-   if g:netrw_list_hide != "" && g:netrw_hide
-    if g:netrw_hide == 1
-     NetrwKeepj put ='\"   Hiding:        '.g:netrw_list_hide
-    else
-     NetrwKeepj put ='\"   Showing:       '.g:netrw_list_hide
+    "   call Decho("--handle hiding/showing in banner (g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">)",'~'.expand(""))
+    if g:netrw_list_hide != "" && g:netrw_hide
+      if g:netrw_hide == 1
+        NetrwKeepj put ='\"   Hiding:        '.g:netrw_list_hide
+      else
+        NetrwKeepj put ='\"   Showing:       '.g:netrw_list_hide
+      endif
+      let w:netrw_bannercnt= w:netrw_bannercnt + 1
     endif
-    let w:netrw_bannercnt= w:netrw_bannercnt + 1
-   endif
-   exe "NetrwKeepj ".w:netrw_bannercnt
+    exe "NetrwKeepj ".w:netrw_bannercnt
 
-"   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-   let quickhelp   = g:netrw_quickhelp%len(s:QuickHelp)
-"   call Decho("quickhelp   =".quickhelp,'~'.expand(""))
-   NetrwKeepj put ='\"   Quick Help: :help  '.s:QuickHelp[quickhelp]
-"   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-   NetrwKeepj put ='\" =============================================================================='
-   let w:netrw_bannercnt= w:netrw_bannercnt + 2
-"  else " Decho
-"   call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+    "   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    let quickhelp   = g:netrw_quickhelp%len(s:QuickHelp)
+    "   call Decho("quickhelp   =".quickhelp,'~'.expand(""))
+    NetrwKeepj put ='\"   Quick Help: :help  '.s:QuickHelp[quickhelp]
+    "   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    NetrwKeepj put ='\" =============================================================================='
+    let w:netrw_bannercnt= w:netrw_bannercnt + 2
+    "  else " Decho
+    "   call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
   endif
 
   " bannercnt should index the line just after the banner
   if g:netrw_banner
-   let w:netrw_bannercnt= w:netrw_bannercnt + 1
-   exe "sil! NetrwKeepj ".w:netrw_bannercnt
-"   call Decho("--w:netrw_bannercnt=".w:netrw_bannercnt." (should index line just after banner) line($)=".line("$"),'~'.expand(""))
-"  else " Decho
-"   call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+    let w:netrw_bannercnt= w:netrw_bannercnt + 1
+    exe "sil! NetrwKeepj ".w:netrw_bannercnt
+    "   call Decho("--w:netrw_bannercnt=".w:netrw_bannercnt." (should index line just after banner) line($)=".line("$"),'~'.expand(""))
+    "  else " Decho
+    "   call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
   endif
 
   " get list of files
-"  call Decho("--Get list of files - islocal=".a:islocal,'~'.expand(""))
+  "  call Decho("--Get list of files - islocal=".a:islocal,'~'.expand(""))
   if a:islocal
-   NetrwKeepj call s:LocalListing()
+    NetrwKeepj call s:LocalListing()
   else " remote
-   NetrwKeepj let badresult= s:NetrwRemoteListing()
-   if badresult
-"    call Decho("w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a')." win#".winnr()." buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
-"    call Dret("s:PerformListing : error detected by NetrwRemoteListing")
-    return
-   endif
+    NetrwKeepj let badresult= s:NetrwRemoteListing()
+    if badresult
+      "    call Decho("w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a')." win#".winnr()." buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
+      "    call Dret("s:PerformListing : error detected by NetrwRemoteListing")
+      return
+    endif
   endif
 
   " manipulate the directory listing (hide, sort) {{{3
   if !exists("w:netrw_bannercnt")
-   let w:netrw_bannercnt= 0
+    let w:netrw_bannercnt= 0
   endif
-"  call Decho("--manipulate directory listing (hide, sort)",'~'.expand(""))
-"  call Decho("g:netrw_banner=".g:netrw_banner." w:netrw_bannercnt=".w:netrw_bannercnt." (banner complete)",'~'.expand(""))
-"  call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+  "  call Decho("--manipulate directory listing (hide, sort)",'~'.expand(""))
+  "  call Decho("g:netrw_banner=".g:netrw_banner." w:netrw_bannercnt=".w:netrw_bannercnt." (banner complete)",'~'.expand(""))
+  "  call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
 
   if !g:netrw_banner || line("$") >= w:netrw_bannercnt
-"   call Decho("manipulate directory listing (support hide)",'~'.expand(""))
-"   call Decho("g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">",'~'.expand(""))
-   if g:netrw_hide && g:netrw_list_hide != ""
-    NetrwKeepj call s:NetrwListHide()
-   endif
-   if !g:netrw_banner || line("$") >= w:netrw_bannercnt
-"    call Decho("manipulate directory listing (sort) : g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand(""))
+    "   call Decho("manipulate directory listing (support hide)",'~'.expand(""))
+    "   call Decho("g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">",'~'.expand(""))
+    if g:netrw_hide && g:netrw_list_hide != ""
+      NetrwKeepj call s:NetrwListHide()
+    endif
+    if !g:netrw_banner || line("$") >= w:netrw_bannercnt
+      "    call Decho("manipulate directory listing (sort) : g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand(""))
+
+      if g:netrw_sort_by =~# "^n"
+        " sort by name
+        "     call Decho("sort by name",'~'.expand(""))
+        NetrwKeepj call s:NetrwSetSort()
+
+        if !g:netrw_banner || w:netrw_bannercnt < line("$")
+          "      call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+          if g:netrw_sort_direction =~# 'n'
+            " name: sort by name of file
+            exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
+          else
+            " reverse direction sorting
+            exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
+          endif
+        endif
 
-    if g:netrw_sort_by =~# "^n"
-     " sort by name
-"     call Decho("sort by name",'~'.expand(""))
-     NetrwKeepj call s:NetrwSetSort()
-
-     if !g:netrw_banner || w:netrw_bannercnt < line("$")
-"      call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
-      if g:netrw_sort_direction =~# 'n'
-       " name: sort by name of file
-       exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
-      else
-       " reverse direction sorting
-       exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
-      endif
-     endif
-
-     " remove priority pattern prefix
-"     call Decho("remove priority pattern prefix",'~'.expand(""))
-     exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e'
-     NetrwKeepj call histdel("/",-1)
-
-    elseif g:netrw_sort_by =~# "^ext"
-     " exten: sort by extension
-     "   The histdel(...,-1) calls remove the last search from the search history
-"     call Decho("sort by extension",'~'.expand(""))
-     exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g+/+s/^/001'.g:netrw_sepchr.'/'
-     NetrwKeepj call histdel("/",-1)
-     exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+[./]+s/^/002'.g:netrw_sepchr.'/'
-     NetrwKeepj call histdel("/",-1)
-     exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+['.g:netrw_sepchr.'/]+s/^\(.*\.\)\(.\{-\}\)$/\2'.g:netrw_sepchr.'&/e'
-     NetrwKeepj call histdel("/",-1)
-     if !g:netrw_banner || w:netrw_bannercnt < line("$")
-"      call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
-      if g:netrw_sort_direction =~# 'n'
-       " normal direction sorting
-       exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
-      else
-       " reverse direction sorting
-       exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
-      endif
-     endif
-     exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^.\{-}'.g:netrw_sepchr.'//e'
-     NetrwKeepj call histdel("/",-1)
-
-    elseif a:islocal
-     if !g:netrw_banner || w:netrw_bannercnt < line("$")
-"      call Decho("g:netrw_sort_direction=".g:netrw_sort_direction,'~'.expand(""))
-      if g:netrw_sort_direction =~# 'n'
-"       call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort','~'.expand(""))
-       exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
-      else
-"       call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort!','~'.expand(""))
-       exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
+        " remove priority pattern prefix
+        "     call Decho("remove priority pattern prefix",'~'.expand(""))
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e'
+        NetrwKeepj call histdel("/",-1)
+
+      elseif g:netrw_sort_by =~# "^ext"
+        " exten: sort by extension
+        "   The histdel(...,-1) calls remove the last search from the search history
+        "     call Decho("sort by extension",'~'.expand(""))
+        exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g+/+s/^/001'.g:netrw_sepchr.'/'
+        NetrwKeepj call histdel("/",-1)
+        exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+[./]+s/^/002'.g:netrw_sepchr.'/'
+        NetrwKeepj call histdel("/",-1)
+        exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+['.g:netrw_sepchr.'/]+s/^\(.*\.\)\(.\{-\}\)$/\2'.g:netrw_sepchr.'&/e'
+        NetrwKeepj call histdel("/",-1)
+        if !g:netrw_banner || w:netrw_bannercnt < line("$")
+          "      call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+          if g:netrw_sort_direction =~# 'n'
+            " normal direction sorting
+            exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
+          else
+            " reverse direction sorting
+            exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
+          endif
+        endif
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^.\{-}'.g:netrw_sepchr.'//e'
+        NetrwKeepj call histdel("/",-1)
+
+      elseif a:islocal
+        if !g:netrw_banner || w:netrw_bannercnt < line("$")
+          "      call Decho("g:netrw_sort_direction=".g:netrw_sort_direction,'~'.expand(""))
+          if g:netrw_sort_direction =~# 'n'
+            "       call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort','~'.expand(""))
+            exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
+          else
+            "       call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort!','~'.expand(""))
+            exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
+          endif
+          "     call Decho("remove leading digits/ (sorting) information from listing",'~'.expand(""))
+          exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e'
+          NetrwKeepj call histdel("/",-1)
+        endif
       endif
-"     call Decho("remove leading digits/ (sorting) information from listing",'~'.expand(""))
-     exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e'
-     NetrwKeepj call histdel("/",-1)
-     endif
-    endif
 
-   elseif g:netrw_sort_direction =~# 'r'
-"    call Decho('(s:PerformListing) reverse the sorted listing','~'.expand(""))
-    if !g:netrw_banner || w:netrw_bannercnt < line('$')
-     exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g/^/m '.w:netrw_bannercnt
-     call histdel("/",-1)
+    elseif g:netrw_sort_direction =~# 'r'
+      "    call Decho('(s:PerformListing) reverse the sorted listing','~'.expand(""))
+      if !g:netrw_banner || w:netrw_bannercnt < line('$')
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g/^/m '.w:netrw_bannercnt
+        call histdel("/",-1)
+      endif
     endif
-   endif
   endif
-"  call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
+  "  call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand(""))
 
   " convert to wide/tree listing {{{3
-"  call Decho("--modify display if wide/tree listing style",'~'.expand(""))
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#1)",'~'.expand(""))
+  "  call Decho("--modify display if wide/tree listing style",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#1)",'~'.expand(""))
   NetrwKeepj call s:NetrwWideListing()
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#2)",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#2)",'~'.expand(""))
   NetrwKeepj call s:NetrwTreeListing(b:netrw_curdir)
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#3)",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#3)",'~'.expand(""))
 
   " resolve symbolic links if local and (thin or tree)
   if a:islocal && (w:netrw_liststyle == s:THINLIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST))
-"   call Decho("--resolve symbolic links if local and thin|tree",'~'.expand(""))
-   sil! keepp g/@$/call s:ShowLink()
+    "   call Decho("--resolve symbolic links if local and thin|tree",'~'.expand(""))
+    sil! keepp g/@$/call s:ShowLink()
   endif
 
   if exists("w:netrw_bannercnt") && (line("$") >= w:netrw_bannercnt || !g:netrw_banner)
-   " place cursor on the top-left corner of the file listing
-"   call Decho("--place cursor on top-left corner of file listing",'~'.expand(""))
-   exe 'sil! '.w:netrw_bannercnt
-   sil! NetrwKeepj norm! 0
-"   call Decho("  tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
+    " place cursor on the top-left corner of the file listing
+    "   call Decho("--place cursor on top-left corner of file listing",'~'.expand(""))
+    exe 'sil! '.w:netrw_bannercnt
+    sil! NetrwKeepj norm! 0
+    "   call Decho("  tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
   else
-"   call Decho("--did NOT place cursor on top-left corner",'~'.expand(""))
-"   call Decho("  w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'),'~'.expand(""))
-"   call Decho("  line($)=".line("$"),'~'.expand(""))
-"   call Decho("  g:netrw_banner=".(exists("g:netrw_banner")? g:netrw_banner : 'n/a'),'~'.expand(""))
+    "   call Decho("--did NOT place cursor on top-left corner",'~'.expand(""))
+    "   call Decho("  w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'),'~'.expand(""))
+    "   call Decho("  line($)=".line("$"),'~'.expand(""))
+    "   call Decho("  g:netrw_banner=".(exists("g:netrw_banner")? g:netrw_banner : 'n/a'),'~'.expand(""))
   endif
 
   " record previous current directory
   let w:netrw_prvdir= b:netrw_curdir
-"  call Decho("--record netrw_prvdir<".w:netrw_prvdir.">",'~'.expand(""))
+  "  call Decho("--record netrw_prvdir<".w:netrw_prvdir.">",'~'.expand(""))
 
   " save certain window-oriented variables into buffer-oriented variables {{{3
-"  call Decho("--save some window-oriented variables into buffer oriented variables",'~'.expand(""))
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#4)",'~'.expand(""))
+  "  call Decho("--save some window-oriented variables into buffer oriented variables",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#4)",'~'.expand(""))
   NetrwKeepj call s:SetBufWinVars()
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#5)",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#5)",'~'.expand(""))
   NetrwKeepj call s:NetrwOptionsRestore("w:")
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#6)",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#6)",'~'.expand(""))
 
   " set display to netrw display settings
-"  call Decho("--set display to netrw display settings (".g:netrw_bufsettings.")",'~'.expand(""))
+  "  call Decho("--set display to netrw display settings (".g:netrw_bufsettings.")",'~'.expand(""))
   exe "setl ".g:netrw_bufsettings
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#7)",'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#7)",'~'.expand(""))
   if g:netrw_liststyle == s:LONGLIST
-"   call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand(""))
-   exe "setl ts=".(g:netrw_maxfilenamelen+1)
+    "   call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand(""))
+    exe "setl ts=".(g:netrw_maxfilenamelen+1)
   endif
-"  call Decho("PerformListing buffer:",'~'.expand(""))
-"  call DechoBuf(bufnr("%"))
+  "  call Decho("PerformListing buffer:",'~'.expand(""))
+  "  call DechoBuf(bufnr("%"))
 
   if exists("s:treecurpos")
-"   call Decho("s:treecurpos exists; restore posn",'~'.expand(""))
-"   call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#8)",'~'.expand(""))
-"   call Decho("restoring posn to s:treecurpos<".string(s:treecurpos).">",'~'.expand(""))
-   NetrwKeepj call winrestview(s:treecurpos)
-   unlet s:treecurpos
+    "   call Decho("s:treecurpos exists; restore posn",'~'.expand(""))
+    "   call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#8)",'~'.expand(""))
+    "   call Decho("restoring posn to s:treecurpos<".string(s:treecurpos).">",'~'.expand(""))
+    NetrwKeepj call winrestview(s:treecurpos)
+    unlet s:treecurpos
   endif
 
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (return)",'~'.expand(""))
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
-"  call Dret("s:PerformListing : curpos<".string(getpos(".")).">")
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (return)",'~'.expand(""))
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
+  "  call Dret("s:PerformListing : curpos<".string(getpos(".")).">")
 endfun
 
 " ---------------------------------------------------------------------
 " s:SetupNetrwStatusLine: {{{2
 fun! s:SetupNetrwStatusLine(statline)
-"  call Dfunc("SetupNetrwStatusLine(statline<".a:statline.">)")
+  "  call Dfunc("SetupNetrwStatusLine(statline<".a:statline.">)")
 
   if !exists("s:netrw_setup_statline")
-   let s:netrw_setup_statline= 1
-"   call Decho("do first-time status line setup",'~'.expand(""))
+    let s:netrw_setup_statline= 1
+    "   call Decho("do first-time status line setup",'~'.expand(""))
 
-   if !exists("s:netrw_users_stl")
-    let s:netrw_users_stl= &stl
-   endif
-   if !exists("s:netrw_users_ls")
-    let s:netrw_users_ls= &laststatus
-   endif
-
-   " set up User9 highlighting as needed
-  let dict={}
-  let dict.a=[getreg('a'), getregtype('a')]
-   redir @a
-   try
-    hi User9
-   catch /^Vim\%((\a\{3,})\)\=:E411/
-    if &bg == "dark"
-     hi User9 ctermfg=yellow ctermbg=blue guifg=yellow guibg=blue
-    else
-     hi User9 ctermbg=yellow ctermfg=blue guibg=yellow guifg=blue
+    if !exists("s:netrw_users_stl")
+      let s:netrw_users_stl= &stl
     endif
-   endtry
-   redir END
-   call s:RestoreRegister(dict)
+    if !exists("s:netrw_users_ls")
+      let s:netrw_users_ls= &laststatus
+    endif
+
+    " set up User9 highlighting as needed
+    let dict={}
+    let dict.a=[getreg('a'), getregtype('a')]
+    redir @a
+    try
+      hi User9
+    catch /^Vim\%((\a\{3,})\)\=:E411/
+      if &bg == "dark"
+        hi User9 ctermfg=yellow ctermbg=blue guifg=yellow guibg=blue
+      else
+        hi User9 ctermbg=yellow ctermfg=blue guibg=yellow guifg=blue
+      endif
+    endtry
+    redir END
+    call s:RestoreRegister(dict)
   endif
 
   " set up status line (may use User9 highlighting)
@@ -9471,10 +9471,10 @@ fun! s:SetupNetrwStatusLine(statline)
   " make sure statusline is displayed
   let &l:stl=a:statline
   setl laststatus=2
-"  call Decho("stl=".&stl,'~'.expand(""))
+  "  call Decho("stl=".&stl,'~'.expand(""))
   redraw
 
-"  call Dret("SetupNetrwStatusLine : stl=".&stl)
+  "  call Dret("SetupNetrwStatusLine : stl=".&stl)
 endfun
 
 " =========================================
@@ -9487,343 +9487,343 @@ endfun
 "  and reverse sorts will be requested of the server but not otherwise
 "  enforced here.
 fun! s:NetrwRemoteFtpCmd(path,listcmd)
-"  call Dfunc("NetrwRemoteFtpCmd(path<".a:path."> listcmd<".a:listcmd.">) w:netrw_method=".(exists("w:netrw_method")? w:netrw_method : (exists("b:netrw_method")? b:netrw_method : "???")))
-"  call Decho("line($)=".line("$")." win#".winnr()." w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand(""))
+  "  call Dfunc("NetrwRemoteFtpCmd(path<".a:path."> listcmd<".a:listcmd.">) w:netrw_method=".(exists("w:netrw_method")? w:netrw_method : (exists("b:netrw_method")? b:netrw_method : "???")))
+  "  call Decho("line($)=".line("$")." win#".winnr()." w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand(""))
   " sanity check: {{{3
   if !exists("w:netrw_method")
-   if exists("b:netrw_method")
-    let w:netrw_method= b:netrw_method
-   else
-    call netrw#ErrorMsg(2,"(s:NetrwRemoteFtpCmd) internal netrw error",93)
-"    call Dret("NetrwRemoteFtpCmd")
-    return
-   endif
+    if exists("b:netrw_method")
+      let w:netrw_method= b:netrw_method
+    else
+      call netrw#ErrorMsg(2,"(s:NetrwRemoteFtpCmd) internal netrw error",93)
+      "    call Dret("NetrwRemoteFtpCmd")
+      return
+    endif
   endif
 
-  " WinXX ftp uses unix style input, so set ff to unix	" {{{3
+  " WinXX ftp uses unix style input, so set ff to unix  " {{{3
   let ffkeep= &ff
   setl ma ff=unix noro
-"  call Decho("setl ma ff=unix noro",'~'.expand(""))
+  "  call Decho("setl ma ff=unix noro",'~'.expand(""))
 
-  " clear off any older non-banner lines	" {{{3
+  " clear off any older non-banner lines        " {{{3
   " note that w:netrw_bannercnt indexes the line after the banner
-"  call Decho('exe sil! NetrwKeepj '.w:netrw_bannercnt.",$d _  (clear off old non-banner lines)",'~'.expand(""))
+  "  call Decho('exe sil! NetrwKeepj '.w:netrw_bannercnt.",$d _  (clear off old non-banner lines)",'~'.expand(""))
   exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _"
 
   ".........................................
-  if w:netrw_method == 2 || w:netrw_method == 5	" {{{3
-   " ftp + <.netrc>:  Method #2
-   if a:path != ""
-    NetrwKeepj put ='cd \"'.a:path.'\"'
-   endif
-   if exists("g:netrw_ftpextracmd")
-    NetrwKeepj put =g:netrw_ftpextracmd
-"    call Decho("filter input: ".getline('.'),'~'.expand(""))
-   endif
-   NetrwKeepj call setline(line("$")+1,a:listcmd)
-"   exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))'
-   if exists("g:netrw_port") && g:netrw_port != ""
-"    call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1),'~'.expand(""))
-    exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)
-   else
-"    call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1),'~'.expand(""))
-    exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)
-   endif
+  if w:netrw_method == 2 || w:netrw_method == 5 " {{{3
+    " ftp + <.netrc>:  Method #2
+    if a:path != ""
+      NetrwKeepj put ='cd \"'.a:path.'\"'
+    endif
+    if exists("g:netrw_ftpextracmd")
+      NetrwKeepj put =g:netrw_ftpextracmd
+      "    call Decho("filter input: ".getline('.'),'~'.expand(""))
+    endif
+    NetrwKeepj call setline(line("$")+1,a:listcmd)
+    "   exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))'
+    if exists("g:netrw_port") && g:netrw_port != ""
+      "    call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1),'~'.expand(""))
+      exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)
+    else
+      "    call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1),'~'.expand(""))
+      exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)
+    endif
 
-  ".........................................
-  elseif w:netrw_method == 3	" {{{3
-   " ftp + machine,id,passwd,filename:  Method #3
+    ".........................................
+  elseif w:netrw_method == 3    " {{{3
+    " ftp + machine,id,passwd,filename:  Method #3
     setl ff=unix
     if exists("g:netrw_port") && g:netrw_port != ""
-     NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
+      NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
     else
-     NetrwKeepj put ='open '.g:netrw_machine
+      NetrwKeepj put ='open '.g:netrw_machine
     endif
 
     " handle userid and password
     let host= substitute(g:netrw_machine,'\..*$','','')
-"    call Decho("host<".host.">",'~'.expand(""))
+    "    call Decho("host<".host.">",'~'.expand(""))
     if exists("s:netrw_hup") && exists("s:netrw_hup[host]")
-     call NetUserPass("ftp:".host)
+      call NetUserPass("ftp:".host)
     endif
     if exists("g:netrw_uid") && g:netrw_uid != ""
-     if exists("g:netrw_ftp") && g:netrw_ftp == 1
-      NetrwKeepj put =g:netrw_uid
-      if exists("s:netrw_passwd") && s:netrw_passwd != ""
-       NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
-      endif
-     elseif exists("s:netrw_passwd")
-      NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
-     endif
-    endif
-
-   if a:path != ""
-    NetrwKeepj put ='cd \"'.a:path.'\"'
-   endif
-   if exists("g:netrw_ftpextracmd")
-    NetrwKeepj put =g:netrw_ftpextracmd
-"    call Decho("filter input: ".getline('.'),'~'.expand(""))
-   endif
-   NetrwKeepj call setline(line("$")+1,a:listcmd)
-
-   " perform ftp:
-   " -i       : turns off interactive prompting from ftp
-   " -n  unix : DON'T use <.netrc>, even though it exists
-   " -n  win32: quit being obnoxious about password
-   if exists("w:netrw_bannercnt")
-"    exe w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))'
-    call s:NetrwExe(s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
-"   else " Decho
-"    call Decho("WARNING: w:netrw_bannercnt doesn't exist!",'~'.expand(""))
-"    g/^./call Decho("SKIPPING ftp#".line(".").": ".getline("."),'~'.expand(""))
-   endif
+      if exists("g:netrw_ftp") && g:netrw_ftp == 1
+        NetrwKeepj put =g:netrw_uid
+        if exists("s:netrw_passwd") && s:netrw_passwd != ""
+          NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
+        endif
+      elseif exists("s:netrw_passwd")
+        NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+      endif
+    endif
 
-  ".........................................
-  elseif w:netrw_method == 9	" {{{3
-   " sftp username@machine: Method #9
-   " s:netrw_sftp_cmd
-   setl ff=unix
+    if a:path != ""
+      NetrwKeepj put ='cd \"'.a:path.'\"'
+    endif
+    if exists("g:netrw_ftpextracmd")
+      NetrwKeepj put =g:netrw_ftpextracmd
+      "    call Decho("filter input: ".getline('.'),'~'.expand(""))
+    endif
+    NetrwKeepj call setline(line("$")+1,a:listcmd)
+
+    " perform ftp:
+    " -i       : turns off interactive prompting from ftp
+    " -n  unix : DON'T use <.netrc>, even though it exists
+    " -n  win32: quit being obnoxious about password
+    if exists("w:netrw_bannercnt")
+      "    exe w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))'
+      call s:NetrwExe(s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
+      "   else " Decho
+      "    call Decho("WARNING: w:netrw_bannercnt doesn't exist!",'~'.expand(""))
+      "    g/^./call Decho("SKIPPING ftp#".line(".").": ".getline("."),'~'.expand(""))
+    endif
+
+    ".........................................
+  elseif w:netrw_method == 9    " {{{3
+    " sftp username@machine: Method #9
+    " s:netrw_sftp_cmd
+    setl ff=unix
 
-   " restore settings
-   let &l:ff= ffkeep
-"   call Dret("NetrwRemoteFtpCmd")
-   return
+    " restore settings
+    let &l:ff= ffkeep
+    "   call Dret("NetrwRemoteFtpCmd")
+    return
 
-  ".........................................
-  else	" {{{3
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . bufname("%") . ">",23)
+    ".........................................
+  else  " {{{3
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . bufname("%") . ">",23)
   endif
 
   " cleanup for Windows " {{{3
   if has("win32")
-   sil! NetrwKeepj %s/\r$//e
-   NetrwKeepj call histdel("/",-1)
+    sil! NetrwKeepj %s/\r$//e
+    NetrwKeepj call histdel("/",-1)
   endif
   if a:listcmd == "dir"
-   " infer directory/link based on the file permission string
-   sil! NetrwKeepj g/d\%([-r][-w][-x]\)\{3}/NetrwKeepj s@$@/@e
-   sil! NetrwKeepj g/l\%([-r][-w][-x]\)\{3}/NetrwKeepj s/$/@/e
-   NetrwKeepj call histdel("/",-1)
-   NetrwKeepj call histdel("/",-1)
-   if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST)
-    exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/^\%(\S\+\s\+\)\{8}//e'
+    " infer directory/link based on the file permission string
+    sil! NetrwKeepj g/d\%([-r][-w][-x]\)\{3}/NetrwKeepj s@$@/@e
+    sil! NetrwKeepj g/l\%([-r][-w][-x]\)\{3}/NetrwKeepj s/$/@/e
+    NetrwKeepj call histdel("/",-1)
     NetrwKeepj call histdel("/",-1)
-   endif
+    if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST)
+      exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/^\%(\S\+\s\+\)\{8}//e'
+      NetrwKeepj call histdel("/",-1)
+    endif
   endif
 
   " ftp's listing doesn't seem to include ./ or ../ " {{{3
   if !search('^\.\/$\|\s\.\/$','wn')
-   exe 'NetrwKeepj '.w:netrw_bannercnt
-   NetrwKeepj put ='./'
+    exe 'NetrwKeepj '.w:netrw_bannercnt
+    NetrwKeepj put ='./'
   endif
   if !search('^\.\.\/$\|\s\.\.\/$','wn')
-   exe 'NetrwKeepj '.w:netrw_bannercnt
-   NetrwKeepj put ='../'
+    exe 'NetrwKeepj '.w:netrw_bannercnt
+    NetrwKeepj put ='../'
   endif
 
   " restore settings " {{{3
   let &l:ff= ffkeep
-"  call Dret("NetrwRemoteFtpCmd")
+  "  call Dret("NetrwRemoteFtpCmd")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwRemoteListing: {{{2
 fun! s:NetrwRemoteListing()
-"  call Dfunc("s:NetrwRemoteListing() b:netrw_curdir<".b:netrw_curdir.">) win#".winnr())
+  "  call Dfunc("s:NetrwRemoteListing() b:netrw_curdir<".b:netrw_curdir.">) win#".winnr())
 
   if !exists("w:netrw_bannercnt") && exists("s:bannercnt")
-   let w:netrw_bannercnt= s:bannercnt
+    let w:netrw_bannercnt= s:bannercnt
   endif
   if !exists("w:netrw_bannercnt") && exists("b:bannercnt")
-   let w:netrw_bannercnt= b:bannercnt
+    let w:netrw_bannercnt= b:bannercnt
   endif
 
   call s:RemotePathAnalysis(b:netrw_curdir)
 
   " sanity check:
   if exists("b:netrw_method") && b:netrw_method =~ '[235]'
-"   call Decho("b:netrw_method=".b:netrw_method,'~'.expand(""))
-   if !executable("ftp")
-"    call Decho("ftp is not executable",'~'.expand(""))
-    if !exists("g:netrw_quiet")
-     call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ftp",18)
+    "   call Decho("b:netrw_method=".b:netrw_method,'~'.expand(""))
+    if !executable("ftp")
+      "    call Decho("ftp is not executable",'~'.expand(""))
+      if !exists("g:netrw_quiet")
+        call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ftp",18)
+      endif
+      call s:NetrwOptionsRestore("w:")
+      "    call Dret("s:NetrwRemoteListing -1")
+      return -1
     endif
-    call s:NetrwOptionsRestore("w:")
-"    call Dret("s:NetrwRemoteListing -1")
-    return -1
-   endif
 
   elseif !exists("g:netrw_list_cmd") || g:netrw_list_cmd == ''
-"   call Decho("g:netrw_list_cmd<",(exists("g:netrw_list_cmd")? 'n/a' : "-empty-").">",'~'.expand(""))
-   if !exists("g:netrw_quiet")
-    if g:netrw_list_cmd == ""
-     NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your g:netrw_list_cmd is empty; perhaps ".g:netrw_ssh_cmd." is not executable on your system",47)
-    else
-     NetrwKeepj call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ".g:netrw_list_cmd,19)
+    "   call Decho("g:netrw_list_cmd<",(exists("g:netrw_list_cmd")? 'n/a' : "-empty-").">",'~'.expand(""))
+    if !exists("g:netrw_quiet")
+      if g:netrw_list_cmd == ""
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your g:netrw_list_cmd is empty; perhaps ".g:netrw_ssh_cmd." is not executable on your system",47)
+      else
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ".g:netrw_list_cmd,19)
+      endif
     endif
-   endif
 
-   NetrwKeepj call s:NetrwOptionsRestore("w:")
-"   call Dret("s:NetrwRemoteListing -1")
-   return -1
+    NetrwKeepj call s:NetrwOptionsRestore("w:")
+    "   call Dret("s:NetrwRemoteListing -1")
+    return -1
   endif  " (remote handling sanity check)
-"  call Decho("passed remote listing sanity checks",'~'.expand(""))
+  "  call Decho("passed remote listing sanity checks",'~'.expand(""))
 
   if exists("b:netrw_method")
-"   call Decho("setting w:netrw_method to b:netrw_method<".b:netrw_method.">",'~'.expand(""))
-   let w:netrw_method= b:netrw_method
+    "   call Decho("setting w:netrw_method to b:netrw_method<".b:netrw_method.">",'~'.expand(""))
+    let w:netrw_method= b:netrw_method
   endif
 
   if s:method == "ftp"
-   " use ftp to get remote file listing {{{3
-"   call Decho("use ftp to get remote file listing",'~'.expand(""))
-   let s:method  = "ftp"
-   let listcmd = g:netrw_ftp_list_cmd
-   if g:netrw_sort_by =~# '^t'
-    let listcmd= g:netrw_ftp_timelist_cmd
-   elseif g:netrw_sort_by =~# '^s'
-    let listcmd= g:netrw_ftp_sizelist_cmd
-   endif
-"   call Decho("listcmd<".listcmd."> (using g:netrw_ftp_list_cmd)",'~'.expand(""))
-   call s:NetrwRemoteFtpCmd(s:path,listcmd)
-"   exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("raw listing: ".getline("."),''~''.expand(""))'
-
-   " report on missing file or directory messages
-   if search('[Nn]o such file or directory\|Failed to change directory')
-    let mesg= getline(".")
-    if exists("w:netrw_bannercnt")
-     setl ma
-     exe w:netrw_bannercnt.",$d _"
-     setl noma
+    " use ftp to get remote file listing {{{3
+    "   call Decho("use ftp to get remote file listing",'~'.expand(""))
+    let s:method  = "ftp"
+    let listcmd = g:netrw_ftp_list_cmd
+    if g:netrw_sort_by =~# '^t'
+      let listcmd= g:netrw_ftp_timelist_cmd
+    elseif g:netrw_sort_by =~# '^s'
+      let listcmd= g:netrw_ftp_sizelist_cmd
+    endif
+    "   call Decho("listcmd<".listcmd."> (using g:netrw_ftp_list_cmd)",'~'.expand(""))
+    call s:NetrwRemoteFtpCmd(s:path,listcmd)
+    "   exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("raw listing: ".getline("."),''~''.expand(""))'
+
+    " report on missing file or directory messages
+    if search('[Nn]o such file or directory\|Failed to change directory')
+      let mesg= getline(".")
+      if exists("w:netrw_bannercnt")
+        setl ma
+        exe w:netrw_bannercnt.",$d _"
+        setl noma
+      endif
+      NetrwKeepj call s:NetrwOptionsRestore("w:")
+      call netrw#ErrorMsg(s:WARNING,mesg,96)
+      "    call Dret("s:NetrwRemoteListing : -1")
+      return -1
     endif
-    NetrwKeepj call s:NetrwOptionsRestore("w:")
-    call netrw#ErrorMsg(s:WARNING,mesg,96)
-"    call Dret("s:NetrwRemoteListing : -1")
-    return -1
-   endif
 
-   if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST)
-    " shorten the listing
-"    call Decho("generate short listing",'~'.expand(""))
-    exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt
-
-    " cleanup
-    if g:netrw_ftp_browse_reject != ""
-     exe "sil! keepalt NetrwKeepj g/".g:netrw_ftp_browse_reject."/NetrwKeepj d"
-     NetrwKeepj call histdel("/",-1)
-    endif
-    sil! NetrwKeepj %s/\r$//e
-    NetrwKeepj call histdel("/",-1)
+    if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST)
+      " shorten the listing
+      "    call Decho("generate short listing",'~'.expand(""))
+      exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt
 
-    " if there's no ../ listed, then put ../ in
-    let line1= line(".")
-    exe "sil! NetrwKeepj ".w:netrw_bannercnt
-    let line2= search('\.\.\/\%(\s\|$\)','cnW')
-"    call Decho("search(".'\.\.\/\%(\s\|$\)'."','cnW')=".line2."  w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand(""))
-    if line2 == 0
-"     call Decho("netrw is putting ../ into listing",'~'.expand(""))
-     sil! NetrwKeepj put='../'
+      " cleanup
+      if g:netrw_ftp_browse_reject != ""
+        exe "sil! keepalt NetrwKeepj g/".g:netrw_ftp_browse_reject."/NetrwKeepj d"
+        NetrwKeepj call histdel("/",-1)
+      endif
+      sil! NetrwKeepj %s/\r$//e
+      NetrwKeepj call histdel("/",-1)
+
+      " if there's no ../ listed, then put ../ in
+      let line1= line(".")
+      exe "sil! NetrwKeepj ".w:netrw_bannercnt
+      let line2= search('\.\.\/\%(\s\|$\)','cnW')
+      "    call Decho("search(".'\.\.\/\%(\s\|$\)'."','cnW')=".line2."  w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand(""))
+      if line2 == 0
+        "     call Decho("netrw is putting ../ into listing",'~'.expand(""))
+        sil! NetrwKeepj put='../'
+      endif
+      exe "sil! NetrwKeepj ".line1
+      sil! NetrwKeepj norm! 0
+
+      "    call Decho("line1=".line1." line2=".line2." line(.)=".line("."),'~'.expand(""))
+      if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup
+        "     call Decho("M$ ftp cleanup",'~'.expand(""))
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+//'
+        NetrwKeepj call histdel("/",-1)
+      else " normal ftp cleanup
+        "     call Decho("normal ftp cleanup",'~'.expand(""))
+        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2/e'
+        exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*/$#/#e'
+        exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*$#/#e'
+        NetrwKeepj call histdel("/",-1)
+        NetrwKeepj call histdel("/",-1)
+        NetrwKeepj call histdel("/",-1)
+      endif
     endif
-    exe "sil! NetrwKeepj ".line1
-    sil! NetrwKeepj norm! 0
 
-"    call Decho("line1=".line1." line2=".line2." line(.)=".line("."),'~'.expand(""))
-    if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup
-"     call Decho("M$ ftp cleanup",'~'.expand(""))
-     exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+//'
-     NetrwKeepj call histdel("/",-1)
-    else " normal ftp cleanup
-"     call Decho("normal ftp cleanup",'~'.expand(""))
-     exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2/e'
-     exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*/$#/#e'
-     exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*$#/#e'
-     NetrwKeepj call histdel("/",-1)
-     NetrwKeepj call histdel("/",-1)
-     NetrwKeepj call histdel("/",-1)
-    endif
-   endif
-
-   else
-   " use ssh to get remote file listing {{{3
-"   call Decho("use ssh to get remote file listing: s:path<".s:path.">",'~'.expand(""))
-   let listcmd= s:MakeSshCmd(g:netrw_list_cmd)
-"   call Decho("listcmd<".listcmd."> (using g:netrw_list_cmd)",'~'.expand(""))
-   if g:netrw_scp_cmd =~ '^pscp'
-"    call Decho("1: exe r! ".s:ShellEscape(listcmd.s:path, 1),'~'.expand(""))
-    exe "NetrwKeepj r! ".listcmd.s:ShellEscape(s:path, 1)
-    " remove rubbish and adjust listing format of 'pscp' to 'ssh ls -FLa' like
-    sil! NetrwKeepj g/^Listing directory/NetrwKeepj d
-    sil! NetrwKeepj g/^d[-rwx][-rwx][-rwx]/NetrwKeepj s+$+/+e
-    sil! NetrwKeepj g/^l[-rwx][-rwx][-rwx]/NetrwKeepj s+$+@+e
-    NetrwKeepj call histdel("/",-1)
-    NetrwKeepj call histdel("/",-1)
-    NetrwKeepj call histdel("/",-1)
-    if g:netrw_liststyle != s:LONGLIST
-     sil! NetrwKeepj g/^[dlsp-][-rwx][-rwx][-rwx]/NetrwKeepj s/^.*\s\(\S\+\)$/\1/e
-     NetrwKeepj call histdel("/",-1)
-    endif
-   else
-    if s:path == ""
-"     call Decho("2: exe r! ".listcmd,'~'.expand(""))
-     exe "NetrwKeepj keepalt r! ".listcmd
+  else
+    " use ssh to get remote file listing {{{3
+    "   call Decho("use ssh to get remote file listing: s:path<".s:path.">",'~'.expand(""))
+    let listcmd= s:MakeSshCmd(g:netrw_list_cmd)
+    "   call Decho("listcmd<".listcmd."> (using g:netrw_list_cmd)",'~'.expand(""))
+    if g:netrw_scp_cmd =~ '^pscp'
+      "    call Decho("1: exe r! ".s:ShellEscape(listcmd.s:path, 1),'~'.expand(""))
+      exe "NetrwKeepj r! ".listcmd.s:ShellEscape(s:path, 1)
+      " remove rubbish and adjust listing format of 'pscp' to 'ssh ls -FLa' like
+      sil! NetrwKeepj g/^Listing directory/NetrwKeepj d
+      sil! NetrwKeepj g/^d[-rwx][-rwx][-rwx]/NetrwKeepj s+$+/+e
+      sil! NetrwKeepj g/^l[-rwx][-rwx][-rwx]/NetrwKeepj s+$+@+e
+      NetrwKeepj call histdel("/",-1)
+      NetrwKeepj call histdel("/",-1)
+      NetrwKeepj call histdel("/",-1)
+      if g:netrw_liststyle != s:LONGLIST
+        sil! NetrwKeepj g/^[dlsp-][-rwx][-rwx][-rwx]/NetrwKeepj s/^.*\s\(\S\+\)$/\1/e
+        NetrwKeepj call histdel("/",-1)
+      endif
     else
-"     call Decho("3: exe r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1),'~'.expand(""))
-     exe "NetrwKeepj keepalt r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1)
-"     call Decho("listcmd<".listcmd."> path<".s:path.">",'~'.expand(""))
+      if s:path == ""
+        "     call Decho("2: exe r! ".listcmd,'~'.expand(""))
+        exe "NetrwKeepj keepalt r! ".listcmd
+      else
+        "     call Decho("3: exe r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1),'~'.expand(""))
+        exe "NetrwKeepj keepalt r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1)
+        "     call Decho("listcmd<".listcmd."> path<".s:path.">",'~'.expand(""))
+      endif
     endif
-   endif
 
-   " cleanup
-   if g:netrw_ssh_browse_reject != ""
-"    call Decho("cleanup: exe sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d",'~'.expand(""))
-    exe "sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d"
-    NetrwKeepj call histdel("/",-1)
-   endif
+    " cleanup
+    if g:netrw_ssh_browse_reject != ""
+      "    call Decho("cleanup: exe sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d",'~'.expand(""))
+      exe "sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d"
+      NetrwKeepj call histdel("/",-1)
+    endif
   endif
 
   if w:netrw_liststyle == s:LONGLIST
-   " do a long listing; these substitutions need to be done prior to sorting {{{3
-"   call Decho("fix long listing:",'~'.expand(""))
+    " do a long listing; these substitutions need to be done prior to sorting {{{3
+    "   call Decho("fix long listing:",'~'.expand(""))
+
+    if s:method == "ftp"
+      " cleanup
+      exe "sil! NetrwKeepj ".w:netrw_bannercnt
+      while getline('.') =~# g:netrw_ftp_browse_reject
+        sil! NetrwKeepj d
+      endwhile
+      " if there's no ../ listed, then put ../ in
+      let line1= line(".")
+      sil! NetrwKeepj 1
+      sil! NetrwKeepj call search('^\.\.\/\%(\s\|$\)','W')
+      let line2= line(".")
+      if line2 == 0
+        if b:netrw_curdir != '/'
+          exe 'sil! NetrwKeepj '.w:netrw_bannercnt."put='../'"
+        endif
+      endif
+      exe "sil! NetrwKeepj ".line1
+      sil! NetrwKeepj norm! 0
+    endif
 
-   if s:method == "ftp"
-    " cleanup
-    exe "sil! NetrwKeepj ".w:netrw_bannercnt
-    while getline('.') =~# g:netrw_ftp_browse_reject
-     sil! NetrwKeepj d
-    endwhile
-    " if there's no ../ listed, then put ../ in
-    let line1= line(".")
-    sil! NetrwKeepj 1
-    sil! NetrwKeepj call search('^\.\.\/\%(\s\|$\)','W')
-    let line2= line(".")
-    if line2 == 0
-     if b:netrw_curdir != '/'
-      exe 'sil! NetrwKeepj '.w:netrw_bannercnt."put='../'"
-     endif
-    endif
-    exe "sil! NetrwKeepj ".line1
-    sil! NetrwKeepj norm! 0
-   endif
-
-   if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup
-"    call Decho("M$ ftp site listing cleanup",'~'.expand(""))
-    exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+\)\(\w.*\)$/\2\t\1/'
-   elseif exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$")
-"    call Decho("normal ftp site listing cleanup: bannercnt=".w:netrw_bannercnt." line($)=".line("$"),'~'.expand(""))
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/ -> .*$//e'
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2 \t\1/e'
-    exe 'sil NetrwKeepj '.w:netrw_bannercnt
-    NetrwKeepj call histdel("/",-1)
-    NetrwKeepj call histdel("/",-1)
-    NetrwKeepj call histdel("/",-1)
-   endif
+    if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup
+      "    call Decho("M$ ftp site listing cleanup",'~'.expand(""))
+      exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+\)\(\w.*\)$/\2\t\1/'
+    elseif exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$")
+      "    call Decho("normal ftp site listing cleanup: bannercnt=".w:netrw_bannercnt." line($)=".line("$"),'~'.expand(""))
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/ -> .*$//e'
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2 \t\1/e'
+      exe 'sil NetrwKeepj '.w:netrw_bannercnt
+      NetrwKeepj call histdel("/",-1)
+      NetrwKeepj call histdel("/",-1)
+      NetrwKeepj call histdel("/",-1)
+    endif
   endif
 
-"  if exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") " Decho
-"   exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("listing: ".getline("."),''~''.expand(""))'
-"  endif " Decho
+  "  if exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") " Decho
+  "   exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("listing: ".getline("."),''~''.expand(""))'
+  "  endif " Decho
 
-"  call Dret("s:NetrwRemoteListing 0")
+  "  call Dret("s:NetrwRemoteListing 0")
   return 0
 endfun
 
@@ -9834,37 +9834,37 @@ fun! s:NetrwRemoteRm(usrhost,path) range
 
   let all= 0
   if exists("s:netrwmarkfilelist_{bufnr('%')}")
-   " remove all marked files
-   for fname in s:netrwmarkfilelist_{bufnr("%")}
-    let ok= s:NetrwRemoteRmFile(a:path,fname,all)
-    if ok =~# 'q\%[uit]'
-     break
-    elseif ok =~# 'a\%[ll]'
-     let all= 1
-    endif
-   endfor
-   call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+    " remove all marked files
+    for fname in s:netrwmarkfilelist_{bufnr("%")}
+      let ok= s:NetrwRemoteRmFile(a:path,fname,all)
+      if ok =~# 'q\%[uit]'
+        break
+      elseif ok =~# 'a\%[ll]'
+        let all= 1
+      endif
+    endfor
+    call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
 
   else
-   " remove files specified by range
+    " remove files specified by range
 
-   " preparation for removing multiple files/directories
-   let keepsol = &l:sol
-   setl nosol
-   let ctr    = a:firstline
+    " preparation for removing multiple files/directories
+    let keepsol = &l:sol
+    setl nosol
+    let ctr    = a:firstline
 
-   " remove multiple files and directories
-   while ctr <= a:lastline
-    exe "NetrwKeepj ".ctr
-    let ok= s:NetrwRemoteRmFile(a:path,s:NetrwGetWord(),all)
-    if ok =~# 'q\%[uit]'
-     break
-    elseif ok =~# 'a\%[ll]'
-     let all= 1
-    endif
-    let ctr= ctr + 1
-   endwhile
-   let &l:sol = keepsol
+    " remove multiple files and directories
+    while ctr <= a:lastline
+      exe "NetrwKeepj ".ctr
+      let ok= s:NetrwRemoteRmFile(a:path,s:NetrwGetWord(),all)
+      if ok =~# 'q\%[uit]'
+        break
+      elseif ok =~# 'a\%[ll]'
+        let all= 1
+      endif
+      let ctr= ctr + 1
+    endwhile
+    let &l:sol = keepsol
   endif
 
   " refresh the (remote) directory listing
@@ -9875,120 +9875,120 @@ endfun
 " ---------------------------------------------------------------------
 " s:NetrwRemoteRmFile: {{{2
 fun! s:NetrwRemoteRmFile(path,rmfile,all)
-"  call Dfunc("s:NetrwRemoteRmFile(path<".a:path."> rmfile<".a:rmfile.">) all=".a:all)
+  "  call Dfunc("s:NetrwRemoteRmFile(path<".a:path."> rmfile<".a:rmfile.">) all=".a:all)
 
   let all= a:all
   let ok = ""
 
   if a:rmfile !~ '^"' && (a:rmfile =~ '@$' || a:rmfile !~ '[\/]$')
-   " attempt to remove file
-"    call Decho("attempt to remove file (all=".all.")",'~'.expand(""))
-   if !all
-    echohl Statement
-"    call Decho("case all=0:",'~'.expand(""))
-    call inputsave()
-    let ok= input("Confirm deletion of file<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
-    call inputrestore()
-    echohl NONE
-    if ok == ""
-     let ok="no"
-    endif
-    let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
-    if ok =~# 'a\%[ll]'
-     let all= 1
-    endif
-   endif
-
-   if all || ok =~# 'y\%[es]' || ok == ""
-"    call Decho("case all=".all." or ok<".ok.">".(exists("w:netrw_method")? ': netrw_method='.w:netrw_method : ""),'~'.expand(""))
-    if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
-"     call Decho("case ftp:",'~'.expand(""))
-     let path= a:path
-     if path =~ '^\a\{3,}://'
-      let path= substitute(path,'^\a\{3,}://[^/]\+/','','')
-     endif
-     sil! NetrwKeepj .,$d _
-     call s:NetrwRemoteFtpCmd(path,"delete ".'"'.a:rmfile.'"')
-    else
-"     call Decho("case ssh: g:netrw_rm_cmd<".g:netrw_rm_cmd.">",'~'.expand(""))
-     let netrw_rm_cmd= s:MakeSshCmd(g:netrw_rm_cmd)
-"     call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand(""))
-     if !exists("b:netrw_curdir")
-      NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
-      let ok="q"
-     else
-      let remotedir= substitute(b:netrw_curdir,'^.\{-}//[^/]\+/\(.*\)$','\1','')
-"      call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand(""))
-"      call Decho("remotedir<".remotedir.">",'~'.expand(""))
-"      call Decho("rmfile<".a:rmfile.">",'~'.expand(""))
-      if remotedir != ""
-       let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(remotedir.a:rmfile))
-      else
-       let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(a:rmfile))
+    " attempt to remove file
+    "    call Decho("attempt to remove file (all=".all.")",'~'.expand(""))
+    if !all
+      echohl Statement
+      "    call Decho("case all=0:",'~'.expand(""))
+      call inputsave()
+      let ok= input("Confirm deletion of file<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
+      call inputrestore()
+      echohl NONE
+      if ok == ""
+        let ok="no"
       endif
-"      call Decho("call system(".netrw_rm_cmd.")",'~'.expand(""))
-      let ret= system(netrw_rm_cmd)
-      if v:shell_error != 0
-       if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir
-        call netrw#ErrorMsg(s:ERROR,"remove failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",102)
-       else
-        call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60)
-       endif
-      elseif ret != 0
-       call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60)
+      let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
+      if ok =~# 'a\%[ll]'
+        let all= 1
       endif
-"      call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand(""))
-     endif
     endif
-   elseif ok =~# 'q\%[uit]'
-"    call Decho("ok==".ok,'~'.expand(""))
-   endif
-
-  else
-   " attempt to remove directory
-"    call Decho("attempt to remove directory",'~'.expand(""))
-   if !all
-    call inputsave()
-    let ok= input("Confirm deletion of directory<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
-    call inputrestore()
-    if ok == ""
-     let ok="no"
-    endif
-    let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
-    if ok =~# 'a\%[ll]'
-     let all= 1
-    endif
-   endif
-
-   if all || ok =~# 'y\%[es]' || ok == ""
-    if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
-     NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rmdir ".a:rmfile)
-    else
-     let rmfile          = substitute(a:path.a:rmfile,'/$','','')
-     let netrw_rmdir_cmd = s:MakeSshCmd(netrw#WinPath(g:netrw_rmdir_cmd)).' '.s:ShellEscape(netrw#WinPath(rmfile))
-"      call Decho("attempt to remove dir: system(".netrw_rmdir_cmd.")",'~'.expand(""))
-     let ret= system(netrw_rmdir_cmd)
-"      call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand(""))
 
-     if v:shell_error != 0
-"      call Decho("v:shell_error not 0",'~'.expand(""))
-      let netrw_rmf_cmd= s:MakeSshCmd(netrw#WinPath(g:netrw_rmf_cmd)).' '.s:ShellEscape(netrw#WinPath(substitute(rmfile,'[\/]$','','e')))
-"      call Decho("2nd attempt to remove dir: system(".netrw_rmf_cmd.")",'~'.expand(""))
-      let ret= system(netrw_rmf_cmd)
-"      call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand(""))
+    if all || ok =~# 'y\%[es]' || ok == ""
+      "    call Decho("case all=".all." or ok<".ok.">".(exists("w:netrw_method")? ': netrw_method='.w:netrw_method : ""),'~'.expand(""))
+      if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
+        "     call Decho("case ftp:",'~'.expand(""))
+        let path= a:path
+        if path =~ '^\a\{3,}://'
+          let path= substitute(path,'^\a\{3,}://[^/]\+/','','')
+        endif
+        sil! NetrwKeepj .,$d _
+        call s:NetrwRemoteFtpCmd(path,"delete ".'"'.a:rmfile.'"')
+      else
+        "     call Decho("case ssh: g:netrw_rm_cmd<".g:netrw_rm_cmd.">",'~'.expand(""))
+        let netrw_rm_cmd= s:MakeSshCmd(g:netrw_rm_cmd)
+        "     call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand(""))
+        if !exists("b:netrw_curdir")
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
+          let ok="q"
+        else
+          let remotedir= substitute(b:netrw_curdir,'^.\{-}//[^/]\+/\(.*\)$','\1','')
+          "      call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand(""))
+          "      call Decho("remotedir<".remotedir.">",'~'.expand(""))
+          "      call Decho("rmfile<".a:rmfile.">",'~'.expand(""))
+          if remotedir != ""
+            let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(remotedir.a:rmfile))
+          else
+            let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(a:rmfile))
+          endif
+          "      call Decho("call system(".netrw_rm_cmd.")",'~'.expand(""))
+          let ret= system(netrw_rm_cmd)
+          if v:shell_error != 0
+            if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir
+              call netrw#ErrorMsg(s:ERROR,"remove failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",102)
+            else
+              call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60)
+            endif
+          elseif ret != 0
+            call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60)
+          endif
+          "      call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand(""))
+        endif
+      endif
+    elseif ok =~# 'q\%[uit]'
+      "    call Decho("ok==".ok,'~'.expand(""))
+    endif
 
-      if v:shell_error != 0 && !exists("g:netrw_quiet")
-      	NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",22)
+  else
+    " attempt to remove directory
+    "    call Decho("attempt to remove directory",'~'.expand(""))
+    if !all
+      call inputsave()
+      let ok= input("Confirm deletion of directory<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
+      call inputrestore()
+      if ok == ""
+        let ok="no"
+      endif
+      let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
+      if ok =~# 'a\%[ll]'
+        let all= 1
       endif
-     endif
     endif
 
-   elseif ok =~# 'q\%[uit]'
-"    call Decho("ok==".ok,'~'.expand(""))
-   endif
+    if all || ok =~# 'y\%[es]' || ok == ""
+      if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
+        NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rmdir ".a:rmfile)
+      else
+        let rmfile          = substitute(a:path.a:rmfile,'/$','','')
+        let netrw_rmdir_cmd = s:MakeSshCmd(netrw#WinPath(g:netrw_rmdir_cmd)).' '.s:ShellEscape(netrw#WinPath(rmfile))
+        "      call Decho("attempt to remove dir: system(".netrw_rmdir_cmd.")",'~'.expand(""))
+        let ret= system(netrw_rmdir_cmd)
+        "      call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand(""))
+
+        if v:shell_error != 0
+          "      call Decho("v:shell_error not 0",'~'.expand(""))
+          let netrw_rmf_cmd= s:MakeSshCmd(netrw#WinPath(g:netrw_rmf_cmd)).' '.s:ShellEscape(netrw#WinPath(substitute(rmfile,'[\/]$','','e')))
+          "      call Decho("2nd attempt to remove dir: system(".netrw_rmf_cmd.")",'~'.expand(""))
+          let ret= system(netrw_rmf_cmd)
+          "      call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand(""))
+
+          if v:shell_error != 0 && !exists("g:netrw_quiet")
+            NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",22)
+          endif
+        endif
+      endif
+
+    elseif ok =~# 'q\%[uit]'
+      "    call Decho("ok==".ok,'~'.expand(""))
+    endif
   endif
 
-"  call Dret("s:NetrwRemoteRmFile ".ok)
+  "  call Dret("s:NetrwRemoteRmFile ".ok)
   return ok
 endfun
 
@@ -9998,62 +9998,62 @@ fun! s:NetrwRemoteRename(usrhost,path) range
 
   " preparation for removing multiple files/directories
   let svpos      = winsaveview()
-"  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
+  "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
   let ctr        = a:firstline
   let rename_cmd = s:MakeSshCmd(g:netrw_rename_cmd)
 
   " rename files given by the markfilelist
   if exists("s:netrwmarkfilelist_{bufnr('%')}")
-   for oldname in s:netrwmarkfilelist_{bufnr("%")}
-    if exists("subfrom")
-     let newname= substitute(oldname,subfrom,subto,'')
-    else
-     call inputsave()
-     let newname= input("Moving ".oldname." to : ",oldname)
-     call inputrestore()
-     if newname =~ '^s/'
-      let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','')
-      let subto   = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','')
-      let newname = substitute(oldname,subfrom,subto,'')
-     endif
-    endif
-
-    if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
-     NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname)
-    else
-     let oldname= s:ShellEscape(a:path.oldname)
-     let newname= s:ShellEscape(a:path.newname)
-     let ret    = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname)
-    endif
+    for oldname in s:netrwmarkfilelist_{bufnr("%")}
+      if exists("subfrom")
+        let newname= substitute(oldname,subfrom,subto,'')
+      else
+        call inputsave()
+        let newname= input("Moving ".oldname." to : ",oldname)
+        call inputrestore()
+        if newname =~ '^s/'
+          let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','')
+          let subto   = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','')
+          let newname = substitute(oldname,subfrom,subto,'')
+        endif
+      endif
 
-   endfor
-   call s:NetrwUnMarkFile(1)
+      if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
+        NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname)
+      else
+        let oldname= s:ShellEscape(a:path.oldname)
+        let newname= s:ShellEscape(a:path.newname)
+        let ret    = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname)
+      endif
+
+    endfor
+    call s:NetrwUnMarkFile(1)
 
   else
 
-  " attempt to rename files/directories
-   let keepsol= &l:sol
-   setl nosol
-   while ctr <= a:lastline
-    exe "NetrwKeepj ".ctr
+    " attempt to rename files/directories
+    let keepsol= &l:sol
+    setl nosol
+    while ctr <= a:lastline
+      exe "NetrwKeepj ".ctr
 
-    let oldname= s:NetrwGetWord()
+      let oldname= s:NetrwGetWord()
 
-    call inputsave()
-    let newname= input("Moving ".oldname." to : ",oldname)
-    call inputrestore()
+      call inputsave()
+      let newname= input("Moving ".oldname." to : ",oldname)
+      call inputrestore()
 
-    if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
-     call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname)
-    else
-     let oldname= s:ShellEscape(a:path.oldname)
-     let newname= s:ShellEscape(a:path.newname)
-     let ret    = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname)
-    endif
+      if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
+        call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname)
+      else
+        let oldname= s:ShellEscape(a:path.oldname)
+        let newname= s:ShellEscape(a:path.newname)
+        let ret    = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname)
+      endif
 
-    let ctr= ctr + 1
-   endwhile
-   let &l:sol= keepsol
+      let ctr= ctr + 1
+    endwhile
+    let &l:sol= keepsol
   endif
 
   " refresh the directory
@@ -10077,39 +10077,39 @@ endfun
 "                    file:///c:/foo.txt
 " and %XX (where X is [0-9a-fA-F] is converted into a character with the given hexadecimal value
 fun! netrw#FileUrlEdit(fname)
-"  call Dfunc("netrw#FileUrlEdit(fname<".a:fname.">)")
+  "  call Dfunc("netrw#FileUrlEdit(fname<".a:fname.">)")
   let fname = a:fname
   if fname =~ '^file://localhost/'
-"   call Decho('converting file://localhost/   -to-  file:///','~'.expand(""))
-   let fname= substitute(fname,'^file://localhost/','file:///','')
-"   call Decho("fname<".fname.">",'~'.expand(""))
+    "   call Decho('converting file://localhost/   -to-  file:///','~'.expand(""))
+    let fname= substitute(fname,'^file://localhost/','file:///','')
+    "   call Decho("fname<".fname.">",'~'.expand(""))
   endif
   if has("win32")
-   if fname  =~ '^file:///\=\a[|:]/'
-"    call Decho('converting file:///\a|/   -to-  file://\a:/','~'.expand(""))
-    let fname = substitute(fname,'^file:///\=\(\a\)[|:]/','file://\1:/','')
-"    call Decho("fname<".fname.">",'~'.expand(""))
-   endif
+    if fname  =~ '^file:///\=\a[|:]/'
+      "    call Decho('converting file:///\a|/   -to-  file://\a:/','~'.expand(""))
+      let fname = substitute(fname,'^file:///\=\(\a\)[|:]/','file://\1:/','')
+      "    call Decho("fname<".fname.">",'~'.expand(""))
+    endif
   endif
   let fname2396 = netrw#RFC2396(fname)
   let fname2396e= fnameescape(fname2396)
   let plainfname= substitute(fname2396,'file://\(.*\)','\1',"")
   if has("win32")
-"   call Decho("windows exception for plainfname",'~'.expand(""))
-   if plainfname =~ '^/\+\a:'
-"    call Decho('removing leading "/"s','~'.expand(""))
-    let plainfname= substitute(plainfname,'^/\+\(\a:\)','\1','')
-   endif
+    "   call Decho("windows exception for plainfname",'~'.expand(""))
+    if plainfname =~ '^/\+\a:'
+      "    call Decho('removing leading "/"s','~'.expand(""))
+      let plainfname= substitute(plainfname,'^/\+\(\a:\)','\1','')
+    endif
   endif
 
-"  call Decho("fname2396<".fname2396.">",'~'.expand(""))
-"  call Decho("plainfname<".plainfname.">",'~'.expand(""))
+  "  call Decho("fname2396<".fname2396.">",'~'.expand(""))
+  "  call Decho("plainfname<".plainfname.">",'~'.expand(""))
   exe "sil doau BufReadPre ".fname2396e
   exe 'NetrwKeepj keepalt edit '.plainfname
   exe 'sil! NetrwKeepj keepalt bdelete '.fnameescape(a:fname)
 
-"  call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-"  call Dret("netrw#FileUrlEdit")
+  "  call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+  "  call Dret("netrw#FileUrlEdit")
   exe "sil doau BufReadPost ".fname2396e
 endfun
 
@@ -10127,56 +10127,56 @@ fun! netrw#LocalBrowseCheck(dirname)
   " The &ft == "netrw" test was installed because the BufEnter event
   " would hit when re-entering netrw windows, creating unexpected
   " refreshes (and would do so in the middle of NetrwSaveOptions(), too)
-"  call Dfunc("netrw#LocalBrowseCheck(dirname<".a:dirname.">)")
-"  call Decho("isdir<".a:dirname."> =".isdirectory(s:NetrwFile(a:dirname)).((exists("s:treeforceredraw")? " treeforceredraw" : "")).'~'.expand(""))
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Dfunc("netrw#LocalBrowseCheck(dirname<".a:dirname.">)")
+  "  call Decho("isdir<".a:dirname."> =".isdirectory(s:NetrwFile(a:dirname)).((exists("s:treeforceredraw")? " treeforceredraw" : "")).'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
   " getting E930: Cannot use :redir inside execute
-""  call Dredir("ls!","netrw#LocalBrowseCheck")
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"  call Decho("current buffer#".bufnr("%")."<".bufname("%")."> ft=".&ft,'~'.expand(""))
+  ""  call Dredir("ls!","netrw#LocalBrowseCheck")
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+  "  call Decho("current buffer#".bufnr("%")."<".bufname("%")."> ft=".&ft,'~'.expand(""))
 
   let ykeep= @@
   if isdirectory(s:NetrwFile(a:dirname))
-"   call Decho("is-directory ft<".&ft."> b:netrw_curdir<".(exists("b:netrw_curdir")? b:netrw_curdir : " doesn't exist")."> dirname<".a:dirname.">"." line($)=".line("$")." ft<".&ft."> g:netrw_fastbrowse=".g:netrw_fastbrowse,'~'.expand(""))
+    "   call Decho("is-directory ft<".&ft."> b:netrw_curdir<".(exists("b:netrw_curdir")? b:netrw_curdir : " doesn't exist")."> dirname<".a:dirname.">"." line($)=".line("$")." ft<".&ft."> g:netrw_fastbrowse=".g:netrw_fastbrowse,'~'.expand(""))
 
-   if &ft != "netrw" || (exists("b:netrw_curdir") && b:netrw_curdir != a:dirname) || g:netrw_fastbrowse <= 1
-"    call Decho("case 1 : ft=".&ft,'~'.expand(""))
-"    call Decho("s:rexposn_".bufnr("%")."<".bufname("%")."> ".(exists("s:rexposn_".bufnr("%"))? "exists" : "does not exist"),'~'.expand(""))
-    sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
+    if &ft != "netrw" || (exists("b:netrw_curdir") && b:netrw_curdir != a:dirname) || g:netrw_fastbrowse <= 1
+      "    call Decho("case 1 : ft=".&ft,'~'.expand(""))
+      "    call Decho("s:rexposn_".bufnr("%")."<".bufname("%")."> ".(exists("s:rexposn_".bufnr("%"))? "exists" : "does not exist"),'~'.expand(""))
+      sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
 
-   elseif &ft == "netrw" && line("$") == 1
-"    call Decho("case 2 (ft≡netrw && line($)≡1)",'~'.expand(""))
-    sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
+    elseif &ft == "netrw" && line("$") == 1
+      "    call Decho("case 2 (ft≡netrw && line($)≡1)",'~'.expand(""))
+      sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
 
-   elseif exists("s:treeforceredraw")
-"    call Decho("case 3 (treeforceredraw)",'~'.expand(""))
-    unlet s:treeforceredraw
-    sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
-   endif
-"   call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
-"   call Dret("netrw#LocalBrowseCheck")
-   return
+    elseif exists("s:treeforceredraw")
+      "    call Decho("case 3 (treeforceredraw)",'~'.expand(""))
+      unlet s:treeforceredraw
+      sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
+    endif
+    "   call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+    "   call Dret("netrw#LocalBrowseCheck")
+    return
   endif
 
   " The following code wipes out currently unused netrw buffers
   "       IF g:netrw_fastbrowse is zero (ie. slow browsing selected)
   "   AND IF the listing style is not a tree listing
   if exists("g:netrw_fastbrowse") && g:netrw_fastbrowse == 0 && g:netrw_liststyle != s:TREELIST
-"   call Decho("wiping out currently unused netrw buffers",'~'.expand(""))
-   let ibuf    = 1
-   let buflast = bufnr("$")
-   while ibuf <= buflast
-    if bufwinnr(ibuf) == -1 && isdirectory(s:NetrwFile(bufname(ibuf)))
-     exe "sil! keepj keepalt ".ibuf."bw!"
-    endif
-    let ibuf= ibuf + 1
-   endwhile
+    "   call Decho("wiping out currently unused netrw buffers",'~'.expand(""))
+    let ibuf    = 1
+    let buflast = bufnr("$")
+    while ibuf <= buflast
+      if bufwinnr(ibuf) == -1 && isdirectory(s:NetrwFile(bufname(ibuf)))
+        exe "sil! keepj keepalt ".ibuf."bw!"
+      endif
+      let ibuf= ibuf + 1
+    endwhile
   endif
   let @@= ykeep
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand(""))
   " not a directory, ignore it
-"  call Dret("netrw#LocalBrowseCheck : not a directory, ignoring it; dirname<".a:dirname.">")
+  "  call Dret("netrw#LocalBrowseCheck : not a directory, ignoring it; dirname<".a:dirname.">")
 endfun
 
 " ---------------------------------------------------------------------
@@ -10187,27 +10187,27 @@ endfun
 fun! s:LocalBrowseRefresh()
   " determine which buffers currently reside in a tab
   if !exists("s:netrw_browselist")
-   return
+    return
   endif
   if !exists("w:netrw_bannercnt")
-   return
+    return
   endif
   if !empty(getcmdwintype())
     " cannot move away from cmdline window, see :h E11
     return
   endif
   if exists("s:netrw_events") && s:netrw_events == 1
-   " s:LocalFastBrowser gets called (indirectly) from a
-   let s:netrw_events= 2
-   return
+    " s:LocalFastBrowser gets called (indirectly) from a
+    let s:netrw_events= 2
+    return
   endif
   let itab       = 1
   let buftablist = []
   let ykeep      = @@
   while itab <= tabpagenr("$")
-   let buftablist = buftablist + tabpagebuflist()
-   let itab       = itab + 1
-   sil! tabn
+    let buftablist = buftablist + tabpagebuflist()
+    let itab       = itab + 1
+    sil! tabn
   endwhile
   "  GO through all buffers on netrw_browselist (ie. just local-netrw buffers):
   "   | refresh any netrw window
@@ -10215,27 +10215,27 @@ fun! s:LocalBrowseRefresh()
   let curwinid = win_getid(winnr())
   let ibl    = 0
   for ibuf in s:netrw_browselist
-   if bufwinnr(ibuf) == -1 && index(buftablist,ibuf) == -1
-    " wipe out any non-displaying netrw buffer
-    " (ibuf not shown in a current window AND
-    "  ibuf not in any tab)
-    exe "sil! keepj bd ".fnameescape(ibuf)
-    call remove(s:netrw_browselist,ibl)
-    continue
-   elseif index(tabpagebuflist(),ibuf) != -1
-    " refresh any netrw buffer
-    exe bufwinnr(ibuf)."wincmd w"
-    if getline(".") =~# 'Quick Help'
-     " decrement g:netrw_quickhelp to prevent refresh from changing g:netrw_quickhelp
-     " (counteracts s:NetrwBrowseChgDir()'s incrementing)
-     let g:netrw_quickhelp= g:netrw_quickhelp - 1
-    endif
-    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-     NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop)
+    if bufwinnr(ibuf) == -1 && index(buftablist,ibuf) == -1
+      " wipe out any non-displaying netrw buffer
+      " (ibuf not shown in a current window AND
+      "  ibuf not in any tab)
+      exe "sil! keepj bd ".fnameescape(ibuf)
+      call remove(s:netrw_browselist,ibl)
+      continue
+    elseif index(tabpagebuflist(),ibuf) != -1
+      " refresh any netrw buffer
+      exe bufwinnr(ibuf)."wincmd w"
+      if getline(".") =~# 'Quick Help'
+        " decrement g:netrw_quickhelp to prevent refresh from changing g:netrw_quickhelp
+        " (counteracts s:NetrwBrowseChgDir()'s incrementing)
+        let g:netrw_quickhelp= g:netrw_quickhelp - 1
+      endif
+      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+        NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop)
+      endif
+      NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
     endif
-    NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
-   endif
-   let ibl= ibl + 1
+    let ibl= ibl + 1
   endfor
   call win_gotoid(curwinid)
   let @@= ykeep
@@ -10263,12 +10263,12 @@ fun! s:LocalFastBrowser()
 
   " initialize browselist, a list of buffer numbers that the local browser has used
   if !exists("s:netrw_browselist")
-   let s:netrw_browselist= []
+    let s:netrw_browselist= []
   endif
 
   " append current buffer to fastbrowse list
   if empty(s:netrw_browselist) || bufnr("%") > s:netrw_browselist[-1]
-   call add(s:netrw_browselist,bufnr("%"))
+    call add(s:netrw_browselist,bufnr("%"))
   endif
 
   " enable autocmd events to handle refreshing/removing local browser buffers
@@ -10278,24 +10278,24 @@ fun! s:LocalFastBrowser()
   "                      =1 : medium speed, re-use directory listing for remote only
   "                      =2 : fast   speed, always re-use directory listing when possible
   if g:netrw_fastbrowse <= 1 && !exists("#ShellCmdPost") && !exists("s:netrw_events")
-   let s:netrw_events= 1
-   augroup AuNetrwEvent
-    au!
-    if has("win32")
-     au ShellCmdPost			*	call s:LocalBrowseRefresh()
-    else
-     au ShellCmdPost,FocusGained	*	call s:LocalBrowseRefresh()
-    endif
-   augroup END
+    let s:netrw_events= 1
+    augroup AuNetrwEvent
+      au!
+      if has("win32")
+        au ShellCmdPost                    *       call s:LocalBrowseRefresh()
+      else
+        au ShellCmdPost,FocusGained        *       call s:LocalBrowseRefresh()
+      endif
+    augroup END
 
-  " user must have changed fastbrowse to its fast setting, so remove
-  " the associated autocmd events
+    " user must have changed fastbrowse to its fast setting, so remove
+    " the associated autocmd events
   elseif g:netrw_fastbrowse > 1 && exists("#ShellCmdPost") && exists("s:netrw_events")
-   unlet s:netrw_events
-   augroup AuNetrwEvent
-    au!
-   augroup END
-   augroup! AuNetrwEvent
+    unlet s:netrw_events
+    augroup AuNetrwEvent
+      au!
+    augroup END
+    augroup! AuNetrwEvent
   endif
 endfun
 
@@ -10309,93 +10309,93 @@ fun! s:NetrwLocalListingList(dirname,setmaxfilenamelen)
   if g:netrw_cygwin == 0 && has("win32")
   elseif index(filelist,'..') == -1 && dirname !~ '/'
     " include ../ in the glob() entry if its missing
-   let filelist= filelist+[s:ComposePath(dirname,"../")]
+    let filelist= filelist+[s:ComposePath(dirname,"../")]
   endif
 
   if a:setmaxfilenamelen && get(g:, 'netrw_dynamic_maxfilenamelen', 0)
-   let filelistcopy           = map(deepcopy(filelist),'fnamemodify(v:val, ":t")')
-   let g:netrw_maxfilenamelen = max(map(filelistcopy,'len(v:val)')) + 1
+    let filelistcopy           = map(deepcopy(filelist),'fnamemodify(v:val, ":t")')
+    let g:netrw_maxfilenamelen = max(map(filelistcopy,'len(v:val)')) + 1
   endif
 
   let resultfilelist = []
   for filename in filelist
 
-   if getftype(filename) == "link"
-    " indicate a symbolic link
-    let pfile= filename."@"
+    if getftype(filename) == "link"
+      " indicate a symbolic link
+      let pfile= filename."@"
+
+    elseif getftype(filename) == "socket"
+      " indicate a socket
+      let pfile= filename."="
+
+    elseif getftype(filename) == "fifo"
+      " indicate a fifo
+      let pfile= filename."|"
+
+    elseif isdirectory(s:NetrwFile(filename))
+      " indicate a directory
+      let pfile= filename."/"
+
+    elseif exists("b:netrw_curdir") && b:netrw_curdir !~ '^.*://' && !isdirectory(s:NetrwFile(filename))
+      if has("win32")
+        if filename =~ '\.[eE][xX][eE]$' || filename =~ '\.[cC][oO][mM]$' || filename =~ '\.[bB][aA][tT]$'
+          " indicate an executable
+          let pfile= filename."*"
+        else
+          " normal file
+          let pfile= filename
+        endif
+      elseif executable(filename)
+        " indicate an executable
+        let pfile= filename."*"
+      else
+        " normal file
+        let pfile= filename
+      endif
+
+    else
+      " normal file
+      let pfile= filename
+    endif
+
+    if pfile =~ '//$'
+      let pfile= substitute(pfile,'//$','/','e')
+    endif
+    let pfile= strpart(pfile,dirnamelen)
+    let pfile= substitute(pfile,'^[/\\]','','e')
 
-   elseif getftype(filename) == "socket"
-    " indicate a socket
-    let pfile= filename."="
+    if w:netrw_liststyle == s:LONGLIST
+      let longfile = printf("%-".g:netrw_maxfilenamelen."S",pfile)
+      let sz       = getfsize(filename)
+      let szlen    = 15 - (strdisplaywidth(longfile) - g:netrw_maxfilenamelen)
+      let szlen    = (szlen > 0) ? szlen : 0
 
-   elseif getftype(filename) == "fifo"
-    " indicate a fifo
-    let pfile= filename."|"
+      if g:netrw_sizestyle =~# "[hH]"
+        let sz= s:NetrwHumanReadable(sz)
+      endif
+      let fsz  = printf("%".szlen."S",sz)
+      let pfile= longfile."  ".fsz." ".strftime(g:netrw_timefmt,getftime(filename))
+    endif
 
-   elseif isdirectory(s:NetrwFile(filename))
-    " indicate a directory
-    let pfile= filename."/"
+    if     g:netrw_sort_by =~# "^t"
+      " sort by time (handles time up to 1 quintillion seconds, US)
+      " Decorate listing by prepending a timestamp/  .  Sorting will then be done based on time.
+      let t  = getftime(filename)
+      let ft = printf("%018d",t)
+      let ftpfile= ft.'/'.pfile
+      let resultfilelist += [ftpfile]
+
+    elseif g:netrw_sort_by =~ "^s"
+      " sort by size (handles file sizes up to 1 quintillion bytes, US)
+      let sz   = getfsize(filename)
+      let fsz  = printf("%018d",sz)
+      let fszpfile= fsz.'/'.pfile
+      let resultfilelist += [fszpfile]
 
-   elseif exists("b:netrw_curdir") && b:netrw_curdir !~ '^.*://' && !isdirectory(s:NetrwFile(filename))
-    if has("win32")
-     if filename =~ '\.[eE][xX][eE]$' || filename =~ '\.[cC][oO][mM]$' || filename =~ '\.[bB][aA][tT]$'
-      " indicate an executable
-      let pfile= filename."*"
-     else
-      " normal file
-      let pfile= filename
-     endif
-    elseif executable(filename)
-     " indicate an executable
-     let pfile= filename."*"
     else
-     " normal file
-     let pfile= filename
-    endif
-
-   else
-    " normal file
-    let pfile= filename
-   endif
-
-   if pfile =~ '//$'
-    let pfile= substitute(pfile,'//$','/','e')
-   endif
-   let pfile= strpart(pfile,dirnamelen)
-   let pfile= substitute(pfile,'^[/\\]','','e')
-
-   if w:netrw_liststyle == s:LONGLIST
-    let longfile = printf("%-".g:netrw_maxfilenamelen."S",pfile)
-    let sz       = getfsize(filename)
-    let szlen    = 15 - (strdisplaywidth(longfile) - g:netrw_maxfilenamelen)
-    let szlen    = (szlen > 0) ? szlen : 0
-
-    if g:netrw_sizestyle =~# "[hH]"
-     let sz= s:NetrwHumanReadable(sz)
-    endif
-    let fsz  = printf("%".szlen."S",sz)
-    let pfile= longfile."  ".fsz." ".strftime(g:netrw_timefmt,getftime(filename))
-   endif
-
-   if     g:netrw_sort_by =~# "^t"
-    " sort by time (handles time up to 1 quintillion seconds, US)
-    " Decorate listing by prepending a timestamp/  .  Sorting will then be done based on time.
-    let t  = getftime(filename)
-    let ft = printf("%018d",t)
-    let ftpfile= ft.'/'.pfile
-    let resultfilelist += [ftpfile]
-
-   elseif g:netrw_sort_by =~ "^s"
-    " sort by size (handles file sizes up to 1 quintillion bytes, US)
-    let sz   = getfsize(filename)
-    let fsz  = printf("%018d",sz)
-    let fszpfile= fsz.'/'.pfile
-    let resultfilelist += [fszpfile]
-
-   else
-    " sort by name
-    let resultfilelist += [pfile]
-   endif
+      " sort by name
+      let resultfilelist += [pfile]
+    endif
   endfor
 
   return resultfilelist
@@ -10420,20 +10420,20 @@ endfun
 " ---------------------------------------------------------------------
 " s:NetrwLocalExecute: uses system() to execute command under cursor ("X" command support) {{{2
 fun! s:NetrwLocalExecute(cmd)
-"  call Dfunc("s:NetrwLocalExecute(cmd<".a:cmd.">)")
+  "  call Dfunc("s:NetrwLocalExecute(cmd<".a:cmd.">)")
   let ykeep= @@
   " sanity check
   if !executable(a:cmd)
-   call netrw#ErrorMsg(s:ERROR,"the file<".a:cmd."> is not executable!",89)
-   let @@= ykeep
-"   call Dret("s:NetrwLocalExecute")
-   return
+    call netrw#ErrorMsg(s:ERROR,"the file<".a:cmd."> is not executable!",89)
+    let @@= ykeep
+    "   call Dret("s:NetrwLocalExecute")
+    return
   endif
 
   let optargs= input(":!".a:cmd,"","file")
-"  call Decho("optargs<".optargs.">",'~'.expand(""))
+  "  call Decho("optargs<".optargs.">",'~'.expand(""))
   let result= system(a:cmd.optargs)
-"  call Decho("result,'~'.expand(""))
+  "  call Decho("result,'~'.expand(""))
 
   " strip any ansi escape sequences off
   let result = substitute(result,"\e\\[[0-9;]*m","","g")
@@ -10442,7 +10442,7 @@ fun! s:NetrwLocalExecute(cmd)
   echomsg result
   let @@= ykeep
 
-"  call Dret("s:NetrwLocalExecute")
+  "  call Dret("s:NetrwLocalExecute")
 endfun
 
 " ---------------------------------------------------------------------
@@ -10450,7 +10450,7 @@ endfun
 fun! s:NetrwLocalRename(path) range
 
   if !exists("w:netrw_bannercnt")
-   let w:netrw_bannercnt= b:netrw_bannercnt
+    let w:netrw_bannercnt= b:netrw_bannercnt
   endif
 
   " preparation for removing multiple files/directories
@@ -10461,71 +10461,71 @@ fun! s:NetrwLocalRename(path) range
 
   " rename files given by the markfilelist
   if exists("s:netrwmarkfilelist_{bufnr('%')}")
-   for oldname in s:netrwmarkfilelist_{bufnr("%")}
-    if exists("subfrom")
-     let newname= substitute(oldname,subfrom,subto,'')
-    else
-     call inputsave()
-     let newname= input("Moving ".oldname." to : ",oldname,"file")
-     call inputrestore()
-     if newname =~ ''
-      " two ctrl-x's : ignore all of string preceding the ctrl-x's
-      let newname = substitute(newname,'^.*','','')
-     elseif newname =~ ''
-      " one ctrl-x : ignore portion of string preceding ctrl-x but after last /
-      let newname = substitute(newname,'[^/]*','','')
-     endif
-     if newname =~ '^s/'
-      let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','')
-      let subto   = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','')
-      let newname = substitute(oldname,subfrom,subto,'')
-     endif
-    endif
-    if !all && filereadable(newname)
-     call inputsave()
-      let response= input("File<".newname."> already exists; do you want to overwrite it? (y/all/n) ")
-     call inputrestore()
-     if response == "all"
-      let all= 1
-     elseif response != "y" && response != "yes"
-      " refresh the directory
-      NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
-      NetrwKeepj call winrestview(svpos)
-      let @@= ykeep
-      return
-     endif
-    endif
-    call rename(oldname,newname)
-   endfor
-   call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+    for oldname in s:netrwmarkfilelist_{bufnr("%")}
+      if exists("subfrom")
+        let newname= substitute(oldname,subfrom,subto,'')
+      else
+        call inputsave()
+        let newname= input("Moving ".oldname." to : ",oldname,"file")
+        call inputrestore()
+        if newname =~ ''
+          " two ctrl-x's : ignore all of string preceding the ctrl-x's
+          let newname = substitute(newname,'^.*','','')
+        elseif newname =~ ''
+          " one ctrl-x : ignore portion of string preceding ctrl-x but after last /
+          let newname = substitute(newname,'[^/]*','','')
+        endif
+        if newname =~ '^s/'
+          let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','')
+          let subto   = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','')
+          let newname = substitute(oldname,subfrom,subto,'')
+        endif
+      endif
+      if !all && filereadable(newname)
+        call inputsave()
+        let response= input("File<".newname."> already exists; do you want to overwrite it? (y/all/n) ")
+        call inputrestore()
+        if response == "all"
+          let all= 1
+        elseif response != "y" && response != "yes"
+          " refresh the directory
+          NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
+          NetrwKeepj call winrestview(svpos)
+          let @@= ykeep
+          return
+        endif
+      endif
+      call rename(oldname,newname)
+    endfor
+    call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
 
   else
 
-   " attempt to rename files/directories
-   while ctr <= a:lastline
-    exe "NetrwKeepj ".ctr
+    " attempt to rename files/directories
+    while ctr <= a:lastline
+      exe "NetrwKeepj ".ctr
 
-    " sanity checks
-    if line(".") < w:netrw_bannercnt
-     let ctr= ctr + 1
-     continue
-    endif
-    let curword= s:NetrwGetWord()
-    if curword == "./" || curword == "../"
-     let ctr= ctr + 1
-     continue
-    endif
+      " sanity checks
+      if line(".") < w:netrw_bannercnt
+        let ctr= ctr + 1
+        continue
+      endif
+      let curword= s:NetrwGetWord()
+      if curword == "./" || curword == "../"
+        let ctr= ctr + 1
+        continue
+      endif
 
-    NetrwKeepj norm! 0
-    let oldname= s:ComposePath(a:path,curword)
+      NetrwKeepj norm! 0
+      let oldname= s:ComposePath(a:path,curword)
 
-    call inputsave()
-    let newname= input("Moving ".oldname." to : ",substitute(oldname,'/*$','','e'))
-    call inputrestore()
+      call inputsave()
+      let newname= input("Moving ".oldname." to : ",substitute(oldname,'/*$','','e'))
+      call inputrestore()
 
-    call rename(oldname,newname)
-    let ctr= ctr + 1
-   endwhile
+      call rename(oldname,newname)
+      let ctr= ctr + 1
+    endwhile
   endif
 
   " refresh the directory
@@ -10538,7 +10538,7 @@ endfun
 " s:NetrwLocalRm: {{{2
 fun! s:NetrwLocalRm(path) range
   if !exists("w:netrw_bannercnt")
-   let w:netrw_bannercnt= b:netrw_bannercnt
+    let w:netrw_bannercnt= b:netrw_bannercnt
   endif
 
   " preparation for removing multiple files/directories
@@ -10548,51 +10548,51 @@ fun! s:NetrwLocalRm(path) range
   let svpos = winsaveview()
 
   if exists("s:netrwmarkfilelist_{bufnr('%')}")
-   " remove all marked files
-   for fname in s:netrwmarkfilelist_{bufnr("%")}
-    let ok= s:NetrwLocalRmFile(a:path,fname,all)
-    if ok =~# 'q\%[uit]' || ok == "no"
-     break
-    elseif ok =~# '^a\%[ll]$'
-     let all= 1
-    endif
-   endfor
-   call s:NetrwUnMarkFile(1)
+    " remove all marked files
+    for fname in s:netrwmarkfilelist_{bufnr("%")}
+      let ok= s:NetrwLocalRmFile(a:path,fname,all)
+      if ok =~# 'q\%[uit]' || ok == "no"
+        break
+      elseif ok =~# '^a\%[ll]$'
+        let all= 1
+      endif
+    endfor
+    call s:NetrwUnMarkFile(1)
 
   else
-  " remove (multiple) files and directories
+    " remove (multiple) files and directories
 
-   let keepsol= &l:sol
-   setl nosol
-   let ctr = a:firstline
-   while ctr <= a:lastline
-    exe "NetrwKeepj ".ctr
+    let keepsol= &l:sol
+    setl nosol
+    let ctr = a:firstline
+    while ctr <= a:lastline
+      exe "NetrwKeepj ".ctr
 
-    " sanity checks
-    if line(".") < w:netrw_bannercnt
-     let ctr= ctr + 1
-     continue
-    endif
-    let curword= s:NetrwGetWord()
-    if curword == "./" || curword == "../"
-     let ctr= ctr + 1
-     continue
-    endif
-    let ok= s:NetrwLocalRmFile(a:path,curword,all)
-    if ok =~# 'q\%[uit]' || ok == "no"
-     break
-    elseif ok =~# '^a\%[ll]$'
-     let all= 1
-    endif
-    let ctr= ctr + 1
-   endwhile
-   let &l:sol= keepsol
+      " sanity checks
+      if line(".") < w:netrw_bannercnt
+        let ctr= ctr + 1
+        continue
+      endif
+      let curword= s:NetrwGetWord()
+      if curword == "./" || curword == "../"
+        let ctr= ctr + 1
+        continue
+      endif
+      let ok= s:NetrwLocalRmFile(a:path,curword,all)
+      if ok =~# 'q\%[uit]' || ok == "no"
+        break
+      elseif ok =~# '^a\%[ll]$'
+        let all= 1
+      endif
+      let ctr= ctr + 1
+    endwhile
+    let &l:sol= keepsol
   endif
 
   " refresh the directory
   if bufname("%") != "NetrwMessage"
-   NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
-   NetrwKeepj call winrestview(svpos)
+    NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
+    NetrwKeepj call winrestview(svpos)
   endif
   let @@= ykeep
 endfun
@@ -10601,64 +10601,64 @@ endfun
 " s:NetrwLocalRmFile: remove file fname given the path {{{2
 "                     Give confirmation prompt unless all==1
 fun! s:NetrwLocalRmFile(path,fname,all)
-"  call Dfunc("s:NetrwLocalRmFile(path<".a:path."> fname<".a:fname."> all=".a:all)
+  "  call Dfunc("s:NetrwLocalRmFile(path<".a:path."> fname<".a:fname."> all=".a:all)
 
   let all= a:all
   let ok = ""
   NetrwKeepj norm! 0
   let rmfile= s:NetrwFile(s:ComposePath(a:path,escape(a:fname, '\\')))
-"  call Decho("rmfile<".rmfile.">",'~'.expand(""))
+  "  call Decho("rmfile<".rmfile.">",'~'.expand(""))
 
   if rmfile !~ '^"' && (rmfile =~ '@$' || rmfile !~ '[\/]$')
-   " attempt to remove file
-"   call Decho("attempt to remove file<".rmfile.">",'~'.expand(""))
-   if !all
-    echohl Statement
-    call inputsave()
-    let ok= input("Confirm deletion of file <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
-    call inputrestore()
-    echohl NONE
-    if ok == ""
-     let ok="no"
-    endif
-"    call Decho("response: ok<".ok.">",'~'.expand(""))
-    let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
-"    call Decho("response: ok<".ok."> (after sub)",'~'.expand(""))
-    if ok =~# '^a\%[ll]$'
-     let all= 1
-    endif
-   endif
-
-   if all || ok =~# '^y\%[es]$' || ok == ""
-    let ret= s:NetrwDelete(rmfile)
-"    call Decho("errcode=".v:shell_error." ret=".ret,'~'.expand(""))
-   endif
+    " attempt to remove file
+    "   call Decho("attempt to remove file<".rmfile.">",'~'.expand(""))
+    if !all
+      echohl Statement
+      call inputsave()
+      let ok= input("Confirm deletion of file <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
+      call inputrestore()
+      echohl NONE
+      if ok == ""
+        let ok="no"
+      endif
+      "    call Decho("response: ok<".ok.">",'~'.expand(""))
+      let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
+      "    call Decho("response: ok<".ok."> (after sub)",'~'.expand(""))
+      if ok =~# '^a\%[ll]$'
+        let all= 1
+      endif
+    endif
 
-  else
-   " attempt to remove directory
-   if !all
-    echohl Statement
-    call inputsave()
-    let ok= input("Confirm *recursive* deletion of directory <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
-    call inputrestore()
-    let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
-    if ok == ""
-     let ok="no"
+    if all || ok =~# '^y\%[es]$' || ok == ""
+      let ret= s:NetrwDelete(rmfile)
+      "    call Decho("errcode=".v:shell_error." ret=".ret,'~'.expand(""))
     endif
-    if ok =~# '^a\%[ll]$'
-     let all= 1
+
+  else
+    " attempt to remove directory
+    if !all
+      echohl Statement
+      call inputsave()
+      let ok= input("Confirm *recursive* deletion of directory <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ")
+      call inputrestore()
+      let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e')
+      if ok == ""
+        let ok="no"
+      endif
+      if ok =~# '^a\%[ll]$'
+        let all= 1
+      endif
     endif
-   endif
-   let rmfile= substitute(rmfile,'[\/]$','','e')
+    let rmfile= substitute(rmfile,'[\/]$','','e')
 
-   if all || ok =~# '^y\%[es]$' || ok == ""
-    if delete(rmfile,"rf")
-     call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103)
+    if all || ok =~# '^y\%[es]$' || ok == ""
+      if delete(rmfile,"rf")
+        call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103)
+      endif
     endif
-   endif
   endif
 
-"  call Dret("s:NetrwLocalRmFile ".ok)
+  "  call Dret("s:NetrwLocalRmFile ".ok)
   return ok
 endfun
 
@@ -10671,13 +10671,13 @@ endfun
 "   1: marked file target
 fun! netrw#Access(ilist)
   if     a:ilist == 0
-   if exists("s:netrwmarkfilelist_".bufnr('%'))
-    return s:netrwmarkfilelist_{bufnr('%')}
-   else
-    return "no-list-buf#".bufnr('%')
-   endif
+    if exists("s:netrwmarkfilelist_".bufnr('%'))
+      return s:netrwmarkfilelist_{bufnr('%')}
+    else
+      return "no-list-buf#".bufnr('%')
+    endif
   elseif a:ilist == 1
-   return s:netrwmftgt
+    return s:netrwmftgt
   endif
 endfun
 
@@ -10693,48 +10693,48 @@ endfun
 "                 :PChkAssert netrw#Expose("netrwmarkfilelist")
 "               for example.
 fun! netrw#Expose(varname)
-"   call Dfunc("netrw#Expose(varname<".a:varname.">)")
+  "   call Dfunc("netrw#Expose(varname<".a:varname.">)")
   if exists("s:".a:varname)
-   exe "let retval= s:".a:varname
-"   call Decho("retval=".retval,'~'.expand(""))
-   if exists("g:netrw_pchk")
-"    call Decho("type(g:netrw_pchk=".g:netrw_pchk.")=".type(retval),'~'.expand(""))
-    if type(retval) == 3
-     let retval = copy(retval)
-     let i      = 0
-     while i < len(retval)
-      let retval[i]= substitute(retval[i],expand("$HOME"),'~','')
-      let i        = i + 1
-     endwhile
-    endif
-"     call Dret("netrw#Expose ".string(retval)),'~'.expand(""))
-    return string(retval)
-   else
-"    call Decho("g:netrw_pchk doesn't exist",'~'.expand(""))
-   endif
+    exe "let retval= s:".a:varname
+    "   call Decho("retval=".retval,'~'.expand(""))
+    if exists("g:netrw_pchk")
+      "    call Decho("type(g:netrw_pchk=".g:netrw_pchk.")=".type(retval),'~'.expand(""))
+      if type(retval) == 3
+        let retval = copy(retval)
+        let i      = 0
+        while i < len(retval)
+          let retval[i]= substitute(retval[i],expand("$HOME"),'~','')
+          let i        = i + 1
+        endwhile
+      endif
+      "     call Dret("netrw#Expose ".string(retval)),'~'.expand(""))
+      return string(retval)
+    else
+      "    call Decho("g:netrw_pchk doesn't exist",'~'.expand(""))
+    endif
   else
-"   call Decho("s:".a:varname." doesn't exist",'~'.expand(""))
-   let retval= "n/a"
+    "   call Decho("s:".a:varname." doesn't exist",'~'.expand(""))
+    let retval= "n/a"
   endif
 
-"  call Dret("netrw#Expose ".string(retval))
+  "  call Dret("netrw#Expose ".string(retval))
   return retval
 endfun
 
 " ---------------------------------------------------------------------
 " netrw#Modify: allows UserMaps to set (modify) script-local variables {{{2
 fun! netrw#Modify(varname,newvalue)
-"  call Dfunc("netrw#Modify(varname<".a:varname.">,newvalue<".string(a:newvalue).">)")
+  "  call Dfunc("netrw#Modify(varname<".a:varname.">,newvalue<".string(a:newvalue).">)")
   exe "let s:".a:varname."= ".string(a:newvalue)
-"  call Dret("netrw#Modify")
+  "  call Dret("netrw#Modify")
 endfun
 
 " ---------------------------------------------------------------------
 "  netrw#RFC2396: converts %xx into characters {{{2
 fun! netrw#RFC2396(fname)
-"  call Dfunc("netrw#RFC2396(fname<".a:fname.">)")
+  "  call Dfunc("netrw#RFC2396(fname<".a:fname.">)")
   let fname = escape(substitute(a:fname,'%\(\x\x\)','\=printf("%c","0x".submatch(1))','ge')," \t")
-"  call Dret("netrw#RFC2396 ".fname)
+  "  call Dret("netrw#RFC2396 ".fname)
   return fname
 endfun
 
@@ -10746,8 +10746,8 @@ endfun
 "                       [[keymap sequence, function reference],...]
 "
 "                 The referenced function may return a string,
-"                 	refresh : refresh the display
-"                 	-other- : this string will be executed
+"                       refresh : refresh the display
+"                       -other- : this string will be executed
 "                 or it may return a List of strings.
 "
 "                 Each keymap-sequence will be set up with a nnoremap
@@ -10757,52 +10757,52 @@ endfun
 "                   netrw#Modify(varname,newvalue) -- modify value of s:varname variable
 "                   netrw#Call(funcname,...)       -- call internal netrw function with optional arguments
 fun! netrw#UserMaps(islocal)
-"  call Dfunc("netrw#UserMaps(islocal=".a:islocal.")")
-"  call Decho("g:Netrw_UserMaps ".(exists("g:Netrw_UserMaps")? "exists" : "does NOT exist"),'~'.expand(""))
+  "  call Dfunc("netrw#UserMaps(islocal=".a:islocal.")")
+  "  call Decho("g:Netrw_UserMaps ".(exists("g:Netrw_UserMaps")? "exists" : "does NOT exist"),'~'.expand(""))
 
-   " set up usermaplist
-   if exists("g:Netrw_UserMaps") && type(g:Netrw_UserMaps) == 3
-"    call Decho("g:Netrw_UserMaps has type 3",'~'.expand(""))
+  " set up usermaplist
+  if exists("g:Netrw_UserMaps") && type(g:Netrw_UserMaps) == 3
+    "    call Decho("g:Netrw_UserMaps has type 3",'~'.expand(""))
     for umap in g:Netrw_UserMaps
-"     call Decho("type(umap[0]<".string(umap[0]).">)=".type(umap[0])." (should be 1=string)",'~'.expand(""))
-"     call Decho("type(umap[1])=".type(umap[1])." (should be 1=string)",'~'.expand(""))
-     " if umap[0] is a string and umap[1] is a string holding a function name
-     if type(umap[0]) == 1 && type(umap[1]) == 1
-"      call Decho("nno   ".umap[0]." :call s:UserMaps(".a:islocal.",".string(umap[1]).")",'~'.expand(""))
-      exe "nno   ".umap[0]." :call UserMaps(".a:islocal.",'".umap[1]."')"
+      "     call Decho("type(umap[0]<".string(umap[0]).">)=".type(umap[0])." (should be 1=string)",'~'.expand(""))
+      "     call Decho("type(umap[1])=".type(umap[1])." (should be 1=string)",'~'.expand(""))
+      " if umap[0] is a string and umap[1] is a string holding a function name
+      if type(umap[0]) == 1 && type(umap[1]) == 1
+        "      call Decho("nno   ".umap[0]." :call s:UserMaps(".a:islocal.",".string(umap[1]).")",'~'.expand(""))
+        exe "nno   ".umap[0]." :call UserMaps(".a:islocal.",'".umap[1]."')"
       else
-       call netrw#ErrorMsg(s:WARNING,"ignoring usermap <".string(umap[0])."> -- not a [string,funcref] entry",99)
-     endif
+        call netrw#ErrorMsg(s:WARNING,"ignoring usermap <".string(umap[0])."> -- not a [string,funcref] entry",99)
+      endif
     endfor
-   endif
-"  call Dret("netrw#UserMaps")
+  endif
+  "  call Dret("netrw#UserMaps")
 endfun
 
 " ---------------------------------------------------------------------
 " netrw#WinPath: tries to insure that the path is windows-acceptable, whether cygwin is used or not {{{2
 fun! netrw#WinPath(path)
-"  call Dfunc("netrw#WinPath(path<".a:path.">)")
+  "  call Dfunc("netrw#WinPath(path<".a:path.">)")
   if (!g:netrw_cygwin || &shell !~ '\%(\\|\\)\%(\.exe\)\=$') && has("win32")
-   " remove cygdrive prefix, if present
-   let path = substitute(a:path,g:netrw_cygdrive.'/\(.\)','\1:','')
-   " remove trailing slash (Win95)
-   let path = substitute(path, '\(\\\|/\)$', '', 'g')
-   " remove escaped spaces
-   let path = substitute(path, '\ ', ' ', 'g')
-   " convert slashes to backslashes
-   let path = substitute(path, '/', '\', 'g')
-  else
-   let path= a:path
-  endif
-"  call Dret("netrw#WinPath <".path.">")
+    " remove cygdrive prefix, if present
+    let path = substitute(a:path,g:netrw_cygdrive.'/\(.\)','\1:','')
+    " remove trailing slash (Win95)
+    let path = substitute(path, '\(\\\|/\)$', '', 'g')
+    " remove escaped spaces
+    let path = substitute(path, '\ ', ' ', 'g')
+    " convert slashes to backslashes
+    let path = substitute(path, '/', '\', 'g')
+  else
+    let path= a:path
+  endif
+  "  call Dret("netrw#WinPath <".path.">")
   return path
 endfun
 
 " ---------------------------------------------------------------------
 " s:StripTrailingSlash: removes trailing slashes from a path {{{2
 fun! s:StripTrailingSlash(path)
-   " remove trailing slash
-   return substitute(a:path, '[/\\]$', '', 'g')
+  " remove trailing slash
+  return substitute(a:path, '[/\\]$', '', 'g')
 endfun
 
 " ---------------------------------------------------------------------
@@ -10811,85 +10811,85 @@ endfun
 "              cB : bl2mf=1  use bufferlist to mark files
 "              (mnemonic: cb = copy (marked files) to buffer list)
 fun! s:NetrwBadd(islocal,bl2mf)
-"  "  call Dfunc("s:NetrwBadd(islocal=".a:islocal." mf2bl=".mf2bl.")")
+  "  "  call Dfunc("s:NetrwBadd(islocal=".a:islocal." mf2bl=".mf2bl.")")
   if a:bl2mf
-   " cB: add buffer list to marked files
-   redir => bufl
+    " cB: add buffer list to marked files
+    redir => bufl
     ls
-   redir END
-   let bufl = map(split(bufl,"\n"),'substitute(v:val,''^.\{-}"\(.*\)".\{-}$'',''\1'','''')')
-   for fname in bufl
-    call s:NetrwMarkFile(a:islocal,fname)
-   endfor
+    redir END
+    let bufl = map(split(bufl,"\n"),'substitute(v:val,''^.\{-}"\(.*\)".\{-}$'',''\1'','''')')
+    for fname in bufl
+      call s:NetrwMarkFile(a:islocal,fname)
+    endfor
   else
-   " cb: add marked files to buffer list
-   for fname in s:netrwmarkfilelist_{bufnr("%")}
-" "   call Decho("badd ".fname,'~'.expand(""))
-    exe "badd ".fnameescape(fname)
-   endfor
-   let curbufnr = bufnr("%")
-   let curdir   = s:NetrwGetCurdir(a:islocal)
-   call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
-  endif
-"  call Dret("s:NetrwBadd")
+    " cb: add marked files to buffer list
+    for fname in s:netrwmarkfilelist_{bufnr("%")}
+      " "   call Decho("badd ".fname,'~'.expand(""))
+      exe "badd ".fnameescape(fname)
+    endfor
+    let curbufnr = bufnr("%")
+    let curdir   = s:NetrwGetCurdir(a:islocal)
+    call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
+  endif
+  "  call Dret("s:NetrwBadd")
 endfun
 
 " ---------------------------------------------------------------------
 "  s:ComposePath: Appends a new part to a path taking different systems into consideration {{{2
 fun! s:ComposePath(base,subdir)
-"  call Dfunc("s:ComposePath(base<".a:base."> subdir<".a:subdir.">)")
+  "  call Dfunc("s:ComposePath(base<".a:base."> subdir<".a:subdir.">)")
 
   if has("amiga")
-"   call Decho("amiga",'~'.expand(""))
-   let ec = a:base[s:Strlen(a:base)-1]
-   if ec != '/' && ec != ':'
-    let ret = a:base."/" . a:subdir
-   else
-    let ret = a:base.a:subdir
-   endif
-
-   " COMBAK: test on windows with changing to root directory: :e C:/
+    "   call Decho("amiga",'~'.expand(""))
+    let ec = a:base[s:Strlen(a:base)-1]
+    if ec != '/' && ec != ':'
+      let ret = a:base."/" . a:subdir
+    else
+      let ret = a:base.a:subdir
+    endif
+
+    " COMBAK: test on windows with changing to root directory: :e C:/
   elseif a:subdir =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32")
-"   call Decho("windows",'~'.expand(""))
-   let ret= a:subdir
+    "   call Decho("windows",'~'.expand(""))
+    let ret= a:subdir
 
   elseif a:base =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32")
-"   call Decho("windows",'~'.expand(""))
-   if a:base =~ '[/\\]$'
-    let ret= a:base.a:subdir
-   else
-    let ret= a:base.'/'.a:subdir
-   endif
+    "   call Decho("windows",'~'.expand(""))
+    if a:base =~ '[/\\]$'
+      let ret= a:base.a:subdir
+    else
+      let ret= a:base.'/'.a:subdir
+    endif
 
   elseif a:base =~ '^\a\{3,}://'
-"   call Decho("remote linux/macos",'~'.expand(""))
-   let urlbase = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\1','')
-   let curpath = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\2','')
-   if a:subdir == '../'
-    if curpath =~ '[^/]/[^/]\+/$'
-     let curpath= substitute(curpath,'[^/]\+/$','','')
+    "   call Decho("remote linux/macos",'~'.expand(""))
+    let urlbase = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\1','')
+    let curpath = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\2','')
+    if a:subdir == '../'
+      if curpath =~ '[^/]/[^/]\+/$'
+        let curpath= substitute(curpath,'[^/]\+/$','','')
+      else
+        let curpath=""
+      endif
+      let ret= urlbase.curpath
     else
-     let curpath=""
+      let ret= urlbase.curpath.a:subdir
     endif
-    let ret= urlbase.curpath
-   else
-    let ret= urlbase.curpath.a:subdir
-   endif
-"   call Decho("urlbase<".urlbase.">",'~'.expand(""))
-"   call Decho("curpath<".curpath.">",'~'.expand(""))
-"   call Decho("ret<".ret.">",'~'.expand(""))
+    "   call Decho("urlbase<".urlbase.">",'~'.expand(""))
+    "   call Decho("curpath<".curpath.">",'~'.expand(""))
+    "   call Decho("ret<".ret.">",'~'.expand(""))
 
   else
-"   call Decho("local linux/macos",'~'.expand(""))
-   let ret = substitute(a:base."/".a:subdir,"//","/","g")
-   if a:base =~ '^//'
-    " keeping initial '//' for the benefit of network share listing support
-    let ret= '/'.ret
-   endif
-   let ret= simplify(ret)
+    "   call Decho("local linux/macos",'~'.expand(""))
+    let ret = substitute(a:base."/".a:subdir,"//","/","g")
+    if a:base =~ '^//'
+      " keeping initial '//' for the benefit of network share listing support
+      let ret= '/'.ret
+    endif
+    let ret= simplify(ret)
   endif
 
-"  call Dret("s:ComposePath ".ret)
+  "  call Dret("s:ComposePath ".ret)
   return ret
 endfun
 
@@ -10897,41 +10897,41 @@ endfun
 " s:DeleteBookmark: deletes a file/directory from Netrw's bookmark system {{{2
 "   Related Functions: s:MakeBookmark() s:NetrwBookHistHandler() s:NetrwBookmark()
 fun! s:DeleteBookmark(fname)
-"  call Dfunc("s:DeleteBookmark(fname<".a:fname.">)")
+  "  call Dfunc("s:DeleteBookmark(fname<".a:fname.">)")
   call s:MergeBookmarks()
 
   if exists("g:netrw_bookmarklist")
-   let indx= index(g:netrw_bookmarklist,a:fname)
-   if indx == -1
-    let indx= 0
-    while indx < len(g:netrw_bookmarklist)
-     if g:netrw_bookmarklist[indx] =~ a:fname
+    let indx= index(g:netrw_bookmarklist,a:fname)
+    if indx == -1
+      let indx= 0
+      while indx < len(g:netrw_bookmarklist)
+        if g:netrw_bookmarklist[indx] =~ a:fname
+          call remove(g:netrw_bookmarklist,indx)
+          let indx= indx - 1
+        endif
+        let indx= indx + 1
+      endwhile
+    else
+      " remove exact match
       call remove(g:netrw_bookmarklist,indx)
-      let indx= indx - 1
-     endif
-     let indx= indx + 1
-    endwhile
-   else
-    " remove exact match
-    call remove(g:netrw_bookmarklist,indx)
-   endif
+    endif
   endif
 
-"  call Dret("s:DeleteBookmark")
+  "  call Dret("s:DeleteBookmark")
 endfun
 
 " ---------------------------------------------------------------------
 " s:FileReadable: o/s independent filereadable {{{2
 fun! s:FileReadable(fname)
-"  call Dfunc("s:FileReadable(fname<".a:fname.">)")
+  "  call Dfunc("s:FileReadable(fname<".a:fname.">)")
 
   if g:netrw_cygwin
-   let ret= filereadable(s:NetrwFile(substitute(a:fname,g:netrw_cygdrive.'/\(.\)','\1:/','')))
+    let ret= filereadable(s:NetrwFile(substitute(a:fname,g:netrw_cygdrive.'/\(.\)','\1:/','')))
   else
-   let ret= filereadable(s:NetrwFile(a:fname))
+    let ret= filereadable(s:NetrwFile(a:fname))
   endif
 
-"  call Dret("s:FileReadable ".ret)
+  "  call Dret("s:FileReadable ".ret)
   return ret
 endfun
 
@@ -10940,68 +10940,68 @@ endfun
 "                 Places correct suffix on end of temporary filename,
 "                 using the suffix provided with fname
 fun! s:GetTempfile(fname)
-"  call Dfunc("s:GetTempfile(fname<".a:fname.">)")
+  "  call Dfunc("s:GetTempfile(fname<".a:fname.">)")
 
   if !exists("b:netrw_tmpfile")
-   " get a brand new temporary filename
-   let tmpfile= tempname()
-"   call Decho("tmpfile<".tmpfile."> : from tempname()",'~'.expand(""))
-
-   let tmpfile= substitute(tmpfile,'\','/','ge')
-"   call Decho("tmpfile<".tmpfile."> : chgd any \\ -> /",'~'.expand(""))
-
-   " sanity check -- does the temporary file's directory exist?
-   if !isdirectory(s:NetrwFile(substitute(tmpfile,'[^/]\+$','','e')))
-"    call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your <".substitute(tmpfile,'[^/]\+$','','e')."> directory is missing!",2)
-"    call Dret("s:GetTempfile getcwd<".getcwd().">")
-    return ""
-   endif
-
-   " let netrw#NetSource() know about the tmpfile
-   let s:netrw_tmpfile= tmpfile " used by netrw#NetSource() and netrw#BrowseX()
-"   call Decho("tmpfile<".tmpfile."> s:netrw_tmpfile<".s:netrw_tmpfile.">",'~'.expand(""))
-
-   " o/s dependencies
-   if g:netrw_cygwin != 0
-    let tmpfile = substitute(tmpfile,'^\(\a\):',g:netrw_cygdrive.'/\1','e')
-   elseif has("win32")
-    if !exists("+shellslash") || !&ssl
-     let tmpfile = substitute(tmpfile,'/','\','g')
-    endif
-   else
-    let tmpfile = tmpfile
-   endif
-   let b:netrw_tmpfile= tmpfile
-"   call Decho("o/s dependent fixed tempname<".tmpfile.">",'~'.expand(""))
+    " get a brand new temporary filename
+    let tmpfile= tempname()
+    "   call Decho("tmpfile<".tmpfile."> : from tempname()",'~'.expand(""))
+
+    let tmpfile= substitute(tmpfile,'\','/','ge')
+    "   call Decho("tmpfile<".tmpfile."> : chgd any \\ -> /",'~'.expand(""))
+
+    " sanity check -- does the temporary file's directory exist?
+    if !isdirectory(s:NetrwFile(substitute(tmpfile,'[^/]\+$','','e')))
+      "    call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+      NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your <".substitute(tmpfile,'[^/]\+$','','e')."> directory is missing!",2)
+      "    call Dret("s:GetTempfile getcwd<".getcwd().">")
+      return ""
+    endif
+
+    " let netrw#NetSource() know about the tmpfile
+    let s:netrw_tmpfile= tmpfile " used by netrw#NetSource() and netrw#BrowseX()
+    "   call Decho("tmpfile<".tmpfile."> s:netrw_tmpfile<".s:netrw_tmpfile.">",'~'.expand(""))
+
+    " o/s dependencies
+    if g:netrw_cygwin != 0
+      let tmpfile = substitute(tmpfile,'^\(\a\):',g:netrw_cygdrive.'/\1','e')
+    elseif has("win32")
+      if !exists("+shellslash") || !&ssl
+        let tmpfile = substitute(tmpfile,'/','\','g')
+      endif
+    else
+      let tmpfile = tmpfile
+    endif
+    let b:netrw_tmpfile= tmpfile
+    "   call Decho("o/s dependent fixed tempname<".tmpfile.">",'~'.expand(""))
   else
-   " re-use temporary filename
-   let tmpfile= b:netrw_tmpfile
-"   call Decho("tmpfile<".tmpfile."> re-using",'~'.expand(""))
+    " re-use temporary filename
+    let tmpfile= b:netrw_tmpfile
+    "   call Decho("tmpfile<".tmpfile."> re-using",'~'.expand(""))
   endif
 
   " use fname's suffix for the temporary file
   if a:fname != ""
-   if a:fname =~ '\.[^./]\+$'
-"    call Decho("using fname<".a:fname.">'s suffix",'~'.expand(""))
-    if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$'
-     let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
-    elseif a:fname =~ '.txz$'
-     let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
-    else
-     let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+    if a:fname =~ '\.[^./]\+$'
+      "    call Decho("using fname<".a:fname.">'s suffix",'~'.expand(""))
+      if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$'
+        let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+      elseif a:fname =~ '.txz$'
+        let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+      else
+        let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+      endif
+      "    call Decho("suffix<".suffix.">",'~'.expand(""))
+      let tmpfile= substitute(tmpfile,'\.tmp$','','e')
+      "    call Decho("chgd tmpfile<".tmpfile."> (removed any .tmp suffix)",'~'.expand(""))
+      let tmpfile .= suffix
+      "    call Decho("chgd tmpfile<".tmpfile."> (added ".suffix." suffix) netrw_fname<".b:netrw_fname.">",'~'.expand(""))
+      let s:netrw_tmpfile= tmpfile " supports netrw#NetSource()
     endif
-"    call Decho("suffix<".suffix.">",'~'.expand(""))
-    let tmpfile= substitute(tmpfile,'\.tmp$','','e')
-"    call Decho("chgd tmpfile<".tmpfile."> (removed any .tmp suffix)",'~'.expand(""))
-    let tmpfile .= suffix
-"    call Decho("chgd tmpfile<".tmpfile."> (added ".suffix." suffix) netrw_fname<".b:netrw_fname.">",'~'.expand(""))
-    let s:netrw_tmpfile= tmpfile " supports netrw#NetSource()
-   endif
   endif
 
-"  call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-"  call Dret("s:GetTempfile <".tmpfile.">")
+  "  call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+  "  call Dret("s:GetTempfile <".tmpfile.">")
   return tmpfile
 endfun
 
@@ -11009,167 +11009,167 @@ endfun
 " s:MakeSshCmd: transforms input command using USEPORT HOSTNAME into {{{2
 "               a correct command for use with a system() call
 fun! s:MakeSshCmd(sshcmd)
-"  call Dfunc("s:MakeSshCmd(sshcmd<".a:sshcmd.">) user<".s:user."> machine<".s:machine.">")
+  "  call Dfunc("s:MakeSshCmd(sshcmd<".a:sshcmd.">) user<".s:user."> machine<".s:machine.">")
   if s:user == ""
-   let sshcmd = substitute(a:sshcmd,'\',s:machine,'')
+    let sshcmd = substitute(a:sshcmd,'\',s:machine,'')
   else
-   let sshcmd = substitute(a:sshcmd,'\',s:user."@".s:machine,'')
+    let sshcmd = substitute(a:sshcmd,'\',s:user."@".s:machine,'')
   endif
   if exists("g:netrw_port") && g:netrw_port != ""
-   let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'')
+    let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'')
   elseif exists("s:port") && s:port != ""
-   let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'')
+    let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'')
   else
-   let sshcmd= substitute(sshcmd,"USEPORT ",'','')
+    let sshcmd= substitute(sshcmd,"USEPORT ",'','')
   endif
-"  call Dret("s:MakeSshCmd <".sshcmd.">")
+  "  call Dret("s:MakeSshCmd <".sshcmd.">")
   return sshcmd
 endfun
 
 " ---------------------------------------------------------------------
 " s:MakeBookmark: enters a bookmark into Netrw's bookmark system   {{{2
 fun! s:MakeBookmark(fname)
-"  call Dfunc("s:MakeBookmark(fname<".a:fname.">)")
+  "  call Dfunc("s:MakeBookmark(fname<".a:fname.">)")
 
   if !exists("g:netrw_bookmarklist")
-   let g:netrw_bookmarklist= []
+    let g:netrw_bookmarklist= []
   endif
 
   if index(g:netrw_bookmarklist,a:fname) == -1
-   " curdir not currently in g:netrw_bookmarklist, so include it
-   if isdirectory(s:NetrwFile(a:fname)) && a:fname !~ '/$'
-    call add(g:netrw_bookmarklist,a:fname.'/')
-   elseif a:fname !~ '/'
-    call add(g:netrw_bookmarklist,getcwd()."/".a:fname)
-   else
-    call add(g:netrw_bookmarklist,a:fname)
-   endif
-   call sort(g:netrw_bookmarklist)
-  endif
-
-"  call Dret("s:MakeBookmark")
+    " curdir not currently in g:netrw_bookmarklist, so include it
+    if isdirectory(s:NetrwFile(a:fname)) && a:fname !~ '/$'
+      call add(g:netrw_bookmarklist,a:fname.'/')
+    elseif a:fname !~ '/'
+      call add(g:netrw_bookmarklist,getcwd()."/".a:fname)
+    else
+      call add(g:netrw_bookmarklist,a:fname)
+    endif
+    call sort(g:netrw_bookmarklist)
+  endif
+
+  "  call Dret("s:MakeBookmark")
 endfun
 
 " ---------------------------------------------------------------------
 " s:MergeBookmarks: merge current bookmarks with saved bookmarks {{{2
 fun! s:MergeBookmarks()
-"  call Dfunc("s:MergeBookmarks() : merge current bookmarks into .netrwbook")
+  "  call Dfunc("s:MergeBookmarks() : merge current bookmarks into .netrwbook")
   " get bookmarks from .netrwbook file
   let savefile= s:NetrwHome()."/.netrwbook"
   if filereadable(s:NetrwFile(savefile))
-"   call Decho("merge bookmarks (active and file)",'~'.expand(""))
-   NetrwKeepj call s:NetrwBookHistSave()
-"   call Decho("bookmark delete savefile<".savefile.">",'~'.expand(""))
-   NetrwKeepj call delete(savefile)
+    "   call Decho("merge bookmarks (active and file)",'~'.expand(""))
+    NetrwKeepj call s:NetrwBookHistSave()
+    "   call Decho("bookmark delete savefile<".savefile.">",'~'.expand(""))
+    NetrwKeepj call delete(savefile)
   endif
-"  call Dret("s:MergeBookmarks")
+  "  call Dret("s:MergeBookmarks")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwBMShow: {{{2
 fun! s:NetrwBMShow()
-"  call Dfunc("s:NetrwBMShow()")
+  "  call Dfunc("s:NetrwBMShow()")
   redir => bmshowraw
-   menu
+  menu
   redir END
   let bmshowlist = split(bmshowraw,'\n')
   if bmshowlist != []
-   let bmshowfuncs= filter(bmshowlist,'v:val =~# "\\d\\+_BMShow()"')
-   if bmshowfuncs != []
-    let bmshowfunc = substitute(bmshowfuncs[0],'^.*:\(call.*BMShow()\).*$','\1','')
-    if bmshowfunc =~# '^call.*BMShow()'
-     exe "sil! NetrwKeepj ".bmshowfunc
+    let bmshowfuncs= filter(bmshowlist,'v:val =~# "\\d\\+_BMShow()"')
+    if bmshowfuncs != []
+      let bmshowfunc = substitute(bmshowfuncs[0],'^.*:\(call.*BMShow()\).*$','\1','')
+      if bmshowfunc =~# '^call.*BMShow()'
+        exe "sil! NetrwKeepj ".bmshowfunc
+      endif
     endif
-   endif
   endif
-"  call Dret("s:NetrwBMShow : bmshowfunc<".(exists("bmshowfunc")? bmshowfunc : 'n/a').">")
+  "  call Dret("s:NetrwBMShow : bmshowfunc<".(exists("bmshowfunc")? bmshowfunc : 'n/a').">")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwCursor: responsible for setting cursorline/cursorcolumn based upon g:netrw_cursor {{{2
 fun! s:NetrwCursor(editfile)
   if !exists("w:netrw_liststyle")
-   let w:netrw_liststyle= g:netrw_liststyle
+    let w:netrw_liststyle= g:netrw_liststyle
   endif
-"  call Dfunc("s:NetrwCursor() ft<".&ft."> liststyle=".w:netrw_liststyle." g:netrw_cursor=".g:netrw_cursor." s:netrw_usercuc=".s:netrw_usercuc." s:netrw_usercul=".s:netrw_usercul)
+  "  call Dfunc("s:NetrwCursor() ft<".&ft."> liststyle=".w:netrw_liststyle." g:netrw_cursor=".g:netrw_cursor." s:netrw_usercuc=".s:netrw_usercuc." s:netrw_usercul=".s:netrw_usercul)
 
-"  call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
+  "  call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
 
   if &ft != "netrw"
-   " if the current window isn't a netrw directory listing window, then use user cursorline/column
-   " settings.  Affects when netrw is used to read/write a file using scp/ftp/etc.
-"   call Decho("case ft!=netrw: use user cul,cuc",'~'.expand(""))
+    " if the current window isn't a netrw directory listing window, then use user cursorline/column
+    " settings.  Affects when netrw is used to read/write a file using scp/ftp/etc.
+    "   call Decho("case ft!=netrw: use user cul,cuc",'~'.expand(""))
 
   elseif g:netrw_cursor == 8
-   if w:netrw_liststyle == s:WIDELIST
-    setl cursorline
-    setl cursorcolumn
-   else
-    setl cursorline
-   endif
+    if w:netrw_liststyle == s:WIDELIST
+      setl cursorline
+      setl cursorcolumn
+    else
+      setl cursorline
+    endif
   elseif g:netrw_cursor == 7
     setl cursorline
   elseif g:netrw_cursor == 6
-   if w:netrw_liststyle == s:WIDELIST
-    setl cursorline
-   endif
+    if w:netrw_liststyle == s:WIDELIST
+      setl cursorline
+    endif
   elseif g:netrw_cursor == 4
-   " all styles: cursorline, cursorcolumn
-"   call Decho("case g:netrw_cursor==4: setl cul cuc",'~'.expand(""))
-   setl cursorline
-   setl cursorcolumn
-
-  elseif g:netrw_cursor == 3
-   " thin-long-tree: cursorline, user's cursorcolumn
-   " wide          : cursorline, cursorcolumn
-   if w:netrw_liststyle == s:WIDELIST
-"    call Decho("case g:netrw_cursor==3 and wide: setl cul cuc",'~'.expand(""))
+    " all styles: cursorline, cursorcolumn
+    "   call Decho("case g:netrw_cursor==4: setl cul cuc",'~'.expand(""))
     setl cursorline
     setl cursorcolumn
-   else
-"    call Decho("case g:netrw_cursor==3 and not wide: setl cul (use user's cuc)",'~'.expand(""))
-    setl cursorline
-   endif
+
+  elseif g:netrw_cursor == 3
+    " thin-long-tree: cursorline, user's cursorcolumn
+    " wide          : cursorline, cursorcolumn
+    if w:netrw_liststyle == s:WIDELIST
+      "    call Decho("case g:netrw_cursor==3 and wide: setl cul cuc",'~'.expand(""))
+      setl cursorline
+      setl cursorcolumn
+    else
+      "    call Decho("case g:netrw_cursor==3 and not wide: setl cul (use user's cuc)",'~'.expand(""))
+      setl cursorline
+    endif
 
   elseif g:netrw_cursor == 2
-   " thin-long-tree: cursorline, user's cursorcolumn
-   " wide          : cursorline, user's cursorcolumn
-"   call Decho("case g:netrw_cursor==2: setl cuc (use user's cul)",'~'.expand(""))
-   setl cursorline
+    " thin-long-tree: cursorline, user's cursorcolumn
+    " wide          : cursorline, user's cursorcolumn
+    "   call Decho("case g:netrw_cursor==2: setl cuc (use user's cul)",'~'.expand(""))
+    setl cursorline
 
   elseif g:netrw_cursor == 1
-   " thin-long-tree: user's cursorline, user's cursorcolumn
-   " wide          : cursorline,        user's cursorcolumn
-   if w:netrw_liststyle == s:WIDELIST
-"    call Decho("case g:netrw_cursor==2 and wide: setl cul (use user's cuc)",'~'.expand(""))
-    setl cursorline
-   else
-"    call Decho("case g:netrw_cursor==2 and not wide: (use user's cul,cuc)",'~'.expand(""))
-   endif
+    " thin-long-tree: user's cursorline, user's cursorcolumn
+    " wide          : cursorline,        user's cursorcolumn
+    if w:netrw_liststyle == s:WIDELIST
+      "    call Decho("case g:netrw_cursor==2 and wide: setl cul (use user's cuc)",'~'.expand(""))
+      setl cursorline
+    else
+      "    call Decho("case g:netrw_cursor==2 and not wide: (use user's cul,cuc)",'~'.expand(""))
+    endif
 
   else
-   " all styles: user's cursorline, user's cursorcolumn
-"   call Decho("default: (use user's cul,cuc)",'~'.expand(""))
-   let &l:cursorline   = s:netrw_usercul
-   let &l:cursorcolumn = s:netrw_usercuc
+    " all styles: user's cursorline, user's cursorcolumn
+    "   call Decho("default: (use user's cul,cuc)",'~'.expand(""))
+    let &l:cursorline   = s:netrw_usercul
+    let &l:cursorcolumn = s:netrw_usercuc
   endif
 
-" call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
-"  call Dret("s:NetrwCursor : l:cursorline=".&l:cursorline." l:cursorcolumn=".&l:cursorcolumn)
+  " call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
+  "  call Dret("s:NetrwCursor : l:cursorline=".&l:cursorline." l:cursorcolumn=".&l:cursorcolumn)
 endfun
 
 " ---------------------------------------------------------------------
 " s:RestoreCursorline: restores cursorline/cursorcolumn to original user settings {{{2
 fun! s:RestoreCursorline()
-"  call Dfunc("s:RestoreCursorline() currently, cul=".&l:cursorline." cuc=".&l:cursorcolumn." win#".winnr()." buf#".bufnr("%"))
+  "  call Dfunc("s:RestoreCursorline() currently, cul=".&l:cursorline." cuc=".&l:cursorcolumn." win#".winnr()." buf#".bufnr("%"))
   if exists("s:netrw_usercul")
-   let &l:cursorline   = s:netrw_usercul
+    let &l:cursorline   = s:netrw_usercul
   endif
   if exists("s:netrw_usercuc")
-   let &l:cursorcolumn = s:netrw_usercuc
+    let &l:cursorcolumn = s:netrw_usercuc
   endif
-"  call Decho("(s:RestoreCursorline) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
-"  call Dret("s:RestoreCursorline : restored cul=".&l:cursorline." cuc=".&l:cursorcolumn)
+  "  call Decho("(s:RestoreCursorline) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
+  "  call Dret("s:RestoreCursorline : restored cul=".&l:cursorline." cuc=".&l:cursorcolumn)
 endfun
 
 " s:RestoreRegister: restores all registers given in the dict {{{2
@@ -11188,28 +11188,28 @@ endfun
 "           acceptable.  No effect on Unix paths.
 "  Examples of use:  let result= s:NetrwDelete(path)
 fun! s:NetrwDelete(path)
-"  call Dfunc("s:NetrwDelete(path<".a:path.">)")
+  "  call Dfunc("s:NetrwDelete(path<".a:path.">)")
 
   let path = netrw#WinPath(a:path)
   if !g:netrw_cygwin && has("win32")
-   if exists("+shellslash")
-    let sskeep= &shellslash
-    setl noshellslash
-    let result      = delete(path)
-    let &shellslash = sskeep
-   else
-"    call Decho("exe let result= ".a:cmd."('".path."')",'~'.expand(""))
-    let result= delete(path)
-   endif
+    if exists("+shellslash")
+      let sskeep= &shellslash
+      setl noshellslash
+      let result      = delete(path)
+      let &shellslash = sskeep
+    else
+      "    call Decho("exe let result= ".a:cmd."('".path."')",'~'.expand(""))
+      let result= delete(path)
+    endif
   else
-"   call Decho("let result= delete(".path.")",'~'.expand(""))
-   let result= delete(path)
+    "   call Decho("let result= delete(".path.")",'~'.expand(""))
+    let result= delete(path)
   endif
   if result < 0
-   NetrwKeepj call netrw#ErrorMsg(s:WARNING,"delete(".path.") failed!",71)
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"delete(".path.") failed!",71)
   endif
 
-"  call Dret("s:NetrwDelete ".result)
+  "  call Dret("s:NetrwDelete ".result)
   return result
 endfun
 
@@ -11220,25 +11220,25 @@ endfun
 "                    is unnamed
 "                    does not appear in any window
 fun! s:NetrwBufRemover(bufid)
-"  call Dfunc("s:NetrwBufRemover(".a:bufid.")")
-"  call Decho("buf#".a:bufid."           ".((a:bufid > 1)? ">" : "≯")." must be >1 for removal","~".expand(""))
-"  call Decho("buf#".a:bufid." is        ".(buflisted(a:bufid)? "listed" : "unlisted"),"~".expand(""))
-"  call Decho("buf#".a:bufid." has name <".bufname(a:bufid).">","~".expand(""))
-"  call Decho("buf#".a:bufid." has winid#".bufwinid(a:bufid),"~".expand(""))
+  "  call Dfunc("s:NetrwBufRemover(".a:bufid.")")
+  "  call Decho("buf#".a:bufid."           ".((a:bufid > 1)? ">" : "≯")." must be >1 for removal","~".expand(""))
+  "  call Decho("buf#".a:bufid." is        ".(buflisted(a:bufid)? "listed" : "unlisted"),"~".expand(""))
+  "  call Decho("buf#".a:bufid." has name <".bufname(a:bufid).">","~".expand(""))
+  "  call Decho("buf#".a:bufid." has winid#".bufwinid(a:bufid),"~".expand(""))
 
   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(""))
-   exe "sil! bd! ".a:bufid
+    "   call Decho("(s:NetrwBufRemover) removing buffer#".a:bufid,"~".expand(""))
+    exe "sil! bd! ".a:bufid
   endif
 
-"  call Dret("s:NetrwBufRemover")
+  "  call Dret("s:NetrwBufRemover")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwEnew: opens a new buffer, passes netrw buffer variables through {{{2
 fun! s:NetrwEnew(...)
-"  call Dfunc("s:NetrwEnew() a:0=".a:0." win#".winnr()." winnr($)=".winnr("$")." bufnr($)=".bufnr("$")." expand(%)<".expand("%").">")
-"  call Decho("curdir<".((a:0>0)? a:1 : "")."> buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
+  "  call Dfunc("s:NetrwEnew() a:0=".a:0." win#".winnr()." winnr($)=".winnr("$")." bufnr($)=".bufnr("$")." expand(%)<".expand("%").">")
+  "  call Decho("curdir<".((a:0>0)? a:1 : "")."> buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
 
   " Clean out the last buffer:
   " Check if the last buffer has # > 1, is unlisted, is unnamed, and does not appear in a window
@@ -11246,7 +11246,7 @@ fun! s:NetrwEnew(...)
   call s:NetrwBufRemover(bufnr("$"))
 
   " grab a function-local-variable copy of buffer variables
-"  call Decho("make function-local copy of netrw variables",'~'.expand(""))
+  "  call Decho("make function-local copy of netrw variables",'~'.expand(""))
   if exists("b:netrw_bannercnt")      |let netrw_bannercnt       = b:netrw_bannercnt      |endif
   if exists("b:netrw_browser_active") |let netrw_browser_active  = b:netrw_browser_active |endif
   if exists("b:netrw_cpf")            |let netrw_cpf             = b:netrw_cpf            |endif
@@ -11265,19 +11265,19 @@ fun! s:NetrwEnew(...)
   if exists("b:netrw_prvdir")         |let netrw_prvdir          = b:netrw_prvdir         |endif
 
   NetrwKeepj call s:NetrwOptionsRestore("w:")
-"  call Decho("generate a buffer with NetrwKeepj enew!",'~'.expand(""))
+  "  call Decho("generate a buffer with NetrwKeepj enew!",'~'.expand(""))
   " when tree listing uses file TreeListing... a new buffer is made.
   " Want the old buffer to be unlisted.
   " COMBAK: this causes a problem, see P43
-"  setl nobl
+  "  setl nobl
   let netrw_keepdiff= &l:diff
   call s:NetrwEditFile("enew!","","")
   let &l:diff= netrw_keepdiff
-"  call Decho("bufnr($)=".bufnr("$")."<".bufname(bufnr("$"))."> winnr($)=".winnr("$"),'~'.expand(""))
+  "  call Decho("bufnr($)=".bufnr("$")."<".bufname(bufnr("$"))."> winnr($)=".winnr("$"),'~'.expand(""))
   NetrwKeepj call s:NetrwOptionsSave("w:")
 
   " copy function-local-variables to buffer variable equivalents
-"  call Decho("copy function-local variables back to buffer netrw variables",'~'.expand(""))
+  "  call Decho("copy function-local variables back to buffer netrw variables",'~'.expand(""))
   if exists("netrw_bannercnt")      |let b:netrw_bannercnt       = netrw_bannercnt      |endif
   if exists("netrw_browser_active") |let b:netrw_browser_active  = netrw_browser_active |endif
   if exists("netrw_cpf")            |let b:netrw_cpf             = netrw_cpf            |endif
@@ -11296,24 +11296,24 @@ fun! s:NetrwEnew(...)
   if exists("netrw_prvdir")         |let b:netrw_prvdir          = netrw_prvdir         |endif
 
   if a:0 > 0
-   let b:netrw_curdir= a:1
-   if b:netrw_curdir =~ '/$'
-    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-     setl nobl
-     file NetrwTreeListing
-     setl nobl bt=nowrite bh=hide
-     nno   [	:sil call TreeListMove('[')
-     nno   ]	:sil call TreeListMove(']')
-    else
-     call s:NetrwBufRename(b:netrw_curdir)
+    let b:netrw_curdir= a:1
+    if b:netrw_curdir =~ '/$'
+      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+        setl nobl
+        file NetrwTreeListing
+        setl nobl bt=nowrite bh=hide
+        nno   [    :sil call TreeListMove('[')
+        nno   ]    :sil call TreeListMove(']')
+      else
+        call s:NetrwBufRename(b:netrw_curdir)
+      endif
     endif
-   endif
   endif
   if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on")
-   let &l:bexpr = "netrw#BalloonHelp()"
+    let &l:bexpr = "netrw#BalloonHelp()"
   endif
 
-"  call Dret("s:NetrwEnew : buf#".bufnr("%")."<".bufname("%")."> expand(%)<".expand("%")."> expand(#)<".expand("#")."> bh=".&bh." win#".winnr()." winnr($)#".winnr("$"))
+  "  call Dret("s:NetrwEnew : buf#".bufnr("%")."<".bufname("%")."> expand(%)<".expand("%")."> expand(#)<".expand("#")."> bh=".&bh." win#".winnr()." winnr($)#".winnr("$"))
 endfun
 
 " ---------------------------------------------------------------------
@@ -11324,15 +11324,15 @@ fun! s:NetrwExe(cmd)
     set shell& shellcmdflag& shellxquote& shellxescape&
     set shellquote& shellpipe& shellredir& shellslash&
     try
-     exe a:cmd
+      exe a:cmd
     finally
       let [&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] = savedShell
     endtry
   else
-   exe a:cmd
+    exe a:cmd
   endif
   if v:shell_error
-   call netrw#ErrorMsg(s:WARNING,"shell signalled an error",106)
+    call netrw#ErrorMsg(s:WARNING,"shell signalled an error",106)
   endif
 endfun
 
@@ -11340,27 +11340,27 @@ endfun
 " s:NetrwInsureWinVars: insure that a netrw buffer has its w: variables in spite of a wincmd v or s {{{2
 fun! s:NetrwInsureWinVars()
   if !exists("w:netrw_liststyle")
-"   call Dfunc("s:NetrwInsureWinVars() win#".winnr())
-   let curbuf = bufnr("%")
-   let curwin = winnr()
-   let iwin   = 1
-   while iwin <= winnr("$")
-    exe iwin."wincmd w"
-    if winnr() != curwin && bufnr("%") == curbuf && exists("w:netrw_liststyle")
-     " looks like ctrl-w_s or ctrl-w_v was used to split a netrw buffer
-     let winvars= w:
-     break
-    endif
-    let iwin= iwin + 1
-   endwhile
-   exe "keepalt ".curwin."wincmd w"
-   if exists("winvars")
-"    call Decho("copying w#".iwin." window variables to w#".curwin,'~'.expand(""))
-    for k in keys(winvars)
-     let w:{k}= winvars[k]
-    endfor
-   endif
-"   call Dret("s:NetrwInsureWinVars win#".winnr())
+    "   call Dfunc("s:NetrwInsureWinVars() win#".winnr())
+    let curbuf = bufnr("%")
+    let curwin = winnr()
+    let iwin   = 1
+    while iwin <= winnr("$")
+      exe iwin."wincmd w"
+      if winnr() != curwin && bufnr("%") == curbuf && exists("w:netrw_liststyle")
+        " looks like ctrl-w_s or ctrl-w_v was used to split a netrw buffer
+        let winvars= w:
+        break
+      endif
+      let iwin= iwin + 1
+    endwhile
+    exe "keepalt ".curwin."wincmd w"
+    if exists("winvars")
+      "    call Decho("copying w#".iwin." window variables to w#".curwin,'~'.expand(""))
+      for k in keys(winvars)
+        let w:{k}= winvars[k]
+      endfor
+    endif
+    "   call Dret("s:NetrwInsureWinVars win#".winnr())
   endif
 endfun
 
@@ -11369,46 +11369,46 @@ endfun
 "   Returns: 0=success
 "           -1=failed
 fun! s:NetrwLcd(newdir)
-"  call Dfunc("s:NetrwLcd(newdir<".a:newdir.">)")
-"  call Decho("changing local directory",'~'.expand(""))
+  "  call Dfunc("s:NetrwLcd(newdir<".a:newdir.">)")
+  "  call Decho("changing local directory",'~'.expand(""))
 
   let err472= 0
   try
-   exe 'NetrwKeepj sil lcd '.fnameescape(a:newdir)
+    exe 'NetrwKeepj sil lcd '.fnameescape(a:newdir)
   catch /^Vim\%((\a\+)\)\=:E344/
-     " Vim's lcd fails with E344 when attempting to go above the 'root' of a Windows share.
-     " Therefore, detect if a Windows share is present, and if E344 occurs, just settle at
-     " 'root' (ie. '\').  The share name may start with either backslashes ('\\Foo') or
-     " forward slashes ('//Foo'), depending on whether backslashes have been converted to
-     " forward slashes by earlier code; so check for both.
-     if has("win32") && !g:netrw_cygwin
-       if a:newdir =~ '^\\\\\w\+' || a:newdir =~ '^//\w\+'
-         let dirname = '\'
-         exe 'NetrwKeepj sil lcd '.fnameescape(dirname)
-       endif
-     endif
+    " Vim's lcd fails with E344 when attempting to go above the 'root' of a Windows share.
+    " Therefore, detect if a Windows share is present, and if E344 occurs, just settle at
+    " 'root' (ie. '\').  The share name may start with either backslashes ('\\Foo') or
+    " forward slashes ('//Foo'), depending on whether backslashes have been converted to
+    " forward slashes by earlier code; so check for both.
+    if has("win32") && !g:netrw_cygwin
+      if a:newdir =~ '^\\\\\w\+' || a:newdir =~ '^//\w\+'
+        let dirname = '\'
+        exe 'NetrwKeepj sil lcd '.fnameescape(dirname)
+      endif
+    endif
   catch /^Vim\%((\a\+)\)\=:E472/
-   let err472= 1
+    let err472= 1
   endtry
 
   if err472
-   call netrw#ErrorMsg(s:ERROR,"unable to change directory to <".a:newdir."> (permissions?)",61)
-   if exists("w:netrw_prvdir")
-    let a:newdir= w:netrw_prvdir
-   else
-    call s:NetrwOptionsRestore("w:")
-"    call Decho("setl noma nomod nowrap",'~'.expand(""))
-    exe "setl ".g:netrw_bufsettings
-"    call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-    let a:newdir= dirname
-   endif
-"   call Dret("s:NetrwBrowse -1 : reusing buffer#".(exists("bufnum")? bufnum : 'N/A')."<".dirname."> getcwd<".getcwd().">")
-   return -1
+    call netrw#ErrorMsg(s:ERROR,"unable to change directory to <".a:newdir."> (permissions?)",61)
+    if exists("w:netrw_prvdir")
+      let a:newdir= w:netrw_prvdir
+    else
+      call s:NetrwOptionsRestore("w:")
+      "    call Decho("setl noma nomod nowrap",'~'.expand(""))
+      exe "setl ".g:netrw_bufsettings
+      "    call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+      let a:newdir= dirname
+    endif
+    "   call Dret("s:NetrwBrowse -1 : reusing buffer#".(exists("bufnum")? bufnum : 'N/A')."<".dirname."> getcwd<".getcwd().">")
+    return -1
   endif
 
-"  call Decho("getcwd        <".getcwd().">")
-"  call Decho("b:netrw_curdir<".b:netrw_curdir.">")
-"  call Dret("s:NetrwLcd 0")
+  "  call Decho("getcwd        <".getcwd().">")
+  "  call Decho("b:netrw_curdir<".b:netrw_curdir.">")
+  "  call Dret("s:NetrwLcd 0")
   return 0
 endfun
 
@@ -11416,52 +11416,52 @@ endfun
 " s:NetrwSaveWordPosn: used to keep cursor on same word after refresh, {{{2
 " changed sorting, etc.  Also see s:NetrwRestoreWordPosn().
 fun! s:NetrwSaveWordPosn()
-"  call Dfunc("NetrwSaveWordPosn()")
+  "  call Dfunc("NetrwSaveWordPosn()")
   let s:netrw_saveword= '^'.fnameescape(getline('.')).'$'
-"  call Dret("NetrwSaveWordPosn : saveword<".s:netrw_saveword.">")
+  "  call Dret("NetrwSaveWordPosn : saveword<".s:netrw_saveword.">")
 endfun
 
 " ---------------------------------------------------------------------
 " s:NetrwHumanReadable: takes a number and makes it "human readable" {{{2
 "                       1000 -> 1K, 1000000 -> 1M, 1000000000 -> 1G
 fun! s:NetrwHumanReadable(sz)
-"  call Dfunc("s:NetrwHumanReadable(sz=".a:sz.") type=".type(a:sz)." style=".g:netrw_sizestyle )
+  "  call Dfunc("s:NetrwHumanReadable(sz=".a:sz.") type=".type(a:sz)." style=".g:netrw_sizestyle )
 
   if g:netrw_sizestyle == 'h'
-   if a:sz >= 1000000000
-    let sz = printf("%.1f",a:sz/1000000000.0)."g"
-   elseif a:sz >= 10000000
-    let sz = printf("%d",a:sz/1000000)."m"
-   elseif a:sz >= 1000000
-    let sz = printf("%.1f",a:sz/1000000.0)."m"
-   elseif a:sz >= 10000
-    let sz = printf("%d",a:sz/1000)."k"
-   elseif a:sz >= 1000
-    let sz = printf("%.1f",a:sz/1000.0)."k"
-   else
-    let sz= a:sz
-   endif
+    if a:sz >= 1000000000
+      let sz = printf("%.1f",a:sz/1000000000.0)."g"
+    elseif a:sz >= 10000000
+      let sz = printf("%d",a:sz/1000000)."m"
+    elseif a:sz >= 1000000
+      let sz = printf("%.1f",a:sz/1000000.0)."m"
+    elseif a:sz >= 10000
+      let sz = printf("%d",a:sz/1000)."k"
+    elseif a:sz >= 1000
+      let sz = printf("%.1f",a:sz/1000.0)."k"
+    else
+      let sz= a:sz
+    endif
 
   elseif g:netrw_sizestyle == 'H'
-   if a:sz >= 1073741824
-    let sz = printf("%.1f",a:sz/1073741824.0)."G"
-   elseif a:sz >= 10485760
-    let sz = printf("%d",a:sz/1048576)."M"
-   elseif a:sz >= 1048576
-    let sz = printf("%.1f",a:sz/1048576.0)."M"
-   elseif a:sz >= 10240
-    let sz = printf("%d",a:sz/1024)."K"
-   elseif a:sz >= 1024
-    let sz = printf("%.1f",a:sz/1024.0)."K"
-   else
-    let sz= a:sz
-   endif
+    if a:sz >= 1073741824
+      let sz = printf("%.1f",a:sz/1073741824.0)."G"
+    elseif a:sz >= 10485760
+      let sz = printf("%d",a:sz/1048576)."M"
+    elseif a:sz >= 1048576
+      let sz = printf("%.1f",a:sz/1048576.0)."M"
+    elseif a:sz >= 10240
+      let sz = printf("%d",a:sz/1024)."K"
+    elseif a:sz >= 1024
+      let sz = printf("%.1f",a:sz/1024.0)."K"
+    else
+      let sz= a:sz
+    endif
 
   else
-   let sz= a:sz
+    let sz= a:sz
   endif
 
-"  call Dret("s:NetrwHumanReadable ".sz)
+  "  call Dret("s:NetrwHumanReadable ".sz)
   return sz
 endfun
 
@@ -11469,15 +11469,15 @@ endfun
 " s:NetrwRestoreWordPosn: used to keep cursor on same word after refresh, {{{2
 "  changed sorting, etc.  Also see s:NetrwSaveWordPosn().
 fun! s:NetrwRestoreWordPosn()
-"  call Dfunc("NetrwRestoreWordPosn()")
+  "  call Dfunc("NetrwRestoreWordPosn()")
   sil! call search(s:netrw_saveword,'w')
-"  call Dret("NetrwRestoreWordPosn")
+  "  call Dret("NetrwRestoreWordPosn")
 endfun
 
 " ---------------------------------------------------------------------
 " s:RestoreBufVars: {{{2
 fun! s:RestoreBufVars()
-"  call Dfunc("s:RestoreBufVars()")
+  "  call Dfunc("s:RestoreBufVars()")
 
   if exists("s:netrw_curdir")        |let b:netrw_curdir         = s:netrw_curdir        |endif
   if exists("s:netrw_lastfile")      |let b:netrw_lastfile       = s:netrw_lastfile      |endif
@@ -11486,13 +11486,13 @@ fun! s:RestoreBufVars()
   if exists("s:netrw_machine")       |let b:netrw_machine        = s:netrw_machine       |endif
   if exists("s:netrw_browser_active")|let b:netrw_browser_active = s:netrw_browser_active|endif
 
-"  call Dret("s:RestoreBufVars")
+  "  call Dret("s:RestoreBufVars")
 endfun
 
 " ---------------------------------------------------------------------
 " s:RemotePathAnalysis: {{{2
 fun! s:RemotePathAnalysis(dirname)
-"  call Dfunc("s:RemotePathAnalysis(a:dirname<".a:dirname.">)")
+  "  call Dfunc("s:RemotePathAnalysis(a:dirname<".a:dirname.">)")
 
   "                method   ://    user  @      machine      :port            /path
   let dirpat  = '^\(\w\{-}\)://\(\(\w\+\)@\)\=\([^/:#]\+\)\%([:#]\(\d\+\)\)\=/\(.*\)$'
@@ -11503,19 +11503,19 @@ fun! s:RemotePathAnalysis(dirname)
   let s:path    = substitute(a:dirname,dirpat,'\6','')
   let s:fname   = substitute(s:path,'^.*/\ze.','','')
   if s:machine =~ '@'
-   let dirpat    = '^\(.*\)@\(.\{-}\)$'
-   let s:user    = s:user.'@'.substitute(s:machine,dirpat,'\1','')
-   let s:machine = substitute(s:machine,dirpat,'\2','')
+    let dirpat    = '^\(.*\)@\(.\{-}\)$'
+    let s:user    = s:user.'@'.substitute(s:machine,dirpat,'\1','')
+    let s:machine = substitute(s:machine,dirpat,'\2','')
   endif
 
-"  call Decho("set up s:method <".s:method .">",'~'.expand(""))
-"  call Decho("set up s:user   <".s:user   .">",'~'.expand(""))
-"  call Decho("set up s:machine<".s:machine.">",'~'.expand(""))
-"  call Decho("set up s:port   <".s:port.">",'~'.expand(""))
-"  call Decho("set up s:path   <".s:path   .">",'~'.expand(""))
-"  call Decho("set up s:fname  <".s:fname  .">",'~'.expand(""))
+  "  call Decho("set up s:method <".s:method .">",'~'.expand(""))
+  "  call Decho("set up s:user   <".s:user   .">",'~'.expand(""))
+  "  call Decho("set up s:machine<".s:machine.">",'~'.expand(""))
+  "  call Decho("set up s:port   <".s:port.">",'~'.expand(""))
+  "  call Decho("set up s:path   <".s:path   .">",'~'.expand(""))
+  "  call Decho("set up s:fname  <".s:fname  .">",'~'.expand(""))
 
-"  call Dret("s:RemotePathAnalysis")
+  "  call Dret("s:RemotePathAnalysis")
 endfun
 
 " ---------------------------------------------------------------------
@@ -11525,31 +11525,31 @@ endfun
 "    [cd REMOTEDIRPATH;] a:cmd
 " Note that it doesn't do s:ShellEscape(a:cmd)!
 fun! s:RemoteSystem(cmd)
-"  call Dfunc("s:RemoteSystem(cmd<".a:cmd.">)")
+  "  call Dfunc("s:RemoteSystem(cmd<".a:cmd.">)")
   if !executable(g:netrw_ssh_cmd)
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"g:netrw_ssh_cmd<".g:netrw_ssh_cmd."> is not executable!",52)
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"g:netrw_ssh_cmd<".g:netrw_ssh_cmd."> is not executable!",52)
   elseif !exists("b:netrw_curdir")
-   NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
   else
-   let cmd      = s:MakeSshCmd(g:netrw_ssh_cmd." USEPORT HOSTNAME")
-   let remotedir= substitute(b:netrw_curdir,'^.*//[^/]\+/\(.*\)$','\1','')
-   if remotedir != ""
-    let cmd= cmd.' cd '.s:ShellEscape(remotedir).";"
-   else
-    let cmd= cmd.' '
-   endif
-   let cmd= cmd.a:cmd
-"   call Decho("call system(".cmd.")",'~'.expand(""))
-   let ret= system(cmd)
-  endif
-"  call Dret("s:RemoteSystem ".ret)
+    let cmd      = s:MakeSshCmd(g:netrw_ssh_cmd." USEPORT HOSTNAME")
+    let remotedir= substitute(b:netrw_curdir,'^.*//[^/]\+/\(.*\)$','\1','')
+    if remotedir != ""
+      let cmd= cmd.' cd '.s:ShellEscape(remotedir).";"
+    else
+      let cmd= cmd.' '
+    endif
+    let cmd= cmd.a:cmd
+    "   call Decho("call system(".cmd.")",'~'.expand(""))
+    let ret= system(cmd)
+  endif
+  "  call Dret("s:RemoteSystem ".ret)
   return ret
 endfun
 
 " ---------------------------------------------------------------------
 " s:RestoreWinVars: (used by Explore() and NetrwSplit()) {{{2
 fun! s:RestoreWinVars()
-"  call Dfunc("s:RestoreWinVars()")
+  "  call Dfunc("s:RestoreWinVars()")
   if exists("s:bannercnt")      |let w:netrw_bannercnt       = s:bannercnt      |unlet s:bannercnt      |endif
   if exists("s:col")            |let w:netrw_col             = s:col            |unlet s:col            |endif
   if exists("s:curdir")         |let w:netrw_curdir          = s:curdir         |unlet s:curdir         |endif
@@ -11568,7 +11568,7 @@ fun! s:RestoreWinVars()
   if exists("s:treedict")       |let w:netrw_treedict        = s:treedict       |unlet s:treedict       |endif
   if exists("s:treetop")        |let w:netrw_treetop         = s:treetop        |unlet s:treetop        |endif
   if exists("s:winnr")          |let w:netrw_winnr           = s:winnr          |unlet s:winnr          |endif
-"  call Dret("s:RestoreWinVars")
+  "  call Dret("s:RestoreWinVars")
 endfun
 
 " ---------------------------------------------------------------------
@@ -11582,22 +11582,22 @@ endfun
 "             s:rexposn_BUFNR used to save/restore cursor position
 fun! s:NetrwRexplore(islocal,dirname)
   if exists("s:netrwdrag")
-   return
+    return
   endif
-"  call Dfunc("s:NetrwRexplore() w:netrw_rexlocal=".w:netrw_rexlocal." w:netrw_rexdir<".w:netrw_rexdir."> win#".winnr())
-"  call Decho("currently in bufname<".bufname("%").">",'~'.expand(""))
-"  call Decho("ft=".&ft." win#".winnr()." w:netrw_rexfile<".(exists("w:netrw_rexfile")? w:netrw_rexfile : 'n/a').">",'~'.expand(""))
+  "  call Dfunc("s:NetrwRexplore() w:netrw_rexlocal=".w:netrw_rexlocal." w:netrw_rexdir<".w:netrw_rexdir."> win#".winnr())
+  "  call Decho("currently in bufname<".bufname("%").">",'~'.expand(""))
+  "  call Decho("ft=".&ft." win#".winnr()." w:netrw_rexfile<".(exists("w:netrw_rexfile")? w:netrw_rexfile : 'n/a').">",'~'.expand(""))
 
   if &ft == "netrw" && exists("w:netrw_rexfile") && w:netrw_rexfile != ""
-   " a :Rex while in a netrw buffer means: edit the file in w:netrw_rexfile
-"   call Decho("in netrw buffer, will edit file<".w:netrw_rexfile.">",'~'.expand(""))
-   exe "NetrwKeepj e ".w:netrw_rexfile
-   unlet w:netrw_rexfile
-"   call Dret("s:NetrwRexplore returning from netrw to buf#".bufnr("%")."<".bufname("%").">  (ft=".&ft.")")
-   return
-"  else " Decho
-"   call Decho("treating as not-netrw-buffer: ft=".&ft.((&ft == "netrw")? " == netrw" : "!= netrw"),'~'.expand(""))
-"   call Decho("treating as not-netrw-buffer: w:netrw_rexfile<".((exists("w:netrw_rexfile"))? w:netrw_rexfile : 'n/a').">",'~'.expand(""))
+    " a :Rex while in a netrw buffer means: edit the file in w:netrw_rexfile
+    "   call Decho("in netrw buffer, will edit file<".w:netrw_rexfile.">",'~'.expand(""))
+    exe "NetrwKeepj e ".w:netrw_rexfile
+    unlet w:netrw_rexfile
+    "   call Dret("s:NetrwRexplore returning from netrw to buf#".bufnr("%")."<".bufname("%").">  (ft=".&ft.")")
+    return
+    "  else " Decho
+    "   call Decho("treating as not-netrw-buffer: ft=".&ft.((&ft == "netrw")? " == netrw" : "!= netrw"),'~'.expand(""))
+    "   call Decho("treating as not-netrw-buffer: w:netrw_rexfile<".((exists("w:netrw_rexfile"))? w:netrw_rexfile : 'n/a').">",'~'.expand(""))
   endif
 
   " ---------------------------
@@ -11606,48 +11606,48 @@ fun! s:NetrwRexplore(islocal,dirname)
 
   " record current file so :Rex can return to it from netrw
   let w:netrw_rexfile= expand("%")
-"  call Decho("set w:netrw_rexfile<".w:netrw_rexfile.">  (win#".winnr().")",'~'.expand(""))
+  "  call Decho("set w:netrw_rexfile<".w:netrw_rexfile.">  (win#".winnr().")",'~'.expand(""))
 
   if !exists("w:netrw_rexlocal")
-"   call Dret("s:NetrwRexplore w:netrw_rexlocal doesn't exist (".&ft." win#".winnr().")")
-   return
+    "   call Dret("s:NetrwRexplore w:netrw_rexlocal doesn't exist (".&ft." win#".winnr().")")
+    return
   endif
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
   if w:netrw_rexlocal
-   NetrwKeepj call netrw#LocalBrowseCheck(w:netrw_rexdir)
+    NetrwKeepj call netrw#LocalBrowseCheck(w:netrw_rexdir)
   else
-   NetrwKeepj call s:NetrwBrowse(0,w:netrw_rexdir)
+    NetrwKeepj call s:NetrwBrowse(0,w:netrw_rexdir)
   endif
   if exists("s:initbeval")
-   setl beval
+    setl beval
   endif
   if exists("s:rexposn_".bufnr("%"))
-"   call Decho("restore posn, then unlet s:rexposn_".bufnr('%')."<".bufname("%").">",'~'.expand(""))
-   " restore position in directory listing
-"   call Decho("restoring posn to s:rexposn_".bufnr('%')."<".string(s:rexposn_{bufnr('%')}).">",'~'.expand(""))
-   NetrwKeepj call winrestview(s:rexposn_{bufnr('%')})
-   if exists("s:rexposn_".bufnr('%'))
-    unlet s:rexposn_{bufnr('%')}
-   endif
+    "   call Decho("restore posn, then unlet s:rexposn_".bufnr('%')."<".bufname("%").">",'~'.expand(""))
+    " restore position in directory listing
+    "   call Decho("restoring posn to s:rexposn_".bufnr('%')."<".string(s:rexposn_{bufnr('%')}).">",'~'.expand(""))
+    NetrwKeepj call winrestview(s:rexposn_{bufnr('%')})
+    if exists("s:rexposn_".bufnr('%'))
+      unlet s:rexposn_{bufnr('%')}
+    endif
   else
-"   call Decho("s:rexposn_".bufnr('%')."<".bufname("%")."> doesn't exist",'~'.expand(""))
+    "   call Decho("s:rexposn_".bufnr('%')."<".bufname("%")."> doesn't exist",'~'.expand(""))
   endif
 
   if has("syntax") && exists("g:syntax_on") && g:syntax_on
-   if exists("s:explore_match")
-    exe "2match netrwMarkFile /".s:explore_match."/"
-   endif
+    if exists("s:explore_match")
+      exe "2match netrwMarkFile /".s:explore_match."/"
+    endif
   endif
 
-"  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
-"  call Dret("s:NetrwRexplore : ft=".&ft)
+  "  call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand(""))
+  "  call Dret("s:NetrwRexplore : ft=".&ft)
 endfun
 
 " ---------------------------------------------------------------------
 " s:SaveBufVars: save selected b: variables to s: variables {{{2
 "                use s:RestoreBufVars() to restore b: variables from s: variables
 fun! s:SaveBufVars()
-"  call Dfunc("s:SaveBufVars() buf#".bufnr("%"))
+  "  call Dfunc("s:SaveBufVars() buf#".bufnr("%"))
 
   if exists("b:netrw_curdir")        |let s:netrw_curdir         = b:netrw_curdir        |endif
   if exists("b:netrw_lastfile")      |let s:netrw_lastfile       = b:netrw_lastfile      |endif
@@ -11656,49 +11656,49 @@ fun! s:SaveBufVars()
   if exists("b:netrw_machine")       |let s:netrw_machine        = b:netrw_machine       |endif
   if exists("b:netrw_browser_active")|let s:netrw_browser_active = b:netrw_browser_active|endif
 
-"  call Dret("s:SaveBufVars")
+  "  call Dret("s:SaveBufVars")
 endfun
 
 " ---------------------------------------------------------------------
 " s:SavePosn: saves position associated with current buffer into a dictionary {{{2
 fun! s:SavePosn(posndict)
-"  call Dfunc("s:SavePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">")
+  "  call Dfunc("s:SavePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">")
 
   if !exists("a:posndict[bufnr('%')]")
-   let a:posndict[bufnr("%")]= []
+    let a:posndict[bufnr("%")]= []
   endif
-"  call Decho("before push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
+  "  call Decho("before push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
   call add(a:posndict[bufnr("%")],winsaveview())
-"  call Decho("after  push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
+  "  call Decho("after  push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
 
-"  call Dret("s:SavePosn posndict")
+  "  call Dret("s:SavePosn posndict")
   return a:posndict
 endfun
 
 " ---------------------------------------------------------------------
 " s:RestorePosn: restores position associated with current buffer using dictionary {{{2
 fun! s:RestorePosn(posndict)
-"  call Dfunc("s:RestorePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">")
+  "  call Dfunc("s:RestorePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">")
   if exists("a:posndict")
-   if has_key(a:posndict,bufnr("%"))
-"    call Decho("before pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
-    let posnlen= len(a:posndict[bufnr("%")])
-    if posnlen > 0
-     let posnlen= posnlen - 1
-"     call Decho("restoring posn posndict[".bufnr("%")."][".posnlen."]=".string(a:posndict[bufnr("%")][posnlen]),'~'.expand(""))
-     call winrestview(a:posndict[bufnr("%")][posnlen])
-     call remove(a:posndict[bufnr("%")],posnlen)
-"     call Decho("after  pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
-    endif
-   endif
-  endif
-"  call Dret("s:RestorePosn")
+    if has_key(a:posndict,bufnr("%"))
+      "    call Decho("before pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
+      let posnlen= len(a:posndict[bufnr("%")])
+      if posnlen > 0
+        let posnlen= posnlen - 1
+        "     call Decho("restoring posn posndict[".bufnr("%")."][".posnlen."]=".string(a:posndict[bufnr("%")][posnlen]),'~'.expand(""))
+        call winrestview(a:posndict[bufnr("%")][posnlen])
+        call remove(a:posndict[bufnr("%")],posnlen)
+        "     call Decho("after  pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')]))
+      endif
+    endif
+  endif
+  "  call Dret("s:RestorePosn")
 endfun
 
 " ---------------------------------------------------------------------
 " s:SaveWinVars: (used by Explore() and NetrwSplit()) {{{2
 fun! s:SaveWinVars()
-"  call Dfunc("s:SaveWinVars() win#".winnr())
+  "  call Dfunc("s:SaveWinVars() win#".winnr())
   if exists("w:netrw_bannercnt")      |let s:bannercnt       = w:netrw_bannercnt      |endif
   if exists("w:netrw_col")            |let s:col             = w:netrw_col            |endif
   if exists("w:netrw_curdir")         |let s:curdir          = w:netrw_curdir         |endif
@@ -11717,7 +11717,7 @@ fun! s:SaveWinVars()
   if exists("w:netrw_treedict")       |let s:treedict        = w:netrw_treedict       |endif
   if exists("w:netrw_treetop")        |let s:treetop         = w:netrw_treetop        |endif
   if exists("w:netrw_winnr")          |let s:winnr           = w:netrw_winnr          |endif
-"  call Dret("s:SaveWinVars")
+  "  call Dret("s:SaveWinVars")
 endfun
 
 " ---------------------------------------------------------------------
@@ -11728,7 +11728,7 @@ endfun
 "   variables are not inherited by the new window.  SetBufWinVars() and
 "   UseBufWinVars() get around that.
 fun! s:SetBufWinVars()
-"  call Dfunc("s:SetBufWinVars() win#".winnr())
+  "  call Dfunc("s:SetBufWinVars() win#".winnr())
   if exists("w:netrw_liststyle")      |let b:netrw_liststyle      = w:netrw_liststyle      |endif
   if exists("w:netrw_bannercnt")      |let b:netrw_bannercnt      = w:netrw_bannercnt      |endif
   if exists("w:netrw_method")         |let b:netrw_method         = w:netrw_method         |endif
@@ -11739,44 +11739,44 @@ fun! s:SetBufWinVars()
   if exists("w:netrw_explore_bufnr")  |let b:netrw_explore_bufnr  = w:netrw_explore_bufnr  |endif
   if exists("w:netrw_explore_line")   |let b:netrw_explore_line   = w:netrw_explore_line   |endif
   if exists("w:netrw_explore_list")   |let b:netrw_explore_list   = w:netrw_explore_list   |endif
-"  call Dret("s:SetBufWinVars")
+  "  call Dret("s:SetBufWinVars")
 endfun
 
 " ---------------------------------------------------------------------
 " s:SetRexDir: set directory for :Rexplore {{{2
 fun! s:SetRexDir(islocal,dirname)
-"  call Dfunc("s:SetRexDir(islocal=".a:islocal." dirname<".a:dirname.">) win#".winnr())
+  "  call Dfunc("s:SetRexDir(islocal=".a:islocal." dirname<".a:dirname.">) win#".winnr())
   let w:netrw_rexdir         = a:dirname
   let w:netrw_rexlocal       = a:islocal
   let s:rexposn_{bufnr("%")} = winsaveview()
-"  call Decho("setting w:netrw_rexdir  =".w:netrw_rexdir,'~'.expand(""))
-"  call Decho("setting w:netrw_rexlocal=".w:netrw_rexlocal,'~'.expand(""))
-"  call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand(""))
-"  call Decho("setting s:rexposn_".bufnr("%")."<".bufname("%")."> to ".string(winsaveview()),'~'.expand(""))
-"  call Dret("s:SetRexDir : win#".winnr()." ".(a:islocal? "local" : "remote")." dir: ".a:dirname)
+  "  call Decho("setting w:netrw_rexdir  =".w:netrw_rexdir,'~'.expand(""))
+  "  call Decho("setting w:netrw_rexlocal=".w:netrw_rexlocal,'~'.expand(""))
+  "  call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand(""))
+  "  call Decho("setting s:rexposn_".bufnr("%")."<".bufname("%")."> to ".string(winsaveview()),'~'.expand(""))
+  "  call Dret("s:SetRexDir : win#".winnr()." ".(a:islocal? "local" : "remote")." dir: ".a:dirname)
 endfun
 
 " ---------------------------------------------------------------------
 " s:ShowLink: used to modify thin and tree listings to show links {{{2
 fun! s:ShowLink()
   if exists("b:netrw_curdir")
-   keepp :norm! $?\a
-   "call histdel("/",-1)
-   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop")
-    let basedir = s:NetrwTreePath(w:netrw_treetop)
-   else
-    let basedir = b:netrw_curdir.'/'
-   endif
-   let fname = basedir.s:NetrwGetWord()
-   let resname = resolve(fname)
-   if resname =~ '^\M'.basedir
-    let dirlen  = strlen(basedir)
-    let resname = strpart(resname,dirlen)
-   endif
-   let modline = getline(".")."\t --> ".resname
-   setl noro ma
-   call setline(".",modline)
-   setl ro noma nomod
+    keepp :norm! $?\a
+    "call histdel("/",-1)
+    if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop")
+      let basedir = s:NetrwTreePath(w:netrw_treetop)
+    else
+      let basedir = b:netrw_curdir.'/'
+    endif
+    let fname = basedir.s:NetrwGetWord()
+    let resname = resolve(fname)
+    if resname =~ '^\M'.basedir
+      let dirlen  = strlen(basedir)
+      let resname = strpart(resname,dirlen)
+    endif
+    let modline = getline(".")."\t --> ".resname
+    setl noro ma
+    call setline(".",modline)
+    setl ro noma nomod
   endif
 endfun
 
@@ -11784,20 +11784,20 @@ endfun
 " s:ShowStyle: {{{2
 fun! s:ShowStyle()
   if !exists("w:netrw_liststyle")
-   let liststyle= g:netrw_liststyle
+    let liststyle= g:netrw_liststyle
   else
-   let liststyle= w:netrw_liststyle
+    let liststyle= w:netrw_liststyle
   endif
   if     liststyle == s:THINLIST
-   return s:THINLIST.":thin"
+    return s:THINLIST.":thin"
   elseif liststyle == s:LONGLIST
-   return s:LONGLIST.":long"
+    return s:LONGLIST.":long"
   elseif liststyle == s:WIDELIST
-   return s:WIDELIST.":wide"
+    return s:WIDELIST.":wide"
   elseif liststyle == s:TREELIST
-   return s:TREELIST.":tree"
+    return s:TREELIST.":tree"
   else
-   return 'n/a'
+    return 'n/a'
   endif
 endfun
 
@@ -11806,44 +11806,44 @@ endfun
 "           Solution from Nicolai Weibull, vim docs (:help strlen()),
 "           Tony Mechelynck, and my own invention.
 fun! s:Strlen(x)
-"  "" call Dfunc("s:Strlen(x<".a:x."> g:Align_xstrlen=".g:Align_xstrlen.")")
+  "  "" call Dfunc("s:Strlen(x<".a:x."> g:Align_xstrlen=".g:Align_xstrlen.")")
 
   if v:version >= 703 && exists("*strdisplaywidth")
-   let ret= strdisplaywidth(a:x)
+    let ret= strdisplaywidth(a:x)
 
   elseif type(g:Align_xstrlen) == 1
-   " allow user to specify a function to compute the string length  (ie. let g:Align_xstrlen="mystrlenfunc")
-   exe "let ret= ".g:Align_xstrlen."('".substitute(a:x,"'","''","g")."')"
+    " allow user to specify a function to compute the string length  (ie. let g:Align_xstrlen="mystrlenfunc")
+    exe "let ret= ".g:Align_xstrlen."('".substitute(a:x,"'","''","g")."')"
 
   elseif g:Align_xstrlen == 1
-   " number of codepoints (Latin a + combining circumflex is two codepoints)
-   " (comment from TM, solution from NW)
-   let ret= strlen(substitute(a:x,'.','c','g'))
+    " number of codepoints (Latin a + combining circumflex is two codepoints)
+    " (comment from TM, solution from NW)
+    let ret= strlen(substitute(a:x,'.','c','g'))
 
   elseif g:Align_xstrlen == 2
-   " number of spacing codepoints (Latin a + combining circumflex is one spacing
-   " codepoint; a hard tab is one; wide and narrow CJK are one each; etc.)
-   " (comment from TM, solution from TM)
-   let ret=strlen(substitute(a:x, '.\Z', 'x', 'g'))
+    " number of spacing codepoints (Latin a + combining circumflex is one spacing
+    " codepoint; a hard tab is one; wide and narrow CJK are one each; etc.)
+    " (comment from TM, solution from TM)
+    let ret=strlen(substitute(a:x, '.\Z', 'x', 'g'))
 
   elseif g:Align_xstrlen == 3
-   " virtual length (counting, for instance, tabs as anything between 1 and
-   " 'tabstop', wide CJK as 2 rather than 1, Arabic alif as zero when immediately
-   " preceded by lam, one otherwise, etc.)
-   " (comment from TM, solution from me)
-   let modkeep= &l:mod
-   exe "norm! o\"
-   call setline(line("."),a:x)
-   let ret= virtcol("$") - 1
-   d
-   NetrwKeepj norm! k
-   let &l:mod= modkeep
-
-  else
-   " at least give a decent default
+    " virtual length (counting, for instance, tabs as anything between 1 and
+    " 'tabstop', wide CJK as 2 rather than 1, Arabic alif as zero when immediately
+    " preceded by lam, one otherwise, etc.)
+    " (comment from TM, solution from me)
+    let modkeep= &l:mod
+    exe "norm! o\"
+    call setline(line("."),a:x)
+    let ret= virtcol("$") - 1
+    d
+    NetrwKeepj norm! k
+    let &l:mod= modkeep
+
+  else
+    " at least give a decent default
     let ret= strlen(a:x)
   endif
-"  "" call Dret("s:Strlen ".ret)
+  "  "" call Dret("s:Strlen ".ret)
   return ret
 endfun
 
@@ -11860,7 +11860,7 @@ endfun
 " ---------------------------------------------------------------------
 " s:TreeListMove: supports [[, ]], [], and ][ in tree mode {{{2
 fun! s:TreeListMove(dir)
-"  call Dfunc("s:TreeListMove(dir<".a:dir.">)")
+  "  call Dfunc("s:TreeListMove(dir<".a:dir.">)")
   let curline      = getline('.')
   let prvline      = (line(".") > 1)?         getline(line(".")-1) : ''
   let nxtline      = (line(".") < line("$"))? getline(line(".")+1) : ''
@@ -11868,32 +11868,32 @@ fun! s:TreeListMove(dir)
   let indentm1     = substitute(curindent,'^'.s:treedepthstring,'','')
   let treedepthchr = substitute(s:treedepthstring,' ','','g')
   let stopline     = exists("w:netrw_bannercnt")? w:netrw_bannercnt : 1
-"  call Decho("prvline  <".prvline."> #".(line(".")-1), '~'.expand(""))
-"  call Decho("curline  <".curline."> #".line(".")    , '~'.expand(""))
-"  call Decho("nxtline  <".nxtline."> #".(line(".")+1), '~'.expand(""))
-"  call Decho("curindent<".curindent.">"              , '~'.expand(""))
-"  call Decho("indentm1 <".indentm1.">"               , '~'.expand(""))
+  "  call Decho("prvline  <".prvline."> #".(line(".")-1), '~'.expand(""))
+  "  call Decho("curline  <".curline."> #".line(".")    , '~'.expand(""))
+  "  call Decho("nxtline  <".nxtline."> #".(line(".")+1), '~'.expand(""))
+  "  call Decho("curindent<".curindent.">"              , '~'.expand(""))
+  "  call Decho("indentm1 <".indentm1.">"               , '~'.expand(""))
   "  COMBAK : need to handle when on a directory
   "  COMBAK : need to handle ]] and ][.  In general, needs work!!!
   if curline !~ '/$'
-   if     a:dir == '[[' && prvline != ''
-    NetrwKeepj norm! 0
-    let nl = search('^'.indentm1.'\%('.s:treedepthstring.'\)\@!','bWe',stopline) " search backwards
-"    call Decho("regfile srch back: ".nl,'~'.expand(""))
-   elseif a:dir == '[]' && nxtline != ''
-    NetrwKeepj norm! 0
-"    call Decho('srchpat<'.'^\%('.curindent.'\)\@!'.'>','~'.expand(""))
-    let nl = search('^\%('.curindent.'\)\@!','We') " search forwards
-    if nl != 0
-     NetrwKeepj norm! k
-    else
-     NetrwKeepj norm! G
+    if     a:dir == '[[' && prvline != ''
+      NetrwKeepj norm! 0
+      let nl = search('^'.indentm1.'\%('.s:treedepthstring.'\)\@!','bWe',stopline) " search backwards
+      "    call Decho("regfile srch back: ".nl,'~'.expand(""))
+    elseif a:dir == '[]' && nxtline != ''
+      NetrwKeepj norm! 0
+      "    call Decho('srchpat<'.'^\%('.curindent.'\)\@!'.'>','~'.expand(""))
+      let nl = search('^\%('.curindent.'\)\@!','We') " search forwards
+      if nl != 0
+        NetrwKeepj norm! k
+      else
+        NetrwKeepj norm! G
+      endif
+      "    call Decho("regfile srch fwd: ".nl,'~'.expand(""))
     endif
-"    call Decho("regfile srch fwd: ".nl,'~'.expand(""))
-   endif
   endif
 
-"  call Dret("s:TreeListMove")
+  "  call Dret("s:TreeListMove")
 endfun
 
 " ---------------------------------------------------------------------
@@ -11902,23 +11902,23 @@ endfun
 "                      can't be called except via emenu.  But due to locale, that menu line may not be called
 "                      Buffers.Refresh; hence, s:NetrwBMShow() utilizes a "cheat" to call that function anyway.
 fun! s:UpdateBuffersMenu()
-"  call Dfunc("s:UpdateBuffersMenu()")
+  "  call Dfunc("s:UpdateBuffersMenu()")
   if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
-   try
-    sil emenu Buffers.Refresh\ menu
-   catch /^Vim\%((\a\+)\)\=:E/
-    let v:errmsg= ""
-    sil NetrwKeepj call s:NetrwBMShow()
-   endtry
-  endif
-"  call Dret("s:UpdateBuffersMenu")
+    try
+      sil emenu Buffers.Refresh\ menu
+    catch /^Vim\%((\a\+)\)\=:E/
+      let v:errmsg= ""
+      sil NetrwKeepj call s:NetrwBMShow()
+    endtry
+  endif
+  "  call Dret("s:UpdateBuffersMenu")
 endfun
 
 " ---------------------------------------------------------------------
 " s:UseBufWinVars: (used by NetrwBrowse() and LocalBrowseCheck() {{{2
 "              Matching function to s:SetBufWinVars()
 fun! s:UseBufWinVars()
-"  call Dfunc("s:UseBufWinVars()")
+  "  call Dfunc("s:UseBufWinVars()")
   if exists("b:netrw_liststyle")       && !exists("w:netrw_liststyle")      |let w:netrw_liststyle       = b:netrw_liststyle      |endif
   if exists("b:netrw_bannercnt")       && !exists("w:netrw_bannercnt")      |let w:netrw_bannercnt       = b:netrw_bannercnt      |endif
   if exists("b:netrw_method")          && !exists("w:netrw_method")         |let w:netrw_method          = b:netrw_method         |endif
@@ -11929,7 +11929,7 @@ fun! s:UseBufWinVars()
   if exists("b:netrw_explore_bufnr")   && !exists("w:netrw_explore_bufnr")  |let w:netrw_explore_bufnr   = b:netrw_explore_bufnr  |endif
   if exists("b:netrw_explore_line")    && !exists("w:netrw_explore_line")   |let w:netrw_explore_line    = b:netrw_explore_line   |endif
   if exists("b:netrw_explore_list")    && !exists("w:netrw_explore_list")   |let w:netrw_explore_list    = b:netrw_explore_list   |endif
-"  call Dret("s:UseBufWinVars")
+  "  call Dret("s:UseBufWinVars")
 endfun
 
 " ---------------------------------------------------------------------
@@ -11939,28 +11939,28 @@ endfun
 "             See netrw#UserMaps()
 fun! s:UserMaps(islocal,funcname)
   if !exists("b:netrw_curdir")
-   let b:netrw_curdir= getcwd()
+    let b:netrw_curdir= getcwd()
   endif
   let Funcref = function(a:funcname)
   let result  = Funcref(a:islocal)
 
   if     type(result) == 1
-   " if result from user's funcref is a string...
-   if result == "refresh"
-    call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-   elseif result != ""
-    exe result
-   endif
+    " if result from user's funcref is a string...
+    if result == "refresh"
+      call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    elseif result != ""
+      exe result
+    endif
 
   elseif type(result) == 3
-   " if result from user's funcref is a List...
-   for action in result
-    if action == "refresh"
-     call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-    elseif action != ""
-     exe action
-    endif
-   endfor
+    " if result from user's funcref is a List...
+    for action in result
+      if action == "refresh"
+        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      elseif action != ""
+        exe action
+      endif
+    endfor
   endif
 endfun
 
@@ -11973,4 +11973,4 @@ unlet s:keepcpo
 " ===============
 " Modelines: {{{1
 " ===============
-" vim:ts=8 fdm=marker
+" vim:ts=8 sts=2 sw=2 et fdm=marker
diff --git a/runtime/plugin/netrwPlugin.vim b/runtime/plugin/netrwPlugin.vim
index 775b650e71..d534b36966 100644
--- a/runtime/plugin/netrwPlugin.vim
+++ b/runtime/plugin/netrwPlugin.vim
@@ -1,12 +1,13 @@
 " netrwPlugin.vim: Handles file transfer and remote directory listing across a network
 "            PLUGIN SECTION
-" Maintainer:	This runtime file is looking for a new maintainer.
-" Date:		Sep 09, 2021
+" Maintainer:   This runtime file is looking for a new maintainer.
+" Date:         Sep 09, 2021
 " Last Change:
 "   2024 May 08 by Vim Project: cleanup legacy Win9X checks
 "   2024 Oct 27 by Vim Project: cleanup gx mapping
 "   2024 Oct 28 by Vim Project: further improvements
 "   2024 Oct 31 by Vim Project: use autoloaded functions
+"   2024 Dec 19 by Vim Project: change style (#16248)
 " Former Maintainer:   Charles E Campbell
 " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
 " Copyright:    Copyright (C) 1999-2021 Charles E. Campbell {{{1
@@ -24,7 +25,7 @@
 " =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 " Load Once: {{{1
 if &cp || exists("g:loaded_netrwPlugin")
- finish
+  finish
 endif
 let g:loaded_netrwPlugin = "v173"
 let s:keepcpo = &cpo
@@ -40,71 +41,71 @@ command -complete=file     -nargs=1   Open    call netrw#Open(trim())
 " " }}}
 " Local Browsing Autocmds: {{{2
 augroup FileExplorer
- au!
- au BufLeave *  if &ft != "netrw"|let w:netrw_prvfile= expand("%:p")|endif
- au BufEnter *	sil call s:LocalBrowse(expand(""))
- au VimEnter *	sil call s:VimEnter(expand(""))
- if has("win32")
-  au BufEnter .* sil call s:LocalBrowse(expand(""))
- endif
+  au!
+  au BufLeave *  if &ft != "netrw"|let w:netrw_prvfile= expand("%:p")|endif
+  au BufEnter * sil call s:LocalBrowse(expand(""))
+  au VimEnter * sil call s:VimEnter(expand(""))
+  if has("win32")
+    au BufEnter .* sil call s:LocalBrowse(expand(""))
+  endif
 augroup END
 
 " Network Browsing Reading Writing: {{{2
 augroup Network
- au!
- au BufReadCmd   file://*											call netrw#FileUrlEdit(expand(""))
- au BufReadCmd   ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://*	exe "sil doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "sil doau BufReadPost ".fnameescape(expand(""))
- au FileReadCmd  ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://*	exe "sil doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "sil doau FileReadPost ".fnameescape(expand(""))
- au BufWriteCmd  ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://*			exe "sil doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "sil doau BufWritePost ".fnameescape(expand(""))
- au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://*			exe "sil doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "sil doau FileWritePost ".fnameescape(expand(""))
- try
-  au SourceCmd   ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://*	exe 'Nsource '.fnameescape(expand(""))
- catch /^Vim\%((\a\+)\)\=:E216/
-  au SourcePre   ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://*	exe 'Nsource '.fnameescape(expand(""))
- endtry
+  au!
+  au BufReadCmd   file://*                                                                                      call netrw#FileUrlEdit(expand(""))
+  au BufReadCmd   ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://*        exe "sil doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "sil doau BufReadPost ".fnameescape(expand(""))
+  au FileReadCmd  ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://*       exe "sil doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "sil doau FileReadPost ".fnameescape(expand(""))
+  au BufWriteCmd  ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://*                 exe "sil doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "sil doau BufWritePost ".fnameescape(expand(""))
+  au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://*                 exe "sil doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "sil doau FileWritePost ".fnameescape(expand(""))
+  try
+    au SourceCmd   ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://*      exe 'Nsource '.fnameescape(expand(""))
+  catch /^Vim\%((\a\+)\)\=:E216/
+    au SourcePre   ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://*      exe 'Nsource '.fnameescape(expand(""))
+  endtry
 augroup END
 
 " Commands: :Nread, :Nwrite, :NetUserPass {{{2
-com! -count=1 -nargs=*	Nread		let s:svpos= winsaveview()call netrw#NetRead(,)call winrestview(s:svpos)
-com! -range=% -nargs=*	Nwrite		let s:svpos= winsaveview(),call netrw#NetWrite()call winrestview(s:svpos)
-com! -nargs=*		NetUserPass	call NetUserPass()
-com! -nargs=*	        Nsource		let s:svpos= winsaveview()call netrw#NetSource()call winrestview(s:svpos)
-com! -nargs=?		Ntree		call netrw#SetTreetop(1,)
+com! -count=1 -nargs=*  Nread           let s:svpos= winsaveview()call netrw#NetRead(,)call winrestview(s:svpos)
+com! -range=% -nargs=*  Nwrite          let s:svpos= winsaveview(),call netrw#NetWrite()call winrestview(s:svpos)
+com! -nargs=*           NetUserPass     call NetUserPass()
+com! -nargs=*           Nsource         let s:svpos= winsaveview()call netrw#NetSource()call winrestview(s:svpos)
+com! -nargs=?           Ntree           call netrw#SetTreetop(1,)
 
 " Commands: :Explore, :Sexplore, Hexplore, Vexplore, Lexplore {{{2
-com! -nargs=* -bar -bang -count=0 -complete=dir	Explore		call netrw#Explore(,0,0+0,)
-com! -nargs=* -bar -bang -count=0 -complete=dir	Sexplore	call netrw#Explore(,1,0+0,)
-com! -nargs=* -bar -bang -count=0 -complete=dir	Hexplore	call netrw#Explore(,1,2+0,)
-com! -nargs=* -bar -bang -count=0 -complete=dir	Vexplore	call netrw#Explore(,1,4+0,)
-com! -nargs=* -bar       -count=0 -complete=dir	Texplore	call netrw#Explore(,0,6        ,)
-com! -nargs=* -bar -bang			Nexplore	call netrw#Explore(-1,0,0,)
-com! -nargs=* -bar -bang			Pexplore	call netrw#Explore(-2,0,0,)
-com! -nargs=* -bar -bang -count=0 -complete=dir Lexplore	call netrw#Lexplore(,0,)
+com! -nargs=* -bar -bang -count=0 -complete=dir Explore         call netrw#Explore(,0,0+0,)
+com! -nargs=* -bar -bang -count=0 -complete=dir Sexplore        call netrw#Explore(,1,0+0,)
+com! -nargs=* -bar -bang -count=0 -complete=dir Hexplore        call netrw#Explore(,1,2+0,)
+com! -nargs=* -bar -bang -count=0 -complete=dir Vexplore        call netrw#Explore(,1,4+0,)
+com! -nargs=* -bar       -count=0 -complete=dir Texplore        call netrw#Explore(,0,6        ,)
+com! -nargs=* -bar -bang                        Nexplore        call netrw#Explore(-1,0,0,)
+com! -nargs=* -bar -bang                        Pexplore        call netrw#Explore(-2,0,0,)
+com! -nargs=* -bar -bang -count=0 -complete=dir Lexplore        call netrw#Lexplore(,0,)
 
 " Commands: NetrwSettings {{{2
-com! -nargs=0	NetrwSettings	call netrwSettings#NetrwSettings()
-com! -bang	NetrwClean	call netrw#Clean(0)
+com! -nargs=0   NetrwSettings   call netrwSettings#NetrwSettings()
+com! -bang      NetrwClean      call netrw#Clean(0)
 
 " Maps:
 if !exists("g:netrw_nogx")
- if maparg('gx','n') == ""
-  if !hasmapto('NetrwBrowseX')
-   nmap  gx NetrwBrowseX
+  if maparg('gx','n') == ""
+    if !hasmapto('NetrwBrowseX')
+      nmap  gx NetrwBrowseX
+    endif
+    nno  NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))
   endif
-  nno  NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))
- endif
- if maparg('gx','x') == ""
-  if !hasmapto('NetrwBrowseXVis')
-   xmap  gx NetrwBrowseXVis
+  if maparg('gx','x') == ""
+    if !hasmapto('NetrwBrowseXVis')
+      xmap  gx NetrwBrowseXVis
+    endif
+    xno  NetrwBrowseXVis :call netrw#BrowseXVis()
   endif
-  xno  NetrwBrowseXVis :call netrw#BrowseXVis()
- endif
 endif
 if exists("g:netrw_usetab") && g:netrw_usetab
- if maparg('','n') == ""
-  nmap   NetrwShrink
- endif
- nno  NetrwShrink :call netrw#Shrink()
+  if maparg('','n') == ""
+    nmap   NetrwShrink
+  endif
+  nno  NetrwShrink :call netrw#Shrink()
 endif
 
 " ---------------------------------------------------------------------
@@ -115,43 +116,43 @@ fun! s:LocalBrowse(dirname)
   " the DBG buffer are made.
 
   if !exists("s:vimentered")
-   " If s:vimentered doesn't exist, then the VimEnter event hasn't fired.  It will,
-   " and so s:VimEnter() will then be calling this routine, but this time with s:vimentered defined.
-"   call Dfunc("s:LocalBrowse(dirname<".a:dirname.">)  (s:vimentered doesn't exist)")
-"   call Dret("s:LocalBrowse")
-   return
+    " If s:vimentered doesn't exist, then the VimEnter event hasn't fired.  It will,
+    " and so s:VimEnter() will then be calling this routine, but this time with s:vimentered defined.
+    "   call Dfunc("s:LocalBrowse(dirname<".a:dirname.">)  (s:vimentered doesn't exist)")
+    "   call Dret("s:LocalBrowse")
+    return
   endif
 
-"  call Dfunc("s:LocalBrowse(dirname<".a:dirname.">)  (s:vimentered=".s:vimentered.")")
+  "  call Dfunc("s:LocalBrowse(dirname<".a:dirname.">)  (s:vimentered=".s:vimentered.")")
 
   if has("amiga")
-   " The check against '' is made for the Amiga, where the empty
-   " string is the current directory and not checking would break
-   " things such as the help command.
-"   call Decho("(LocalBrowse) dirname<".a:dirname.">  (isdirectory, amiga)")
-   if a:dirname != '' && isdirectory(a:dirname)
-    sil! call netrw#LocalBrowseCheck(a:dirname)
-    if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt
-     exe w:netrw_bannercnt
+    " The check against '' is made for the Amiga, where the empty
+    " string is the current directory and not checking would break
+    " things such as the help command.
+    "   call Decho("(LocalBrowse) dirname<".a:dirname.">  (isdirectory, amiga)")
+    if a:dirname != '' && isdirectory(a:dirname)
+      sil! call netrw#LocalBrowseCheck(a:dirname)
+      if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt
+        exe w:netrw_bannercnt
+      endif
     endif
-   endif
 
   elseif isdirectory(a:dirname)
-"   call Decho("(LocalBrowse) dirname<".a:dirname."> ft=".&ft."  (isdirectory, not amiga)")
-"   call Dredir("LocalBrowse ft last set: ","verbose set ft")
-   " Jul 13, 2021: for whatever reason, preceding the following call with
-   " a   sil!  causes an unbalanced if-endif vim error
-   call netrw#LocalBrowseCheck(a:dirname)
-   if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt
-    exe w:netrw_bannercnt
-   endif
+    "   call Decho("(LocalBrowse) dirname<".a:dirname."> ft=".&ft."  (isdirectory, not amiga)")
+    "   call Dredir("LocalBrowse ft last set: ","verbose set ft")
+    " Jul 13, 2021: for whatever reason, preceding the following call with
+    " a   sil!  causes an unbalanced if-endif vim error
+    call netrw#LocalBrowseCheck(a:dirname)
+    if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt
+      exe w:netrw_bannercnt
+    endif
 
   else
-   " not a directory, ignore it
-"   call Decho("(LocalBrowse) dirname<".a:dirname."> not a directory, ignoring...")
+    " not a directory, ignore it
+    "   call Decho("(LocalBrowse) dirname<".a:dirname."> not a directory, ignoring...")
   endif
 
-"  call Dret("s:LocalBrowse")
+  "  call Dret("s:LocalBrowse")
 endfun
 
 " ---------------------------------------------------------------------
@@ -162,72 +163,72 @@ endfun
 "             It also sets s:vimentered, letting s:LocalBrowse() know that s:VimEnter()
 "             has already been called.
 fun! s:VimEnter(dirname)
-"  call Dfunc("s:VimEnter(dirname<".a:dirname.">) expand(%)<".expand("%").">")
+  "  call Dfunc("s:VimEnter(dirname<".a:dirname.">) expand(%)<".expand("%").">")
   if has('nvim') || v:version < 802
-  " Johann Höchtl: reported that the call range... line causes an E488: Trailing characters
-  "                error with neovim. I suspect its because neovim hasn't updated with recent
-  "                vim patches. As is, this code will have problems with popup terminals
-  "                instantiated before the VimEnter event runs.
-  " Ingo Karkat  : E488 also in Vim 8.1.1602
-  let curwin       = winnr()
-  let s:vimentered = 1
-  windo call s:LocalBrowse(expand("%:p"))
-  exe curwin."wincmd w"
- else
-  " the following complicated expression comes courtesy of lacygoill; largely does the same thing as the windo and 
-  " wincmd which are commented out, but avoids some side effects. Allows popup terminal before VimEnter.
-  let s:vimentered = 1
-  call range(1, winnr('$'))->map({_, v -> win_execute(win_getid(v), 'call expand("%:p")->s:LocalBrowse()')})
- endif
-"  call Dret("s:VimEnter")
+    " Johann Höchtl: reported that the call range... line causes an E488: Trailing characters
+    "                error with neovim. I suspect its because neovim hasn't updated with recent
+    "                vim patches. As is, this code will have problems with popup terminals
+    "                instantiated before the VimEnter event runs.
+    " Ingo Karkat  : E488 also in Vim 8.1.1602
+    let curwin       = winnr()
+    let s:vimentered = 1
+    windo call s:LocalBrowse(expand("%:p"))
+    exe curwin."wincmd w"
+  else
+    " the following complicated expression comes courtesy of lacygoill; largely does the same thing as the windo and
+    " wincmd which are commented out, but avoids some side effects. Allows popup terminal before VimEnter.
+    let s:vimentered = 1
+    call range(1, winnr('$'))->map({_, v -> win_execute(win_getid(v), 'call expand("%:p")->s:LocalBrowse()')})
+  endif
+  "  call Dret("s:VimEnter")
 endfun
 
 " ---------------------------------------------------------------------
 " NetrwStatusLine: {{{1
 fun! NetrwStatusLine()
-"  let g:stlmsg= "Xbufnr=".w:netrw_explore_bufnr." bufnr=".bufnr("%")." Xline#".w:netrw_explore_line." line#".line(".")
+  "  let g:stlmsg= "Xbufnr=".w:netrw_explore_bufnr." bufnr=".bufnr("%")." Xline#".w:netrw_explore_line." line#".line(".")
   if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list")
-   let &stl= s:netrw_explore_stl
-   if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif
-   if exists("w:netrw_explore_line")|unlet w:netrw_explore_line|endif
-   return ""
+    let &stl= s:netrw_explore_stl
+    if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif
+    if exists("w:netrw_explore_line")|unlet w:netrw_explore_line|endif
+    return ""
   else
-   return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen
+    return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen
   endif
 endfun
 
 " ------------------------------------------------------------------------
 " NetUserPass: set username and password for subsequent ftp transfer {{{1
-"   Usage:  :call NetUserPass()			-- will prompt for userid and password
-"	    :call NetUserPass("uid")		-- will prompt for password
-"	    :call NetUserPass("uid","password") -- sets global userid and password
+"   Usage:  :call NetUserPass()                 -- will prompt for userid and password
+"           :call NetUserPass("uid")            -- will prompt for password
+"           :call NetUserPass("uid","password") -- sets global userid and password
 fun! NetUserPass(...)
 
- " get/set userid
- if a:0 == 0
-"  call Dfunc("NetUserPass(a:0<".a:0.">)")
-  if !exists("g:netrw_uid") || g:netrw_uid == ""
-   " via prompt
-   let g:netrw_uid= input('Enter username: ')
+  " get/set userid
+  if a:0 == 0
+    "  call Dfunc("NetUserPass(a:0<".a:0.">)")
+    if !exists("g:netrw_uid") || g:netrw_uid == ""
+      " via prompt
+      let g:netrw_uid= input('Enter username: ')
+    endif
+  else  " from command line
+    "  call Dfunc("NetUserPass(a:1<".a:1.">) {")
+    let g:netrw_uid= a:1
+  endif
+
+  " get password
+  if a:0 <= 1 " via prompt
+    "  call Decho("a:0=".a:0." case <=1:")
+    let g:netrw_passwd= inputsecret("Enter Password: ")
+  else " from command line
+    "  call Decho("a:0=".a:0." case >1: a:2<".a:2.">")
+    let g:netrw_passwd=a:2
   endif
- else	" from command line
-"  call Dfunc("NetUserPass(a:1<".a:1.">) {")
-  let g:netrw_uid= a:1
- endif
-
- " get password
- if a:0 <= 1 " via prompt
-"  call Decho("a:0=".a:0." case <=1:")
-  let g:netrw_passwd= inputsecret("Enter Password: ")
- else " from command line
-"  call Decho("a:0=".a:0." case >1: a:2<".a:2.">")
-  let g:netrw_passwd=a:2
- endif
-"  call Dret("NetUserPass")
+  "  call Dret("NetUserPass")
 endfun
 
 " ------------------------------------------------------------------------
 " Modelines And Restoration: {{{1
 let &cpo= s:keepcpo
 unlet s:keepcpo
-" vim:ts=8 fdm=marker
+" vim:ts=8 sts=2 sw=2 et fdm=marker
-- 
cgit 


From 4e130c1ee446f4389a8c76c5e81b53bff8b9193c Mon Sep 17 00:00:00 2001
From: Dan Pascu 
Date: Fri, 20 Dec 2024 11:43:56 +0200
Subject: fix(vim.system): invalid MAX_TIMEOUT for 32-bit systems #31638

The maximum signed value on 32-bit systems is 2 ^ 31 - 1. When using 2 ^ 31 for
the default timeout, the value would overflow on such systems resulting in
a negative value, which caused a stack trace when calling wait() without
a timeout.
---
 runtime/lua/vim/_system.lua | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua
index c0a0570e13..157172447a 100644
--- a/runtime/lua/vim/_system.lua
+++ b/runtime/lua/vim/_system.lua
@@ -79,7 +79,8 @@ function SystemObj:_timeout(signal)
   self:kill(signal or SIG.TERM)
 end
 
-local MAX_TIMEOUT = 2 ^ 31
+-- Use max 32-bit signed int value to avoid overflow on 32-bit systems. #31633
+local MAX_TIMEOUT = 2 ^ 31 - 1
 
 --- @param timeout? integer
 --- @return vim.SystemCompleted
-- 
cgit 


From 909b18d05a8d472b12c156e1663282bf6f5ce307 Mon Sep 17 00:00:00 2001
From: Tomasz N 
Date: Fri, 20 Dec 2024 13:41:57 +0100
Subject: fix(messages): no message kind for completion menu messages #31646

---
 runtime/doc/ui.txt                   |  1 +
 src/nvim/insexpand.c                 |  4 ++++
 test/functional/ui/messages_spec.lua | 14 ++++++++++++++
 3 files changed, 19 insertions(+)

diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 6fb000b285..f531411354 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -796,6 +796,7 @@ must handle.
 		"echo"		|:echo| message
 		"echomsg"	|:echomsg| message
 		"echoerr"	|:echoerr| message
+		"completion"    |ins-completion-menu| message
 		"list_cmd"	List output for various commands (|:ls|, |:set|, …)
 		"lua_error"	Error in |:lua| code
 		"lua_print"	|print()| from |:lua| code
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 872ed2b4c3..a8ee269ecd 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1582,6 +1582,7 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags,
     FILE *fp = os_fopen(files[i], "r");  // open dictionary file
     if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) {
       msg_hist_off = true;  // reset in msg_trunc()
+      msg_ext_set_kind("completion");
       vim_snprintf(IObuff, IOSIZE,
                    _("Scanning dictionary: %s"), files[i]);
       msg_trunc(IObuff, true, HLF_R);
@@ -3060,6 +3061,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
     }
     if (!shortmess(SHM_COMPLETIONSCAN)) {
       msg_hist_off = true;  // reset in msg_trunc()
+      msg_ext_set_kind("completion");
       vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"),
                    st->ins_buf->b_fname == NULL
                    ? buf_spname(st->ins_buf)
@@ -3092,6 +3094,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
     } else if (*st->e_cpt == ']' || *st->e_cpt == 't') {
       compl_type = CTRL_X_TAGS;
       if (!shortmess(SHM_COMPLETIONSCAN)) {
+        msg_ext_set_kind("completion");
         msg_hist_off = true;  // reset in msg_trunc()
         vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags."));
         msg_trunc(IObuff, true, HLF_R);
@@ -4602,6 +4605,7 @@ static void ins_compl_show_statusmsg(void)
     if (edit_submode_extra != NULL) {
       if (!p_smd) {
         msg_hist_off = true;
+        msg_ext_set_kind("completion");
         msg(edit_submode_extra, (edit_submode_highl < HLF_COUNT
                                  ? (int)edit_submode_highl + 1 : 0));
         msg_hist_off = false;
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index d48d56aeb6..8f8795370f 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -253,6 +253,20 @@ describe('ui/ext_messages', function()
         },
       },
     })
+
+    -- kind=completion
+    command('set noshowmode')
+    feed('i')
+    screen:expect({
+      messages = {
+        {
+          content = { { 'The only match' } },
+          kind = 'completion',
+        },
+      },
+    })
+    feed('')
+    command('set showmode')
   end)
 
   it(':echoerr', function()
-- 
cgit 


From e1c2179dd93ed2cd787b1cd016606b1901a1acfe Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Fri, 20 Dec 2024 16:48:40 +0100
Subject: fix(coverity): INTEGER_OVERFLOW #31657

    CID 516419:  Integer handling issues  (INTEGER_OVERFLOW)
    /src/nvim/message.c: 2242 in msg_puts_display()
    2236         }
    2237         // Concat pieces with the same highlight
    2238         size_t len = maxlen < 0 ? strlen(str) : strnlen(str, (size_t)maxlen);
    2239         ga_concat_len(&msg_ext_last_chunk, str, len);
    2240         msg_ext_cur_len += len;
    2241         // When message ends in newline, reset variables used to format message: msg_advance().
    >>>     CID 516419:  Integer handling issues  (INTEGER_OVERFLOW)
    >>>     Expression "len - 1UL", which is equal to 18446744073709551615, where "len" is known to be equal to 0, underflows the type that receives it, an unsigned integer 64 bits wide.
    2242         if (str[len - 1] == '\n') {
    2243           msg_ext_cur_len = 0;
    2244           msg_col = 0;
    2245         }
    2246         return;
    2247       }
---
 src/nvim/message.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/nvim/message.c b/src/nvim/message.c
index 066aa6bc96..1c46194a1c 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2239,6 +2239,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse
     ga_concat_len(&msg_ext_last_chunk, str, len);
     msg_ext_cur_len += len;
     // When message ends in newline, reset variables used to format message: msg_advance().
+    assert(len > 0);
     if (str[len - 1] == '\n') {
       msg_ext_cur_len = 0;
       msg_col = 0;
-- 
cgit 


From 725d3e25a32f68c30402e0dbd373a93a294ffed6 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sat, 21 Dec 2024 09:17:23 +0800
Subject: vim-patch:c673b80: runtime(netrw): more reformating vim/vim#16248
 (#31662)

closes: vim/vim#16266

https://github.com/vim/vim/commit/c673b805ad80d0aef07e745d412a2bf298ba1c07

Co-authored-by: shane.xb.qian 
---
 runtime/autoload/netrw.vim | 5779 ++++++++++++++++++++++----------------------
 1 file changed, 2890 insertions(+), 2889 deletions(-)

diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index b87eb85913..8e991e88f1 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -42,6 +42,7 @@
 "   2024 Dec 08 by Vim Project: check the first arg of netrw_browsex_viewer for being executable (#16185)
 "   2024 Dec 12 by Vim Project: do not pollute the search history (#16206)
 "   2024 Dec 19 by Vim Project: change style (#16248)
+"   2024 Dec 20 by Vim Project: change style continued (#16266)
 "   }}}
 " Former Maintainer:    Charles E Campbell
 " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
@@ -634,11 +635,11 @@ if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists(
   au VimEnter *          let s:initbeval= &beval
   "else " Decho
   " if v:version < 700           | call Decho("did not install beval events: v:version=".v:version." < 700","~".expand(""))     | endif
-" if !has("balloon_eval")      | call Decho("did not install beval events: does not have balloon_eval","~".expand(""))        | endif
-" if exists("s:initbeval")     | call Decho("did not install beval events: s:initbeval exists","~".expand(""))                | endif
-" if exists("g:netrw_nobeval") | call Decho("did not install beval events: g:netrw_nobeval exists","~".expand(""))            | endif
-" if !has("syntax")            | call Decho("did not install beval events: does not have syntax highlighting","~".expand("")) | endif
-" if exists("g:syntax_on")     | call Decho("did not install beval events: g:syntax_on exists","~".expand(""))                | endif
+  " if !has("balloon_eval")      | call Decho("did not install beval events: does not have balloon_eval","~".expand(""))        | endif
+  " if exists("s:initbeval")     | call Decho("did not install beval events: s:initbeval exists","~".expand(""))                | endif
+  " if exists("g:netrw_nobeval") | call Decho("did not install beval events: g:netrw_nobeval exists","~".expand(""))            | endif
+  " if !has("syntax")            | call Decho("did not install beval events: does not have syntax highlighting","~".expand("")) | endif
+  " if exists("g:syntax_on")     | call Decho("did not install beval events: g:syntax_on exists","~".expand(""))                | endif
 endif
 au WinEnter *   if &ft == "netrw"|call s:NetrwInsureWinVars()|endif
 
@@ -695,10 +696,10 @@ if v:version >= 700 && has("balloon_eval") && has("syntax") && exists("g:syntax_
   endfun
   "else " Decho
   " if v:version < 700            |call Decho("did not load netrw#BalloonHelp(): vim version ".v:version." < 700 -","~".expand(""))|endif
-" if !has("balloon_eval")       |call Decho("did not load netrw#BalloonHelp(): does not have balloon eval","~".expand(""))       |endif
-" if !has("syntax")             |call Decho("did not load netrw#BalloonHelp(): syntax disabled","~".expand(""))                  |endif
-" if !exists("g:syntax_on")     |call Decho("did not load netrw#BalloonHelp(): g:syntax_on n/a","~".expand(""))                  |endif
-" if  exists("g:netrw_nobeval") |call Decho("did not load netrw#BalloonHelp(): g:netrw_nobeval exists","~".expand(""))           |endif
+  " if !has("balloon_eval")       |call Decho("did not load netrw#BalloonHelp(): does not have balloon eval","~".expand(""))       |endif
+  " if !has("syntax")             |call Decho("did not load netrw#BalloonHelp(): syntax disabled","~".expand(""))                  |endif
+  " if !exists("g:syntax_on")     |call Decho("did not load netrw#BalloonHelp(): g:syntax_on n/a","~".expand(""))                  |endif
+  " if  exists("g:netrw_nobeval") |call Decho("did not load netrw#BalloonHelp(): g:netrw_nobeval exists","~".expand(""))           |endif
 endif
 
 " ------------------------------------------------------------------------
@@ -894,10 +895,10 @@ fun! netrw#Explore(indx,dosplit,style,...)
     endif
 
 
-    " starpat=1: Explore *//pattern  (current directory only search for files containing pattern)
-    " starpat=2: Explore **//pattern (recursive descent search for files containing pattern)
-    " starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
-    " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
+  " starpat=1: Explore *//pattern  (current directory only search for files containing pattern)
+  " starpat=2: Explore **//pattern (recursive descent search for files containing pattern)
+  " starpat=3: Explore */filepat   (search in current directory for filenames matching filepat)
+  " starpat=4: Explore **/filepat  (recursive descent search for filenames matching filepat)
   elseif a:indx <= 0
     " Nexplore, Pexplore, Explore: handle starpat
     if !mapcheck("","n") && !mapcheck("","n") && exists("b:netrw_curdir")
@@ -1426,7 +1427,7 @@ fun! netrw#Obtain(islocal,fname,...)
 
       if exists("g:netrw_port") && g:netrw_port != ""
         NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-        "     call Decho("filter input: ".getline('$'),'~'.expand(""))
+      "     call Decho("filter input: ".getline('$'),'~'.expand(""))
       else
         NetrwKeepj put ='open '.g:netrw_machine
         "     call Decho("filter input: ".getline('$'),'~'.expand(""))
@@ -1439,7 +1440,7 @@ fun! netrw#Obtain(islocal,fname,...)
           if exists("s:netrw_passwd") && s:netrw_passwd != ""
             NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
           endif
-          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        "      call Decho("filter input: ".getline('$'),'~'.expand(""))
         elseif exists("s:netrw_passwd")
           NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
           "      call Decho("filter input: ".getline('$'),'~'.expand(""))
@@ -2070,7 +2071,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (ftp + <.netrc>)  NetRead Method #2 {{{3
+    " NetRead: (ftp + <.netrc>)  NetRead Method #2 {{{3
     elseif b:netrw_method  == 2          " read with ftp + <.netrc>
       "     call Decho("read via ftp+.netrc (method #2)",'~'.expand(""))
       let netrw_fname= b:netrw_fname
@@ -2108,7 +2109,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (ftp + machine,id,passwd,filename)  NetRead Method #3 {{{3
+    " NetRead: (ftp + machine,id,passwd,filename)  NetRead Method #3 {{{3
     elseif b:netrw_method == 3           " read with ftp + machine, id, passwd, and fname
       " Construct execution string (four lines) which will be passed through filter
       "    call Decho("read via ftp+mipf (method #3)",'~'.expand(""))
@@ -2118,7 +2119,7 @@ fun! netrw#NetRead(mode,...)
       setl ff=unix
       if exists("g:netrw_port") && g:netrw_port != ""
         NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      "     call Decho("filter input: ".getline('.'),'~'.expand(""))
       else
         NetrwKeepj put ='open '.g:netrw_machine
         "     call Decho("filter input: ".getline('.'),'~'.expand(""))
@@ -2131,7 +2132,7 @@ fun! netrw#NetRead(mode,...)
           if exists("s:netrw_passwd")
             NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
           endif
-          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+        "      call Decho("filter input: ".getline('.'),'~'.expand(""))
         elseif exists("s:netrw_passwd")
           NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
           "      call Decho("filter input: ".getline('.'),'~'.expand(""))
@@ -2167,7 +2168,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (scp) NetRead Method #4 {{{3
+    " NetRead: (scp) NetRead Method #4 {{{3
     elseif     b:netrw_method  == 4      " read with scp
       "    call Decho("read via scp (method #4)",'~'.expand(""))
       if exists("g:netrw_port") && g:netrw_port != ""
@@ -2187,7 +2188,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (http) NetRead Method #5 (wget) {{{3
+    " NetRead: (http) NetRead Method #5 (wget) {{{3
     elseif     b:netrw_method  == 5
       "    call Decho("read via http (method #5)",'~'.expand(""))
       if g:netrw_http_cmd == ""
@@ -2225,7 +2226,7 @@ fun! netrw#NetRead(mode,...)
       setl ro nomod
 
       ".........................................
-      " NetRead: (dav) NetRead Method #6 {{{3
+    " NetRead: (dav) NetRead Method #6 {{{3
     elseif     b:netrw_method  == 6
       "    call Decho("read via cadaver (method #6)",'~'.expand(""))
 
@@ -2261,7 +2262,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (rsync) NetRead Method #7 {{{3
+    " NetRead: (rsync) NetRead Method #7 {{{3
     elseif     b:netrw_method  == 7
       "    call Decho("read via rsync (method #7)",'~'.expand(""))
       call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1))
@@ -2269,8 +2270,8 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (fetch) NetRead Method #8 {{{3
-      "    fetch://[user@]host[:http]/path
+    " NetRead: (fetch) NetRead Method #8 {{{3
+    "    fetch://[user@]host[:http]/path
     elseif     b:netrw_method  == 8
       "    call Decho("read via fetch (method #8)",'~'.expand(""))
       if g:netrw_fetch_cmd == ""
@@ -2299,7 +2300,7 @@ fun! netrw#NetRead(mode,...)
       setl ro nomod
 
       ".........................................
-      " NetRead: (sftp) NetRead Method #9 {{{3
+    " NetRead: (sftp) NetRead Method #9 {{{3
     elseif     b:netrw_method  == 9
       "    call Decho("read via sftp (method #9)",'~'.expand(""))
       call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
@@ -2307,7 +2308,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: (file) NetRead Method #10 {{{3
+    " NetRead: (file) NetRead Method #10 {{{3
     elseif      b:netrw_method == 10 && exists("g:netrw_file_cmd")
       "   "    call Decho("read via ".b:netrw_file_cmd." (method #10)",'~'.expand(""))
       call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_file_cmd." ".s:ShellEscape(b:netrw_fname,1)." ".tmpfile)
@@ -2315,7 +2316,7 @@ fun! netrw#NetRead(mode,...)
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetRead: Complain {{{3
+    " NetRead: Complain {{{3
     else
       call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",8)
     endif
@@ -2477,7 +2478,7 @@ fun! netrw#NetWrite(...) range
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetWrite: (ftp + <.netrc>) NetWrite Method #2 {{{3
+    " NetWrite: (ftp + <.netrc>) NetWrite Method #2 {{{3
     elseif b:netrw_method == 2
       "    call Decho("write via ftp+.netrc (method #2)",'~'.expand(""))
       let netrw_fname = b:netrw_fname
@@ -2521,7 +2522,7 @@ fun! netrw#NetWrite(...) range
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetWrite: (ftp + machine, id, passwd, filename) NetWrite Method #3 {{{3
+    " NetWrite: (ftp + machine, id, passwd, filename) NetWrite Method #3 {{{3
     elseif b:netrw_method == 3
       " Construct execution string (three or more lines) which will be passed through filter
       "    call Decho("read via ftp+mipf (method #3)",'~'.expand(""))
@@ -2536,7 +2537,7 @@ fun! netrw#NetWrite(...) range
 
       if exists("g:netrw_port") && g:netrw_port != ""
         NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-        "     call Decho("filter input: ".getline('.'),'~'.expand(""))
+      "     call Decho("filter input: ".getline('.'),'~'.expand(""))
       else
         NetrwKeepj put ='open '.g:netrw_machine
         "     call Decho("filter input: ".getline('.'),'~'.expand(""))
@@ -2548,7 +2549,7 @@ fun! netrw#NetWrite(...) range
           if exists("s:netrw_passwd") && s:netrw_passwd != ""
             NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
           endif
-          "      call Decho("filter input: ".getline('.'),'~'.expand(""))
+        "      call Decho("filter input: ".getline('.'),'~'.expand(""))
         elseif exists("s:netrw_passwd") && s:netrw_passwd != ""
           NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
           "      call Decho("filter input: ".getline('.'),'~'.expand(""))
@@ -2586,7 +2587,7 @@ fun! netrw#NetWrite(...) range
       exe filtbuf."bw!"
 
       ".........................................
-      " NetWrite: (scp) NetWrite Method #4 {{{3
+    " NetWrite: (scp) NetWrite Method #4 {{{3
     elseif     b:netrw_method == 4
       "    call Decho("write via scp (method #4)",'~'.expand(""))
       if exists("g:netrw_port") && g:netrw_port != ""
@@ -2598,7 +2599,7 @@ fun! netrw#NetWrite(...) range
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetWrite: (http) NetWrite Method #5 {{{3
+    " NetWrite: (http) NetWrite Method #5 {{{3
     elseif     b:netrw_method == 5
       "    call Decho("write via http (method #5)",'~'.expand(""))
       let curl= substitute(g:netrw_http_put_cmd,'\s\+.*$',"","")
@@ -2610,7 +2611,7 @@ fun! netrw#NetWrite(...) range
       endif
 
       ".........................................
-      " NetWrite: (dav) NetWrite Method #6 (cadaver) {{{3
+    " NetWrite: (dav) NetWrite Method #6 (cadaver) {{{3
     elseif     b:netrw_method == 6
       "    call Decho("write via cadaver (method #6)",'~'.expand(""))
 
@@ -2647,14 +2648,14 @@ fun! netrw#NetWrite(...) range
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetWrite: (rsync) NetWrite Method #7 {{{3
+    " NetWrite: (rsync) NetWrite Method #7 {{{3
     elseif     b:netrw_method == 7
       "    call Decho("write via rsync (method #7)",'~'.expand(""))
       call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1))
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetWrite: (sftp) NetWrite Method #9 {{{3
+    " NetWrite: (sftp) NetWrite Method #9 {{{3
     elseif     b:netrw_method == 9
       "    call Decho("write via sftp (method #9)",'~'.expand(""))
       let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
@@ -2682,7 +2683,7 @@ fun! netrw#NetWrite(...) range
       let b:netrw_lastfile = choice
 
       ".........................................
-      " NetWrite: Complain {{{3
+    " NetWrite: Complain {{{3
     else
       call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",17)
       let leavemod= 1
@@ -2700,7 +2701,7 @@ fun! netrw#NetWrite(...) range
   if a:firstline == 1 && a:lastline == line("$")
     " restore modifiability; usually equivalent to set nomod
     let &l:mod= mod
-    "   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+  "   call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
   elseif !exists("leavemod")
     " indicate that the buffer has not been modified since last written
     "   call Decho("set nomod",'~'.expand(""))
@@ -2962,7 +2963,7 @@ fun! s:NetrwMethod(choice)
   " curmachine used if protocol == ftp and no .netrc
   if exists("g:netrw_machine")
     let curmachine= g:netrw_machine
-    "    call Decho("curmachine<".curmachine.">",'~'.expand(""))
+  "    call Decho("curmachine<".curmachine.">",'~'.expand(""))
   else
     let curmachine= "N O T A HOST"
   endif
@@ -3020,7 +3021,7 @@ fun! s:NetrwMethod(choice)
       let g:netrw_uid= userid
     endif
 
-    " Method#4: scp://user@hostname/...path-to-file {{{3
+  " Method#4: scp://user@hostname/...path-to-file {{{3
   elseif match(a:choice,scpurm) == 0
     "   call Decho("scp://...",'~'.expand(""))
     let b:netrw_method  = 4
@@ -3028,7 +3029,7 @@ fun! s:NetrwMethod(choice)
     let g:netrw_port    = substitute(a:choice,scpurm,'\2',"")
     let b:netrw_fname   = substitute(a:choice,scpurm,'\3',"")
 
-    " Method#5: http[s]://user@hostname/...path-to-file {{{3
+  " Method#5: http[s]://user@hostname/...path-to-file {{{3
   elseif match(a:choice,httpurm) == 0
     "   call Decho("http[s]://...",'~'.expand(""))
     let b:netrw_method = 5
@@ -3036,7 +3037,7 @@ fun! s:NetrwMethod(choice)
     let b:netrw_fname  = substitute(a:choice,httpurm,'\2',"")
     let b:netrw_http   = (a:choice =~ '^https:')? "https" : "http"
 
-    " Method#6: dav://hostname[:port]/..path-to-file.. {{{3
+  " Method#6: dav://hostname[:port]/..path-to-file.. {{{3
   elseif match(a:choice,davurm) == 0
     "   call Decho("dav://...",'~'.expand(""))
     let b:netrw_method= 6
@@ -3047,14 +3048,14 @@ fun! s:NetrwMethod(choice)
     endif
     let b:netrw_fname  = substitute(a:choice,davurm,'\3',"")
 
-    " Method#7: rsync://user@hostname/...path-to-file {{{3
+  " Method#7: rsync://user@hostname/...path-to-file {{{3
   elseif match(a:choice,rsyncurm) == 0
     "   call Decho("rsync://...",'~'.expand(""))
     let b:netrw_method = 7
     let g:netrw_machine= substitute(a:choice,rsyncurm,'\1',"")
     let b:netrw_fname  = substitute(a:choice,rsyncurm,'\2',"")
 
-    " Methods 2,3: ftp://[user@]hostname[[:#]port]/...path-to-file {{{3
+  " Methods 2,3: ftp://[user@]hostname[[:#]port]/...path-to-file {{{3
   elseif match(a:choice,ftpurm) == 0
     "   call Decho("ftp://...",'~'.expand(""))
     let userid         = substitute(a:choice,ftpurm,'\2',"")
@@ -3107,7 +3108,7 @@ fun! s:NetrwMethod(choice)
       endif
     endif
 
-    " Method#8: fetch {{{3
+  " Method#8: fetch {{{3
   elseif match(a:choice,fetchurm) == 0
     "   call Decho("fetch://...",'~'.expand(""))
     let b:netrw_method = 8
@@ -3116,7 +3117,7 @@ fun! s:NetrwMethod(choice)
     let b:netrw_option = substitute(a:choice,fetchurm,'\4',"")
     let b:netrw_fname  = substitute(a:choice,fetchurm,'\5',"")
 
-    " Method#3: Issue an ftp : "machine id password [path/]filename" {{{3
+  " Method#3: Issue an ftp : "machine id password [path/]filename" {{{3
   elseif match(a:choice,mipf) == 0
     "   call Decho("(ftp) host id pass file",'~'.expand(""))
     let b:netrw_method  = 3
@@ -3126,7 +3127,7 @@ fun! s:NetrwMethod(choice)
     let b:netrw_fname   = substitute(a:choice,mipf,'\4',"")
     call NetUserPass(g:netrw_machine,g:netrw_uid,s:netrw_passwd)
 
-    " Method#3: Issue an ftp: "hostname [path/]filename" {{{3
+  " Method#3: Issue an ftp: "hostname [path/]filename" {{{3
   elseif match(a:choice,mf) == 0
     "   call Decho("(ftp) host file",'~'.expand(""))
     if exists("g:netrw_uid") && exists("s:netrw_passwd")
@@ -3140,14 +3141,14 @@ fun! s:NetrwMethod(choice)
       let b:netrw_fname   = substitute(a:choice,mf,'\2',"")
     endif
 
-    " Method#9: sftp://user@hostname/...path-to-file {{{3
+  " Method#9: sftp://user@hostname/...path-to-file {{{3
   elseif match(a:choice,sftpurm) == 0
     "   call Decho("sftp://...",'~'.expand(""))
     let b:netrw_method = 9
     let g:netrw_machine= substitute(a:choice,sftpurm,'\1',"")
     let b:netrw_fname  = substitute(a:choice,sftpurm,'\2',"")
 
-    " Method#1: Issue an rcp: hostname:filename"  (this one should be last) {{{3
+  " Method#1: Issue an rcp: hostname:filename"  (this one should be last) {{{3
   elseif match(a:choice,rcphf) == 0
     "   call Decho("(rcp) [user@]host:file) rcphf<".rcphf.">",'~'.expand(""))
     let b:netrw_method  = 1
@@ -3162,14 +3163,14 @@ fun! s:NetrwMethod(choice)
       let g:netrw_uid= userid
     endif
 
-    " Method#10: file://user@hostname/...path-to-file {{{3
+  " Method#10: file://user@hostname/...path-to-file {{{3
   elseif match(a:choice,fileurm) == 0 && exists("g:netrw_file_cmd")
     "   call Decho("http[s]://...",'~'.expand(""))
     let b:netrw_method = 10
     let b:netrw_fname  = substitute(a:choice,fileurm,'\1',"")
-    "   call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">",'~'.expand(""))
+  "   call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">",'~'.expand(""))
 
-    " Cannot Determine Method {{{3
+  " Cannot Determine Method {{{3
   else
     if !exists("g:netrw_quiet")
       call netrw#ErrorMsg(s:WARNING,"cannot determine method (format: protocol://[user@]hostname[:port]/[path])",45)
@@ -3249,8 +3250,8 @@ fun! NetUserPass(...)
       if exists("s:netrw_hup[host]")
         let g:netrw_uid    = s:netrw_hup[host].uid
         let s:netrw_passwd = s:netrw_hup[host].passwd
-        "    call Decho("get s:netrw_hup[".host."].uid   <".s:netrw_hup[host].uid.">",'~'.expand(""))
-        "    call Decho("get s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand(""))
+      "    call Decho("get s:netrw_hup[".host."].uid   <".s:netrw_hup[host].uid.">",'~'.expand(""))
+      "    call Decho("get s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand(""))
       else
         let g:netrw_uid    = input("Enter UserId: ")
         let s:netrw_passwd = inputsecret("Enter Password: ")
@@ -3824,9 +3825,9 @@ fun! s:NetrwBrowse(islocal,dirname)
       endif
     endif
 
-    " --------------------------------
-    " remote handling: {{{3
-    " --------------------------------
+  " --------------------------------
+  " remote handling: {{{3
+  " --------------------------------
   else
 
     " analyze dirname and g:netrw_list_cmd {{{3
@@ -3911,7 +3912,7 @@ fun! s:NetrwFile(fname)
   " clean up any leading treedepthstring
   if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
     let fname= substitute(a:fname,'^'.s:treedepthstring.'\+','','')
-    "   "" call Decho("clean up any leading treedepthstring: fname<".fname.">",'~'.expand(""))
+  "   "" call Decho("clean up any leading treedepthstring: fname<".fname.">",'~'.expand(""))
   else
     let fname= a:fname
   endif
@@ -3926,7 +3927,7 @@ fun! s:NetrwFile(fname)
       if fname =~ '^\' || fname =~ '^\a:\'
         " windows, but full path given
         let ret= fname
-        "     "" call Decho("windows+full path: isdirectory(".fname.")",'~'.expand(""))
+      "     "" call Decho("windows+full path: isdirectory(".fname.")",'~'.expand(""))
       else
         " windows, relative path given
         let ret= s:ComposePath(b:netrw_curdir,fname)
@@ -3936,7 +3937,7 @@ fun! s:NetrwFile(fname)
     elseif fname =~ '^/'
       " not windows, full path given
       let ret= fname
-      "    "" call Decho("unix+full path: isdirectory(".fname.")",'~'.expand(""))
+    "    "" call Decho("unix+full path: isdirectory(".fname.")",'~'.expand(""))
     else
       " not windows, relative path given
       let ret= s:ComposePath(b:netrw_curdir,fname)
@@ -3971,15 +3972,15 @@ fun! s:NetrwFileInfo(islocal,fname)
 
       if getline(".") == "../"
         echo system("/bin/ls ".lsopt." ".s:ShellEscape(".."))
-        "     call Decho("#1: echo system(/bin/ls -lsad ".s:ShellEscape(..).")",'~'.expand(""))
+      "     call Decho("#1: echo system(/bin/ls -lsad ".s:ShellEscape(..).")",'~'.expand(""))
 
       elseif w:netrw_liststyle == s:TREELIST && getline(".") !~ '^'.s:treedepthstring
         echo system("/bin/ls ".lsopt." ".s:ShellEscape(b:netrw_curdir))
-        "     call Decho("#2: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir).")",'~'.expand(""))
+      "     call Decho("#2: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir).")",'~'.expand(""))
 
       elseif exists("b:netrw_curdir")
         echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,a:fname)))
-        "      call Decho("#3: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir.a:fname).")",'~'.expand(""))
+      "      call Decho("#3: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir.a:fname).")",'~'.expand(""))
 
       else
         "     call Decho('using ls '.a:fname." using cwd<".getcwd().">",'~'.expand(""))
@@ -4061,7 +4062,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
       endif
     elseif bufnr("NetrwTreeListing") != -1
       let bufnum= bufnr("NetrwTreeListing")
-      "    call Decho("  NetrwTreeListing".": bufnum#".bufnum,'~'.expand(""))
+    "    call Decho("  NetrwTreeListing".": bufnum#".bufnum,'~'.expand(""))
     else
       "    call Decho("  did not find a NetrwTreeListing buffer",'~'.expand(""))
       let bufnum= -1
@@ -4122,7 +4123,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
       nnoremap   ]]       :sil call TreeListMove(']]')
       nnoremap   []       :sil call TreeListMove('[]')
       nnoremap   ][       :sil call TreeListMove('][')
-      "    call Decho("  tree listing bufnr=".w:netrw_treebufnr,'~'.expand(""))
+    "    call Decho("  tree listing bufnr=".w:netrw_treebufnr,'~'.expand(""))
     else
       call s:NetrwBufRename(dirname)
       " enter the new buffer into the s:netrwbuf dictionary
@@ -4130,7 +4131,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
       "    call Decho("update netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnr("%"),'~'.expand(""))
       "    call Decho("netrwbuf dictionary=".string(s:netrwbuf),'~'.expand(""))
     endif
-    "   call Decho("  named enew buffer#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
+  "   call Decho("  named enew buffer#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
 
   else " Re-use the buffer
     "   call Decho("--re-use buffer#".bufnum." (bufnum#".bufnum.">=0 AND bufexists(".bufnum.")=".bufexists(bufnum)."!=0)",'~'.expand(""))
@@ -4141,7 +4142,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
     if &ft == "netrw"
       "    call Decho("buffer type is netrw; not using keepalt with b ".bufnum)
       exe "sil! NetrwKeepj noswapfile b ".bufnum
-      "    call Dredir("ls!","one")
+    "    call Dredir("ls!","one")
     else
       "    call Decho("buffer type is not netrw; using keepalt with b ".bufnum)
       call s:NetrwEditBuf(bufnum)
@@ -4300,55 +4301,55 @@ fun! s:NetrwGetWord()
     if !exists("b:netrw_cpf")
       let b:netrw_cpf= 0
       exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
-    call histdel("/",-1)
-    "   "call Decho("computed cpf=".b:netrw_cpf,'~'.expand(""))
-  endif
+      call histdel("/",-1)
+      "   "call Decho("computed cpf=".b:netrw_cpf,'~'.expand(""))
+    endif
 
-  "   call Decho("buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
-  let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf
-  "   call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart."  bannercnt=".w:netrw_bannercnt,'~'.expand(""))
-  "   call Decho("1: dirname<".dirname.">",'~'.expand(""))
-  if filestart == 0
-    NetrwKeepj norm! 0ma
-  else
-    call cursor(line("."),filestart+1)
-    NetrwKeepj norm! ma
-  endif
+    "   call Decho("buf#".bufnr("%")."<".bufname("%").">",'~'.expand(""))
+    let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf
+    "   call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart."  bannercnt=".w:netrw_bannercnt,'~'.expand(""))
+    "   call Decho("1: dirname<".dirname.">",'~'.expand(""))
+    if filestart == 0
+      NetrwKeepj norm! 0ma
+    else
+      call cursor(line("."),filestart+1)
+      NetrwKeepj norm! ma
+    endif
 
-  let dict={}
-  " save the unnamed register and register 0-9 and a
-  let dict.a=[getreg('a'), getregtype('a')]
-  for i in range(0, 9)
-    let dict[i] = [getreg(i), getregtype(i)]
-  endfor
-  let dict.unnamed = [getreg(''), getregtype('')]
+    let dict={}
+    " save the unnamed register and register 0-9 and a
+    let dict.a=[getreg('a'), getregtype('a')]
+    for i in range(0, 9)
+      let dict[i] = [getreg(i), getregtype(i)]
+    endfor
+    let dict.unnamed = [getreg(''), getregtype('')]
 
-  let eofname= filestart + b:netrw_cpf + 1
-  if eofname <= col("$")
-    call cursor(line("."),filestart+b:netrw_cpf+1)
-    NetrwKeepj norm! "ay`a
-  else
-    NetrwKeepj norm! "ay$
-  endif
+    let eofname= filestart + b:netrw_cpf + 1
+    if eofname <= col("$")
+      call cursor(line("."),filestart+b:netrw_cpf+1)
+      NetrwKeepj norm! "ay`a
+    else
+      NetrwKeepj norm! "ay$
+    endif
 
-  let dirname = @a
-  call s:RestoreRegister(dict)
+    let dirname = @a
+    call s:RestoreRegister(dict)
 
-  "   call Decho("2: dirname<".dirname.">",'~'.expand(""))
-  let dirname= substitute(dirname,'\s\+$','','e')
-  "   call Decho("3: dirname<".dirname.">",'~'.expand(""))
-endif
+    "   call Decho("2: dirname<".dirname.">",'~'.expand(""))
+    let dirname= substitute(dirname,'\s\+$','','e')
+    "   call Decho("3: dirname<".dirname.">",'~'.expand(""))
+  endif
 
-" symlinks are indicated by a trailing "@".  Remove it before further processing.
-let dirname= substitute(dirname,"@$","","")
+  " symlinks are indicated by a trailing "@".  Remove it before further processing.
+  let dirname= substitute(dirname,"@$","","")
 
-" executables are indicated by a trailing "*".  Remove it before further processing.
-let dirname= substitute(dirname,"\*$","","")
+  " executables are indicated by a trailing "*".  Remove it before further processing.
+  let dirname= substitute(dirname,"\*$","","")
 
-let &l:sol= keepsol
+  let &l:sol= keepsol
 
-"  call Dret("s:NetrwGetWord <".dirname.">")
-return dirname
+  "  call Dret("s:NetrwGetWord <".dirname.">")
+  return dirname
 endfun
 
 " ---------------------------------------------------------------------
@@ -5062,13 +5063,13 @@ endif
 if has('win32unix')
   " (cyg)start suffices
   let s:os_viewer = ''
-  " Windows / WSL
+" Windows / WSL
 elseif executable('explorer.exe')
   let s:os_viewer = 'explorer.exe'
-  " Linux / BSD
+" Linux / BSD
 elseif executable('xdg-open')
   let s:os_viewer = 'xdg-open'
-  " MacOS
+" MacOS
 elseif executable('open')
   let s:os_viewer = 'open'
 endif
@@ -5108,2980 +5109,2980 @@ endfun
 
 fun! netrw#Open(file) abort
   call netrw#Launch(s:viewer() .. ' ' .. shellescape(a:file, 1))
-  endf
-
-  if !exists('g:netrw_regex_url')
-    let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}'
-  endif
+endfun
 
-  " ---------------------------------------------------------------------
-  " netrw#BrowseX:  (implements "x" and "gx") executes a special "viewer" script or program for the {{{2
-  "              given filename; typically this means given their extension.
-  "              0=local, 1=remote
-  fun! netrw#BrowseX(fname,remote)
-    if a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$'
-      " remote directory, not a webpage access, looks like an attempt to do a directory listing
-      norm! gf
-    endif
+if !exists('g:netrw_regex_url')
+  let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}'
+endif
 
-    if exists("g:netrw_browsex_viewer") && exists("g:netrw_browsex_support_remote") && !g:netrw_browsex_support_remote
-      let remote = a:remote
-    else
-      let remote = 0
-    endif
+" ---------------------------------------------------------------------
+" netrw#BrowseX:  (implements "x" and "gx") executes a special "viewer" script or program for the {{{2
+"              given filename; typically this means given their extension.
+"              0=local, 1=remote
+fun! netrw#BrowseX(fname,remote)
+  if a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$'
+    " remote directory, not a webpage access, looks like an attempt to do a directory listing
+    norm! gf
+  endif
 
-    let ykeep      = @@
-    let screenposn = winsaveview()
+  if exists("g:netrw_browsex_viewer") && exists("g:netrw_browsex_support_remote") && !g:netrw_browsex_support_remote
+    let remote = a:remote
+  else
+    let remote = 0
+  endif
 
-    " need to save and restore aw setting as gx can invoke this function from non-netrw buffers
-    let awkeep     = &aw
-    set noaw
+  let ykeep      = @@
+  let screenposn = winsaveview()
 
-    " special core dump handler
-    if a:fname =~ '/core\(\.\d\+\)\=$'
-      if exists("g:Netrw_corehandler")
-        if type(g:Netrw_corehandler) == 2
-          " g:Netrw_corehandler is a function reference (see :help Funcref)
-          call g:Netrw_corehandler(s:NetrwFile(a:fname))
-        elseif type(g:Netrw_corehandler) == 3
-          " g:Netrw_corehandler is a List of function references (see :help Funcref)
-          for Fncref in g:Netrw_corehandler
-            if type(Fncref) == 2
-              call Fncref(a:fname)
-            endif
-          endfor
-        endif
-        call winrestview(screenposn)
-        let @@= ykeep
-        let &aw= awkeep
-        return
+  " need to save and restore aw setting as gx can invoke this function from non-netrw buffers
+  let awkeep     = &aw
+  set noaw
+
+  " special core dump handler
+  if a:fname =~ '/core\(\.\d\+\)\=$'
+    if exists("g:Netrw_corehandler")
+      if type(g:Netrw_corehandler) == 2
+        " g:Netrw_corehandler is a function reference (see :help Funcref)
+        call g:Netrw_corehandler(s:NetrwFile(a:fname))
+      elseif type(g:Netrw_corehandler) == 3
+        " g:Netrw_corehandler is a List of function references (see :help Funcref)
+        for Fncref in g:Netrw_corehandler
+          if type(Fncref) == 2
+            call Fncref(a:fname)
+          endif
+        endfor
       endif
+      call winrestview(screenposn)
+      let @@= ykeep
+      let &aw= awkeep
+      return
     endif
+  endif
 
-    " set up the filename
-    " (lower case the extension, make a local copy of a remote file)
-    let exten= substitute(a:fname,'.*\.\(.\{-}\)','\1','e')
-    if has("win32")
-      let exten= substitute(exten,'^.*$','\L&\E','')
-    endif
-    if exten =~ "[\\/]"
-      let exten= ""
-    endif
-
-    if remote == 1
-      " create a local copy
-      setl bh=delete
-      call netrw#NetRead(3,a:fname)
-      " attempt to rename tempfile
-      let basename= substitute(a:fname,'^\(.*\)/\(.*\)\.\([^.]*\)$','\2','')
-      let newname = substitute(s:netrw_tmpfile,'^\(.*\)/\(.*\)\.\([^.]*\)$','\1/'.basename.'.\3','')
-      if s:netrw_tmpfile != newname && newname != ""
-        if rename(s:netrw_tmpfile,newname) == 0
-          " renaming succeeded
-          let fname= newname
-        else
-          " renaming failed
-          let fname= s:netrw_tmpfile
-        endif
+  " set up the filename
+  " (lower case the extension, make a local copy of a remote file)
+  let exten= substitute(a:fname,'.*\.\(.\{-}\)','\1','e')
+  if has("win32")
+    let exten= substitute(exten,'^.*$','\L&\E','')
+  endif
+  if exten =~ "[\\/]"
+    let exten= ""
+  endif
+
+  if remote == 1
+    " create a local copy
+    setl bh=delete
+    call netrw#NetRead(3,a:fname)
+    " attempt to rename tempfile
+    let basename= substitute(a:fname,'^\(.*\)/\(.*\)\.\([^.]*\)$','\2','')
+    let newname = substitute(s:netrw_tmpfile,'^\(.*\)/\(.*\)\.\([^.]*\)$','\1/'.basename.'.\3','')
+    if s:netrw_tmpfile != newname && newname != ""
+      if rename(s:netrw_tmpfile,newname) == 0
+        " renaming succeeded
+        let fname= newname
       else
+        " renaming failed
         let fname= s:netrw_tmpfile
       endif
     else
-      let fname= a:fname
-      " special ~ handler for local
-      if fname =~ '^\~' && expand("$HOME") != ""
-        let fname= s:NetrwFile(substitute(fname,'^\~',expand("$HOME"),''))
-      endif
+      let fname= s:netrw_tmpfile
+    endif
+  else
+    let fname= a:fname
+    " special ~ handler for local
+    if fname =~ '^\~' && expand("$HOME") != ""
+      let fname= s:NetrwFile(substitute(fname,'^\~',expand("$HOME"),''))
     endif
+  endif
 
-    " although shellescape(..., 1) is used in netrw#Open(), it's insufficient
-    call netrw#Open(escape(fname, '#%'))
+  " although shellescape(..., 1) is used in netrw#Open(), it's insufficient
+  call netrw#Open(escape(fname, '#%'))
 
-    " cleanup: remove temporary file,
-    "          delete current buffer if success with handler,
-    "          return to prior buffer (directory listing)
-    "          Feb 12, 2008: had to de-activate removal of
-    "          temporary file because it wasn't getting seen.
-    "  if remote == 1 && fname != a:fname
-    "   call s:NetrwDelete(fname)
-    "  endif
+  " cleanup: remove temporary file,
+  "          delete current buffer if success with handler,
+  "          return to prior buffer (directory listing)
+  "          Feb 12, 2008: had to de-activate removal of
+  "          temporary file because it wasn't getting seen.
+  "  if remote == 1 && fname != a:fname
+  "   call s:NetrwDelete(fname)
+  "  endif
 
-    if remote == 1
-      setl bh=delete bt=nofile
-      if g:netrw_use_noswf
-        setl noswf
-      endif
-      exe "sil! NetrwKeepj norm! \"
+  if remote == 1
+    setl bh=delete bt=nofile
+    if g:netrw_use_noswf
+      setl noswf
     endif
-    call winrestview(screenposn)
-    let @@ = ykeep
-    let &aw= awkeep
-  endfun
+    exe "sil! NetrwKeepj norm! \"
+  endif
+  call winrestview(screenposn)
+  let @@ = ykeep
+  let &aw= awkeep
+endfun
 
-  " ---------------------------------------------------------------------
-  " netrw#GX: gets word under cursor for gx support {{{2
-  "           See also: netrw#BrowseXVis
-  "                     netrw#BrowseX
-  fun! netrw#GX()
-    "  call Dfunc("netrw#GX()")
-    if &ft == "netrw"
-      let fname= s:NetrwGetWord()
-    else
-      let fname= exists("g:netrw_gx")? expand(g:netrw_gx) : s:GetURL()
-    endif
-    "  call Dret("netrw#GX <".fname.">")
-    return fname
-  endfun
+" ---------------------------------------------------------------------
+" netrw#GX: gets word under cursor for gx support {{{2
+"           See also: netrw#BrowseXVis
+"                     netrw#BrowseX
+fun! netrw#GX()
+  "  call Dfunc("netrw#GX()")
+  if &ft == "netrw"
+    let fname= s:NetrwGetWord()
+  else
+    let fname= exists("g:netrw_gx")? expand(g:netrw_gx) : s:GetURL()
+  endif
+  "  call Dret("netrw#GX <".fname.">")
+  return fname
+endfun
 
-  fun! s:GetURL() abort
-    let URL = ''
-    if exists('*Netrw_get_URL_' .. &filetype)
-      let URL = call('Netrw_get_URL_' .. &filetype, [])
-    endif
-    if !empty(URL) | return URL | endif
-    " URLs end in letter, digit or forward slash
-    let URL = matchstr(expand(""), '\<' .. g:netrw_regex_url .. '\ze[^A-Za-z0-9/]*$')
-    if !empty(URL) | return URL | endif
+fun! s:GetURL() abort
+  let URL = ''
+  if exists('*Netrw_get_URL_' .. &filetype)
+    let URL = call('Netrw_get_URL_' .. &filetype, [])
+  endif
+  if !empty(URL) | return URL | endif
+  " URLs end in letter, digit or forward slash
+  let URL = matchstr(expand(""), '\<' .. g:netrw_regex_url .. '\ze[^A-Za-z0-9/]*$')
+  if !empty(URL) | return URL | endif
+
+  " Is it a file in the current work dir ...
+  let file = expand("")
+  if filereadable(file) | return file | endif
+  " ... or in that of the current buffer?
+  let path = fnamemodify(expand('%'), ':p')
+  if isdirectory(path)
+    let dir = path
+  elseif filereadable(path)
+    let dir = fnamemodify(path, ':h')
+  endif
+  if exists('dir') && filereadable(dir..'/'..file) | return dir..'/'..file | endif
+
+  return ''
+endf
 
-    " Is it a file in the current work dir ...
-    let file = expand("")
-    if filereadable(file) | return file | endif
-    " ... or in that of the current buffer?
-    let path = fnamemodify(expand('%'), ':p')
-    if isdirectory(path)
-      let dir = path
-    elseif filereadable(path)
-      let dir = fnamemodify(path, ':h')
+" ---------------------------------------------------------------------
+" netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2
+fun! netrw#BrowseXVis()
+  let dict={}
+  let dict.a=[getreg('a'), getregtype('a')]
+  norm! gv"ay
+  let gxfile= @a
+  call s:RestoreRegister(dict)
+  call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile))
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwBufRename: renames a buffer without the side effect of retaining an unlisted buffer having the old name {{{2
+"                   Using the file command on a "[No Name]" buffer does not seem to cause the old "[No Name]" buffer
+"                   to become an unlisted buffer, so in that case don't bwipe it.
+fun! s:NetrwBufRename(newname)
+  "  call Dfunc("s:NetrwBufRename(newname<".a:newname.">) buf(%)#".bufnr("%")."<".bufname(bufnr("%")).">")
+  "  call Dredir("ls!","s:NetrwBufRename (before rename)")
+  let oldbufname= bufname(bufnr("%"))
+  "  call Decho("buf#".bufnr("%").": oldbufname<".oldbufname.">",'~'.expand(""))
+
+  if oldbufname != a:newname
+    "   call Decho("do buffer rename: oldbufname<".oldbufname."> ≠ a:newname<".a:newname.">",'~'.expand(""))
+    let b:junk= 1
+    "   call Decho("rename buffer: sil! keepj keepalt file ".fnameescape(a:newname),'~'.expand(""))
+    exe 'sil! keepj keepalt file '.fnameescape(a:newname)
+    "   call Dredir("ls!","s:NetrwBufRename (before bwipe)~".expand(""))
+    let oldbufnr= bufnr(oldbufname)
+    "   call Decho("oldbufname<".oldbufname."> oldbufnr#".oldbufnr,'~'.expand(""))
+    "   call Decho("bufnr(%)=".bufnr("%"),'~'.expand(""))
+    if oldbufname != "" && oldbufnr != -1 && oldbufnr != bufnr("%")
+      "    call Decho("bwipe ".oldbufnr,'~'.expand(""))
+      exe "bwipe! ".oldbufnr
+      "   else " Decho
+      "    call Decho("did *not* bwipe buf#".oldbufnr,'~'.expand(""))
+      "    call Decho("..reason: if oldbufname<".oldbufname."> is empty",'~'.expand(""))"
+      "    call Decho("..reason: if oldbufnr#".oldbufnr." is -1",'~'.expand(""))"
+      "    call Decho("..reason: if oldbufnr#".oldbufnr." != bufnr(%)#".bufnr("%"),'~'.expand(""))"
     endif
-    if exists('dir') && filereadable(dir..'/'..file) | return dir..'/'..file | endif
+    "   call Dredir("ls!","s:NetrwBufRename (after rename)")
+    "  else " Decho
+    "   call Decho("oldbufname<".oldbufname."> == a:newname: did *not* rename",'~'.expand(""))
+  endif
 
-    return ''
-    endf
+  "  call Dret("s:NetrwBufRename : buf#".bufnr("%").": oldname<".oldbufname."> newname<".a:newname."> expand(%)<".expand("%").">")
+endfun
 
-    " ---------------------------------------------------------------------
-    " netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2
-    fun! netrw#BrowseXVis()
-      let dict={}
-      let dict.a=[getreg('a'), getregtype('a')]
-      norm! gv"ay
-      let gxfile= @a
-      call s:RestoreRegister(dict)
-      call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile))
-    endfun
+" ---------------------------------------------------------------------
+" netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2
+fun! netrw#CheckIfRemote(...)
+  "  call Dfunc("netrw#CheckIfRemote() a:0=".a:0)
+  if a:0 > 0
+    let curfile= a:1
+  else
+    let curfile= expand("%")
+  endif
+  " Ignore terminal buffers
+  if &buftype ==# 'terminal'
+    return 0
+  endif
+  "  call Decho("curfile<".curfile.">")
+  if curfile =~ '^\a\{3,}://'
+    "   call Dret("netrw#CheckIfRemote 1")
+    return 1
+  else
+    "   call Dret("netrw#CheckIfRemote 0")
+    return 0
+  endif
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwBufRename: renames a buffer without the side effect of retaining an unlisted buffer having the old name {{{2
-    "                   Using the file command on a "[No Name]" buffer does not seem to cause the old "[No Name]" buffer
-    "                   to become an unlisted buffer, so in that case don't bwipe it.
-    fun! s:NetrwBufRename(newname)
-      "  call Dfunc("s:NetrwBufRename(newname<".a:newname.">) buf(%)#".bufnr("%")."<".bufname(bufnr("%")).">")
-      "  call Dredir("ls!","s:NetrwBufRename (before rename)")
-      let oldbufname= bufname(bufnr("%"))
-      "  call Decho("buf#".bufnr("%").": oldbufname<".oldbufname.">",'~'.expand(""))
-
-      if oldbufname != a:newname
-        "   call Decho("do buffer rename: oldbufname<".oldbufname."> ≠ a:newname<".a:newname.">",'~'.expand(""))
-        let b:junk= 1
-        "   call Decho("rename buffer: sil! keepj keepalt file ".fnameescape(a:newname),'~'.expand(""))
-        exe 'sil! keepj keepalt file '.fnameescape(a:newname)
-        "   call Dredir("ls!","s:NetrwBufRename (before bwipe)~".expand(""))
-        let oldbufnr= bufnr(oldbufname)
-        "   call Decho("oldbufname<".oldbufname."> oldbufnr#".oldbufnr,'~'.expand(""))
-        "   call Decho("bufnr(%)=".bufnr("%"),'~'.expand(""))
-        if oldbufname != "" && oldbufnr != -1 && oldbufnr != bufnr("%")
-          "    call Decho("bwipe ".oldbufnr,'~'.expand(""))
-          exe "bwipe! ".oldbufnr
-          "   else " Decho
-          "    call Decho("did *not* bwipe buf#".oldbufnr,'~'.expand(""))
-          "    call Decho("..reason: if oldbufname<".oldbufname."> is empty",'~'.expand(""))"
-          "    call Decho("..reason: if oldbufnr#".oldbufnr." is -1",'~'.expand(""))"
-          "    call Decho("..reason: if oldbufnr#".oldbufnr." != bufnr(%)#".bufnr("%"),'~'.expand(""))"
-        endif
-        "   call Dredir("ls!","s:NetrwBufRename (after rename)")
-        "  else " Decho
-        "   call Decho("oldbufname<".oldbufname."> == a:newname: did *not* rename",'~'.expand(""))
+" ---------------------------------------------------------------------
+" s:NetrwChgPerm: (implements "gp") change file permission {{{2
+fun! s:NetrwChgPerm(islocal,curdir)
+  let ykeep  = @@
+  call inputsave()
+  let newperm= input("Enter new permission: ")
+  call inputrestore()
+  let chgperm= substitute(g:netrw_chgperm,'\',s:ShellEscape(expand("")),'')
+  let chgperm= substitute(chgperm,'\',s:ShellEscape(newperm),'')
+  call system(chgperm)
+  if v:shell_error != 0
+    NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("")."> seems to have failed",75)
+  endif
+  if a:islocal
+    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  endif
+  let @@= ykeep
+endfun
+
+" ---------------------------------------------------------------------
+" s:CheckIfKde: checks if kdeinit is running {{{2
+"    Returns 0: kdeinit not running
+"            1: kdeinit is  running
+fun! s:CheckIfKde()
+  "  call Dfunc("s:CheckIfKde()")
+  " seems kde systems often have gnome-open due to dependencies, even though
+  " gnome-open's subsidiary display tools are largely absent.  Kde systems
+  " usually have "kdeinit" running, though...  (tnx Mikolaj Machowski)
+  if !exists("s:haskdeinit")
+    if has("unix") && executable("ps") && !has("win32unix")
+      let s:haskdeinit= system("ps -e") =~ '\"))
+  endif
 
-      "  call Dret("s:NetrwBufRename : buf#".bufnr("%").": oldname<".oldbufname."> newname<".a:newname."> expand(%)<".expand("%").">")
-    endfun
+  "  call Dret("s:CheckIfKde ".s:haskdeinit)
+  return s:haskdeinit
+endfun
 
-    " ---------------------------------------------------------------------
-    " netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2
-    fun! netrw#CheckIfRemote(...)
-      "  call Dfunc("netrw#CheckIfRemote() a:0=".a:0)
-      if a:0 > 0
-        let curfile= a:1
-      else
-        let curfile= expand("%")
-      endif
-      " Ignore terminal buffers
-      if &buftype ==# 'terminal'
-        return 0
-      endif
-      "  call Decho("curfile<".curfile.">")
-      if curfile =~ '^\a\{3,}://'
-        "   call Dret("netrw#CheckIfRemote 1")
-        return 1
-      else
-        "   call Dret("netrw#CheckIfRemote 0")
-        return 0
-      endif
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwClearExplore: clear explore variables (if any) {{{2
+fun! s:NetrwClearExplore()
+  "  call Dfunc("s:NetrwClearExplore()")
+  2match none
+  if exists("s:explore_match")        |unlet s:explore_match        |endif
+  if exists("s:explore_indx")         |unlet s:explore_indx         |endif
+  if exists("s:netrw_explore_prvdir") |unlet s:netrw_explore_prvdir |endif
+  if exists("s:dirstarstar")          |unlet s:dirstarstar          |endif
+  if exists("s:explore_prvdir")       |unlet s:explore_prvdir       |endif
+  if exists("w:netrw_explore_indx")   |unlet w:netrw_explore_indx   |endif
+  if exists("w:netrw_explore_listlen")|unlet w:netrw_explore_listlen|endif
+  if exists("w:netrw_explore_list")   |unlet w:netrw_explore_list   |endif
+  if exists("w:netrw_explore_bufnr")  |unlet w:netrw_explore_bufnr  |endif
+  "   redraw!
+  "  call Dret("s:NetrwClearExplore")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwChgPerm: (implements "gp") change file permission {{{2
-    fun! s:NetrwChgPerm(islocal,curdir)
-      let ykeep  = @@
-      call inputsave()
-      let newperm= input("Enter new permission: ")
-      call inputrestore()
-      let chgperm= substitute(g:netrw_chgperm,'\',s:ShellEscape(expand("")),'')
-      let chgperm= substitute(chgperm,'\',s:ShellEscape(newperm),'')
-      call system(chgperm)
-      if v:shell_error != 0
-        NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("")."> seems to have failed",75)
-      endif
-      if a:islocal
-        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-      endif
-      let @@= ykeep
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2
+fun! s:NetrwEditBuf(bufnum)
+  "  call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)")
+  if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
+    "   call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum))
+    exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)
+  else
+    "   call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum))
+    exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)
+  endif
+  "  call Dret("s:NetrwEditBuf")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:CheckIfKde: checks if kdeinit is running {{{2
-    "    Returns 0: kdeinit not running
-    "            1: kdeinit is  running
-    fun! s:CheckIfKde()
-      "  call Dfunc("s:CheckIfKde()")
-      " seems kde systems often have gnome-open due to dependencies, even though
-      " gnome-open's subsidiary display tools are largely absent.  Kde systems
-      " usually have "kdeinit" running, though...  (tnx Mikolaj Machowski)
-      if !exists("s:haskdeinit")
-        if has("unix") && executable("ps") && !has("win32unix")
-          let s:haskdeinit= system("ps -e") =~ '\"))
-      endif
+" ---------------------------------------------------------------------
+" s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2
+"    NetrwKeepj [keepalt]   
+fun! s:NetrwEditFile(cmd,opt,fname)
+  "  call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">)  ft<".&ft.">")
+  if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
+    "   call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname))
+    exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)
+  else
+    "   call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname))
+    if a:cmd =~# 'e\%[new]!' && !&hidden && getbufvar(bufname('%'), '&modified', 0)
+      call setbufvar(bufname('%'), '&bufhidden', 'hide')
+    endif
+    exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)
+  endif
+  "  call Dret("s:NetrwEditFile")
+endfun
 
-      "  call Dret("s:CheckIfKde ".s:haskdeinit)
-      return s:haskdeinit
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwExploreListUniq: {{{2
+fun! s:NetrwExploreListUniq(explist)
+  " this assumes that the list is already sorted
+  let newexplist= []
+  for member in a:explist
+    if !exists("uniqmember") || member != uniqmember
+      let uniqmember = member
+      let newexplist = newexplist + [ member ]
+    endif
+  endfor
+  return newexplist
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwClearExplore: clear explore variables (if any) {{{2
-    fun! s:NetrwClearExplore()
-      "  call Dfunc("s:NetrwClearExplore()")
-      2match none
-      if exists("s:explore_match")        |unlet s:explore_match        |endif
-      if exists("s:explore_indx")         |unlet s:explore_indx         |endif
-      if exists("s:netrw_explore_prvdir") |unlet s:netrw_explore_prvdir |endif
-      if exists("s:dirstarstar")          |unlet s:dirstarstar          |endif
-      if exists("s:explore_prvdir")       |unlet s:explore_prvdir       |endif
-      if exists("w:netrw_explore_indx")   |unlet w:netrw_explore_indx   |endif
-      if exists("w:netrw_explore_listlen")|unlet w:netrw_explore_listlen|endif
-      if exists("w:netrw_explore_list")   |unlet w:netrw_explore_list   |endif
-      if exists("w:netrw_explore_bufnr")  |unlet w:netrw_explore_bufnr  |endif
-      "   redraw!
-      "  call Dret("s:NetrwClearExplore")
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwForceChgDir: (gd support) Force treatment as a directory {{{2
+fun! s:NetrwForceChgDir(islocal,newdir)
+  let ykeep= @@
+  if a:newdir !~ '/$'
+    " ok, looks like force is needed to get directory-style treatment
+    if a:newdir =~ '@$'
+      let newdir= substitute(a:newdir,'@$','/','')
+    elseif a:newdir =~ '[*=|\\]$'
+      let newdir= substitute(a:newdir,'.$','/','')
+    else
+      let newdir= a:newdir.'/'
+    endif
+  else
+    " should already be getting treatment as a directory
+    let newdir= a:newdir
+  endif
+  let newdir= s:NetrwBrowseChgDir(a:islocal,newdir,0)
+  call s:NetrwBrowse(a:islocal,newdir)
+  let @@= ykeep
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2
-    fun! s:NetrwEditBuf(bufnum)
-      "  call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)")
-      if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
-        "   call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum))
-        exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)
-      else
-        "   call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum))
-        exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)
-      endif
-      "  call Dret("s:NetrwEditBuf")
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwGlob: does glob() if local, remote listing otherwise {{{2
+"     direntry: this is the name of the directory.  Will be fnameescape'd to prevent wildcard handling by glob()
+"     expr    : this is the expression to follow the directory.  Will use s:ComposePath()
+"     pare    =1: remove the current directory from the resulting glob() filelist
+"             =0: leave  the current directory   in the resulting glob() filelist
+fun! s:NetrwGlob(direntry,expr,pare)
+  "  call Dfunc("s:NetrwGlob(direntry<".a:direntry."> expr<".a:expr."> pare=".a:pare.")")
+  if netrw#CheckIfRemote()
+    keepalt 1sp
+    keepalt enew
+    let keep_liststyle    = w:netrw_liststyle
+    let w:netrw_liststyle = s:THINLIST
+    if s:NetrwRemoteListing() == 0
+      keepj keepalt %s@/@@
+      let filelist= getline(1,$)
+      q!
+    else
+      " remote listing error -- leave treedict unchanged
+      let filelist= w:netrw_treedict[a:direntry]
+    endif
+    let w:netrw_liststyle= keep_liststyle
+  else
+    let path= s:ComposePath(fnameescape(a:direntry), a:expr)
+    if has("win32")
+      " escape [ so it is not detected as wildcard character, see :h wildcard
+      let path= substitute(path, '[', '[[]', 'g')
+    endif
+    if v:version > 704 || (v:version == 704 && has("patch656"))
+      let filelist= glob(path,0,1,1)
+    else
+      let filelist= glob(path,0,1)
+    endif
+    if a:pare
+      let filelist= map(filelist,'substitute(v:val, "^.*/", "", "")')
+    endif
+  endif
+  return filelist
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2
-    "    NetrwKeepj [keepalt]   
-    fun! s:NetrwEditFile(cmd,opt,fname)
-      "  call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">)  ft<".&ft.">")
-      if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
-        "   call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname))
-        exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)
-      else
-        "   call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname))
-        if a:cmd =~# 'e\%[new]!' && !&hidden && getbufvar(bufname('%'), '&modified', 0)
-          call setbufvar(bufname('%'), '&bufhidden', 'hide')
-        endif
-        exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)
-      endif
-      "  call Dret("s:NetrwEditFile")
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwForceFile: (gf support) Force treatment as a file {{{2
+fun! s:NetrwForceFile(islocal,newfile)
+  if a:newfile =~ '[/@*=|\\]$'
+    let newfile= substitute(a:newfile,'.$','','')
+  else
+    let newfile= a:newfile
+  endif
+  if a:islocal
+    call s:NetrwBrowseChgDir(a:islocal,newfile,0)
+  else
+    call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,newfile,0))
+  endif
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwExploreListUniq: {{{2
-    fun! s:NetrwExploreListUniq(explist)
-      " this assumes that the list is already sorted
-      let newexplist= []
-      for member in a:explist
-        if !exists("uniqmember") || member != uniqmember
-          let uniqmember = member
-          let newexplist = newexplist + [ member ]
-        endif
-      endfor
-      return newexplist
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwHide: this function is invoked by the "a" map for browsing {{{2
+"          and switches the hiding mode.  The actual hiding is done by
+"          s:NetrwListHide().
+"             g:netrw_hide= 0: show all
+"                           1: show not-hidden files
+"                           2: show hidden files only
+fun! s:NetrwHide(islocal)
+  let ykeep= @@
+  let svpos= winsaveview()
 
-    " ---------------------------------------------------------------------
-    " s:NetrwForceChgDir: (gd support) Force treatment as a directory {{{2
-    fun! s:NetrwForceChgDir(islocal,newdir)
-      let ykeep= @@
-      if a:newdir !~ '/$'
-        " ok, looks like force is needed to get directory-style treatment
-        if a:newdir =~ '@$'
-          let newdir= substitute(a:newdir,'@$','/','')
-        elseif a:newdir =~ '[*=|\\]$'
-          let newdir= substitute(a:newdir,'.$','/','')
-        else
-          let newdir= a:newdir.'/'
-        endif
-      else
-        " should already be getting treatment as a directory
-        let newdir= a:newdir
-      endif
-      let newdir= s:NetrwBrowseChgDir(a:islocal,newdir,0)
-      call s:NetrwBrowse(a:islocal,newdir)
-      let @@= ykeep
-    endfun
+  if exists("s:netrwmarkfilelist_{bufnr('%')}")
 
-    " ---------------------------------------------------------------------
-    " s:NetrwGlob: does glob() if local, remote listing otherwise {{{2
-    "     direntry: this is the name of the directory.  Will be fnameescape'd to prevent wildcard handling by glob()
-    "     expr    : this is the expression to follow the directory.  Will use s:ComposePath()
-    "     pare    =1: remove the current directory from the resulting glob() filelist
-    "             =0: leave  the current directory   in the resulting glob() filelist
-    fun! s:NetrwGlob(direntry,expr,pare)
-      "  call Dfunc("s:NetrwGlob(direntry<".a:direntry."> expr<".a:expr."> pare=".a:pare.")")
-      if netrw#CheckIfRemote()
-        keepalt 1sp
-        keepalt enew
-        let keep_liststyle    = w:netrw_liststyle
-        let w:netrw_liststyle = s:THINLIST
-        if s:NetrwRemoteListing() == 0
-          keepj keepalt %s@/@@
-          let filelist= getline(1,$)
-          q!
-        else
-          " remote listing error -- leave treedict unchanged
-          let filelist= w:netrw_treedict[a:direntry]
-        endif
-        let w:netrw_liststyle= keep_liststyle
+    " hide the files in the markfile list
+    for fname in s:netrwmarkfilelist_{bufnr("%")}
+      if match(g:netrw_list_hide,'\<'.fname.'\>') != -1
+        " remove fname from hiding list
+        let g:netrw_list_hide= substitute(g:netrw_list_hide,'..\<'.escape(fname,g:netrw_fname_escape).'\>..','','')
+        let g:netrw_list_hide= substitute(g:netrw_list_hide,',,',',','g')
+        let g:netrw_list_hide= substitute(g:netrw_list_hide,'^,\|,$','','')
       else
-        let path= s:ComposePath(fnameescape(a:direntry), a:expr)
-        if has("win32")
-          " escape [ so it is not detected as wildcard character, see :h wildcard
-          let path= substitute(path, '[', '[[]', 'g')
-        endif
-        if v:version > 704 || (v:version == 704 && has("patch656"))
-          let filelist= glob(path,0,1,1)
+        " append fname to hiding list
+        if exists("g:netrw_list_hide") && g:netrw_list_hide != ""
+          let g:netrw_list_hide= g:netrw_list_hide.',\<'.escape(fname,g:netrw_fname_escape).'\>'
         else
-          let filelist= glob(path,0,1)
+          let g:netrw_list_hide= '\<'.escape(fname,g:netrw_fname_escape).'\>'
         endif
-        if a:pare
-          let filelist= map(filelist,'substitute(v:val, "^.*/", "", "")')
-        endif
-      endif
-      return filelist
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwForceFile: (gf support) Force treatment as a file {{{2
-    fun! s:NetrwForceFile(islocal,newfile)
-      if a:newfile =~ '[/@*=|\\]$'
-        let newfile= substitute(a:newfile,'.$','','')
-      else
-        let newfile= a:newfile
-      endif
-      if a:islocal
-        call s:NetrwBrowseChgDir(a:islocal,newfile,0)
-      else
-        call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,newfile,0))
       endif
-    endfun
+    endfor
+    NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+    let g:netrw_hide= 1
 
-    " ---------------------------------------------------------------------
-    " s:NetrwHide: this function is invoked by the "a" map for browsing {{{2
-    "          and switches the hiding mode.  The actual hiding is done by
-    "          s:NetrwListHide().
-    "             g:netrw_hide= 0: show all
-    "                           1: show not-hidden files
-    "                           2: show hidden files only
-    fun! s:NetrwHide(islocal)
-      let ykeep= @@
-      let svpos= winsaveview()
+  else
 
-      if exists("s:netrwmarkfilelist_{bufnr('%')}")
+    " switch between show-all/show-not-hidden/show-hidden
+    let g:netrw_hide=(g:netrw_hide+1)%3
+    exe "NetrwKeepj norm! 0"
+    if g:netrw_hide && g:netrw_list_hide == ""
+      NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49)
+      let @@= ykeep
+      return
+    endif
+  endif
 
-        " hide the files in the markfile list
-        for fname in s:netrwmarkfilelist_{bufnr("%")}
-          if match(g:netrw_list_hide,'\<'.fname.'\>') != -1
-            " remove fname from hiding list
-            let g:netrw_list_hide= substitute(g:netrw_list_hide,'..\<'.escape(fname,g:netrw_fname_escape).'\>..','','')
-            let g:netrw_list_hide= substitute(g:netrw_list_hide,',,',',','g')
-            let g:netrw_list_hide= substitute(g:netrw_list_hide,'^,\|,$','','')
-          else
-            " append fname to hiding list
-            if exists("g:netrw_list_hide") && g:netrw_list_hide != ""
-              let g:netrw_list_hide= g:netrw_list_hide.',\<'.escape(fname,g:netrw_fname_escape).'\>'
-            else
-              let g:netrw_list_hide= '\<'.escape(fname,g:netrw_fname_escape).'\>'
-            endif
-          endif
-        endfor
-        NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
-        let g:netrw_hide= 1
+  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  NetrwKeepj call winrestview(svpos)
+  let @@= ykeep
+endfun
 
-      else
+" ---------------------------------------------------------------------
+" s:NetrwHideEdit: allows user to edit the file/directory hiding list {{{2
+fun! s:NetrwHideEdit(islocal)
+  let ykeep= @@
+  " save current cursor position
+  let svpos= winsaveview()
 
-        " switch between show-all/show-not-hidden/show-hidden
-        let g:netrw_hide=(g:netrw_hide+1)%3
-        exe "NetrwKeepj norm! 0"
-        if g:netrw_hide && g:netrw_list_hide == ""
-          NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49)
-          let @@= ykeep
-          return
-        endif
-      endif
+  " get new hiding list from user
+  call inputsave()
+  let newhide= input("Edit Hiding List: ",g:netrw_list_hide)
+  call inputrestore()
+  let g:netrw_list_hide= newhide
 
-      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-      NetrwKeepj call winrestview(svpos)
-      let @@= ykeep
-    endfun
+  " refresh the listing
+  sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./",0))
 
-    " ---------------------------------------------------------------------
-    " s:NetrwHideEdit: allows user to edit the file/directory hiding list {{{2
-    fun! s:NetrwHideEdit(islocal)
-      let ykeep= @@
-      " save current cursor position
-      let svpos= winsaveview()
+  " restore cursor position
+  call winrestview(svpos)
+  let @@= ykeep
+endfun
 
-      " get new hiding list from user
-      call inputsave()
-      let newhide= input("Edit Hiding List: ",g:netrw_list_hide)
-      call inputrestore()
-      let g:netrw_list_hide= newhide
+" ---------------------------------------------------------------------
+" s:NetrwHidden: invoked by "gh" {{{2
+fun! s:NetrwHidden(islocal)
+  let ykeep= @@
+  "  save current position
+  let svpos  = winsaveview()
 
-      " refresh the listing
-      sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./",0))
+  if g:netrw_list_hide =~ '\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+'
+    " remove .file pattern from hiding list
+    let g:netrw_list_hide= substitute(g:netrw_list_hide,'\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+','','')
+  elseif s:Strlen(g:netrw_list_hide) >= 1
+    let g:netrw_list_hide= g:netrw_list_hide . ',\(^\|\s\s\)\zs\.\S\+'
+  else
+    let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+'
+  endif
+  if g:netrw_list_hide =~ '^,'
+    let g:netrw_list_hide= strpart(g:netrw_list_hide,1)
+  endif
 
-      " restore cursor position
-      call winrestview(svpos)
-      let @@= ykeep
-    endfun
+  " refresh screen and return to saved position
+  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  NetrwKeepj call winrestview(svpos)
+  let @@= ykeep
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwHidden: invoked by "gh" {{{2
-    fun! s:NetrwHidden(islocal)
-      let ykeep= @@
-      "  save current position
-      let svpos  = winsaveview()
-
-      if g:netrw_list_hide =~ '\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+'
-        " remove .file pattern from hiding list
-        let g:netrw_list_hide= substitute(g:netrw_list_hide,'\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+','','')
-      elseif s:Strlen(g:netrw_list_hide) >= 1
-        let g:netrw_list_hide= g:netrw_list_hide . ',\(^\|\s\s\)\zs\.\S\+'
-      else
-        let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+'
-      endif
-      if g:netrw_list_hide =~ '^,'
-        let g:netrw_list_hide= strpart(g:netrw_list_hide,1)
-      endif
+" ---------------------------------------------------------------------
+"  s:NetrwHome: this function determines a "home" for saving bookmarks and history {{{2
+fun! s:NetrwHome()
+  if exists("g:netrw_home")
+    let home= expand(g:netrw_home)
+  else
+    let home = stdpath('data')
+  endif
+  " insure that the home directory exists
+  if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home))
+    "   call Decho("insure that the home<".home."> directory exists")
+    if exists("g:netrw_mkdir")
+      "    call Decho("call system(".g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)).")")
+      call system(g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)))
+    else
+      "    call Decho("mkdir(".home.")")
+      call mkdir(home)
+    endif
+  endif
+  let g:netrw_home= home
+  return home
+endfun
 
-      " refresh screen and return to saved position
-      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-      NetrwKeepj call winrestview(svpos)
-      let @@= ykeep
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwLeftmouse: handles the  when in a netrw browsing window {{{2
+fun! s:NetrwLeftmouse(islocal)
+  if exists("s:netrwdrag")
+    return
+  endif
+  if &ft != "netrw"
+    return
+  endif
+
+  let ykeep= @@
+  " check if the status bar was clicked on instead of a file/directory name
+  while getchar(0) != 0
+    "clear the input stream
+  endwhile
+  call feedkeys("\")
+  let c          = getchar()
+  let mouse_lnum = v:mouse_lnum
+  let wlastline  = line('w$')
+  let lastline   = line('$')
+  if mouse_lnum >= wlastline + 1 || v:mouse_win != winnr()
+    " appears to be a status bar leftmouse click
+    let @@= ykeep
+    return
+  endif
+  " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode
+  " Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar
+  " without this test when its disabled.
+  " May 26, 2014: edit file, :Lex, resize window -- causes refresh.  Reinstated a modified test.  See if problems develop.
+  if v:mouse_col > virtcol('.')
+    let @@= ykeep
+    return
+  endif
+
+  if a:islocal
+    if exists("b:netrw_curdir")
+      NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
+    endif
+  else
+    if exists("b:netrw_curdir")
+      NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+    endif
+  endif
+  let @@= ykeep
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwCLeftmouse: used to select a file/directory for a target {{{2
+fun! s:NetrwCLeftmouse(islocal)
+  if &ft != "netrw"
+    return
+  endif
+  call s:NetrwMarkFileTgt(a:islocal)
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER  (implements ){{{2
+"   a:islocal=0 :  not used, remote
+"   a:islocal=1 :  not used, local
+"   a:islocal=2 :      used, remote
+"   a:islocal=3 :      used, local
+fun! s:NetrwServerEdit(islocal,fname)
+  "  call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)")
+  let islocal = a:islocal%2      " =0: remote           =1: local
+  let ctrlr   = a:islocal >= 2   " =0:  not used   =1:  used
+
+  if (islocal && isdirectory(s:NetrwFile(a:fname))) || (!islocal && a:fname =~ '/$')
+    " handle directories in the local window -- not in the remote vim server
+    " user must have closed the NETRWSERVER window.  Treat as normal editing from netrw.
+    let g:netrw_browse_split= 0
+    if exists("s:netrw_browse_split") && exists("s:netrw_browse_split_".winnr())
+      let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
+      unlet s:netrw_browse_split_{winnr()}
+    endif
+    call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
+    return
+  endif
+
+  if has("clientserver") && executable("gvim")
+
+    if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3
+      let srvrname = g:netrw_browse_split[0]
+      let tabnum   = g:netrw_browse_split[1]
+      let winnum   = g:netrw_browse_split[2]
+
+      if serverlist() !~ '\<'.srvrname.'\>'
+        if !ctrlr
+          " user must have closed the server window and the user did not use , but
+          " used something like .
+          if exists("g:netrw_browse_split")
+            unlet g:netrw_browse_split
+          endif
+          let g:netrw_browse_split= 0
+          if exists("s:netrw_browse_split_".winnr())
+            let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
+          endif
+          call s:NetrwBrowseChgDir(islocal,a:fname,0)
+          return
+
+        elseif has("win32") && executable("start")
+          " start up remote netrw server under windows
+          call system("start gvim --servername ".srvrname)
 
-    " ---------------------------------------------------------------------
-    "  s:NetrwHome: this function determines a "home" for saving bookmarks and history {{{2
-    fun! s:NetrwHome()
-      if exists("g:netrw_home")
-        let home= expand(g:netrw_home)
-      else
-        let home = stdpath('data')
-      endif
-      " insure that the home directory exists
-      if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home))
-        "   call Decho("insure that the home<".home."> directory exists")
-        if exists("g:netrw_mkdir")
-          "    call Decho("call system(".g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)).")")
-          call system(g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)))
         else
-          "    call Decho("mkdir(".home.")")
-          call mkdir(home)
+          " start up remote netrw server under linux
+          call system("gvim --servername ".srvrname)
         endif
       endif
-      let g:netrw_home= home
-      return home
-    endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwLeftmouse: handles the  when in a netrw browsing window {{{2
-    fun! s:NetrwLeftmouse(islocal)
-      if exists("s:netrwdrag")
-        return
-      endif
-      if &ft != "netrw"
-        return
-      endif
+      call remote_send(srvrname,":tabn ".tabnum."\")
+      call remote_send(srvrname,":".winnum."wincmd w\")
+      call remote_send(srvrname,":e ".fnameescape(s:NetrwFile(a:fname))."\")
+    else
 
-      let ykeep= @@
-      " check if the status bar was clicked on instead of a file/directory name
-      while getchar(0) != 0
-        "clear the input stream
-      endwhile
-      call feedkeys("\")
-      let c          = getchar()
-      let mouse_lnum = v:mouse_lnum
-      let wlastline  = line('w$')
-      let lastline   = line('$')
-      if mouse_lnum >= wlastline + 1 || v:mouse_win != winnr()
-        " appears to be a status bar leftmouse click
-        let @@= ykeep
-        return
-      endif
-      " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode
-      " Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar
-      " without this test when its disabled.
-      " May 26, 2014: edit file, :Lex, resize window -- causes refresh.  Reinstated a modified test.  See if problems develop.
-      if v:mouse_col > virtcol('.')
-        let @@= ykeep
-        return
-      endif
+      if serverlist() !~ '\<'.g:netrw_servername.'\>'
 
-      if a:islocal
-        if exists("b:netrw_curdir")
-          NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1))
-        endif
-      else
-        if exists("b:netrw_curdir")
-          NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+        if !ctrlr
+          if exists("g:netrw_browse_split")
+            unlet g:netrw_browse_split
+          endif
+          let g:netrw_browse_split= 0
+          call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
+          return
+
+        else
+          if has("win32") && executable("start")
+            " start up remote netrw server under windows
+            call system("start gvim --servername ".g:netrw_servername)
+          else
+            " start up remote netrw server under linux
+            call system("gvim --servername ".g:netrw_servername)
+          endif
         endif
       endif
-      let @@= ykeep
-    endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwCLeftmouse: used to select a file/directory for a target {{{2
-    fun! s:NetrwCLeftmouse(islocal)
-      if &ft != "netrw"
-        return
-      endif
-      call s:NetrwMarkFileTgt(a:islocal)
-    endfun
+      while 1
+        try
+          call remote_send(g:netrw_servername,":e ".fnameescape(s:NetrwFile(a:fname))."\")
+          break
+        catch /^Vim\%((\a\+)\)\=:E241/
+          sleep 200m
+        endtry
+      endwhile
 
-    " ---------------------------------------------------------------------
-    " s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER  (implements ){{{2
-    "   a:islocal=0 :  not used, remote
-    "   a:islocal=1 :  not used, local
-    "   a:islocal=2 :      used, remote
-    "   a:islocal=3 :      used, local
-    fun! s:NetrwServerEdit(islocal,fname)
-      "  call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)")
-      let islocal = a:islocal%2      " =0: remote           =1: local
-      let ctrlr   = a:islocal >= 2   " =0:  not used   =1:  used
-
-      if (islocal && isdirectory(s:NetrwFile(a:fname))) || (!islocal && a:fname =~ '/$')
-        " handle directories in the local window -- not in the remote vim server
-        " user must have closed the NETRWSERVER window.  Treat as normal editing from netrw.
-        let g:netrw_browse_split= 0
-        if exists("s:netrw_browse_split") && exists("s:netrw_browse_split_".winnr())
-          let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
-          unlet s:netrw_browse_split_{winnr()}
+      if exists("g:netrw_browse_split")
+        if type(g:netrw_browse_split) != 3
+          let s:netrw_browse_split_{winnr()}= g:netrw_browse_split
         endif
-        call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
-        return
+        unlet g:netrw_browse_split
       endif
+      let g:netrw_browse_split= [g:netrw_servername,1,1]
+    endif
 
-      if has("clientserver") && executable("gvim")
-
-        if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3
-          let srvrname = g:netrw_browse_split[0]
-          let tabnum   = g:netrw_browse_split[1]
-          let winnum   = g:netrw_browse_split[2]
+  else
+    call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use ",98)
+  endif
 
-          if serverlist() !~ '\<'.srvrname.'\>'
-            if !ctrlr
-              " user must have closed the server window and the user did not use , but
-              " used something like .
-              if exists("g:netrw_browse_split")
-                unlet g:netrw_browse_split
-              endif
-              let g:netrw_browse_split= 0
-              if exists("s:netrw_browse_split_".winnr())
-                let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
-              endif
-              call s:NetrwBrowseChgDir(islocal,a:fname,0)
-              return
+endfun
 
-            elseif has("win32") && executable("start")
-              " start up remote netrw server under windows
-              call system("start gvim --servername ".srvrname)
+" ---------------------------------------------------------------------
+" s:NetrwSLeftmouse: marks the file under the cursor.  May be dragged to select additional files {{{2
+fun! s:NetrwSLeftmouse(islocal)
+  if &ft != "netrw"
+    return
+  endif
+  "  call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")")
 
-            else
-              " start up remote netrw server under linux
-              call system("gvim --servername ".srvrname)
-            endif
-          endif
+  let s:ngw= s:NetrwGetWord()
+  call s:NetrwMarkFile(a:islocal,s:ngw)
 
-          call remote_send(srvrname,":tabn ".tabnum."\")
-          call remote_send(srvrname,":".winnum."wincmd w\")
-          call remote_send(srvrname,":e ".fnameescape(s:NetrwFile(a:fname))."\")
-        else
+  "  call Dret("s:NetrwSLeftmouse")
+endfun
 
-          if serverlist() !~ '\<'.g:netrw_servername.'\>'
+" ---------------------------------------------------------------------
+" s:NetrwSLeftdrag: invoked via a shift-leftmouse and dragging {{{2
+"                   Used to mark multiple files.
+fun! s:NetrwSLeftdrag(islocal)
+  "  call Dfunc("s:NetrwSLeftdrag(islocal=".a:islocal.")")
+  if !exists("s:netrwdrag")
+    let s:netrwdrag = winnr()
+    if a:islocal
+      nno   :call NetrwSLeftrelease(1)
+    else
+      nno   :call NetrwSLeftrelease(0)
+    endif
+  endif
+  let ngw = s:NetrwGetWord()
+  if !exists("s:ngw") || s:ngw != ngw
+    call s:NetrwMarkFile(a:islocal,ngw)
+  endif
+  let s:ngw= ngw
+  "  call Dret("s:NetrwSLeftdrag : s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
+endfun
 
-            if !ctrlr
-              if exists("g:netrw_browse_split")
-                unlet g:netrw_browse_split
-              endif
-              let g:netrw_browse_split= 0
-              call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0))
-              return
+" ---------------------------------------------------------------------
+" s:NetrwSLeftrelease: terminates shift-leftmouse dragging {{{2
+fun! s:NetrwSLeftrelease(islocal)
+  "  call Dfunc("s:NetrwSLeftrelease(islocal=".a:islocal.") s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
+  if exists("s:netrwdrag")
+    nunmap 
+    let ngw = s:NetrwGetWord()
+    if !exists("s:ngw") || s:ngw != ngw
+      call s:NetrwMarkFile(a:islocal,ngw)
+    endif
+    if exists("s:ngw")
+      unlet s:ngw
+    endif
+    unlet s:netrwdrag
+  endif
+  "  call Dret("s:NetrwSLeftrelease")
+endfun
 
-            else
-              if has("win32") && executable("start")
-                " start up remote netrw server under windows
-                call system("start gvim --servername ".g:netrw_servername)
-              else
-                " start up remote netrw server under linux
-                call system("gvim --servername ".g:netrw_servername)
-              endif
-            endif
-          endif
+" ---------------------------------------------------------------------
+" s:NetrwListHide: uses [range]g~...~d to delete files that match       {{{2
+"                  comma-separated patterns given in g:netrw_list_hide
+fun! s:NetrwListHide()
+  "  call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
+  "  call Decho("initial: ".string(getline(w:netrw_bannercnt,'$')))
+  let ykeep= @@
 
-          while 1
-            try
-              call remote_send(g:netrw_servername,":e ".fnameescape(s:NetrwFile(a:fname))."\")
-              break
-            catch /^Vim\%((\a\+)\)\=:E241/
-              sleep 200m
-            endtry
-          endwhile
+  " find a character not in the "hide" string to use as a separator for :g and :v commands
+  " How-it-works: take the hiding command, convert it into a range.
+  " Duplicate characters don't matter.
+  " Remove all such characters from the '/~@#...890' string.
+  " Use the first character left as a separator character.
+  "  call Decho("find a character not in the hide string to use as a separator",'~'.expand(""))
+  let listhide= g:netrw_list_hide
+  let sep     = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1)
+  "  call Decho("sep<".sep.">  (sep not in hide string)",'~'.expand(""))
+
+  while listhide != ""
+    if listhide =~ ','
+      let hide     = substitute(listhide,',.*$','','e')
+      let listhide = substitute(listhide,'^.\{-},\(.*\)$','\1','e')
+    else
+      let hide     = listhide
+      let listhide = ""
+    endif
+    "   call Decho("..extracted pattern from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand(""))
+    if g:netrw_sort_by =~ '^[ts]'
+      if hide =~ '^\^'
+        "     call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand(""))
+        let hide= substitute(hide,'^\^','^\(\\d\\+/\)','')
+      elseif hide =~ '^\\(\^'
+        let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','')
+      endif
+      "    call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand(""))
+    endif
+
+    " Prune the list by hiding any files which match
+    "   call Decho("..prune the list by hiding any files which ".((g:netrw_hide == 1)? "" : "don't")."match hide<".hide.">")
+    if g:netrw_hide == 1
+      "    call Decho("..hiding<".hide.">",'~'.expand(""))
+      exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d'
+    elseif g:netrw_hide == 2
+      "    call Decho("..showing<".hide.">",'~'.expand(""))
+      exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @'
+    endif
+    "   call Decho("..result: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
+  endwhile
 
-          if exists("g:netrw_browse_split")
-            if type(g:netrw_browse_split) != 3
-              let s:netrw_browse_split_{winnr()}= g:netrw_browse_split
-            endif
-            unlet g:netrw_browse_split
-          endif
-          let g:netrw_browse_split= [g:netrw_servername,1,1]
-        endif
+  if g:netrw_hide == 2
+    exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d'
+    "   call Decho("..v KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
+    exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e'
+    "   call Decho("..g KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
+  endif
 
-      else
-        call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use ",98)
-      endif
+  " remove any blank lines that have somehow remained.
+  " This seems to happen under Windows.
+  exe 'sil! NetrwKeepj 1,$g@^\s*$@d'
 
-    endfun
+  let @@= ykeep
+  "  call Dret("s:NetrwListHide")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwSLeftmouse: marks the file under the cursor.  May be dragged to select additional files {{{2
-    fun! s:NetrwSLeftmouse(islocal)
-      if &ft != "netrw"
-        return
-      endif
-      "  call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")")
+" ---------------------------------------------------------------------
+" s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2
+"                 implements the "d" mapping.
+fun! s:NetrwMakeDir(usrhost)
 
-      let s:ngw= s:NetrwGetWord()
-      call s:NetrwMarkFile(a:islocal,s:ngw)
+  let ykeep= @@
+  " get name of new directory from user.  A bare  will skip.
+  " if its currently a directory, also request will be skipped, but with
+  " a message.
+  call inputsave()
+  let newdirname= input("Please give directory name: ")
+  call inputrestore()
+
+  if newdirname == ""
+    let @@= ykeep
+    return
+  endif
 
-      "  call Dret("s:NetrwSLeftmouse")
-    endfun
+  if a:usrhost == ""
 
-    " ---------------------------------------------------------------------
-    " s:NetrwSLeftdrag: invoked via a shift-leftmouse and dragging {{{2
-    "                   Used to mark multiple files.
-    fun! s:NetrwSLeftdrag(islocal)
-      "  call Dfunc("s:NetrwSLeftdrag(islocal=".a:islocal.")")
-      if !exists("s:netrwdrag")
-        let s:netrwdrag = winnr()
-        if a:islocal
-          nno   :call NetrwSLeftrelease(1)
-        else
-          nno   :call NetrwSLeftrelease(0)
-        endif
+    " Local mkdir:
+    " sanity checks
+    let fullnewdir= b:netrw_curdir.'/'.newdirname
+    if isdirectory(s:NetrwFile(fullnewdir))
+      if !exists("g:netrw_quiet")
+        NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24)
       endif
-      let ngw = s:NetrwGetWord()
-      if !exists("s:ngw") || s:ngw != ngw
-        call s:NetrwMarkFile(a:islocal,ngw)
+      let @@= ykeep
+      return
+    endif
+    if s:FileReadable(fullnewdir)
+      if !exists("g:netrw_quiet")
+        NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25)
       endif
-      let s:ngw= ngw
-      "  call Dret("s:NetrwSLeftdrag : s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
-    endfun
+      let @@= ykeep
+      return
+    endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwSLeftrelease: terminates shift-leftmouse dragging {{{2
-    fun! s:NetrwSLeftrelease(islocal)
-      "  call Dfunc("s:NetrwSLeftrelease(islocal=".a:islocal.") s:netrwdrag=".s:netrwdrag." buf#".bufnr("%"))
-      if exists("s:netrwdrag")
-        nunmap 
-        let ngw = s:NetrwGetWord()
-        if !exists("s:ngw") || s:ngw != ngw
-          call s:NetrwMarkFile(a:islocal,ngw)
-        endif
-        if exists("s:ngw")
-          unlet s:ngw
+    " requested new local directory is neither a pre-existing file or
+    " directory, so make it!
+    if exists("*mkdir")
+      if has("unix")
+        call mkdir(fullnewdir,"p",xor(0777, system("umask")))
+      else
+        call mkdir(fullnewdir,"p")
+      endif
+    else
+      let netrw_origdir= s:NetrwGetcwd(1)
+      if s:NetrwLcd(b:netrw_curdir)
+        return
+      endif
+      call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(newdirname,1))
+      if v:shell_error != 0
+        let @@= ykeep
+        call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
+        return
+      endif
+      if !g:netrw_keepdir
+        if s:NetrwLcd(netrw_origdir)
+          return
         endif
-        unlet s:netrwdrag
       endif
-      "  call Dret("s:NetrwSLeftrelease")
-    endfun
+    endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwListHide: uses [range]g~...~d to delete files that match       {{{2
-    "                  comma-separated patterns given in g:netrw_list_hide
-    fun! s:NetrwListHide()
-      "  call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
-      "  call Decho("initial: ".string(getline(w:netrw_bannercnt,'$')))
-      let ykeep= @@
-
-      " find a character not in the "hide" string to use as a separator for :g and :v commands
-      " How-it-works: take the hiding command, convert it into a range.
-      " Duplicate characters don't matter.
-      " Remove all such characters from the '/~@#...890' string.
-      " Use the first character left as a separator character.
-      "  call Decho("find a character not in the hide string to use as a separator",'~'.expand(""))
-      let listhide= g:netrw_list_hide
-      let sep     = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1)
-      "  call Decho("sep<".sep.">  (sep not in hide string)",'~'.expand(""))
-
-      while listhide != ""
-        if listhide =~ ','
-          let hide     = substitute(listhide,',.*$','','e')
-          let listhide = substitute(listhide,'^.\{-},\(.*\)$','\1','e')
-        else
-          let hide     = listhide
-          let listhide = ""
-        endif
-        "   call Decho("..extracted pattern from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand(""))
-        if g:netrw_sort_by =~ '^[ts]'
-          if hide =~ '^\^'
-            "     call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand(""))
-            let hide= substitute(hide,'^\^','^\(\\d\\+/\)','')
-          elseif hide =~ '^\\(\^'
-            let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','')
-          endif
-          "    call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand(""))
-        endif
+    if v:shell_error == 0
+      " refresh listing
+      let svpos= winsaveview()
+      call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
+      call winrestview(svpos)
+    elseif !exists("g:netrw_quiet")
+      call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",26)
+    endif
 
-        " Prune the list by hiding any files which match
-        "   call Decho("..prune the list by hiding any files which ".((g:netrw_hide == 1)? "" : "don't")."match hide<".hide.">")
-        if g:netrw_hide == 1
-          "    call Decho("..hiding<".hide.">",'~'.expand(""))
-          exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d'
-        elseif g:netrw_hide == 2
-          "    call Decho("..showing<".hide.">",'~'.expand(""))
-          exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @'
-        endif
-        "   call Decho("..result: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
-      endwhile
+  elseif !exists("b:netrw_method") || b:netrw_method == 4
+    " Remote mkdir:  using ssh
+    let mkdircmd  = s:MakeSshCmd(g:netrw_mkdir_cmd)
+    let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname
+    call s:NetrwExe("sil! !".mkdircmd." ".s:ShellEscape(newdirname,1))
+    if v:shell_error == 0
+      " refresh listing
+      let svpos= winsaveview()
+      NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
+      NetrwKeepj call winrestview(svpos)
+    elseif !exists("g:netrw_quiet")
+      NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27)
+    endif
 
-      if g:netrw_hide == 2
-        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d'
-        "   call Decho("..v KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
-        exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e'
-        "   call Decho("..g KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand(""))
-      endif
+  elseif b:netrw_method == 2
+    " Remote mkdir:  using ftp+.netrc
+    let svpos= winsaveview()
+    if exists("b:netrw_fname")
+      let remotepath= b:netrw_fname
+    else
+      let remotepath= ""
+    endif
+    call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
+    NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
+    NetrwKeepj call winrestview(svpos)
 
-      " remove any blank lines that have somehow remained.
-      " This seems to happen under Windows.
-      exe 'sil! NetrwKeepj 1,$g@^\s*$@d'
+  elseif b:netrw_method == 3
+    " Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc)
+    let svpos= winsaveview()
+    if exists("b:netrw_fname")
+      let remotepath= b:netrw_fname
+    else
+      let remotepath= ""
+    endif
+    call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
+    NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
+    NetrwKeepj call winrestview(svpos)
+  endif
 
-      let @@= ykeep
-      "  call Dret("s:NetrwListHide")
-    endfun
+  let @@= ykeep
+endfun
 
+" ---------------------------------------------------------------------
+" s:TreeSqueezeDir: allows a shift-cr (gvim only) to squeeze the current tree-listing directory {{{2
+fun! s:TreeSqueezeDir(islocal)
+  if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
+    " its a tree-listing style
+    let curdepth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e')
+    let stopline = (exists("w:netrw_bannercnt")? (w:netrw_bannercnt + 1) : 1)
+    let depth    = strchars(substitute(curdepth,' ','','g'))
+    let srch     = -1
+    if depth >= 2
+      NetrwKeepj norm! 0
+      let curdepthm1= substitute(curdepth,'^'.s:treedepthstring,'','')
+      let srch      = search('^'.curdepthm1.'\%('.s:treedepthstring.'\)\@!','bW',stopline)
+    elseif depth == 1
+      NetrwKeepj norm! 0
+      let treedepthchr= substitute(s:treedepthstring,' ','','')
+      let srch        = search('^[^'.treedepthchr.']','bW',stopline)
+    endif
+    if srch > 0
+      call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,s:NetrwGetWord(),1))
+      exe srch
+    endif
+  endif
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwMaps: {{{2
+fun! s:NetrwMaps(islocal)
+
+  " mouse  maps: {{{3
+  if g:netrw_mousemaps && g:netrw_retmap
+    "   call Decho("set up Rexplore 2-leftmouse",'~'.expand(""))
+    if !hasmapto("NetrwReturn")
+      if maparg("<2-leftmouse>","n") == "" || maparg("<2-leftmouse>","n") =~ '^-$'
+        nmap   <2-leftmouse>       NetrwReturn
+      elseif maparg("","n") == ""
+        nmap          NetrwReturn
+      endif
+    endif
+    nno  NetrwReturn       :Rexplore
+  endif
+
+  " generate default  maps {{{3
+  if !hasmapto('NetrwHide')              |nmap    a       NetrwHide_a|endif
+  if !hasmapto('NetrwBrowseUpDir')       |nmap    -       NetrwBrowseUpDir|endif
+  if !hasmapto('NetrwOpenFile')          |nmap    %       NetrwOpenFile|endif
+  if !hasmapto('NetrwBadd_cb')           |nmap    cb      NetrwBadd_cb|endif
+  if !hasmapto('NetrwBadd_cB')           |nmap    cB      NetrwBadd_cB|endif
+  if !hasmapto('NetrwLcd')               |nmap    cd      NetrwLcd|endif
+  if !hasmapto('NetrwSetChgwin')         |nmap    C       NetrwSetChgwin|endif
+  if !hasmapto('NetrwRefresh')           |nmap       NetrwRefresh|endif
+  if !hasmapto('NetrwLocalBrowseCheck')  |nmap        NetrwLocalBrowseCheck|endif
+  if !hasmapto('NetrwServerEdit')        |nmap       NetrwServerEdit|endif
+  if !hasmapto('NetrwMakeDir')           |nmap    d       NetrwMakeDir|endif
+  if !hasmapto('NetrwBookHistHandler_gb')|nmap    gb      NetrwBookHistHandler_gb|endif
+
+  if a:islocal
+    " local normal-mode maps {{{3
+    nnoremap   NetrwHide_a                 :call NetrwHide(1)
+    nnoremap   NetrwBrowseUpDir            :call NetrwBrowseUpDir(1)
+    nnoremap   NetrwOpenFile               :call NetrwOpenFile(1)
+    nnoremap   NetrwBadd_cb                :call NetrwBadd(1,0)
+    nnoremap   NetrwBadd_cB                :call NetrwBadd(1,1)
+    nnoremap   NetrwLcd                    :call NetrwLcd(b:netrw_curdir)
+    nnoremap   NetrwSetChgwin              :call NetrwSetChgwin()
+    nnoremap   NetrwLocalBrowseCheck       :call netrw#LocalBrowseCheck(NetrwBrowseChgDir(1,NetrwGetWord(),1))
+    nnoremap   NetrwServerEdit             :call NetrwServerEdit(3,NetrwGetWord())
+    nnoremap   NetrwMakeDir                :call NetrwMakeDir("")
+    nnoremap   NetrwBookHistHandler_gb     :call NetrwBookHistHandler(1,b:netrw_curdir)
     " ---------------------------------------------------------------------
-    " s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2
-    "                 implements the "d" mapping.
-    fun! s:NetrwMakeDir(usrhost)
-
-      let ykeep= @@
-      " get name of new directory from user.  A bare  will skip.
-      " if its currently a directory, also request will be skipped, but with
-      " a message.
-      call inputsave()
-      let newdirname= input("Please give directory name: ")
-      call inputrestore()
+    nnoremap    gd       :call NetrwForceChgDir(1,NetrwGetWord())
+    nnoremap    gf       :call NetrwForceFile(1,NetrwGetWord())
+    nnoremap    gh       :call NetrwHidden(1)
+    nnoremap    gn       :call netrw#SetTreetop(0,NetrwGetWord())
+    nnoremap    gp       :call NetrwChgPerm(1,b:netrw_curdir)
+    nnoremap    I        :call NetrwBannerCtrl(1)
+    nnoremap    i        :call NetrwListStyle(1)
+    nnoremap    ma       :call NetrwMarkFileArgList(1,0)
+    nnoremap    mA       :call NetrwMarkFileArgList(1,1)
+    nnoremap    mb       :call NetrwBookHistHandler(0,b:netrw_curdir)
+    nnoremap    mB       :call NetrwBookHistHandler(6,b:netrw_curdir)
+    nnoremap    mc       :call NetrwMarkFileCopy(1)
+    nnoremap    md       :call NetrwMarkFileDiff(1)
+    nnoremap    me       :call NetrwMarkFileEdit(1)
+    nnoremap    mf       :call NetrwMarkFile(1,NetrwGetWord())
+    nnoremap    mF       :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+    nnoremap    mg       :call NetrwMarkFileGrep(1)
+    nnoremap    mh       :call NetrwMarkHideSfx(1)
+    nnoremap    mm       :call NetrwMarkFileMove(1)
+    " nnoremap    mp       :call NetrwMarkFilePrint(1)
+    nnoremap    mr       :call NetrwMarkFileRegexp(1)
+    nnoremap    ms       :call NetrwMarkFileSource(1)
+    nnoremap    mT       :call NetrwMarkFileTag(1)
+    nnoremap    mt       :call NetrwMarkFileTgt(1)
+    nnoremap    mu       :call NetrwUnMarkFile(1)
+    nnoremap    mv       :call NetrwMarkFileVimCmd(1)
+    nnoremap    mx       :call NetrwMarkFileExe(1,0)
+    nnoremap    mX       :call NetrwMarkFileExe(1,1)
+    nnoremap    mz       :call NetrwMarkFileCompress(1)
+    nnoremap    O        :call NetrwObtain(1)
+    nnoremap    o        :call NetrwSplit(3)
+    nnoremap    p        :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
+    nnoremap    P        :call NetrwPrevWinOpen(1)
+    nnoremap    qb       :call NetrwBookHistHandler(2,b:netrw_curdir)
+    nnoremap    qf       :call NetrwFileInfo(1,NetrwGetWord())
+    nnoremap    qF       :call NetrwMarkFileQFEL(1,getqflist())
+    nnoremap    qL       :call NetrwMarkFileQFEL(1,getloclist(v:count))
+    nnoremap    s        :call NetrwSortStyle(1)
+    nnoremap    S        :call NetSortSequence(1)
+    nnoremap    Tb       :call NetrwSetTgt(1,'b',v:count1)
+    nnoremap    t        :call NetrwSplit(4)
+    nnoremap    Th       :call NetrwSetTgt(1,'h',v:count)
+    nnoremap    u        :call NetrwBookHistHandler(4,expand("%"))
+    nnoremap    U        :call NetrwBookHistHandler(5,expand("%"))
+    nnoremap    v        :call NetrwSplit(5)
+    nnoremap    x        :call netrw#BrowseX(NetrwBrowseChgDir(1,NetrwGetWord(),1,0),0)"
+    nnoremap    X        :call NetrwLocalExecute(expand(""))"
+
+    nnoremap    r        :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwRefresh(1,NetrwBrowseChgDir(1,'./',0))
+    if !hasmapto('NetrwHideEdit')
+      nmap    NetrwHideEdit
+    endif
+    nnoremap   NetrwHideEdit               :call NetrwHideEdit(1)
+    if !hasmapto('NetrwRefresh')
+      nmap    NetrwRefresh
+    endif
+    nnoremap   NetrwRefresh                :call NetrwRefresh(1,NetrwBrowseChgDir(1,(exists("w:netrw_liststyle") && exists("w:netrw_treetop") && w:netrw_liststyle == 3)? w:netrw_treetop : './',0))
+    if s:didstarstar || !mapcheck("","n")
+      nnoremap    :Nexplore
+    endif
+    if s:didstarstar || !mapcheck("","n")
+      nnoremap      :Pexplore
+    endif
+    if !hasmapto('NetrwTreeSqueeze')
+      nmap                          NetrwTreeSqueeze
+    endif
+    nnoremap   NetrwTreeSqueeze            :call TreeSqueezeDir(1)
+    let mapsafecurdir = escape(b:netrw_curdir, s:netrw_map_escape)
+    if g:netrw_mousemaps == 1
+      nmap                                     NetrwLeftmouse
+      nmap                                   NetrwCLeftmouse
+      nmap                                   NetrwMiddlemouse
+      nmap                                   NetrwSLeftmouse
+      nmap                                    NetrwSLeftdrag
+      nmap                        <2-leftmouse>           Netrw2Leftmouse
+      imap                                     ILeftmouse
+      imap                                   IMiddlemouse
+      nno                 NetrwLeftmouse    :exec "norm! \leftmouse>"call NetrwLeftmouse(1)
+      nno                 NetrwCLeftmouse   :exec "norm! \leftmouse>"call NetrwCLeftmouse(1)
+      nno                 NetrwMiddlemouse  :exec "norm! \leftmouse>"call NetrwPrevWinOpen(1)
+      nno                 NetrwSLeftmouse   :exec "norm! \leftmouse>"call NetrwSLeftmouse(1)
+      nno                 NetrwSLeftdrag    :exec "norm! \leftmouse>"call NetrwSLeftdrag(1)
+      nmap                Netrw2Leftmouse   -
+        exe 'nnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
+      exe 'vnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
+    endif
+    exe 'nnoremap           :call NetrwLocalRm("'.mapsafecurdir.'")'
+    exe 'nnoremap    D           :call NetrwLocalRm("'.mapsafecurdir.'")'
+    exe 'nnoremap    R           :call NetrwLocalRename("'.mapsafecurdir.'")'
+    exe 'nnoremap    d           :call NetrwMakeDir("")'
+    exe 'vnoremap           :call NetrwLocalRm("'.mapsafecurdir.'")'
+    exe 'vnoremap    D           :call NetrwLocalRm("'.mapsafecurdir.'")'
+    exe 'vnoremap    R           :call NetrwLocalRename("'.mapsafecurdir.'")'
+    nnoremap                         :he netrw-quickhelp
+
+    " support user-specified maps
+    call netrw#UserMaps(1)
 
-      if newdirname == ""
-        let @@= ykeep
-        return
+  else
+    " remote normal-mode maps {{{3
+    call s:RemotePathAnalysis(b:netrw_curdir)
+    nnoremap   NetrwHide_a                 :call NetrwHide(0)
+    nnoremap   NetrwBrowseUpDir            :call NetrwBrowseUpDir(0)
+    nnoremap   NetrwOpenFile               :call NetrwOpenFile(0)
+    nnoremap   NetrwBadd_cb                :call NetrwBadd(0,0)
+    nnoremap   NetrwBadd_cB                :call NetrwBadd(0,1)
+    nnoremap   NetrwLcd                    :call NetrwLcd(b:netrw_curdir)
+    nnoremap   NetrwSetChgwin              :call NetrwSetChgwin()
+    nnoremap   NetrwRefresh                :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
+    nnoremap   NetrwLocalBrowseCheck       :call NetrwBrowse(0,NetrwBrowseChgDir(0,NetrwGetWord(),1))
+    nnoremap   NetrwServerEdit             :call NetrwServerEdit(2,NetrwGetWord())
+    nnoremap   NetrwBookHistHandler_gb     :call NetrwBookHistHandler(1,b:netrw_curdir)
+    " ---------------------------------------------------------------------
+    nnoremap    gd       :call NetrwForceChgDir(0,NetrwGetWord())
+    nnoremap    gf       :call NetrwForceFile(0,NetrwGetWord())
+    nnoremap    gh       :call NetrwHidden(0)
+    nnoremap    gp       :call NetrwChgPerm(0,b:netrw_curdir)
+    nnoremap    I        :call NetrwBannerCtrl(1)
+    nnoremap    i        :call NetrwListStyle(0)
+    nnoremap    ma       :call NetrwMarkFileArgList(0,0)
+    nnoremap    mA       :call NetrwMarkFileArgList(0,1)
+    nnoremap    mb       :call NetrwBookHistHandler(0,b:netrw_curdir)
+    nnoremap    mB       :call NetrwBookHistHandler(6,b:netrw_curdir)
+    nnoremap    mc       :call NetrwMarkFileCopy(0)
+    nnoremap    md       :call NetrwMarkFileDiff(0)
+    nnoremap    me       :call NetrwMarkFileEdit(0)
+    nnoremap    mf       :call NetrwMarkFile(0,NetrwGetWord())
+    nnoremap    mF       :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+    nnoremap    mg       :call NetrwMarkFileGrep(0)
+    nnoremap    mh       :call NetrwMarkHideSfx(0)
+    nnoremap    mm       :call NetrwMarkFileMove(0)
+    " nnoremap    mp       :call NetrwMarkFilePrint(0)
+    nnoremap    mr       :call NetrwMarkFileRegexp(0)
+    nnoremap    ms       :call NetrwMarkFileSource(0)
+    nnoremap    mT       :call NetrwMarkFileTag(0)
+    nnoremap    mt       :call NetrwMarkFileTgt(0)
+    nnoremap    mu       :call NetrwUnMarkFile(0)
+    nnoremap    mv       :call NetrwMarkFileVimCmd(0)
+    nnoremap    mx       :call NetrwMarkFileExe(0,0)
+    nnoremap    mX       :call NetrwMarkFileExe(0,1)
+    nnoremap    mz       :call NetrwMarkFileCompress(0)
+    nnoremap    O        :call NetrwObtain(0)
+    nnoremap    o        :call NetrwSplit(0)
+    nnoremap    p        :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
+    nnoremap    P        :call NetrwPrevWinOpen(0)
+    nnoremap    qb       :call NetrwBookHistHandler(2,b:netrw_curdir)
+    nnoremap    qf       :call NetrwFileInfo(0,NetrwGetWord())
+    nnoremap    qF       :call NetrwMarkFileQFEL(0,getqflist())
+    nnoremap    qL       :call NetrwMarkFileQFEL(0,getloclist(v:count))
+    nnoremap    r        :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwBrowse(0,NetrwBrowseChgDir(0,'./',0))
+    nnoremap    s        :call NetrwSortStyle(0)
+    nnoremap    S        :call NetSortSequence(0)
+    nnoremap    Tb       :call NetrwSetTgt(0,'b',v:count1)
+    nnoremap    t        :call NetrwSplit(1)
+    nnoremap    Th       :call NetrwSetTgt(0,'h',v:count)
+    nnoremap    u        :call NetrwBookHistHandler(4,b:netrw_curdir)
+    nnoremap    U        :call NetrwBookHistHandler(5,b:netrw_curdir)
+    nnoremap    v        :call NetrwSplit(2)
+    nnoremap    x        :call netrw#BrowseX(NetrwBrowseChgDir(0,NetrwGetWord(),1),1)
+    nmap                gx       x
+    if !hasmapto('NetrwHideEdit')
+      nmap   NetrwHideEdit
+    endif
+    nnoremap   NetrwHideEdit       :call NetrwHideEdit(0)
+    if !hasmapto('NetrwRefresh')
+      nmap   NetrwRefresh
+    endif
+    if !hasmapto('NetrwTreeSqueeze')
+      nmap          NetrwTreeSqueeze
+    endif
+    nnoremap   NetrwTreeSqueeze    :call TreeSqueezeDir(0)
+
+    let mapsafepath     = escape(s:path, s:netrw_map_escape)
+    let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape)
+
+    nnoremap   NetrwRefresh        :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
+    if g:netrw_mousemaps == 1
+      nmap             NetrwLeftmouse
+      nno                 NetrwLeftmouse    :exec "norm! \leftmouse>"call NetrwLeftmouse(0)
+      nmap           NetrwCLeftmouse
+      nno                 NetrwCLeftmouse   :exec "norm! \leftmouse>"call NetrwCLeftmouse(0)
+      nmap           NetrwSLeftmouse
+      nno                 NetrwSLeftmouse   :exec "norm! \leftmouse>"call NetrwSLeftmouse(0)
+      nmap            NetrwSLeftdrag
+      nno                 NetrwSLeftdrag    :exec "norm! \leftmouse>"call NetrwSLeftdrag(0)
+      nmap                   NetrwMiddlemouse
+      nno                            NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(0)
+      nmap  <2-leftmouse>         Netrw2Leftmouse
+      nmap                Netrw2Leftmouse   -
+        imap             ILeftmouse
+      imap           IMiddlemouse
+      imap           ISLeftmouse
+      exe 'nnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+      exe 'vnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+    endif
+    exe 'nnoremap           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+    exe 'nnoremap    d           :call NetrwMakeDir("'.mapsafeusermach.'")'
+    exe 'nnoremap    D           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+    exe 'nnoremap    R           :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
+    exe 'vnoremap           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+    exe 'vnoremap    D           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
+    exe 'vnoremap    R           :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
+    nnoremap                         :he netrw-quickhelp
+
+    " support user-specified maps
+    call netrw#UserMaps(0)
+  endif " }}}3
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwCommands: set up commands                              {{{2
+"  If -buffer, the command is only available from within netrw buffers
+"  Otherwise, the command is available from any window, so long as netrw
+"  has been used at least once in the session.
+fun! s:NetrwCommands(islocal)
+  "  call Dfunc("s:NetrwCommands(islocal=".a:islocal.")")
+
+  com! -nargs=* -complete=file -bang    NetrwMB call s:NetrwBookmark(0,)
+  com! -nargs=*                         NetrwC  call s:NetrwSetChgwin()
+  com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"win#".winnr()." not a former netrw window",79)|endif
+  if a:islocal
+    com! -buffer -nargs=+ -complete=file MF      call s:NetrwMarkFiles(1,)
+  else
+    com! -buffer -nargs=+ -complete=file MF      call s:NetrwMarkFiles(0,)
+  endif
+  com! -buffer -nargs=? -complete=file  MT      call s:NetrwMarkTarget()
+
+  "  call Dret("s:NetrwCommands")
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwMarkFiles: apply s:NetrwMarkFile() to named file(s) {{{2
+"                   glob()ing only works with local files
+fun! s:NetrwMarkFiles(islocal,...)
+  "  call Dfunc("s:NetrwMarkFiles(islocal=".a:islocal."...) a:0=".a:0)
+  let curdir = s:NetrwGetCurdir(a:islocal)
+  let i      = 1
+  while i <= a:0
+    if a:islocal
+      if v:version > 704 || (v:version == 704 && has("patch656"))
+        let mffiles= glob(a:{i},0,1,1)
+      else
+        let mffiles= glob(a:{i},0,1)
       endif
+    else
+      let mffiles= [a:{i}]
+    endif
+    "   call Decho("mffiles".string(mffiles),'~'.expand(""))
+    for mffile in mffiles
+      "    call Decho("mffile<".mffile.">",'~'.expand(""))
+      call s:NetrwMarkFile(a:islocal,mffile)
+    endfor
+    let i= i + 1
+  endwhile
+  "  call Dret("s:NetrwMarkFiles")
+endfun
 
-      if a:usrhost == ""
+" ---------------------------------------------------------------------
+" s:NetrwMarkTarget: implements :MT (mark target) {{{2
+fun! s:NetrwMarkTarget(...)
+  if a:0 == 0 || (a:0 == 1 && a:1 == "")
+    let curdir = s:NetrwGetCurdir(1)
+    let tgt    = b:netrw_curdir
+  else
+    let curdir = s:NetrwGetCurdir((a:1 =~ '^\a\{3,}://')? 0 : 1)
+    let tgt    = a:1
+  endif
+  let s:netrwmftgt         = tgt
+  let s:netrwmftgt_islocal = tgt !~ '^\a\{3,}://'
+  let curislocal           = b:netrw_curdir !~ '^\a\{3,}://'
+  let svpos                = winsaveview()
+  call s:NetrwRefresh(curislocal,s:NetrwBrowseChgDir(curislocal,'./',0))
+  call winrestview(svpos)
+endfun
 
-        " Local mkdir:
-        " sanity checks
-        let fullnewdir= b:netrw_curdir.'/'.newdirname
-        if isdirectory(s:NetrwFile(fullnewdir))
-          if !exists("g:netrw_quiet")
-            NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24)
-          endif
-          let @@= ykeep
-          return
-        endif
-        if s:FileReadable(fullnewdir)
-          if !exists("g:netrw_quiet")
-            NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25)
-          endif
-          let @@= ykeep
-          return
-        endif
+" ---------------------------------------------------------------------
+" s:NetrwMarkFile: (invoked by mf) This function is used to both {{{2
+"                  mark and unmark files.  If a markfile list exists,
+"                  then the rename and delete functions will use it instead
+"                  of whatever may happen to be under the cursor at that
+"                  moment.  When the mouse and gui are available,
+"                  shift-leftmouse may also be used to mark files.
+"
+"  Creates two lists
+"    s:netrwmarkfilelist    -- holds complete paths to all marked files
+"    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
+"
+"  Creates a marked file match string
+"    s:netrwmarfilemtch_#   -- used with 2match to display marked files
+"
+"  Creates a buffer version of islocal
+"    b:netrw_islocal
+fun! s:NetrwMarkFile(islocal,fname)
+  "  call Dfunc("s:NetrwMarkFile(islocal=".a:islocal." fname<".a:fname.">)")
+  "  call Decho("bufnr(%)=".bufnr("%").": ".bufname("%"),'~'.expand(""))
 
-        " requested new local directory is neither a pre-existing file or
-        " directory, so make it!
-        if exists("*mkdir")
-          if has("unix")
-            call mkdir(fullnewdir,"p",xor(0777, system("umask")))
+  " sanity check
+  if empty(a:fname)
+    "   call Dret("s:NetrwMarkFile : empty fname")
+    return
+  endif
+  let curdir = s:NetrwGetCurdir(a:islocal)
+
+  let ykeep   = @@
+  let curbufnr= bufnr("%")
+  let leader= '\%(^\|\s\)\zs'
+  if a:fname =~ '\a$'
+    let trailer = '\>[@=|\/\*]\=\ze\%(  \|\t\|$\)'
+  else
+    let trailer = '[@=|\/\*]\=\ze\%(  \|\t\|$\)'
+  endif
+
+  if exists("s:netrwmarkfilelist_".curbufnr)
+    " markfile list pre-exists
+    "   call Decho("case s:netrwmarkfilelist_".curbufnr." already exists",'~'.expand(""))
+    "   call Decho("starting s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+    "   call Decho("starting s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
+    let b:netrw_islocal= a:islocal
+
+    if index(s:netrwmarkfilelist_{curbufnr},a:fname) == -1
+      " append filename to buffer's markfilelist
+      "    call Decho("append filename<".a:fname."> to local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+      call add(s:netrwmarkfilelist_{curbufnr},a:fname)
+      let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(a:fname,g:netrw_markfileesc).trailer
+
+    else
+      " remove filename from buffer's markfilelist
+      "    call Decho("remove filename<".a:fname."> from local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+      call filter(s:netrwmarkfilelist_{curbufnr},'v:val != a:fname')
+      if s:netrwmarkfilelist_{curbufnr} == []
+        " local markfilelist is empty; remove it entirely
+        "     call Decho("markfile list now empty",'~'.expand(""))
+        call s:NetrwUnmarkList(curbufnr,curdir)
+      else
+        " rebuild match list to display markings correctly
+        "     call Decho("rebuild s:netrwmarkfilemtch_".curbufnr,'~'.expand(""))
+        let s:netrwmarkfilemtch_{curbufnr}= ""
+        let first                         = 1
+        for fname in s:netrwmarkfilelist_{curbufnr}
+          if first
+            let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.leader.escape(fname,g:netrw_markfileesc).trailer
           else
-            call mkdir(fullnewdir,"p")
-          endif
-        else
-          let netrw_origdir= s:NetrwGetcwd(1)
-          if s:NetrwLcd(b:netrw_curdir)
-            return
-          endif
-          call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(newdirname,1))
-          if v:shell_error != 0
-            let @@= ykeep
-            call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
-            return
+            let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(fname,g:netrw_markfileesc).trailer
           endif
-          if !g:netrw_keepdir
-            if s:NetrwLcd(netrw_origdir)
-              return
-            endif
-          endif
-        endif
+          let first= 0
+        endfor
+        "     call Decho("ending s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+      endif
+    endif
 
-        if v:shell_error == 0
-          " refresh listing
-          let svpos= winsaveview()
-          call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0))
-          call winrestview(svpos)
-        elseif !exists("g:netrw_quiet")
-          call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",26)
-        endif
+  else
+    " initialize new markfilelist
+    "   call Decho("case: initialize new markfilelist",'~'.expand(""))
 
-      elseif !exists("b:netrw_method") || b:netrw_method == 4
-        " Remote mkdir:  using ssh
-        let mkdircmd  = s:MakeSshCmd(g:netrw_mkdir_cmd)
-        let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname
-        call s:NetrwExe("sil! !".mkdircmd." ".s:ShellEscape(newdirname,1))
-        if v:shell_error == 0
-          " refresh listing
-          let svpos= winsaveview()
-          NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
-          NetrwKeepj call winrestview(svpos)
-        elseif !exists("g:netrw_quiet")
-          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27)
-        endif
+    "   call Decho("add fname<".a:fname."> to new markfilelist_".curbufnr,'~'.expand(""))
+    let s:netrwmarkfilelist_{curbufnr}= []
+    call add(s:netrwmarkfilelist_{curbufnr},substitute(a:fname,'[|@]$','',''))
+    "   call Decho("ending s:netrwmarkfilelist_{curbufnr}<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
 
-      elseif b:netrw_method == 2
-        " Remote mkdir:  using ftp+.netrc
-        let svpos= winsaveview()
-        if exists("b:netrw_fname")
-          let remotepath= b:netrw_fname
-        else
-          let remotepath= ""
-        endif
-        call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
-        NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
-        NetrwKeepj call winrestview(svpos)
+    " build initial markfile matching pattern
+    if a:fname =~ '/$'
+      let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc)
+    else
+      let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc).trailer
+    endif
+    "   call Decho("ending s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
+  endif
 
-      elseif b:netrw_method == 3
-        " Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc)
-        let svpos= winsaveview()
-        if exists("b:netrw_fname")
-          let remotepath= b:netrw_fname
-        else
-          let remotepath= ""
-        endif
-        call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
-        NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0))
-        NetrwKeepj call winrestview(svpos)
+  " handle global markfilelist
+  if exists("s:netrwmarkfilelist")
+    let dname= s:ComposePath(b:netrw_curdir,a:fname)
+    if index(s:netrwmarkfilelist,dname) == -1
+      " append new filename to global markfilelist
+      call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
+    "    call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
+    else
+      " remove new filename from global markfilelist
+      "    call Decho("remove new filename from global s:markfilelist",'~'.expand(""))
+      "    call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand(""))
+      call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"')
+      "    call Decho("..ending s:netrwmarkfilelist  <".string(s:netrwmarkfilelist).">",'~'.expand(""))
+      if s:netrwmarkfilelist == []
+        "     call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand(""))
+        unlet s:netrwmarkfilelist
       endif
+    endif
+  else
+    " initialize new global-directory markfilelist
+    let s:netrwmarkfilelist= []
+    call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
+    "   call Decho("init s:netrwmarkfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
+  endif
 
-      let @@= ykeep
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:TreeSqueezeDir: allows a shift-cr (gvim only) to squeeze the current tree-listing directory {{{2
-    fun! s:TreeSqueezeDir(islocal)
-      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-        " its a tree-listing style
-        let curdepth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e')
-        let stopline = (exists("w:netrw_bannercnt")? (w:netrw_bannercnt + 1) : 1)
-        let depth    = strchars(substitute(curdepth,' ','','g'))
-        let srch     = -1
-        if depth >= 2
-          NetrwKeepj norm! 0
-          let curdepthm1= substitute(curdepth,'^'.s:treedepthstring,'','')
-          let srch      = search('^'.curdepthm1.'\%('.s:treedepthstring.'\)\@!','bW',stopline)
-        elseif depth == 1
-          NetrwKeepj norm! 0
-          let treedepthchr= substitute(s:treedepthstring,' ','','')
-          let srch        = search('^[^'.treedepthchr.']','bW',stopline)
-        endif
-        if srch > 0
-          call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,s:NetrwGetWord(),1))
-          exe srch
-        endif
+  " set up 2match'ing to netrwmarkfilemtch_# list
+  if has("syntax") && exists("g:syntax_on") && g:syntax_on
+    if exists("s:netrwmarkfilemtch_{curbufnr}") && s:netrwmarkfilemtch_{curbufnr} != ""
+      " "   call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/",'~'.expand(""))
+      if exists("g:did_drchip_netrwlist_syntax")
+        exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/"
       endif
-    endfun
+    else
+      " "   call Decho("2match none",'~'.expand(""))
+      2match none
+    endif
+  endif
+  let @@= ykeep
+  "  call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand(""))
+  "  call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist").">  (buf#".curbufnr."list)")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMaps: {{{2
-    fun! s:NetrwMaps(islocal)
-
-      " mouse  maps: {{{3
-      if g:netrw_mousemaps && g:netrw_retmap
-        "   call Decho("set up Rexplore 2-leftmouse",'~'.expand(""))
-        if !hasmapto("NetrwReturn")
-          if maparg("<2-leftmouse>","n") == "" || maparg("<2-leftmouse>","n") =~ '^-$'
-            nmap   <2-leftmouse>       NetrwReturn
-          elseif maparg("","n") == ""
-            nmap          NetrwReturn
-          endif
-        endif
-        nno  NetrwReturn       :Rexplore
-      endif
-
-      " generate default  maps {{{3
-      if !hasmapto('NetrwHide')              |nmap    a       NetrwHide_a|endif
-      if !hasmapto('NetrwBrowseUpDir')       |nmap    -       NetrwBrowseUpDir|endif
-      if !hasmapto('NetrwOpenFile')          |nmap    %       NetrwOpenFile|endif
-      if !hasmapto('NetrwBadd_cb')           |nmap    cb      NetrwBadd_cb|endif
-      if !hasmapto('NetrwBadd_cB')           |nmap    cB      NetrwBadd_cB|endif
-      if !hasmapto('NetrwLcd')               |nmap    cd      NetrwLcd|endif
-      if !hasmapto('NetrwSetChgwin')         |nmap    C       NetrwSetChgwin|endif
-      if !hasmapto('NetrwRefresh')           |nmap       NetrwRefresh|endif
-      if !hasmapto('NetrwLocalBrowseCheck')  |nmap        NetrwLocalBrowseCheck|endif
-      if !hasmapto('NetrwServerEdit')        |nmap       NetrwServerEdit|endif
-      if !hasmapto('NetrwMakeDir')           |nmap    d       NetrwMakeDir|endif
-      if !hasmapto('NetrwBookHistHandler_gb')|nmap    gb      NetrwBookHistHandler_gb|endif
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileArgList: ma: move the marked file list to the argument list (tomflist=0) {{{2
+"                         mA: move the argument list to marked file list     (tomflist=1)
+"                            Uses the global marked file list
+fun! s:NetrwMarkFileArgList(islocal,tomflist)
+  let svpos    = winsaveview()
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
 
-      if a:islocal
-        " local normal-mode maps {{{3
-        nnoremap   NetrwHide_a                 :call NetrwHide(1)
-        nnoremap   NetrwBrowseUpDir            :call NetrwBrowseUpDir(1)
-        nnoremap   NetrwOpenFile               :call NetrwOpenFile(1)
-        nnoremap   NetrwBadd_cb                :call NetrwBadd(1,0)
-        nnoremap   NetrwBadd_cB                :call NetrwBadd(1,1)
-        nnoremap   NetrwLcd                    :call NetrwLcd(b:netrw_curdir)
-        nnoremap   NetrwSetChgwin              :call NetrwSetChgwin()
-        nnoremap   NetrwLocalBrowseCheck       :call netrw#LocalBrowseCheck(NetrwBrowseChgDir(1,NetrwGetWord(),1))
-        nnoremap   NetrwServerEdit             :call NetrwServerEdit(3,NetrwGetWord())
-        nnoremap   NetrwMakeDir                :call NetrwMakeDir("")
-        nnoremap   NetrwBookHistHandler_gb     :call NetrwBookHistHandler(1,b:netrw_curdir)
-        " ---------------------------------------------------------------------
-        nnoremap    gd       :call NetrwForceChgDir(1,NetrwGetWord())
-        nnoremap    gf       :call NetrwForceFile(1,NetrwGetWord())
-        nnoremap    gh       :call NetrwHidden(1)
-        nnoremap    gn       :call netrw#SetTreetop(0,NetrwGetWord())
-        nnoremap    gp       :call NetrwChgPerm(1,b:netrw_curdir)
-        nnoremap    I        :call NetrwBannerCtrl(1)
-        nnoremap    i        :call NetrwListStyle(1)
-        nnoremap    ma       :call NetrwMarkFileArgList(1,0)
-        nnoremap    mA       :call NetrwMarkFileArgList(1,1)
-        nnoremap    mb       :call NetrwBookHistHandler(0,b:netrw_curdir)
-        nnoremap    mB       :call NetrwBookHistHandler(6,b:netrw_curdir)
-        nnoremap    mc       :call NetrwMarkFileCopy(1)
-        nnoremap    md       :call NetrwMarkFileDiff(1)
-        nnoremap    me       :call NetrwMarkFileEdit(1)
-        nnoremap    mf       :call NetrwMarkFile(1,NetrwGetWord())
-        nnoremap    mF       :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
-        nnoremap    mg       :call NetrwMarkFileGrep(1)
-        nnoremap    mh       :call NetrwMarkHideSfx(1)
-        nnoremap    mm       :call NetrwMarkFileMove(1)
-        " nnoremap    mp       :call NetrwMarkFilePrint(1)
-        nnoremap    mr       :call NetrwMarkFileRegexp(1)
-        nnoremap    ms       :call NetrwMarkFileSource(1)
-        nnoremap    mT       :call NetrwMarkFileTag(1)
-        nnoremap    mt       :call NetrwMarkFileTgt(1)
-        nnoremap    mu       :call NetrwUnMarkFile(1)
-        nnoremap    mv       :call NetrwMarkFileVimCmd(1)
-        nnoremap    mx       :call NetrwMarkFileExe(1,0)
-        nnoremap    mX       :call NetrwMarkFileExe(1,1)
-        nnoremap    mz       :call NetrwMarkFileCompress(1)
-        nnoremap    O        :call NetrwObtain(1)
-        nnoremap    o        :call NetrwSplit(3)
-        nnoremap    p        :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
-        nnoremap    P        :call NetrwPrevWinOpen(1)
-        nnoremap    qb       :call NetrwBookHistHandler(2,b:netrw_curdir)
-        nnoremap    qf       :call NetrwFileInfo(1,NetrwGetWord())
-        nnoremap    qF       :call NetrwMarkFileQFEL(1,getqflist())
-        nnoremap    qL       :call NetrwMarkFileQFEL(1,getloclist(v:count))
-        nnoremap    s        :call NetrwSortStyle(1)
-        nnoremap    S        :call NetSortSequence(1)
-        nnoremap    Tb       :call NetrwSetTgt(1,'b',v:count1)
-        nnoremap    t        :call NetrwSplit(4)
-        nnoremap    Th       :call NetrwSetTgt(1,'h',v:count)
-        nnoremap    u        :call NetrwBookHistHandler(4,expand("%"))
-        nnoremap    U        :call NetrwBookHistHandler(5,expand("%"))
-        nnoremap    v        :call NetrwSplit(5)
-        nnoremap    x        :call netrw#BrowseX(NetrwBrowseChgDir(1,NetrwGetWord(),1,0),0)"
-        nnoremap    X        :call NetrwLocalExecute(expand(""))"
-
-        nnoremap    r        :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwRefresh(1,NetrwBrowseChgDir(1,'./',0))
-        if !hasmapto('NetrwHideEdit')
-          nmap    NetrwHideEdit
-        endif
-        nnoremap   NetrwHideEdit               :call NetrwHideEdit(1)
-        if !hasmapto('NetrwRefresh')
-          nmap    NetrwRefresh
-        endif
-        nnoremap   NetrwRefresh                :call NetrwRefresh(1,NetrwBrowseChgDir(1,(exists("w:netrw_liststyle") && exists("w:netrw_treetop") && w:netrw_liststyle == 3)? w:netrw_treetop : './',0))
-        if s:didstarstar || !mapcheck("","n")
-          nnoremap    :Nexplore
-        endif
-        if s:didstarstar || !mapcheck("","n")
-          nnoremap      :Pexplore
-        endif
-        if !hasmapto('NetrwTreeSqueeze')
-          nmap                          NetrwTreeSqueeze
-        endif
-        nnoremap   NetrwTreeSqueeze            :call TreeSqueezeDir(1)
-        let mapsafecurdir = escape(b:netrw_curdir, s:netrw_map_escape)
-        if g:netrw_mousemaps == 1
-          nmap                                     NetrwLeftmouse
-          nmap                                   NetrwCLeftmouse
-          nmap                                   NetrwMiddlemouse
-          nmap                                   NetrwSLeftmouse
-          nmap                                    NetrwSLeftdrag
-          nmap                        <2-leftmouse>           Netrw2Leftmouse
-          imap                                     ILeftmouse
-          imap                                   IMiddlemouse
-          nno                 NetrwLeftmouse    :exec "norm! \leftmouse>"call NetrwLeftmouse(1)
-          nno                 NetrwCLeftmouse   :exec "norm! \leftmouse>"call NetrwCLeftmouse(1)
-          nno                 NetrwMiddlemouse  :exec "norm! \leftmouse>"call NetrwPrevWinOpen(1)
-          nno                 NetrwSLeftmouse   :exec "norm! \leftmouse>"call NetrwSLeftmouse(1)
-          nno                 NetrwSLeftdrag    :exec "norm! \leftmouse>"call NetrwSLeftdrag(1)
-          nmap                Netrw2Leftmouse   -
-          exe 'nnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
-          exe 'vnoremap     :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")'
-        endif
-        exe 'nnoremap           :call NetrwLocalRm("'.mapsafecurdir.'")'
-        exe 'nnoremap    D           :call NetrwLocalRm("'.mapsafecurdir.'")'
-        exe 'nnoremap    R           :call NetrwLocalRename("'.mapsafecurdir.'")'
-        exe 'nnoremap    d           :call NetrwMakeDir("")'
-        exe 'vnoremap           :call NetrwLocalRm("'.mapsafecurdir.'")'
-        exe 'vnoremap    D           :call NetrwLocalRm("'.mapsafecurdir.'")'
-        exe 'vnoremap    R           :call NetrwLocalRename("'.mapsafecurdir.'")'
-        nnoremap                         :he netrw-quickhelp
-
-        " support user-specified maps
-        call netrw#UserMaps(1)
+  if a:tomflist
+    " mA: move argument list to marked file list
+    while argc()
+      let fname= argv(0)
+      exe "argdel ".fnameescape(fname)
+      call s:NetrwMarkFile(a:islocal,fname)
+    endwhile
 
-      else
-        " remote normal-mode maps {{{3
-        call s:RemotePathAnalysis(b:netrw_curdir)
-        nnoremap   NetrwHide_a                 :call NetrwHide(0)
-        nnoremap   NetrwBrowseUpDir            :call NetrwBrowseUpDir(0)
-        nnoremap   NetrwOpenFile               :call NetrwOpenFile(0)
-        nnoremap   NetrwBadd_cb                :call NetrwBadd(0,0)
-        nnoremap   NetrwBadd_cB                :call NetrwBadd(0,1)
-        nnoremap   NetrwLcd                    :call NetrwLcd(b:netrw_curdir)
-        nnoremap   NetrwSetChgwin              :call NetrwSetChgwin()
-        nnoremap   NetrwRefresh                :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
-        nnoremap   NetrwLocalBrowseCheck       :call NetrwBrowse(0,NetrwBrowseChgDir(0,NetrwGetWord(),1))
-        nnoremap   NetrwServerEdit             :call NetrwServerEdit(2,NetrwGetWord())
-        nnoremap   NetrwBookHistHandler_gb     :call NetrwBookHistHandler(1,b:netrw_curdir)
-        " ---------------------------------------------------------------------
-        nnoremap    gd       :call NetrwForceChgDir(0,NetrwGetWord())
-        nnoremap    gf       :call NetrwForceFile(0,NetrwGetWord())
-        nnoremap    gh       :call NetrwHidden(0)
-        nnoremap    gp       :call NetrwChgPerm(0,b:netrw_curdir)
-        nnoremap    I        :call NetrwBannerCtrl(1)
-        nnoremap    i        :call NetrwListStyle(0)
-        nnoremap    ma       :call NetrwMarkFileArgList(0,0)
-        nnoremap    mA       :call NetrwMarkFileArgList(0,1)
-        nnoremap    mb       :call NetrwBookHistHandler(0,b:netrw_curdir)
-        nnoremap    mB       :call NetrwBookHistHandler(6,b:netrw_curdir)
-        nnoremap    mc       :call NetrwMarkFileCopy(0)
-        nnoremap    md       :call NetrwMarkFileDiff(0)
-        nnoremap    me       :call NetrwMarkFileEdit(0)
-        nnoremap    mf       :call NetrwMarkFile(0,NetrwGetWord())
-        nnoremap    mF       :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
-        nnoremap    mg       :call NetrwMarkFileGrep(0)
-        nnoremap    mh       :call NetrwMarkHideSfx(0)
-        nnoremap    mm       :call NetrwMarkFileMove(0)
-        " nnoremap    mp       :call NetrwMarkFilePrint(0)
-        nnoremap    mr       :call NetrwMarkFileRegexp(0)
-        nnoremap    ms       :call NetrwMarkFileSource(0)
-        nnoremap    mT       :call NetrwMarkFileTag(0)
-        nnoremap    mt       :call NetrwMarkFileTgt(0)
-        nnoremap    mu       :call NetrwUnMarkFile(0)
-        nnoremap    mv       :call NetrwMarkFileVimCmd(0)
-        nnoremap    mx       :call NetrwMarkFileExe(0,0)
-        nnoremap    mX       :call NetrwMarkFileExe(0,1)
-        nnoremap    mz       :call NetrwMarkFileCompress(0)
-        nnoremap    O        :call NetrwObtain(0)
-        nnoremap    o        :call NetrwSplit(0)
-        nnoremap    p        :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1))
-        nnoremap    P        :call NetrwPrevWinOpen(0)
-        nnoremap    qb       :call NetrwBookHistHandler(2,b:netrw_curdir)
-        nnoremap    qf       :call NetrwFileInfo(0,NetrwGetWord())
-        nnoremap    qF       :call NetrwMarkFileQFEL(0,getqflist())
-        nnoremap    qL       :call NetrwMarkFileQFEL(0,getloclist(v:count))
-        nnoremap    r        :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwBrowse(0,NetrwBrowseChgDir(0,'./',0))
-        nnoremap    s        :call NetrwSortStyle(0)
-        nnoremap    S        :call NetSortSequence(0)
-        nnoremap    Tb       :call NetrwSetTgt(0,'b',v:count1)
-        nnoremap    t        :call NetrwSplit(1)
-        nnoremap    Th       :call NetrwSetTgt(0,'h',v:count)
-        nnoremap    u        :call NetrwBookHistHandler(4,b:netrw_curdir)
-        nnoremap    U        :call NetrwBookHistHandler(5,b:netrw_curdir)
-        nnoremap    v        :call NetrwSplit(2)
-        nnoremap    x        :call netrw#BrowseX(NetrwBrowseChgDir(0,NetrwGetWord(),1),1)
-        nmap                gx       x
-        if !hasmapto('NetrwHideEdit')
-          nmap   NetrwHideEdit
-        endif
-        nnoremap   NetrwHideEdit       :call NetrwHideEdit(0)
-        if !hasmapto('NetrwRefresh')
-          nmap   NetrwRefresh
-        endif
-        if !hasmapto('NetrwTreeSqueeze')
-          nmap          NetrwTreeSqueeze
-        endif
-        nnoremap   NetrwTreeSqueeze    :call TreeSqueezeDir(0)
-
-        let mapsafepath     = escape(s:path, s:netrw_map_escape)
-        let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape)
-
-        nnoremap   NetrwRefresh        :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0))
-        if g:netrw_mousemaps == 1
-          nmap             NetrwLeftmouse
-          nno                 NetrwLeftmouse    :exec "norm! \leftmouse>"call NetrwLeftmouse(0)
-          nmap           NetrwCLeftmouse
-          nno                 NetrwCLeftmouse   :exec "norm! \leftmouse>"call NetrwCLeftmouse(0)
-          nmap           NetrwSLeftmouse
-          nno                 NetrwSLeftmouse   :exec "norm! \leftmouse>"call NetrwSLeftmouse(0)
-          nmap            NetrwSLeftdrag
-          nno                 NetrwSLeftdrag    :exec "norm! \leftmouse>"call NetrwSLeftdrag(0)
-          nmap                   NetrwMiddlemouse
-          nno                            NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(0)
-          nmap  <2-leftmouse>         Netrw2Leftmouse
-          nmap                Netrw2Leftmouse   -
-          imap             ILeftmouse
-          imap           IMiddlemouse
-          imap           ISLeftmouse
-          exe 'nnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-          exe 'vnoremap    :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-        endif
-        exe 'nnoremap           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-        exe 'nnoremap    d           :call NetrwMakeDir("'.mapsafeusermach.'")'
-        exe 'nnoremap    D           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-        exe 'nnoremap    R           :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
-        exe 'vnoremap           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-        exe 'vnoremap    D           :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")'
-        exe 'vnoremap    R           :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")'
-        nnoremap                         :he netrw-quickhelp
-
-        " support user-specified maps
-        call netrw#UserMaps(0)
-      endif " }}}3
-    endfun
+  else
+    " ma: move marked file list to argument list
+    if exists("s:netrwmarkfilelist")
 
-    " ---------------------------------------------------------------------
-    " s:NetrwCommands: set up commands                              {{{2
-    "  If -buffer, the command is only available from within netrw buffers
-    "  Otherwise, the command is available from any window, so long as netrw
-    "  has been used at least once in the session.
-    fun! s:NetrwCommands(islocal)
-      "  call Dfunc("s:NetrwCommands(islocal=".a:islocal.")")
-
-      com! -nargs=* -complete=file -bang    NetrwMB call s:NetrwBookmark(0,)
-      com! -nargs=*                         NetrwC  call s:NetrwSetChgwin()
-      com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"win#".winnr()." not a former netrw window",79)|endif
-      if a:islocal
-        com! -buffer -nargs=+ -complete=file MF      call s:NetrwMarkFiles(1,)
-      else
-        com! -buffer -nargs=+ -complete=file MF      call s:NetrwMarkFiles(0,)
-      endif
-      com! -buffer -nargs=? -complete=file  MT      call s:NetrwMarkTarget()
+      " for every filename in the marked list
+      for fname in s:netrwmarkfilelist
+        exe "argadd ".fnameescape(fname)
+      endfor      " for every file in the marked list
 
-      "  call Dret("s:NetrwCommands")
-    endfun
+      " unmark list and refresh
+      call s:NetrwUnmarkList(curbufnr,curdir)
+      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      NetrwKeepj call winrestview(svpos)
+    endif
+  endif
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFiles: apply s:NetrwMarkFile() to named file(s) {{{2
-    "                   glob()ing only works with local files
-    fun! s:NetrwMarkFiles(islocal,...)
-      "  call Dfunc("s:NetrwMarkFiles(islocal=".a:islocal."...) a:0=".a:0)
-      let curdir = s:NetrwGetCurdir(a:islocal)
-      let i      = 1
-      while i <= a:0
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileCompress: (invoked by mz) This function is used to {{{2
+"                          compress/decompress files using the programs
+"                          in g:netrw_compress and g:netrw_uncompress,
+"                          using g:netrw_compress_suffix to know which to
+"                          do.  By default:
+"                            g:netrw_compress        = "gzip"
+"                            g:netrw_decompress      = { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf", ".xz" : "unxz"}
+fun! s:NetrwMarkFileCompress(islocal)
+  let svpos    = winsaveview()
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
+
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    return
+  endif
+
+  if exists("s:netrwmarkfilelist_{curbufnr}") && exists("g:netrw_compress") && exists("g:netrw_decompress")
+
+    " for every filename in the marked list
+    for fname in s:netrwmarkfilelist_{curbufnr}
+      let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','')
+      if exists("g:netrw_decompress['".sfx."']")
+        " fname has a suffix indicating that its compressed; apply associated decompression routine
+        let exe= g:netrw_decompress[sfx]
+        let exe= netrw#WinPath(exe)
         if a:islocal
-          if v:version > 704 || (v:version == 704 && has("patch656"))
-            let mffiles= glob(a:{i},0,1,1)
-          else
-            let mffiles= glob(a:{i},0,1)
+          if g:netrw_keepdir
+            let fname= s:ShellEscape(s:ComposePath(curdir,fname))
+          endif
+          call system(exe." ".fname)
+          if v:shell_error
+            NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50)
           endif
         else
-          let mffiles= [a:{i}]
+          let fname= s:ShellEscape(b:netrw_curdir.fname,1)
+          NetrwKeepj call s:RemoteSystem(exe." ".fname)
         endif
-        "   call Decho("mffiles".string(mffiles),'~'.expand(""))
-        for mffile in mffiles
-          "    call Decho("mffile<".mffile.">",'~'.expand(""))
-          call s:NetrwMarkFile(a:islocal,mffile)
-        endfor
-        let i= i + 1
-      endwhile
-      "  call Dret("s:NetrwMarkFiles")
-    endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkTarget: implements :MT (mark target) {{{2
-    fun! s:NetrwMarkTarget(...)
-      if a:0 == 0 || (a:0 == 1 && a:1 == "")
-        let curdir = s:NetrwGetCurdir(1)
-        let tgt    = b:netrw_curdir
-      else
-        let curdir = s:NetrwGetCurdir((a:1 =~ '^\a\{3,}://')? 0 : 1)
-        let tgt    = a:1
-      endif
-      let s:netrwmftgt         = tgt
-      let s:netrwmftgt_islocal = tgt !~ '^\a\{3,}://'
-      let curislocal           = b:netrw_curdir !~ '^\a\{3,}://'
-      let svpos                = winsaveview()
-      call s:NetrwRefresh(curislocal,s:NetrwBrowseChgDir(curislocal,'./',0))
-      call winrestview(svpos)
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFile: (invoked by mf) This function is used to both {{{2
-    "                  mark and unmark files.  If a markfile list exists,
-    "                  then the rename and delete functions will use it instead
-    "                  of whatever may happen to be under the cursor at that
-    "                  moment.  When the mouse and gui are available,
-    "                  shift-leftmouse may also be used to mark files.
-    "
-    "  Creates two lists
-    "    s:netrwmarkfilelist    -- holds complete paths to all marked files
-    "    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
-    "
-    "  Creates a marked file match string
-    "    s:netrwmarfilemtch_#   -- used with 2match to display marked files
-    "
-    "  Creates a buffer version of islocal
-    "    b:netrw_islocal
-    fun! s:NetrwMarkFile(islocal,fname)
-      "  call Dfunc("s:NetrwMarkFile(islocal=".a:islocal." fname<".a:fname.">)")
-      "  call Decho("bufnr(%)=".bufnr("%").": ".bufname("%"),'~'.expand(""))
-
-      " sanity check
-      if empty(a:fname)
-        "   call Dret("s:NetrwMarkFile : empty fname")
-        return
       endif
-      let curdir = s:NetrwGetCurdir(a:islocal)
+      unlet sfx
 
-      let ykeep   = @@
-      let curbufnr= bufnr("%")
-      let leader= '\%(^\|\s\)\zs'
-      if a:fname =~ '\a$'
-        let trailer = '\>[@=|\/\*]\=\ze\%(  \|\t\|$\)'
+      if exists("exe")
+        unlet exe
+      elseif a:islocal
+        " fname not a compressed file, so compress it
+        call system(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,fname)))
+        if v:shell_error
+          call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_compress<".g:netrw_compress."> to something that works",104)
+        endif
       else
-        let trailer = '[@=|\/\*]\=\ze\%(  \|\t\|$\)'
+        " fname not a compressed file, so compress it
+        NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(fname))
       endif
+    endfor       " for every file in the marked list
 
-      if exists("s:netrwmarkfilelist_".curbufnr)
-        " markfile list pre-exists
-        "   call Decho("case s:netrwmarkfilelist_".curbufnr." already exists",'~'.expand(""))
-        "   call Decho("starting s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-        "   call Decho("starting s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
-        let b:netrw_islocal= a:islocal
+    call s:NetrwUnmarkList(curbufnr,curdir)
+    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    NetrwKeepj call winrestview(svpos)
+  endif
+endfun
 
-        if index(s:netrwmarkfilelist_{curbufnr},a:fname) == -1
-          " append filename to buffer's markfilelist
-          "    call Decho("append filename<".a:fname."> to local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-          call add(s:netrwmarkfilelist_{curbufnr},a:fname)
-          let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(a:fname,g:netrw_markfileesc).trailer
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileCopy: (invoked by mc) copy marked files to target {{{2
+"                      If no marked files, then set up directory as the
+"                      target.  Currently does not support copying entire
+"                      directories.  Uses the local-buffer marked file list.
+"                      Returns 1=success  (used by NetrwMarkFileMove())
+"                              0=failure
+fun! s:NetrwMarkFileCopy(islocal,...)
+  "  call Dfunc("s:NetrwMarkFileCopy(islocal=".a:islocal.") target<".(exists("s:netrwmftgt")? s:netrwmftgt : '---')."> a:0=".a:0)
+
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
+  if b:netrw_curdir !~ '/$'
+    if !exists("b:netrw_curdir")
+      let b:netrw_curdir= curdir
+    endif
+    let b:netrw_curdir= b:netrw_curdir."/"
+  endif
 
-        else
-          " remove filename from buffer's markfilelist
-          "    call Decho("remove filename<".a:fname."> from local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
-          call filter(s:netrwmarkfilelist_{curbufnr},'v:val != a:fname')
-          if s:netrwmarkfilelist_{curbufnr} == []
-            " local markfilelist is empty; remove it entirely
-            "     call Decho("markfile list now empty",'~'.expand(""))
-            call s:NetrwUnmarkList(curbufnr,curdir)
-          else
-            " rebuild match list to display markings correctly
-            "     call Decho("rebuild s:netrwmarkfilemtch_".curbufnr,'~'.expand(""))
-            let s:netrwmarkfilemtch_{curbufnr}= ""
-            let first                         = 1
-            for fname in s:netrwmarkfilelist_{curbufnr}
-              if first
-                let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.leader.escape(fname,g:netrw_markfileesc).trailer
-              else
-                let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(fname,g:netrw_markfileesc).trailer
-              endif
-              let first= 0
-            endfor
-            "     call Decho("ending s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    "   call Dret("s:NetrwMarkFileCopy")
+    return
+  endif
+  "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+
+  if !exists("s:netrwmftgt")
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67)
+    "   call Dret("s:NetrwMarkFileCopy 0")
+    return 0
+  endif
+  "  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+
+  if a:islocal &&  s:netrwmftgt_islocal
+    " Copy marked files, local directory to local directory
+    "   call Decho("copy from local to local",'~'.expand(""))
+    if !executable(g:netrw_localcopycmd)
+      call netrw#ErrorMsg(s:ERROR,"g:netrw_localcopycmd<".g:netrw_localcopycmd."> not executable on your system, aborting",91)
+      "    call Dfunc("s:NetrwMarkFileMove : g:netrw_localcopycmd<".g:netrw_localcopycmd."> n/a!")
+      return
+    endif
+
+    " copy marked files while within the same directory (ie. allow renaming)
+    if s:StripTrailingSlash(simplify(s:netrwmftgt)) == s:StripTrailingSlash(simplify(b:netrw_curdir))
+      if len(s:netrwmarkfilelist_{bufnr('%')}) == 1
+        " only one marked file
+        "     call Decho("case: only one marked file",'~'.expand(""))
+        let args    = s:ShellEscape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0])
+        let oldname = s:netrwmarkfilelist_{bufnr('%')}[0]
+      elseif a:0 == 1
+        "     call Decho("case: handling one input argument",'~'.expand(""))
+        " this happens when the next case was used to recursively call s:NetrwMarkFileCopy()
+        let args    = s:ShellEscape(b:netrw_curdir.a:1)
+        let oldname = a:1
+      else
+        " copy multiple marked files inside the same directory
+        "     call Decho("case: handling a multiple marked files",'~'.expand(""))
+        let s:recursive= 1
+        for oldname in s:netrwmarkfilelist_{bufnr("%")}
+          let ret= s:NetrwMarkFileCopy(a:islocal,oldname)
+          if ret == 0
+            break
           endif
-        endif
+        endfor
+        unlet s:recursive
+        call s:NetrwUnmarkList(curbufnr,curdir)
+        "     call Dret("s:NetrwMarkFileCopy ".ret)
+        return ret
+      endif
 
+      call inputsave()
+      let newname= input("Copy ".oldname." to : ",oldname,"file")
+      call inputrestore()
+      if newname == ""
+        "     call Dret("s:NetrwMarkFileCopy 0")
+        return 0
+      endif
+      let args= s:ShellEscape(oldname)
+      let tgt = s:ShellEscape(s:netrwmftgt.'/'.newname)
+    else
+      let args= join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"s:ShellEscape(b:netrw_curdir.\"/\".v:val)"))
+      let tgt = s:ShellEscape(s:netrwmftgt)
+    endif
+    if !g:netrw_cygwin && has("win32")
+      let args= substitute(args,'/','\\','g')
+      let tgt = substitute(tgt, '/','\\','g')
+    endif
+    if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif
+    if tgt  =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif
+    if args =~ '//'|let args= substitute(args,'//','/','g')|endif
+    if tgt  =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif
+    "   call Decho("args   <".args.">",'~'.expand(""))
+    "   call Decho("tgt    <".tgt.">",'~'.expand(""))
+    if isdirectory(s:NetrwFile(args))
+      "    call Decho("args<".args."> is a directory",'~'.expand(""))
+      let copycmd= g:netrw_localcopydircmd
+      "    call Decho("using copydircmd<".copycmd.">",'~'.expand(""))
+      if !g:netrw_cygwin && has("win32")
+        " window's xcopy doesn't copy a directory to a target properly.  Instead, it copies a directory's
+        " contents to a target.  One must append the source directory name to the target to get xcopy to
+        " do the right thing.
+        let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','')
+        "     call Decho("modified tgt for xcopy",'~'.expand(""))
+      endif
+    else
+      let copycmd= g:netrw_localcopycmd
+    endif
+    if g:netrw_localcopycmd =~ '\s'
+      let copycmd     = substitute(copycmd,'\s.*$','','')
+      let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','')
+      let copycmd     = netrw#WinPath(copycmd).copycmdargs
+    else
+      let copycmd = netrw#WinPath(copycmd)
+    endif
+    "   call Decho("args   <".args.">",'~'.expand(""))
+    "   call Decho("tgt    <".tgt.">",'~'.expand(""))
+    "   call Decho("copycmd<".copycmd.">",'~'.expand(""))
+    "   call Decho("system(".copycmd." '".args."' '".tgt."')",'~'.expand(""))
+    call system(copycmd.g:netrw_localcopycmdopt." '".args."' '".tgt."'")
+    if v:shell_error != 0
+      if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir
+        call netrw#ErrorMsg(s:ERROR,"copy failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",101)
       else
-        " initialize new markfilelist
-        "   call Decho("case: initialize new markfilelist",'~'.expand(""))
+        call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80)
+      endif
+      "    call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".s:ShellEscape(s:netrwmftgt))
+      return 0
+    endif
 
-        "   call Decho("add fname<".a:fname."> to new markfilelist_".curbufnr,'~'.expand(""))
-        let s:netrwmarkfilelist_{curbufnr}= []
-        call add(s:netrwmarkfilelist_{curbufnr},substitute(a:fname,'[|@]$','',''))
-        "   call Decho("ending s:netrwmarkfilelist_{curbufnr}<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand(""))
+  elseif  a:islocal && !s:netrwmftgt_islocal
+    " Copy marked files, local directory to remote directory
+    "   call Decho("copy from local to remote",'~'.expand(""))
+    NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
 
-        " build initial markfile matching pattern
-        if a:fname =~ '/$'
-          let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc)
-        else
-          let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc).trailer
-        endif
-        "   call Decho("ending s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand(""))
-      endif
+  elseif !a:islocal &&  s:netrwmftgt_islocal
+    " Copy marked files, remote directory to local directory
+    "   call Decho("copy from remote to local",'~'.expand(""))
+    NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
 
-      " handle global markfilelist
-      if exists("s:netrwmarkfilelist")
-        let dname= s:ComposePath(b:netrw_curdir,a:fname)
-        if index(s:netrwmarkfilelist,dname) == -1
-          " append new filename to global markfilelist
-          call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
-          "    call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
-        else
-          " remove new filename from global markfilelist
-          "    call Decho("remove new filename from global s:markfilelist",'~'.expand(""))
-          "    call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand(""))
-          call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"')
-          "    call Decho("..ending s:netrwmarkfilelist  <".string(s:netrwmarkfilelist).">",'~'.expand(""))
-          if s:netrwmarkfilelist == []
-            "     call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand(""))
-            unlet s:netrwmarkfilelist
-          endif
+  elseif !a:islocal && !s:netrwmftgt_islocal
+    " Copy marked files, remote directory to remote directory
+    "   call Decho("copy from remote to remote",'~'.expand(""))
+    let curdir = getcwd()
+    let tmpdir = s:GetTempfile("")
+    if tmpdir !~ '/'
+      let tmpdir= curdir."/".tmpdir
+    endif
+    if exists("*mkdir")
+      call mkdir(tmpdir)
+    else
+      call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(tmpdir,1))
+      if v:shell_error != 0
+        call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
+        "     call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.s:ShellEscape(tmpdir,1) )
+        return
+      endif
+    endif
+    if isdirectory(s:NetrwFile(tmpdir))
+      if s:NetrwLcd(tmpdir)
+        "     call Dret("s:NetrwMarkFileCopy : lcd failure")
+        return
+      endif
+      NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir)
+      let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")')
+      NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt)
+      if getcwd() == tmpdir
+        for fname in s:netrwmarkfilelist_{bufnr('%')}
+          NetrwKeepj call s:NetrwDelete(fname)
+        endfor
+        if s:NetrwLcd(curdir)
+          "      call Dret("s:NetrwMarkFileCopy : lcd failure")
+          return
+        endif
+        if delete(tmpdir,"d")
+          call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103)
         endif
       else
-        " initialize new global-directory markfilelist
-        let s:netrwmarkfilelist= []
-        call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname))
-        "   call Decho("init s:netrwmarkfilelist<".string(s:netrwmarkfilelist).">",'~'.expand(""))
-      endif
-
-      " set up 2match'ing to netrwmarkfilemtch_# list
-      if has("syntax") && exists("g:syntax_on") && g:syntax_on
-        if exists("s:netrwmarkfilemtch_{curbufnr}") && s:netrwmarkfilemtch_{curbufnr} != ""
-          " "   call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/",'~'.expand(""))
-          if exists("g:did_drchip_netrwlist_syntax")
-            exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/"
-          endif
-        else
-          " "   call Decho("2match none",'~'.expand(""))
-          2match none
+        if s:NetrwLcd(curdir)
+          "      call Dret("s:NetrwMarkFileCopy : lcd failure")
+          return
         endif
       endif
-      let @@= ykeep
-      "  call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand(""))
-      "  call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist").">  (buf#".curbufnr."list)")
-    endfun
+    endif
+  endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileArgList: ma: move the marked file list to the argument list (tomflist=0) {{{2
-    "                         mA: move the argument list to marked file list     (tomflist=1)
-    "                            Uses the global marked file list
-    fun! s:NetrwMarkFileArgList(islocal,tomflist)
-      let svpos    = winsaveview()
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
+  " -------
+  " cleanup
+  " -------
+  "  call Decho("cleanup",'~'.expand(""))
+  " remove markings from local buffer
+  call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
+  "  call Decho(" g:netrw_fastbrowse  =".g:netrw_fastbrowse,'~'.expand(""))
+  "  call Decho(" s:netrwmftgt        =".s:netrwmftgt,'~'.expand(""))
+  "  call Decho(" s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand(""))
+  "  call Decho(" curdir              =".curdir,'~'.expand(""))
+  "  call Decho(" a:islocal           =".a:islocal,'~'.expand(""))
+  "  call Decho(" curbufnr            =".curbufnr,'~'.expand(""))
+  if exists("s:recursive")
+  "   call Decho(" s:recursive         =".s:recursive,'~'.expand(""))
+  else
+    "   call Decho(" s:recursive         =n/a",'~'.expand(""))
+  endif
+  " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium)
+  if g:netrw_fastbrowse <= 1
+    NetrwKeepj call s:LocalBrowseRefresh()
+  else
+    " refresh local and targets for fast browsing
+    if !exists("s:recursive")
+      " remove markings from local buffer
+      "    call Decho(" remove markings from local buffer",'~'.expand(""))
+      NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir)
+    endif
 
-      if a:tomflist
-        " mA: move argument list to marked file list
-        while argc()
-          let fname= argv(0)
-          exe "argdel ".fnameescape(fname)
-          call s:NetrwMarkFile(a:islocal,fname)
-        endwhile
+    " refresh buffers
+    if s:netrwmftgt_islocal
+      "    call Decho(" refresh s:netrwmftgt=".s:netrwmftgt,'~'.expand(""))
+      NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
+    endif
+    if a:islocal && s:netrwmftgt != curdir
+      "    call Decho(" refresh curdir=".curdir,'~'.expand(""))
+      NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir)
+    endif
+  endif
 
-      else
-        " ma: move marked file list to argument list
-        if exists("s:netrwmarkfilelist")
+  "  call Dret("s:NetrwMarkFileCopy 1")
+  return 1
+endfun
 
-          " for every filename in the marked list
-          for fname in s:netrwmarkfilelist
-            exe "argadd ".fnameescape(fname)
-          endfor      " for every file in the marked list
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileDiff: (invoked by md) This function is used to {{{2
+"                      invoke vim's diff mode on the marked files.
+"                      Either two or three files can be so handled.
+"                      Uses the global marked file list.
+fun! s:NetrwMarkFileDiff(islocal)
+  "  call Dfunc("s:NetrwMarkFileDiff(islocal=".a:islocal.") b:netrw_curdir<".b:netrw_curdir.">")
+  let curbufnr= bufnr("%")
 
-          " unmark list and refresh
-          call s:NetrwUnmarkList(curbufnr,curdir)
-          NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-          NetrwKeepj call winrestview(svpos)
-        endif
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    "   call Dret("s:NetrwMarkFileDiff")
+    return
+  endif
+  let curdir= s:NetrwGetCurdir(a:islocal)
+  "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+
+  if exists("s:netrwmarkfilelist_{".curbufnr."}")
+    let cnt    = 0
+    for fname in s:netrwmarkfilelist
+      let cnt= cnt + 1
+      if cnt == 1
+        "     call Decho("diffthis: fname<".fname.">",'~'.expand(""))
+        exe "NetrwKeepj e ".fnameescape(fname)
+        diffthis
+      elseif cnt == 2 || cnt == 3
+        below vsplit
+        "     call Decho("diffthis: ".fname,'~'.expand(""))
+        exe "NetrwKeepj e ".fnameescape(fname)
+        diffthis
+      else
+        break
       endif
-    endfun
+    endfor
+    call s:NetrwUnmarkList(curbufnr,curdir)
+  endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileCompress: (invoked by mz) This function is used to {{{2
-    "                          compress/decompress files using the programs
-    "                          in g:netrw_compress and g:netrw_uncompress,
-    "                          using g:netrw_compress_suffix to know which to
-    "                          do.  By default:
-    "                            g:netrw_compress        = "gzip"
-    "                            g:netrw_decompress      = { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf", ".xz" : "unxz"}
-    fun! s:NetrwMarkFileCompress(islocal)
-      let svpos    = winsaveview()
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
+  "  call Dret("s:NetrwMarkFileDiff")
+endfun
 
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        return
-      endif
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileEdit: (invoked by me) put marked files on arg list and start editing them {{{2
+"                       Uses global markfilelist
+fun! s:NetrwMarkFileEdit(islocal)
+  "  call Dfunc("s:NetrwMarkFileEdit(islocal=".a:islocal.")")
 
-      if exists("s:netrwmarkfilelist_{curbufnr}") && exists("g:netrw_compress") && exists("g:netrw_decompress")
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
 
-        " for every filename in the marked list
-        for fname in s:netrwmarkfilelist_{curbufnr}
-          let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','')
-          if exists("g:netrw_decompress['".sfx."']")
-            " fname has a suffix indicating that its compressed; apply associated decompression routine
-            let exe= g:netrw_decompress[sfx]
-            let exe= netrw#WinPath(exe)
-            if a:islocal
-              if g:netrw_keepdir
-                let fname= s:ShellEscape(s:ComposePath(curdir,fname))
-              endif
-              call system(exe." ".fname)
-              if v:shell_error
-                NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50)
-              endif
-            else
-              let fname= s:ShellEscape(b:netrw_curdir.fname,1)
-              NetrwKeepj call s:RemoteSystem(exe." ".fname)
-            endif
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    "   call Dret("s:NetrwMarkFileEdit")
+    return
+  endif
+  "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
 
-          endif
-          unlet sfx
-
-          if exists("exe")
-            unlet exe
-          elseif a:islocal
-            " fname not a compressed file, so compress it
-            call system(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,fname)))
-            if v:shell_error
-              call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_compress<".g:netrw_compress."> to something that works",104)
-            endif
-          else
-            " fname not a compressed file, so compress it
-            NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(fname))
-          endif
-        endfor       " for every file in the marked list
+  if exists("s:netrwmarkfilelist_{curbufnr}")
+    call s:SetRexDir(a:islocal,curdir)
+    let flist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
+    " unmark markedfile list
+    "   call s:NetrwUnmarkList(curbufnr,curdir)
+    call s:NetrwUnmarkAll()
+    "   call Decho("exe sil args ".flist,'~'.expand(""))
+    exe "sil args ".flist
+  endif
+  echo "(use :bn, :bp to navigate files; :Rex to return)"
 
-        call s:NetrwUnmarkList(curbufnr,curdir)
-        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-        NetrwKeepj call winrestview(svpos)
-      endif
-    endfun
+  "  call Dret("s:NetrwMarkFileEdit")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileCopy: (invoked by mc) copy marked files to target {{{2
-    "                      If no marked files, then set up directory as the
-    "                      target.  Currently does not support copying entire
-    "                      directories.  Uses the local-buffer marked file list.
-    "                      Returns 1=success  (used by NetrwMarkFileMove())
-    "                              0=failure
-    fun! s:NetrwMarkFileCopy(islocal,...)
-      "  call Dfunc("s:NetrwMarkFileCopy(islocal=".a:islocal.") target<".(exists("s:netrwmftgt")? s:netrwmftgt : '---')."> a:0=".a:0)
-
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
-      if b:netrw_curdir !~ '/$'
-        if !exists("b:netrw_curdir")
-          let b:netrw_curdir= curdir
-        endif
-        let b:netrw_curdir= b:netrw_curdir."/"
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileQFEL: convert a quickfix-error or location list into a marked file list {{{2
+fun! s:NetrwMarkFileQFEL(islocal,qfel)
+  "  call Dfunc("s:NetrwMarkFileQFEL(islocal=".a:islocal.",qfel)")
+  call s:NetrwUnmarkAll()
+  let curbufnr= bufnr("%")
+
+  if !empty(a:qfel)
+    for entry in a:qfel
+      let bufnmbr= entry["bufnr"]
+      "    call Decho("bufname(".bufnmbr.")<".bufname(bufnmbr)."> line#".entry["lnum"]." text=".entry["text"],'~'.expand(""))
+      if !exists("s:netrwmarkfilelist_{curbufnr}")
+        "     call Decho("case: no marked file list",'~'.expand(""))
+        call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
+      elseif index(s:netrwmarkfilelist_{curbufnr},bufname(bufnmbr)) == -1
+        " s:NetrwMarkFile will remove duplicate entries from the marked file list.
+        " So, this test lets two or more hits on the same pattern to be ignored.
+        "     call Decho("case: ".bufname(bufnmbr)." not currently in marked file list",'~'.expand(""))
+        call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
+      else
+        "     call Decho("case: ".bufname(bufnmbr)." already in marked file list",'~'.expand(""))
       endif
+    endfor
+    echo "(use me to edit marked files)"
+  else
+    call netrw#ErrorMsg(s:WARNING,"can't convert quickfix error list; its empty!",92)
+  endif
 
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        "   call Dret("s:NetrwMarkFileCopy")
-        return
-      endif
-      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+  "  call Dret("s:NetrwMarkFileQFEL")
+endfun
 
-      if !exists("s:netrwmftgt")
-        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67)
-        "   call Dret("s:NetrwMarkFileCopy 0")
-        return 0
-      endif
-      "  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2
+"                     mx enbloc=0: Uses the local marked-file list, applies command to each file individually
+"                     mX enbloc=1: Uses the global marked-file list, applies command to entire list
+fun! s:NetrwMarkFileExe(islocal,enbloc)
+  let svpos    = winsaveview()
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
 
-      if a:islocal &&  s:netrwmftgt_islocal
-        " Copy marked files, local directory to local directory
-        "   call Decho("copy from local to local",'~'.expand(""))
-        if !executable(g:netrw_localcopycmd)
-          call netrw#ErrorMsg(s:ERROR,"g:netrw_localcopycmd<".g:netrw_localcopycmd."> not executable on your system, aborting",91)
-          "    call Dfunc("s:NetrwMarkFileMove : g:netrw_localcopycmd<".g:netrw_localcopycmd."> n/a!")
-          return
-        endif
+  if a:enbloc == 0
+    " individually apply command to files, one at a time
+    " sanity check
+    if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+      NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+      return
+    endif
 
-        " copy marked files while within the same directory (ie. allow renaming)
-        if s:StripTrailingSlash(simplify(s:netrwmftgt)) == s:StripTrailingSlash(simplify(b:netrw_curdir))
-          if len(s:netrwmarkfilelist_{bufnr('%')}) == 1
-            " only one marked file
-            "     call Decho("case: only one marked file",'~'.expand(""))
-            let args    = s:ShellEscape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0])
-            let oldname = s:netrwmarkfilelist_{bufnr('%')}[0]
-          elseif a:0 == 1
-            "     call Decho("case: handling one input argument",'~'.expand(""))
-            " this happens when the next case was used to recursively call s:NetrwMarkFileCopy()
-            let args    = s:ShellEscape(b:netrw_curdir.a:1)
-            let oldname = a:1
-          else
-            " copy multiple marked files inside the same directory
-            "     call Decho("case: handling a multiple marked files",'~'.expand(""))
-            let s:recursive= 1
-            for oldname in s:netrwmarkfilelist_{bufnr("%")}
-              let ret= s:NetrwMarkFileCopy(a:islocal,oldname)
-              if ret == 0
-                break
-              endif
-            endfor
-            unlet s:recursive
-            call s:NetrwUnmarkList(curbufnr,curdir)
-            "     call Dret("s:NetrwMarkFileCopy ".ret)
-            return ret
-          endif
+    if exists("s:netrwmarkfilelist_{curbufnr}")
+      " get the command
+      call inputsave()
+      let cmd= input("Enter command: ","","file")
+      call inputrestore()
+      if cmd == ""
+        return
+      endif
 
-          call inputsave()
-          let newname= input("Copy ".oldname." to : ",oldname,"file")
-          call inputrestore()
-          if newname == ""
-            "     call Dret("s:NetrwMarkFileCopy 0")
-            return 0
+      " apply command to marked files, individually.  Substitute: filename -> %
+      " If no %, then append a space and the filename to the command
+      for fname in s:netrwmarkfilelist_{curbufnr}
+        if a:islocal
+          if g:netrw_keepdir
+            let fname= s:ShellEscape(netrw#WinPath(s:ComposePath(curdir,fname)))
           endif
-          let args= s:ShellEscape(oldname)
-          let tgt = s:ShellEscape(s:netrwmftgt.'/'.newname)
         else
-          let args= join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"s:ShellEscape(b:netrw_curdir.\"/\".v:val)"))
-          let tgt = s:ShellEscape(s:netrwmftgt)
-        endif
-        if !g:netrw_cygwin && has("win32")
-          let args= substitute(args,'/','\\','g')
-          let tgt = substitute(tgt, '/','\\','g')
+          let fname= s:ShellEscape(netrw#WinPath(b:netrw_curdir.fname))
         endif
-        if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif
-        if tgt  =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif
-        if args =~ '//'|let args= substitute(args,'//','/','g')|endif
-        if tgt  =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif
-        "   call Decho("args   <".args.">",'~'.expand(""))
-        "   call Decho("tgt    <".tgt.">",'~'.expand(""))
-        if isdirectory(s:NetrwFile(args))
-          "    call Decho("args<".args."> is a directory",'~'.expand(""))
-          let copycmd= g:netrw_localcopydircmd
-          "    call Decho("using copydircmd<".copycmd.">",'~'.expand(""))
-          if !g:netrw_cygwin && has("win32")
-            " window's xcopy doesn't copy a directory to a target properly.  Instead, it copies a directory's
-            " contents to a target.  One must append the source directory name to the target to get xcopy to
-            " do the right thing.
-            let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','')
-            "     call Decho("modified tgt for xcopy",'~'.expand(""))
-          endif
+        if cmd =~ '%'
+          let xcmd= substitute(cmd,'%',fname,'g')
         else
-          let copycmd= g:netrw_localcopycmd
+          let xcmd= cmd.' '.fname
         endif
-        if g:netrw_localcopycmd =~ '\s'
-          let copycmd     = substitute(copycmd,'\s.*$','','')
-          let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','')
-          let copycmd     = netrw#WinPath(copycmd).copycmdargs
+        if a:islocal
+          let ret= system(xcmd)
         else
-          let copycmd = netrw#WinPath(copycmd)
-        endif
-        "   call Decho("args   <".args.">",'~'.expand(""))
-        "   call Decho("tgt    <".tgt.">",'~'.expand(""))
-        "   call Decho("copycmd<".copycmd.">",'~'.expand(""))
-        "   call Decho("system(".copycmd." '".args."' '".tgt."')",'~'.expand(""))
-        call system(copycmd.g:netrw_localcopycmdopt." '".args."' '".tgt."'")
-        if v:shell_error != 0
-          if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir
-            call netrw#ErrorMsg(s:ERROR,"copy failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",101)
-          else
-            call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80)
-          endif
-          "    call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".s:ShellEscape(s:netrwmftgt))
-          return 0
-        endif
-
-      elseif  a:islocal && !s:netrwmftgt_islocal
-        " Copy marked files, local directory to remote directory
-        "   call Decho("copy from local to remote",'~'.expand(""))
-        NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
-
-      elseif !a:islocal &&  s:netrwmftgt_islocal
-        " Copy marked files, remote directory to local directory
-        "   call Decho("copy from remote to local",'~'.expand(""))
-        NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
-
-      elseif !a:islocal && !s:netrwmftgt_islocal
-        " Copy marked files, remote directory to remote directory
-        "   call Decho("copy from remote to remote",'~'.expand(""))
-        let curdir = getcwd()
-        let tmpdir = s:GetTempfile("")
-        if tmpdir !~ '/'
-          let tmpdir= curdir."/".tmpdir
+          let ret= s:RemoteSystem(xcmd)
         endif
-        if exists("*mkdir")
-          call mkdir(tmpdir)
+        if v:shell_error < 0
+          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
+          break
         else
-          call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(tmpdir,1))
-          if v:shell_error != 0
-            call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
-            "     call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.s:ShellEscape(tmpdir,1) )
-            return
-          endif
-        endif
-        if isdirectory(s:NetrwFile(tmpdir))
-          if s:NetrwLcd(tmpdir)
-            "     call Dret("s:NetrwMarkFileCopy : lcd failure")
-            return
-          endif
-          NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir)
-          let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")')
-          NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt)
-          if getcwd() == tmpdir
-            for fname in s:netrwmarkfilelist_{bufnr('%')}
-              NetrwKeepj call s:NetrwDelete(fname)
-            endfor
-            if s:NetrwLcd(curdir)
-              "      call Dret("s:NetrwMarkFileCopy : lcd failure")
-              return
-            endif
-            if delete(tmpdir,"d")
-              call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103)
-            endif
+          if ret !=# ''
+            echo "\n"
+            " skip trailing new line
+            echo ret[0:-2]
           else
-            if s:NetrwLcd(curdir)
-              "      call Dret("s:NetrwMarkFileCopy : lcd failure")
-              return
-            endif
+            echo ret
           endif
         endif
-      endif
-
-      " -------
-      " cleanup
-      " -------
-      "  call Decho("cleanup",'~'.expand(""))
-      " remove markings from local buffer
-      call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
-      "  call Decho(" g:netrw_fastbrowse  =".g:netrw_fastbrowse,'~'.expand(""))
-      "  call Decho(" s:netrwmftgt        =".s:netrwmftgt,'~'.expand(""))
-      "  call Decho(" s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand(""))
-      "  call Decho(" curdir              =".curdir,'~'.expand(""))
-      "  call Decho(" a:islocal           =".a:islocal,'~'.expand(""))
-      "  call Decho(" curbufnr            =".curbufnr,'~'.expand(""))
-      if exists("s:recursive")
-        "   call Decho(" s:recursive         =".s:recursive,'~'.expand(""))
-      else
-        "   call Decho(" s:recursive         =n/a",'~'.expand(""))
-      endif
-      " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium)
-      if g:netrw_fastbrowse <= 1
-        NetrwKeepj call s:LocalBrowseRefresh()
-      else
-        " refresh local and targets for fast browsing
-        if !exists("s:recursive")
-          " remove markings from local buffer
-          "    call Decho(" remove markings from local buffer",'~'.expand(""))
-          NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir)
-        endif
+      endfor
 
-        " refresh buffers
-        if s:netrwmftgt_islocal
-          "    call Decho(" refresh s:netrwmftgt=".s:netrwmftgt,'~'.expand(""))
-          NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
-        endif
-        if a:islocal && s:netrwmftgt != curdir
-          "    call Decho(" refresh curdir=".curdir,'~'.expand(""))
-          NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir)
-        endif
-      endif
+      " unmark marked file list
+      call s:NetrwUnmarkList(curbufnr,curdir)
 
-      "  call Dret("s:NetrwMarkFileCopy 1")
-      return 1
-    endfun
+      " refresh the listing
+      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      NetrwKeepj call winrestview(svpos)
+    else
+      NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+    endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileDiff: (invoked by md) This function is used to {{{2
-    "                      invoke vim's diff mode on the marked files.
-    "                      Either two or three files can be so handled.
-    "                      Uses the global marked file list.
-    fun! s:NetrwMarkFileDiff(islocal)
-      "  call Dfunc("s:NetrwMarkFileDiff(islocal=".a:islocal.") b:netrw_curdir<".b:netrw_curdir.">")
-      let curbufnr= bufnr("%")
-
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        "   call Dret("s:NetrwMarkFileDiff")
-        return
-      endif
-      let curdir= s:NetrwGetCurdir(a:islocal)
-      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+  else " apply command to global list of files, en bloc
 
-      if exists("s:netrwmarkfilelist_{".curbufnr."}")
-        let cnt    = 0
-        for fname in s:netrwmarkfilelist
-          let cnt= cnt + 1
-          if cnt == 1
-            "     call Decho("diffthis: fname<".fname.">",'~'.expand(""))
-            exe "NetrwKeepj e ".fnameescape(fname)
-            diffthis
-          elseif cnt == 2 || cnt == 3
-            below vsplit
-            "     call Decho("diffthis: ".fname,'~'.expand(""))
-            exe "NetrwKeepj e ".fnameescape(fname)
-            diffthis
-          else
-            break
-          endif
-        endfor
-        call s:NetrwUnmarkList(curbufnr,curdir)
+    call inputsave()
+    let cmd= input("Enter command: ","","file")
+    call inputrestore()
+    if cmd == ""
+      return
+    endif
+    if cmd =~ '%'
+      let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' '),'g')
+    else
+      let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' ')
+    endif
+    if a:islocal
+      call system(cmd)
+      if v:shell_error < 0
+        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
       endif
+    else
+      let ret= s:RemoteSystem(cmd)
+    endif
+    call s:NetrwUnmarkAll()
 
-      "  call Dret("s:NetrwMarkFileDiff")
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileEdit: (invoked by me) put marked files on arg list and start editing them {{{2
-    "                       Uses global markfilelist
-    fun! s:NetrwMarkFileEdit(islocal)
-      "  call Dfunc("s:NetrwMarkFileEdit(islocal=".a:islocal.")")
-
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
+    " refresh the listing
+    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    NetrwKeepj call winrestview(svpos)
 
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        "   call Dret("s:NetrwMarkFileEdit")
-        return
-      endif
-      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+  endif
+endfun
 
-      if exists("s:netrwmarkfilelist_{curbufnr}")
-        call s:SetRexDir(a:islocal,curdir)
-        let flist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
-        " unmark markedfile list
-        "   call s:NetrwUnmarkList(curbufnr,curdir)
-        call s:NetrwUnmarkAll()
-        "   call Decho("exe sil args ".flist,'~'.expand(""))
-        exe "sil args ".flist
-      endif
-      echo "(use :bn, :bp to navigate files; :Rex to return)"
+" ---------------------------------------------------------------------
+" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
+"                  as the marked file(s) (toggles suffix presence)
+"                  Uses the local marked file list.
+fun! s:NetrwMarkHideSfx(islocal)
+  let svpos    = winsaveview()
+  let curbufnr = bufnr("%")
 
-      "  call Dret("s:NetrwMarkFileEdit")
-    endfun
+  " s:netrwmarkfilelist_{curbufnr}: the List of marked files
+  if exists("s:netrwmarkfilelist_{curbufnr}")
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileQFEL: convert a quickfix-error or location list into a marked file list {{{2
-    fun! s:NetrwMarkFileQFEL(islocal,qfel)
-      "  call Dfunc("s:NetrwMarkFileQFEL(islocal=".a:islocal.",qfel)")
-      call s:NetrwUnmarkAll()
-      let curbufnr= bufnr("%")
-
-      if !empty(a:qfel)
-        for entry in a:qfel
-          let bufnmbr= entry["bufnr"]
-          "    call Decho("bufname(".bufnmbr.")<".bufname(bufnmbr)."> line#".entry["lnum"]." text=".entry["text"],'~'.expand(""))
-          if !exists("s:netrwmarkfilelist_{curbufnr}")
-            "     call Decho("case: no marked file list",'~'.expand(""))
-            call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
-          elseif index(s:netrwmarkfilelist_{curbufnr},bufname(bufnmbr)) == -1
-            " s:NetrwMarkFile will remove duplicate entries from the marked file list.
-            " So, this test lets two or more hits on the same pattern to be ignored.
-            "     call Decho("case: ".bufname(bufnmbr)." not currently in marked file list",'~'.expand(""))
-            call s:NetrwMarkFile(a:islocal,bufname(bufnmbr))
-          else
-            "     call Decho("case: ".bufname(bufnmbr)." already in marked file list",'~'.expand(""))
+    for fname in s:netrwmarkfilelist_{curbufnr}
+      " construct suffix pattern
+      if fname =~ '\.'
+        let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
+      else
+        let sfxpat= '^\%(\%(\.\)\@!.\)*$'
+      endif
+      " determine if its in the hiding list or not
+      let inhidelist= 0
+      if g:netrw_list_hide != ""
+        let itemnum = 0
+        let hidelist= split(g:netrw_list_hide,',')
+        for hidepat in hidelist
+          if sfxpat == hidepat
+            let inhidelist= 1
+            break
           endif
+          let itemnum= itemnum + 1
         endfor
-        echo "(use me to edit marked files)"
+      endif
+      if inhidelist
+        " remove sfxpat from list
+        call remove(hidelist,itemnum)
+        let g:netrw_list_hide= join(hidelist,",")
+      elseif g:netrw_list_hide != ""
+        " append sfxpat to non-empty list
+        let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
       else
-        call netrw#ErrorMsg(s:WARNING,"can't convert quickfix error list; its empty!",92)
+        " set hiding list to sfxpat
+        let g:netrw_list_hide= sfxpat
       endif
+    endfor
 
-      "  call Dret("s:NetrwMarkFileQFEL")
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2
-    "                     mx enbloc=0: Uses the local marked-file list, applies command to each file individually
-    "                     mX enbloc=1: Uses the global marked-file list, applies command to entire list
-    fun! s:NetrwMarkFileExe(islocal,enbloc)
-      let svpos    = winsaveview()
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
-
-      if a:enbloc == 0
-        " individually apply command to files, one at a time
-        " sanity check
-        if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-          NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-          return
-        endif
-
-        if exists("s:netrwmarkfilelist_{curbufnr}")
-          " get the command
-          call inputsave()
-          let cmd= input("Enter command: ","","file")
-          call inputrestore()
-          if cmd == ""
-            return
-          endif
-
-          " apply command to marked files, individually.  Substitute: filename -> %
-          " If no %, then append a space and the filename to the command
-          for fname in s:netrwmarkfilelist_{curbufnr}
-            if a:islocal
-              if g:netrw_keepdir
-                let fname= s:ShellEscape(netrw#WinPath(s:ComposePath(curdir,fname)))
-              endif
-            else
-              let fname= s:ShellEscape(netrw#WinPath(b:netrw_curdir.fname))
-            endif
-            if cmd =~ '%'
-              let xcmd= substitute(cmd,'%',fname,'g')
-            else
-              let xcmd= cmd.' '.fname
-            endif
-            if a:islocal
-              let ret= system(xcmd)
-            else
-              let ret= s:RemoteSystem(xcmd)
-            endif
-            if v:shell_error < 0
-              NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
-              break
-            else
-              if ret !=# ''
-                echo "\n"
-                " skip trailing new line
-                echo ret[0:-2]
-              else
-                echo ret
-              endif
-            endif
-          endfor
+    " refresh the listing
+    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    NetrwKeepj call winrestview(svpos)
+  else
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+  endif
+endfun
 
-          " unmark marked file list
-          call s:NetrwUnmarkList(curbufnr,curdir)
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2
+"                     Uses the local marked-file list.
+fun! s:NetrwMarkFileVimCmd(islocal)
+  let svpos    = winsaveview()
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
 
-          " refresh the listing
-          NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-          NetrwKeepj call winrestview(svpos)
-        else
-          NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-        endif
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    return
+  endif
 
-      else " apply command to global list of files, en bloc
+  if exists("s:netrwmarkfilelist_{curbufnr}")
+    " get the command
+    call inputsave()
+    let cmd= input("Enter vim command: ","","file")
+    call inputrestore()
+    if cmd == ""
+      return
+    endif
 
-        call inputsave()
-        let cmd= input("Enter command: ","","file")
-        call inputrestore()
-        if cmd == ""
-          return
-        endif
-        if cmd =~ '%'
-          let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' '),'g')
-        else
-          let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' ')
-        endif
-        if a:islocal
-          call system(cmd)
-          if v:shell_error < 0
-            NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
-          endif
-        else
-          let ret= s:RemoteSystem(cmd)
-        endif
-        call s:NetrwUnmarkAll()
+    " apply command to marked files.  Substitute: filename -> %
+    " If no %, then append a space and the filename to the command
+    for fname in s:netrwmarkfilelist_{curbufnr}
+      if a:islocal
+        1split
+        exe "sil! NetrwKeepj keepalt e ".fnameescape(fname)
+        exe cmd
+        exe "sil! keepalt wq!"
+      else
+        echo "sorry, \"mv\" not supported yet for remote files"
+      endif
+    endfor
 
-        " refresh the listing
-        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-        NetrwKeepj call winrestview(svpos)
+    " unmark marked file list
+    call s:NetrwUnmarkList(curbufnr,curdir)
 
-      endif
-    endfun
+    " refresh the listing
+    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    NetrwKeepj call winrestview(svpos)
+  else
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+  endif
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
-    "                  as the marked file(s) (toggles suffix presence)
-    "                  Uses the local marked file list.
-    fun! s:NetrwMarkHideSfx(islocal)
-      let svpos    = winsaveview()
-      let curbufnr = bufnr("%")
+" ---------------------------------------------------------------------
+" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
+"                  as the marked file(s) (toggles suffix presence)
+"                  Uses the local marked file list.
+fun! s:NetrwMarkHideSfx(islocal)
+  let svpos    = winsaveview()
+  let curbufnr = bufnr("%")
 
-      " s:netrwmarkfilelist_{curbufnr}: the List of marked files
-      if exists("s:netrwmarkfilelist_{curbufnr}")
+  " s:netrwmarkfilelist_{curbufnr}: the List of marked files
+  if exists("s:netrwmarkfilelist_{curbufnr}")
 
-        for fname in s:netrwmarkfilelist_{curbufnr}
-          " construct suffix pattern
-          if fname =~ '\.'
-            let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
-          else
-            let sfxpat= '^\%(\%(\.\)\@!.\)*$'
-          endif
-          " determine if its in the hiding list or not
-          let inhidelist= 0
-          if g:netrw_list_hide != ""
-            let itemnum = 0
-            let hidelist= split(g:netrw_list_hide,',')
-            for hidepat in hidelist
-              if sfxpat == hidepat
-                let inhidelist= 1
-                break
-              endif
-              let itemnum= itemnum + 1
-            endfor
-          endif
-          if inhidelist
-            " remove sfxpat from list
-            call remove(hidelist,itemnum)
-            let g:netrw_list_hide= join(hidelist,",")
-          elseif g:netrw_list_hide != ""
-            " append sfxpat to non-empty list
-            let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
-          else
-            " set hiding list to sfxpat
-            let g:netrw_list_hide= sfxpat
+    for fname in s:netrwmarkfilelist_{curbufnr}
+      " construct suffix pattern
+      if fname =~ '\.'
+        let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
+      else
+        let sfxpat= '^\%(\%(\.\)\@!.\)*$'
+      endif
+      " determine if its in the hiding list or not
+      let inhidelist= 0
+      if g:netrw_list_hide != ""
+        let itemnum = 0
+        let hidelist= split(g:netrw_list_hide,',')
+        for hidepat in hidelist
+          if sfxpat == hidepat
+            let inhidelist= 1
+            break
           endif
+          let itemnum= itemnum + 1
         endfor
-
-        " refresh the listing
-        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-        NetrwKeepj call winrestview(svpos)
+      endif
+      if inhidelist
+        " remove sfxpat from list
+        call remove(hidelist,itemnum)
+        let g:netrw_list_hide= join(hidelist,",")
+      elseif g:netrw_list_hide != ""
+        " append sfxpat to non-empty list
+        let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
       else
-        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+        " set hiding list to sfxpat
+        let g:netrw_list_hide= sfxpat
       endif
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2
-    "                     Uses the local marked-file list.
-    fun! s:NetrwMarkFileVimCmd(islocal)
-      let svpos    = winsaveview()
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
+    endfor
 
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        return
-      endif
+    " refresh the listing
+    NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    NetrwKeepj call winrestview(svpos)
+  else
+    NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+  endif
+endfun
 
-      if exists("s:netrwmarkfilelist_{curbufnr}")
-        " get the command
-        call inputsave()
-        let cmd= input("Enter vim command: ","","file")
-        call inputrestore()
-        if cmd == ""
-          return
-        endif
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileGrep: (invoked by mg) This function applies vimgrep to marked files {{{2
+"                     Uses the global markfilelist
+fun! s:NetrwMarkFileGrep(islocal)
+  "  call Dfunc("s:NetrwMarkFileGrep(islocal=".a:islocal.")")
+  let svpos    = winsaveview()
+  "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
+  let curbufnr = bufnr("%")
+  let curdir   = s:NetrwGetCurdir(a:islocal)
 
-        " apply command to marked files.  Substitute: filename -> %
-        " If no %, then append a space and the filename to the command
-        for fname in s:netrwmarkfilelist_{curbufnr}
-          if a:islocal
-            1split
-            exe "sil! NetrwKeepj keepalt e ".fnameescape(fname)
-            exe cmd
-            exe "sil! keepalt wq!"
-          else
-            echo "sorry, \"mv\" not supported yet for remote files"
-          endif
-        endfor
+  if exists("s:netrwmarkfilelist")
+    "   call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand(""))
+    let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
+    "   call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand(""))"
+    call s:NetrwUnmarkAll()
+  else
+    "   call Decho('no marked files, using "*"','~'.expand(""))
+    let netrwmarkfilelist= "*"
+  endif
+
+  " ask user for pattern
+  "  call Decho("ask user for search pattern",'~'.expand(""))
+  call inputsave()
+  let pat= input("Enter pattern: ","")
+  call inputrestore()
+  let patbang = ""
+  if pat =~ '^!'
+    let patbang = "!"
+    let pat     = strpart(pat,2)
+  endif
+  if pat =~ '^\i'
+    let pat    = escape(pat,'/')
+    let pat    = '/'.pat.'/'
+  else
+    let nonisi = pat[0]
+  endif
 
-        " unmark marked file list
-        call s:NetrwUnmarkList(curbufnr,curdir)
+  " use vimgrep for both local and remote
+  "  call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist,'~'.expand(""))
+  try
+    exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist
+  catch /^Vim\%((\a\+)\)\=:E480/
+    NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76)
+    "   call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">")
+    return
+  endtry
+  echo "(use :cn, :cp to navigate, :Rex to return)"
 
-        " refresh the listing
-        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-        NetrwKeepj call winrestview(svpos)
-      else
-        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-      endif
-    endfun
+  2match none
+  "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
+  NetrwKeepj call winrestview(svpos)
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix
-    "                  as the marked file(s) (toggles suffix presence)
-    "                  Uses the local marked file list.
-    fun! s:NetrwMarkHideSfx(islocal)
-      let svpos    = winsaveview()
-      let curbufnr = bufnr("%")
+  if exists("nonisi")
+    " original, user-supplied pattern did not begin with a character from isident
+    "   call Decho("looking for trailing nonisi<".nonisi."> followed by a j, gj, or jg",'~'.expand(""))
+    if pat =~# nonisi.'j$\|'.nonisi.'gj$\|'.nonisi.'jg$'
+      call s:NetrwMarkFileQFEL(a:islocal,getqflist())
+    endif
+  endif
 
-      " s:netrwmarkfilelist_{curbufnr}: the List of marked files
-      if exists("s:netrwmarkfilelist_{curbufnr}")
+  "  call Dret("s:NetrwMarkFileGrep")
+endfun
 
-        for fname in s:netrwmarkfilelist_{curbufnr}
-          " construct suffix pattern
-          if fname =~ '\.'
-            let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','')
-          else
-            let sfxpat= '^\%(\%(\.\)\@!.\)*$'
-          endif
-          " determine if its in the hiding list or not
-          let inhidelist= 0
-          if g:netrw_list_hide != ""
-            let itemnum = 0
-            let hidelist= split(g:netrw_list_hide,',')
-            for hidepat in hidelist
-              if sfxpat == hidepat
-                let inhidelist= 1
-                break
-              endif
-              let itemnum= itemnum + 1
-            endfor
-          endif
-          if inhidelist
-            " remove sfxpat from list
-            call remove(hidelist,itemnum)
-            let g:netrw_list_hide= join(hidelist,",")
-          elseif g:netrw_list_hide != ""
-            " append sfxpat to non-empty list
-            let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat
-          else
-            " set hiding list to sfxpat
-            let g:netrw_list_hide= sfxpat
-          endif
-        endfor
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileMove: (invoked by mm) execute arbitrary command on marked files, one at a time {{{2
+"                      uses the global marked file list
+"                      s:netrwmfloc= 0: target directory is remote
+"                                  = 1: target directory is local
+fun! s:NetrwMarkFileMove(islocal)
+  "  call Dfunc("s:NetrwMarkFileMove(islocal=".a:islocal.")")
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
 
-        " refresh the listing
-        NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-        NetrwKeepj call winrestview(svpos)
-      else
-        NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
-      endif
-    endfun
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    "   call Dret("s:NetrwMarkFileMove")
+    return
+  endif
+  "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileGrep: (invoked by mg) This function applies vimgrep to marked files {{{2
-    "                     Uses the global markfilelist
-    fun! s:NetrwMarkFileGrep(islocal)
-      "  call Dfunc("s:NetrwMarkFileGrep(islocal=".a:islocal.")")
-      let svpos    = winsaveview()
-      "  call Decho("saving posn to svpos<".string(svpos).">",'~'.expand(""))
-      let curbufnr = bufnr("%")
-      let curdir   = s:NetrwGetCurdir(a:islocal)
+  if !exists("s:netrwmftgt")
+    NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67)
+    "   call Dret("s:NetrwMarkFileCopy 0")
+    return 0
+  endif
+  "  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
 
-      if exists("s:netrwmarkfilelist")
-        "   call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand(""))
-        let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)"))
-        "   call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand(""))"
-        call s:NetrwUnmarkAll()
+  if      a:islocal &&  s:netrwmftgt_islocal
+    " move: local -> local
+    "   call Decho("move from local to local",'~'.expand(""))
+    "   call Decho("local to local move",'~'.expand(""))
+    if !executable(g:netrw_localmovecmd)
+      call netrw#ErrorMsg(s:ERROR,"g:netrw_localmovecmd<".g:netrw_localmovecmd."> not executable on your system, aborting",90)
+      "    call Dfunc("s:NetrwMarkFileMove : g:netrw_localmovecmd<".g:netrw_localmovecmd."> n/a!")
+      return
+    endif
+    let tgt = s:ShellEscape(s:netrwmftgt)
+    "   call Decho("tgt<".tgt.">",'~'.expand(""))
+    if !g:netrw_cygwin && has("win32")
+      let tgt= substitute(tgt, '/','\\','g')
+      "    call Decho("windows exception: tgt<".tgt.">",'~'.expand(""))
+      if g:netrw_localmovecmd =~ '\s'
+        let movecmd     = substitute(g:netrw_localmovecmd,'\s.*$','','')
+        let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','')
+        let movecmd     = netrw#WinPath(movecmd).movecmdargs
+      "     call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand(""))
       else
-        "   call Decho('no marked files, using "*"','~'.expand(""))
-        let netrwmarkfilelist= "*"
+        let movecmd = netrw#WinPath(g:netrw_localmovecmd)
+        "     call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand(""))
       endif
-
-      " ask user for pattern
-      "  call Decho("ask user for search pattern",'~'.expand(""))
-      call inputsave()
-      let pat= input("Enter pattern: ","")
-      call inputrestore()
-      let patbang = ""
-      if pat =~ '^!'
-        let patbang = "!"
-        let pat     = strpart(pat,2)
-      endif
-      if pat =~ '^\i'
-        let pat    = escape(pat,'/')
-        let pat    = '/'.pat.'/'
-      else
-        let nonisi = pat[0]
+    else
+      let movecmd = netrw#WinPath(g:netrw_localmovecmd)
+      "    call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand(""))
+    endif
+    for fname in s:netrwmarkfilelist_{bufnr("%")}
+      if g:netrw_keepdir
+        " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1
+        let fname= b:netrw_curdir."/".fname
+      endif
+      if !g:netrw_cygwin && has("win32")
+        let fname= substitute(fname,'/','\\','g')
+      endif
+      "    call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand(""))
+      let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt)
+      if v:shell_error != 0
+        if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir
+          call netrw#ErrorMsg(s:ERROR,"move failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",100)
+        else
+          call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localmovecmd<".g:netrw_localmovecmd.">; it doesn't work!",54)
+        endif
+        break
       endif
+    endfor
 
-      " use vimgrep for both local and remote
-      "  call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist,'~'.expand(""))
-      try
-        exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist
-      catch /^Vim\%((\a\+)\)\=:E480/
-        NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76)
-        "   call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">")
-        return
-      endtry
-      echo "(use :cn, :cp to navigate, :Rex to return)"
+  elseif  a:islocal && !s:netrwmftgt_islocal
+    " move: local -> remote
+    "   call Decho("move from local to remote",'~'.expand(""))
+    "   call Decho("copy",'~'.expand(""))
+    let mflist= s:netrwmarkfilelist_{bufnr("%")}
+    NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
+    "   call Decho("remove",'~'.expand(""))
+    for fname in mflist
+      let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
+      let ok        = s:NetrwLocalRmFile(b:netrw_curdir,barefname,1)
+    endfor
+    unlet mflist
+
+  elseif !a:islocal &&  s:netrwmftgt_islocal
+    " move: remote -> local
+    "   call Decho("move from remote to local",'~'.expand(""))
+    "   call Decho("copy",'~'.expand(""))
+    let mflist= s:netrwmarkfilelist_{bufnr("%")}
+    NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
+    "   call Decho("remove",'~'.expand(""))
+    for fname in mflist
+      let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
+      let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
+    endfor
+    unlet mflist
+
+  elseif !a:islocal && !s:netrwmftgt_islocal
+    " move: remote -> remote
+    "   call Decho("move from remote to remote",'~'.expand(""))
+    "   call Decho("copy",'~'.expand(""))
+    let mflist= s:netrwmarkfilelist_{bufnr("%")}
+    NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
+    "   call Decho("remove",'~'.expand(""))
+    for fname in mflist
+      let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
+      let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
+    endfor
+    unlet mflist
+  endif
 
-      2match none
-      "  call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))
-      NetrwKeepj call winrestview(svpos)
+  " -------
+  " cleanup
+  " -------
+  "  call Decho("cleanup",'~'.expand(""))
 
-      if exists("nonisi")
-        " original, user-supplied pattern did not begin with a character from isident
-        "   call Decho("looking for trailing nonisi<".nonisi."> followed by a j, gj, or jg",'~'.expand(""))
-        if pat =~# nonisi.'j$\|'.nonisi.'gj$\|'.nonisi.'jg$'
-          call s:NetrwMarkFileQFEL(a:islocal,getqflist())
-        endif
-      endif
+  " remove markings from local buffer
+  call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
 
-      "  call Dret("s:NetrwMarkFileGrep")
-    endfun
+  " refresh buffers
+  if !s:netrwmftgt_islocal
+    "   call Decho("refresh netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+    NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
+  endif
+  if a:islocal
+    "   call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">",'~'.expand(""))
+    NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir)
+  endif
+  if g:netrw_fastbrowse <= 1
+    "   call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh",'~'.expand(""))
+    NetrwKeepj call s:LocalBrowseRefresh()
+  endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileMove: (invoked by mm) execute arbitrary command on marked files, one at a time {{{2
-    "                      uses the global marked file list
-    "                      s:netrwmfloc= 0: target directory is remote
-    "                                  = 1: target directory is local
-    fun! s:NetrwMarkFileMove(islocal)
-      "  call Dfunc("s:NetrwMarkFileMove(islocal=".a:islocal.")")
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
+  "  call Dret("s:NetrwMarkFileMove")
+endfun
 
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        "   call Dret("s:NetrwMarkFileMove")
-        return
-      endif
-      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+" ---------------------------------------------------------------------
+" s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2
+"                       using the hardcopy command.  Local marked-file list only.
+fun! s:NetrwMarkFilePrint(islocal)
+  "  call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")")
+  let curbufnr= bufnr("%")
 
-      if !exists("s:netrwmftgt")
-        NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67)
-        "   call Dret("s:NetrwMarkFileCopy 0")
-        return 0
-      endif
-      "  call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    "   call Dret("s:NetrwMarkFilePrint")
+    return
+  endif
+  "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+  let curdir= s:NetrwGetCurdir(a:islocal)
 
-      if      a:islocal &&  s:netrwmftgt_islocal
-        " move: local -> local
-        "   call Decho("move from local to local",'~'.expand(""))
-        "   call Decho("local to local move",'~'.expand(""))
-        if !executable(g:netrw_localmovecmd)
-          call netrw#ErrorMsg(s:ERROR,"g:netrw_localmovecmd<".g:netrw_localmovecmd."> not executable on your system, aborting",90)
-          "    call Dfunc("s:NetrwMarkFileMove : g:netrw_localmovecmd<".g:netrw_localmovecmd."> n/a!")
-          return
-        endif
-        let tgt = s:ShellEscape(s:netrwmftgt)
-        "   call Decho("tgt<".tgt.">",'~'.expand(""))
-        if !g:netrw_cygwin && has("win32")
-          let tgt= substitute(tgt, '/','\\','g')
-          "    call Decho("windows exception: tgt<".tgt.">",'~'.expand(""))
-          if g:netrw_localmovecmd =~ '\s'
-            let movecmd     = substitute(g:netrw_localmovecmd,'\s.*$','','')
-            let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','')
-            let movecmd     = netrw#WinPath(movecmd).movecmdargs
-            "     call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand(""))
-          else
-            let movecmd = netrw#WinPath(g:netrw_localmovecmd)
-            "     call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand(""))
-          endif
-        else
-          let movecmd = netrw#WinPath(g:netrw_localmovecmd)
-          "    call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand(""))
+  if exists("s:netrwmarkfilelist_{curbufnr}")
+    let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr}
+    call s:NetrwUnmarkList(curbufnr,curdir)
+    for fname in netrwmarkfilelist
+      if a:islocal
+        if g:netrw_keepdir
+          let fname= s:ComposePath(curdir,fname)
         endif
-        for fname in s:netrwmarkfilelist_{bufnr("%")}
-          if g:netrw_keepdir
-            " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1
-            let fname= b:netrw_curdir."/".fname
-          endif
-          if !g:netrw_cygwin && has("win32")
-            let fname= substitute(fname,'/','\\','g')
-          endif
-          "    call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand(""))
-          let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt)
-          if v:shell_error != 0
-            if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir
-              call netrw#ErrorMsg(s:ERROR,"move failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",100)
-            else
-              call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localmovecmd<".g:netrw_localmovecmd.">; it doesn't work!",54)
-            endif
-            break
-          endif
-        endfor
+      else
+        let fname= curdir.fname
+      endif
+      1split
+      " the autocmds will handle both local and remote files
+      "    call Decho("exe sil e ".escape(fname,' '),'~'.expand(""))
+      exe "sil NetrwKeepj e ".fnameescape(fname)
+      "    call Decho("hardcopy",'~'.expand(""))
+      hardcopy
+      q
+    endfor
+    2match none
+  endif
+  "  call Dret("s:NetrwMarkFilePrint")
+endfun
 
-      elseif  a:islocal && !s:netrwmftgt_islocal
-        " move: local -> remote
-        "   call Decho("move from local to remote",'~'.expand(""))
-        "   call Decho("copy",'~'.expand(""))
-        let mflist= s:netrwmarkfilelist_{bufnr("%")}
-        NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
-        "   call Decho("remove",'~'.expand(""))
-        for fname in mflist
-          let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
-          let ok        = s:NetrwLocalRmFile(b:netrw_curdir,barefname,1)
-        endfor
-        unlet mflist
-
-      elseif !a:islocal &&  s:netrwmftgt_islocal
-        " move: remote -> local
-        "   call Decho("move from remote to local",'~'.expand(""))
-        "   call Decho("copy",'~'.expand(""))
-        let mflist= s:netrwmarkfilelist_{bufnr("%")}
-        NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
-        "   call Decho("remove",'~'.expand(""))
-        for fname in mflist
-          let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
-          let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
-        endfor
-        unlet mflist
-
-      elseif !a:islocal && !s:netrwmftgt_islocal
-        " move: remote -> remote
-        "   call Decho("move from remote to remote",'~'.expand(""))
-        "   call Decho("copy",'~'.expand(""))
-        let mflist= s:netrwmarkfilelist_{bufnr("%")}
-        NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
-        "   call Decho("remove",'~'.expand(""))
-        for fname in mflist
-          let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
-          let ok        = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1)
-        endfor
-        unlet mflist
-      endif
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2
+"                        files when given a regexp (for which a prompt is
+"                        issued) (matches to name of files).
+fun! s:NetrwMarkFileRegexp(islocal)
+  "  call Dfunc("s:NetrwMarkFileRegexp(islocal=".a:islocal.")")
 
-      " -------
-      " cleanup
-      " -------
-      "  call Decho("cleanup",'~'.expand(""))
+  " get the regular expression
+  call inputsave()
+  let regexp= input("Enter regexp: ","","file")
+  call inputrestore()
 
-      " remove markings from local buffer
-      call s:NetrwUnmarkList(curbufnr,curdir)                   " remove markings from local buffer
+  if a:islocal
+    let curdir= s:NetrwGetCurdir(a:islocal)
+    "   call Decho("curdir<".fnameescape(curdir).">")
+    " get the matching list of files using local glob()
+    "   call Decho("handle local regexp",'~'.expand(""))
+    let dirname = escape(b:netrw_curdir,g:netrw_glob_escape)
+    if v:version > 704 || (v:version == 704 && has("patch656"))
+      let filelist= glob(s:ComposePath(dirname,regexp),0,1,1)
+    else
+      let files   = glob(s:ComposePath(dirname,regexp),0,0)
+      let filelist= split(files,"\n")
+    endif
+    "   call Decho("files<".string(filelist).">",'~'.expand(""))
 
-      " refresh buffers
-      if !s:netrwmftgt_islocal
-        "   call Decho("refresh netrwmftgt<".s:netrwmftgt.">",'~'.expand(""))
-        NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
-      endif
-      if a:islocal
-        "   call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">",'~'.expand(""))
-        NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir)
-      endif
-      if g:netrw_fastbrowse <= 1
-        "   call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh",'~'.expand(""))
-        NetrwKeepj call s:LocalBrowseRefresh()
+    " mark the list of files
+    for fname in filelist
+      if fname =~ '^'.fnameescape(curdir)
+        "    call Decho("fname<".substitute(fname,'^'.fnameescape(curdir).'/','','').">",'~'.expand(""))
+        NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^'.fnameescape(curdir).'/','',''))
+      else
+        "    call Decho("fname<".fname.">",'~'.expand(""))
+        NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','',''))
       endif
+    endfor
 
-      "  call Dret("s:NetrwMarkFileMove")
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2
-    "                       using the hardcopy command.  Local marked-file list only.
-    fun! s:NetrwMarkFilePrint(islocal)
-      "  call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")")
-      let curbufnr= bufnr("%")
-
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        "   call Dret("s:NetrwMarkFilePrint")
-        return
-      endif
-      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-      let curdir= s:NetrwGetCurdir(a:islocal)
+  else
+    "   call Decho("handle remote regexp",'~'.expand(""))
+
+    " convert displayed listing into a filelist
+    let eikeep = &ei
+    let areg   = @a
+    sil NetrwKeepj %y a
+    setl ei=all ma
+    "   call Decho("setl ei=all ma",'~'.expand(""))
+    1split
+    NetrwKeepj call s:NetrwEnew()
+    NetrwKeepj call s:NetrwOptionsSafe(a:islocal)
+    sil NetrwKeepj norm! "ap
+    NetrwKeepj 2
+    let bannercnt= search('^" =====','W')
+    exe "sil NetrwKeepj 1,".bannercnt."d"
+    setl bt=nofile
+    if     g:netrw_liststyle == s:LONGLIST
+      sil NetrwKeepj %s/\s\{2,}\S.*$//e
+      call histdel("/",-1)
+    elseif g:netrw_liststyle == s:WIDELIST
+      sil NetrwKeepj %s/\s\{2,}/\r/ge
+      call histdel("/",-1)
+    elseif g:netrw_liststyle == s:TREELIST
+      exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e'
+      sil! NetrwKeepj g/^ .*$/d
+      call histdel("/",-1)
+      call histdel("/",-1)
+    endif
+    " convert regexp into the more usual glob-style format
+    let regexp= substitute(regexp,'\*','.*','g')
+    "   call Decho("regexp<".regexp.">",'~'.expand(""))
+    exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d"
+    call histdel("/",-1)
+    let filelist= getline(1,line("$"))
+    q!
+    for filename in filelist
+      NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','',''))
+    endfor
+    unlet filelist
+    let @a  = areg
+    let &ei = eikeep
+  endif
+  echo "  (use me to edit marked files)"
 
-      if exists("s:netrwmarkfilelist_{curbufnr}")
-        let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr}
-        call s:NetrwUnmarkList(curbufnr,curdir)
-        for fname in netrwmarkfilelist
-          if a:islocal
-            if g:netrw_keepdir
-              let fname= s:ComposePath(curdir,fname)
-            endif
-          else
-            let fname= curdir.fname
-          endif
-          1split
-          " the autocmds will handle both local and remote files
-          "    call Decho("exe sil e ".escape(fname,' '),'~'.expand(""))
-          exe "sil NetrwKeepj e ".fnameescape(fname)
-          "    call Decho("hardcopy",'~'.expand(""))
-          hardcopy
-          q
-        endfor
-        2match none
-      endif
-      "  call Dret("s:NetrwMarkFilePrint")
-    endfun
+  "  call Dret("s:NetrwMarkFileRegexp")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2
-    "                        files when given a regexp (for which a prompt is
-    "                        issued) (matches to name of files).
-    fun! s:NetrwMarkFileRegexp(islocal)
-      "  call Dfunc("s:NetrwMarkFileRegexp(islocal=".a:islocal.")")
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileSource: (invoked by ms) This function sources marked files {{{2
+"                        Uses the local marked file list.
+fun! s:NetrwMarkFileSource(islocal)
+  "  call Dfunc("s:NetrwMarkFileSource(islocal=".a:islocal.")")
+  let curbufnr= bufnr("%")
 
-      " get the regular expression
-      call inputsave()
-      let regexp= input("Enter regexp: ","","file")
-      call inputrestore()
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    "   call Dret("s:NetrwMarkFileSource")
+    return
+  endif
+  "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
+  let curdir= s:NetrwGetCurdir(a:islocal)
 
+  if exists("s:netrwmarkfilelist_{curbufnr}")
+    let netrwmarkfilelist = s:netrwmarkfilelist_{bufnr("%")}
+    call s:NetrwUnmarkList(curbufnr,curdir)
+    for fname in netrwmarkfilelist
       if a:islocal
-        let curdir= s:NetrwGetCurdir(a:islocal)
-        "   call Decho("curdir<".fnameescape(curdir).">")
-        " get the matching list of files using local glob()
-        "   call Decho("handle local regexp",'~'.expand(""))
-        let dirname = escape(b:netrw_curdir,g:netrw_glob_escape)
-        if v:version > 704 || (v:version == 704 && has("patch656"))
-          let filelist= glob(s:ComposePath(dirname,regexp),0,1,1)
-        else
-          let files   = glob(s:ComposePath(dirname,regexp),0,0)
-          let filelist= split(files,"\n")
+        if g:netrw_keepdir
+          let fname= s:ComposePath(curdir,fname)
         endif
-        "   call Decho("files<".string(filelist).">",'~'.expand(""))
-
-        " mark the list of files
-        for fname in filelist
-          if fname =~ '^'.fnameescape(curdir)
-            "    call Decho("fname<".substitute(fname,'^'.fnameescape(curdir).'/','','').">",'~'.expand(""))
-            NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^'.fnameescape(curdir).'/','',''))
-          else
-            "    call Decho("fname<".fname.">",'~'.expand(""))
-            NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','',''))
-          endif
-        endfor
-
       else
-        "   call Decho("handle remote regexp",'~'.expand(""))
-
-        " convert displayed listing into a filelist
-        let eikeep = &ei
-        let areg   = @a
-        sil NetrwKeepj %y a
-        setl ei=all ma
-        "   call Decho("setl ei=all ma",'~'.expand(""))
-        1split
-        NetrwKeepj call s:NetrwEnew()
-        NetrwKeepj call s:NetrwOptionsSafe(a:islocal)
-        sil NetrwKeepj norm! "ap
-        NetrwKeepj 2
-        let bannercnt= search('^" =====','W')
-        exe "sil NetrwKeepj 1,".bannercnt."d"
-        setl bt=nofile
-        if     g:netrw_liststyle == s:LONGLIST
-          sil NetrwKeepj %s/\s\{2,}\S.*$//e
-          call histdel("/",-1)
-        elseif g:netrw_liststyle == s:WIDELIST
-          sil NetrwKeepj %s/\s\{2,}/\r/ge
-          call histdel("/",-1)
-        elseif g:netrw_liststyle == s:TREELIST
-          exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e'
-          sil! NetrwKeepj g/^ .*$/d
-          call histdel("/",-1)
-          call histdel("/",-1)
-        endif
-        " convert regexp into the more usual glob-style format
-        let regexp= substitute(regexp,'\*','.*','g')
-        "   call Decho("regexp<".regexp.">",'~'.expand(""))
-        exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d"
-        call histdel("/",-1)
-        let filelist= getline(1,line("$"))
-        q!
-        for filename in filelist
-          NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','',''))
-        endfor
-        unlet filelist
-        let @a  = areg
-        let &ei = eikeep
+        let fname= curdir.fname
       endif
-      echo "  (use me to edit marked files)"
+      " the autocmds will handle sourcing both local and remote files
+      "    call Decho("exe so ".fnameescape(fname),'~'.expand(""))
+      exe "so ".fnameescape(fname)
+    endfor
+    2match none
+  endif
+  "  call Dret("s:NetrwMarkFileSource")
+endfun
 
-      "  call Dret("s:NetrwMarkFileRegexp")
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileTag: (invoked by mT) This function applies g:netrw_ctags to marked files {{{2
+"                     Uses the global markfilelist
+fun! s:NetrwMarkFileTag(islocal)
+  let svpos    = winsaveview()
+  let curdir   = s:NetrwGetCurdir(a:islocal)
+  let curbufnr = bufnr("%")
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileSource: (invoked by ms) This function sources marked files {{{2
-    "                        Uses the local marked file list.
-    fun! s:NetrwMarkFileSource(islocal)
-      "  call Dfunc("s:NetrwMarkFileSource(islocal=".a:islocal.")")
-      let curbufnr= bufnr("%")
-
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        "   call Dret("s:NetrwMarkFileSource")
-        return
+  " sanity check
+  if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+    NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+    return
+  endif
+
+  if exists("s:netrwmarkfilelist")
+    let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "s:ShellEscape(v:val,".!a:islocal.")"))
+    call s:NetrwUnmarkAll()
+
+    if a:islocal
+
+      call system(g:netrw_ctags." ".netrwmarkfilelist)
+      if v:shell_error
+        call netrw#ErrorMsg(s:ERROR,"g:netrw_ctags<".g:netrw_ctags."> is not executable!",51)
       endif
-      "  call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand(""))
-      let curdir= s:NetrwGetCurdir(a:islocal)
 
-      if exists("s:netrwmarkfilelist_{curbufnr}")
-        let netrwmarkfilelist = s:netrwmarkfilelist_{bufnr("%")}
-        call s:NetrwUnmarkList(curbufnr,curdir)
-        for fname in netrwmarkfilelist
-          if a:islocal
-            if g:netrw_keepdir
-              let fname= s:ComposePath(curdir,fname)
-            endif
-          else
-            let fname= curdir.fname
-          endif
-          " the autocmds will handle sourcing both local and remote files
-          "    call Decho("exe so ".fnameescape(fname),'~'.expand(""))
-          exe "so ".fnameescape(fname)
-        endfor
-        2match none
+    else
+      let cmd   = s:RemoteSystem(g:netrw_ctags." ".netrwmarkfilelist)
+      call netrw#Obtain(a:islocal,"tags")
+      let curdir= b:netrw_curdir
+      1split
+      NetrwKeepj e tags
+      let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','')
+      exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e'
+      call histdel("/",-1)
+      wq!
+    endif
+    2match none
+    call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+    call winrestview(svpos)
+  endif
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwMarkFileTgt:  (invoked by mt) This function sets up a marked file target {{{2
+"   Sets up two variables,
+"     s:netrwmftgt         : holds the target directory
+"     s:netrwmftgt_islocal : 0=target directory is remote
+"                            1=target directory is local
+fun! s:NetrwMarkFileTgt(islocal)
+  let svpos  = winsaveview()
+  let curdir = s:NetrwGetCurdir(a:islocal)
+  let hadtgt = exists("s:netrwmftgt")
+  if !exists("w:netrw_bannercnt")
+    let w:netrw_bannercnt= b:netrw_bannercnt
+  endif
+
+  " set up target
+  if line(".") < w:netrw_bannercnt
+    " if cursor in banner region, use b:netrw_curdir for the target unless its already the target
+    if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") && s:netrwmftgt == b:netrw_curdir
+      unlet s:netrwmftgt s:netrwmftgt_islocal
+      if g:netrw_fastbrowse <= 1
+        call s:LocalBrowseRefresh()
       endif
-      "  call Dret("s:NetrwMarkFileSource")
-    endfun
+      call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+      call winrestview(svpos)
+      return
+    else
+      let s:netrwmftgt= b:netrw_curdir
+    endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileTag: (invoked by mT) This function applies g:netrw_ctags to marked files {{{2
-    "                     Uses the global markfilelist
-    fun! s:NetrwMarkFileTag(islocal)
-      let svpos    = winsaveview()
-      let curdir   = s:NetrwGetCurdir(a:islocal)
-      let curbufnr = bufnr("%")
+  else
+    " get word under cursor.
+    "  * If directory, use it for the target.
+    "  * If file, use b:netrw_curdir for the target
+    let curword= s:NetrwGetWord()
+    let tgtdir = s:ComposePath(curdir,curword)
+    if a:islocal && isdirectory(s:NetrwFile(tgtdir))
+      let s:netrwmftgt = tgtdir
+    elseif !a:islocal && tgtdir =~ '/$'
+      let s:netrwmftgt = tgtdir
+    else
+      let s:netrwmftgt = curdir
+    endif
+  endif
+  if a:islocal
+    " simplify the target (eg. /abc/def/../ghi -> /abc/ghi)
+    let s:netrwmftgt= simplify(s:netrwmftgt)
+  endif
+  if g:netrw_cygwin
+    let s:netrwmftgt= substitute(system("cygpath ".s:ShellEscape(s:netrwmftgt)),'\n$','','')
+    let s:netrwmftgt= substitute(s:netrwmftgt,'\n$','','')
+  endif
+  let s:netrwmftgt_islocal= a:islocal
 
-      " sanity check
-      if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
-        NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-        return
-      endif
+  " need to do refresh so that the banner will be updated
+  "  s:LocalBrowseRefresh handles all local-browsing buffers when not fast browsing
+  if g:netrw_fastbrowse <= 1
+    call s:LocalBrowseRefresh()
+  endif
+  "  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+    call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,w:netrw_treetop,0))
+  else
+    call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  endif
+  call winrestview(svpos)
+  if !hadtgt
+    sil! NetrwKeepj norm! j
+  endif
+endfun
 
-      if exists("s:netrwmarkfilelist")
-        let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "s:ShellEscape(v:val,".!a:islocal.")"))
-        call s:NetrwUnmarkAll()
+" ---------------------------------------------------------------------
+" s:NetrwGetCurdir: gets current directory and sets up b:netrw_curdir if necessary {{{2
+fun! s:NetrwGetCurdir(islocal)
+  "  call Dfunc("s:NetrwGetCurdir(islocal=".a:islocal.")")
 
-        if a:islocal
+  if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
+    let b:netrw_curdir = s:NetrwTreePath(w:netrw_treetop)
+  "   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used s:NetrwTreeDir)",'~'.expand(""))
+  elseif !exists("b:netrw_curdir")
+    let b:netrw_curdir= getcwd()
+    "   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used getcwd)",'~'.expand(""))
+  endif
 
-          call system(g:netrw_ctags." ".netrwmarkfilelist)
-          if v:shell_error
-            call netrw#ErrorMsg(s:ERROR,"g:netrw_ctags<".g:netrw_ctags."> is not executable!",51)
-          endif
+  "  call Decho("b:netrw_curdir<".b:netrw_curdir."> ".((b:netrw_curdir !~ '\<\a\{3,}://')? "does not match" : "matches")." url pattern",'~'.expand(""))
+  if b:netrw_curdir !~ '\<\a\{3,}://'
+    let curdir= b:netrw_curdir
+    "   call Decho("g:netrw_keepdir=".g:netrw_keepdir,'~'.expand(""))
+    if g:netrw_keepdir == 0
+      call s:NetrwLcd(curdir)
+    endif
+  endif
 
-        else
-          let cmd   = s:RemoteSystem(g:netrw_ctags." ".netrwmarkfilelist)
-          call netrw#Obtain(a:islocal,"tags")
-          let curdir= b:netrw_curdir
-          1split
-          NetrwKeepj e tags
-          let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','')
-          exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e'
-          call histdel("/",-1)
-          wq!
-        endif
-        2match none
-        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-        call winrestview(svpos)
-      endif
-    endfun
+  "  call Dret("s:NetrwGetCurdir <".curdir.">")
+  return b:netrw_curdir
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMarkFileTgt:  (invoked by mt) This function sets up a marked file target {{{2
-    "   Sets up two variables,
-    "     s:netrwmftgt         : holds the target directory
-    "     s:netrwmftgt_islocal : 0=target directory is remote
-    "                            1=target directory is local
-    fun! s:NetrwMarkFileTgt(islocal)
-      let svpos  = winsaveview()
-      let curdir = s:NetrwGetCurdir(a:islocal)
-      let hadtgt = exists("s:netrwmftgt")
-      if !exists("w:netrw_bannercnt")
-        let w:netrw_bannercnt= b:netrw_bannercnt
-      endif
-
-      " set up target
-      if line(".") < w:netrw_bannercnt
-        " if cursor in banner region, use b:netrw_curdir for the target unless its already the target
-        if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") && s:netrwmftgt == b:netrw_curdir
-          unlet s:netrwmftgt s:netrwmftgt_islocal
-          if g:netrw_fastbrowse <= 1
-            call s:LocalBrowseRefresh()
-          endif
-          call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-          call winrestview(svpos)
-          return
-        else
-          let s:netrwmftgt= b:netrw_curdir
-        endif
+" ---------------------------------------------------------------------
+" s:NetrwOpenFile: query user for a filename and open it {{{2
+fun! s:NetrwOpenFile(islocal)
+  "  call Dfunc("s:NetrwOpenFile(islocal=".a:islocal.")")
+  let ykeep= @@
+  call inputsave()
+  let fname= input("Enter filename: ")
+  call inputrestore()
+  "  call Decho("(s:NetrwOpenFile) fname<".fname.">",'~'.expand(""))
 
-      else
-        " get word under cursor.
-        "  * If directory, use it for the target.
-        "  * If file, use b:netrw_curdir for the target
-        let curword= s:NetrwGetWord()
-        let tgtdir = s:ComposePath(curdir,curword)
-        if a:islocal && isdirectory(s:NetrwFile(tgtdir))
-          let s:netrwmftgt = tgtdir
-        elseif !a:islocal && tgtdir =~ '/$'
-          let s:netrwmftgt = tgtdir
-        else
-          let s:netrwmftgt = curdir
-        endif
-      endif
-      if a:islocal
-        " simplify the target (eg. /abc/def/../ghi -> /abc/ghi)
-        let s:netrwmftgt= simplify(s:netrwmftgt)
-      endif
-      if g:netrw_cygwin
-        let s:netrwmftgt= substitute(system("cygpath ".s:ShellEscape(s:netrwmftgt)),'\n$','','')
-        let s:netrwmftgt= substitute(s:netrwmftgt,'\n$','','')
-      endif
-      let s:netrwmftgt_islocal= a:islocal
+  " determine if Lexplore is in use
+  if exists("t:netrw_lexbufnr")
+    " check if t:netrw_lexbufnr refers to a netrw window
+    "   call Decho("(s:netrwOpenFile) ..t:netrw_lexbufnr=".t:netrw_lexbufnr,'~'.expand(""))
+    let lexwinnr = bufwinnr(t:netrw_lexbufnr)
+    if lexwinnr != -1 && exists("g:netrw_chgwin") && g:netrw_chgwin != -1
+      "    call Decho("(s:netrwOpenFile) ..Lexplore in use",'~'.expand(""))
+      exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
+      exe "NetrwKeepj e ".fnameescape(fname)
+      let @@= ykeep
+      "    call Dret("s:NetrwOpenFile : creating a file with Lexplore mode")
+    endif
+  endif
 
-      " need to do refresh so that the banner will be updated
-      "  s:LocalBrowseRefresh handles all local-browsing buffers when not fast browsing
-      if g:netrw_fastbrowse <= 1
-        call s:LocalBrowseRefresh()
-      endif
-      "  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,w:netrw_treetop,0))
+  " Does the filename contain a path?
+  if fname !~ '[/\\]'
+    if exists("b:netrw_curdir")
+      if exists("g:netrw_quiet")
+        let netrw_quiet_keep = g:netrw_quiet
+      endif
+      let g:netrw_quiet = 1
+      " save position for benefit of Rexplore
+      let s:rexposn_{bufnr("%")}= winsaveview()
+      "    call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand(""))
+      if b:netrw_curdir =~ '/$'
+        exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname)
       else
-        call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+        exe "e ".fnameescape(b:netrw_curdir."/".fname)
       endif
-      call winrestview(svpos)
-      if !hadtgt
-        sil! NetrwKeepj norm! j
+      if exists("netrw_quiet_keep")
+        let g:netrw_quiet= netrw_quiet_keep
+      else
+        unlet g:netrw_quiet
       endif
-    endfun
-
-    " ---------------------------------------------------------------------
-    " s:NetrwGetCurdir: gets current directory and sets up b:netrw_curdir if necessary {{{2
-    fun! s:NetrwGetCurdir(islocal)
-      "  call Dfunc("s:NetrwGetCurdir(islocal=".a:islocal.")")
+    endif
+  else
+    exe "NetrwKeepj e ".fnameescape(fname)
+  endif
+  let @@= ykeep
+  "  call Dret("s:NetrwOpenFile")
+endfun
 
-      if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
-        let b:netrw_curdir = s:NetrwTreePath(w:netrw_treetop)
-        "   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used s:NetrwTreeDir)",'~'.expand(""))
-      elseif !exists("b:netrw_curdir")
-        let b:netrw_curdir= getcwd()
-        "   call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used getcwd)",'~'.expand(""))
-      endif
-
-      "  call Decho("b:netrw_curdir<".b:netrw_curdir."> ".((b:netrw_curdir !~ '\<\a\{3,}://')? "does not match" : "matches")." url pattern",'~'.expand(""))
-      if b:netrw_curdir !~ '\<\a\{3,}://'
-        let curdir= b:netrw_curdir
-        "   call Decho("g:netrw_keepdir=".g:netrw_keepdir,'~'.expand(""))
-        if g:netrw_keepdir == 0
-          call s:NetrwLcd(curdir)
-        endif
-      endif
+" ---------------------------------------------------------------------
+" netrw#Shrink: shrinks/expands a netrw or Lexplorer window {{{2
+"               For the mapping to this function be made via
+"               netrwPlugin, you'll need to have had
+"               g:netrw_usetab set to non-zero.
+fun! netrw#Shrink()
+  "  call Dfunc("netrw#Shrink() ft<".&ft."> winwidth=".winwidth(0)." lexbuf#".((exists("t:netrw_lexbufnr"))? t:netrw_lexbufnr : 'n/a'))
+  let curwin  = winnr()
+  let wiwkeep = &wiw
+  set wiw=1
 
-      "  call Dret("s:NetrwGetCurdir <".curdir.">")
-      return b:netrw_curdir
-    endfun
+  if &ft == "netrw"
+    if winwidth(0) > g:netrw_wiw
+      let t:netrw_winwidth= winwidth(0)
+      exe "vert resize ".g:netrw_wiw
+      wincmd l
+      if winnr() == curwin
+        wincmd h
+      endif
+    "    call Decho("vert resize 0",'~'.expand(""))
+    else
+      exe "vert resize ".t:netrw_winwidth
+      "    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
+    endif
+
+  elseif exists("t:netrw_lexbufnr")
+    exe bufwinnr(t:netrw_lexbufnr)."wincmd w"
+    if     winwidth(bufwinnr(t:netrw_lexbufnr)) >  g:netrw_wiw
+      let t:netrw_winwidth= winwidth(0)
+      exe "vert resize ".g:netrw_wiw
+      wincmd l
+      if winnr() == curwin
+        wincmd h
+      endif
+    "    call Decho("vert resize 0",'~'.expand(""))
+    elseif winwidth(bufwinnr(t:netrw_lexbufnr)) >= 0
+      exe "vert resize ".t:netrw_winwidth
+    "    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
+    else
+      call netrw#Lexplore(0,0)
+    endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwOpenFile: query user for a filename and open it {{{2
-    fun! s:NetrwOpenFile(islocal)
-      "  call Dfunc("s:NetrwOpenFile(islocal=".a:islocal.")")
-      let ykeep= @@
-      call inputsave()
-      let fname= input("Enter filename: ")
-      call inputrestore()
-      "  call Decho("(s:NetrwOpenFile) fname<".fname.">",'~'.expand(""))
-
-      " determine if Lexplore is in use
-      if exists("t:netrw_lexbufnr")
-        " check if t:netrw_lexbufnr refers to a netrw window
-        "   call Decho("(s:netrwOpenFile) ..t:netrw_lexbufnr=".t:netrw_lexbufnr,'~'.expand(""))
-        let lexwinnr = bufwinnr(t:netrw_lexbufnr)
-        if lexwinnr != -1 && exists("g:netrw_chgwin") && g:netrw_chgwin != -1
-          "    call Decho("(s:netrwOpenFile) ..Lexplore in use",'~'.expand(""))
-          exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
-          exe "NetrwKeepj e ".fnameescape(fname)
-          let @@= ykeep
-          "    call Dret("s:NetrwOpenFile : creating a file with Lexplore mode")
-        endif
-      endif
+  else
+    call netrw#Lexplore(0,0)
+  endif
+  let wiw= wiwkeep
 
-      " Does the filename contain a path?
-      if fname !~ '[/\\]'
-        if exists("b:netrw_curdir")
-          if exists("g:netrw_quiet")
-            let netrw_quiet_keep = g:netrw_quiet
-          endif
-          let g:netrw_quiet = 1
-          " save position for benefit of Rexplore
-          let s:rexposn_{bufnr("%")}= winsaveview()
-          "    call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand(""))
-          if b:netrw_curdir =~ '/$'
-            exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname)
-          else
-            exe "e ".fnameescape(b:netrw_curdir."/".fname)
-          endif
-          if exists("netrw_quiet_keep")
-            let g:netrw_quiet= netrw_quiet_keep
-          else
-            unlet g:netrw_quiet
-          endif
-        endif
-      else
-        exe "NetrwKeepj e ".fnameescape(fname)
-      endif
-      let @@= ykeep
-      "  call Dret("s:NetrwOpenFile")
-    endfun
+  "  call Dret("netrw#Shrink")
+endfun
 
-    " ---------------------------------------------------------------------
-    " netrw#Shrink: shrinks/expands a netrw or Lexplorer window {{{2
-    "               For the mapping to this function be made via
-    "               netrwPlugin, you'll need to have had
-    "               g:netrw_usetab set to non-zero.
-    fun! netrw#Shrink()
-      "  call Dfunc("netrw#Shrink() ft<".&ft."> winwidth=".winwidth(0)." lexbuf#".((exists("t:netrw_lexbufnr"))? t:netrw_lexbufnr : 'n/a'))
-      let curwin  = winnr()
-      let wiwkeep = &wiw
-      set wiw=1
-
-      if &ft == "netrw"
-        if winwidth(0) > g:netrw_wiw
-          let t:netrw_winwidth= winwidth(0)
-          exe "vert resize ".g:netrw_wiw
-          wincmd l
-          if winnr() == curwin
-            wincmd h
-          endif
-          "    call Decho("vert resize 0",'~'.expand(""))
-        else
-          exe "vert resize ".t:netrw_winwidth
-          "    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
-        endif
+" ---------------------------------------------------------------------
+" s:NetSortSequence: allows user to edit the sorting sequence {{{2
+fun! s:NetSortSequence(islocal)
+  let ykeep= @@
+  let svpos= winsaveview()
+  call inputsave()
+  let newsortseq= input("Edit Sorting Sequence: ",g:netrw_sort_sequence)
+  call inputrestore()
 
-      elseif exists("t:netrw_lexbufnr")
-        exe bufwinnr(t:netrw_lexbufnr)."wincmd w"
-        if     winwidth(bufwinnr(t:netrw_lexbufnr)) >  g:netrw_wiw
-          let t:netrw_winwidth= winwidth(0)
-          exe "vert resize ".g:netrw_wiw
-          wincmd l
-          if winnr() == curwin
-            wincmd h
-          endif
-          "    call Decho("vert resize 0",'~'.expand(""))
-        elseif winwidth(bufwinnr(t:netrw_lexbufnr)) >= 0
-          exe "vert resize ".t:netrw_winwidth
-          "    call Decho("vert resize ".t:netrw_winwidth,'~'.expand(""))
-        else
-          call netrw#Lexplore(0,0)
-        endif
+  " refresh the listing
+  let g:netrw_sort_sequence= newsortseq
+  NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  NetrwKeepj call winrestview(svpos)
+  let @@= ykeep
+endfun
 
-      else
-        call netrw#Lexplore(0,0)
-      endif
-      let wiw= wiwkeep
+" ---------------------------------------------------------------------
+" s:NetrwUnmarkList: delete local marked file list and remove their contents from the global marked-file list {{{2
+"   User access provided by the  mapping. (see :help netrw-mF)
+"   Used by many MarkFile functions.
+fun! s:NetrwUnmarkList(curbufnr,curdir)
+  "  call Dfunc("s:NetrwUnmarkList(curbufnr=".a:curbufnr." curdir<".a:curdir.">)")
+
+  "  remove all files in local marked-file list from global list
+  if exists("s:netrwmarkfilelist")
+    for mfile in s:netrwmarkfilelist_{a:curbufnr}
+      let dfile = s:ComposePath(a:curdir,mfile)       " prepend directory to mfile
+      let idx   = index(s:netrwmarkfilelist,dfile)    " get index in list of dfile
+      call remove(s:netrwmarkfilelist,idx)            " remove from global list
+    endfor
+    if s:netrwmarkfilelist == []
+      unlet s:netrwmarkfilelist
+    endif
 
-      "  call Dret("netrw#Shrink")
-    endfun
+    " getting rid of the local marked-file lists is easy
+    unlet s:netrwmarkfilelist_{a:curbufnr}
+  endif
+  if exists("s:netrwmarkfilemtch_{a:curbufnr}")
+    unlet s:netrwmarkfilemtch_{a:curbufnr}
+  endif
+  2match none
+  "  call Dret("s:NetrwUnmarkList")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetSortSequence: allows user to edit the sorting sequence {{{2
-    fun! s:NetSortSequence(islocal)
-      let ykeep= @@
-      let svpos= winsaveview()
-      call inputsave()
-      let newsortseq= input("Edit Sorting Sequence: ",g:netrw_sort_sequence)
-      call inputrestore()
+" ---------------------------------------------------------------------
+" s:NetrwUnmarkAll: remove the global marked file list and all local ones {{{2
+fun! s:NetrwUnmarkAll()
+  "  call Dfunc("s:NetrwUnmarkAll()")
+  if exists("s:netrwmarkfilelist")
+    unlet s:netrwmarkfilelist
+  endif
+  sil call s:NetrwUnmarkAll2()
+  2match none
+  "  call Dret("s:NetrwUnmarkAll")
+endfun
 
-      " refresh the listing
-      let g:netrw_sort_sequence= newsortseq
-      NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-      NetrwKeepj call winrestview(svpos)
-      let @@= ykeep
-    endfun
+" ---------------------------------------------------------------------
+" s:NetrwUnmarkAll2: unmark all files from all buffers {{{2
+fun! s:NetrwUnmarkAll2()
+  "  call Dfunc("s:NetrwUnmarkAll2()")
+  redir => netrwmarkfilelist_let
+  let
+  redir END
+  let netrwmarkfilelist_list= split(netrwmarkfilelist_let,'\n')          " convert let string into a let list
+  call filter(netrwmarkfilelist_list,"v:val =~ '^s:netrwmarkfilelist_'") " retain only those vars that start as s:netrwmarkfilelist_
+  call map(netrwmarkfilelist_list,"substitute(v:val,'\\s.*$','','')")    " remove what the entries are equal to
+  for flist in netrwmarkfilelist_list
+    let curbufnr= substitute(flist,'s:netrwmarkfilelist_','','')
+    unlet s:netrwmarkfilelist_{curbufnr}
+    unlet s:netrwmarkfilemtch_{curbufnr}
+  endfor
+  "  call Dret("s:NetrwUnmarkAll2")
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwUnmarkList: delete local marked file list and remove their contents from the global marked-file list {{{2
-    "   User access provided by the  mapping. (see :help netrw-mF)
-    "   Used by many MarkFile functions.
-    fun! s:NetrwUnmarkList(curbufnr,curdir)
-      "  call Dfunc("s:NetrwUnmarkList(curbufnr=".a:curbufnr." curdir<".a:curdir.">)")
-
-      "  remove all files in local marked-file list from global list
-      if exists("s:netrwmarkfilelist")
-        for mfile in s:netrwmarkfilelist_{a:curbufnr}
-          let dfile = s:ComposePath(a:curdir,mfile)       " prepend directory to mfile
-          let idx   = index(s:netrwmarkfilelist,dfile)    " get index in list of dfile
-          call remove(s:netrwmarkfilelist,idx)            " remove from global list
-        endfor
-        if s:netrwmarkfilelist == []
-          unlet s:netrwmarkfilelist
-        endif
+" ---------------------------------------------------------------------
+" s:NetrwUnMarkFile: called via mu map; unmarks *all* marked files, both global and buffer-local {{{2
+"
+" Marked files are in two types of lists:
+"    s:netrwmarkfilelist    -- holds complete paths to all marked files
+"    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
+"
+" Marked files suitable for use with 2match are in:
+"    s:netrwmarkfilemtch_#   -- used with 2match to display marked files
+fun! s:NetrwUnMarkFile(islocal)
+  let svpos    = winsaveview()
+  let curbufnr = bufnr("%")
 
-        " getting rid of the local marked-file lists is easy
-        unlet s:netrwmarkfilelist_{a:curbufnr}
-      endif
-      if exists("s:netrwmarkfilemtch_{a:curbufnr}")
-        unlet s:netrwmarkfilemtch_{a:curbufnr}
-      endif
-      2match none
-      "  call Dret("s:NetrwUnmarkList")
-    endfun
+  " unmark marked file list
+  " (although I expect s:NetrwUpload() to do it, I'm just making sure)
+  if exists("s:netrwmarkfilelist")
+    "   "   call Decho("unlet'ing: s:netrwmarkfilelist",'~'.expand(""))
+    unlet s:netrwmarkfilelist
+  endif
 
-    " ---------------------------------------------------------------------
-    " s:NetrwUnmarkAll: remove the global marked file list and all local ones {{{2
-    fun! s:NetrwUnmarkAll()
-      "  call Dfunc("s:NetrwUnmarkAll()")
-      if exists("s:netrwmarkfilelist")
-        unlet s:netrwmarkfilelist
-      endif
-      sil call s:NetrwUnmarkAll2()
-      2match none
-      "  call Dret("s:NetrwUnmarkAll")
-    endfun
+  let ibuf= 1
+  while ibuf < bufnr("$")
+    if exists("s:netrwmarkfilelist_".ibuf)
+      unlet s:netrwmarkfilelist_{ibuf}
+      unlet s:netrwmarkfilemtch_{ibuf}
+    endif
+    let ibuf = ibuf + 1
+  endwhile
+  2match none
 
-    " ---------------------------------------------------------------------
-    " s:NetrwUnmarkAll2: unmark all files from all buffers {{{2
-    fun! s:NetrwUnmarkAll2()
-      "  call Dfunc("s:NetrwUnmarkAll2()")
-      redir => netrwmarkfilelist_let
-      let
-      redir END
-      let netrwmarkfilelist_list= split(netrwmarkfilelist_let,'\n')          " convert let string into a let list
-      call filter(netrwmarkfilelist_list,"v:val =~ '^s:netrwmarkfilelist_'") " retain only those vars that start as s:netrwmarkfilelist_
-      call map(netrwmarkfilelist_list,"substitute(v:val,'\\s.*$','','')")    " remove what the entries are equal to
-      for flist in netrwmarkfilelist_list
-        let curbufnr= substitute(flist,'s:netrwmarkfilelist_','','')
-        unlet s:netrwmarkfilelist_{curbufnr}
-        unlet s:netrwmarkfilemtch_{curbufnr}
-      endfor
-      "  call Dret("s:NetrwUnmarkAll2")
-    endfun
+  "  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
+  call winrestview(svpos)
+endfun
 
-    " ---------------------------------------------------------------------
-    " s:NetrwUnMarkFile: called via mu map; unmarks *all* marked files, both global and buffer-local {{{2
-    "
-    " Marked files are in two types of lists:
-    "    s:netrwmarkfilelist    -- holds complete paths to all marked files
-    "    s:netrwmarkfilelist_#  -- holds list of marked files in current-buffer's directory (#==bufnr())
-    "
-    " Marked files suitable for use with 2match are in:
-    "    s:netrwmarkfilemtch_#   -- used with 2match to display marked files
-    fun! s:NetrwUnMarkFile(islocal)
-      let svpos    = winsaveview()
-      let curbufnr = bufnr("%")
+" ---------------------------------------------------------------------
+" s:NetrwMenu: generates the menu for gvim and netrw {{{2
+fun! s:NetrwMenu(domenu)
 
-      " unmark marked file list
-      " (although I expect s:NetrwUpload() to do it, I'm just making sure)
-      if exists("s:netrwmarkfilelist")
-        "   "   call Decho("unlet'ing: s:netrwmarkfilelist",'~'.expand(""))
-        unlet s:netrwmarkfilelist
-      endif
+  if !exists("g:NetrwMenuPriority")
+    let g:NetrwMenuPriority= 80
+  endif
 
-      let ibuf= 1
-      while ibuf < bufnr("$")
-        if exists("s:netrwmarkfilelist_".ibuf)
-          unlet s:netrwmarkfilelist_{ibuf}
-          unlet s:netrwmarkfilemtch_{ibuf}
-        endif
-        let ibuf = ibuf + 1
-      endwhile
-      2match none
+  if has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
+    "   call Dfunc("NetrwMenu(domenu=".a:domenu.")")
 
-      "  call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0))
-      call winrestview(svpos)
-    endfun
+    if !exists("s:netrw_menu_enabled") && a:domenu
+      "    call Decho("initialize menu",'~'.expand(""))
+      let s:netrw_menu_enabled= 1
+      exe 'sil! menu '.g:NetrwMenuPriority.'.1      '.g:NetrwTopLvlMenu.'Help    '
+      exe 'sil! menu '.g:NetrwMenuPriority.'.5      '.g:NetrwTopLvlMenu.'-Sep1-   :'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.6      '.g:NetrwTopLvlMenu.'Go\ Up\ Directory-  -'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.7      '.g:NetrwTopLvlMenu.'Apply\ Special\ Viewerx     x'
+      if g:netrw_dirhistmax > 0
+        exe 'sil! menu '.g:NetrwMenuPriority.'.8.1   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Current\ Directorymb      mb'
+        exe 'sil! menu '.g:NetrwMenuPriority.'.8.4   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Prev\ Dir\ (History)u u'
+        exe 'sil! menu '.g:NetrwMenuPriority.'.8.5   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Next\ Dir\ (History)U U'
+        exe 'sil! menu '.g:NetrwMenuPriority.'.8.6   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Listqb      qb'
+      else
+        exe 'sil! menu '.g:NetrwMenuPriority.'.8     '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History  :echo "(disabled)"'."\"
+      endif
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.1    '.g:NetrwTopLvlMenu.'Browsing\ Control.Horizontal\ Splito        o'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.2    '.g:NetrwTopLvlMenu.'Browsing\ Control.Vertical\ Splitv  v'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.3    '.g:NetrwTopLvlMenu.'Browsing\ Control.New\ Tabt t'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.4    '.g:NetrwTopLvlMenu.'Browsing\ Control.Previewp  p'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.5    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ File\ Hiding\ List'."       \'"
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.6    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ Sorting\ SequenceS  S'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.7    '.g:NetrwTopLvlMenu.'Browsing\ Control.Quick\ Hide/Unhide\ Dot\ Files'."gh       gh"
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.8    '.g:NetrwTopLvlMenu.'Browsing\ Control.Refresh\ Listing'."       \"
+      exe 'sil! menu '.g:NetrwMenuPriority.'.9.9    '.g:NetrwTopLvlMenu.'Browsing\ Control.Settings/Options:NetrwSettings    '.":NetrwSettings\"
+      exe 'sil! menu '.g:NetrwMenuPriority.'.10     '.g:NetrwTopLvlMenu.'Delete\ File/DirectoryD     D'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Create\ New\ File%   %'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Current\ Window      '."\"
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.2   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directoryp     p'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.3   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ WindowP        P'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.4   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Windowo     o'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tabt        t'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Windowv   v'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.1   '.g:NetrwTopLvlMenu.'Explore.Directory\ Name  :Explore '
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only):Explore\ */    :Explore */'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (+subdirs):Explore\ **/       :Explore **/'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.3   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (curdir\ only):Explore\ *//     :Explore *//'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (+subdirs):Explore\ **//        :Explore **//'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Next\ Match:Nexplore        :Nexplore'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Prev\ Match:Pexplore        :Pexplore'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.13     '.g:NetrwTopLvlMenu.'Make\ Subdirectoryd d'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.1   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Filemf  mf'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.2   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Files\ by\ Regexpmr     mr'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.3   '.g:NetrwTopLvlMenu.'Marked\ Files.Hide-Show-List\ Controla      a'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.4   '.g:NetrwTopLvlMenu.'Marked\ Files.Copy\ To\ Targetmc    mc'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.5   '.g:NetrwTopLvlMenu.'Marked\ Files.DeleteD       D'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.6   '.g:NetrwTopLvlMenu.'Marked\ Files.Diffmd        md'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.7   '.g:NetrwTopLvlMenu.'Marked\ Files.Editme        me'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.8   '.g:NetrwTopLvlMenu.'Marked\ Files.Exe\ Cmdmx    mx'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.9   '.g:NetrwTopLvlMenu.'Marked\ Files.Move\ To\ Targetmm    mm'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.10  '.g:NetrwTopLvlMenu.'Marked\ Files.ObtainO       O'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.11  '.g:NetrwTopLvlMenu.'Marked\ Files.Printmp       mp'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.12  '.g:NetrwTopLvlMenu.'Marked\ Files.ReplaceR      R'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.13  '.g:NetrwTopLvlMenu.'Marked\ Files.Set\ Targetmt mt'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.14  '.g:NetrwTopLvlMenu.'Marked\ Files.TagmT mT'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.14.15  '.g:NetrwTopLvlMenu.'Marked\ Files.Zip/Unzip/Compress/Uncompressmz       mz'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.15     '.g:NetrwTopLvlMenu.'Obtain\ FileO       O'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.thini :let w:netrw_liststyle=0'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.longi :let w:netrw_liststyle=1'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.widei :let w:netrw_liststyle=2'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.treei :let w:netrw_liststyle=3'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.1 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Show\ Alla   :let g:netrw_hide=0'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.3 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Normala      :let g:netrw_hide=1'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.2 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Hidden\ Onlya        :let g:netrw_hide=2'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.3   '.g:NetrwTopLvlMenu.'Style.Reverse\ Sorting\ Order'."r   r"
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.1 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Names       :let g:netrw_sort_by="name"'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.2 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Times       :let g:netrw_sort_by="time"'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Sizes       :let g:netrw_sort_by="size"'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Extens      :let g:netrw_sort_by="exten"'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.17     '.g:NetrwTopLvlMenu.'Rename\ File/DirectoryR     R'
+      exe 'sil! menu '.g:NetrwMenuPriority.'.18     '.g:NetrwTopLvlMenu.'Set\ Current\ Directoryc    c'
+      let s:netrw_menucnt= 28
+      call s:NetrwBookmarkMenu() " provide some history!  uses priorities 2,3, reserves 4, 8.2.x
+      call s:NetrwTgtMenu()      " let bookmarks and history be easy targets
+
+    elseif !a:domenu
+      let s:netrwcnt = 0
+      let curwin     = winnr()
+      windo if getline(2) =~# "Netrw" | let s:netrwcnt= s:netrwcnt + 1 | endif
+      exe curwin."wincmd w"
 
-    " ---------------------------------------------------------------------
-    " s:NetrwMenu: generates the menu for gvim and netrw {{{2
-    fun! s:NetrwMenu(domenu)
-
-      if !exists("g:NetrwMenuPriority")
-        let g:NetrwMenuPriority= 80
-      endif
-
-      if has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
-        "   call Dfunc("NetrwMenu(domenu=".a:domenu.")")
-
-        if !exists("s:netrw_menu_enabled") && a:domenu
-          "    call Decho("initialize menu",'~'.expand(""))
-          let s:netrw_menu_enabled= 1
-          exe 'sil! menu '.g:NetrwMenuPriority.'.1      '.g:NetrwTopLvlMenu.'Help    '
-          exe 'sil! menu '.g:NetrwMenuPriority.'.5      '.g:NetrwTopLvlMenu.'-Sep1-   :'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.6      '.g:NetrwTopLvlMenu.'Go\ Up\ Directory-  -'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.7      '.g:NetrwTopLvlMenu.'Apply\ Special\ Viewerx     x'
-          if g:netrw_dirhistmax > 0
-            exe 'sil! menu '.g:NetrwMenuPriority.'.8.1   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Current\ Directorymb      mb'
-            exe 'sil! menu '.g:NetrwMenuPriority.'.8.4   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Prev\ Dir\ (History)u u'
-            exe 'sil! menu '.g:NetrwMenuPriority.'.8.5   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Next\ Dir\ (History)U U'
-            exe 'sil! menu '.g:NetrwMenuPriority.'.8.6   '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Listqb      qb'
-          else
-            exe 'sil! menu '.g:NetrwMenuPriority.'.8     '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History  :echo "(disabled)"'."\"
-          endif
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.1    '.g:NetrwTopLvlMenu.'Browsing\ Control.Horizontal\ Splito        o'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.2    '.g:NetrwTopLvlMenu.'Browsing\ Control.Vertical\ Splitv  v'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.3    '.g:NetrwTopLvlMenu.'Browsing\ Control.New\ Tabt t'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.4    '.g:NetrwTopLvlMenu.'Browsing\ Control.Previewp  p'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.5    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ File\ Hiding\ List'."       \'"
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.6    '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ Sorting\ SequenceS  S'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.7    '.g:NetrwTopLvlMenu.'Browsing\ Control.Quick\ Hide/Unhide\ Dot\ Files'."gh       gh"
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.8    '.g:NetrwTopLvlMenu.'Browsing\ Control.Refresh\ Listing'."       \"
-          exe 'sil! menu '.g:NetrwMenuPriority.'.9.9    '.g:NetrwTopLvlMenu.'Browsing\ Control.Settings/Options:NetrwSettings    '.":NetrwSettings\"
-          exe 'sil! menu '.g:NetrwMenuPriority.'.10     '.g:NetrwTopLvlMenu.'Delete\ File/DirectoryD     D'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Create\ New\ File%   %'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.1   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Current\ Window      '."\"
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.2   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directoryp     p'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.3   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ WindowP        P'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.4   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Windowo     o'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tabt        t'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.11.5   '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Windowv   v'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.1   '.g:NetrwTopLvlMenu.'Explore.Directory\ Name  :Explore '
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only):Explore\ */    :Explore */'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.2   '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (+subdirs):Explore\ **/       :Explore **/'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.3   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (curdir\ only):Explore\ *//     :Explore *//'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (+subdirs):Explore\ **//        :Explore **//'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Next\ Match:Nexplore        :Nexplore'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.12.4   '.g:NetrwTopLvlMenu.'Explore.Prev\ Match:Pexplore        :Pexplore'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.13     '.g:NetrwTopLvlMenu.'Make\ Subdirectoryd d'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.1   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Filemf  mf'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.2   '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Files\ by\ Regexpmr     mr'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.3   '.g:NetrwTopLvlMenu.'Marked\ Files.Hide-Show-List\ Controla      a'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.4   '.g:NetrwTopLvlMenu.'Marked\ Files.Copy\ To\ Targetmc    mc'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.5   '.g:NetrwTopLvlMenu.'Marked\ Files.DeleteD       D'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.6   '.g:NetrwTopLvlMenu.'Marked\ Files.Diffmd        md'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.7   '.g:NetrwTopLvlMenu.'Marked\ Files.Editme        me'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.8   '.g:NetrwTopLvlMenu.'Marked\ Files.Exe\ Cmdmx    mx'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.9   '.g:NetrwTopLvlMenu.'Marked\ Files.Move\ To\ Targetmm    mm'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.10  '.g:NetrwTopLvlMenu.'Marked\ Files.ObtainO       O'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.11  '.g:NetrwTopLvlMenu.'Marked\ Files.Printmp       mp'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.12  '.g:NetrwTopLvlMenu.'Marked\ Files.ReplaceR      R'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.13  '.g:NetrwTopLvlMenu.'Marked\ Files.Set\ Targetmt mt'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.14  '.g:NetrwTopLvlMenu.'Marked\ Files.TagmT mT'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.14.15  '.g:NetrwTopLvlMenu.'Marked\ Files.Zip/Unzip/Compress/Uncompressmz       mz'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.15     '.g:NetrwTopLvlMenu.'Obtain\ FileO       O'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.thini :let w:netrw_liststyle=0'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.longi :let w:netrw_liststyle=1'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.widei :let w:netrw_liststyle=2'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.treei :let w:netrw_liststyle=3'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.1 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Show\ Alla   :let g:netrw_hide=0'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.3 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Normala      :let g:netrw_hide=1'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.2 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Hidden\ Onlya        :let g:netrw_hide=2'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.3   '.g:NetrwTopLvlMenu.'Style.Reverse\ Sorting\ Order'."r   r"
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.1 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Names       :let g:netrw_sort_by="name"'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.2 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Times       :let g:netrw_sort_by="time"'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Sizes       :let g:netrw_sort_by="size"'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Extens      :let g:netrw_sort_by="exten"'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.17     '.g:NetrwTopLvlMenu.'Rename\ File/DirectoryR     R'
-          exe 'sil! menu '.g:NetrwMenuPriority.'.18     '.g:NetrwTopLvlMenu.'Set\ Current\ Directoryc    c'
-          let s:netrw_menucnt= 28
-          call s:NetrwBookmarkMenu() " provide some history!  uses priorities 2,3, reserves 4, 8.2.x
-          call s:NetrwTgtMenu()      " let bookmarks and history be easy targets
-
-        elseif !a:domenu
-          let s:netrwcnt = 0
-          let curwin     = winnr()
-          windo if getline(2) =~# "Netrw" | let s:netrwcnt= s:netrwcnt + 1 | endif
-        exe curwin."wincmd w"
-
-        if s:netrwcnt <= 1
-          "     call Decho("clear menus",'~'.expand(""))
-          exe 'sil! unmenu '.g:NetrwTopLvlMenu
-          "     call Decho('exe sil! unmenu '.g:NetrwTopLvlMenu.'*','~'.expand(""))
-          sil! unlet s:netrw_menu_enabled
-        endif
+      if s:netrwcnt <= 1
+        "     call Decho("clear menus",'~'.expand(""))
+        exe 'sil! unmenu '.g:NetrwTopLvlMenu
+        "     call Decho('exe sil! unmenu '.g:NetrwTopLvlMenu.'*','~'.expand(""))
+        sil! unlet s:netrw_menu_enabled
       endif
-      "   call Dret("NetrwMenu")
-      return
     endif
+    "   call Dret("NetrwMenu")
+    return
+  endif
 
-  endfun
+endfun
 
-  " ---------------------------------------------------------------------
-  " s:NetrwObtain: obtain file under cursor or from markfile list {{{2
-  "                Used by the O maps (as NetrwObtain())
-  fun! s:NetrwObtain(islocal)
-    "  call Dfunc("NetrwObtain(islocal=".a:islocal.")")
+" ---------------------------------------------------------------------
+" s:NetrwObtain: obtain file under cursor or from markfile list {{{2
+"                Used by the O maps (as NetrwObtain())
+fun! s:NetrwObtain(islocal)
+  "  call Dfunc("NetrwObtain(islocal=".a:islocal.")")
 
-    let ykeep= @@
-    if exists("s:netrwmarkfilelist_{bufnr('%')}")
-      let islocal= s:netrwmarkfilelist_{bufnr('%')}[1] !~ '^\a\{3,}://'
-      call netrw#Obtain(islocal,s:netrwmarkfilelist_{bufnr('%')})
-      call s:NetrwUnmarkList(bufnr('%'),b:netrw_curdir)
+  let ykeep= @@
+  if exists("s:netrwmarkfilelist_{bufnr('%')}")
+    let islocal= s:netrwmarkfilelist_{bufnr('%')}[1] !~ '^\a\{3,}://'
+    call netrw#Obtain(islocal,s:netrwmarkfilelist_{bufnr('%')})
+    call s:NetrwUnmarkList(bufnr('%'),b:netrw_curdir)
+  else
+    call netrw#Obtain(a:islocal,s:NetrwGetWord())
+  endif
+  let @@= ykeep
+
+  "  call Dret("NetrwObtain")
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwPrevWinOpen: open file/directory in previous window.  {{{2
+"   If there's only one window, then the window will first be split.
+"   Returns:
+"     choice = 0 : didn't have to choose
+"     choice = 1 : saved modified file in window first
+"     choice = 2 : didn't save modified file, opened window
+"     choice = 3 : cancel open
+fun! s:NetrwPrevWinOpen(islocal)
+  let ykeep= @@
+  " grab a copy of the b:netrw_curdir to pass it along to newly split windows
+  let curdir = b:netrw_curdir
+
+  " get last window number and the word currently under the cursor
+  let origwin   = winnr()
+  let lastwinnr = winnr("$")
+  let curword      = s:NetrwGetWord()
+  let choice       = 0
+  let s:prevwinopen= 1  " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen)
+  let s:treedir = s:NetrwTreeDir(a:islocal)
+  let curdir    = s:treedir
+
+  let didsplit = 0
+  if lastwinnr == 1
+    " if only one window, open a new one first
+    " g:netrw_preview=0: preview window shown in a horizontally split window
+    " g:netrw_preview=1: preview window shown in a vertically   split window
+    if g:netrw_preview
+      " vertically split preview window
+      let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
+      exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
     else
-      call netrw#Obtain(a:islocal,s:NetrwGetWord())
+      " horizontally split preview window
+      let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
+      exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
     endif
-    let @@= ykeep
-
-    "  call Dret("NetrwObtain")
-  endfun
+    let didsplit = 1
 
-  " ---------------------------------------------------------------------
-  " s:NetrwPrevWinOpen: open file/directory in previous window.  {{{2
-  "   If there's only one window, then the window will first be split.
-  "   Returns:
-  "     choice = 0 : didn't have to choose
-  "     choice = 1 : saved modified file in window first
-  "     choice = 2 : didn't save modified file, opened window
-  "     choice = 3 : cancel open
-  fun! s:NetrwPrevWinOpen(islocal)
-    let ykeep= @@
-    " grab a copy of the b:netrw_curdir to pass it along to newly split windows
-    let curdir = b:netrw_curdir
-
-    " get last window number and the word currently under the cursor
-    let origwin   = winnr()
-    let lastwinnr = winnr("$")
-    let curword      = s:NetrwGetWord()
-    let choice       = 0
-    let s:prevwinopen= 1  " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen)
-    let s:treedir = s:NetrwTreeDir(a:islocal)
-    let curdir    = s:treedir
-
-    let didsplit = 0
-    if lastwinnr == 1
-      " if only one window, open a new one first
-      " g:netrw_preview=0: preview window shown in a horizontally split window
-      " g:netrw_preview=1: preview window shown in a vertically   split window
-      if g:netrw_preview
-        " vertically split preview window
-        let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
-        exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
-      else
-        " horizontally split preview window
-        let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
-        exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
-      endif
-      let didsplit = 1
+  else
+    NetrwKeepj call s:SaveBufVars()
+    let eikeep= &ei
+    setl ei=all
+    wincmd p
 
-    else
-      NetrwKeepj call s:SaveBufVars()
-      let eikeep= &ei
-      setl ei=all
+    if exists("s:lexplore_win") && s:lexplore_win == winnr()
+      " whoops -- user trying to open file in the Lexplore window.
+      " Use Lexplore's opening-file window instead.
+      "    exe g:netrw_chgwin."wincmd w"
       wincmd p
-
-      if exists("s:lexplore_win") && s:lexplore_win == winnr()
-        " whoops -- user trying to open file in the Lexplore window.
-        " Use Lexplore's opening-file window instead.
-        "    exe g:netrw_chgwin."wincmd w"
-        wincmd p
-        call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
-      endif
-
-      " prevwinnr: the window number of the "prev" window
-      " prevbufnr: the buffer number of the buffer in the "prev" window
-      " bnrcnt   : the qty of windows open on the "prev" buffer
-      let prevwinnr   = winnr()
-      let prevbufnr   = bufnr("%")
-      let prevbufname = bufname("%")
-      let prevmod     = &mod
-      let bnrcnt      = 0
-      NetrwKeepj call s:RestoreBufVars()
-
-      " if the previous window's buffer has been changed (ie. its modified flag is set),
-      " and it doesn't appear in any other extant window, then ask the
-      " user if s/he wants to abandon modifications therein.
-      if prevmod
-        windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif
+      call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1))
+    endif
+
+    " prevwinnr: the window number of the "prev" window
+    " prevbufnr: the buffer number of the buffer in the "prev" window
+    " bnrcnt   : the qty of windows open on the "prev" buffer
+    let prevwinnr   = winnr()
+    let prevbufnr   = bufnr("%")
+    let prevbufname = bufname("%")
+    let prevmod     = &mod
+    let bnrcnt      = 0
+    NetrwKeepj call s:RestoreBufVars()
+
+    " if the previous window's buffer has been changed (ie. its modified flag is set),
+    " and it doesn't appear in any other extant window, then ask the
+    " user if s/he wants to abandon modifications therein.
+    if prevmod
+      windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif
       exe prevwinnr."wincmd w"
 
       if bnrcnt == 1 && &hidden == 0
@@ -8250,7 +8251,7 @@ fun! s:NetrwUpload(fname,tgt,...)
 
         if exists("g:netrw_port") && g:netrw_port != ""
           NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
-          "      call Decho("filter input: ".getline('$'),'~'.expand(""))
+        "      call Decho("filter input: ".getline('$'),'~'.expand(""))
         else
           NetrwKeepj put ='open '.g:netrw_machine
           "      call Decho("filter input: ".getline('$'),'~'.expand(""))
@@ -8263,7 +8264,7 @@ fun! s:NetrwUpload(fname,tgt,...)
             if exists("s:netrw_passwd")
               NetrwKeepj call setline(line("$")+1,'"'.s:netrw_passwd.'"')
             endif
-            "       call Decho("filter input: ".getline('$'),'~'.expand(""))
+          "       call Decho("filter input: ".getline('$'),'~'.expand(""))
           elseif exists("s:netrw_passwd")
             NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
             "       call Decho("filter input: ".getline('$'),'~'.expand(""))
@@ -8340,7 +8341,7 @@ fun! s:NetrwPreview(path) range
         let pvhkeep = &pvh
         let winsz   = (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
         let &pvh    = winwidth(0) - winsz
-        "     call Decho("g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily)  g:netrw_winsize=".g:netrw_winsize,'~'.expand(""))
+      "     call Decho("g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily)  g:netrw_winsize=".g:netrw_winsize,'~'.expand(""))
       else
         " horizontal split
         let pvhkeep = &pvh
@@ -8935,7 +8936,7 @@ fun! s:NetrwTreeListing(dirname)
     if !exists("w:netrw_treetop")
       let w:netrw_treetop= a:dirname
       let s:netrw_treetop= w:netrw_treetop
-      " use \V in case the directory contains specials chars like '$' or '~'
+    " use \V in case the directory contains specials chars like '$' or '~'
     elseif (w:netrw_treetop =~ ('^'.'\V'.a:dirname) && s:Strlen(a:dirname) < s:Strlen(w:netrw_treetop))
           \ || a:dirname !~ ('^'.'\V'.w:netrw_treetop)
       let w:netrw_treetop= a:dirname
@@ -9006,12 +9007,12 @@ fun! s:NetrwTreePath(treetop)
   if curline =~ '/$'
     "   call Decho("extract tree directory from current line",'~'.expand(""))
     let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
-    "   call Decho("treedir<".treedir.">",'~'.expand(""))
+  "   call Decho("treedir<".treedir.">",'~'.expand(""))
   elseif curline =~ '@\s\+-->'
     "   call Decho("extract tree directory using symbolic link",'~'.expand(""))
     let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
     let treedir= substitute(treedir,'@\s\+-->.*$','','e')
-    "   call Decho("treedir<".treedir.">",'~'.expand(""))
+  "   call Decho("treedir<".treedir.">",'~'.expand(""))
   else
     "   call Decho("do not extract tree directory from current line and set treedir to empty",'~'.expand(""))
     let treedir= ""
@@ -9063,73 +9064,73 @@ fun! s:NetrwWideListing()
     if line("$") >= w:netrw_bannercnt
       " determine the maximum filename size; use that to set cpf
       exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
+      NetrwKeepj call histdel("/",-1)
+    else
+      " restore stored registers
+      call s:RestoreRegister(dict)
+      "    call Dret("NetrwWideListing")
+      return
+    endif
+    " allow for two spaces to separate columns
+    let b:netrw_cpf= b:netrw_cpf + 2
+    "   call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand(""))
+
+    " determine qty files per line (fpl)
+    let w:netrw_fpl= winwidth(0)/b:netrw_cpf
+    if w:netrw_fpl <= 0
+      let w:netrw_fpl= 1
+    endif
+    "   call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl,'~'.expand(""))
+
+    " make wide display
+    "   fpc: files per column of wide listing
+    exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'S",submatch(0)),"\\")/'
     NetrwKeepj call histdel("/",-1)
-  else
-    " restore stored registers
+    let fpc         = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl
+    let newcolstart = w:netrw_bannercnt + fpc
+    let newcolend   = newcolstart + fpc - 1
+    "   call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand(""))
+    if !has('nvim') && has("clipboard") && g:netrw_clipboard
+      "    call Decho("(s:NetrwWideListing) save @* and @+",'~'.expand(""))
+      sil! let keepregstar = @*
+      sil! let keepregplus = @+
+    endif
+    while line("$") >= newcolstart
+      if newcolend > line("$") | let newcolend= line("$") | endif
+      let newcolqty= newcolend - newcolstart
+      exe newcolstart
+      " COMBAK: both of the visual-mode using lines below are problematic vis-a-vis @*
+      if newcolqty == 0
+        exe "sil! NetrwKeepj norm! 0\$h\"ax".w:netrw_bannercnt."G$\"ap"
+      else
+        exe "sil! NetrwKeepj norm! 0\".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap'
+      endif
+      exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _'
+      exe 'sil! NetrwKeepj '.w:netrw_bannercnt
+    endwhile
+    if !has('nvim') && has("clipboard")
+      "    call Decho("(s:NetrwWideListing) restore @* and @+",'~'.expand(""))
+      if @* != keepregstar | sil! let @* = keepregstar | endif
+      if @+ != keepregplus | sil! let @+ = keepregplus | endif
+    endif
+    exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e'
+    NetrwKeepj call histdel("/",-1)
+    exe 'nno   w :call search(''^.\\|\s\s\zs\S'',''W'')'."\"
+    exe 'nno   b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\"
+    "   call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand(""))
+    exe "setl ".g:netrw_bufsettings
     call s:RestoreRegister(dict)
-    "    call Dret("NetrwWideListing")
+    "   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
+    "   call Dret("NetrwWideListing")
     return
-  endif
-  " allow for two spaces to separate columns
-  let b:netrw_cpf= b:netrw_cpf + 2
-  "   call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand(""))
-
-  " determine qty files per line (fpl)
-  let w:netrw_fpl= winwidth(0)/b:netrw_cpf
-  if w:netrw_fpl <= 0
-    let w:netrw_fpl= 1
-  endif
-  "   call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl,'~'.expand(""))
-
-  " make wide display
-  "   fpc: files per column of wide listing
-  exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'S",submatch(0)),"\\")/'
-  NetrwKeepj call histdel("/",-1)
-  let fpc         = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl
-  let newcolstart = w:netrw_bannercnt + fpc
-  let newcolend   = newcolstart + fpc - 1
-  "   call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand(""))
-  if !has('nvim') && has("clipboard") && g:netrw_clipboard
-    "    call Decho("(s:NetrwWideListing) save @* and @+",'~'.expand(""))
-    sil! let keepregstar = @*
-    sil! let keepregplus = @+
-  endif
-  while line("$") >= newcolstart
-    if newcolend > line("$") | let newcolend= line("$") | endif
-    let newcolqty= newcolend - newcolstart
-    exe newcolstart
-    " COMBAK: both of the visual-mode using lines below are problematic vis-a-vis @*
-    if newcolqty == 0
-      exe "sil! NetrwKeepj norm! 0\$h\"ax".w:netrw_bannercnt."G$\"ap"
-    else
-      exe "sil! NetrwKeepj norm! 0\".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap'
+  else
+    if hasmapto("w","n")
+      sil! nunmap  w
+    endif
+    if hasmapto("b","n")
+      sil! nunmap  b
     endif
-    exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _'
-    exe 'sil! NetrwKeepj '.w:netrw_bannercnt
-  endwhile
-  if !has('nvim') && has("clipboard")
-    "    call Decho("(s:NetrwWideListing) restore @* and @+",'~'.expand(""))
-    if @* != keepregstar | sil! let @* = keepregstar | endif
-    if @+ != keepregplus | sil! let @+ = keepregplus | endif
-  endif
-  exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e'
-  NetrwKeepj call histdel("/",-1)
-  exe 'nno   w :call search(''^.\\|\s\s\zs\S'',''W'')'."\"
-  exe 'nno   b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\"
-  "   call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand(""))
-  exe "setl ".g:netrw_bufsettings
-  call s:RestoreRegister(dict)
-  "   call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand(""))
-  "   call Dret("NetrwWideListing")
-  return
-else
-  if hasmapto("w","n")
-    sil! nunmap  w
-  endif
-  if hasmapto("b","n")
-    sil! nunmap  b
   endif
-endif
 endfun
 
 " ---------------------------------------------------------------------
@@ -9389,7 +9390,7 @@ fun! s:PerformListing(islocal)
     "   call Decho("--place cursor on top-left corner of file listing",'~'.expand(""))
     exe 'sil! '.w:netrw_bannercnt
     sil! NetrwKeepj norm! 0
-    "   call Decho("  tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
+  "   call Decho("  tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand(""))
   else
     "   call Decho("--did NOT place cursor on top-left corner",'~'.expand(""))
     "   call Decho("  w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'),'~'.expand(""))
@@ -10288,8 +10289,8 @@ fun! s:LocalFastBrowser()
       endif
     augroup END
 
-    " user must have changed fastbrowse to its fast setting, so remove
-    " the associated autocmd events
+  " user must have changed fastbrowse to its fast setting, so remove
+  " the associated autocmd events
   elseif g:netrw_fastbrowse > 1 && exists("#ShellCmdPost") && exists("s:netrw_events")
     unlet s:netrw_events
     augroup AuNetrwEvent
@@ -10848,7 +10849,7 @@ fun! s:ComposePath(base,subdir)
       let ret = a:base.a:subdir
     endif
 
-    " COMBAK: test on windows with changing to root directory: :e C:/
+  " COMBAK: test on windows with changing to root directory: :e C:/
   elseif a:subdir =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32")
     "   call Decho("windows",'~'.expand(""))
     let ret= a:subdir
@@ -10875,9 +10876,9 @@ fun! s:ComposePath(base,subdir)
     else
       let ret= urlbase.curpath.a:subdir
     endif
-    "   call Decho("urlbase<".urlbase.">",'~'.expand(""))
-    "   call Decho("curpath<".curpath.">",'~'.expand(""))
-    "   call Decho("ret<".ret.">",'~'.expand(""))
+  "   call Decho("urlbase<".urlbase.">",'~'.expand(""))
+  "   call Decho("curpath<".curpath.">",'~'.expand(""))
+  "   call Decho("ret<".ret.">",'~'.expand(""))
 
   else
     "   call Decho("local linux/macos",'~'.expand(""))
@@ -10973,7 +10974,7 @@ fun! s:GetTempfile(fname)
       let tmpfile = tmpfile
     endif
     let b:netrw_tmpfile= tmpfile
-    "   call Decho("o/s dependent fixed tempname<".tmpfile.">",'~'.expand(""))
+  "   call Decho("o/s dependent fixed tempname<".tmpfile.">",'~'.expand(""))
   else
     " re-use temporary filename
     let tmpfile= b:netrw_tmpfile
@@ -11096,9 +11097,9 @@ fun! s:NetrwCursor(editfile)
   "  call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul)
 
   if &ft != "netrw"
-    " if the current window isn't a netrw directory listing window, then use user cursorline/column
-    " settings.  Affects when netrw is used to read/write a file using scp/ftp/etc.
-    "   call Decho("case ft!=netrw: use user cul,cuc",'~'.expand(""))
+  " if the current window isn't a netrw directory listing window, then use user cursorline/column
+  " settings.  Affects when netrw is used to read/write a file using scp/ftp/etc.
+  "   call Decho("case ft!=netrw: use user cul,cuc",'~'.expand(""))
 
   elseif g:netrw_cursor == 8
     if w:netrw_liststyle == s:WIDELIST
@@ -11879,7 +11880,7 @@ fun! s:TreeListMove(dir)
     if     a:dir == '[[' && prvline != ''
       NetrwKeepj norm! 0
       let nl = search('^'.indentm1.'\%('.s:treedepthstring.'\)\@!','bWe',stopline) " search backwards
-      "    call Decho("regfile srch back: ".nl,'~'.expand(""))
+    "    call Decho("regfile srch back: ".nl,'~'.expand(""))
     elseif a:dir == '[]' && nxtline != ''
       NetrwKeepj norm! 0
       "    call Decho('srchpat<'.'^\%('.curindent.'\)\@!'.'>','~'.expand(""))
-- 
cgit 


From 0fea57bc7127f4e160c1d66cb3ec910c9ec409a6 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 20 Dec 2024 19:56:22 +0100
Subject: vim-patch:9.1.0950: filetype: fennelrc files are not recognized

Problem:  filetype: fennelrc files are not recognized
Solution: detect 'fennelrc' files as fennel filetype
          (Wu Zhenyu)

References:
https://github.com/bakpakin/Fennel/issues/193

closes: vim/vim#16262

https://github.com/vim/vim/commit/f173f4249fc785fb3e2b341bcfb0f21192cd4bf5

Co-authored-by: Wu, Zhenyu 
---
 runtime/lua/vim/filetype.lua       | 2 ++
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 83e82392de..e1751103c4 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1544,6 +1544,8 @@ local filename = {
   ['filter-rules'] = 'elmfilt',
   ['exim.conf'] = 'exim',
   exports = 'exports',
+  fennelrc = 'fennel',
+  ['.fennelrc'] = 'fennel',
   ['.fetchmailrc'] = 'fetchmail',
   fvSchemes = detect.foam,
   fvSolution = detect.foam,
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 18957da220..bc8c388983 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -273,7 +273,7 @@ func s:GetFilenameChecks() abort
     \ 'falcon': ['file.fal'],
     \ 'fan': ['file.fan', 'file.fwt'],
     \ 'faust': ['file.dsp', 'file.lib'],
-    \ 'fennel': ['file.fnl'],
+    \ 'fennel': ['file.fnl', '.fennelrc', 'fennelrc'],
     \ 'fetchmail': ['.fetchmailrc'],
     \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'],
     \ 'firrtl': ['file.fir'],
-- 
cgit 


From 5f8fac00bf48fa7afbdddd2a96d7cc81cc2ed893 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 20 Dec 2024 19:58:00 +0100
Subject: vim-patch:70881ba: runtime(dockerfile): do not set commentstring in
 syntax script

fixes: vim/vim#16268

https://github.com/vim/vim/commit/70881ba195d267d01df912294ddc5b5d525bba3d

Co-authored-by: Christian Brabandt 
---
 runtime/ftplugin/dockerfile.vim | 6 +++---
 runtime/syntax/dockerfile.vim   | 3 +--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/runtime/ftplugin/dockerfile.vim b/runtime/ftplugin/dockerfile.vim
index 2e3c447b59..e45bf4c1d8 100644
--- a/runtime/ftplugin/dockerfile.vim
+++ b/runtime/ftplugin/dockerfile.vim
@@ -1,7 +1,7 @@
 " Vim filetype plugin
 " Language:	Dockerfile
 " Maintainer:   Honza Pokorny 
-" Last Change:	2014 Aug 29
+" Last Change:	2024 Dec 20
 
 " Only do this when not done yet for this buffer
 if exists("b:did_ftplugin")
@@ -11,6 +11,6 @@ endif
 " Don't load another plugin for this buffer
 let b:did_ftplugin = 1
 
-let b:undo_ftplugin = "setl commentstring<"
-
 setlocal commentstring=#\ %s
+
+let b:undo_ftplugin = "setl commentstring<"
diff --git a/runtime/syntax/dockerfile.vim b/runtime/syntax/dockerfile.vim
index 6ec71fcdb6..f1d612f4ad 100644
--- a/runtime/syntax/dockerfile.vim
+++ b/runtime/syntax/dockerfile.vim
@@ -1,6 +1,6 @@
 " dockerfile.vim - Syntax highlighting for Dockerfiles
 " Maintainer:   Honza Pokorny 
-" Last Change:  2024 Jul 03
+" Last Change:  2024 Dec 20
 " License:      BSD
 
 " https://docs.docker.com/engine/reference/builder/
@@ -35,7 +35,6 @@ syntax region dockerfileShell  contained keepend start=/\v/ skip=/\v\\\_./ end=/
 syntax region dockerfileValue  contained keepend start=/\v/ skip=/\v\\\_./ end=/\v$/ contains=dockerfileString
 
 syntax region dockerfileComment start=/\v^\s*#/ end=/\v$/ contains=@Spell
-set commentstring=#\ %s
 
 hi def link dockerfileString String
 hi def link dockerfileKeyword Keyword
-- 
cgit 


From 382eb878bcbfba3b11d9c2deb21928e5319b32ad Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Fri, 20 Dec 2024 14:52:01 +0100
Subject: build: bump lua dev dependencies

busted: 2.1.1 -> 2.2.0
https://github.com/lunarmodules/busted/releases/tag/v2.1.2
https://github.com/lunarmodules/busted/releases/tag/v2.2.0

luacheck: 1.1.0 -> 1.2.0
https://github.com/lunarmodules/luacheck/releases/tag/v1.1.1
https://github.com/lunarmodules/luacheck/releases/tag/v1.1.2
https://github.com/lunarmodules/luacheck/releases/tag/v1.2.0
---
 cmake.deps/deps.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index 6473a81880..a94c85bd4d 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -58,5 +58,5 @@ WASMTIME_SHA256 6d1c17c756b83f29f629963228e5fa208ba9d6578421ba2cd07132b6a120accb
 
 UNCRUSTIFY_URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.80.1.tar.gz
 UNCRUSTIFY_SHA256 0e2616ec2f78e12816388c513f7060072ff7942b42f1175eb28b24cb75aaec48
-LUA_DEV_DEPS_URL https://github.com/neovim/deps/raw/5a1f71cceb24990a0b15fd9a472a5f549f019248/opt/lua-dev-deps.tar.gz
-LUA_DEV_DEPS_SHA256 27db2495f5eddc7fc191701ec9b291486853530c6125609d3197d03481e8d5a2
+LUA_DEV_DEPS_URL https://github.com/neovim/deps/raw/06ef2b58b0876f8de1a3f5a710473dcd7afff251/opt/lua-dev-deps.tar.gz
+LUA_DEV_DEPS_SHA256 49f8399e453103064a23c65534f266f3067cda716b6502f016bfafeed5799354
-- 
cgit 


From 130b5fd85f074ac5e40cb4b07c6c9dc6f91512bd Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Sat, 21 Dec 2024 08:27:27 -0600
Subject: feat(lsp): return table from lsp/ files on runtimepath (#31663)

Problem: LSP configs on the runtimepath must have the same name as the
LSP server and must also explicitly set the name in vim.lsp.config. This
is redundant and creates a footgun where a user may accidentally use the
wrong name when assigning to the vim.lsp.config table.

Solution: Return a table from lsp/ runtimepath files instead
---
 runtime/doc/lsp.txt     | 12 ++++--------
 runtime/doc/news.txt    |  6 ++++++
 runtime/lua/vim/lsp.lua | 16 ++++++++++------
 3 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 228bbafdd2..8b822daf9e 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -74,12 +74,8 @@ configurations, in increasing priority, from the following:
 
 1. Configuration defined for the `'*'` name.
 
-2. Configuration from the result of sourcing all `lsp/.lua` files
-   in 'runtimepath' for a server of name `name`.
-
-   Note: because of this, calls to |vim.lsp.config()| in `lsp/*.lua` are
-   treated independently to other calls. This ensures configurations
-   defined in `lsp/*.lua` have a lower priority.
+2. Configuration from the result of merging all tables returned by
+   `lsp/.lua` files in 'runtimepath' for a server of name `name`.
 
 3. Configurations defined anywhere else.
 
@@ -102,11 +98,11 @@ Given: >lua
   })
 
   -- Defined in ../lsp/clangd.lua
-  vim.lsp.config('clangd', {
+  return {
     cmd = { 'clangd' },
     root_markers = { '.clangd', 'compile_commands.json' },
     filetypes = { 'c', 'cpp' },
-  })
+  }
 
   -- Defined in init.lua
   vim.lsp.config('clangd', {
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 011970f5eb..eb04702ec2 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -22,6 +22,12 @@ EXPERIMENTS
 
 • Removed `vim.loader.disable()`. Use `vim.loader.enable(false)` instead.
 
+LSP
+
+• `lsp/` runtimepath files should return a table instead of calling
+  |vim.lsp.config()| (or assigning to `vim.lsp.config`). See |lsp-config|
+
+
 OPTIONS
 
 • 'jumpoptions' flag "unload" has been renamed to "clean".
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 5a93da4298..19d0377585 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -450,16 +450,20 @@ function lsp._resolve_config(name)
   if not econfig.resolved_config then
     -- Resolve configs from lsp/*.lua
     -- Calls to vim.lsp.config in lsp/* have a lower precedence than calls from other sites.
-    local orig_configs = lsp.config._configs
-    lsp.config._configs = {}
-    pcall(vim.cmd.runtime, { ('lsp/%s.lua'):format(name), bang = true })
-    local rtp_configs = lsp.config._configs
-    lsp.config._configs = orig_configs
+    local rtp_config = {} ---@type vim.lsp.Config
+    for _, v in ipairs(api.nvim_get_runtime_file(('lsp/%s.lua'):format(name), true)) do
+      local config = assert(loadfile(v))() ---@type any?
+      if type(config) == 'table' then
+        rtp_config = vim.tbl_deep_extend('force', rtp_config, config)
+      else
+        log.warn(string.format('%s does not return a table, ignoring', v))
+      end
+    end
 
     local config = vim.tbl_deep_extend(
       'force',
       lsp.config._configs['*'] or {},
-      rtp_configs[name] or {},
+      rtp_config,
       lsp.config._configs[name] or {}
     )
 
-- 
cgit 


From fd05c7f19d942edce39a2c99b5c064b71e66bc31 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Sat, 21 Dec 2024 13:03:17 +0100
Subject: test: format C test files and fix clang-tidy warnings

It's probably not worth adding the C test files to regular formatting as
they're pretty much never touched, but ensuring the files are formatted
according to our standards and getting rid of warnings is a cheap
one-time fix.
---
 test/functional/fixtures/printargs-test.c |   2 +-
 test/functional/fixtures/shell-test.c     |  17 +--
 test/functional/fixtures/streams-test.c   |   3 +-
 test/unit/fixtures/multiqueue.c           |   5 +-
 test/unit/fixtures/multiqueue.h           |   4 +-
 test/unit/fixtures/posix.h                |   6 +-
 test/unit/fixtures/vterm_test.c           | 201 +++++++++++++++---------------
 test/unit/fixtures/vterm_test.h           |   3 +-
 8 files changed, 122 insertions(+), 119 deletions(-)

diff --git a/test/functional/fixtures/printargs-test.c b/test/functional/fixtures/printargs-test.c
index 2c25cf8447..a1d3fdf76e 100644
--- a/test/functional/fixtures/printargs-test.c
+++ b/test/functional/fixtures/printargs-test.c
@@ -2,7 +2,7 @@
 
 int main(int argc, char **argv)
 {
-  for (int i=1; i
+#include 
 #include 
 #include 
-#include 
 #ifdef _MSC_VER
-#include 
-#define usleep(usecs) Sleep(usecs/1000)
+# include 
+# define usleep(usecs) Sleep(usecs/1000)
 #else
-#include 
+# include 
 #endif
 
 static void flush_wait(void)
@@ -56,7 +57,7 @@ int main(int argc, char **argv)
   if (argc >= 2) {
     if (strcmp(argv[1], "-t") == 0) {
       if (argc < 3) {
-        fprintf(stderr,"Missing prompt text for -t option\n");
+        fprintf(stderr, "Missing prompt text for -t option\n");
         return 5;
       } else {
         fprintf(stderr, "%s $ ", argv[2]);
@@ -107,18 +108,18 @@ int main(int argc, char **argv)
       char cmd[100];
       int arg;
 
-      while (1) {
+      while (true) {
         fprintf(stderr, "interact $ ");
 
         if (fgets(input, sizeof(input), stdin) == NULL) {
           break;  // EOF
         }
 
-        if(1 == sscanf(input, "%99s %d", cmd, &arg)) {
+        if (1 == sscanf(input, "%99s %d", cmd, &arg)) {
           arg = 0;
         }
         if (strcmp(cmd, "exit") == 0) {
-            return arg;
+          return arg;
         } else {
           fprintf(stderr, "command not found: %s\n", cmd);
         }
diff --git a/test/functional/fixtures/streams-test.c b/test/functional/fixtures/streams-test.c
index 5a59abb33b..68e668d5fa 100644
--- a/test/functional/fixtures/streams-test.c
+++ b/test/functional/fixtures/streams-test.c
@@ -1,6 +1,5 @@
 /// Helper program to exit and keep stdout open (like "xclip -i -loops 1").
 #include 
-
 #include 
 
 int main(int argc, char **argv)
@@ -8,7 +7,7 @@ int main(int argc, char **argv)
   uv_loop_t *loop = uv_default_loop();
   uv_process_t child_req;
 
-  char * args[3];
+  char *args[3];
   args[0] = "sleep";
   args[1] = "10";
   args[2] = NULL;
diff --git a/test/unit/fixtures/multiqueue.c b/test/unit/fixtures/multiqueue.c
index 149daca893..2003bc7a5a 100644
--- a/test/unit/fixtures/multiqueue.c
+++ b/test/unit/fixtures/multiqueue.c
@@ -1,8 +1,9 @@
-#include 
 #include 
-#include "nvim/event/multiqueue.h"
+#include 
+
 #include "multiqueue.h"
 
+#include "nvim/event/multiqueue.h"
 
 void ut_multiqueue_put(MultiQueue *self, const char *str)
 {
diff --git a/test/unit/fixtures/multiqueue.h b/test/unit/fixtures/multiqueue.h
index 78a3a89063..37da1d4db9 100644
--- a/test/unit/fixtures/multiqueue.h
+++ b/test/unit/fixtures/multiqueue.h
@@ -1,4 +1,4 @@
 #include "nvim/event/multiqueue.h"
 
-void ut_multiqueue_put(MultiQueue *queue, const char *str);
-const char *ut_multiqueue_get(MultiQueue *queue);
+void ut_multiqueue_put(MultiQueue *self, const char *str);
+const char *ut_multiqueue_get(MultiQueue *self);
diff --git a/test/unit/fixtures/posix.h b/test/unit/fixtures/posix.h
index f6f24cd9dc..0d16f8aac9 100644
--- a/test/unit/fixtures/posix.h
+++ b/test/unit/fixtures/posix.h
@@ -1,8 +1,8 @@
-#include 
-#include 
 #include 
-#include 
 #include 
+#include 
+#include 
+#include 
 
 enum {
   kPOSIXErrnoEINTR = EINTR,
diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c
index 47aa071f9b..f227ae4591 100644
--- a/test/unit/fixtures/vterm_test.c
+++ b/test/unit/fixtures/vterm_test.c
@@ -1,15 +1,15 @@
-#include "vterm_test.h"
-
 #include 
 
+#include "vterm_test.h"
+
 int parser_text(const char bytes[], size_t len, void *user)
 {
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "text ");
   size_t i;
-  for(i = 0; i < len; i++) {
+  for (i = 0; i < len; i++) {
     unsigned char b = (unsigned char)bytes[i];
-    if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) {
+    if (b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) {
       break;
     }
     fprintf(f, i ? ",%x" : "%x", b);
@@ -22,36 +22,37 @@ int parser_text(const char bytes[], size_t len, void *user)
 
 static void printchars(const char *s, size_t len, FILE *f)
 {
-  while(len--) {
+  while (len--) {
     fprintf(f, "%c", (s++)[0]);
   }
 }
 
-int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+int parser_csi(const char *leader, const long args[], int argcount, const char *intermed,
+               char command, void *user)
 {
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "csi %02x", command);
 
-  if(leader && leader[0]) {
+  if (leader && leader[0]) {
     fprintf(f, " L=");
-    for(int i = 0; leader[i]; i++) {
+    for (int i = 0; leader[i]; i++) {
       fprintf(f, "%02x", leader[i]);
     }
   }
 
-  for(int i = 0; i < argcount; i++) {
+  for (int i = 0; i < argcount; i++) {
     char sep = i ? ',' : ' ';
 
-    if(args[i] == CSI_ARG_MISSING) {
+    if (args[i] == CSI_ARG_MISSING) {
       fprintf(f, "%c*", sep);
     } else {
       fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
     }
   }
 
-  if(intermed && intermed[0]) {
+  if (intermed && intermed[0]) {
     fprintf(f, " I=");
-    for(int i = 0; intermed[i]; i++) {
+    for (int i = 0; intermed[i]; i++) {
       fprintf(f, "%02x", intermed[i]);
     }
   }
@@ -68,8 +69,8 @@ int parser_osc(int command, VTermStringFragment frag, void *user)
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "osc ");
 
-  if(frag.initial) {
-    if(command == -1) {
+  if (frag.initial) {
+    if (command == -1) {
       fprintf(f, "[");
     } else {
       fprintf(f, "[%d;", command);
@@ -78,7 +79,7 @@ int parser_osc(int command, VTermStringFragment frag, void *user)
 
   printchars(frag.str, frag.len, f);
 
-  if(frag.final) {
+  if (frag.final) {
     fprintf(f, "]");
   }
 
@@ -93,16 +94,16 @@ int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag,
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "dcs ");
 
-  if(frag.initial) {
+  if (frag.initial) {
     fprintf(f, "[");
-    for(size_t i = 0; i < commandlen; i++) {
+    for (size_t i = 0; i < commandlen; i++) {
       fprintf(f, "%c", command[i]);
     }
   }
 
-  printchars(frag.str, frag.len,f);
+  printchars(frag.str, frag.len, f);
 
-  if(frag.final) {
+  if (frag.final) {
     fprintf(f, "]");
   }
 
@@ -117,13 +118,13 @@ int parser_apc(VTermStringFragment frag, void *user)
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "apc ");
 
-  if(frag.initial) {
+  if (frag.initial) {
     fprintf(f, "[");
   }
 
   printchars(frag.str, frag.len, f);
 
-  if(frag.final) {
+  if (frag.final) {
     fprintf(f, "]");
   }
 
@@ -138,13 +139,13 @@ int parser_pm(VTermStringFragment frag, void *user)
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "pm ");
 
-  if(frag.initial) {
+  if (frag.initial) {
     fprintf(f, "[");
   }
 
-  printchars(frag.str, frag.len,f);
+  printchars(frag.str, frag.len, f);
 
-  if(frag.final) {
+  if (frag.final) {
     fprintf(f, "]");
   }
 
@@ -159,13 +160,13 @@ int parser_sos(VTermStringFragment frag, void *user)
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "sos ");
 
-  if(frag.initial) {
+  if (frag.initial) {
     fprintf(f, "[");
   }
 
-  printchars(frag.str, frag.len,f);
+  printchars(frag.str, frag.len, f);
 
-  if(frag.final) {
+  if (frag.final) {
     fprintf(f, "]");
   }
 
@@ -179,14 +180,14 @@ int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
 {
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "selection-set mask=%04X ", mask);
-  if(frag.initial) {
+  if (frag.initial) {
     fprintf(f, "[");
-}
+  }
   printchars(frag.str, frag.len, f);
-  if(frag.final) {
+  if (frag.final) {
     fprintf(f, "]");
-}
-  fprintf(f,"\n");
+  }
+  fprintf(f, "\n");
 
   fclose(f);
   return 1;
@@ -195,7 +196,7 @@ int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
 int selection_query(VTermSelectionMask mask, void *user)
 {
   FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"selection-query mask=%04X\n", mask);
+  fprintf(f, "selection-query mask=%04X\n", mask);
 
   fclose(f);
   return 1;
@@ -204,24 +205,24 @@ int selection_query(VTermSelectionMask mask, void *user)
 bool want_state_putglyph;
 int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
 {
-  if(!want_state_putglyph) {
+  if (!want_state_putglyph) {
     return 1;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "putglyph ");
-  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
+  for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
     fprintf(f, i ? ",%x" : "%x", info->chars[i]);
   }
   fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
-  if(info->protected_cell) {
+  if (info->protected_cell) {
     fprintf(f, " prot");
   }
-  if(info->dwl) {
+  if (info->dwl) {
     fprintf(f, " dwl");
   }
-  if(info->dhl) {
-    fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" );
+  if (info->dhl) {
+    fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?");
   }
   fprintf(f, "\n");
 
@@ -237,8 +238,8 @@ int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   state_pos = pos;
 
-  if(want_state_movecursor) {
-    fprintf(f,"movecursor %d,%d\n", pos.row, pos.col);
+  if (want_state_movecursor) {
+    fprintf(f, "movecursor %d,%d\n", pos.row, pos.col);
   }
 
   fclose(f);
@@ -248,15 +249,15 @@ int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
 bool want_state_scrollrect;
 int state_scrollrect(VTermRect rect, int downward, int rightward, void *user)
 {
-  if(!want_state_scrollrect) {
+  if (!want_state_scrollrect) {
     return 0;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
 
-  fprintf(f,"scrollrect %d..%d,%d..%d => %+d,%+d\n",
-      rect.start_row, rect.end_row, rect.start_col, rect.end_col,
-      downward, rightward);
+  fprintf(f, "scrollrect %d..%d,%d..%d => %+d,%+d\n",
+          rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+          downward, rightward);
 
   fclose(f);
   return 1;
@@ -265,14 +266,14 @@ int state_scrollrect(VTermRect rect, int downward, int rightward, void *user)
 bool want_state_moverect;
 int state_moverect(VTermRect dest, VTermRect src, void *user)
 {
-  if(!want_state_moverect) {
+  if (!want_state_moverect) {
     return 0;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
-      src.start_row,  src.end_row,  src.start_col,  src.end_col,
-      dest.start_row, dest.end_row, dest.start_col, dest.end_col);
+  fprintf(f, "moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
+          src.start_row,  src.end_row,  src.start_col,  src.end_col,
+          dest.start_row, dest.end_row, dest.start_col, dest.end_col);
 
   fclose(f);
   return 1;
@@ -282,28 +283,26 @@ void print_color(const VTermColor *col)
 {
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   if (VTERM_COLOR_IS_RGB(col)) {
-    fprintf(f,"rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue);
-  }
-  else if (VTERM_COLOR_IS_INDEXED(col)) {
-    fprintf(f,"idx(%d", col->indexed.idx);
-  }
-  else {
-    fprintf(f,"invalid(%d", col->type);
+    fprintf(f, "rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue);
+  } else if (VTERM_COLOR_IS_INDEXED(col)) {
+    fprintf(f, "idx(%d", col->indexed.idx);
+  } else {
+    fprintf(f, "invalid(%d", col->type);
   }
   if (VTERM_COLOR_IS_DEFAULT_FG(col)) {
-    fprintf(f,",is_default_fg");
+    fprintf(f, ",is_default_fg");
   }
   if (VTERM_COLOR_IS_DEFAULT_BG(col)) {
-    fprintf(f,",is_default_bg");
+    fprintf(f, ",is_default_bg");
   }
-  fprintf(f,")");
+  fprintf(f, ")");
   fclose(f);
 }
 
 bool want_state_settermprop;
 int state_settermprop(VTermProp prop, VTermValue *val, void *user)
 {
-  if(!want_state_settermprop) {
+  if (!want_state_settermprop) {
     return 1;
   }
 
@@ -311,28 +310,29 @@ int state_settermprop(VTermProp prop, VTermValue *val, void *user)
   FILE *f = fopen(VTERM_TEST_FILE, "a");
 
   VTermValueType type = vterm_get_prop_type(prop);
-  switch(type) {
-    case VTERM_VALUETYPE_BOOL:
-      fprintf(f,"settermprop %d %s\n", prop, val->boolean ? "true" : "false");
-      errcode = 1;
-      goto end;
-    case VTERM_VALUETYPE_INT:
-      fprintf(f,"settermprop %d %d\n", prop, val->number);
-      errcode = 1;
-      goto end;
-    case VTERM_VALUETYPE_STRING:
-      fprintf(f,"settermprop %d %s\"%.*s\"%s\n", prop,
-          val->string.initial ? "[" : "", (int)val->string.len, val->string.str, val->string.final ? "]" : "");
-      errcode=0;
-      goto end;
-    case VTERM_VALUETYPE_COLOR:
-      fprintf(f,"settermprop %d ", prop);
-      print_color(&val->color);
-      fprintf(f,"\n");
-      errcode=1;
-      goto end;
-    case VTERM_N_VALUETYPES:
-      goto end;
+  switch (type) {
+  case VTERM_VALUETYPE_BOOL:
+    fprintf(f, "settermprop %d %s\n", prop, val->boolean ? "true" : "false");
+    errcode = 1;
+    goto end;
+  case VTERM_VALUETYPE_INT:
+    fprintf(f, "settermprop %d %d\n", prop, val->number);
+    errcode = 1;
+    goto end;
+  case VTERM_VALUETYPE_STRING:
+    fprintf(f, "settermprop %d %s\"%.*s\"%s\n", prop,
+            val->string.initial ? "[" : "", (int)val->string.len, val->string.str,
+            val->string.final ? "]" : "");
+    errcode = 0;
+    goto end;
+  case VTERM_VALUETYPE_COLOR:
+    fprintf(f, "settermprop %d ", prop);
+    print_color(&val->color);
+    fprintf(f, "\n");
+    errcode = 1;
+    goto end;
+  case VTERM_N_VALUETYPES:
+    goto end;
   }
 
 end:
@@ -343,15 +343,15 @@ end:
 bool want_state_erase;
 int state_erase(VTermRect rect, int selective, void *user)
 {
-  if(!want_state_erase) {
+  if (!want_state_erase) {
     return 1;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
 
-  fprintf(f,"erase %d..%d,%d..%d%s\n",
-      rect.start_row, rect.end_row, rect.start_col, rect.end_col,
-      selective ? " selective" : "");
+  fprintf(f, "erase %d..%d,%d..%d%s\n",
+          rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+          selective ? " selective" : "");
 
   fclose(f);
   return 1;
@@ -374,7 +374,7 @@ struct {
 
 int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
 {
-  switch(attr) {
+  switch (attr) {
   case VTERM_ATTR_BOLD:
     state_pen.bold = val->boolean;
     break;
@@ -422,13 +422,14 @@ int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
 }
 
 bool want_state_scrollback;
-int state_sb_clear(void *user) {
-  if(!want_state_scrollback) {
+int state_sb_clear(void *user)
+{
+  if (!want_state_scrollback) {
     return 1;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"sb_clear\n");
+  fprintf(f, "sb_clear\n");
   fclose(f);
 
   return 0;
@@ -437,18 +438,18 @@ int state_sb_clear(void *user) {
 bool want_screen_scrollback;
 int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
 {
-  if(!want_screen_scrollback) {
+  if (!want_screen_scrollback) {
     return 1;
   }
 
   int eol = cols;
-  while(eol && !cells[eol-1].chars[0]) {
+  while (eol && !cells[eol - 1].chars[0]) {
     eol--;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "sb_pushline %d =", cols);
-  for(int c = 0; c < eol; c++) {
+  for (int c = 0; c < eol; c++) {
     fprintf(f, " %02X", cells[c].chars[0]);
   }
   fprintf(f, "\n");
@@ -460,13 +461,13 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
 
 int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
 {
-  if(!want_screen_scrollback) {
+  if (!want_screen_scrollback) {
     return 0;
   }
 
   // All lines of scrollback contain "ABCDE"
-  for(int col = 0; col < cols; col++) {
-    if(col < 5) {
+  for (int col = 0; col < cols; col++) {
+    if (col < 5) {
       cells[col].chars[0] = (uint32_t)('A' + col);
     } else {
       cells[col].chars[0] = 0;
@@ -476,14 +477,14 @@ int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
-  fprintf(f,"sb_popline %d\n", cols);
+  fprintf(f, "sb_popline %d\n", cols);
   fclose(f);
   return 1;
 }
 
 int screen_sb_clear(void *user)
 {
-  if(!want_screen_scrollback) {
+  if (!want_screen_scrollback) {
     return 1;
   }
 
@@ -497,8 +498,8 @@ void term_output(const char *s, size_t len, void *user)
 {
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "output ");
-  for(size_t i = 0; i < len; i++) {
-    fprintf(f, "%x%s", (unsigned char)s[i], i < len-1 ? "," : "\n");
+  for (size_t i = 0; i < len; i++) {
+    fprintf(f, "%x%s", (unsigned char)s[i], i < len - 1 ? "," : "\n");
   }
   fclose(f);
 }
diff --git a/test/unit/fixtures/vterm_test.h b/test/unit/fixtures/vterm_test.h
index 924c6c1633..a05e7d499e 100644
--- a/test/unit/fixtures/vterm_test.h
+++ b/test/unit/fixtures/vterm_test.h
@@ -5,7 +5,8 @@
 #include "vterm/vterm.h"
 
 int parser_text(const char bytes[], size_t len, void *user);
-int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
+int parser_csi(const char *leader, const long args[], int argcount, const char *intermed,
+               char command, void *user);
 int parser_osc(int command, VTermStringFragment frag, void *user);
 int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
 int parser_apc(VTermStringFragment frag, void *user);
-- 
cgit 


From c7a4197a5c344f02241eed0761c86487ee5bbd96 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 22 Dec 2024 11:15:12 +0100
Subject: vim-patch:9.1.0951: filetype: jshell files are not recognized

Problem:  filetype: jshell files are not recognized
Solution: detect '*.jsh' files as java filetype
          (Konfekt)

closes: vim/vim#16260

https://github.com/vim/vim/commit/62e3014ab1146d7f78694c97fc6974f1af2cc5af

Co-authored-by: Konfekt 
---
 runtime/lua/vim/filetype.lua       | 1 +
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index e1751103c4..31de25a714 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -616,6 +616,7 @@ local extension = {
   janet = 'janet',
   jav = 'java',
   java = 'java',
+  jsh = 'java',
   jj = 'javacc',
   jjt = 'javacc',
   es = 'javascript',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index bc8c388983..7b7e3bdbfb 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -374,7 +374,7 @@ func s:GetFilenameChecks() abort
     \ 'jal': ['file.jal', 'file.JAL'],
     \ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'],
     \ 'janet': ['file.janet'],
-    \ 'java': ['file.java', 'file.jav'],
+    \ 'java': ['file.java', 'file.jav', 'file.jsh'],
     \ 'javacc': ['file.jj', 'file.jjt'],
     \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs', '.node_repl_history', '.bun_repl_history', 'deno_history.txt'],
     \ 'javascript.glimmer': ['file.gjs'],
-- 
cgit 


From 394f69a25dc32c5b101ba2d34ac6376b0c75b2a2 Mon Sep 17 00:00:00 2001
From: Luuk van Baal 
Date: Fri, 20 Dec 2024 21:11:38 +0100
Subject: feat(ui): additional arguments for cmdline_show/hide events

Problem:  Unable to tell what highlight the prompt part of a
          cmdline_show event should have, and whether cmdline_hide was
          emitted after aborting.
Solution: Add additional arguments hl_id to cmdline_show, and abort to
          cmdline_hide.
---
 runtime/doc/news.txt                  |  5 +++
 runtime/doc/ui.txt                    | 11 ++++---
 src/nvim/api/ui_events.in.h           |  4 +--
 src/nvim/ex_getln.c                   |  7 ++--
 test/functional/lua/ui_event_spec.lua |  4 +++
 test/functional/ui/cmdline_spec.lua   | 62 ++++++++++++++++++++++++++++-------
 test/functional/ui/messages_spec.lua  | 59 ++++++++++++++++++++++++++++-----
 test/functional/ui/screen.lua         | 19 ++++++++---
 8 files changed, 135 insertions(+), 36 deletions(-)

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 011970f5eb..19c4e3b51d 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -86,6 +86,11 @@ EVENTS
 
 • |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in
   |api-fast| context.
+• Various additions for the following UI events:
+  • `cmdline_show`: `hl_id` to highlight the prompt text.
+  • `cmdline_hide`: `abort` indicating if the cmdline was aborted.
+  • `msg_show`: new message kinds: "bufwrite", "completion", "list_cmd",
+    "lua_print", "number_prompt", "search_cmd", "undo", "wildlist".
 
 HIGHLIGHTS
 
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index f531411354..0082e9d76b 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -715,7 +715,7 @@ Activated by the `ext_cmdline` |ui-option|.
 This UI extension delegates presentation of the |cmdline| (except 'wildmenu').
 For command-line 'wildmenu' UI events, activate |ui-popupmenu|.
 
-["cmdline_show", content, pos, firstc, prompt, indent, level] ~
+["cmdline_show", content, pos, firstc, prompt, indent, level, hl_id] ~
         content: List of [attrs, string]
 	         [[{}, "t"], [attrs, "est"], ...]
 
@@ -728,8 +728,8 @@ For command-line 'wildmenu' UI events, activate |ui-popupmenu|.
 	`firstc` and `prompt` are text, that if non-empty should be
 	displayed in front of the command line. `firstc` always indicates
 	built-in command lines such as `:` (ex command) and `/` `?` (search),
-	while `prompt` is an |input()| prompt. `indent` tells how many spaces
-	the content should be indented.
+	while `prompt` is an |input()| prompt, highlighted with `hl_id`.
+	`indent` tells how many spaces the content should be indented.
 
 	The Nvim command line can be invoked recursively, for instance by
 	typing `=` at the command line prompt. The `level` field is used
@@ -749,8 +749,9 @@ For command-line 'wildmenu' UI events, activate |ui-popupmenu|.
 
 	Should be hidden at next cmdline_show.
 
-["cmdline_hide"] ~
-	Hide the cmdline.
+["cmdline_hide", abort] ~
+	Hide the cmdline. `abort` is true if the cmdline is hidden after an
+	aborting condition (|c_Esc| or |c_CTRL-C|).
 
 ["cmdline_block_show", lines] ~
 	Show a block of context to the current command line. For example if
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 0ed208fc1a..603f9b2005 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -136,13 +136,13 @@ void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array bu
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
 
 void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent,
-                  Integer level)
+                  Integer level, Integer hl_id)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
 void cmdline_pos(Integer pos, Integer level)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
 void cmdline_special_char(String c, Boolean shift, Integer level)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
-void cmdline_hide(Integer level)
+void cmdline_hide(Integer level, Boolean abort)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
 void cmdline_block_show(Array lines)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 2c1653006c..b134a899c9 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -956,7 +956,7 @@ theend:
   char *p = ccline.cmdbuff;
 
   if (ui_has(kUICmdline)) {
-    ui_call_cmdline_hide(ccline.level);
+    ui_call_cmdline_hide(ccline.level, s->gotesc);
     msg_ext_clear_later();
   }
   if (!cmd_silent) {
@@ -3422,8 +3422,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
   ui_call_cmdline_show(content, line->cmdpos,
                        cstr_as_string(charbuf),
                        cstr_as_string((line->cmdprompt)),
-                       line->cmdindent,
-                       line->level);
+                       line->cmdindent, line->level, line->hl_id);
   if (line->special_char) {
     charbuf[0] = line->special_char;
     ui_call_cmdline_special_char(cstr_as_string(charbuf),
@@ -4477,7 +4476,7 @@ static int open_cmdwin(void)
   invalidate_botline(curwin);
   if (ui_has(kUICmdline)) {
     ccline.redraw_state = kCmdRedrawNone;
-    ui_call_cmdline_hide(ccline.level);
+    ui_call_cmdline_hide(ccline.level, false);
   }
   redraw_later(curwin, UPD_SOME_VALID);
 
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
index 25cc46e260..2922fb5414 100644
--- a/test/functional/lua/ui_event_spec.lua
+++ b/test/functional/lua/ui_event_spec.lua
@@ -261,6 +261,7 @@ describe('vim.ui_attach', function()
         lled in a fast event context            |
         {1:~                                       }|
       ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 6 } },
@@ -278,6 +279,7 @@ describe('vim.ui_attach', function()
         Y)?                                     |
         {1:~                                       }|
       ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 18 } },
@@ -294,6 +296,7 @@ describe('vim.ui_attach', function()
         e mouse (q or empty cancels):           |
         {1:^~                                       }|
       ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'Select:\nOne\nTwo\n' } },
@@ -359,6 +362,7 @@ describe('vim.ui_attach', function()
         {9:back from ns: 1.}                        |
         {100:Press ENTER or type command to continue}^ |
       ]],
+      cmdline = { { abort = false } },
     })
     feed('')
     -- Also when scheduled
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 0221c1e0b0..63764a9b6c 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -91,25 +91,27 @@ local function test_cmdline(linegrid)
       {1:~                        }|*3
                                |
     ]],
+      cmdline = { { abort = true } },
     }
   end)
 
   it('works with input()', function()
     feed(':call input("input", "default")')
-    screen:expect {
+    screen:expect({
       grid = [[
-      ^                         |
-      {1:~                        }|*3
-                               |
-    ]],
+        ^                         |
+        {1:~                        }|*3
+                                 |
+      ]],
       cmdline = {
         {
-          prompt = 'input',
           content = { { 'default' } },
+          hl_id = 0,
           pos = 7,
+          prompt = 'input',
         },
       },
-    }
+    })
 
     feed('')
     screen:expect {
@@ -118,6 +120,7 @@ local function test_cmdline(linegrid)
       {1:~                        }|*3
                                |
     ]],
+      cmdline = { { abort = false } },
     }
   end)
 
@@ -210,6 +213,7 @@ local function test_cmdline(linegrid)
           content = { { 'xx3' } },
           pos = 3,
         },
+        { abort = false },
       },
     }
 
@@ -220,6 +224,7 @@ local function test_cmdline(linegrid)
       {1:~                        }|*3
                                |
     ]],
+      cmdline = { { abort = true } },
     }
   end)
 
@@ -294,6 +299,7 @@ local function test_cmdline(linegrid)
       {1:~                        }|*3
                                |
     ]],
+      cmdline = { { abort = false } },
     }
 
     -- Try once more, to check buffer is reinitialized. #8007
@@ -324,6 +330,7 @@ local function test_cmdline(linegrid)
       {1:~                        }|*3
                                |
     ]],
+      cmdline = { { abort = false } },
     }
   end)
 
@@ -353,6 +360,7 @@ local function test_cmdline(linegrid)
       {3:[Command Line]           }|
                                |
     ]],
+      cmdline = { { abort = false } },
     }
 
     -- nested cmdline
@@ -404,6 +412,7 @@ local function test_cmdline(linegrid)
       {3:[Command Line]           }|
                                |
     ]],
+      cmdline = { [2] = { abort = true } },
     }
 
     feed('')
@@ -452,6 +461,7 @@ local function test_cmdline(linegrid)
       cmdline = {
         {
           prompt = 'secret:',
+          hl_id = 0,
           content = { { '******' } },
           pos = 6,
         },
@@ -495,6 +505,7 @@ local function test_cmdline(linegrid)
       cmdline = {
         {
           prompt = '>',
+          hl_id = 0,
           content = {
             { '(', 30 },
             { 'a' },
@@ -797,11 +808,14 @@ local function test_cmdline(linegrid)
     -- This used to send an invalid event where pos where larger than the total
     -- length of content. Checked in _handle_cmdline_show.
     feed('')
-    screen:expect([[
-      ^                         |
-      {1:~                        }|*3
-                               |
-    ]])
+    screen:expect({
+      grid = [[
+        ^                         |
+        {1:~                        }|*3
+                                 |
+      ]],
+      cmdline = { { abort = true } },
+    })
   end)
 
   it('does not move cursor to curwin #20309', function()
@@ -827,6 +841,30 @@ local function test_cmdline(linegrid)
       } },
     }
   end)
+
+  it('show prompt hl_id', function()
+    screen:expect([[
+      ^                         |
+      {1:~                        }|*3
+                               |
+    ]])
+    feed(':echohl Error | call input("Prompt:")')
+    screen:expect({
+      grid = [[
+        ^                         |
+        {1:~                        }|*3
+                                 |
+      ]],
+      cmdline = {
+        {
+          content = { { '' } },
+          hl_id = 237,
+          pos = 0,
+          prompt = 'Prompt:',
+        },
+      },
+    })
+  end)
 end
 
 -- the representation of cmdline and cmdline_block contents changed with ext_linegrid
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 8f8795370f..be5b25e4be 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -47,6 +47,7 @@ describe('ui/ext_messages', function()
       line ^1                   |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { '\ntest\n[O]k: ', 6, 10 } },
@@ -75,6 +76,7 @@ describe('ui/ext_messages', function()
       line ^2                   |
       {1:~                        }|*3
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { '\ntest\n[O]k: ', 6, 10 } },
@@ -109,6 +111,7 @@ describe('ui/ext_messages', function()
       l{10:i}ne ^2                   |
       {1:~                        }|*3
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 18 } },
@@ -144,6 +147,7 @@ describe('ui/ext_messages', function()
       line 2                   |
       {1:~                        }|*3
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'search hit BOTTOM, continuing at TOP', 19, 26 } },
@@ -155,6 +159,7 @@ describe('ui/ext_messages', function()
     -- kind=emsg after :throw
     feed(':throw "foo"')
     screen:expect {
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'Error detected while processing :', 9, 6 } },
@@ -181,6 +186,7 @@ describe('ui/ext_messages', function()
       ^line 2                   |
       {1:~                        }|*3
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { '(2 of 2): line2' } },
@@ -197,6 +203,7 @@ describe('ui/ext_messages', function()
         line 2                   |
         {1:~                        }|*3
       ]],
+      cmdline = { { abort = false } },
       messages = { {
         content = { { '?line ' } },
         kind = 'search_cmd',
@@ -206,6 +213,7 @@ describe('ui/ext_messages', function()
     -- highlight
     feed(':filter character highlight')
     screen:expect({
+      cmdline = { { abort = false } },
       messages = {
         {
           content = {
@@ -276,6 +284,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = { {
         content = { { 'raa', 9, 6 } },
         kind = 'echoerr',
@@ -302,6 +311,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'bork', 9, 6 } },
@@ -324,6 +334,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'bork', 9, 6 } },
@@ -366,6 +377,7 @@ describe('ui/ext_messages', function()
       cmdline = {
         {
           prompt = 'foo> ',
+          hl_id = 0,
           content = { { '' } },
           pos = 0,
         },
@@ -378,6 +390,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
     }
     eq('solution', eval('x'))
 
@@ -387,6 +400,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       msg_history = {
         { kind = 'echoerr', content = { { 'raa', 9, 6 } } },
         { kind = 'echoerr', content = { { 'bork', 9, 6 } } },
@@ -419,6 +433,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'bork\nfail', 9, 6 } },
@@ -433,6 +448,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
@@ -459,6 +475,7 @@ describe('ui/ext_messages', function()
       {10:line} 2                   |
       {1:~                        }|*3
     ]],
+      cmdline = { { abort = false } },
       messages = {
         { content = { { '/line      W [1/2]' } }, kind = 'search_count' },
       },
@@ -485,6 +502,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         { content = { { 'x                     #1' } }, kind = 'list_cmd' },
         { content = { { 'y                     #2' } }, kind = 'list_cmd' },
@@ -595,6 +613,7 @@ describe('ui/ext_messages', function()
       alphpabe^t                |
       {1:~                        }|*2
     ]],
+      cmdline = { { abort = false } },
       msg_history = { {
         content = { { 'stuff' } },
         kind = 'echomsg',
@@ -793,6 +812,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = { {
         content = { { 'howdy' } },
         kind = 'echomsg',
@@ -821,6 +841,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = { {
         content = { { 'bork', 9, 6 } },
         kind = 'echoerr',
@@ -833,6 +854,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = { {
         content = { { 'xyz' } },
         kind = 'echo',
@@ -845,6 +867,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } },
@@ -859,6 +882,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       msg_history = {
         { kind = 'echomsg', content = { { 'howdy' } } },
         { kind = '', content = { { 'Type  :qa  and press  to exit Nvim' } } },
@@ -892,11 +916,14 @@ describe('ui/ext_messages', function()
     }
 
     feed('')
-    screen:expect([[
-      ^                         |
-      {1:~                        }|*3
-                               |
-    ]])
+    screen:expect({
+      grid = [[
+        ^                         |
+        {1:~                        }|*3
+                                 |
+      ]],
+      cmdline = { { abort = false } },
+    })
     eq(1, eval('&cmdheight'))
 
     feed(':set cmdheight=0')
@@ -915,10 +942,15 @@ describe('ui/ext_messages', function()
       },
     }
     feed('')
-    screen:expect([[
-      ^                         |
-      {1:~                        }|*4
-    ]])
+    screen:expect({
+      grid = [[
+        ^                         |
+        {1:~                        }|*4
+      ]],
+      cmdline = { {
+        abort = false
+      } },
+    })
     eq(0, eval('&cmdheight'))
   end)
 
@@ -929,6 +961,7 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = {
@@ -957,6 +990,7 @@ stack traceback:
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = {
@@ -981,6 +1015,7 @@ stack traceback:
     feed(':map')
 
     screen:expect {
+      cmdline = { { abort = false } },
       messages = {
         {
           content = {
@@ -1101,6 +1136,7 @@ stack traceback:
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         { content = { { '\n  1 %a   "[No Name]"                    line 1' } }, kind = 'list_cmd' },
       },
@@ -1112,6 +1148,7 @@ stack traceback:
       ^                         |
       {1:~                        }|*4
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
@@ -1853,6 +1890,7 @@ describe('ui/ext_messages', function()
                        type  :help iccf{18:}       for information                  |
                                                                                       |*5
     ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
@@ -1935,6 +1973,7 @@ describe('ui/ext_messages', function()
       {1:~                                                                               }|*10
       {3:[No Name]                                                                       }|
     ]],
+      cmdline = { { abort = false } },
       messages = {
         { content = { { '  cmdheight=0' } }, kind = 'list_cmd' },
       },
@@ -1951,6 +1990,7 @@ describe('ui/ext_messages', function()
       {1:~                                                                               }|*9
       {3:[No Name]                                                                       }|
     ]],
+      cmdline = { { abort = false } },
       messages = {
         { content = { { '  laststatus=3' } }, kind = 'list_cmd' },
       },
@@ -1971,6 +2011,7 @@ describe('ui/ext_messages', function()
       {1:~                                                                               }|*10
       {3:[No Name]                                                                       }|
     ]],
+      cmdline = { { abort = false } },
       messages = {
         { content = { { '  cmdheight=0' } }, kind = 'list_cmd' },
       },
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 42734d07ca..8c050195ee 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -79,6 +79,7 @@ end
 --- @field win_position table>
 --- @field float_pos table
 --- @field cmdline table
+--- @field cmdline_hide_level integer?
 --- @field cmdline_block table[]
 --- @field hl_groups table
 --- @field messages table
@@ -654,6 +655,12 @@ screen:redraw_debug() to show all intermediate screen states.]]
       end
     end
 
+    -- Only test the abort state of a cmdline level once.
+    if self.cmdline_hide_level ~= nil then
+      self.cmdline[self.cmdline_hide_level] = nil
+      self.cmdline_hide_level = nil
+    end
+
     if expected.hl_groups ~= nil then
       for name, id in pairs(expected.hl_groups) do
         local expected_hl = attr_state.ids[id]
@@ -1296,7 +1303,7 @@ function Screen:_handle_popupmenu_hide()
   self.popupmenu = nil
 end
 
-function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level)
+function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level, hl_id)
   if firstc == '' then
     firstc = nil
   end
@@ -1320,11 +1327,13 @@ function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level
     firstc = firstc,
     prompt = prompt,
     indent = indent,
+    hl_id = prompt and hl_id,
   }
 end
 
-function Screen:_handle_cmdline_hide(level)
-  self.cmdline[level] = nil
+function Screen:_handle_cmdline_hide(level, abort)
+  self.cmdline[level] = { abort = abort }
+  self.cmdline_hide_level = level
 end
 
 function Screen:_handle_cmdline_special_char(char, shift, level)
@@ -1468,7 +1477,9 @@ function Screen:_extstate_repr(attr_state)
   local cmdline = {}
   for i, entry in pairs(self.cmdline) do
     entry = shallowcopy(entry)
-    entry.content = self:_chunks_repr(entry.content, attr_state)
+    if entry.content ~= nil then
+      entry.content = self:_chunks_repr(entry.content, attr_state)
+    end
     cmdline[i] = entry
   end
 
-- 
cgit 


From d1e00a5f6dce9cf1fa80f613d4d27019966e5a79 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Sun, 22 Dec 2024 15:42:48 +0100
Subject: fix(messages): typo and unwanted truncation in msg_outtrans_long
 #31669

- Typo/bug in msg_outtrans_long passing string length as "hist" argument.
- Avoid truncating message in msg_outtrans_long with ext_messages (followup to
  1097d239c307a10a87fa995c4cfbe5987939e177).
- Remove `_hl` from `msg_keep`, `smsg_keep` as there is no non-`_hl` variant.
- `msg_printf_hl` is removed (identical to `smsg` except it sets
  `msg_scroll = true`, seemingly as a caveat to force a more prompt in
  cmdline mode). Move this logic to the only the only place this was
  used in ex_getln.c.
---
 src/nvim/ex_getln.c |  3 ++-
 src/nvim/message.c  | 41 ++++++++++-------------------------------
 src/nvim/ops.c      |  2 +-
 src/nvim/quickfix.c |  2 +-
 src/nvim/undo.c     | 12 ++++++------
 5 files changed, 20 insertions(+), 40 deletions(-)

diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 2c1653006c..09d4c88dcd 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -3142,8 +3142,9 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
 
 #define PRINT_ERRMSG(...) \
   do { \
+    msg_scroll = true; \
     msg_putchar('\n'); \
-    msg_printf_hl(HLF_E, __VA_ARGS__); \
+    smsg(HLF_E, __VA_ARGS__); \
     printed_errmsg = true; \
   } while (0)
   bool ret = true;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 1c46194a1c..a654395455 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -130,7 +130,7 @@ bool keep_msg_more = false;    // keep_msg was set by msgmore()
 // msg_scrolled     How many lines the screen has been scrolled (because of
 //                  messages).  Used in update_screen() to scroll the screen
 //                  back.  Incremented each time the screen scrolls a line.
-// msg_scrolled_ign  true when msg_scrolled is non-zero and msg_puts_hl_id()
+// msg_scrolled_ign  true when msg_scrolled is non-zero and msg_puts_hl()
 //                  writes something without scrolling should not make
 //                  need_wait_return to be set.  This is a hack to make ":ts"
 //                  work without an extra prompt.
@@ -244,7 +244,7 @@ void msg_grid_validate(void)
 int verb_msg(const char *s)
 {
   verbose_enter();
-  int n = msg_hl_keep(s, 0, false, false);
+  int n = msg_keep(s, 0, false, false);
   verbose_leave();
 
   return n;
@@ -257,7 +257,7 @@ int verb_msg(const char *s)
 bool msg(const char *s, const int hl_id)
   FUNC_ATTR_NONNULL_ARG(1)
 {
-  return msg_hl_keep(s, hl_id, false, false);
+  return msg_keep(s, hl_id, false, false);
 }
 
 /// Similar to msg_outtrans_len, but support newlines and tabs.
@@ -309,7 +309,7 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history)
 }
 
 /// @param keep set keep_msg if it doesn't scroll
-bool msg_hl_keep(const char *s, int hl_id, bool keep, bool multiline)
+bool msg_keep(const char *s, int hl_id, bool keep, bool multiline)
   FUNC_ATTR_NONNULL_ALL
 {
   static int entered = 0;
@@ -514,7 +514,7 @@ int smsg(int hl_id, const char *s, ...)
   return msg(IObuff, hl_id);
 }
 
-int smsg_hl_keep(int hl_id, const char *s, ...)
+int smsg_keep(int hl_id, const char *s, ...)
   FUNC_ATTR_PRINTF(2, 3)
 {
   va_list arglist;
@@ -522,7 +522,7 @@ int smsg_hl_keep(int hl_id, const char *s, ...)
   va_start(arglist, s);
   vim_vsnprintf(IObuff, IOSIZE, s, arglist);
   va_end(arglist);
-  return msg_hl_keep(IObuff, hl_id, true, false);
+  return msg_keep(IObuff, hl_id, true, false);
 }
 
 // Remember the last sourcing name/lnum used in an error message, so that it
@@ -767,7 +767,7 @@ bool emsg_multiline(const char *s, bool multiline)
 
   // Display the error message itself.
   msg_nowait = false;  // Wait for this msg.
-  return msg_hl_keep(s, hl_id, false, multiline);
+  return msg_keep(s, hl_id, false, multiline);
 }
 
 /// emsg() - display an error message
@@ -1193,7 +1193,7 @@ void ex_messages(exarg_T *eap)
       if (kv_size(p->multihl)) {
         msg_multihl(p->multihl, p->kind, false);
       } else if (p->msg != NULL) {
-        msg_hl_keep(p->msg, p->hl_id, false, p->multiline);
+        msg_keep(p->msg, p->hl_id, false, p->multiline);
       }
     }
     msg_hist_off = false;
@@ -2103,12 +2103,12 @@ void msg_outtrans_long(const char *longstr, int hl_id)
   int len = (int)strlen(longstr);
   int slen = len;
   int room = Columns - msg_col;
-  if (len > room && room >= 20) {
+  if (!ui_has(kUIMessages) && len > room && room >= 20) {
     slen = (room - 3) / 2;
     msg_outtrans_len(longstr, slen, hl_id, false);
     msg_puts_hl("...", HLF_8, false);
   }
-  msg_outtrans_len(longstr + len - slen, slen, hl_id, len);
+  msg_outtrans_len(longstr + len - slen, slen, hl_id, false);
 }
 
 /// Basic function for writing a message with highlight id.
@@ -2178,27 +2178,6 @@ void msg_puts_len(const char *const str, const ptrdiff_t len, int hl_id, bool hi
   need_fileinfo = false;
 }
 
-/// Print a formatted message
-///
-/// Message printed is limited by #IOSIZE. Must not be used from inside
-/// msg_puts_hl_id().
-///
-/// @param[in]  hl_id  Highlight id.
-/// @param[in]  fmt  Format string.
-void msg_printf_hl(const int hl_id, const char *const fmt, ...)
-  FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PRINTF(2, 3)
-{
-  static char msgbuf[IOSIZE];
-
-  va_list ap;
-  va_start(ap, fmt);
-  const size_t len = (size_t)vim_vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
-  va_end(ap);
-
-  msg_scroll = true;
-  msg_puts_len(msgbuf, (ptrdiff_t)len, hl_id, true);
-}
-
 static void msg_ext_emit_chunk(void)
 {
   if (msg_ext_chunks == NULL) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 71a005bd24..b508c65662 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -258,7 +258,7 @@ void op_shift(oparg_T *oap, bool curs_top, int amount)
     vim_snprintf(IObuff, IOSIZE,
                  NGETTEXT(msg_line_single, msg_line_plural, oap->line_count),
                  (int64_t)oap->line_count, op, amount);
-    msg_hl_keep(IObuff, 0, true, false);
+    msg_keep(IObuff, 0, true, false);
   }
 
   if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 76c794b5a9..e9e0228ec4 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -2937,7 +2937,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
     msg_scroll = false;
   }
   msg_ext_set_kind("quickfix");
-  msg_hl_keep(gap->ga_data, 0, true, false);
+  msg_keep(gap->ga_data, 0, true, false);
   msg_scroll = (int)i;
 
   qfga_clear();
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index d523e55640..45401ca5e1 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2596,12 +2596,12 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
     check_pos(curbuf, &VIsual);
   }
 
-  smsg_hl_keep(0, _("%" PRId64 " %s; %s #%" PRId64 "  %s"),
-               u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount,
-               _(msgstr),
-               did_undo ? _("before") : _("after"),
-               uhp == NULL ? 0 : (int64_t)uhp->uh_seq,
-               msgbuf);
+  smsg_keep(0, _("%" PRId64 " %s; %s #%" PRId64 "  %s"),
+            u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount,
+            _(msgstr),
+            did_undo ? _("before") : _("after"),
+            uhp == NULL ? 0 : (int64_t)uhp->uh_seq,
+            msgbuf);
 }
 
 /// Put the timestamp of an undo header in "buf[buflen]" in a nice format.
-- 
cgit 


From 665a0e85c4788cb2847e270c333c0aee306f07ad Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 22 Dec 2024 15:15:57 +0100
Subject: vim-patch:9.1.0953: filetype: APKBUILD files not correctly detected

Problem:  filetype: APKBUILD files not correctly detected
Solution: detect 'APKBUILD' files as apkbuild filetype,
          include a apkbuild syntax script (which basically
          just sources the sh.vim syntax file)
          (Hugo Osvaldo Barrera)

Vim plugins (e.g.: ALE, nvim-lspconfig, etc) rely on filetype to
determine which integrations/helpers are applicable. They expect
filetype=apkbuild for APKBUILD files.

On the other hand, plugins also enable bash-specific linters and
functionality when filetype=bash, but APKBUILD files are POSIX sh, not
bash, so these often provide bogus results.

Change the filetype for APKBUILD to a 'apkbuild', so that tools and
ftplugin can properly target these files. This filetype will use the
existing `sh` syntax rules, since these are applicable for them.

https://github.com/vim/vim/commit/7cb24917a112ba473cb351bdcdc48b8adbd46793

Co-authored-by: Hugo Osvaldo Barrera' via vim_dev 
---
 runtime/lua/vim/filetype.lua       |  2 +-
 runtime/syntax/apkbuild.vim        | 17 +++++++++++++++++
 test/old/testdir/test_filetype.vim |  3 ++-
 3 files changed, 20 insertions(+), 2 deletions(-)
 create mode 100644 runtime/syntax/apkbuild.vim

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 31de25a714..6fa1684704 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1439,6 +1439,7 @@ local filename = {
   ['/etc/asound.conf'] = 'alsaconf',
   ['build.xml'] = 'ant',
   ['.htaccess'] = 'apache',
+  APKBUILD = 'apkbuild',
   ['apt.conf'] = 'aptconf',
   ['/.aptitude/config'] = 'aptconf',
   ['=tagging-method'] = 'arch',
@@ -1798,7 +1799,6 @@ local filename = {
   ['.kshrc'] = detect.ksh,
   ['.profile'] = detect.sh,
   ['/etc/profile'] = detect.sh,
-  APKBUILD = detect.bash,
   PKGBUILD = detect.bash,
   ['.tcshrc'] = detect.tcsh,
   ['tcsh.login'] = detect.tcsh,
diff --git a/runtime/syntax/apkbuild.vim b/runtime/syntax/apkbuild.vim
new file mode 100644
index 0000000000..f969ff0e2e
--- /dev/null
+++ b/runtime/syntax/apkbuild.vim
@@ -0,0 +1,17 @@
+" Vim syntax file
+" Language:	apkbuild
+" Maintainer:	The Vim Project 
+" Last Change:	2024 Dec 22
+
+" quit when a syntax file was already loaded
+if exists("b:current_syntax")
+  finish
+endif
+
+" The actual syntax is in sh.vim and controlled by buffer-local variables.
+unlet! b:is_bash b:is_kornshell
+let b:is_sh = 1
+
+runtime! syntax/sh.vim
+
+let b:current_syntax = 'apkbuild'
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 7b7e3bdbfb..eb4d14c800 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -108,6 +108,7 @@ func s:GetFilenameChecks() abort
     \            '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'],
     \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file',
     \                 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'],
+    \ 'apkbuild': ['APKBUILD'],
     \ 'applescript': ['file.scpt'],
     \ 'aptconf': ['apt.conf', '/.aptitude/config', 'any/.aptitude/config'],
     \ 'arch': ['.arch-inventory', '=tagging-method'],
@@ -680,7 +681,7 @@ func s:GetFilenameChecks() abort
     \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
     \ 'sexplib': ['file.sexp'],
     \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '.bash_history', '.bash-history',
-    \        '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'APKBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh',
+    \        '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh',
     \        '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', 'file.bats', '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile',
     \        'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport', '.env', '.envrc', 'devscripts.conf',
     \        '.devscripts', 'file.lo', 'file.la', 'file.lai'],
-- 
cgit 


From a10636fbe7bb4dba45c42c64548e5e32fe8f8d12 Mon Sep 17 00:00:00 2001
From: Luuk van Baal 
Date: Sun, 22 Dec 2024 13:21:57 +0100
Subject: feat(ui): specify whether msg_show event is added to history

Pass along whether message in msg_show event is added to the internal
:messages history.
---
 runtime/doc/news.txt                  |  12 +--
 runtime/doc/ui.txt                    |   5 +-
 src/nvim/api/ui_events.in.h           |   2 +-
 src/nvim/message.c                    |  12 ++-
 test/functional/lua/ui_event_spec.lua |   7 ++
 test/functional/ui/messages_spec.lua  | 161 +++++++++++++++++++++++++---------
 test/functional/ui/screen.lua         |  10 ++-
 7 files changed, 153 insertions(+), 56 deletions(-)

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 19c4e3b51d..8bfca39552 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -86,11 +86,13 @@ EVENTS
 
 • |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in
   |api-fast| context.
-• Various additions for the following UI events:
-  • `cmdline_show`: `hl_id` to highlight the prompt text.
-  • `cmdline_hide`: `abort` indicating if the cmdline was aborted.
-  • `msg_show`: new message kinds: "bufwrite", "completion", "list_cmd",
-    "lua_print", "number_prompt", "search_cmd", "undo", "wildlist".
+• New/enhanced arguments in these existing UI events:
+  • `cmdline_show`: `hl_id` argument to highlight the prompt text.
+  • `cmdline_hide`: `abort` argument indicating if the cmdline was aborted.
+  • `msg_show`:
+    • `history` argument indicating if the message was added to the history.
+    • new message kinds: "bufwrite", "completion", "list_cmd",
+      "lua_print", "number_prompt", "search_cmd", "undo", "wildlist".
 
 HIGHLIGHTS
 
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 0082e9d76b..1b11565eeb 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -784,7 +784,7 @@ will be set to zero, but can be changed and used for the replacing cmdline or
 message window. Cmdline state is emitted as |ui-cmdline| events, which the UI
 must handle.
 
-["msg_show", kind, content, replace_last] ~
+["msg_show", kind, content, replace_last, history] ~
 	Display a message to the user.
 
 	kind
@@ -827,6 +827,9 @@ must handle.
 	    true:  Replace the message in the most-recent `msg_show` call,
 		   but any other visible message should still remain.
 
+	history
+	    True if the message was added to the |:messages| history.
+
 ["msg_clear"] ~
 	Clear all messages currently displayed by "msg_show". (Messages sent
 	by other "msg_" events below will not be affected).
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 603f9b2005..74718e7ac5 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -158,7 +158,7 @@ void wildmenu_select(Integer selected)
 void wildmenu_hide(void)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
 
-void msg_show(String kind, Array content, Boolean replace_last)
+void msg_show(String kind, Array content, Boolean replace_last, Boolean history)
   FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY;
 void msg_clear(void)
   FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 1c46194a1c..c37232e4bc 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -153,6 +153,7 @@ static sattr_T msg_ext_last_attr = -1;
 static int msg_ext_last_hl_id;
 static size_t msg_ext_cur_len = 0;
 
+static bool msg_ext_history = false;  ///< message was added to history
 static bool msg_ext_overwrite = false;  ///< will overwrite last message
 static int msg_ext_visible = 0;  ///< number of messages currently visible
 
@@ -988,7 +989,7 @@ static void add_msg_hist(const char *s, int len, int hl_id, bool multiline)
 static void add_msg_hist_multihl(const char *s, int len, int hl_id, bool multiline,
                                  HlMessage multihl)
 {
-  if (msg_hist_off || msg_silent != 0) {
+  if (msg_hist_off || msg_silent != 0 || (s != NULL && *s == NUL)) {
     hl_msg_free(multihl);
     return;
   }
@@ -999,12 +1000,13 @@ static void add_msg_hist_multihl(const char *s, int len, int hl_id, bool multili
     if (len < 0) {
       len = (int)strlen(s);
     }
+    assert(len > 0);
     // remove leading and trailing newlines
-    while (len > 0 && *s == '\n') {
+    while (*s == '\n') {
       s++;
       len--;
     }
-    while (len > 0 && s[len - 1] == '\n') {
+    while (s[len - 1] == '\n') {
       len--;
     }
     p->msg = xmemdupz(s, (size_t)len);
@@ -1024,6 +1026,7 @@ static void add_msg_hist_multihl(const char *s, int len, int hl_id, bool multili
     first_msg_hist = last_msg_hist;
   }
   msg_hist_len++;
+  msg_ext_history = true;
 
   check_msg_hist();
 }
@@ -3159,13 +3162,14 @@ void msg_ext_ui_flush(void)
   msg_ext_emit_chunk();
   if (msg_ext_chunks->size > 0) {
     Array *tofree = msg_ext_init_chunks();
-    ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite);
+    ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history);
     api_free_array(*tofree);
     xfree(tofree);
     if (!msg_ext_overwrite) {
       msg_ext_visible++;
     }
     msg_ext_overwrite = false;
+    msg_ext_history = false;
     msg_ext_kind = NULL;
   }
 }
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
index 2922fb5414..7e890e8ae0 100644
--- a/test/functional/lua/ui_event_spec.lua
+++ b/test/functional/lua/ui_event_spec.lua
@@ -265,6 +265,7 @@ describe('vim.ui_attach', function()
       messages = {
         {
           content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 6 } },
+          history = true,
           kind = 'emsg',
         },
       },
@@ -283,6 +284,7 @@ describe('vim.ui_attach', function()
       messages = {
         {
           content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 18 } },
+          history = true,
           kind = 'confirm_sub',
         },
       },
@@ -300,10 +302,12 @@ describe('vim.ui_attach', function()
       messages = {
         {
           content = { { 'Select:\nOne\nTwo\n' } },
+          history = false,
           kind = 'list_cmd',
         },
         {
           content = { { 'Type number and  or click with the mouse (q or empty cancels): ' } },
+          history = false,
           kind = 'number_prompt',
         },
       },
@@ -382,6 +386,7 @@ describe('vim.ui_attach', function()
               6,
             },
           },
+          history = true,
           kind = 'lua_error',
         },
         {
@@ -392,10 +397,12 @@ describe('vim.ui_attach', function()
               6,
             },
           },
+          history = true,
           kind = 'lua_error',
         },
         {
           content = { { 'Press ENTER or type command to continue', 100, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index be5b25e4be..8acf8495c6 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -51,6 +51,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { '\ntest\n[O]k: ', 6, 10 } },
+          history = false,
           kind = 'confirm',
         },
       },
@@ -80,6 +81,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { '\ntest\n[O]k: ', 6, 10 } },
+          history = false,
           kind = 'confirm',
         },
       },
@@ -89,14 +91,17 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { '\ntest\n[O]k: ', 6, 10 } },
+          history = false,
           kind = 'confirm',
         },
         {
           content = { { '1' } },
+          history = false,
           kind = 'echo',
         },
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -115,6 +120,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 18 } },
+          history = true,
           kind = 'confirm_sub',
         },
       },
@@ -134,6 +140,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'W10: Warning: Changing a readonly file', 19, 26 } },
+          history = true,
           kind = 'wmsg',
         },
       },
@@ -151,6 +158,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'search hit BOTTOM, continuing at TOP', 19, 26 } },
+          history = true,
           kind = 'wmsg',
         },
       },
@@ -163,14 +171,17 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Error detected while processing :', 9, 6 } },
+          history = true,
           kind = 'emsg',
         },
         {
           content = { { 'E605: Exception not caught: foo', 9, 6 } },
+          history = true,
           kind = 'emsg',
         },
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -190,6 +201,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { '(2 of 2): line2' } },
+          history = true,
           kind = 'quickfix',
         },
       },
@@ -204,10 +216,13 @@ describe('ui/ext_messages', function()
         {1:~                        }|*3
       ]],
       cmdline = { { abort = false } },
-      messages = { {
-        content = { { '?line ' } },
-        kind = 'search_cmd',
-      } },
+      messages = {
+        {
+          content = { { '?line ' } },
+          history = false,
+          kind = 'search_cmd',
+        },
+      },
     })
 
     -- highlight
@@ -227,6 +242,7 @@ describe('ui/ext_messages', function()
             { 'links to', 18, 5 },
             { ' SpecialChar' },
           },
+          history = false,
           kind = 'list_cmd',
         },
       },
@@ -242,6 +258,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Already at oldest change' } },
+          history = true,
           kind = 'undo',
         },
       },
@@ -257,6 +274,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Already at newest change' } },
+          history = true,
           kind = 'undo',
         },
       },
@@ -269,6 +287,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'The only match' } },
+          history = false,
           kind = 'completion',
         },
       },
@@ -285,10 +304,13 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       cmdline = { { abort = false } },
-      messages = { {
-        content = { { 'raa', 9, 6 } },
-        kind = 'echoerr',
-      } },
+      messages = {
+        {
+          content = { { 'raa', 9, 6 } },
+          history = true,
+          kind = 'echoerr',
+        },
+      },
     }
 
     -- cmdline in a later input cycle clears error message
@@ -315,14 +337,17 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'bork', 9, 6 } },
+          history = true,
           kind = 'echoerr',
         },
         {
           content = { { 'fail', 9, 6 } },
+          history = true,
           kind = 'echoerr',
         },
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -338,18 +363,22 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'bork', 9, 6 } },
+          history = true,
           kind = 'echoerr',
         },
         {
           content = { { 'fail', 9, 6 } },
+          history = true,
           kind = 'echoerr',
         },
         {
           content = { { 'extrafail', 9, 6 } },
+          history = true,
           kind = 'echoerr',
         },
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -370,10 +399,13 @@ describe('ui/ext_messages', function()
       ^                         |
       {1:~                        }|*4
     ]],
-      messages = { {
-        content = { { 'problem', 9, 6 } },
-        kind = 'echoerr',
-      } },
+      messages = {
+        {
+          content = { { 'problem', 9, 6 } },
+          history = true,
+          kind = 'echoerr',
+        },
+      },
       cmdline = {
         {
           prompt = 'foo> ',
@@ -411,6 +443,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -437,6 +470,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'bork\nfail', 9, 6 } },
+          history = true,
           kind = 'echoerr',
         },
       },
@@ -452,6 +486,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -477,7 +512,7 @@ describe('ui/ext_messages', function()
     ]],
       cmdline = { { abort = false } },
       messages = {
-        { content = { { '/line      W [1/2]' } }, kind = 'search_count' },
+        { content = { { '/line      W [1/2]' } }, kind = 'search_count', history = false },
       },
     }
 
@@ -489,7 +524,7 @@ describe('ui/ext_messages', function()
       {1:~                        }|*3
     ]],
       messages = {
-        { content = { { '/line        [2/2]' } }, kind = 'search_count' },
+        { content = { { '/line        [2/2]' } }, kind = 'search_count', history = false },
       },
     }
   end)
@@ -504,10 +539,11 @@ describe('ui/ext_messages', function()
     ]],
       cmdline = { { abort = false } },
       messages = {
-        { content = { { 'x                     #1' } }, kind = 'list_cmd' },
-        { content = { { 'y                     #2' } }, kind = 'list_cmd' },
+        { content = { { 'x                     #1' } }, kind = 'list_cmd', history = false },
+        { content = { { 'y                     #2' } }, kind = 'list_cmd', history = false },
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -578,10 +614,13 @@ describe('ui/ext_messages', function()
         items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } },
         pos = 1,
       },
-      messages = { {
-        content = { { 'stuff' } },
-        kind = 'echomsg',
-      } },
+      messages = {
+        {
+          content = { { 'stuff' } },
+          history = true,
+          kind = 'echomsg',
+        },
+      },
       showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } },
     }
 
@@ -598,10 +637,13 @@ describe('ui/ext_messages', function()
         items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } },
         pos = 0,
       },
-      messages = { {
-        content = { { 'stuff' } },
-        kind = 'echomsg',
-      } },
+      messages = {
+        {
+          content = { { 'stuff' } },
+          history = true,
+          kind = 'echomsg',
+        },
+      },
       showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 2 of 2', 6, 18 } },
     }
 
@@ -621,6 +663,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -813,10 +856,13 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       cmdline = { { abort = false } },
-      messages = { {
-        content = { { 'howdy' } },
-        kind = 'echomsg',
-      } },
+      messages = {
+        {
+          content = { { 'howdy' } },
+          history = true,
+          kind = 'echomsg',
+        },
+      },
     }
 
     -- always test a message without kind. If this one gets promoted to a
@@ -830,6 +876,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Type  :qa  and press  to exit Nvim' } },
+          history = true,
           kind = '',
         },
       },
@@ -842,10 +889,13 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       cmdline = { { abort = false } },
-      messages = { {
-        content = { { 'bork', 9, 6 } },
-        kind = 'echoerr',
-      } },
+      messages = {
+        {
+          content = { { 'bork', 9, 6 } },
+          history = true,
+          kind = 'echoerr',
+        },
+      },
     }
 
     feed(':echo "xyz"')
@@ -855,10 +905,13 @@ describe('ui/ext_messages', function()
       {1:~                        }|*4
     ]],
       cmdline = { { abort = false } },
-      messages = { {
-        content = { { 'xyz' } },
-        kind = 'echo',
-      } },
+      messages = {
+        {
+          content = { { 'xyz' } },
+          history = false,
+          kind = 'echo',
+        },
+      },
     }
 
     feed(':call nosuchfunction()')
@@ -871,6 +924,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } },
+          history = true,
           kind = 'emsg',
         },
       },
@@ -892,6 +946,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -948,7 +1003,7 @@ describe('ui/ext_messages', function()
         {1:~                        }|*4
       ]],
       cmdline = { {
-        abort = false
+        abort = false,
       } },
     })
     eq(0, eval('&cmdheight'))
@@ -976,6 +1031,7 @@ stack traceback:
               6,
             },
           },
+          history = true,
           kind = 'lua_error',
         },
       },
@@ -996,6 +1052,7 @@ stack traceback:
           content = {
             { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 6 },
           },
+          history = true,
           kind = 'rpc_error',
         },
       },
@@ -1023,6 +1080,7 @@ stack traceback:
             { '*', 18, 1 },
             { ' k' },
           },
+          history = false,
           kind = 'list_cmd',
         },
       },
@@ -1043,6 +1101,7 @@ stack traceback:
       messages = {
         {
           content = { { 'wildmenu  wildmode' } },
+          history = false,
           kind = 'wildlist',
         },
       },
@@ -1070,10 +1129,12 @@ stack traceback:
       messages = {
         {
           content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } },
+          history = false,
           kind = 'list_cmd',
         },
         {
           content = { { 'Type number and  or click with the mouse (q or empty cancels): ' } },
+          history = false,
           kind = 'number_prompt',
         },
       },
@@ -1089,14 +1150,17 @@ stack traceback:
       messages = {
         {
           content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } },
+          history = false,
           kind = 'list_cmd',
         },
         {
           content = { { 'Type number and  or click with the mouse (q or empty cancels): ' } },
+          history = false,
           kind = 'number_prompt',
         },
         {
           content = { { '1' } },
+          history = false,
           kind = '',
         },
       },
@@ -1125,6 +1189,7 @@ stack traceback:
       messages = {
         {
           content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } },
+          history = true,
           kind = 'echomsg',
         },
       },
@@ -1138,7 +1203,11 @@ stack traceback:
     ]],
       cmdline = { { abort = false } },
       messages = {
-        { content = { { '\n  1 %a   "[No Name]"                    line 1' } }, kind = 'list_cmd' },
+        {
+          content = { { '\n  1 %a   "[No Name]"                    line 1' } },
+          kind = 'list_cmd',
+          history = false,
+        },
       },
     }
 
@@ -1152,6 +1221,7 @@ stack traceback:
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -1176,7 +1246,11 @@ stack traceback:
     command('write ' .. fname)
     screen:expect({
       messages = {
-        { content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = 'bufwrite' },
+        {
+          content = { { string.format('"%s" [New] 0L, 0B written', fname) } },
+          kind = 'bufwrite',
+          history = true,
+        },
       },
     })
   end)
@@ -1203,6 +1277,7 @@ stack traceback:
       messages = {
         {
           content = { { 'foo\nbar\nbaz' } },
+          history = true,
           kind = 'lua_print',
         },
       },
@@ -1216,6 +1291,7 @@ stack traceback:
       messages = {
         {
           content = { { '{\n  foo = "bar"\n}' } },
+          history = true,
           kind = 'lua_print',
         },
       },
@@ -1894,6 +1970,7 @@ describe('ui/ext_messages', function()
       messages = {
         {
           content = { { 'Press ENTER or type command to continue', 6, 18 } },
+          history = false,
           kind = 'return_prompt',
         },
       },
@@ -1975,7 +2052,7 @@ describe('ui/ext_messages', function()
     ]],
       cmdline = { { abort = false } },
       messages = {
-        { content = { { '  cmdheight=0' } }, kind = 'list_cmd' },
+        { content = { { '  cmdheight=0' } }, kind = 'list_cmd', history = false },
       },
     })
 
@@ -1992,7 +2069,7 @@ describe('ui/ext_messages', function()
     ]],
       cmdline = { { abort = false } },
       messages = {
-        { content = { { '  laststatus=3' } }, kind = 'list_cmd' },
+        { content = { { '  laststatus=3' } }, kind = 'list_cmd', history = false },
       },
     })
 
@@ -2013,7 +2090,7 @@ describe('ui/ext_messages', function()
     ]],
       cmdline = { { abort = false } },
       messages = {
-        { content = { { '  cmdheight=0' } }, kind = 'list_cmd' },
+        { content = { { '  cmdheight=0' } }, kind = 'list_cmd', history = false },
       },
     })
   end)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 8c050195ee..6a8e7df6a0 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -1369,12 +1369,12 @@ function Screen:_handle_wildmenu_hide()
   self.wildmenu_items, self.wildmenu_pos = nil, nil
 end
 
-function Screen:_handle_msg_show(kind, chunks, replace_last)
+function Screen:_handle_msg_show(kind, chunks, replace_last, history)
   local pos = #self.messages
   if not replace_last or pos == 0 then
     pos = pos + 1
   end
-  self.messages[pos] = { kind = kind, content = chunks }
+  self.messages[pos] = { kind = kind, content = chunks, history = history }
 end
 
 function Screen:_handle_msg_clear()
@@ -1490,7 +1490,11 @@ function Screen:_extstate_repr(attr_state)
 
   local messages = {}
   for i, entry in ipairs(self.messages) do
-    messages[i] = { kind = entry.kind, content = self:_chunks_repr(entry.content, attr_state) }
+    messages[i] = {
+      kind = entry.kind,
+      content = self:_chunks_repr(entry.content, attr_state),
+      history = entry.history,
+    }
   end
 
   local msg_history = {}
-- 
cgit 


From adcd9360dfefc7b1e1edb0e86df460e074991c8d Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 23 Dec 2024 11:12:58 +0100
Subject: vim-patch:4ce1cb5: runtime(graphql): contribute vim-graphql to Vim
 core

Contribute the core of my vim-graphql project (ftplugin, indent, syntax)
to the Vim project. This replaces the basic ftplugin support that was
already in the runtime with a more complete set of filetype settings. I
can assume maintainership for all of these files.

I'll continue to maintain the higher-level embedded filetype support
separately (in vim-graphql) for now, because it's fairly complex, but we
can consider integrating that code directly into vim later.

runtime files use the MIT license.

closes: vim/vim#16273

https://github.com/vim/vim/commit/4ce1cb5bf1dc507224792543d8e56e6ab431a2b5

Co-authored-by: Jon Parise 
---
 runtime/ftplugin/graphql.vim | 17 ++++++--
 runtime/indent/graphql.vim   | 92 ++++++++++++++++++++++++++++++++++++++++++++
 runtime/syntax/graphql.vim   | 90 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 195 insertions(+), 4 deletions(-)
 create mode 100644 runtime/indent/graphql.vim
 create mode 100644 runtime/syntax/graphql.vim

diff --git a/runtime/ftplugin/graphql.vim b/runtime/ftplugin/graphql.vim
index 56f6e36e20..1717ebf2cc 100644
--- a/runtime/ftplugin/graphql.vim
+++ b/runtime/ftplugin/graphql.vim
@@ -1,13 +1,22 @@
 " Vim filetype plugin
 " Language:	graphql
-" Maintainer:	Riley Bruins 
-" Last Change:	2024 May 18
+" Maintainer:	Jon Parise 
+" Filenames:	*.graphql *.graphqls *.gql
+" URL:		https://github.com/jparise/vim-graphql
+" License:	MIT 
+" Last Change:	2024 Dec 21
 
 if exists('b:did_ftplugin')
   finish
 endif
 let b:did_ftplugin = 1
 
-setl comments=:# commentstring=#\ %s
+setlocal comments=:#
+setlocal commentstring=#\ %s
+setlocal formatoptions-=t
+setlocal iskeyword+=$,@-@
+setlocal softtabstop=2
+setlocal shiftwidth=2
+setlocal expandtab
 
-let b:undo_ftplugin = 'setl com< cms<'
+let b:undo_ftplugin = 'setlocal com< cms< fo< isk< sts< sw< et<'
diff --git a/runtime/indent/graphql.vim b/runtime/indent/graphql.vim
new file mode 100644
index 0000000000..dc0769b9a8
--- /dev/null
+++ b/runtime/indent/graphql.vim
@@ -0,0 +1,92 @@
+" Vim indent file
+" Language:	graphql
+" Maintainer:	Jon Parise 
+" Filenames:	*.graphql *.graphqls *.gql
+" URL:		https://github.com/jparise/vim-graphql
+" License:	MIT 
+" Last Change:	2024 Dec 21
+
+" Set our local options if indentation hasn't already been set up.
+" This generally means we've been detected as the primary filetype.
+if !exists('b:did_indent')
+  setlocal autoindent
+  setlocal nocindent
+  setlocal nolisp
+  setlocal nosmartindent
+
+  setlocal indentexpr=GetGraphQLIndent()
+  setlocal indentkeys=0{,0},0),0[,0],0#,!^F,o,O
+
+  let b:did_indent = 1
+endif
+
+" If our indentation function already exists, we have nothing more to do.
+if exists('*GetGraphQLIndent')
+  finish
+endif
+
+let s:cpo_save = &cpoptions
+set cpoptions&vim
+
+" searchpair() skip expression that matches in comments and strings.
+let s:pair_skip_expr =
+  \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "comment\\|string"'
+
+" Check if the character at lnum:col is inside a string.
+function s:InString(lnum, col)
+  return synIDattr(synID(a:lnum, a:col, 1), 'name') ==# 'graphqlString'
+endfunction
+
+function GetGraphQLIndent()
+  " If this is the first non-blank line, we have nothing more to do because
+  " all of our indentation rules are based on matching against earlier lines.
+  let l:prevlnum = prevnonblank(v:lnum - 1)
+  if l:prevlnum == 0
+    return 0
+  endif
+
+  " If the previous line isn't GraphQL, assume we're part of a template
+  " string and indent this new line within it.
+  let l:stack = map(synstack(l:prevlnum, 1), "synIDattr(v:val, 'name')")
+  if get(l:stack, -1) !~# '^graphql'
+    return indent(l:prevlnum) + shiftwidth()
+  endif
+
+  let l:line = getline(v:lnum)
+
+  " If this line contains just a closing bracket, find its matching opening
+  " bracket and indent the closing bracket to match.
+  let l:col = matchend(l:line, '^\s*[]})]')
+  if l:col > 0 && !s:InString(v:lnum, l:col)
+    call cursor(v:lnum, l:col)
+
+    let l:bracket = l:line[l:col - 1]
+    if l:bracket ==# '}'
+      let l:matched = searchpair('{', '', '}', 'bW', s:pair_skip_expr)
+    elseif l:bracket ==# ']'
+      let l:matched = searchpair('\[', '', '\]', 'bW', s:pair_skip_expr)
+    elseif l:bracket ==# ')'
+      let l:matched = searchpair('(', '', ')', 'bW', s:pair_skip_expr)
+    else
+      let l:matched = -1
+    endif
+
+    return l:matched > 0 ? indent(l:matched) : virtcol('.') - 1
+  endif
+
+  " If we're inside of a multiline string, continue with the same indentation.
+  if s:InString(v:lnum, matchend(l:line, '^\s*') + 1)
+    return indent(v:lnum)
+  endif
+
+  " If the previous line ended with an opening bracket, indent this line.
+  if getline(l:prevlnum) =~# '\%(#.*\)\@
+" Filenames:	*.graphql *.graphqls *.gql
+" URL:		https://github.com/jparise/vim-graphql
+" License:	MIT 
+" Last Change:	2024 Dec 21
+
+if !exists('main_syntax')
+  if exists('b:current_syntax')
+    finish
+  endif
+  let main_syntax = 'graphql'
+endif
+
+syn case match
+
+syn match graphqlComment    "#.*$" contains=@Spell
+
+syn match graphqlOperator   "=" display
+syn match graphqlOperator   "!" display
+syn match graphqlOperator   "|" display
+syn match graphqlOperator   "&" display
+syn match graphqlOperator   "\M..." display
+
+syn keyword graphqlBoolean  true false
+syn keyword graphqlNull     null
+syn match   graphqlNumber   "-\=\<\%(0\|[1-9]\d*\)\%(\.\d\+\)\=\%([eE][-+]\=\d\+\)\=\>" display
+syn region  graphqlString   start=+"+  skip=+\\\\\|\\"+  end=+"\|$+
+syn region  graphqlString   start=+"""+ skip=+\\"""+ end=+"""+
+
+syn keyword graphqlKeyword repeatable nextgroup=graphqlKeyword skipwhite
+syn keyword graphqlKeyword on nextgroup=graphqlType,graphqlDirectiveLocation skipwhite
+
+syn keyword graphqlStructure enum scalar type union nextgroup=graphqlType skipwhite
+syn keyword graphqlStructure input interface subscription nextgroup=graphqlType skipwhite
+syn keyword graphqlStructure implements nextgroup=graphqlType skipwhite
+syn keyword graphqlStructure query mutation fragment nextgroup=graphqlName skipwhite
+syn keyword graphqlStructure directive nextgroup=graphqlDirective skipwhite
+syn keyword graphqlStructure extend nextgroup=graphqlStructure skipwhite
+syn keyword graphqlStructure schema nextgroup=graphqlFold skipwhite
+
+syn match graphqlDirective  "\<@\h\w*\>"   display
+syn match graphqlVariable   "\<\$\h\w*\>"  display
+syn match graphqlName       "\<\h\w*\>"    display
+syn match graphqlType       "\<_*\u\w*\>"  display
+
+" https://spec.graphql.org/October2021/#ExecutableDirectiveLocation
+syn keyword graphqlDirectiveLocation QUERY MUTATION SUBSCRIPTION FIELD
+syn keyword graphqlDirectiveLocation FRAGMENT_DEFINITION FRAGMENT_SPREAD
+syn keyword graphqlDirectiveLocation INLINE_FRAGMENT VARIABLE_DEFINITION
+" https://spec.graphql.org/October2021/#TypeSystemDirectiveLocation
+syn keyword graphqlDirectiveLocation SCHEMA SCALAR OBJECT FIELD_DEFINITION
+syn keyword graphqlDirectiveLocation ARGUMENT_DEFINITION INTERFACE UNION
+syn keyword graphqlDirectiveLocation ENUM ENUM_VALUE INPUT_OBJECT
+syn keyword graphqlDirectiveLocation INPUT_FIELD_DEFINITION
+
+syn keyword graphqlMetaFields __schema __type __typename
+
+syn region  graphqlFold matchgroup=graphqlBraces start="{" end="}" transparent fold contains=ALLBUT,graphqlStructure
+syn region  graphqlList matchgroup=graphqlBraces start="\[" end="]" transparent contains=ALLBUT,graphqlDirective,graphqlStructure
+
+if main_syntax ==# 'graphql'
+  syn sync minlines=500
+endif
+
+hi def link graphqlComment          Comment
+hi def link graphqlOperator         Operator
+
+hi def link graphqlBraces           Delimiter
+
+hi def link graphqlBoolean          Boolean
+hi def link graphqlNull             Keyword
+hi def link graphqlNumber           Number
+hi def link graphqlString           String
+
+hi def link graphqlDirective        PreProc
+hi def link graphqlDirectiveLocation Special
+hi def link graphqlName             Identifier
+hi def link graphqlMetaFields       Special
+hi def link graphqlKeyword          Keyword
+hi def link graphqlStructure        Structure
+hi def link graphqlType             Type
+hi def link graphqlVariable         Identifier
+
+let b:current_syntax = 'graphql'
+
+if main_syntax ==# 'graphql'
+  unlet main_syntax
+endif
-- 
cgit 


From 55c5d0de262b8a9eb03a65f6e6f45e8d26213eb4 Mon Sep 17 00:00:00 2001
From: Lukasz Piepiora 
Date: Mon, 23 Dec 2024 14:08:20 +0100
Subject: docs(api): vim.version.range():has() method #31622

Problem:
The :has() method of the vim.version.range() result is not documented
though it's mentioned in examples.

Solution:
Mention it in the range() result doc.
---
 runtime/doc/lua.txt         | 1 +
 runtime/lua/vim/version.lua | 3 +--
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index dad3d92238..711607d14b 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -3998,6 +3998,7 @@ vim.version.range({spec})                                *vim.version.range()*
         (`table?`) A table with the following fields:
         • {from} (`vim.Version`)
         • {to}? (`vim.Version`)
+        • {has} (`fun(self: vim.VersionRange, version: string|vim.Version)`)
 
     See also: ~
       • https://github.com/npm/node-semver#ranges
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index d64ef98d2d..06c54ac033 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -227,8 +227,7 @@ end
 ---@field to? vim.Version
 local VersionRange = {}
 
---- @private
----
+---@nodoc
 ---@param version string|vim.Version
 function VersionRange:has(version)
   if type(version) == 'string' then
-- 
cgit 


From 2a7d0ed6145bf3f8b139c2694563f460f829813a Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Mon, 23 Dec 2024 05:43:52 -0800
Subject: refactor: iwyu #31637

Result of `make iwyu` (after some "fixups").
---
 src/nvim/api/autocmd.c            |  2 +-
 src/nvim/api/buffer.c             |  1 -
 src/nvim/api/command.c            |  1 +
 src/nvim/api/deprecated.c         |  4 +---
 src/nvim/api/extmark.c            |  1 +
 src/nvim/api/extmark.h            |  1 +
 src/nvim/api/options.c            |  1 +
 src/nvim/api/private/converter.c  |  3 ++-
 src/nvim/api/private/helpers.c    |  5 +----
 src/nvim/api/private/helpers.h    |  2 +-
 src/nvim/api/tabpage.c            |  5 +++--
 src/nvim/api/ui.c                 |  2 +-
 src/nvim/api/ui.h                 |  1 +
 src/nvim/api/vim.c                |  3 +--
 src/nvim/api/vimscript.c          |  2 ++
 src/nvim/api/win_config.c         |  7 ++-----
 src/nvim/api/window.c             |  3 +--
 src/nvim/autocmd.c                |  3 +--
 src/nvim/buffer_updates.c         |  3 ++-
 src/nvim/bufwrite.c               |  1 -
 src/nvim/change.c                 |  1 -
 src/nvim/channel.c                |  3 ++-
 src/nvim/cmdexpand.c              |  1 -
 src/nvim/cmdhist.c                |  1 +
 src/nvim/cmdhist.h                |  4 +++-
 src/nvim/context.c                |  1 +
 src/nvim/context.h                |  2 +-
 src/nvim/cursor.c                 |  1 +
 src/nvim/decoration.c             |  3 +--
 src/nvim/diff.c                   |  1 +
 src/nvim/digraph.c                |  1 -
 src/nvim/drawscreen.c             |  1 -
 src/nvim/drawscreen.h             |  1 +
 src/nvim/edit.c                   |  4 ++++
 src/nvim/eval/buffer.c            |  1 +
 src/nvim/eval/decode.c            |  3 ++-
 src/nvim/eval/decode.h            |  2 +-
 src/nvim/eval/deprecated.c        |  2 +-
 src/nvim/eval/deprecated.h        |  2 --
 src/nvim/eval/executor.c          |  1 -
 src/nvim/eval/funcs.c             |  4 +++-
 src/nvim/eval/typval.c            |  2 ++
 src/nvim/eval/vars.c              |  1 -
 src/nvim/eval/window.c            |  1 -
 src/nvim/event/proc.c             |  3 +++
 src/nvim/event/rstream.c          |  6 +++---
 src/nvim/event/stream.c           |  1 +
 src/nvim/ex_cmds.c                |  3 +--
 src/nvim/ex_cmds2.c               |  2 +-
 src/nvim/ex_docmd.c               |  1 -
 src/nvim/ex_getln.c               |  3 ---
 src/nvim/ex_session.c             |  2 ++
 src/nvim/file_search.c            |  4 +++-
 src/nvim/fileio.c                 |  2 --
 src/nvim/garray.c                 |  3 ++-
 src/nvim/getchar.c                |  6 +++++-
 src/nvim/getchar.h                |  1 -
 src/nvim/hashtab.c                |  1 +
 src/nvim/highlight_group.c        |  2 ++
 src/nvim/indent.c                 |  1 -
 src/nvim/input.c                  |  2 --
 src/nvim/insexpand.c              |  1 -
 src/nvim/linematch.c              |  2 --
 src/nvim/linematch.h              |  2 +-
 src/nvim/lua/api_wrappers.c       | 30 +++++++++++++++---------------
 src/nvim/lua/converter.c          |  1 +
 src/nvim/lua/executor.c           |  3 ++-
 src/nvim/lua/executor.h           |  2 +-
 src/nvim/lua/secure.c             |  2 +-
 src/nvim/lua/spell.c              |  2 +-
 src/nvim/lua/stdlib.c             |  2 ++
 src/nvim/lua/treesitter.c         |  3 +--
 src/nvim/lua/xdiff.c              |  1 +
 src/nvim/main.c                   |  5 ++---
 src/nvim/main.h                   |  1 -
 src/nvim/mapping.c                |  3 ++-
 src/nvim/mark.c                   |  1 -
 src/nvim/marktree.h               |  1 -
 src/nvim/mbyte.c                  |  2 +-
 src/nvim/memline.c                |  1 -
 src/nvim/memory.c                 |  1 -
 src/nvim/memory.h                 |  1 +
 src/nvim/menu.c                   |  1 -
 src/nvim/message.c                |  2 ++
 src/nvim/move.c                   |  7 +++----
 src/nvim/msgpack_rpc/channel.c    |  1 +
 src/nvim/msgpack_rpc/packer.c     |  9 +++++++++
 src/nvim/msgpack_rpc/packer.h     |  6 +++---
 src/nvim/msgpack_rpc/server.c     |  1 +
 src/nvim/msgpack_rpc/unpacker.c   |  1 +
 src/nvim/msgpack_rpc/unpacker.h   |  2 ++
 src/nvim/normal.c                 |  1 +
 src/nvim/ops.c                    |  3 +--
 src/nvim/ops.h                    |  2 +-
 src/nvim/option.c                 |  6 ++++--
 src/nvim/option.h                 |  4 +---
 src/nvim/optionstr.c              |  1 -
 src/nvim/os/env.c                 |  1 -
 src/nvim/os/fileio.c              |  7 ++-----
 src/nvim/os/input.c               |  3 ++-
 src/nvim/os/pty_proc_unix.c       |  1 +
 src/nvim/path.c                   |  1 +
 src/nvim/popupmenu.c              |  8 ++------
 src/nvim/profile.c                |  1 +
 src/nvim/quickfix.c               |  1 -
 src/nvim/regexp.c                 |  1 +
 src/nvim/runtime.c                |  1 +
 src/nvim/search.c                 |  3 +--
 src/nvim/search.h                 |  3 ++-
 src/nvim/shada.c                  |  9 +++++----
 src/nvim/shada.h                  |  2 +-
 src/nvim/sign.c                   |  3 ---
 src/nvim/state.h                  |  2 +-
 src/nvim/statusline.c             |  1 -
 src/nvim/strings.c                |  4 +++-
 src/nvim/strings.h                |  2 +-
 src/nvim/syntax.c                 |  1 -
 src/nvim/tag.c                    |  1 -
 src/nvim/textformat.c             |  1 +
 src/nvim/textobject.c             |  1 -
 src/nvim/tui/input.c              |  1 -
 src/nvim/tui/input.h              |  1 +
 src/nvim/tui/termkey/driver-csi.c |  3 +--
 src/nvim/tui/termkey/driver-csi.h |  2 +-
 src/nvim/tui/termkey/driver-ti.c  |  6 +++---
 src/nvim/tui/termkey/driver-ti.h  |  2 +-
 src/nvim/tui/termkey/termkey.c    |  6 ++----
 src/nvim/tui/termkey/termkey.h    |  6 +++---
 src/nvim/tui/tui.c                |  4 +---
 src/nvim/ui.c                     |  1 -
 src/nvim/ui_client.c              |  1 +
 src/nvim/undo.c                   |  3 ---
 src/nvim/usercmd.c                |  2 +-
 src/nvim/version.c                |  3 ++-
 src/nvim/window.c                 |  3 ---
 src/nvim/winfloat.c               |  1 -
 136 files changed, 179 insertions(+), 176 deletions(-)

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 45e2de69e0..ae349fdb95 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -2,7 +2,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 
@@ -23,6 +22,7 @@
 #include "nvim/globals.h"
 #include "nvim/lua/executor.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/strings.h"
 #include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 6c40eb9b20..aa349790b3 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -23,7 +23,6 @@
 #include "nvim/buffer_updates.h"
 #include "nvim/change.h"
 #include "nvim/cursor.h"
-#include "nvim/drawscreen.h"
 #include "nvim/ex_cmds.h"
 #include "nvim/extmark.h"
 #include "nvim/extmark_defs.h"
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index 709e18af73..7e79133ed4 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -26,6 +26,7 @@
 #include "nvim/macros_defs.h"
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/ops.h"
 #include "nvim/pos_defs.h"
 #include "nvim/regexp.h"
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 51ed6eaa41..d5eddb74de 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -7,6 +7,7 @@
 #include "nvim/api/extmark.h"
 #include "nvim/api/keysets_defs.h"
 #include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
 #include "nvim/api/private/helpers.h"
 #include "nvim/api/private/validate.h"
 #include "nvim/api/vimscript.h"
@@ -20,9 +21,6 @@
 #include "nvim/lua/executor.h"
 #include "nvim/memory.h"
 #include "nvim/memory_defs.h"
-#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
-#include "nvim/msgpack_rpc/unpacker.h"
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/pos_defs.h"
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 55f537e9c9..a237e7531a 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -27,6 +27,7 @@
 #include "nvim/mbyte.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/move.h"
 #include "nvim/pos_defs.h"
 #include "nvim/sign.h"
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index af2d51c95c..0b4d84110b 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include 
 #include   // IWYU pragma: keep
 
 #include "nvim/api/keysets_defs.h"  // IWYU pragma: keep
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index bfd63ca489..64f8a35d54 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -14,6 +14,7 @@
 #include "nvim/buffer_defs.h"
 #include "nvim/globals.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/option.h"
 #include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 59e7373f68..5f9d20ee73 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -1,4 +1,5 @@
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -7,7 +8,6 @@
 #include "nvim/api/private/converter.h"
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
-#include "nvim/ascii_defs.h"
 #include "nvim/assert_defs.h"
 #include "nvim/eval/decode.h"
 #include "nvim/eval/typval.h"
@@ -15,6 +15,7 @@
 #include "nvim/eval/userfunc.h"
 #include "nvim/lua/executor.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
 
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 4389ae3b35..78aa7c00f7 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -31,13 +31,10 @@
 #include "nvim/msgpack_rpc/unpacker.h"
 #include "nvim/pos_defs.h"
 #include "nvim/types_defs.h"
-#include "nvim/ui.h"
-#include "nvim/ui_defs.h"
-#include "nvim/version.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "api/private/api_metadata.generated.h"
-# include "api/private/helpers.c.generated.h"
+# include "api/private/helpers.c.generated.h"  // IWYU pragma: keep
 #endif
 
 /// Start block that may cause Vimscript exceptions while evaluating another code
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 03ff811449..d581c6bc10 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #include 
-#include 
+#include   // IWYU pragma: keep
 
 #include "klib/kvec.h"
 #include "nvim/api/private/defs.h"  // IWYU pragma: keep
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index b4d519dc98..dce47cd99f 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -7,11 +7,12 @@
 #include "nvim/api/vim.h"
 #include "nvim/buffer_defs.h"
 #include "nvim/globals.h"
-#include "nvim/memory.h"
+#include "nvim/memory_defs.h"
+#include "nvim/types_defs.h"
 #include "nvim/window.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "api/tabpage.c.generated.h"
+# include "api/tabpage.c.generated.h"  // IWYU pragma: keep
 #endif
 
 /// Gets the windows in a tabpage
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index b09a9ed253..db8a5d93d0 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -23,7 +23,6 @@
 #include "nvim/event/wstream.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
-#include "nvim/grid_defs.h"
 #include "nvim/highlight.h"
 #include "nvim/macros_defs.h"
 #include "nvim/main.h"
@@ -34,6 +33,7 @@
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/msgpack_rpc/channel_defs.h"
 #include "nvim/msgpack_rpc/packer.h"
+#include "nvim/msgpack_rpc/packer_defs.h"
 #include "nvim/option.h"
 #include "nvim/types_defs.h"
 #include "nvim/ui.h"
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
index cdccc27ba4..3f996ec219 100644
--- a/src/nvim/api/ui.h
+++ b/src/nvim/api/ui.h
@@ -4,6 +4,7 @@
 
 #include "nvim/api/private/defs.h"  // IWYU pragma: keep
 #include "nvim/highlight_defs.h"  // IWYU pragma: keep
+#include "nvim/macros_defs.h"
 #include "nvim/types_defs.h"  // IWYU pragma: keep
 #include "nvim/ui_defs.h"  // IWYU pragma: keep
 
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index fce7a86245..332c5bc15c 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -20,6 +20,7 @@
 #include "nvim/api/vim.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
+#include "nvim/autocmd_defs.h"
 #include "nvim/buffer.h"
 #include "nvim/buffer_defs.h"
 #include "nvim/channel.h"
@@ -28,7 +29,6 @@
 #include "nvim/cursor.h"
 #include "nvim/decoration.h"
 #include "nvim/drawscreen.h"
-#include "nvim/edit.h"
 #include "nvim/errors.h"
 #include "nvim/eval.h"
 #include "nvim/eval/typval.h"
@@ -76,7 +76,6 @@
 #include "nvim/runtime.h"
 #include "nvim/sign_defs.h"
 #include "nvim/state.h"
-#include "nvim/state_defs.h"
 #include "nvim/statusline.h"
 #include "nvim/statusline_defs.h"
 #include "nvim/strings.h"
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index e2aac07258..fc7e7e1a06 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -2,6 +2,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 
 #include "klib/kvec.h"
@@ -21,6 +22,7 @@
 #include "nvim/garray_defs.h"
 #include "nvim/globals.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/runtime.h"
 #include "nvim/vim_defs.h"
 #include "nvim/viml/parser/expressions.h"
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 6f5a9a90c0..45c9e3f56c 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -1,3 +1,4 @@
+#include 
 #include 
 #include 
 
@@ -7,25 +8,22 @@
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/dispatch.h"
 #include "nvim/api/private/helpers.h"
-#include "nvim/api/tabpage.h"
 #include "nvim/api/win_config.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/autocmd_defs.h"
 #include "nvim/buffer.h"
 #include "nvim/buffer_defs.h"
-#include "nvim/decoration.h"
 #include "nvim/decoration_defs.h"
 #include "nvim/drawscreen.h"
 #include "nvim/errors.h"
 #include "nvim/eval/window.h"
-#include "nvim/extmark_defs.h"
 #include "nvim/globals.h"
-#include "nvim/grid_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/macros_defs.h"
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/option.h"
 #include "nvim/option_vars.h"
 #include "nvim/pos_defs.h"
@@ -33,7 +31,6 @@
 #include "nvim/syntax.h"
 #include "nvim/types_defs.h"
 #include "nvim/ui.h"
-#include "nvim/ui_compositor.h"
 #include "nvim/ui_defs.h"
 #include "nvim/vim_defs.h"
 #include "nvim/window.h"
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 387dad899e..d968af421d 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 #include 
@@ -28,7 +27,7 @@
 #include "nvim/window.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "api/window.c.generated.h"
+# include "api/window.c.generated.h"  // IWYU pragma: keep
 #endif
 
 /// Gets the current buffer in a window
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 118a50e15d..eb7c8c2880 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -30,8 +30,8 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
+#include "nvim/grid_defs.h"
 #include "nvim/hashtab.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/insexpand.h"
 #include "nvim/lua/executor.h"
@@ -42,7 +42,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/os/input.h"
 #include "nvim/os/os.h"
 #include "nvim/os/os_defs.h"
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index e725678937..ab07d67ef3 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -16,12 +16,13 @@
 #include "nvim/lua/executor.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/pos_defs.h"
 #include "nvim/types_defs.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "buffer_updates.c.generated.h"
+# include "buffer_updates.c.generated.h"  // IWYU pragma: keep
 #endif
 
 // Register a channel. Return True if the channel was added, or already added.
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
index 2cf02403da..ced806e524 100644
--- a/src/nvim/bufwrite.c
+++ b/src/nvim/bufwrite.c
@@ -27,7 +27,6 @@
 #include "nvim/fileio.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/iconv_defs.h"
 #include "nvim/input.h"
diff --git a/src/nvim/change.c b/src/nvim/change.c
index faaae96af9..ecd6012679 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -25,7 +25,6 @@
 #include "nvim/fold.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/indent.h"
 #include "nvim/indent_c.h"
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 021fdd4b79..912d515f84 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -1,4 +1,5 @@
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -22,7 +23,6 @@
 #include "nvim/event/proc.h"
 #include "nvim/event/rstream.h"
 #include "nvim/event/socket.h"
-#include "nvim/event/stream.h"
 #include "nvim/event/wstream.h"
 #include "nvim/garray.h"
 #include "nvim/gettext_defs.h"
@@ -32,6 +32,7 @@
 #include "nvim/main.h"
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/msgpack_rpc/server.h"
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index 80ff5e057d..3a9eeb73c8 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -42,7 +42,6 @@
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/keycodes.h"
-#include "nvim/log.h"
 #include "nvim/lua/executor.h"
 #include "nvim/macros_defs.h"
 #include "nvim/mapping.h"
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
index 5993eefd67..d2285cab24 100644
--- a/src/nvim/cmdhist.c
+++ b/src/nvim/cmdhist.c
@@ -13,6 +13,7 @@
 #include "nvim/cmdhist.h"
 #include "nvim/errors.h"
 #include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
 #include "nvim/ex_cmds.h"
 #include "nvim/ex_cmds_defs.h"
 #include "nvim/ex_getln.h"
diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h
index c933982593..f45345372b 100644
--- a/src/nvim/cmdhist.h
+++ b/src/nvim/cmdhist.h
@@ -1,7 +1,9 @@
 #pragma once
 
+#include   // IWYU pragma: keep
+
 #include "nvim/cmdexpand_defs.h"  // IWYU pragma: keep
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/ex_cmds_defs.h"  // IWYU pragma: keep
 #include "nvim/os/time_defs.h"
 #include "nvim/types_defs.h"  // IWYU pragma: keep
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 461b10a9d5..9d8fdb7e74 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -20,6 +20,7 @@
 #include "nvim/hashtab.h"
 #include "nvim/keycodes.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/shada.h"
diff --git a/src/nvim/context.h b/src/nvim/context.h
index 4375030fbc..5ae2a078b0 100644
--- a/src/nvim/context.h
+++ b/src/nvim/context.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include 
+#include   // IWYU pragma: keep
 
 #include "klib/kvec.h"
 #include "nvim/api/private/defs.h"
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index a248a4133e..580ed856e4 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -11,6 +11,7 @@
 #include "nvim/drawscreen.h"
 #include "nvim/fold.h"
 #include "nvim/globals.h"
+#include "nvim/macros_defs.h"
 #include "nvim/mark.h"
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 0808f13491..728a917c22 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -17,12 +17,11 @@
 #include "nvim/fold.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
-#include "nvim/grid_defs.h"
 #include "nvim/highlight.h"
 #include "nvim/highlight_group.h"
 #include "nvim/marktree.h"
 #include "nvim/memory.h"
-#include "nvim/move.h"
+#include "nvim/memory_defs.h"
 #include "nvim/option_vars.h"
 #include "nvim/pos_defs.h"
 #include "nvim/sign.h"
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 20f93049c1..bd98a31a71 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -44,6 +44,7 @@
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
+#include "nvim/memline_defs.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
 #include "nvim/move.h"
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 4d40455507..876d38ea83 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -22,7 +22,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/keycodes.h"
 #include "nvim/mapping.h"
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 0961aabf21..f8aeb02229 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -68,7 +68,6 @@
 #include "nvim/buffer_defs.h"
 #include "nvim/charset.h"
 #include "nvim/cmdexpand.h"
-#include "nvim/cursor.h"
 #include "nvim/decoration.h"
 #include "nvim/decoration_defs.h"
 #include "nvim/decoration_provider.h"
diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h
index 36ba8099fd..58eca9ac07 100644
--- a/src/nvim/drawscreen.h
+++ b/src/nvim/drawscreen.h
@@ -4,6 +4,7 @@
 
 #include "nvim/buffer_defs.h"
 #include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
 
 /// flags for update_screen()
 /// The higher the value, the higher the priority
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index f5e11a188f..e55cda1c21 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -4,9 +4,11 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
+#include "klib/kvec.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/autocmd_defs.h"
@@ -31,6 +33,7 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
+#include "nvim/grid_defs.h"
 #include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
@@ -46,6 +49,7 @@
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
+#include "nvim/memline_defs.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
 #include "nvim/mouse.h"
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
index b4181eb186..41ed17598b 100644
--- a/src/nvim/eval/buffer.c
+++ b/src/nvim/eval/buffer.c
@@ -3,6 +3,7 @@
 #include 
 #include 
 
+#include "klib/kvec.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/autocmd_defs.h"
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index afc2efddf6..cfcd415219 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -6,6 +6,8 @@
 #include 
 
 #include "klib/kvec.h"
+#include "mpack/conv.h"
+#include "mpack/mpack_core.h"
 #include "mpack/object.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/charset.h"
@@ -21,7 +23,6 @@
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
-#include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
 
 /// Helper structure for container_struct
diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h
index 485cc65561..af5fd3979c 100644
--- a/src/nvim/eval/decode.h
+++ b/src/nvim/eval/decode.h
@@ -2,7 +2,7 @@
 
 #include   // IWYU pragma: keep
 
-#include "mpack/object.h"
+#include "mpack/object.h"  // IWYU pragma: keep
 #include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/types_defs.h"  // IWYU pragma: keep
 
diff --git a/src/nvim/eval/deprecated.c b/src/nvim/eval/deprecated.c
index 67c254dac9..79874f6647 100644
--- a/src/nvim/eval/deprecated.c
+++ b/src/nvim/eval/deprecated.c
@@ -12,7 +12,7 @@
 #include "nvim/types_defs.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "eval/deprecated.c.generated.h"
+# include "eval/deprecated.c.generated.h"  // IWYU pragma: keep
 #endif
 
 /// "termopen(cmd[, cwd])" function
diff --git a/src/nvim/eval/deprecated.h b/src/nvim/eval/deprecated.h
index b870403aa4..e2e3ee436e 100644
--- a/src/nvim/eval/deprecated.h
+++ b/src/nvim/eval/deprecated.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#include                 // for true
-
 #include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/types_defs.h"  // IWYU pragma: keep
 
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 5b92f217d1..691fd405e9 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -8,7 +8,6 @@
 #include "nvim/eval/typval_defs.h"
 #include "nvim/garray.h"
 #include "nvim/gettext_defs.h"
-#include "nvim/globals.h"
 #include "nvim/message.h"
 #include "nvim/strings.h"
 #include "nvim/types_defs.h"
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 23bfcff406..c52c915f76 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -13,6 +13,8 @@
 #include 
 
 #include "auto/config.h"
+#include "klib/kvec.h"
+#include "mpack/mpack_core.h"
 #include "mpack/object.h"
 #include "nvim/api/private/converter.h"
 #include "nvim/api/private/defs.h"
@@ -38,7 +40,6 @@
 #include "nvim/eval.h"
 #include "nvim/eval/buffer.h"
 #include "nvim/eval/decode.h"
-#include "nvim/eval/deprecated.h"
 #include "nvim/eval/encode.h"
 #include "nvim/eval/executor.h"
 #include "nvim/eval/funcs.h"
@@ -90,6 +91,7 @@
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/msgpack_rpc/channel_defs.h"
 #include "nvim/msgpack_rpc/packer.h"
+#include "nvim/msgpack_rpc/packer_defs.h"
 #include "nvim/msgpack_rpc/server.h"
 #include "nvim/normal.h"
 #include "nvim/normal_defs.h"
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index e7b6a0feee..cbb6b5644f 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -9,6 +9,7 @@
 
 #include "nvim/ascii_defs.h"
 #include "nvim/assert_defs.h"
+#include "nvim/buffer_defs.h"
 #include "nvim/charset.h"
 #include "nvim/errors.h"
 #include "nvim/eval.h"
@@ -32,6 +33,7 @@
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/os/input.h"
 #include "nvim/pos_defs.h"
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 8c8a8ac5e0..6504d44eb6 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -39,7 +39,6 @@
 #include "nvim/ops.h"
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
-#include "nvim/option_vars.h"
 #include "nvim/os/os.h"
 #include "nvim/search.h"
 #include "nvim/strings.h"
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index a9d3e89177..2e2758d3bc 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -31,7 +31,6 @@
 #include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
 #include "nvim/window.h"
-#include "nvim/winfloat.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "eval/window.c.generated.h"
diff --git a/src/nvim/event/proc.c b/src/nvim/event/proc.c
index 5ae3bd8c2d..37cb102d11 100644
--- a/src/nvim/event/proc.c
+++ b/src/nvim/event/proc.c
@@ -1,8 +1,10 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 
+#include "klib/kvec.h"
 #include "nvim/event/libuv_proc.h"
 #include "nvim/event/loop.h"
 #include "nvim/event/multiqueue.h"
@@ -13,6 +15,7 @@
 #include "nvim/globals.h"
 #include "nvim/log.h"
 #include "nvim/main.h"
+#include "nvim/memory_defs.h"
 #include "nvim/os/proc.h"
 #include "nvim/os/pty_proc.h"
 #include "nvim/os/shell.h"
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index 15bdc547d5..6304953029 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -1,7 +1,6 @@
 #include 
 #include 
-#include 
-#include 
+#include 
 #include 
 
 #include "nvim/event/multiqueue.h"
@@ -9,7 +8,8 @@
 #include "nvim/event/stream.h"
 #include "nvim/log.h"
 #include "nvim/macros_defs.h"
-#include "nvim/main.h"
+#include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/os/os_defs.h"
 #include "nvim/types_defs.h"
 
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 71de6ee1ba..9c155b55ea 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -8,6 +8,7 @@
 #include "nvim/event/loop.h"
 #include "nvim/event/stream.h"
 #include "nvim/log.h"
+#include "nvim/memory.h"
 #include "nvim/types_defs.h"
 #ifdef MSWIN
 # include "nvim/os/os_win_console.h"
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 8cccf08e11..a1a6f13023 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -15,6 +15,7 @@
 
 #include "auto/config.h"
 #include "klib/kvec.h"
+#include "nvim/api/private/helpers.h"
 #include "nvim/arglist.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
@@ -52,7 +53,6 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/help.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/indent.h"
@@ -73,7 +73,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/os/fs.h"
 #include "nvim/os/fs_defs.h"
 #include "nvim/os/input.h"
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index e37c37e8e6..61a6dab897 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -30,10 +30,10 @@
 #include "nvim/fileio.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/macros_defs.h"
 #include "nvim/mark.h"
+#include "nvim/memline_defs.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
 #include "nvim/move.h"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index bd8623397a..551e8fcb1d 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -51,7 +51,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/input.h"
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index ff752fb489..7f2f739e00 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -14,7 +14,6 @@
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
 #include "nvim/api/vim.h"
-#include "nvim/arabic.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/autocmd_defs.h"
@@ -43,7 +42,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/keycodes.h"
@@ -64,7 +62,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/os/input.h"
 #include "nvim/os/os.h"
 #include "nvim/path.h"
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index ddc2705a02..ab7585e90a 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -9,10 +9,12 @@
 #include 
 #include 
 
+#include "klib/kvec.h"
 #include "nvim/arglist.h"
 #include "nvim/arglist_defs.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
+#include "nvim/autocmd_defs.h"
 #include "nvim/buffer.h"
 #include "nvim/buffer_defs.h"
 #include "nvim/errors.h"
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index d183978d2d..1e6153bf8d 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -41,6 +41,7 @@
 // functions.
 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -52,6 +53,7 @@
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/autocmd_defs.h"
+#include "nvim/buffer_defs.h"
 #include "nvim/charset.h"
 #include "nvim/cursor.h"
 #include "nvim/errors.h"
@@ -67,6 +69,7 @@
 #include "nvim/message.h"
 #include "nvim/normal.h"
 #include "nvim/option.h"
+#include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
 #include "nvim/os/fs.h"
 #include "nvim/os/fs_defs.h"
@@ -76,7 +79,6 @@
 #include "nvim/path.h"
 #include "nvim/strings.h"
 #include "nvim/vim_defs.h"
-#include "nvim/window.h"
 
 static char *ff_expand_buffer = NULL;  // used for expanding filenames
 
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index fc7fabc009..031ae30d41 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -38,7 +38,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/iconv_defs.h"
 #include "nvim/log.h"
@@ -55,7 +54,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/os/fs.h"
 #include "nvim/os/fs_defs.h"
 #include "nvim/os/input.h"
diff --git a/src/nvim/garray.c b/src/nvim/garray.c
index 4f8ba30522..a8d15a1fa8 100644
--- a/src/nvim/garray.c
+++ b/src/nvim/garray.c
@@ -7,12 +7,13 @@
 
 #include "nvim/garray.h"
 #include "nvim/log.h"
+#include "nvim/macros_defs.h"
 #include "nvim/memory.h"
 #include "nvim/path.h"
 #include "nvim/strings.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "garray.c.generated.h"
+# include "garray.c.generated.h"  // IWYU pragma: keep
 #endif
 
 /// Clear an allocated growing array.
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index bca224c7d1..60aa1055c3 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2,7 +2,6 @@
 // file, manipulations with redo buffer and stuff buffer.
 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -11,6 +10,7 @@
 #include 
 #include 
 
+#include "klib/kvec.h"
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
 #include "nvim/api/vim.h"
@@ -29,6 +29,7 @@
 #include "nvim/ex_cmds.h"
 #include "nvim/ex_docmd.h"
 #include "nvim/ex_getln.h"
+#include "nvim/ex_getln_defs.h"
 #include "nvim/garray.h"
 #include "nvim/garray_defs.h"
 #include "nvim/getchar.h"
@@ -38,6 +39,7 @@
 #include "nvim/insexpand.h"
 #include "nvim/keycodes.h"
 #include "nvim/lua/executor.h"
+#include "nvim/macros_defs.h"
 #include "nvim/main.h"
 #include "nvim/mapping.h"
 #include "nvim/mapping_defs.h"
@@ -45,6 +47,7 @@
 #include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/mouse.h"
 #include "nvim/move.h"
@@ -53,6 +56,7 @@
 #include "nvim/ops.h"
 #include "nvim/option_vars.h"
 #include "nvim/os/fileio.h"
+#include "nvim/os/fileio_defs.h"
 #include "nvim/os/input.h"
 #include "nvim/os/os.h"
 #include "nvim/os/os_defs.h"
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 4e962c9b03..766c5eb2a4 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -5,7 +5,6 @@
 
 #include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/getchar_defs.h"  // IWYU pragma: keep
-#include "nvim/os/fileio_defs.h"
 #include "nvim/types_defs.h"  // IWYU pragma: keep
 
 /// Argument for flush_buffers().
diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c
index 95df5e5383..4d09fc10db 100644
--- a/src/nvim/hashtab.c
+++ b/src/nvim/hashtab.c
@@ -26,6 +26,7 @@
 #include "nvim/ascii_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/hashtab.h"
+#include "nvim/macros_defs.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
 #include "nvim/vim_defs.h"
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index afdbd4a32f..6fd0b858a8 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1,5 +1,6 @@
 // highlight_group.c: code for managing highlight groups
 
+#include 
 #include 
 #include 
 #include 
@@ -31,6 +32,7 @@
 #include "nvim/garray_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
+#include "nvim/grid_defs.h"
 #include "nvim/highlight.h"
 #include "nvim/highlight_group.h"
 #include "nvim/lua/executor.h"
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 7da61091d4..170346ec0d 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -34,7 +34,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/os/input.h"
 #include "nvim/plines.h"
 #include "nvim/pos_defs.h"
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 0c1a8af45f..5a3a791de4 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -1,7 +1,6 @@
 // input.c: high level functions for prompting the user or input
 // like yes/no or number prompts.
 
-#include 
 #include 
 #include 
 #include 
@@ -10,7 +9,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/input.h"
 #include "nvim/keycodes.h"
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index a8ee269ecd..aee3603d75 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -37,7 +37,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/indent.h"
diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c
index 39dfb8eeb9..2627489106 100644
--- a/src/nvim/linematch.c
+++ b/src/nvim/linematch.c
@@ -3,9 +3,7 @@
 #include 
 #include 
 #include 
-#include 
 
-#include "nvim/ascii_defs.h"
 #include "nvim/linematch.h"
 #include "nvim/macros_defs.h"
 #include "nvim/memory.h"
diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h
index 5f6667a7df..08daf0e16c 100644
--- a/src/nvim/linematch.h
+++ b/src/nvim/linematch.h
@@ -3,7 +3,7 @@
 #include   // IWYU pragma: keep
 
 #include "nvim/pos_defs.h"  // IWYU pragma: keep
-#include "xdiff/xdiff.h"
+#include "xdiff/xdiff.h"  // IWYU pragma: keep
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "linematch.h.generated.h"
diff --git a/src/nvim/lua/api_wrappers.c b/src/nvim/lua/api_wrappers.c
index 36847d1fc9..447ba846b4 100644
--- a/src/nvim/lua/api_wrappers.c
+++ b/src/nvim/lua/api_wrappers.c
@@ -1,19 +1,19 @@
-#include 
-#include 
-#include 
+#include   // IWYU pragma: keep
+#include   // IWYU pragma: keep
+#include   // IWYU pragma: keep
 
-#include "nvim/api/private/defs.h"
-#include "nvim/api/private/dispatch.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/errors.h"
-#include "nvim/ex_docmd.h"
-#include "nvim/ex_getln.h"
-#include "nvim/func_attr.h"
-#include "nvim/globals.h"
-#include "nvim/lua/converter.h"
-#include "nvim/lua/executor.h"
-#include "nvim/memory.h"
+#include "nvim/api/private/defs.h"  // IWYU pragma: keep
+#include "nvim/api/private/dispatch.h"  // IWYU pragma: keep
+#include "nvim/api/private/helpers.h"  // IWYU pragma: keep
+#include "nvim/errors.h"  // IWYU pragma: keep
+#include "nvim/ex_docmd.h"  // IWYU pragma: keep
+#include "nvim/ex_getln.h"  // IWYU pragma: keep
+#include "nvim/func_attr.h"  // IWYU pragma: keep
+#include "nvim/globals.h"  // IWYU pragma: keep
+#include "nvim/lua/converter.h"  // IWYU pragma: keep
+#include "nvim/lua/executor.h"  // IWYU pragma: keep
+#include "nvim/memory.h"  // IWYU pragma: keep
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "lua_api_c_bindings.generated.h"
+# include "lua_api_c_bindings.generated.h"  // IWYU pragma: keep
 #endif
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 45ead154bc..292588ee64 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -22,6 +22,7 @@
 #include "nvim/lua/executor.h"
 #include "nvim/macros_defs.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index c4fa8b0fff..0a412b4ca9 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -23,7 +23,6 @@
 #include "nvim/cursor.h"
 #include "nvim/drawscreen.h"
 #include "nvim/errors.h"
-#include "nvim/eval.h"
 #include "nvim/eval/funcs.h"
 #include "nvim/eval/typval.h"
 #include "nvim/eval/typval_defs.h"
@@ -47,10 +46,12 @@
 #include "nvim/lua/treesitter.h"
 #include "nvim/macros_defs.h"
 #include "nvim/main.h"
+#include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
 #include "nvim/memory_defs.h"
 #include "nvim/message.h"
+#include "nvim/message_defs.h"
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/option_vars.h"
 #include "nvim/os/fileio.h"
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 32fde3853b..3a0c03412f 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -4,10 +4,10 @@
 #include 
 #include 
 
+#include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
 #include "nvim/cmdexpand_defs.h"  // IWYU pragma: keep
 #include "nvim/ex_cmds_defs.h"  // IWYU pragma: keep
-#include "nvim/func_attr.h"
 #include "nvim/macros_defs.h"
 #include "nvim/types_defs.h"
 #include "nvim/usercmd.h"  // IWYU pragma: keep
diff --git a/src/nvim/lua/secure.c b/src/nvim/lua/secure.c
index 61277949c4..1560881b55 100644
--- a/src/nvim/lua/secure.c
+++ b/src/nvim/lua/secure.c
@@ -2,11 +2,11 @@
 #include 
 #include 
 
+#include "nvim/ascii_defs.h"
 #include "nvim/charset.h"
 #include "nvim/errors.h"
 #include "nvim/ex_cmds_defs.h"
 #include "nvim/gettext_defs.h"
-#include "nvim/globals.h"
 #include "nvim/lua/executor.h"
 #include "nvim/lua/secure.h"
 #include "nvim/memory.h"
diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c
index f4dacd7a55..eec5892307 100644
--- a/src/nvim/lua/spell.c
+++ b/src/nvim/lua/spell.c
@@ -16,7 +16,7 @@
 #include "nvim/spell.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "lua/spell.c.generated.h"
+# include "lua/spell.c.generated.h"  // IWYU pragma: keep
 #endif
 
 int nlua_spell_check(lua_State *lstate)
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 4de25f4265..2fc9367ead 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -18,11 +18,13 @@
 #include "nvim/api/private/helpers.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
+#include "nvim/autocmd_defs.h"
 #include "nvim/buffer_defs.h"
 #include "nvim/eval/typval.h"
 #include "nvim/eval/typval_defs.h"
 #include "nvim/eval/vars.h"
 #include "nvim/eval/window.h"
+#include "nvim/ex_cmds_defs.h"
 #include "nvim/ex_docmd.h"
 #include "nvim/ex_eval.h"
 #include "nvim/fold.h"
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index c80e7b7672..3e5d3e24fe 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -19,8 +19,8 @@
 # include 
 #endif
 
-#include "klib/kvec.h"
 #include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
 #include "nvim/buffer_defs.h"
 #include "nvim/globals.h"
 #include "nvim/lua/treesitter.h"
@@ -28,7 +28,6 @@
 #include "nvim/map_defs.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
-#include "nvim/os/fs.h"
 #include "nvim/pos_defs.h"
 #include "nvim/strings.h"
 #include "nvim/types_defs.h"
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index b9f96abf73..f51c1a05cd 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -1,4 +1,5 @@
 #include 
+#include 
 #include 
 #include 
 #include 
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 17990a6735..348f246d27 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -7,6 +7,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -19,6 +20,7 @@
 #endif
 
 #include "auto/config.h"  // IWYU pragma: keep
+#include "klib/kvec.h"
 #include "nvim/api/extmark.h"
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
@@ -79,9 +81,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
-#include "nvim/os/fileio.h"
-#include "nvim/os/fileio_defs.h"
 #include "nvim/os/fs.h"
 #include "nvim/os/input.h"
 #include "nvim/os/lang.h"
diff --git a/src/nvim/main.h b/src/nvim/main.h
index dedfadf270..5ae5d98de4 100644
--- a/src/nvim/main.h
+++ b/src/nvim/main.h
@@ -2,7 +2,6 @@
 
 #include 
 
-#include "nvim/event/loop.h"
 #include "nvim/types_defs.h"
 
 // Maximum number of commands from + or -c arguments.
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index 1896f042f2..d29a2c8e43 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -9,6 +9,7 @@
 #include 
 #include 
 
+#include "klib/kvec.h"
 #include "nvim/api/keysets_defs.h"
 #include "nvim/api/private/converter.h"
 #include "nvim/api/private/defs.h"
@@ -32,7 +33,6 @@
 #include "nvim/getchar_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/keycodes.h"
 #include "nvim/lua/executor.h"
@@ -42,6 +42,7 @@
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index f1de557f50..d1ad920537 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -23,7 +23,6 @@
 #include "nvim/fold.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/mark.h"
 #include "nvim/mbyte.h"
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index 15df57ef63..1fb1a74487 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -4,7 +4,6 @@
 #include   // IWYU pragma: keep
 #include 
 
-#include "nvim/buffer_defs.h"
 #include "nvim/decoration_defs.h"
 #include "nvim/marktree_defs.h"  // IWYU pragma: keep
 #include "nvim/pos_defs.h"  // IWYU pragma: keep
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 3e35cdaa15..ffd4472b8a 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -26,6 +26,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -51,7 +52,6 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
-#include "nvim/grid_defs.h"
 #include "nvim/iconv_defs.h"
 #include "nvim/keycodes.h"
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index bfe90bb680..a2b8aa34ef 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -63,7 +63,6 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/input.h"
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 3aa37c047c..fa102f4e12 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -34,7 +34,6 @@
 #include "nvim/memory.h"
 #include "nvim/message.h"
 #include "nvim/option_vars.h"
-#include "nvim/os/input.h"
 #include "nvim/sign.h"
 #include "nvim/state_defs.h"
 #include "nvim/statusline.h"
diff --git a/src/nvim/memory.h b/src/nvim/memory.h
index a6ff82e39d..0955c5306c 100644
--- a/src/nvim/memory.h
+++ b/src/nvim/memory.h
@@ -2,6 +2,7 @@
 
 #include 
 #include   // IWYU pragma: keep
+#include 
 #include   // IWYU pragma: keep
 
 #include "auto/config.h"
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 4d3058ee44..5f14ea1eed 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -25,7 +25,6 @@
 #include "nvim/getchar_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/keycodes.h"
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 125f602c6c..f86f0feca5 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2,6 +2,7 @@
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -46,6 +47,7 @@
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/mouse.h"
 #include "nvim/ops.h"
diff --git a/src/nvim/move.c b/src/nvim/move.c
index dbd86bb0c8..d912858420 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -10,8 +10,8 @@
 #include 
 #include 
 #include 
-#include 
 #include 
+#include 
 
 #include "nvim/ascii_defs.h"
 #include "nvim/buffer.h"
@@ -28,8 +28,7 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
-#include "nvim/highlight.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/grid_defs.h"
 #include "nvim/macros_defs.h"
 #include "nvim/mark_defs.h"
 #include "nvim/mbyte.h"
@@ -38,12 +37,12 @@
 #include "nvim/mouse.h"
 #include "nvim/move.h"
 #include "nvim/normal.h"
+#include "nvim/normal_defs.h"
 #include "nvim/option.h"
 #include "nvim/option_vars.h"
 #include "nvim/plines.h"
 #include "nvim/popupmenu.h"
 #include "nvim/pos_defs.h"
-#include "nvim/search.h"
 #include "nvim/strings.h"
 #include "nvim/types_defs.h"
 #include "nvim/vim_defs.h"
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 626312b666..e92039b99f 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -26,6 +26,7 @@
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/msgpack_rpc/channel_defs.h"
 #include "nvim/msgpack_rpc/packer.h"
+#include "nvim/msgpack_rpc/packer_defs.h"
 #include "nvim/msgpack_rpc/unpacker.h"
 #include "nvim/os/input.h"
 #include "nvim/types_defs.h"
diff --git a/src/nvim/msgpack_rpc/packer.c b/src/nvim/msgpack_rpc/packer.c
index b739f7ba28..e5eab91b34 100644
--- a/src/nvim/msgpack_rpc/packer.c
+++ b/src/nvim/msgpack_rpc/packer.c
@@ -1,8 +1,17 @@
 #include 
+#include 
+#include 
+#include 
+#include 
 
+#include "klib/kvec.h"
 #include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
 #include "nvim/lua/executor.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
 #include "nvim/msgpack_rpc/packer.h"
+#include "nvim/types_defs.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "msgpack_rpc/packer.c.generated.h"
diff --git a/src/nvim/msgpack_rpc/packer.h b/src/nvim/msgpack_rpc/packer.h
index 299962bab4..da86f8e969 100644
--- a/src/nvim/msgpack_rpc/packer.h
+++ b/src/nvim/msgpack_rpc/packer.h
@@ -1,11 +1,11 @@
 #pragma once
 
 #include 
-#include 
+#include   // IWYU pragma: keep
 #include 
-#include 
+#include   // IWYU pragma: keep
 
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h"  // IWYU pragma: keep
 #include "nvim/msgpack_rpc/packer_defs.h"
 
 #define mpack_w(b, byte) *(*(b))++ = (char)(byte);
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 462f8397f4..b2c6d0d007 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -17,6 +17,7 @@
 #include "nvim/memory.h"
 #include "nvim/msgpack_rpc/server.h"
 #include "nvim/os/os.h"
+#include "nvim/os/os_defs.h"
 #include "nvim/os/stdpaths_defs.h"
 #include "nvim/types_defs.h"
 
diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c
index 4ddc41e596..185a1abccb 100644
--- a/src/nvim/msgpack_rpc/unpacker.c
+++ b/src/nvim/msgpack_rpc/unpacker.c
@@ -1,6 +1,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "klib/kvec.h"
 #include "mpack/conv.h"
diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h
index c29462292f..a9cd7e4652 100644
--- a/src/nvim/msgpack_rpc/unpacker.h
+++ b/src/nvim/msgpack_rpc/unpacker.h
@@ -1,8 +1,10 @@
 #pragma once
 
 #include 
+#include 
 #include 
 
+#include "klib/kvec.h"
 #include "mpack/mpack_core.h"
 #include "mpack/object.h"
 #include "nvim/api/private/defs.h"
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index a664535c0f..74f851a097 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -52,6 +52,7 @@
 #include "nvim/mark_defs.h"
 #include "nvim/math.h"
 #include "nvim/mbyte.h"
+#include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
 #include "nvim/memline_defs.h"
 #include "nvim/memory.h"
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index b508c65662..476c7ee8a4 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -28,6 +28,7 @@
 #include "nvim/errors.h"
 #include "nvim/eval.h"
 #include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
 #include "nvim/ex_cmds2.h"
 #include "nvim/ex_cmds_defs.h"
 #include "nvim/ex_getln.h"
@@ -40,7 +41,6 @@
 #include "nvim/getchar_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/indent.h"
 #include "nvim/indent_c.h"
@@ -76,7 +76,6 @@
 #include "nvim/ui_defs.h"
 #include "nvim/undo.h"
 #include "nvim/vim_defs.h"
-#include "nvim/window.h"
 
 static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
 
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index ed7e5f7466..99b9b6182d 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -3,8 +3,8 @@
 #include 
 #include 
 
+#include "nvim/api/private/defs.h"
 #include "nvim/ascii_defs.h"
-#include "nvim/eval/typval_defs.h"
 #include "nvim/ex_cmds_defs.h"  // IWYU pragma: keep
 #include "nvim/extmark_defs.h"  // IWYU pragma: keep
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 9ad8256f16..a59e55121f 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -22,6 +22,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "auto/config.h"
 #include "klib/kvec.h"
@@ -47,6 +48,7 @@
 #include "nvim/errors.h"
 #include "nvim/eval.h"
 #include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
 #include "nvim/eval/vars.h"
 #include "nvim/eval/window.h"
 #include "nvim/ex_cmds_defs.h"
@@ -58,6 +60,7 @@
 #include "nvim/garray_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
+#include "nvim/grid_defs.h"
 #include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
@@ -74,6 +77,7 @@
 #include "nvim/memfile.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/mouse.h"
 #include "nvim/move.h"
@@ -88,7 +92,6 @@
 #include "nvim/os/os.h"
 #include "nvim/os/os_defs.h"
 #include "nvim/path.h"
-#include "nvim/plines.h"
 #include "nvim/popupmenu.h"
 #include "nvim/pos_defs.h"
 #include "nvim/regexp.h"
@@ -104,7 +107,6 @@
 #include "nvim/terminal.h"
 #include "nvim/types_defs.h"
 #include "nvim/ui.h"
-#include "nvim/ui_defs.h"
 #include "nvim/undo.h"
 #include "nvim/undo_defs.h"
 #include "nvim/vim_defs.h"
diff --git a/src/nvim/option.h b/src/nvim/option.h
index cba5b00d95..2f71990150 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -1,13 +1,11 @@
 #pragma once
 
-#include 
-#include 
 #include   // IWYU pragma: keep
 
 #include "nvim/api/private/defs.h"  // IWYU pragma: keep
 #include "nvim/api/private/helpers.h"
 #include "nvim/cmdexpand_defs.h"  // IWYU pragma: keep
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/ex_cmds_defs.h"  // IWYU pragma: keep
 #include "nvim/macros_defs.h"
 #include "nvim/option_defs.h"  // IWYU pragma: keep
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 75b6585553..9b7b50ae04 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -15,7 +15,6 @@
 #include "nvim/digraph.h"
 #include "nvim/drawscreen.h"
 #include "nvim/errors.h"
-#include "nvim/eval/typval_defs.h"
 #include "nvim/eval/userfunc.h"
 #include "nvim/eval/vars.h"
 #include "nvim/ex_getln.h"
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index ccf6c9554a..733e52e69c 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -15,7 +15,6 @@
 #include "nvim/cmdexpand.h"
 #include "nvim/cmdexpand_defs.h"
 #include "nvim/eval.h"
-#include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/log.h"
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index 1981d0dfd4..1b202fdc59 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -8,16 +8,13 @@
 #include 
 #include 
 #include 
-#include 
+#include 
 #include 
 
 #include "auto/config.h"
-#include "nvim/gettext_defs.h"
-#include "nvim/globals.h"
 #include "nvim/log.h"
 #include "nvim/macros_defs.h"
 #include "nvim/memory.h"
-#include "nvim/message.h"
 #include "nvim/os/fileio.h"
 #include "nvim/os/fs.h"
 #include "nvim/os/os_defs.h"
@@ -28,7 +25,7 @@
 #endif
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/fileio.c.generated.h"
+# include "os/fileio.c.generated.h"  // IWYU pragma: keep
 #endif
 
 /// Open file
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 2d17581bac..579b08c554 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -1,4 +1,5 @@
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -13,7 +14,6 @@
 #include "nvim/event/loop.h"
 #include "nvim/event/multiqueue.h"
 #include "nvim/event/rstream.h"
-#include "nvim/event/stream.h"
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
@@ -29,6 +29,7 @@
 #include "nvim/profile.h"
 #include "nvim/state.h"
 #include "nvim/state_defs.h"
+#include "nvim/types_defs.h"
 
 #define READ_BUFFER_SIZE 0xfff
 #define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN)
diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c
index 3bca065d2d..10a464bbc3 100644
--- a/src/nvim/os/pty_proc_unix.c
+++ b/src/nvim/os/pty_proc_unix.c
@@ -30,6 +30,7 @@
 #endif
 
 #include "auto/config.h"
+#include "klib/kvec.h"
 #include "nvim/eval/typval.h"
 #include "nvim/event/defs.h"
 #include "nvim/event/loop.h"
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 80890acb7d..1ebc318809 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -8,6 +8,7 @@
 
 #include "auto/config.h"
 #include "nvim/ascii_defs.h"
+#include "nvim/buffer_defs.h"
 #include "nvim/charset.h"
 #include "nvim/cmdexpand.h"
 #include "nvim/eval.h"
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 5dc9ddfd60..510736b2a0 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -4,22 +4,18 @@
 
 #include 
 #include 
+#include 
 #include 
 
-#include "nvim/api/buffer.h"
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
-#include "nvim/api/vim.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/buffer.h"
 #include "nvim/buffer_defs.h"
-#include "nvim/buffer_updates.h"
-#include "nvim/change.h"
 #include "nvim/charset.h"
 #include "nvim/cmdexpand.h"
 #include "nvim/drawscreen.h"
-#include "nvim/edit.h"
 #include "nvim/errors.h"
 #include "nvim/eval/typval.h"
 #include "nvim/ex_cmds.h"
@@ -27,6 +23,7 @@
 #include "nvim/extmark.h"
 #include "nvim/extmark_defs.h"
 #include "nvim/garray.h"
+#include "nvim/garray_defs.h"
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
@@ -46,7 +43,6 @@
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/plines.h"
 #include "nvim/popupmenu.h"
 #include "nvim/pos_defs.h"
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index 1b4f4a2029..65d341cac3 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -5,6 +5,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "nvim/ascii_defs.h"
 #include "nvim/charset.h"
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index e9e0228ec4..44b66c4f73 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -38,7 +38,6 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/help.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index c91c112c3c..de9a7e580f 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -12,6 +12,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index ff7471025f..d849a18879 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -43,6 +43,7 @@
 #include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/option.h"
 #include "nvim/option_defs.h"
diff --git a/src/nvim/search.c b/src/nvim/search.c
index f06b679b0d..faa14dfaf4 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -30,10 +30,10 @@
 #include "nvim/fileio.h"
 #include "nvim/fold.h"
 #include "nvim/garray.h"
+#include "nvim/garray_defs.h"
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/indent_c.h"
 #include "nvim/insexpand.h"
@@ -41,7 +41,6 @@
 #include "nvim/mark.h"
 #include "nvim/mark_defs.h"
 #include "nvim/mbyte.h"
-#include "nvim/mbyte_defs.h"
 #include "nvim/memline.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 1b6c1a6375..d92f3c9002 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -1,9 +1,10 @@
 #pragma once
 
 #include 
+#include 
 #include 
 
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/normal_defs.h"  // IWYU pragma: keep
 #include "nvim/os/time_defs.h"
 #include "nvim/pos_defs.h"
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 9e9f762cd5..d60f2368ce 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -2,21 +2,22 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
 #include 
 
 #include "auto/config.h"
+#include "klib/kvec.h"
+#include "mpack/mpack_core.h"
 #include "nvim/api/keysets_defs.h"
 #include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
 #include "nvim/api/private/helpers.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/buffer.h"
 #include "nvim/buffer_defs.h"
 #include "nvim/cmdhist.h"
-#include "nvim/errors.h"
 #include "nvim/eval.h"
 #include "nvim/eval/decode.h"
 #include "nvim/eval/encode.h"
@@ -26,8 +27,6 @@
 #include "nvim/ex_cmds_defs.h"
 #include "nvim/ex_docmd.h"
 #include "nvim/fileio.h"
-#include "nvim/garray.h"
-#include "nvim/garray_defs.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/hashtab.h"
@@ -38,8 +37,10 @@
 #include "nvim/mark_defs.h"
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/msgpack_rpc/packer.h"
+#include "nvim/msgpack_rpc/packer_defs.h"
 #include "nvim/msgpack_rpc/unpacker.h"
 #include "nvim/normal_defs.h"
 #include "nvim/ops.h"
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
index 7d736dadc7..558e020180 100644
--- a/src/nvim/shada.h
+++ b/src/nvim/shada.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h"  // IWYU pragma: keep
 
 /// Flags for shada_read_file and children
 typedef enum {
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index f8e7eeaca4..a6e72b8855 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -32,8 +32,6 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
-#include "nvim/grid_defs.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/macros_defs.h"
@@ -43,7 +41,6 @@
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
-#include "nvim/move.h"
 #include "nvim/pos_defs.h"
 #include "nvim/sign.h"
 #include "nvim/sign_defs.h"
diff --git a/src/nvim/state.h b/src/nvim/state.h
index 8220d90a67..2cc493de46 100644
--- a/src/nvim/state.h
+++ b/src/nvim/state.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "nvim/state_defs.h"  // IWYU pragma: keep
-#include "nvim/types_defs.h"
+#include "nvim/types_defs.h"  // IWYU pragma: keep
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "state.h.generated.h"
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index 4e78067d46..d47340505a 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -35,7 +35,6 @@
 #include "nvim/normal.h"
 #include "nvim/option.h"
 #include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
 #include "nvim/os/os.h"
 #include "nvim/os/os_defs.h"
 #include "nvim/path.h"
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 2de65391cc..7505eaba64 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -9,6 +9,8 @@
 #include 
 
 #include "auto/config.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
 #include "nvim/ascii_defs.h"
 #include "nvim/assert_defs.h"
 #include "nvim/charset.h"
@@ -20,12 +22,12 @@
 #include "nvim/garray.h"
 #include "nvim/garray_defs.h"
 #include "nvim/gettext_defs.h"
-#include "nvim/globals.h"
 #include "nvim/macros_defs.h"
 #include "nvim/math.h"
 #include "nvim/mbyte.h"
 #include "nvim/mbyte_defs.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/message.h"
 #include "nvim/option.h"
 #include "nvim/plines.h"
diff --git a/src/nvim/strings.h b/src/nvim/strings.h
index c2d078615d..e33be2e076 100644
--- a/src/nvim/strings.h
+++ b/src/nvim/strings.h
@@ -5,7 +5,7 @@
 
 #include "auto/config.h"
 #include "klib/kvec.h"
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h"  // IWYU pragma: keep
 #include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
 #include "nvim/os/os_defs.h"
 #include "nvim/types_defs.h"  // IWYU pragma: keep
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 03f20047a5..7160c757bb 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -28,7 +28,6 @@
 #include "nvim/globals.h"
 #include "nvim/hashtab.h"
 #include "nvim/hashtab_defs.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
 #include "nvim/indent_c.h"
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 5844d8d3f2..cdce97fc01 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -34,7 +34,6 @@
 #include "nvim/hashtab.h"
 #include "nvim/hashtab_defs.h"
 #include "nvim/help.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/input.h"
 #include "nvim/insexpand.h"
diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c
index b3b9cc6b44..ca2829fecb 100644
--- a/src/nvim/textformat.c
+++ b/src/nvim/textformat.c
@@ -18,6 +18,7 @@
 #include "nvim/globals.h"
 #include "nvim/indent.h"
 #include "nvim/indent_c.h"
+#include "nvim/macros_defs.h"
 #include "nvim/mark.h"
 #include "nvim/mbyte.h"
 #include "nvim/memline.h"
diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c
index 45978765bf..e3b3bba7c1 100644
--- a/src/nvim/textobject.c
+++ b/src/nvim/textobject.c
@@ -3,7 +3,6 @@
 #include 
 #include 
 #include 
-#include 
 
 #include "nvim/ascii_defs.h"
 #include "nvim/buffer_defs.h"
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 744d306c06..bf00a6a82e 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -8,7 +8,6 @@
 #include "nvim/api/private/helpers.h"
 #include "nvim/event/loop.h"
 #include "nvim/event/rstream.h"
-#include "nvim/event/stream.h"
 #include "nvim/macros_defs.h"
 #include "nvim/main.h"
 #include "nvim/map_defs.h"
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index f6aaff30de..e48982f9a4 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include 
+#include 
 #include 
 #include 
 
diff --git a/src/nvim/tui/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c
index 28c7eaccfd..52349b0abd 100644
--- a/src/nvim/tui/termkey/driver-csi.c
+++ b/src/nvim/tui/termkey/driver-csi.c
@@ -1,11 +1,10 @@
 #include 
-#include 
+#include 
 #include 
 
 #include "nvim/memory.h"
 #include "nvim/tui/termkey/driver-csi.h"
 #include "nvim/tui/termkey/termkey-internal.h"
-#include "nvim/tui/termkey/termkey.h"
 #include "nvim/tui/termkey/termkey_defs.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/tui/termkey/driver-csi.h b/src/nvim/tui/termkey/driver-csi.h
index 0abd8b5c2e..644cc9d58b 100644
--- a/src/nvim/tui/termkey/driver-csi.h
+++ b/src/nvim/tui/termkey/driver-csi.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "nvim/tui/termkey/termkey_defs.h"
+#include "nvim/tui/termkey/termkey_defs.h"  // IWYU pragma: keep
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "tui/termkey/driver-csi.h.generated.h"
diff --git a/src/nvim/tui/termkey/driver-ti.c b/src/nvim/tui/termkey/driver-ti.c
index 745ee9902f..e402f93e93 100644
--- a/src/nvim/tui/termkey/driver-ti.c
+++ b/src/nvim/tui/termkey/driver-ti.c
@@ -1,16 +1,16 @@
-#include 
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
-#include 
 #include 
+#include 
 
 #include "nvim/memory.h"
 #include "nvim/tui/termkey/driver-ti.h"
 #include "nvim/tui/termkey/termkey-internal.h"
-#include "nvim/tui/termkey/termkey.h"
+#include "nvim/tui/termkey/termkey_defs.h"
 
 #ifndef _WIN32
 # include 
diff --git a/src/nvim/tui/termkey/driver-ti.h b/src/nvim/tui/termkey/driver-ti.h
index df9bd72d5b..6dbcb11344 100644
--- a/src/nvim/tui/termkey/driver-ti.h
+++ b/src/nvim/tui/termkey/driver-ti.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "nvim/tui/termkey/termkey_defs.h"
+#include "nvim/tui/termkey/termkey_defs.h"  // IWYU pragma: keep
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "tui/termkey/driver-ti.h.generated.h"
diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c
index e6440118f3..8fdaa1d4f4 100644
--- a/src/nvim/tui/termkey/termkey.c
+++ b/src/nvim/tui/termkey/termkey.c
@@ -1,9 +1,9 @@
 #include 
 #include 
-#include 
 #include 
 #include 
 
+#include "nvim/macros_defs.h"
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
 #include "nvim/tui/termkey/driver-csi.h"
@@ -13,9 +13,7 @@
 #include "nvim/tui/termkey/termkey_defs.h"
 
 #ifndef _WIN32
-# include 
-# include 
-# include 
+# include 
 #else
 # include 
 #endif
diff --git a/src/nvim/tui/termkey/termkey.h b/src/nvim/tui/termkey/termkey.h
index 21ed141346..5cf9894cac 100644
--- a/src/nvim/tui/termkey/termkey.h
+++ b/src/nvim/tui/termkey/termkey.h
@@ -1,9 +1,9 @@
 #pragma once
 
-#include 
-#include 
+#include   // IWYU pragma: keep
+#include   // IWYU pragma: keep
 
-#include "nvim/tui/termkey/termkey_defs.h"
+#include "nvim/tui/termkey/termkey_defs.h"  // IWYU pragma: keep
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "tui/termkey/termkey.h.generated.h"
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index d514a597b1..693b45e80e 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1,9 +1,9 @@
 // Terminal UI functions. Invoked (by ui_client.c) on the UI process.
 
 #include 
+#include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -22,8 +22,6 @@
 #include "nvim/event/stream.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
-#include "nvim/grid_defs.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/log.h"
 #include "nvim/macros_defs.h"
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index f7b5f28cad..4f443028b3 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -6,7 +6,6 @@
 #include 
 #include 
 
-#include "klib/kvec.h"
 #include "nvim/api/private/helpers.h"
 #include "nvim/api/private/validate.h"
 #include "nvim/api/ui.h"
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index adaf3eadb8..af946d799a 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -1,5 +1,6 @@
 /// Nvim's own UI client, which attaches to a child or remote Nvim server.
 
+#include 
 #include 
 #include 
 #include 
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 45401ca5e1..8d791a4c38 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -89,7 +89,6 @@
 #include "nvim/buffer_updates.h"
 #include "nvim/change.h"
 #include "nvim/cursor.h"
-#include "nvim/decoration.h"
 #include "nvim/drawscreen.h"
 #include "nvim/edit.h"
 #include "nvim/errors.h"
@@ -107,12 +106,10 @@
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/macros_defs.h"
 #include "nvim/mark.h"
 #include "nvim/mark_defs.h"
-#include "nvim/marktree_defs.h"
 #include "nvim/mbyte.h"
 #include "nvim/memline.h"
 #include "nvim/memline_defs.h"
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
index d27899b10f..2aa789a2ee 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -18,7 +18,6 @@
 #include "nvim/garray.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
-#include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/keycodes.h"
 #include "nvim/lua/executor.h"
@@ -26,6 +25,7 @@
 #include "nvim/mapping.h"
 #include "nvim/mbyte.h"
 #include "nvim/memory.h"
+#include "nvim/memory_defs.h"
 #include "nvim/menu.h"
 #include "nvim/message.h"
 #include "nvim/option_vars.h"
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 368bf79cf4..fc8819bfcc 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -9,7 +9,6 @@
 #include 
 #include 
 #include 
-#include 
 
 #include "auto/versiondef.h"  // version info generated by the build system
 #include "auto/versiondef_git.h"
@@ -23,6 +22,7 @@
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
 #include "nvim/grid.h"
+#include "nvim/grid_defs.h"
 #include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/lua/executor.h"
@@ -33,6 +33,7 @@
 #include "nvim/os/os.h"
 #include "nvim/strings.h"
 #include "nvim/ui.h"
+#include "nvim/ui_defs.h"
 #include "nvim/version.h"
 #include "nvim/window.h"
 
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 3eb505d0ce..8232d0aa6c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1,9 +1,7 @@
 #include 
-#include 
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 
@@ -52,7 +50,6 @@
 #include "nvim/mark.h"
 #include "nvim/mark_defs.h"
 #include "nvim/match.h"
-#include "nvim/mbyte.h"
 #include "nvim/memory.h"
 #include "nvim/message.h"
 #include "nvim/mouse.h"
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
index b8a51d686d..3e791e2beb 100644
--- a/src/nvim/winfloat.c
+++ b/src/nvim/winfloat.c
@@ -10,7 +10,6 @@
 #include "nvim/ascii_defs.h"
 #include "nvim/autocmd.h"
 #include "nvim/buffer_defs.h"
-#include "nvim/decoration.h"
 #include "nvim/drawscreen.h"
 #include "nvim/errors.h"
 #include "nvim/globals.h"
-- 
cgit 


From 25abcd243e413f960a7a58a44689e8bfbbc3dec7 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Mon, 23 Dec 2024 15:14:40 +0100
Subject: fix: fix broken wasmtime build

Regression from 2a7d0ed6145bf3f8b139c2694563f460f829813a, which removed
header that is only needed if wasmtime support is enabled. Prevent this
from happening again by wrapping the include in a `HAVE_WASMTIME` check.
---
 src/nvim/lua/treesitter.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 3e5d3e24fe..28ad2cf4d3 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -17,6 +17,8 @@
 
 #ifdef HAVE_WASMTIME
 # include 
+
+# include "nvim/os/fs.h"
 #endif
 
 #include "nvim/api/private/helpers.h"
-- 
cgit 


From a1fa2b3a4e5c8e13423e2c656bb5d4a5fab72acc Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Mon, 23 Dec 2024 07:39:25 -0800
Subject: refactor(eval): move funcs to deprecated.c #31650

---
 src/nvim/eval/deprecated.c | 114 +++++++++++++++++++++++++++++++++++++++++++++
 src/nvim/eval/funcs.c      | 113 +-------------------------------------------
 2 files changed, 115 insertions(+), 112 deletions(-)

diff --git a/src/nvim/eval/deprecated.c b/src/nvim/eval/deprecated.c
index 79874f6647..0fc16b605d 100644
--- a/src/nvim/eval/deprecated.c
+++ b/src/nvim/eval/deprecated.c
@@ -1,12 +1,15 @@
 #include                 // for true
 
+#include "nvim/channel.h"
 #include "nvim/errors.h"
+#include "nvim/eval.h"
 #include "nvim/eval/deprecated.h"
 #include "nvim/eval/funcs.h"
 #include "nvim/eval/typval.h"
 #include "nvim/eval/typval_defs.h"
 #include "nvim/ex_cmds.h"
 #include "nvim/gettext_defs.h"      // for _
+#include "nvim/globals.h"
 #include "nvim/macros_defs.h"       // for S_LEN
 #include "nvim/message.h"           // for semsg
 #include "nvim/types_defs.h"
@@ -15,6 +18,117 @@
 # include "eval/deprecated.c.generated.h"  // IWYU pragma: keep
 #endif
 
+/// "rpcstart()" function (DEPRECATED)
+void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+  rettv->v_type = VAR_NUMBER;
+  rettv->vval.v_number = 0;
+
+  if (check_secure()) {
+    return;
+  }
+
+  if (argvars[0].v_type != VAR_STRING
+      || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
+    // Wrong argument types
+    emsg(_(e_invarg));
+    return;
+  }
+
+  list_T *args = NULL;
+  int argsl = 0;
+  if (argvars[1].v_type == VAR_LIST) {
+    args = argvars[1].vval.v_list;
+    argsl = tv_list_len(args);
+    // Assert that all list items are strings
+    int i = 0;
+    TV_LIST_ITER_CONST(args, arg, {
+      if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
+        semsg(_("E5010: List item %d of the second argument is not a string"),
+              i);
+        return;
+      }
+      i++;
+    });
+  }
+
+  if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
+    emsg(_(e_api_spawn_failed));
+    return;
+  }
+
+  // Allocate extra memory for the argument vector and the NULL pointer
+  int argvl = argsl + 2;
+  char **argv = xmalloc(sizeof(char *) * (size_t)argvl);
+
+  // Copy program name
+  argv[0] = xstrdup(argvars[0].vval.v_string);
+
+  int i = 1;
+  // Copy arguments to the vector
+  if (argsl > 0) {
+    TV_LIST_ITER_CONST(args, arg, {
+      argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
+    });
+  }
+
+  // The last item of argv must be NULL
+  argv[i] = NULL;
+
+  Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT,
+                                    CALLBACK_READER_INIT, CALLBACK_NONE,
+                                    false, true, false, false,
+                                    kChannelStdinPipe, NULL, 0, 0, NULL,
+                                    &rettv->vval.v_number);
+  if (chan) {
+    channel_create_event(chan, NULL);
+  }
+}
+
+/// "rpcstop()" function
+void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+  rettv->v_type = VAR_NUMBER;
+  rettv->vval.v_number = 0;
+
+  if (check_secure()) {
+    return;
+  }
+
+  if (argvars[0].v_type != VAR_NUMBER) {
+    // Wrong argument types
+    emsg(_(e_invarg));
+    return;
+  }
+
+  // if called with a job, stop it, else closes the channel
+  uint64_t id = (uint64_t)argvars[0].vval.v_number;
+  if (find_job(id, false)) {
+    f_jobstop(argvars, rettv, fptr);
+  } else {
+    const char *error;
+    rettv->vval.v_number =
+      channel_close((uint64_t)argvars[0].vval.v_number, kChannelPartRpc, &error);
+    if (!rettv->vval.v_number) {
+      emsg(error);
+    }
+  }
+}
+
+/// "last_buffer_nr()" function.
+void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+  int n = 0;
+
+  FOR_ALL_BUFFERS(buf) {
+    if (n < buf->b_fnum) {
+      n = buf->b_fnum;
+    }
+  }
+
+  rettv->vval.v_number = n;
+}
+
 /// "termopen(cmd[, cwd])" function
 void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 {
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index c52c915f76..a0c18a4c95 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -4130,7 +4130,7 @@ void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 }
 
 /// "jobstop()" function
-static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 {
   rettv->v_type = VAR_NUMBER;
   rettv->vval.v_number = 0;
@@ -4317,20 +4317,6 @@ static void f_keytrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   xfree(escaped);
 }
 
-/// "last_buffer_nr()" function.
-static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
-  int n = 0;
-
-  FOR_ALL_BUFFERS(buf) {
-    if (n < buf->b_fnum) {
-      n = buf->b_fnum;
-    }
-  }
-
-  rettv->vval.v_number = n;
-}
-
 /// "len()" function
 static void f_len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 {
@@ -6425,103 +6411,6 @@ end:
   api_clear_error(&err);
 }
 
-/// "rpcstart()" function (DEPRECATED)
-static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
-  rettv->v_type = VAR_NUMBER;
-  rettv->vval.v_number = 0;
-
-  if (check_secure()) {
-    return;
-  }
-
-  if (argvars[0].v_type != VAR_STRING
-      || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
-    // Wrong argument types
-    emsg(_(e_invarg));
-    return;
-  }
-
-  list_T *args = NULL;
-  int argsl = 0;
-  if (argvars[1].v_type == VAR_LIST) {
-    args = argvars[1].vval.v_list;
-    argsl = tv_list_len(args);
-    // Assert that all list items are strings
-    int i = 0;
-    TV_LIST_ITER_CONST(args, arg, {
-      if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
-        semsg(_("E5010: List item %d of the second argument is not a string"),
-              i);
-        return;
-      }
-      i++;
-    });
-  }
-
-  if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
-    emsg(_(e_api_spawn_failed));
-    return;
-  }
-
-  // Allocate extra memory for the argument vector and the NULL pointer
-  int argvl = argsl + 2;
-  char **argv = xmalloc(sizeof(char *) * (size_t)argvl);
-
-  // Copy program name
-  argv[0] = xstrdup(argvars[0].vval.v_string);
-
-  int i = 1;
-  // Copy arguments to the vector
-  if (argsl > 0) {
-    TV_LIST_ITER_CONST(args, arg, {
-      argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
-    });
-  }
-
-  // The last item of argv must be NULL
-  argv[i] = NULL;
-
-  Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT,
-                                    CALLBACK_READER_INIT, CALLBACK_NONE,
-                                    false, true, false, false,
-                                    kChannelStdinPipe, NULL, 0, 0, NULL,
-                                    &rettv->vval.v_number);
-  if (chan) {
-    channel_create_event(chan, NULL);
-  }
-}
-
-/// "rpcstop()" function
-static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
-  rettv->v_type = VAR_NUMBER;
-  rettv->vval.v_number = 0;
-
-  if (check_secure()) {
-    return;
-  }
-
-  if (argvars[0].v_type != VAR_NUMBER) {
-    // Wrong argument types
-    emsg(_(e_invarg));
-    return;
-  }
-
-  // if called with a job, stop it, else closes the channel
-  uint64_t id = (uint64_t)argvars[0].vval.v_number;
-  if (find_job(id, false)) {
-    f_jobstop(argvars, rettv, fptr);
-  } else {
-    const char *error;
-    rettv->vval.v_number =
-      channel_close((uint64_t)argvars[0].vval.v_number, kChannelPartRpc, &error);
-    if (!rettv->vval.v_number) {
-      emsg(error);
-    }
-  }
-}
-
 static void screenchar_adjust(ScreenGrid **grid, int *row, int *col)
 {
   // TODO(bfredl): this is a hack for legacy tests which use screenchar()
-- 
cgit 


From d74c74aae35e79591426bac786a02c8b008cef5d Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Mon, 23 Dec 2024 13:16:42 +0100
Subject: build: remove `lintcommit` from `lint` target

Previously, `make lint` would invoke `lintcommit` which would fail if
there were fixup or other disallowed commits. This would disrupt local
development as developers would often want non-commit linting to work
early on without needing to adhere to the strict commit rules.
---
 CMakeLists.txt  | 2 +-
 CONTRIBUTING.md | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae5704cfe4..a19c04c26f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -271,7 +271,7 @@ add_custom_target(lintcommit
 add_dependencies(lintcommit nvim_bin)
 
 add_custom_target(lint)
-add_dependencies(lint lintc lintlua lintsh lintcommit)
+add_dependencies(lint lintc lintlua lintsh)
 
 # Format
 add_glob_target(
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f40d4c54b7..324cb3e13a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -83,7 +83,7 @@ a comment.
 ### Commit messages
 
 Follow the [conventional commits guidelines][conventional_commits] to *make reviews easier* and to make
-the VCS/git logs more valuable. The structure of a commit message is:
+the VCS/git logs more valuable (try `make lintcommit`). The structure of a commit message is:
 
     type(scope): subject
 
-- 
cgit 


From ffaab09e998678ba2309821300ac10d62f3d3a78 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Mon, 23 Dec 2024 11:13:19 -0800
Subject: fix(build):  is system-dependent #31705
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem:
Since 2a7d0ed6145bf3f8b139c2694563f460f829813a, build fails with glibc
version 2.28 / RHEL8 (where `termios.h` does not include unistd.h and is
therefore missing `_POSIX_VDISABLE`):

    …/src/nvim/tui/termkey/termkey.c: In function 'termkey_start':
    …/src/nvim/tui/termkey/termkey.c:516:31: error: '_POSIX_VDISABLE' undeclared (first use in this function)
      516 |         termios.c_cc[VQUIT] = _POSIX_VDISABLE;
          |                               ^~~~~~~~~~~~~~~
    …/src/nvim/tui/termkey/termkey.c:516:31: note: each undeclared identifier is reported only once for each function it appears in

Solution:
- Undo the `` change and mark the imports with `IWYU pragma: keep`.
---
 src/nvim/tui/termkey/termkey.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c
index 8fdaa1d4f4..f5736e9b69 100644
--- a/src/nvim/tui/termkey/termkey.c
+++ b/src/nvim/tui/termkey/termkey.c
@@ -13,7 +13,10 @@
 #include "nvim/tui/termkey/termkey_defs.h"
 
 #ifndef _WIN32
-# include 
+// Include these directly instead of  which is system-dependent. #31704
+# include   // IWYU pragma: keep
+# include   // IWYU pragma: keep
+# include   // IWYU pragma: keep
 #else
 # include 
 #endif
-- 
cgit 


From 4cbeb6fa3cc764fd5605bccf7362f7a249d6df81 Mon Sep 17 00:00:00 2001
From: Yorick Peterse 
Date: Mon, 23 Dec 2024 20:57:15 +0100
Subject: fix(diagnostic): silence :chistory #31701

vim.diagnostic.set_list() uses chistory to restore the actively selected
entry whenever necessary. This however also results in it displaying
some output in the message bar, but this output isn't useful (and can
even be distracting) when opening the quickfix window. This fixes this
by silencing the chistory command.
---
 runtime/lua/vim/diagnostic.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 39ef18e0b4..90f967fe79 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -874,7 +874,7 @@ local function set_list(loclist, opts)
     if not loclist then
       -- First navigate to the diagnostics quickfix list.
       local nr = vim.fn.getqflist({ id = qf_id, nr = 0 }).nr
-      api.nvim_command(nr .. 'chistory')
+      api.nvim_command(('silent %dchistory'):format(nr))
 
       -- Now open the quickfix list.
       api.nvim_command('botright cwindow')
-- 
cgit 


From c51bf5a6b24928ac04d0bb129b1b424d4c78f28d Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Mon, 23 Dec 2024 15:39:36 -0600
Subject: fix(terminal): set cursor cell percentage (#31703)

Fixes: https://github.com/neovim/neovim/issues/31685
---
 src/nvim/terminal.c                      |  2 ++
 test/functional/terminal/cursor_spec.lua | 10 +++++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 61eede5ae1..673a6ef4be 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -1961,9 +1961,11 @@ static void refresh_cursor(Terminal *term)
     break;
   case VTERM_PROP_CURSORSHAPE_UNDERLINE:
     shape_table[SHAPE_IDX_TERM].shape = SHAPE_HOR;
+    shape_table[SHAPE_IDX_TERM].percentage = 20;
     break;
   case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
     shape_table[SHAPE_IDX_TERM].shape = SHAPE_VER;
+    shape_table[SHAPE_IDX_TERM].percentage = 25;
     break;
   }
 
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index 8594a9ce16..12a20ddc0d 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -152,7 +152,7 @@ describe(':terminal cursor', function()
     end)
   end)
 
-  it('can be modified by application #3681', function()
+  it('can be modified by application #3681 #31685', function()
     skip(is_os('win'), '#31587')
 
     local states = {
@@ -181,7 +181,15 @@ describe(':terminal cursor', function()
             eq(0, screen._mode_info[terminal_mode_idx].blinkon)
             eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
           end
+
           eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape)
+
+          -- Cell percentages are hard coded for each shape in terminal.c
+          if v.shape == 'horizontal' then
+            eq(20, screen._mode_info[terminal_mode_idx].cell_percentage)
+          elseif v.shape == 'vertical' then
+            eq(25, screen._mode_info[terminal_mode_idx].cell_percentage)
+          end
         end,
       })
     end
-- 
cgit 


From 14ee1de7e58276be4b80bc262cd0435eb9eeb01c Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Tue, 24 Dec 2024 10:31:35 +0100
Subject: vim-patch:9.1.0958: filetype: supertux2 config files detected as lisp

Problem:  filetype: supertux2 config files detected as lisp
Solution: detect supertux2 config files as scheme instead
          (Wu, Zhenyu)

References:
https://github.com/SuperTux/supertux/wiki/S-Expression

supertux uses #t and #f as bool type, which is same as scheme, not
common lisp

closes: vim/vim#16287

https://github.com/vim/vim/commit/e62d93ead10b4c5818e3c0b7551f1784d24bfe33

Co-authored-by: Wu, Zhenyu 
---
 runtime/lua/vim/filetype.lua       | 4 ++--
 test/old/testdir/test_filetype.vim | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 6fa1684704..80d25fe515 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -699,7 +699,6 @@ local extension = {
   el = 'lisp',
   lsp = 'lisp',
   asd = 'lisp',
-  stsg = 'lisp',
   lt = 'lite',
   lite = 'lite',
   livemd = 'livebook',
@@ -1072,6 +1071,7 @@ local extension = {
   ss = 'scheme',
   scm = 'scheme',
   sld = 'scheme',
+  stsg = 'scheme',
   sce = 'scilab',
   sci = 'scilab',
   scss = 'scss',
@@ -2159,8 +2159,8 @@ local pattern = {
     ['/gitolite%-admin/conf/'] = starsetf('gitolite'),
     ['/%.i3/config$'] = 'i3config',
     ['/i3/config$'] = 'i3config',
-    ['/supertux2/config$'] = 'lisp',
     ['/%.mplayer/config$'] = 'mplayerconf',
+    ['/supertux2/config$'] = 'scheme',
     ['/neofetch/config%.conf$'] = 'sh',
     ['/%.ssh/config$'] = 'sshconfig',
     ['/%.sway/config$'] = 'swayconfig',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index eb4d14c800..93fdb7fd27 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -426,7 +426,7 @@ func s:GetFilenameChecks() abort
     \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
     \ 'liquidsoap': ['file.liq'],
     \ 'liquid': ['file.liquid'],
-    \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc', 'file.stsg', 'any/local/share/supertux2/config'],
+    \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
     \ 'lite': ['file.lite', 'file.lt'],
     \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'],
     \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'],
@@ -668,7 +668,7 @@ func s:GetFilenameChecks() abort
     \ 'sather': ['file.sa'],
     \ 'sbt': ['file.sbt'],
     \ 'scala': ['file.scala'],
-    \ 'scheme': ['file.scm', 'file.ss', 'file.sld'],
+    \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config'],
     \ 'scilab': ['file.sci', 'file.sce'],
     \ 'screen': ['.screenrc', 'screenrc'],
     \ 'scss': ['file.scss'],
-- 
cgit 


From a103ec7449c4a318788b519cdeac2e525136b66b Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Tue, 24 Dec 2024 17:50:08 +0800
Subject: vim-patch:9.1.0954: popupmenu.c can be improved

Problem:  popupmenu.c can be improved
Solution: slightly refactor the logic
          (glepnir)

closes: vim/vim#16271

Replace some if blocks and combine user attr abstract to an inline
function.

https://github.com/vim/vim/commit/89a107efd141d5a1fed850af80a74900077666cf

Co-authored-by: glepnir 
---
 src/nvim/popupmenu.c | 68 ++++++++++++++++++++++------------------------------
 1 file changed, 28 insertions(+), 40 deletions(-)

diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 510736b2a0..70a479239e 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -439,6 +439,7 @@ static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr)
   const char *ptr = text;
   int cell_idx = 0;
   uint32_t char_pos = 0;
+  bool is_select = hlf == HLF_PSI;
 
   while (*ptr != NUL) {
     int new_attr = win_hl_attr(curwin, (int)hlf);
@@ -447,14 +448,14 @@ static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr)
       // Handle fuzzy matching
       for (int i = 0; i < ga->ga_len; i++) {
         if (char_pos == ((uint32_t *)ga->ga_data)[i]) {
-          new_attr = win_hl_attr(curwin, hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI);
+          new_attr = win_hl_attr(curwin, is_select ? HLF_PMSI : HLF_PMNI);
           new_attr = hl_combine_attr(win_hl_attr(curwin, HLF_PMNI), new_attr);
           new_attr = hl_combine_attr(win_hl_attr(curwin, (int)hlf), new_attr);
           break;
         }
       }
     } else if (matched_start && ptr < text + leader_len) {
-      new_attr = win_hl_attr(curwin, hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI);
+      new_attr = win_hl_attr(curwin, is_select ? HLF_PMSI : HLF_PMNI);
       new_attr = hl_combine_attr(win_hl_attr(curwin, HLF_PMNI), new_attr);
       new_attr = hl_combine_attr(win_hl_attr(curwin, (int)hlf), new_attr);
     }
@@ -520,6 +521,15 @@ static inline char *pum_get_item(int index, int type)
   return NULL;
 }
 
+static inline int pum_user_attr_combine(int idx, int type, int attr)
+{
+  int user_attr[] = {
+    pum_array[idx].pum_user_abbr_hlattr,
+    pum_array[idx].pum_user_kind_hlattr,
+  };
+  return user_attr[type] > 0 ? hl_combine_attr(attr, user_attr[type]) : attr;
+}
+
 /// Redraw the popup menu, using "pum_first" and "pum_selected".
 void pum_redraw(void)
 {
@@ -583,19 +593,16 @@ void pum_redraw(void)
                           pum_row - row_off, pum_left_col, false, pum_grid.zindex);
   }
 
+  int scroll_range = pum_size - pum_height;
   // Never display more than we have
-  if (pum_first > pum_size - pum_height) {
-    pum_first = pum_size - pum_height;
-  }
+  pum_first = MIN(pum_first, scroll_range);
 
   if (pum_scrollbar) {
     thumb_height = pum_height * pum_height / pum_size;
     if (thumb_height == 0) {
       thumb_height = 1;
     }
-    thumb_pos = (pum_first * (pum_height - thumb_height)
-                 + (pum_size - pum_height) / 2)
-                / (pum_size - pum_height);
+    thumb_pos = (pum_first * (pum_height - thumb_height) + scroll_range / 2) / scroll_range;
   }
 
   for (int i = 0; i < pum_height; i++) {
@@ -633,13 +640,8 @@ void pum_redraw(void)
       attr = win_hl_attr(curwin, (int)hlf);
       attr = hl_combine_attr(win_hl_attr(curwin, HLF_PNI), attr);
       orig_attr = attr;
-      int user_abbr_hlattr = pum_array[idx].pum_user_abbr_hlattr;
-      int user_kind_hlattr = pum_array[idx].pum_user_kind_hlattr;
-      if (item_type == CPT_ABBR && user_abbr_hlattr > 0) {
-        attr = hl_combine_attr(attr, user_abbr_hlattr);
-      }
-      if (item_type == CPT_KIND && user_kind_hlattr > 0) {
-        attr = hl_combine_attr(attr, user_kind_hlattr);
+      if (item_type < 2) {  // try combine attr with user custom
+        attr = pum_user_attr_combine(idx, item_type, attr);
       }
       int width = 0;
       char *s = NULL;
@@ -667,7 +669,7 @@ void pum_redraw(void)
 
             int *attrs = NULL;
             if (item_type == CPT_ABBR) {
-              attrs = pum_compute_text_attrs(st, hlf, user_abbr_hlattr);
+              attrs = pum_compute_text_attrs(st, hlf, pum_array[idx].pum_user_abbr_hlattr);
             }
 
             if (pum_rl) {
@@ -904,6 +906,7 @@ static bool pum_set_selected(int n, int repeat)
 {
   bool resized = false;
   int context = pum_height / 2;
+  int scroll_offset = pum_selected - pum_height;
   int prev_selected = pum_selected;
 
   pum_selected = n;
@@ -933,35 +936,24 @@ static bool pum_set_selected(int n, int repeat)
       } else {
         pum_first = pum_selected;
       }
-    } else if (pum_first < pum_selected - pum_height + 5) {
+    } else if (pum_first < scroll_offset + 5) {
       // scroll up; when we did a jump it's probably a PageDown then
       // scroll a whole page
-      if (pum_first < pum_selected - pum_height + 1 + 2) {
-        pum_first += pum_height - 2;
-        if (pum_first < pum_selected - pum_height + 1) {
-          pum_first = pum_selected - pum_height + 1;
-        }
+      if (pum_first < scroll_offset + 3) {
+        pum_first = MAX(pum_first, scroll_offset + 1);
       } else {
-        pum_first = pum_selected - pum_height + 1;
+        pum_first = scroll_offset + 1;
       }
     }
 
     // Give a few lines of context when possible.
-    if (context > 3) {
-      context = 3;
-    }
+    context = MIN(context, 3);
 
     if (pum_height > 2) {
       if (pum_first > pum_selected - context) {
-        // scroll down
-        pum_first = pum_selected - context;
-
-        if (pum_first < 0) {
-          pum_first = 0;
-        }
+        pum_first = MAX(pum_selected - context, 0);  // scroll down
       } else if (pum_first < pum_selected + context - pum_height + 1) {
-        // scroll up
-        pum_first = pum_selected + context - pum_height + 1;
+        pum_first = pum_selected + context - pum_height + 1; // up
       }
     }
     // adjust for the number of lines displayed
@@ -1048,9 +1040,7 @@ static bool pum_set_selected(int n, int repeat)
           // Increase the height of the preview window to show the
           // text, but no more than 'previewheight' lines.
           if (repeat == 0 && !use_float) {
-            if (lnum > p_pvh) {
-              lnum = (linenr_T)p_pvh;
-            }
+            lnum = MIN(lnum, (linenr_T)p_pvh);
 
             if (curwin->w_height < lnum) {
               win_setheight((int)lnum);
@@ -1313,9 +1303,7 @@ static void pum_position_at_mouse(int min_width)
     pum_width = max_col - pum_col;
   }
 
-  if (pum_width > pum_base_width + 1) {
-    pum_width = pum_base_width + 1;
-  }
+  pum_width = MIN(pum_width, pum_base_width + 1);
 }
 
 /// Select the pum entry at the mouse position.
-- 
cgit 


From 05eca4c04d4d2cc6ad3a2af69d76085135e9b16c Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Tue, 24 Dec 2024 18:12:50 +0800
Subject: vim-patch:9.1.0956: completion may crash, completion highlight wrong
 with preview window

Problem:  completion may crash, completion highlight wrong with preview
          window (after v9.1.0954)
Solution: correctly calculate scroll offset, check for preview window
          when adding extra highlighting
          (glepnir)

when there have a preview window prepare_tagpreview
will change curwin to preview window and this may cause
ComplMatchIns check condition not correct. check wp is curwin
and also the type of wp is not a preview or poup info

fixes: https://github.com/vim/vim/issues/16284
closes: https://github.com/vim/vim/pull/16283

https://github.com/vim/vim/commit/8d0bb6dc9f2e5d94ebb59671d592c1b7fa325ca6
---
 src/nvim/drawline.c                   |  4 ++--
 src/nvim/insexpand.c                  |  6 ++++++
 src/nvim/popupmenu.c                  |  6 +++---
 test/functional/ui/popupmenu_spec.lua | 21 ++++++++++++++++++++-
 test/old/testdir/test_popup.vim       | 21 ++++++++++++++++++++-
 5 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 35a41f840d..3062b0f2a3 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -1495,7 +1495,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
     ptr = line + v;  // "line" may have been updated
   }
 
-  if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
+  if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) {
     area_highlighting = true;
   }
 
@@ -1746,7 +1746,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
         }
 
         // Check if ComplMatchIns highlight is needed.
-        if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
+        if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) {
           int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
           if (ins_match_attr > 0) {
             search_attr = hl_combine_attr(search_attr, ins_match_attr);
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index aee3603d75..7245b0d6ce 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1722,6 +1722,12 @@ bool ins_compl_active(void)
   return compl_started;
 }
 
+/// Return true when wp is the actual completion window
+bool ins_compl_win_active(win_T *wp)
+{
+  return ins_compl_active() && !(wp->w_p_pvw || wp->w_float_is_info);
+}
+
 /// Selected one of the matches.  When false the match was edited or using the
 /// longest common string.
 bool ins_compl_used_match(void)
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 70a479239e..75e5a52829 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -906,10 +906,10 @@ static bool pum_set_selected(int n, int repeat)
 {
   bool resized = false;
   int context = pum_height / 2;
-  int scroll_offset = pum_selected - pum_height;
   int prev_selected = pum_selected;
 
   pum_selected = n;
+  int scroll_offset = pum_selected - pum_height;
   unsigned cur_cot_flags = get_cot_flags();
   bool use_float = (cur_cot_flags & kOptCotFlagPopup) != 0;
 
@@ -940,7 +940,7 @@ static bool pum_set_selected(int n, int repeat)
       // scroll up; when we did a jump it's probably a PageDown then
       // scroll a whole page
       if (pum_first < scroll_offset + 3) {
-        pum_first = MAX(pum_first, scroll_offset + 1);
+        pum_first = MAX(pum_first + pum_height - 2, scroll_offset + 1);
       } else {
         pum_first = scroll_offset + 1;
       }
@@ -953,7 +953,7 @@ static bool pum_set_selected(int n, int repeat)
       if (pum_first > pum_selected - context) {
         pum_first = MAX(pum_selected - context, 0);  // scroll down
       } else if (pum_first < pum_selected + context - pum_height + 1) {
-        pum_first = pum_selected + context - pum_height + 1; // up
+        pum_first = pum_selected + context - pum_height + 1;  // up
       }
     }
     // adjust for the number of lines displayed
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index 75421c6998..d1228d3607 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -5567,11 +5567,15 @@ describe('builtin popupmenu', function()
       -- oldtest: Test_pum_matchins_highlight()
       it('with ComplMatchIns highlight', function()
         exec([[
+          let g:change = 0
           func Omni_test(findstart, base)
             if a:findstart
               return col(".")
             endif
-            return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+            if g:change == 0
+              return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+            endif
+            return [#{word: "foo", info: "info"}, #{word: "bar"}, #{word: "你好"}]
           endfunc
           set omnifunc=Omni_test
           hi ComplMatchIns guifg=red
@@ -5663,6 +5667,21 @@ describe('builtin popupmenu', function()
           {2:-- INSERT --}                    |
         ]])
         feed('')
+
+        feed(':let g:change=1S')
+        screen:expect([[
+          info                            |
+          {1:~                               }|*2
+          {3:[Scratch] [Preview]             }|
+          {8:foo}^                             |
+          {s:foo            }{1:                 }|
+          {n:bar            }{1:                 }|
+          {n:你好           }{1:                 }|
+          {1:~                               }|*10
+          {4:[No Name] [+]                   }|
+          {2:-- }{5:match 1 of 3}                 |
+        ]])
+        feed('')
       end)
 
       -- oldtest: Test_pum_matchins_highlight_combine()
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index f16a897b07..e902ea3bc2 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1716,11 +1716,15 @@ endfunc
 func Test_pum_matchins_highlight()
   CheckScreendump
   let lines =<< trim END
+    let g:change = 0
     func Omni_test(findstart, base)
       if a:findstart
         return col(".")
       endif
-      return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+      if g:change == 0
+        return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
+      endif
+      return [#{word: "foo", info: "info"}, #{word: "bar"}, #{word: "你好"}]
     endfunc
     set omnifunc=Omni_test
     hi ComplMatchIns ctermfg=red
@@ -1767,6 +1771,10 @@ func Test_pum_matchins_highlight()
   call VerifyScreenDump(buf, 'Test_pum_matchins_10', {})
   call term_sendkeys(buf, "\")
 
+  call term_sendkeys(buf, ":let g:change=1\S\\")
+  call VerifyScreenDump(buf, 'Test_pum_matchins_11', {})
+  call term_sendkeys(buf, "\")
+
   call StopVimInTerminal(buf)
 endfunc
 
@@ -1812,4 +1820,15 @@ func Test_pum_matchins_highlight_combine()
   call StopVimInTerminal(buf)
 endfunc
 
+" this used to crash
+func Test_popup_completion_many_ctrlp()
+  new
+  let candidates=repeat(['a0'], 99)
+  call setline(1, candidates)
+  exe ":norm! VGg\"
+  norm! G
+  call feedkeys("o" .. repeat("\", 100), 'tx')
+  bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
cgit 


From 34cd94812d42bb6b9ddd54229eb497f660736b4d Mon Sep 17 00:00:00 2001
From: Mathias Fussenegger 
Date: Mon, 23 Dec 2024 12:37:01 +0100
Subject: feat(test): support and document lua test case debugging

Similar to how there is a `GDB` environment variable to let the nvim
test instances to be run under `gdbserver` this adds a `OSV_PORT`
variable to start nvim test instances with `osv` in blocking mode to let
a debug client attach to it for debugging of `exec_lua` code blocks.
---
 test/README.md               | 160 +++++++++++++++++++++++++++++++++++++++++++
 test/functional/testnvim.lua |  10 +++
 2 files changed, 170 insertions(+)

diff --git a/test/README.md b/test/README.md
index d1b053fca3..5b225980a2 100644
--- a/test/README.md
+++ b/test/README.md
@@ -138,6 +138,163 @@ Debugging tests
   Then put `screen:snapshot_util()` anywhere in your test. See the comments in
   `test/functional/ui/screen.lua` for more info.
 
+Debugging Lua test code
+-----------------------
+
+Debugging Lua test code is a bit involved. Get your shopping list ready, you'll
+need to install and configure:
+
+1. [nvim-dap](https://github.com/mfussenegger/nvim-dap)
+2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode)
+3. [nlua](https://github.com/mfussenegger/nlua)
+4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`)
+5. A `nbusted` command in `$PATH`. This command can be a copy of `busted` with
+   `exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the
+   path to your `nlua`)
+
+
+The setup roughly looks like this:
+
+```
+ ┌─────────────────────────┐
+ │ nvim used for debugging │◄────┐
+ └─────────────────────────┘     │
+            │                    │
+            ▼                    │
+   ┌─────────────────┐           │
+   │ local-lua-debug │           │
+   └─────────────────┘           │
+           │                     │
+           ▼                     │
+      ┌─────────┐                │
+      │ nbusted │                │
+      └─────────┘                │
+           │                     │
+           ▼                     │
+      ┌───────────┐              │
+      │ test-case │              │
+      └───────────┘              │
+           │                     │
+           ▼                     │
+   ┌────────────────────┐        │
+   │ nvim test-instance │        │
+   └────────────────────┘        │
+     │   ┌─────┐                 │
+     └──►│ osv │─────────────────┘
+         └─────┘
+```
+
+
+With these installed you can use a configuration like this:
+
+
+```lua
+local dap = require("dap")
+
+
+local function free_port()
+  local tcp = vim.loop.new_tcp()
+  assert(tcp)
+  tcp:bind('127.0.0.1', 0)
+  local port = tcp:getsockname().port
+  tcp:shutdown()
+  tcp:close()
+  return port
+end
+
+
+local name = "nvim-test-case" -- arbitrary name
+local config = {
+  name = name,
+
+  -- value of type must match the key used in `dap.adapters["local-lua"] = ...` from step 2)
+  type = "local-lua",
+
+  request = "launch",
+  cwd = "${workspaceFolder}",
+  program = {
+    command = "nbusted",
+  },
+  args = {
+    "--ignore-lua",
+    "--lazy",
+    "--helper=test/functional/preload.lua",
+    "--lpath=build/?.lua",
+    "--lpath=?.lua",
+
+    -- path to file to debug, could be replaced with a hardcoded string
+    function()
+      return vim.api.nvim_buf_get_name(0)
+    end,
+
+    -- You can filter to specific test-case by adding:
+    -- '--filter="' .. test_case_name .. '"',
+  },
+  env = {
+    OSV_PORT = free_port
+  }
+}
+
+-- Whenever the config is used it needs to launch a second debug session that attaches to `osv`
+-- This makes it possible to step into `exec_lua` code blocks
+setmetatable(config, {
+
+  __call = function(c)
+    ---@param session dap.Session
+    dap.listeners.after.event_initialized["nvim_debug"] = function(session)
+      if session.config.name ~= name then
+        return
+      end
+      dap.listeners.after.event_initialized["nvim_debug"] = nil
+      vim.defer_fn(function()
+        dap.run({
+          name = "attach-osv",
+          type = "nlua", -- value must match the `dap.adapters` definition key for osv
+          request = "attach",
+          port = session.config.env.OSV_PORT,
+        })
+      end, 500)
+    end
+
+    return c
+  end,
+})
+
+```
+
+You can either add this configuration to your `dap.configurations.lua` list as
+described in `:help dap-configuration` or create it dynamically in a
+user-command or function and call it directly via `dap.run(config)`. The latter
+is useful if you use tree-sitter to find the test case around a cursor location
+with a query like the following and set the `--filter` property to it.
+
+```query
+(function_call
+  name: (identifier) @name (#any-of? @name "describe" "it")
+  arguments: (arguments
+    (string) @str
+  )
+)
+```
+
+Limitations:
+
+- You need to add the following boilerplate to each spec file where you want to
+  be able to stop at breakpoints within the test-case code:
+
+```
+if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
+  require("lldebugger").start()
+end
+```
+
+This is a [local-lua-debugger
+limitation](https://github.com/tomblind/local-lua-debugger-vscode?tab=readme-ov-file#busted)
+
+- You cannot step into code of files which get baked into the nvim binary like
+  the `shared.lua`.
+
+
 Filtering Tests
 ---------------
 
@@ -379,3 +536,6 @@ Number; !must be defined to function properly):
 
 - `NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to
   keep. Default is 1024.
+
+- `OSV_PORT`: (F): launches `osv` listening on the given port within nvim test
+  instances.
diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua
index 43c38d18c0..675ad9e3d7 100644
--- a/test/functional/testnvim.lua
+++ b/test/functional/testnvim.lua
@@ -48,6 +48,16 @@ M.nvim_argv = {
   'unlet g:colors_name',
   '--embed',
 }
+if os.getenv('OSV_PORT') then
+  table.insert(M.nvim_argv, '--cmd')
+  table.insert(
+    M.nvim_argv,
+    string.format(
+      "lua require('osv').launch({ port = %s, blocking = true })",
+      os.getenv('OSV_PORT')
+    )
+  )
+end
 
 -- Directory containing nvim.
 M.nvim_dir = M.nvim_prog:gsub('[/\\][^/\\]+$', '')
-- 
cgit 


From b51110f4a11af114401e626cd4c1f1aec23e81c5 Mon Sep 17 00:00:00 2001
From: Shihua Zeng <76579810+Bekaboo@users.noreply.github.com>
Date: Tue, 24 Dec 2024 12:56:10 -0500
Subject: docs(api): return type of nvim_get_keymap() #31708

---
 runtime/lua/vim/_meta/api.lua               |  4 ++--
 runtime/lua/vim/_meta/api_keysets_extra.lua | 20 ++++++++++++++++++++
 scripts/gen_eval_files.lua                  |  4 ++--
 3 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index bea6df43bb..7297c8ad38 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -452,7 +452,7 @@ function vim.api.nvim_buf_get_extmarks(buffer, ns_id, start, end_, opts) end
 ---
 --- @param buffer integer Buffer handle, or 0 for current buffer
 --- @param mode string Mode short-name ("n", "i", "v", ...)
---- @return vim.api.keyset.keymap[] # Array of |maparg()|-like dictionaries describing mappings.
+--- @return vim.api.keyset.get_keymap[] # Array of |maparg()|-like dictionaries describing mappings.
 --- The "buffer" key holds the associated buffer handle.
 function vim.api.nvim_buf_get_keymap(buffer, mode) end
 
@@ -1414,7 +1414,7 @@ function vim.api.nvim_get_hl_ns(opts) end
 --- Gets a list of global (non-buffer-local) `mapping` definitions.
 ---
 --- @param mode string Mode short-name ("n", "i", "v", ...)
---- @return vim.api.keyset.keymap[] # Array of |maparg()|-like dictionaries describing mappings.
+--- @return vim.api.keyset.get_keymap[] # Array of |maparg()|-like dictionaries describing mappings.
 --- The "buffer" key is always zero.
 function vim.api.nvim_get_keymap(mode) end
 
diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua
index 806b3e49c0..fbef6fa3bc 100644
--- a/runtime/lua/vim/_meta/api_keysets_extra.lua
+++ b/runtime/lua/vim/_meta/api_keysets_extra.lua
@@ -173,6 +173,26 @@ error('Cannot require a meta file')
 --- @field force? true
 --- @field cterm? vim.api.keyset.hl_info.cterm
 
+--- @class vim.api.keyset.get_keymap
+--- @field abbr? 0|1
+--- @field buffer? 0|1
+--- @field callback? function
+--- @field desc? string
+--- @field expr? 0|1
+--- @field lhs? string
+--- @field lhsraw? string
+--- @field lhsrawalt? string
+--- @field lnum? integer
+--- @field mode? string
+--- @field mode_bits? integer
+--- @field noremap? 0|1
+--- @field nowait? 0|1
+--- @field rhs? string
+--- @field script? 0|1
+--- @field scriptversion? integer
+--- @field sid? integer
+--- @field silent? 0|1
+
 --- @class vim.api.keyset.get_mode
 --- @field blocking boolean
 --- @field mode string
diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua
index 0970ae503a..f888972f0d 100755
--- a/scripts/gen_eval_files.lua
+++ b/scripts/gen_eval_files.lua
@@ -26,11 +26,11 @@ local LUA_API_RETURN_OVERRIDES = {
   nvim_buf_get_command = 'table',
   nvim_buf_get_extmark_by_id = 'vim.api.keyset.get_extmark_item_by_id',
   nvim_buf_get_extmarks = 'vim.api.keyset.get_extmark_item[]',
-  nvim_buf_get_keymap = 'vim.api.keyset.keymap[]',
+  nvim_buf_get_keymap = 'vim.api.keyset.get_keymap[]',
   nvim_get_autocmds = 'vim.api.keyset.get_autocmds.ret[]',
   nvim_get_color_map = 'table',
   nvim_get_command = 'table',
-  nvim_get_keymap = 'vim.api.keyset.keymap[]',
+  nvim_get_keymap = 'vim.api.keyset.get_keymap[]',
   nvim_get_mark = 'vim.api.keyset.get_mark',
 
   -- Can also return table, however we need to
-- 
cgit 


From 01e1598072249011ac2cec7318559cea6179e509 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Wed, 25 Dec 2024 11:41:36 +0100
Subject: vim-patch:9.1.0960: filetype: hy history files are not recognized

Problem:  filetype: hy history files are not recognized
Solution: detect '*.hy', '.hy-history' files as hy filetype,
          detect '.lips_repl_history' files are scheme filetype
          (Wu, Zhenyu)

closes: vim/vim#16298

https://github.com/vim/vim/commit/a32daed55933df49a7aed571cc6e400ae01c7976

Co-authored-by: Wu, Zhenyu 
---
 runtime/lua/vim/filetype.lua       | 3 +++
 test/old/testdir/test_filetype.vim | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 80d25fe515..2b5a201c0f 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -592,6 +592,7 @@ local extension = {
   hw = detect.hw,
   module = detect.hw,
   pkg = detect.hw,
+  hy = 'hy',
   iba = 'ibasic',
   ibi = 'ibasic',
   icn = 'icon',
@@ -1603,6 +1604,7 @@ local filename = {
   ['/etc/host.conf'] = 'hostconf',
   ['/etc/hosts.allow'] = 'hostsaccess',
   ['/etc/hosts.deny'] = 'hostsaccess',
+  ['.hy-history'] = 'hy',
   ['hyprland.conf'] = 'hyprlang',
   ['hyprpaper.conf'] = 'hyprlang',
   ['hypridle.conf'] = 'hyprlang',
@@ -1778,6 +1780,7 @@ local filename = {
   ['Rantfile'] = 'ruby',
   Vagrantfile = 'ruby',
   ['smb.conf'] = 'samba',
+  ['.lips_repl_history'] = 'scheme',
   screenrc = 'screen',
   ['.screenrc'] = 'screen',
   ['/etc/sensors3.conf'] = 'sensors',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 93fdb7fd27..e30e464fd7 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -356,6 +356,7 @@ func s:GetFilenameChecks() abort
     \ 'htmlm4': ['file.html.m4'],
     \ 'httest': ['file.htt', 'file.htb'],
     \ 'hurl': ['file.hurl'],
+    \ 'hy': ['file.hy', '.hy-history'],
     \ 'hyprlang': ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf', '/hypr/foo.conf'],
     \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'],
     \ 'ibasic': ['file.iba', 'file.ibi'],
@@ -668,7 +669,7 @@ func s:GetFilenameChecks() abort
     \ 'sather': ['file.sa'],
     \ 'sbt': ['file.sbt'],
     \ 'scala': ['file.scala'],
-    \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config'],
+    \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config', '.lips_repl_history'],
     \ 'scilab': ['file.sci', 'file.sce'],
     \ 'screen': ['.screenrc', 'screenrc'],
     \ 'scss': ['file.scss'],
-- 
cgit 


From 7567f7d3226ab247eb2b743460492c1101045b0e Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Wed, 25 Dec 2024 11:44:46 +0100
Subject: vim-patch:9.1.0961: filetype: TI gel files are not recognized

Problem:  filetype: TI gel files are not recognized
Solution: detect '*.gel' files as gel filetype, include
          get filetype and syntax plugins
          (Wu, Zhenyu)

References:
https://downloads.ti.com/ccs/esd/documents/users_guide/ccs_debug-gel.html

closes: vim/vim#16226

https://github.com/vim/vim/commit/9360de9027aa286e802363ede59c9e97025ae123

Co-authored-by: Wu, Zhenyu 
---
 runtime/ftplugin/gel.vim           | 13 +++++++++++++
 runtime/lua/vim/filetype.lua       |  1 +
 runtime/syntax/gel.vim             | 19 +++++++++++++++++++
 test/old/testdir/test_filetype.vim |  1 +
 4 files changed, 34 insertions(+)
 create mode 100644 runtime/ftplugin/gel.vim
 create mode 100644 runtime/syntax/gel.vim

diff --git a/runtime/ftplugin/gel.vim b/runtime/ftplugin/gel.vim
new file mode 100644
index 0000000000..b1f4def2b8
--- /dev/null
+++ b/runtime/ftplugin/gel.vim
@@ -0,0 +1,13 @@
+" Vim filetype plugin file
+" Language:	TI Code Composer Studio General Extension Language
+" Document:	https://downloads.ti.com/ccs/esd/documents/users_guide/ccs_debug-gel.html
+" Maintainer:	Wu, Zhenyu 
+" Last Change:	2024 Dec 25
+
+if exists("b:did_ftplugin") | finish | endif
+let b:did_ftplugin = 1
+
+setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,://
+setlocal commentstring=/*\ %s\ */
+
+let b:undo_ftplugin = "setl commentstring< comments<"
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 2b5a201c0f..ad74a16e09 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -504,6 +504,7 @@ local extension = {
   gdshader = 'gdshader',
   shader = 'gdshader',
   ged = 'gedcom',
+  gel = 'gel',
   gmi = 'gemtext',
   gemini = 'gemtext',
   gift = 'gift',
diff --git a/runtime/syntax/gel.vim b/runtime/syntax/gel.vim
new file mode 100644
index 0000000000..5f3800273c
--- /dev/null
+++ b/runtime/syntax/gel.vim
@@ -0,0 +1,19 @@
+" Vim syntax file
+" Language:	TI Code Composer Studio General Extension Language
+" Document:	https://downloads.ti.com/ccs/esd/documents/users_guide/ccs_debug-gel.html
+" Maintainer:	Wu, Zhenyu 
+" Last Change:	2024 Dec 25
+
+if exists("b:current_syntax")
+  finish
+endif
+
+runtime! syntax/cpp.vim
+
+syn keyword gelStatement	StartUp GEL_AddInputFile GEL_AddOutputFile GEL_AdvancedReset GEL_AsmStepInto GEL_AsmStepOver GEL_BreakPtAdd GEL_BreakPtDel GEL_BreakPtDisable GEL_BreakPtReset GEL_CancelTimer GEL_Connect GEL_Dialog GEL_DisableFileOutput GEL_DisableRealtime GEL_Disconnect GEL_EnableClock GEL_EnableFileOutput GEL_EnableRealtime GEL_EnableZeroFill GEL_EvalOnTarget GEL_GetBoolDebugProperty GEL_GetBoolDriverProperty GEL_GetBoolTargetDbProperty GEL_GetNumericDebugProperty GEL_GetNumericDriverProperty GEL_GetNumericTargetDbProperty GEL_GetStringDebugProperty GEL_GetStringDriverProperty GEL_GetStringTargetDbProperty GEL_Go GEL_Halt GEL_HandleTargetError GEL_HWBreakPtAdd GEL_HWBreakPtDel GEL_HWBreakPtDisable GEL_HWBreakPtReset GEL_IsConnected GEL_IsHalted GEL_IsInRealtimeMode GEL_IsResetSupported GEL_IsTimerSet GEL_Load GEL_LoadBin GEL_LoadGel GEL_LoadProgramOnly GEL_MapAdd GEL_MapAddStr GEL_MapDelete GEL_MapOff GEL_MapOn GEL_MapReset GEL_MatchesConnection GEL_MemoryFill GEL_MemoryListSupportedTypes GEL_MemoryLoad GEL_MemoryLoadData GEL_MemorySave GEL_MemorySaveBin GEL_MemorySaveCoff GEL_MemorySaveData GEL_MemorySaveHex GEL_PatchAssembly GEL_ProbePtAdd GEL_ProbePtDel GEL_ProbePtDisable GEL_ProbePtReset GEL_ReConnect GEL_RefreshWindows GEL_Reload GEL_RemoveDebugState GEL_RemoveInputFile GEL_RemoveOutputFile GEL_Reset GEL_Restart GEL_RestoreDebugState GEL_Run GEL_RunF GEL_SetBlockResetMode GEL_SetBoolDebugProperty GEL_SetClockEvent GEL_SetNumericDebugProperty GEL_SetSemihostingMainArgs GEL_SetStringDebugProperty GEL_SetTimer GEL_SetWaitInResetMode GEL_SrcStepInto GEL_SrcStepOver GEL_StepInto GEL_StepOut GEL_StepOver GEL_StrCat GEL_StrLen GEL_SubStr GEL_SymbolAdd GEL_SymbolAddOffset GEL_SymbolAddRel GEL_SymbolDisable GEL_SymbolEnable GEL_SymbolHideSection GEL_SymbolLoad GEL_SymbolLoadOffset GEL_SymbolLoadRel GEL_SymbolRemove GEL_SymbolShowSection GEL_SyncHalt GEL_SyncRun GEL_SyncStepInto GEL_SyncStepOut GEL_SyncStepOver GEL_System GEL_TargetTextOut GEL_TextOut GEL_Trace GEL_UnloadAllGels GEL_UnloadAllSymbols GEL_UnloadGel GEL_VerifyBinProgram GEL_VerifyProgram OnChildRunning OnFileLoaded OnHalt OnPreFileLoaded OnPreReset OnPreTargetConnect OnReset OnResetDetected OnRestart OnTargetConnect
+syn keyword gelModifier	hotmenu menuitem
+
+hi def link gelStatement	Statement
+hi def link gelModifier		Type
+
+let b:current_syntax = "gel"
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index e30e464fd7..f556d551dd 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -298,6 +298,7 @@ func s:GetFilenameChecks() abort
     \ 'gdscript': ['file.gd'],
     \ 'gdshader': ['file.gdshader', 'file.shader'],
     \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'],
+    \ 'gel': ['file.gel'],
     \ 'gemtext': ['file.gmi', 'file.gemini'],
     \ 'gift': ['file.gift'],
     \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'),
-- 
cgit 


From 487c48ec8689b865bad04fdb87b61f5ada25da97 Mon Sep 17 00:00:00 2001
From: Artem 
Date: Wed, 25 Dec 2024 14:12:40 -0600
Subject: fix(api): clamp range lines in `nvim__redraw()` (#31710)

Problem:
`nvim__redraw()` doesn't clamp the lines in the `range` parameter before truncating to int. The resulting range may be empty when the original range contained buffer lines and vice versa.

E.g. for a buffer with 4 lines, these are the redrawn lines:

```lua
{ 2, 2 ^ 31 } -> none (should be { 2, 3 })
{ 2, 2 ^ 32 } -> none (should be { 2, 3 })
{ 2 ^ 32 - 1, 2 } -> { 0, 1 } (should be none)
```

Solution:
Clamp `range` values before truncating to int.
---
 src/nvim/api/vim.c               | 20 ++++++++++++----
 test/functional/api/vim_spec.lua | 49 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 332c5bc15c..25f44bb4eb 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2374,13 +2374,23 @@ void nvim__redraw(Dict(redraw) *opts, Error *err)
              "%s", "Invalid 'range': Expected 2-tuple of Integers", {
       return;
     });
-    linenr_T first = (linenr_T)kv_A(opts->range, 0).data.integer + 1;
-    linenr_T last = (linenr_T)kv_A(opts->range, 1).data.integer;
+    int64_t begin_raw = kv_A(opts->range, 0).data.integer;
+    int64_t end_raw = kv_A(opts->range, 1).data.integer;
+
     buf_T *rbuf = win ? win->w_buffer : (buf ? buf : curbuf);
-    if (last == -1) {
-      last = rbuf->b_ml.ml_line_count;
+    linenr_T line_count = rbuf->b_ml.ml_line_count;
+
+    int begin = (int)MIN(begin_raw, line_count);
+    int end;
+    if (end_raw == -1) {
+      end = line_count;
+    } else {
+      end = (int)MIN(MAX(begin, end_raw), line_count);
+    }
+
+    if (begin < end) {
+      redraw_buf_range_later(rbuf, 1 + begin, end);
     }
-    redraw_buf_range_later(rbuf, first, last);
   }
 
   // Redraw later types require update_screen() so call implicitly unless set to false.
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index fbc9490df6..578fa361e8 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -5385,8 +5385,53 @@ describe('API', function()
         13                                                          |
       ]],
     })
-    -- takes buffer line count from correct buffer with "win" and {0, -1} "range"
-    api.nvim__redraw({ win = 0, range = { 0, -1 } })
+  end)
+
+  it('nvim__redraw range parameter', function()
+    Screen.new(10, 5)
+    fn.setline(1, fn.range(4))
+
+    exec_lua([[
+      _G.lines_list = {}
+      ns = vim.api.nvim_create_namespace('')
+      vim.api.nvim_set_decoration_provider(ns, {
+        on_win = function()
+        end,
+        on_line = function(_, _, _, line)
+          table.insert(_G.lines_list, line)
+        end,
+      })
+      function _G.get_lines()
+        local lines = _G.lines_list
+        _G.lines_list = {}
+        return lines
+      end
+    ]])
+
+    api.nvim__redraw({ flush = true, valid = false })
+    exec_lua('_G.get_lines()')
+
+    local actual_lines = {}
+    local function test(range)
+      api.nvim__redraw({ win = 0, range = range })
+      table.insert(actual_lines, exec_lua('return _G.get_lines()'))
+    end
+
+    test({ 0, -1 })
+    test({ 2, 2 ^ 31 })
+    test({ 2, 2 ^ 32 })
+    test({ 2 ^ 31 - 1, 2 })
+    test({ 2 ^ 32 - 1, 2 })
+
+    local expected_lines = {
+      { 0, 1, 2, 3 },
+      { 2, 3 },
+      { 2, 3 },
+      {},
+      {},
+    }
+    eq(expected_lines, actual_lines)
+
     n.assert_alive()
   end)
 end)
-- 
cgit 


From 98763ce4e93752f22d379e3f735f4c78ae49afad Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Sat, 7 Dec 2024 06:53:58 +0600
Subject: refactor(autocmd): allow specifying window for autocmd context

Problem: Currently we can only specify a buffer to use for an autocmd context through `aucmd_prepbuf()`, which finds a window that uses that buffer in the current tabpage, or creates an autocmd window. This means it's not possible to actually specify a window to use for an autocmd.

Solution: Add an `aucmd_prepbuf_win()` function which also takes a window as a parameter and uses it for the autocmd. If the window is not provided, then it behaves similarly to `aucmd_prepbuf()`
---
 src/nvim/autocmd.c      | 172 +++++++++++++++++++++++++++++++-----------------
 src/nvim/autocmd_defs.h |  22 ++++---
 2 files changed, 122 insertions(+), 72 deletions(-)

diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index eb7c8c2880..aab09de6b4 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -1251,56 +1251,82 @@ bool check_nomodeline(char **argp)
   return true;
 }
 
-/// Prepare for executing autocommands for (hidden) buffer `buf`.
-/// If the current buffer is not in any visible window, put it in a temporary
-/// floating window using an entry in `aucmd_win[]`.
-/// Set `curbuf` and `curwin` to match `buf`.
+/// Prepare for executing autocommands for (hidden) buffer `buf` on window `win`
+/// If the buffer of `win` is not `buf`, switch the buffer of `win` to `buf` temporarily.
 ///
-/// @param aco  structure to save values in
-/// @param buf  new curbuf
-void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
+/// @param  aco      Structure to save values in.
+/// @param  buf      New curbuf.
+/// @param  win      New curwin.
+void aucmd_prepbuf_win(aco_save_T *aco, buf_T *buf, win_T *win)
 {
-  win_T *win;
-  bool need_append = true;  // Append `aucmd_win` to the window list.
+  bool need_append = false;  // Append `aucmd_win` to the window list.
+  int auc_idx = -1;  // Index of aucmd_win[] to use. -1 if not using aucmd_win[].
 
-  // Find a window that is for the new buffer
-  if (buf == curbuf) {  // be quick when buf is curbuf
-    win = curwin;
-  } else {
-    win = NULL;
-    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
-      if (wp->w_buffer == buf) {
-        win = wp;
-        break;
+  aco->save_curtab_handle = -1;
+  aco->save_buf_handle = -1;
+
+  if (win == NULL) {
+    // Window not provided. Find a window that is for the new buffer
+    if (buf == curbuf) {  // be quick when buf is curbuf
+      win = curwin;
+    } else {
+      win = NULL;
+      FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+        if (wp->w_buffer == buf) {
+          win = wp;
+          break;
+        }
       }
     }
-  }
 
-  // Allocate a window when needed.
-  win_T *auc_win = NULL;
-  int auc_idx = AUCMD_WIN_COUNT;
-  if (win == NULL) {
-    for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) {
-      if (!aucmd_win[auc_idx].auc_win_used) {
-        break;
+    // Allocate a window when needed.
+    if (win == NULL) {
+      for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) {
+        if (!aucmd_win[auc_idx].auc_win_used) {
+          break;
+        }
+      }
+
+      if (auc_idx == AUCMD_WIN_COUNT) {
+        kv_push(aucmd_win_vec, ((aucmdwin_T){
+          .auc_win = NULL,
+          .auc_win_used = false,
+        }));
+      }
+
+      if (aucmd_win[auc_idx].auc_win == NULL) {
+        win_alloc_aucmd_win(auc_idx);
+      } else {
+        need_append = true;
       }
+      win = aucmd_win[auc_idx].auc_win;
+      aucmd_win[auc_idx].auc_win_used = true;
     }
+  } else {
+    tabpage_T *tp = win_find_tabpage(win);
 
-    if (auc_idx == AUCMD_WIN_COUNT) {
-      kv_push(aucmd_win_vec, ((aucmdwin_T){
-        .auc_win = NULL,
-        .auc_win_used = false,
-      }));
+    // If the window is in another tab page, switch to that tab page temporarily.
+    if (tp != curtab) {
+      aco->save_curtab_handle = curtab->handle;
+      unuse_tabpage(curtab);
+      use_tabpage(tp);
     }
+  }
 
-    if (aucmd_win[auc_idx].auc_win == NULL) {
-      win_alloc_aucmd_win(auc_idx);
-      need_append = false;
+  // If the buffer of the window is not the target buffer, switch to it temporarily.
+  if (win->w_buffer != buf) {
+    if (auc_idx == -1) {
+      // No need to store old buffer for aucmd_win[].
+      aco->save_buf_handle = win->w_buffer->handle;
+      win->w_buffer->b_nwindows--;
     }
-    auc_win = aucmd_win[auc_idx].auc_win;
-    aucmd_win[auc_idx].auc_win_used = true;
+
+    win->w_buffer = buf;
+    win->w_s = &buf->b_s;
+    buf->b_nwindows++;
   }
 
+  aco->use_aucmd_win_idx = auc_idx;
   aco->save_curwin_handle = curwin->handle;
   aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle;
   aco->save_State = State;
@@ -1308,26 +1334,15 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
     aco->save_prompt_insert = curbuf->b_prompt_insert;
   }
 
-  if (win != NULL) {
-    // There is a window for "buf" in the current tab page, make it the
-    // curwin.  This is preferred, it has the least side effects (esp. if
-    // "buf" is curbuf).
-    aco->use_aucmd_win_idx = -1;
-    curwin = win;
-  } else {
-    // There is no window for "buf", use "auc_win".  To minimize the side
-    // effects, insert it in the current tab page.
-    // Anything related to a window (e.g., setting folds) may have
-    // unexpected results.
-    aco->use_aucmd_win_idx = auc_idx;
-    auc_win->w_buffer = buf;
-    auc_win->w_s = &buf->b_s;
-    buf->b_nwindows++;
-    win_init_empty(auc_win);  // set cursor and topline to safe values
+  if (auc_idx >= 0) {
+    // There is no window for "buf", use "win". To minimize the side effects, insert it in the
+    // current tab page. Anything related to a window (e.g., setting folds) may have unexpected
+    // results.
+    win_init_empty(win);  // Set cursor and topline to safe values.
 
-    // Make sure w_localdir, tp_localdir and globaldir are NULL to avoid a
-    // chdir() in win_enter_ext().
-    XFREE_CLEAR(auc_win->w_localdir);
+    // Make sure w_localdir, tp_localdir and globaldir are NULL to avoid a chdir() in
+    // win_enter_ext().
+    XFREE_CLEAR(win->w_localdir);
     aco->tp_localdir = curtab->tp_localdir;
     curtab->tp_localdir = NULL;
     aco->globaldir = globaldir;
@@ -1335,30 +1350,43 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
 
     block_autocmds();  // We don't want BufEnter/WinEnter autocommands.
     if (need_append) {
-      win_append(lastwin, auc_win, NULL);
-      pmap_put(int)(&window_handles, auc_win->handle, auc_win);
-      win_config_float(auc_win, auc_win->w_config);
+      win_append(lastwin, win, NULL);
+      pmap_put(int)(&window_handles, win->handle, win);
+      win_config_float(win, win->w_config);
     }
     // Prevent chdir() call in win_enter_ext(), through do_autochdir()
     const int save_acd = p_acd;
     p_acd = false;
-    // no redrawing and don't set the window title
+    // No redrawing and don't set the window title
     RedrawingDisabled++;
-    win_enter(auc_win, false);
+    win_enter(win, false);
     RedrawingDisabled--;
     p_acd = save_acd;
     unblock_autocmds();
-    curwin = auc_win;
   }
+
+  curwin = win;
   curbuf = buf;
   aco->new_curwin_handle = curwin->handle;
   set_bufref(&aco->new_curbuf, curbuf);
 
-  // disable the Visual area, the position may be invalid in another buffer
+  // Disable the Visual area, the position may be invalid in another buffer
   aco->save_VIsual_active = VIsual_active;
   VIsual_active = false;
 }
 
+/// Prepare for executing autocommands for (hidden) buffer `buf`.
+/// If the current buffer is not in any visible window, put it in a temporary
+/// floating window using an entry in `aucmd_win[]`.
+/// Set `curbuf` and `curwin` to match `buf`.
+///
+/// @param  aco  structure to save values in
+/// @param  buf  new curbuf
+void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
+{
+  aucmd_prepbuf_win(aco, buf, NULL);
+}
+
 /// Cleanup after executing autocommands for a (hidden) buffer.
 /// Restore the window as it was (if possible).
 ///
@@ -1447,6 +1475,19 @@ win_found:
       curwin->w_topfill = 0;
     }
   } else {
+    // Restore old buffer of new window if it was changed.
+    if (aco->save_buf_handle != -1) {
+      win_T *new_win = win_find_by_handle(aco->new_curwin_handle);
+      buf_T *new_win_buf = handle_get_buffer(aco->save_buf_handle);
+
+      if (new_win != NULL && new_win_buf != NULL) {
+        new_win->w_buffer->b_nwindows--;
+        new_win->w_buffer = new_win_buf;
+        new_win->w_s = &new_win_buf->b_s;
+        new_win_buf->b_nwindows++;
+      }
+    }
+
     // Restore curwin.  Use the window ID, a window may have been closed
     // and the memory re-used for another one.
     win_T *const save_curwin = win_find_by_handle(aco->save_curwin_handle);
@@ -1482,6 +1523,13 @@ win_found:
   if (VIsual_active) {
     check_pos(curbuf, &VIsual);
   }
+
+  // Switch back to the original tab page if it was switched.
+  if (aco->save_curtab_handle != -1) {
+    tabpage_T *save_curtab = handle_get_tabpage(aco->save_curtab_handle);
+    unuse_tabpage(curtab);
+    use_tabpage(save_curtab);
+  }
 }
 
 /// Execute autocommands for "event" and file name "fname".
diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h
index cba947e85f..777ee7b87a 100644
--- a/src/nvim/autocmd_defs.h
+++ b/src/nvim/autocmd_defs.h
@@ -14,16 +14,18 @@
 /// Struct to save values in before executing autocommands for a buffer that is
 /// not the current buffer.
 typedef struct {
-  int use_aucmd_win_idx;          ///< index in aucmd_win[] if >= 0
-  handle_T save_curwin_handle;    ///< ID of saved curwin
-  handle_T new_curwin_handle;     ///< ID of new curwin
-  handle_T save_prevwin_handle;   ///< ID of saved prevwin
-  bufref_T new_curbuf;            ///< new curbuf
-  char *tp_localdir;              ///< saved value of tp_localdir
-  char *globaldir;                ///< saved value of globaldir
-  bool save_VIsual_active;        ///< saved VIsual_active
-  int save_State;                 ///< saved State
-  int save_prompt_insert;         ///< saved b_prompt_insert
+  int use_aucmd_win_idx;             ///< index in aucmd_win[] if >= 0
+  handle_T save_curwin_handle;       ///< ID of saved curwin
+  handle_T save_curtab_handle;       ///< ID of saved curtab. -1 if not switched.
+  handle_T new_curwin_handle;        ///< ID of new curwin
+  handle_T save_buf_handle;          ///< ID of saved buffer of new curwin. -1 if not switched.
+  handle_T save_prevwin_handle;      ///< ID of saved prevwin
+  bufref_T new_curbuf;               ///< new curbuf
+  char *tp_localdir;                 ///< saved value of tp_localdir
+  char *globaldir;                   ///< saved value of globaldir
+  bool save_VIsual_active;           ///< saved VIsual_active
+  int save_State;                    ///< saved State
+  int save_prompt_insert;            ///< saved b_prompt_insert
 } aco_save_T;
 
 typedef struct {
-- 
cgit 


From 6257270040bc5c61a489f7fb9d4102223c36cf89 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Fri, 8 Nov 2024 11:57:59 +0600
Subject: refactor(options): set option value for non-current context directly

Problem: Currently, we use `switch_option_context` to temporarily switch the current option context before setting an option for a different buffer / window. This is not ideal because we already support getting and setting option values for non-current contexts in the underlying implementation.

Solution: Set option value for non-current context by passing the context directly to the lower level functions. Also introduce a new `OptCtx` struct to store option context information, this will scale much better if we add more option scopes and other context information in the future.
---
 src/nvim/api/deprecated.c         |  10 +-
 src/nvim/api/options.c            |  12 +-
 src/nvim/api/vim.c                |   8 +-
 src/nvim/diff.c                   |   4 +-
 src/nvim/ex_docmd.c               |   2 +-
 src/nvim/insexpand.c              |   6 +-
 src/nvim/option.c                 | 760 +++++++++++++++++---------------------
 src/nvim/option_defs.h            |  18 +-
 src/nvim/optionstr.c              |  60 +--
 src/nvim/tag.c                    |   2 +-
 src/nvim/winfloat.c               |   4 +-
 test/functional/lua/with_spec.lua |   6 +-
 12 files changed, 407 insertions(+), 485 deletions(-)

diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index d5eddb74de..47a49436ab 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -649,8 +649,8 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er
   OptVal value = NIL_OPTVAL;
 
   if (option_has_scope(opt_idx, scope)) {
-    value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL,
-                                 scope, from, err);
+    value = get_option_value_from(opt_idx, option_ctx_from(scope, from),
+                                  scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL);
     if (ERROR_SET(err)) {
       return (Object)OBJECT_INIT;
     }
@@ -701,7 +701,11 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String
       : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL);
 
   WITH_SCRIPT_CONTEXT(channel_id, {
-    set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err);
+    const char *errmsg
+      = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags);
+    if (errmsg) {
+      api_set_error(err, kErrorTypeException, "%s", errmsg);
+    }
   });
 }
 
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index 64f8a35d54..2bbbdbbe8c 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -157,8 +157,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
   void *from = NULL;
   char *filetype = NULL;
 
-  if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from,
-                                  &filetype, err)) {
+  if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, &filetype,
+                                  err)) {
     return (Object)OBJECT_INIT;
   }
 
@@ -182,7 +182,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
     from = ftbuf;
   }
 
-  OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err);
+  OptVal value = get_option_value_from(opt_idx, option_ctx_from(scope, from), opt_flags);
 
   if (ftbuf != NULL) {
     // restore curwin/curbuf and a few other things
@@ -257,7 +257,11 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(
   });
 
   WITH_SCRIPT_CONTEXT(channel_id, {
-    set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err);
+    const char *errmsg
+      = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags);
+    if (errmsg) {
+      api_set_error(err, kErrorTypeException, "%s", errmsg);
+    }
   });
 }
 
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 25f44bb4eb..108d1ee98d 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -982,10 +982,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
     buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
 
     if (scratch) {
-      set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0,
-                            kOptScopeBuf, buf);
-      set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0,
-                            kOptScopeBuf, buf);
+      set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"),
+                            option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0);
+      set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"),
+                            option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0);
       assert(buf->b_ml.ml_mfp->mf_fd < 0);  // ml_open() should not have opened swapfile already
       buf->b_p_swf = false;
       buf->b_p_ml = false;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index bd98a31a71..ebeb9ba088 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -1391,8 +1391,8 @@ void diff_win_options(win_T *wp, bool addbuf)
     }
     wp->w_p_fdm_save = xstrdup(wp->w_p_fdm);
   }
-  set_option_direct_for(kOptFoldmethod, STATIC_CSTR_AS_OPTVAL("diff"), OPT_LOCAL, 0,
-                        kOptScopeWin, wp);
+  set_option_direct_for(kOptFoldmethod, STATIC_CSTR_AS_OPTVAL("diff"),
+                        option_ctx_from(kOptScopeWin, wp), OPT_LOCAL, 0);
 
   if (!wp->w_p_diff) {
     wp->w_p_fen_save = wp->w_p_fen;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 551e8fcb1d..8ee9967465 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5294,7 +5294,7 @@ static char *findfunc_find_file(char *findarg, size_t findarg_len, int count)
 /// Returns NULL on success and an error message on failure.
 const char *did_set_findfunc(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   int retval;
 
   if (args->os_flags & OPT_LOCAL) {
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 7245b0d6ce..908294872f 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -2398,7 +2398,7 @@ static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb)
 /// lambda expression.
 const char *did_set_completefunc(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   if (option_set_callback_func(buf->b_p_cfu, &cfu_cb) == FAIL) {
     return e_invarg;
   }
@@ -2419,7 +2419,7 @@ void set_buflocal_cfu_callback(buf_T *buf)
 /// lambda expression.
 const char *did_set_omnifunc(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   if (option_set_callback_func(buf->b_p_ofu, &ofu_cb) == FAIL) {
     return e_invarg;
   }
@@ -2440,7 +2440,7 @@ void set_buflocal_ofu_callback(buf_T *buf)
 /// lambda expression.
 const char *did_set_thesaurusfunc(optset_T *args FUNC_ATTR_UNUSED)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   int retval;
 
   if (args->os_flags & OPT_LOCAL) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a59e55121f..b8f727cede 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1322,7 +1322,8 @@ static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char *
     return;
   }
 
-  *errmsg = set_option(opt_idx, newval, opt_flags, 0, false, op == OP_NONE, errbuf, errbuflen);
+  *errmsg = set_option_for(opt_idx, newval, option_ctx(), opt_flags, 0, false,
+                           op == OP_NONE, errbuf, errbuflen);
 }
 
 /// Parse 'arg' for option settings.
@@ -1842,7 +1843,7 @@ void set_option_sctx(OptIndex opt_idx, int opt_flags, sctx_T script_ctx)
 
 /// Apply the OptionSet autocommand.
 static void apply_optionset_autocmd(OptIndex opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g,
-                                    OptVal oldval_l, OptVal newval, const char *errmsg)
+                                    OptVal oldval_l, OptVal newval, OptCtx ctx, const char *errmsg)
 {
   // Don't do this while starting up, failure or recursively.
   if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) {
@@ -1876,14 +1877,18 @@ static void apply_optionset_autocmd(OptIndex opt_idx, int opt_flags, OptVal oldv
     set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1);
     set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
   }
-  apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
+
+  WITH_AUCMD_CONTEXT(ctx, {
+    apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
+  });
+
   reset_v_option_vars();
 }
 
 /// Process the updated 'arabic' option value.
 static const char *did_set_arabic(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   const char *errmsg = NULL;
 
   if (win->w_p_arab) {
@@ -1952,7 +1957,7 @@ static const char *did_set_autochdir(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'binary' option value.
 static const char *did_set_binary(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
 
   // when 'bin' is set also set some other options
   set_options_bin((int)args->os_oldval.boolean, buf->b_p_bin, args->os_flags);
@@ -1964,7 +1969,7 @@ static const char *did_set_binary(optset_T *args)
 /// Process the updated 'buflisted' option value.
 static const char *did_set_buflisted(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
 
   // when 'buflisted' changes, trigger autocommands
   if (args->os_oldval.boolean != buf->b_p_bl) {
@@ -1998,7 +2003,7 @@ static const char *did_set_cmdheight(optset_T *args)
 /// Process the updated 'diff' option value.
 static const char *did_set_diff(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   // May add or remove the buffer from the list of diff buffers.
   diff_buf_adjust(win);
   if (foldmethodIsDiff(win)) {
@@ -2019,7 +2024,7 @@ static const char *did_set_eof_eol_fixeol_bomb(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'equalalways' option value.
 static const char *did_set_equalalways(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   if (p_ea && !args->os_oldval.boolean) {
     win_equal(win, false, 0);
   }
@@ -2037,7 +2042,7 @@ static const char *did_set_foldlevel(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the new 'foldminlines' option value.
 static const char *did_set_foldminlines(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   foldUpdateAll(win);
   return NULL;
 }
@@ -2045,7 +2050,7 @@ static const char *did_set_foldminlines(optset_T *args)
 /// Process the new 'foldnestmax' option value.
 static const char *did_set_foldnestmax(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   if (foldmethodIsSyntax(win) || foldmethodIsIndent(win)) {
     foldUpdateAll(win);
   }
@@ -2174,7 +2179,7 @@ static const char *did_set_lines_or_columns(optset_T *args)
 /// Process the updated 'lisp' option value.
 static const char *did_set_lisp(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   // When 'lisp' option changes include/exclude '-' in keyword characters.
   buf_init_chartab(buf, false);          // ignore errors
   return NULL;
@@ -2192,7 +2197,7 @@ static const char *did_set_modifiable(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'modified' option value.
 static const char *did_set_modified(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   if (!args->os_newval.boolean) {
     save_file_ff(buf);  // Buffer is unchanged
   }
@@ -2204,7 +2209,7 @@ static const char *did_set_modified(optset_T *args)
 /// Process the updated 'number' or 'relativenumber' option value.
 static const char *did_set_number_relativenumber(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   if (*win->w_p_stc != NUL) {
     // When 'relativenumber'/'number' is changed and 'statuscolumn' is set, reset width.
     win->w_nrwidth_line_count = 0;
@@ -2216,7 +2221,7 @@ static const char *did_set_number_relativenumber(optset_T *args)
 /// Process the new 'numberwidth' option value.
 static const char *did_set_numberwidth(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   win->w_nrwidth_line_count = 0;  // trigger a redraw
 
   return NULL;
@@ -2354,7 +2359,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'previewwindow' option value.
 static const char *did_set_previewwindow(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
 
   if (!win->w_p_pvw) {
     return NULL;
@@ -2386,7 +2391,7 @@ static const char *did_set_pumblend(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'readonly' option value.
 static const char *did_set_readonly(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
 
   // when 'readonly' is reset globally, also reset readonlymode
   if (!buf->b_p_ro && (args->os_flags & OPT_LOCAL) == 0) {
@@ -2406,7 +2411,7 @@ static const char *did_set_readonly(optset_T *args)
 /// Process the new 'scrollback' option value.
 static const char *did_set_scrollback(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   OptInt old_value = args->os_oldval.number;
   OptInt value = args->os_newval.number;
 
@@ -2420,7 +2425,7 @@ static const char *did_set_scrollback(optset_T *args)
 /// Process the updated 'scrollbind' option value.
 static const char *did_set_scrollbind(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
 
   // when 'scrollbind' is set: snapshot the current position to avoid a jump
   // at the end of normal_cmd()
@@ -2457,8 +2462,8 @@ static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the new 'shiftwidth' or the 'tabstop' option value.
 static const char *did_set_shiftwidth_tabstop(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
-  win_T *win = (win_T *)args->os_win;
+  buf_T *buf = args->os_ctx.buf;
+  win_T *win = args->os_ctx.win;
   OptInt *pp = (OptInt *)args->os_varp;
 
   if (foldmethodIsIndent(win)) {
@@ -2484,7 +2489,7 @@ static const char *did_set_showtabline(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'smoothscroll' option value.
 static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   if (!win->w_p_sms) {
     win->w_skipcol = 0;
   }
@@ -2495,7 +2500,7 @@ static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED)
 /// Process the updated 'spell' option value.
 static const char *did_set_spell(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   if (win->w_p_spell) {
     return parse_spelllang(win);
   }
@@ -2506,7 +2511,7 @@ static const char *did_set_spell(optset_T *args)
 /// Process the updated 'swapfile' option value.
 static const char *did_set_swapfile(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   // when 'swf' is set, create swapfile, when reset remove swapfile
   if (buf->b_p_swf && p_uc) {
     ml_open_file(buf);                     // create the swap file
@@ -2551,7 +2556,7 @@ static const char *did_set_titlelen(optset_T *args)
 /// Process the updated 'undofile' option value.
 static const char *did_set_undofile(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
 
   // Only take action when the option was set.
   if (!buf->b_p_udf && !p_udf) {
@@ -2602,7 +2607,7 @@ const char *did_set_buflocal_undolevels(buf_T *buf, OptInt value, OptInt old_val
 /// Process the new 'undolevels' option value.
 static const char *did_set_undolevels(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   OptInt *pp = (OptInt *)args->os_varp;
 
   if (pp == &p_ul) {                  // global 'undolevels'
@@ -2643,7 +2648,7 @@ static const char *did_set_wildchar(optset_T *args)
 /// Process the new 'winblend' option value.
 static const char *did_set_winblend(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   OptInt old_value = args->os_oldval.number;
   OptInt value = args->os_newval.number;
 
@@ -2692,7 +2697,7 @@ static const char *did_set_winwidth(optset_T *args)
 /// Process the updated 'wrap' option value.
 static const char *did_set_wrap(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   // Set w_leftcol or w_skipcol to zero.
   if (win->w_p_wrap) {
     win->w_leftcol = 0;
@@ -3143,12 +3148,6 @@ static OptValType option_get_type(const OptIndex opt_idx)
 OptVal optval_from_varp(OptIndex opt_idx, void *varp)
   FUNC_ATTR_NONNULL_ARG(2)
 {
-  // Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc'
-  // changed.
-  if ((int *)varp == &curbuf->b_changed) {
-    return BOOLEAN_OPTVAL(curbufIsChanged());
-  }
-
   if (option_is_multitype(opt_idx)) {
     // Multitype options are stored as OptVal.
     return *(OptVal *)varp;
@@ -3381,21 +3380,37 @@ uint32_t get_option_flags(OptIndex opt_idx)
 /// Gets the value for an option.
 ///
 /// @param  opt_idx    Option index in options[] table.
+/// @param  ctx        Context to get the option value from.
 /// @param  opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
 ///
 /// @return [allocated] Option value. Returns NIL_OPTVAL for invalid option index.
-OptVal get_option_value(OptIndex opt_idx, int opt_flags)
+OptVal get_option_value_from(OptIndex opt_idx, OptCtx ctx, int opt_flags)
 {
   if (opt_idx == kOptInvalid) {  // option not in the options[] table.
     return NIL_OPTVAL;
   }
 
-  vimoption_T *opt = &options[opt_idx];
-  void *varp = get_varp_scope(opt, opt_flags);
+  // Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc'
+  // changed.
+  if (opt_idx == kOptModified) {
+    return BOOLEAN_OPTVAL(bufIsChanged(ctx.buf));
+  }
 
+  void * const varp = get_varp_scope_from(&options[opt_idx], opt_flags, ctx);
   return optval_copy(optval_from_varp(opt_idx, varp));
 }
 
+/// Gets the value for an option in the current context.
+///
+/// @param  opt_idx  Option index in options[] table.
+/// @param  opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+///
+/// @return [allocated] Option value. Returns NIL_OPTVAL for invalid option index.
+OptVal get_option_value(OptIndex opt_idx, int opt_flags)
+{
+  return get_option_value_from(opt_idx, option_ctx(), opt_flags);
+}
+
 /// Return information for option at 'opt_idx'
 vimoption_T *get_option(OptIndex opt_idx)
 {
@@ -3464,6 +3479,8 @@ static bool is_option_local_value_unset(OptIndex opt_idx)
 /// @param       opt_idx         Index in options[] table. Must not be kOptInvalid.
 /// @param[in]   varp            Option variable pointer, cannot be NULL.
 /// @param       old_value       Old option value.
+/// @param       new_value       New option value.
+/// @param       ctx             Context in which the option is set.
 /// @param       opt_flags       Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
 /// @param       set_sid         Script ID. Special values:
 ///                                0: Use current script ID.
@@ -3475,8 +3492,9 @@ static bool is_option_local_value_unset(OptIndex opt_idx)
 ///
 /// @return  NULL on success, an untranslated error message on error.
 static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value, OptVal new_value,
-                                  int opt_flags, scid_T set_sid, const bool direct,
-                                  const bool value_replaced, char *errbuf, size_t errbuflen)
+                                  OptCtx ctx, int opt_flags, scid_T set_sid, const bool direct,
+                                  const bool value_replaced, char *errbuf,  // NOLINT(readability-non-const-parameter)
+                                  size_t errbuflen)
 {
   vimoption_T *opt = &options[opt_idx];
   const char *errmsg = NULL;
@@ -3495,8 +3513,7 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value
     .os_restore_chartab = false,
     .os_errbuf = errbuf,
     .os_errbuflen = errbuflen,
-    .os_buf = curbuf,
-    .os_win = curwin
+    .os_ctx = ctx,
   };
 
   if (direct) {
@@ -3533,7 +3550,7 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value
     set_option_varp(opt_idx, varp, old_value, true);
     // When resetting some values, need to act on it.
     if (restore_chartab) {
-      buf_init_chartab(curbuf, true);
+      buf_init_chartab(ctx.buf, true);
     }
 
     return errmsg;
@@ -3564,12 +3581,12 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value
     if (option_is_global_local(opt_idx)) {
       // Global option with local value set to use global value.
       // Free the local value and clear it.
-      void *varp_local = get_varp_scope(opt, OPT_LOCAL);
+      void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, ctx);
       OptVal local_unset_value = get_option_unset_value(opt_idx);
       set_option_varp(opt_idx, varp_local, optval_copy(local_unset_value), true);
     } else {
       // May set global value for local option.
-      void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
+      void *varp_global = get_varp_scope_from(opt, OPT_GLOBAL, ctx);
       set_option_varp(opt_idx, varp_global, optval_copy(new_value), true);
     }
   }
@@ -3580,17 +3597,23 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value
   }
 
   // Trigger the autocommand only after setting the flags.
-  if (varp == &curbuf->b_p_syn) {
-    do_syntax_autocmd(curbuf, value_changed);
-  } else if (varp == &curbuf->b_p_ft) {
+  if (varp == &ctx.buf->b_p_syn) {
+    WITH_AUCMD_CONTEXT(ctx, {
+      do_syntax_autocmd(ctx.buf, value_changed);
+    });
+  } else if (varp == &ctx.buf->b_p_ft) {
     // 'filetype' is set, trigger the FileType autocommand
     // Skip this when called from a modeline
     // Force autocmd when the filetype was changed
     if (!(opt_flags & OPT_MODELINE) || value_changed) {
-      do_filetype_autocmd(curbuf, value_changed);
+      WITH_AUCMD_CONTEXT(ctx, {
+        do_filetype_autocmd(ctx.buf, value_changed);
+      });
     }
-  } else if (varp == &curwin->w_s->b_p_spl) {
-    do_spelllang_source(curwin);
+  } else if (varp == &ctx.win->w_s->b_p_spl) {
+    WITH_AUCMD_CONTEXT(ctx, {
+      do_spelllang_source(ctx.win);
+    });
   }
 
   // In case 'ruler' or 'showcmd' or 'columns' or 'ls' changed.
@@ -3598,25 +3621,25 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value
 
   if (varp == &p_mouse) {
     setmouse();  // in case 'mouse' changed
-  } else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) {
+  } else if ((varp == &p_flp || varp == &(ctx.buf->b_p_flp)) && ctx.win->w_briopt_list) {
     // Changing Formatlistpattern when briopt includes the list setting:
     // redraw
     redraw_all_later(UPD_NOT_VALID);
-  } else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) {
+  } else if (varp == &p_wbr || varp == &(ctx.win->w_p_wbr)) {
     // add / remove window bars for 'winbar'
     set_winbar(true);
   }
 
-  if (curwin->w_curswant != MAXCOL
+  if (ctx.win->w_curswant != MAXCOL
       && (opt->flags & (kOptFlagCurswant | kOptFlagRedrAll)) != 0
       && (opt->flags & kOptFlagHLOnly) == 0) {
-    curwin->w_set_curswant = true;
+    ctx.win->w_set_curswant = true;
   }
 
-  check_redraw(opt->flags);
+  check_redraw_for(ctx.buf, ctx.win, opt->flags);
 
   if (errmsg == NULL) {
-    uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
+    uint32_t *p = insecure_flag(ctx.win, opt_idx, opt_flags);
     opt->flags |= kOptFlagWasSet;
 
     // When an option is set in the sandbox, from a modeline or in secure mode set the kOptFlagInsecure
@@ -3676,6 +3699,7 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval,
 ///
 /// @param       opt_idx         Index in options[] table. Must not be kOptInvalid.
 /// @param       value           New option value. Might get freed.
+/// @param       ctx             Context in which the option is set.
 /// @param       opt_flags       Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
 /// @param       set_sid         Script ID. Special values:
 ///                                0: Use current script ID.
@@ -3686,9 +3710,9 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval,
 /// @param       errbuflen       Length of error buffer.
 ///
 /// @return  NULL on success, an untranslated error message on error.
-static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid,
-                              const bool direct, const bool value_replaced, char *errbuf,
-                              size_t errbuflen)
+static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_flags,
+                                  scid_T set_sid, const bool direct, const bool value_replaced,
+                                  char *errbuf, size_t errbuflen)
 {
   assert(opt_idx != kOptInvalid);
 
@@ -3713,10 +3737,11 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
 
   // When using ":set opt=val" for a global option with a local value the local value will be reset,
   // use the global value in that case.
-  void *varp
-    = scope_both && option_is_global_local(opt_idx) ? opt->var : get_varp_scope(opt, opt_flags);
-  void *varp_local = get_varp_scope(opt, OPT_LOCAL);
-  void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
+  void *varp = scope_both && option_is_global_local(opt_idx)
+               ? opt->var
+               : get_varp_scope_from(opt, opt_flags, ctx);
+  void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, ctx);
+  void *varp_global = get_varp_scope_from(opt, OPT_GLOBAL, ctx);
 
   OptVal old_value = optval_from_varp(opt_idx, varp);
   OptVal old_global_value = optval_from_varp(opt_idx, varp_global);
@@ -3729,7 +3754,7 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
   // unset. In every other case, it is the same as old_value.
   // This value is used instead of old_value when triggering the OptionSet autocommand.
   OptVal used_old_value = (scope_local && is_opt_local_unset)
-                          ? optval_from_varp(opt_idx, get_varp(opt))
+                          ? optval_from_varp(opt_idx, get_varp_from(opt, ctx))
                           : old_value;
 
   // Save the old values and the new value in case they get changed.
@@ -3739,7 +3764,7 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
   // New value (and varp) may become invalid if the buffer is closed by autocommands.
   OptVal saved_new_value = optval_copy(value);
 
-  uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
+  uint32_t *p = insecure_flag(ctx.win, opt_idx, opt_flags);
   const int secure_saved = secure;
 
   // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side
@@ -3752,7 +3777,7 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
   // Set option through its variable pointer.
   set_option_varp(opt_idx, varp, value, false);
   // Process any side effects.
-  errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, set_sid, direct,
+  errmsg = did_set_option(opt_idx, varp, old_value, value, ctx, opt_flags, set_sid, direct,
                           value_replaced, errbuf, errbuflen);
 
   secure = secure_saved;
@@ -3760,7 +3785,7 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
   if (errmsg == NULL && !direct) {
     if (!starting) {
       apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value,
-                              saved_old_local_value, saved_new_value, errmsg);
+                              saved_old_local_value, saved_new_value, ctx, errmsg);
     }
     if (opt->flags & kOptFlagUIOption) {
       ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value));
@@ -3780,11 +3805,13 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
 ///
 /// @param  opt_idx    Option index in options[] table.
 /// @param  value      Option value.
+/// @param  ctx        Context in which the option is set.
 /// @param  opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
 /// @param  set_sid    Script ID. Special values:
 ///                      0: Use current script ID.
 ///                      SID_NONE: Don't set script ID.
-void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid)
+void set_option_direct_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_flags,
+                           scid_T set_sid)
 {
   static char errbuf[IOSIZE];
 
@@ -3792,57 +3819,35 @@ void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set
     return;
   }
 
-  const char *errmsg = set_option(opt_idx, optval_copy(value), opt_flags, set_sid, true, true,
-                                  errbuf, sizeof(errbuf));
+  const char *errmsg = set_option_for(opt_idx, optval_copy(value), ctx, opt_flags, set_sid, true,
+                                      true, errbuf, sizeof(errbuf));
   assert(errmsg == NULL);
   (void)errmsg;  // ignore unused warning
 }
 
-/// Set option value directly for buffer / window, without processing any side effects.
+/// Set option value for current context directly, without processing any side effects.
 ///
-/// @param      opt_idx    Option index in options[] table.
-/// @param      value      Option value.
-/// @param      opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
-/// @param      set_sid    Script ID. Special values:
-///                          0: Use current script ID.
-///                          SID_NONE: Don't set script ID.
-/// @param      scope      Option scope. See OptScope in option.h.
-/// @param[in]  from       Target buffer/window.
-void set_option_direct_for(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid,
-                           OptScope scope, void *const from)
-{
-  buf_T *save_curbuf = curbuf;
-  win_T *save_curwin = curwin;
-
-  // Don't use switch_option_context(), as that calls aucmd_prepbuf(), which may have unintended
-  // side-effects when setting an option directly. Just change the values of curbuf and curwin if
-  // needed, no need to properly switch the window / buffer.
-  switch (scope) {
-  case kOptScopeGlobal:
-    break;
-  case kOptScopeWin:
-    curwin = (win_T *)from;
-    curbuf = curwin->w_buffer;
-    break;
-  case kOptScopeBuf:
-    curbuf = (buf_T *)from;
-    break;
-  }
-
-  set_option_direct(opt_idx, value, opt_flags, set_sid);
-
-  curwin = save_curwin;
-  curbuf = save_curbuf;
+/// @param  opt_idx    Option index in options[] table.
+/// @param  value      Option value.
+/// @param  ctx        Context in which the option is set.
+/// @param  opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param  set_sid    Script ID. Special values:
+///                      0: Use current script ID.
+///                      SID_NONE: Don't set script ID.
+void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid)
+{
+  set_option_direct_for(opt_idx, value, option_ctx(), opt_flags, set_sid);
 }
 
 /// Set the value of an option.
 ///
 /// @param      opt_idx    Index in options[] table. Must not be kOptInvalid.
 /// @param[in]  value      Option value. If NIL_OPTVAL, the option value is cleared.
+/// @param      ctx        Context to set option for.
 /// @param[in]  opt_flags  Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
 ///
 /// @return  NULL on success, an untranslated error message on error.
-const char *set_option_value(const OptIndex opt_idx, const OptVal value, int opt_flags)
+const char *set_option_value_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_flags)
 {
   assert(opt_idx != kOptInvalid);
 
@@ -3854,7 +3859,21 @@ const char *set_option_value(const OptIndex opt_idx, const OptVal value, int opt
     return _(e_sandbox);
   }
 
-  return set_option(opt_idx, optval_copy(value), opt_flags, 0, false, true, errbuf, sizeof(errbuf));
+  return set_option_for(opt_idx, optval_copy(value), ctx, opt_flags, 0, false, true, errbuf,
+                        sizeof(errbuf));
+}
+
+/// Set the value of an option for current context.
+///
+/// @param      opt_idx    Index in options[] table. Must not be kOptInvalid.
+/// @param[in]  value      Option value. If NIL_OPTVAL, the option value is cleared.
+/// @param      ctx        Context to set option for.
+/// @param[in]  opt_flags  Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
+///
+/// @return  NULL on success, an untranslated error message on error.
+const char *set_option_value(OptIndex opt_idx, OptVal value, int opt_flags)
+{
+  return set_option_value_for(opt_idx, value, option_ctx(), opt_flags);
 }
 
 /// Unset the local value of a global-local option.
@@ -3910,135 +3929,6 @@ void set_option_value_give_err(const OptIndex opt_idx, OptVal value, int opt_fla
   }
 }
 
-/// Switch current context to get/set option value for window/buffer.
-///
-/// @param[out]  ctx        Current context. switchwin_T for window and aco_save_T for buffer.
-/// @param       scope      Option scope. See OptScope in option.h.
-/// @param[in]   from       Target buffer/window.
-/// @param[out]  err        Error message, if any.
-///
-/// @return  true if context was switched, false otherwise.
-static bool switch_option_context(void *const ctx, OptScope scope, void *const from, Error *err)
-{
-  switch (scope) {
-  case kOptScopeGlobal:
-    return false;
-  case kOptScopeWin: {
-    win_T *const win = (win_T *)from;
-    switchwin_T *const switchwin = (switchwin_T *)ctx;
-
-    if (win == curwin) {
-      return false;
-    }
-
-    if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
-        == FAIL) {
-      restore_win_noblock(switchwin, true);
-
-      if (ERROR_SET(err)) {
-        return false;
-      }
-      api_set_error(err, kErrorTypeException, "Problem while switching windows");
-      return false;
-    }
-    return true;
-  }
-  case kOptScopeBuf: {
-    buf_T *const buf = (buf_T *)from;
-    aco_save_T *const aco = (aco_save_T *)ctx;
-
-    if (buf == curbuf) {
-      return false;
-    }
-    aucmd_prepbuf(aco, buf);
-    return true;
-  }
-  }
-  UNREACHABLE;
-}
-
-/// Restore context after getting/setting option for window/buffer. See switch_option_context() for
-/// params.
-static void restore_option_context(void *const ctx, OptScope scope)
-{
-  switch (scope) {
-  case kOptScopeGlobal:
-    break;
-  case kOptScopeWin:
-    restore_win_noblock((switchwin_T *)ctx, true);
-    break;
-  case kOptScopeBuf:
-    aucmd_restbuf((aco_save_T *)ctx);
-    break;
-  }
-}
-
-/// Get option value for buffer / window.
-///
-/// @param       opt_idx    Option index in options[] table.
-/// @param[out]  flagsp     Set to the option flags (see OptFlags) (if not NULL).
-/// @param[in]   scope      Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
-/// @param[out]  hidden     Whether option is hidden.
-/// @param       scope      Option scope. See OptScope in option.h.
-/// @param[in]   from       Target buffer/window.
-/// @param[out]  err        Error message, if any.
-///
-/// @return  Option value. Must be freed by caller.
-OptVal get_option_value_for(OptIndex opt_idx, int opt_flags, const OptScope scope, void *const from,
-                            Error *err)
-{
-  switchwin_T switchwin;
-  aco_save_T aco;
-  void *ctx = scope == kOptScopeWin ? (void *)&switchwin
-                                    : (scope == kOptScopeBuf ? (void *)&aco : NULL);
-
-  bool switched = switch_option_context(ctx, scope, from, err);
-  if (ERROR_SET(err)) {
-    return NIL_OPTVAL;
-  }
-
-  OptVal retv = get_option_value(opt_idx, opt_flags);
-
-  if (switched) {
-    restore_option_context(ctx, scope);
-  }
-
-  return retv;
-}
-
-/// Set option value for buffer / window.
-///
-/// @param       name        Option name.
-/// @param       opt_idx     Option index in options[] table.
-/// @param[in]   value       Option value.
-/// @param[in]   opt_flags   Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
-/// @param       scope       Option scope. See OptScope in option.h.
-/// @param[in]   from        Target buffer/window.
-/// @param[out]  err         Error message, if any.
-void set_option_value_for(const char *name, OptIndex opt_idx, OptVal value, const int opt_flags,
-                          const OptScope scope, void *const from, Error *err)
-  FUNC_ATTR_NONNULL_ARG(1)
-{
-  switchwin_T switchwin;
-  aco_save_T aco;
-  void *ctx = scope == kOptScopeWin ? (void *)&switchwin
-                                    : (scope == kOptScopeBuf ? (void *)&aco : NULL);
-
-  bool switched = switch_option_context(ctx, scope, from, err);
-  if (ERROR_SET(err)) {
-    return;
-  }
-
-  const char *const errmsg = set_option_value_handle_tty(name, opt_idx, value, opt_flags);
-  if (errmsg) {
-    api_set_error(err, kErrorTypeException, "%s", errmsg);
-  }
-
-  if (switched) {
-    restore_option_context(ctx, scope);
-  }
-}
-
 /// if 'all' == false: show changed options
 /// if 'all' == true: show all normal options
 ///
@@ -4454,13 +4344,13 @@ static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
   return OK;
 }
 
-void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
+void *get_varp_scope_from(vimoption_T *p, int opt_flags, OptCtx ctx)
 {
   OptIndex opt_idx = get_opt_idx(p);
 
   if ((opt_flags & OPT_GLOBAL) && !option_is_global_only(opt_idx)) {
     if (option_is_window_local(opt_idx)) {
-      return GLOBAL_WO(get_varp_from(p, buf, win));
+      return GLOBAL_WO(get_varp_from(p, ctx));
     }
     return p->var;
   }
@@ -4468,88 +4358,73 @@ void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
   if ((opt_flags & OPT_LOCAL) && option_is_global_local(opt_idx)) {
     switch (opt_idx) {
     case kOptFormatprg:
-      return &(buf->b_p_fp);
+      return &(ctx.buf->b_p_fp);
     case kOptFindfunc:
-      return &(buf->b_p_ffu);
+      return &(ctx.buf->b_p_ffu);
     case kOptErrorformat:
-      return &(buf->b_p_efm);
+      return &(ctx.buf->b_p_efm);
     case kOptGrepprg:
-      return &(buf->b_p_gp);
+      return &(ctx.buf->b_p_gp);
     case kOptMakeprg:
-      return &(buf->b_p_mp);
+      return &(ctx.buf->b_p_mp);
     case kOptEqualprg:
-      return &(buf->b_p_ep);
+      return &(ctx.buf->b_p_ep);
     case kOptKeywordprg:
-      return &(buf->b_p_kp);
+      return &(ctx.buf->b_p_kp);
     case kOptPath:
-      return &(buf->b_p_path);
+      return &(ctx.buf->b_p_path);
     case kOptAutoread:
-      return &(buf->b_p_ar);
+      return &(ctx.buf->b_p_ar);
     case kOptTags:
-      return &(buf->b_p_tags);
+      return &(ctx.buf->b_p_tags);
     case kOptTagcase:
-      return &(buf->b_p_tc);
+      return &(ctx.buf->b_p_tc);
     case kOptSidescrolloff:
-      return &(win->w_p_siso);
+      return &(ctx.win->w_p_siso);
     case kOptScrolloff:
-      return &(win->w_p_so);
+      return &(ctx.win->w_p_so);
     case kOptDefine:
-      return &(buf->b_p_def);
+      return &(ctx.buf->b_p_def);
     case kOptInclude:
-      return &(buf->b_p_inc);
+      return &(ctx.buf->b_p_inc);
     case kOptCompleteopt:
-      return &(buf->b_p_cot);
+      return &(ctx.buf->b_p_cot);
     case kOptDictionary:
-      return &(buf->b_p_dict);
+      return &(ctx.buf->b_p_dict);
     case kOptThesaurus:
-      return &(buf->b_p_tsr);
+      return &(ctx.buf->b_p_tsr);
     case kOptThesaurusfunc:
-      return &(buf->b_p_tsrfu);
+      return &(ctx.buf->b_p_tsrfu);
     case kOptTagfunc:
-      return &(buf->b_p_tfu);
+      return &(ctx.buf->b_p_tfu);
     case kOptShowbreak:
-      return &(win->w_p_sbr);
+      return &(ctx.win->w_p_sbr);
     case kOptStatusline:
-      return &(win->w_p_stl);
+      return &(ctx.win->w_p_stl);
     case kOptWinbar:
-      return &(win->w_p_wbr);
+      return &(ctx.win->w_p_wbr);
     case kOptUndolevels:
-      return &(buf->b_p_ul);
+      return &(ctx.buf->b_p_ul);
     case kOptLispwords:
-      return &(buf->b_p_lw);
+      return &(ctx.buf->b_p_lw);
     case kOptBackupcopy:
-      return &(buf->b_p_bkc);
+      return &(ctx.buf->b_p_bkc);
     case kOptMakeencoding:
-      return &(buf->b_p_menc);
+      return &(ctx.buf->b_p_menc);
     case kOptFillchars:
-      return &(win->w_p_fcs);
+      return &(ctx.win->w_p_fcs);
     case kOptListchars:
-      return &(win->w_p_lcs);
+      return &(ctx.win->w_p_lcs);
     case kOptVirtualedit:
-      return &(win->w_p_ve);
+      return &(ctx.win->w_p_ve);
     default:
       abort();
     }
   }
-  return get_varp_from(p, buf, win);
-}
-
-/// Get pointer to option variable, depending on local or global scope.
-///
-/// @param  opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
-void *get_varp_scope(vimoption_T *p, int opt_flags)
-{
-  return get_varp_scope_from(p, opt_flags, curbuf, curwin);
+  return get_varp_from(p, ctx);
 }
 
-/// Get pointer to option variable at 'opt_idx', depending on local or global
-/// scope.
-void *get_option_varp_scope_from(OptIndex opt_idx, int opt_flags, buf_T *buf, win_T *win)
-{
-  return get_varp_scope_from(&(options[opt_idx]), opt_flags, buf, win);
-}
-
-void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
+void *get_varp_from(vimoption_T *p, OptCtx ctx)
 {
   OptIndex opt_idx = get_opt_idx(p);
 
@@ -4561,299 +4436,330 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
   switch (opt_idx) {
   // global option with local value: use local value if it's been set
   case kOptEqualprg:
-    return *buf->b_p_ep != NUL ? &buf->b_p_ep : p->var;
+    return *ctx.buf->b_p_ep != NUL ? &ctx.buf->b_p_ep : p->var;
   case kOptKeywordprg:
-    return *buf->b_p_kp != NUL ? &buf->b_p_kp : p->var;
+    return *ctx.buf->b_p_kp != NUL ? &ctx.buf->b_p_kp : p->var;
   case kOptPath:
-    return *buf->b_p_path != NUL ? &(buf->b_p_path) : p->var;
+    return *ctx.buf->b_p_path != NUL ? &(ctx.buf->b_p_path) : p->var;
   case kOptAutoread:
-    return buf->b_p_ar >= 0 ? &(buf->b_p_ar) : p->var;
+    return ctx.buf->b_p_ar >= 0 ? &(ctx.buf->b_p_ar) : p->var;
   case kOptTags:
-    return *buf->b_p_tags != NUL ? &(buf->b_p_tags) : p->var;
+    return *ctx.buf->b_p_tags != NUL ? &(ctx.buf->b_p_tags) : p->var;
   case kOptTagcase:
-    return *buf->b_p_tc != NUL ? &(buf->b_p_tc) : p->var;
+    return *ctx.buf->b_p_tc != NUL ? &(ctx.buf->b_p_tc) : p->var;
   case kOptSidescrolloff:
-    return win->w_p_siso >= 0 ? &(win->w_p_siso) : p->var;
+    return ctx.win->w_p_siso >= 0 ? &(ctx.win->w_p_siso) : p->var;
   case kOptScrolloff:
-    return win->w_p_so >= 0 ? &(win->w_p_so) : p->var;
+    return ctx.win->w_p_so >= 0 ? &(ctx.win->w_p_so) : p->var;
   case kOptBackupcopy:
-    return *buf->b_p_bkc != NUL ? &(buf->b_p_bkc) : p->var;
+    return *ctx.buf->b_p_bkc != NUL ? &(ctx.buf->b_p_bkc) : p->var;
   case kOptDefine:
-    return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var;
+    return *ctx.buf->b_p_def != NUL ? &(ctx.buf->b_p_def) : p->var;
   case kOptInclude:
-    return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var;
+    return *ctx.buf->b_p_inc != NUL ? &(ctx.buf->b_p_inc) : p->var;
   case kOptCompleteopt:
-    return *buf->b_p_cot != NUL ? &(buf->b_p_cot) : p->var;
+    return *ctx.buf->b_p_cot != NUL ? &(ctx.buf->b_p_cot) : p->var;
   case kOptDictionary:
-    return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var;
+    return *ctx.buf->b_p_dict != NUL ? &(ctx.buf->b_p_dict) : p->var;
   case kOptThesaurus:
-    return *buf->b_p_tsr != NUL ? &(buf->b_p_tsr) : p->var;
+    return *ctx.buf->b_p_tsr != NUL ? &(ctx.buf->b_p_tsr) : p->var;
   case kOptThesaurusfunc:
-    return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var;
+    return *ctx.buf->b_p_tsrfu != NUL ? &(ctx.buf->b_p_tsrfu) : p->var;
   case kOptFormatprg:
-    return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var;
+    return *ctx.buf->b_p_fp != NUL ? &(ctx.buf->b_p_fp) : p->var;
   case kOptFindfunc:
-    return *buf->b_p_ffu != NUL ? &(buf->b_p_ffu) : p->var;
+    return *ctx.buf->b_p_ffu != NUL ? &(ctx.buf->b_p_ffu) : p->var;
   case kOptErrorformat:
-    return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var;
+    return *ctx.buf->b_p_efm != NUL ? &(ctx.buf->b_p_efm) : p->var;
   case kOptGrepprg:
-    return *buf->b_p_gp != NUL ? &(buf->b_p_gp) : p->var;
+    return *ctx.buf->b_p_gp != NUL ? &(ctx.buf->b_p_gp) : p->var;
   case kOptMakeprg:
-    return *buf->b_p_mp != NUL ? &(buf->b_p_mp) : p->var;
+    return *ctx.buf->b_p_mp != NUL ? &(ctx.buf->b_p_mp) : p->var;
   case kOptShowbreak:
-    return *win->w_p_sbr != NUL ? &(win->w_p_sbr) : p->var;
+    return *ctx.win->w_p_sbr != NUL ? &(ctx.win->w_p_sbr) : p->var;
   case kOptStatusline:
-    return *win->w_p_stl != NUL ? &(win->w_p_stl) : p->var;
+    return *ctx.win->w_p_stl != NUL ? &(ctx.win->w_p_stl) : p->var;
   case kOptWinbar:
-    return *win->w_p_wbr != NUL ? &(win->w_p_wbr) : p->var;
+    return *ctx.win->w_p_wbr != NUL ? &(ctx.win->w_p_wbr) : p->var;
   case kOptUndolevels:
-    return buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(buf->b_p_ul) : p->var;
+    return ctx.buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(ctx.buf->b_p_ul) : p->var;
   case kOptLispwords:
-    return *buf->b_p_lw != NUL ? &(buf->b_p_lw) : p->var;
+    return *ctx.buf->b_p_lw != NUL ? &(ctx.buf->b_p_lw) : p->var;
   case kOptMakeencoding:
-    return *buf->b_p_menc != NUL ? &(buf->b_p_menc) : p->var;
+    return *ctx.buf->b_p_menc != NUL ? &(ctx.buf->b_p_menc) : p->var;
   case kOptFillchars:
-    return *win->w_p_fcs != NUL ? &(win->w_p_fcs) : p->var;
+    return *ctx.win->w_p_fcs != NUL ? &(ctx.win->w_p_fcs) : p->var;
   case kOptListchars:
-    return *win->w_p_lcs != NUL ? &(win->w_p_lcs) : p->var;
+    return *ctx.win->w_p_lcs != NUL ? &(ctx.win->w_p_lcs) : p->var;
   case kOptVirtualedit:
-    return *win->w_p_ve != NUL ? &win->w_p_ve : p->var;
+    return *ctx.win->w_p_ve != NUL ? &ctx.win->w_p_ve : p->var;
 
   case kOptArabic:
-    return &(win->w_p_arab);
+    return &(ctx.win->w_p_arab);
   case kOptList:
-    return &(win->w_p_list);
+    return &(ctx.win->w_p_list);
   case kOptSpell:
-    return &(win->w_p_spell);
+    return &(ctx.win->w_p_spell);
   case kOptCursorcolumn:
-    return &(win->w_p_cuc);
+    return &(ctx.win->w_p_cuc);
   case kOptCursorline:
-    return &(win->w_p_cul);
+    return &(ctx.win->w_p_cul);
   case kOptCursorlineopt:
-    return &(win->w_p_culopt);
+    return &(ctx.win->w_p_culopt);
   case kOptColorcolumn:
-    return &(win->w_p_cc);
+    return &(ctx.win->w_p_cc);
   case kOptDiff:
-    return &(win->w_p_diff);
+    return &(ctx.win->w_p_diff);
   case kOptFoldcolumn:
-    return &(win->w_p_fdc);
+    return &(ctx.win->w_p_fdc);
   case kOptFoldenable:
-    return &(win->w_p_fen);
+    return &(ctx.win->w_p_fen);
   case kOptFoldignore:
-    return &(win->w_p_fdi);
+    return &(ctx.win->w_p_fdi);
   case kOptFoldlevel:
-    return &(win->w_p_fdl);
+    return &(ctx.win->w_p_fdl);
   case kOptFoldmethod:
-    return &(win->w_p_fdm);
+    return &(ctx.win->w_p_fdm);
   case kOptFoldminlines:
-    return &(win->w_p_fml);
+    return &(ctx.win->w_p_fml);
   case kOptFoldnestmax:
-    return &(win->w_p_fdn);
+    return &(ctx.win->w_p_fdn);
   case kOptFoldexpr:
-    return &(win->w_p_fde);
+    return &(ctx.win->w_p_fde);
   case kOptFoldtext:
-    return &(win->w_p_fdt);
+    return &(ctx.win->w_p_fdt);
   case kOptFoldmarker:
-    return &(win->w_p_fmr);
+    return &(ctx.win->w_p_fmr);
   case kOptNumber:
-    return &(win->w_p_nu);
+    return &(ctx.win->w_p_nu);
   case kOptRelativenumber:
-    return &(win->w_p_rnu);
+    return &(ctx.win->w_p_rnu);
   case kOptNumberwidth:
-    return &(win->w_p_nuw);
+    return &(ctx.win->w_p_nuw);
   case kOptWinfixbuf:
-    return &(win->w_p_wfb);
+    return &(ctx.win->w_p_wfb);
   case kOptWinfixheight:
-    return &(win->w_p_wfh);
+    return &(ctx.win->w_p_wfh);
   case kOptWinfixwidth:
-    return &(win->w_p_wfw);
+    return &(ctx.win->w_p_wfw);
   case kOptPreviewwindow:
-    return &(win->w_p_pvw);
+    return &(ctx.win->w_p_pvw);
   case kOptRightleft:
-    return &(win->w_p_rl);
+    return &(ctx.win->w_p_rl);
   case kOptRightleftcmd:
-    return &(win->w_p_rlc);
+    return &(ctx.win->w_p_rlc);
   case kOptScroll:
-    return &(win->w_p_scr);
+    return &(ctx.win->w_p_scr);
   case kOptSmoothscroll:
-    return &(win->w_p_sms);
+    return &(ctx.win->w_p_sms);
   case kOptWrap:
-    return &(win->w_p_wrap);
+    return &(ctx.win->w_p_wrap);
   case kOptLinebreak:
-    return &(win->w_p_lbr);
+    return &(ctx.win->w_p_lbr);
   case kOptBreakindent:
-    return &(win->w_p_bri);
+    return &(ctx.win->w_p_bri);
   case kOptBreakindentopt:
-    return &(win->w_p_briopt);
+    return &(ctx.win->w_p_briopt);
   case kOptScrollbind:
-    return &(win->w_p_scb);
+    return &(ctx.win->w_p_scb);
   case kOptCursorbind:
-    return &(win->w_p_crb);
+    return &(ctx.win->w_p_crb);
   case kOptConcealcursor:
-    return &(win->w_p_cocu);
+    return &(ctx.win->w_p_cocu);
   case kOptConceallevel:
-    return &(win->w_p_cole);
+    return &(ctx.win->w_p_cole);
 
   case kOptAutoindent:
-    return &(buf->b_p_ai);
+    return &(ctx.buf->b_p_ai);
   case kOptBinary:
-    return &(buf->b_p_bin);
+    return &(ctx.buf->b_p_bin);
   case kOptBomb:
-    return &(buf->b_p_bomb);
+    return &(ctx.buf->b_p_bomb);
   case kOptBufhidden:
-    return &(buf->b_p_bh);
+    return &(ctx.buf->b_p_bh);
   case kOptBuftype:
-    return &(buf->b_p_bt);
+    return &(ctx.buf->b_p_bt);
   case kOptBuflisted:
-    return &(buf->b_p_bl);
+    return &(ctx.buf->b_p_bl);
   case kOptChannel:
-    return &(buf->b_p_channel);
+    return &(ctx.buf->b_p_channel);
   case kOptCopyindent:
-    return &(buf->b_p_ci);
+    return &(ctx.buf->b_p_ci);
   case kOptCindent:
-    return &(buf->b_p_cin);
+    return &(ctx.buf->b_p_cin);
   case kOptCinkeys:
-    return &(buf->b_p_cink);
+    return &(ctx.buf->b_p_cink);
   case kOptCinoptions:
-    return &(buf->b_p_cino);
+    return &(ctx.buf->b_p_cino);
   case kOptCinscopedecls:
-    return &(buf->b_p_cinsd);
+    return &(ctx.buf->b_p_cinsd);
   case kOptCinwords:
-    return &(buf->b_p_cinw);
+    return &(ctx.buf->b_p_cinw);
   case kOptComments:
-    return &(buf->b_p_com);
+    return &(ctx.buf->b_p_com);
   case kOptCommentstring:
-    return &(buf->b_p_cms);
+    return &(ctx.buf->b_p_cms);
   case kOptComplete:
-    return &(buf->b_p_cpt);
+    return &(ctx.buf->b_p_cpt);
 #ifdef BACKSLASH_IN_FILENAME
   case kOptCompleteslash:
-    return &(buf->b_p_csl);
+    return &(ctx.buf->b_p_csl);
 #endif
   case kOptCompletefunc:
-    return &(buf->b_p_cfu);
+    return &(ctx.buf->b_p_cfu);
   case kOptOmnifunc:
-    return &(buf->b_p_ofu);
+    return &(ctx.buf->b_p_ofu);
   case kOptEndoffile:
-    return &(buf->b_p_eof);
+    return &(ctx.buf->b_p_eof);
   case kOptEndofline:
-    return &(buf->b_p_eol);
+    return &(ctx.buf->b_p_eol);
   case kOptFixendofline:
-    return &(buf->b_p_fixeol);
+    return &(ctx.buf->b_p_fixeol);
   case kOptExpandtab:
-    return &(buf->b_p_et);
+    return &(ctx.buf->b_p_et);
   case kOptFileencoding:
-    return &(buf->b_p_fenc);
+    return &(ctx.buf->b_p_fenc);
   case kOptFileformat:
-    return &(buf->b_p_ff);
+    return &(ctx.buf->b_p_ff);
   case kOptFiletype:
-    return &(buf->b_p_ft);
+    return &(ctx.buf->b_p_ft);
   case kOptFormatoptions:
-    return &(buf->b_p_fo);
+    return &(ctx.buf->b_p_fo);
   case kOptFormatlistpat:
-    return &(buf->b_p_flp);
+    return &(ctx.buf->b_p_flp);
   case kOptIminsert:
-    return &(buf->b_p_iminsert);
+    return &(ctx.buf->b_p_iminsert);
   case kOptImsearch:
-    return &(buf->b_p_imsearch);
+    return &(ctx.buf->b_p_imsearch);
   case kOptInfercase:
-    return &(buf->b_p_inf);
+    return &(ctx.buf->b_p_inf);
   case kOptIskeyword:
-    return &(buf->b_p_isk);
+    return &(ctx.buf->b_p_isk);
   case kOptIncludeexpr:
-    return &(buf->b_p_inex);
+    return &(ctx.buf->b_p_inex);
   case kOptIndentexpr:
-    return &(buf->b_p_inde);
+    return &(ctx.buf->b_p_inde);
   case kOptIndentkeys:
-    return &(buf->b_p_indk);
+    return &(ctx.buf->b_p_indk);
   case kOptFormatexpr:
-    return &(buf->b_p_fex);
+    return &(ctx.buf->b_p_fex);
   case kOptLisp:
-    return &(buf->b_p_lisp);
+    return &(ctx.buf->b_p_lisp);
   case kOptLispoptions:
-    return &(buf->b_p_lop);
+    return &(ctx.buf->b_p_lop);
   case kOptModeline:
-    return &(buf->b_p_ml);
+    return &(ctx.buf->b_p_ml);
   case kOptMatchpairs:
-    return &(buf->b_p_mps);
+    return &(ctx.buf->b_p_mps);
   case kOptModifiable:
-    return &(buf->b_p_ma);
+    return &(ctx.buf->b_p_ma);
   case kOptModified:
-    return &(buf->b_changed);
+    return &(ctx.buf->b_changed);
   case kOptNrformats:
-    return &(buf->b_p_nf);
+    return &(ctx.buf->b_p_nf);
   case kOptPreserveindent:
-    return &(buf->b_p_pi);
+    return &(ctx.buf->b_p_pi);
   case kOptQuoteescape:
-    return &(buf->b_p_qe);
+    return &(ctx.buf->b_p_qe);
   case kOptReadonly:
-    return &(buf->b_p_ro);
+    return &(ctx.buf->b_p_ro);
   case kOptScrollback:
-    return &(buf->b_p_scbk);
+    return &(ctx.buf->b_p_scbk);
   case kOptSmartindent:
-    return &(buf->b_p_si);
+    return &(ctx.buf->b_p_si);
   case kOptSofttabstop:
-    return &(buf->b_p_sts);
+    return &(ctx.buf->b_p_sts);
   case kOptSuffixesadd:
-    return &(buf->b_p_sua);
+    return &(ctx.buf->b_p_sua);
   case kOptSwapfile:
-    return &(buf->b_p_swf);
+    return &(ctx.buf->b_p_swf);
   case kOptSynmaxcol:
-    return &(buf->b_p_smc);
+    return &(ctx.buf->b_p_smc);
   case kOptSyntax:
-    return &(buf->b_p_syn);
+    return &(ctx.buf->b_p_syn);
   case kOptSpellcapcheck:
-    return &(win->w_s->b_p_spc);
+    return &(ctx.win->w_s->b_p_spc);
   case kOptSpellfile:
-    return &(win->w_s->b_p_spf);
+    return &(ctx.win->w_s->b_p_spf);
   case kOptSpelllang:
-    return &(win->w_s->b_p_spl);
+    return &(ctx.win->w_s->b_p_spl);
   case kOptSpelloptions:
-    return &(win->w_s->b_p_spo);
+    return &(ctx.win->w_s->b_p_spo);
   case kOptShiftwidth:
-    return &(buf->b_p_sw);
+    return &(ctx.buf->b_p_sw);
   case kOptTagfunc:
-    return &(buf->b_p_tfu);
+    return &(ctx.buf->b_p_tfu);
   case kOptTabstop:
-    return &(buf->b_p_ts);
+    return &(ctx.buf->b_p_ts);
   case kOptTextwidth:
-    return &(buf->b_p_tw);
+    return &(ctx.buf->b_p_tw);
   case kOptUndofile:
-    return &(buf->b_p_udf);
+    return &(ctx.buf->b_p_udf);
   case kOptWrapmargin:
-    return &(buf->b_p_wm);
+    return &(ctx.buf->b_p_wm);
   case kOptVarsofttabstop:
-    return &(buf->b_p_vsts);
+    return &(ctx.buf->b_p_vsts);
   case kOptVartabstop:
-    return &(buf->b_p_vts);
+    return &(ctx.buf->b_p_vts);
   case kOptKeymap:
-    return &(buf->b_p_keymap);
+    return &(ctx.buf->b_p_keymap);
   case kOptSigncolumn:
-    return &(win->w_p_scl);
+    return &(ctx.win->w_p_scl);
   case kOptWinhighlight:
-    return &(win->w_p_winhl);
+    return &(ctx.win->w_p_winhl);
   case kOptWinblend:
-    return &(win->w_p_winbl);
+    return &(ctx.win->w_p_winbl);
   case kOptStatuscolumn:
-    return &(win->w_p_stc);
+    return &(ctx.win->w_p_stc);
   default:
     iemsg(_("E356: get_varp ERROR"));
   }
   // always return a valid pointer to avoid a crash!
-  return &(buf->b_p_wm);
+  return &(ctx.buf->b_p_wm);
 }
 
-/// Get option index from option pointer
-static inline OptIndex get_opt_idx(vimoption_T *opt)
-  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+/// Get the current context for options.
+OptCtx option_ctx(void)
 {
-  return (OptIndex)(opt - options);
+  return (OptCtx){ .buf = curbuf, .win = curwin };
+}
+
+/// Get the context for options from a scope and pointer to target.
+///
+/// @param  scope      Option scope. See OptScope in option.h.
+/// @param  from       Pointer to target buffer/window/etc.
+OptCtx option_ctx_from(OptScope scope, void *from)
+{
+  switch (scope) {
+  case kOptScopeGlobal:
+    return option_ctx();
+  case kOptScopeBuf:
+    return (OptCtx){ .buf = (buf_T *)from, .win = curwin };
+  case kOptScopeWin:
+    return (OptCtx){ .buf = ((win_T *)from)->w_buffer, .win = (win_T *)from };
+  }
+  UNREACHABLE;
+}
+
+/// Get pointer to option variable, depending on local or global scope.
+///
+/// @param  opt_flags  Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+void *get_varp_scope(vimoption_T *p, int opt_flags)
+{
+  return get_varp_scope_from(p, opt_flags, option_ctx());
 }
 
 /// Get pointer to option variable.
 static inline void *get_varp(vimoption_T *p)
 {
-  return get_varp_from(p, curbuf, curwin);
+  return get_varp_from(p, option_ctx());
+}
+
+/// Get option index from option pointer
+static inline OptIndex get_opt_idx(vimoption_T *opt)
+  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+  return (OptIndex)(opt - options);
 }
 
 /// Get the value of 'equalprg', either the buffer-local one or the global one.
@@ -5799,9 +5705,7 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c
     return ExpandOldSetting(numMatches, matches);
   }
 
-  char *option_val = *(char **)get_option_varp_scope_from(expand_option_idx,
-                                                          expand_option_flags,
-                                                          curbuf, curwin);
+  char *option_val = *(char **)get_varp_scope(get_option(expand_option_idx), expand_option_flags);
 
   uint32_t option_flags = options[expand_option_idx].flags;
 
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 832e03148a..b76532b1eb 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -81,6 +81,12 @@ typedef struct {
   OptValData data;
 } OptVal;
 
+/// Context that an option is being set for.
+typedef struct {
+  win_T *win;
+  buf_T *buf;
+} OptCtx;
+
 /// :set operator types
 typedef enum {
   OP_NONE = 0,
@@ -122,8 +128,7 @@ typedef struct {
   /// length of the error buffer
   size_t os_errbuflen;
 
-  void *os_win;
-  void *os_buf;
+  OptCtx os_ctx;
 } optset_T;
 
 /// Type for the callback function that is invoked after an option value is
@@ -192,3 +197,12 @@ typedef struct {
   OptVal def_val;                    ///< default value
   LastSet last_set;                  ///< script in which the option was last set
 } vimoption_T;
+
+/// Execute code with autocmd context
+#define WITH_AUCMD_CONTEXT(ctx, code) \
+  do { \
+    aco_save_T _aco; \
+    aucmd_prepbuf_win(&_aco, ctx.buf, ctx.win); \
+    code; \
+    aucmd_restbuf(&_aco); \
+  } while (0)
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 9b7b50ae04..632b4af573 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -567,7 +567,7 @@ int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'backupcopy' option is changed.
 const char *did_set_backupcopy(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   const char *oldval = args->os_oldval.string.data;
   int opt_flags = args->os_flags;
   char *bkc = p_bkc;
@@ -655,7 +655,7 @@ const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED)
 /// The 'breakindentopt' option is changed.
 const char *did_set_breakindentopt(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
 
   if (briopt_check(*varp, varp == &win->w_p_briopt ? win : NULL) == FAIL) {
@@ -682,7 +682,7 @@ int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matche
 /// The 'bufhidden' option is changed.
 const char *did_set_bufhidden(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   return did_set_opt_strings(buf->b_p_bh, opt_bh_values, false);
 }
 
@@ -698,8 +698,8 @@ int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'buftype' option is changed.
 const char *did_set_buftype(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
-  win_T *win = (win_T *)args->os_win;
+  buf_T *buf = args->os_ctx.buf;
+  win_T *win = args->os_ctx.win;
   // When 'buftype' is set, check for valid value.
   if ((buf->terminal && buf->b_p_bt[0] != 't')
       || (!buf->terminal && buf->b_p_bt[0] == 't')
@@ -780,7 +780,7 @@ static const char *did_set_global_chars_option(win_T *win, char *val, CharsOptio
 /// The 'fillchars' option or the 'listchars' option is changed.
 const char *did_set_chars_option(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   const char *errmsg = NULL;
 
@@ -815,7 +815,7 @@ int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'cinoptions' option is changed.
 const char *did_set_cinoptions(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   // TODO(vim): recognize errors
   parse_cino(buf);
 
@@ -840,7 +840,7 @@ int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'colorcolumn' option is changed.
 const char *did_set_colorcolumn(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   return check_colorcolumn(*varp, varp == &win->w_p_cc ? win : NULL);
 }
@@ -985,7 +985,7 @@ const char *did_set_completeitemalign(optset_T *args)
 /// The 'completeopt' option is changed.
 const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   char *cot = p_cot;
   unsigned *flags = &cot_flags;
 
@@ -1021,7 +1021,7 @@ int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'completeslash' option is changed.
 const char *did_set_completeslash(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   if (check_opt_strings(p_csl, opt_csl_values, false) != OK
       || check_opt_strings(buf->b_p_csl, opt_csl_values, false) != OK) {
     return e_invarg;
@@ -1068,7 +1068,7 @@ int expand_set_cpoptions(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'cursorlineopt' option is changed.
 const char *did_set_cursorlineopt(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
 
   // This could be changed to use opt_strings_flags() instead.
@@ -1176,12 +1176,12 @@ int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches)
 /// options is changed.
 const char *did_set_encoding(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   char **varp = (char **)args->os_varp;
   int opt_flags = args->os_flags;
   // Get the global option to compare with, otherwise we would have to check
   // two values for all local options.
-  char **gvarp = (char **)get_option_varp_scope_from(args->os_idx, OPT_GLOBAL, buf, NULL);
+  char **gvarp = (char **)get_varp_scope_from(get_option(args->os_idx), OPT_GLOBAL, args->os_ctx);
 
   if (gvarp == &p_fenc) {
     if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) {
@@ -1246,7 +1246,7 @@ int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'fileformat' option is changed.
 const char *did_set_fileformat(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   char **varp = (char **)args->os_varp;
   const char *oldval = args->os_oldval.string.data;
   int opt_flags = args->os_flags;
@@ -1347,7 +1347,7 @@ int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'foldexpr' option is changed.
 const char *did_set_foldexpr(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   did_set_optexpr(args);
   if (foldmethodIsExpr(win)) {
     foldUpdateAll(win);
@@ -1358,7 +1358,7 @@ const char *did_set_foldexpr(optset_T *args)
 /// The 'foldignore' option is changed.
 const char *did_set_foldignore(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   if (foldmethodIsIndent(win)) {
     foldUpdateAll(win);
   }
@@ -1368,7 +1368,7 @@ const char *did_set_foldignore(optset_T *args)
 /// The 'foldmarker' option is changed.
 const char *did_set_foldmarker(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   char *p = vim_strchr(*varp, ',');
 
@@ -1390,7 +1390,7 @@ const char *did_set_foldmarker(optset_T *args)
 /// The 'foldmethod' option is changed.
 const char *did_set_foldmethod(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   if (check_opt_strings(*varp, opt_fdm_values, false) != OK || **varp == NUL) {
     return e_invarg;
@@ -1536,7 +1536,7 @@ const char *did_set_iskeyword(optset_T *args)
 /// changed.
 const char *did_set_isopt(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   // 'isident', 'iskeyword', 'isprint' or 'isfname' option: refill g_chartab[]
   // If the new option is invalid, use old value.
   // 'lisp' option: refill g_chartab[] for '-' char
@@ -1565,7 +1565,7 @@ int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'keymap' option has changed.
 const char *did_set_keymap(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   char **varp = (char **)args->os_varp;
   int opt_flags = args->os_flags;
 
@@ -2053,7 +2053,7 @@ int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'signcolumn' option is changed.
 const char *did_set_signcolumn(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   const char *oldval = args->os_oldval.string.data;
   if (check_signcolumn(*varp, varp == &win->w_p_scl ? win : NULL) != OK) {
@@ -2079,7 +2079,7 @@ int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches)
 /// The 'spellcapcheck' option is changed.
 const char *did_set_spellcapcheck(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   // When 'spellcapcheck' is set compile the regexp program.
   return compile_cap_prog(win->w_s);
 }
@@ -2113,7 +2113,7 @@ const char *did_set_spelllang(optset_T *args)
 /// The 'spelloptions' option is changed.
 const char *did_set_spelloptions(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   int opt_flags = args->os_flags;
   const char *val = args->os_newval.string.data;
 
@@ -2189,7 +2189,7 @@ const char *did_set_statusline(optset_T *args)
 static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerformat,
                                                      bool statuscolumn)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   if (rulerformat) {       // reset ru_wid first
     ru_wid = 0;
@@ -2264,7 +2264,7 @@ const char *did_set_tabline(optset_T *args)
 /// The 'tagcase' option is changed.
 const char *did_set_tagcase(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   int opt_flags = args->os_flags;
 
   unsigned *flags;
@@ -2337,7 +2337,7 @@ const char *did_set_titlestring(optset_T *args)
 /// The 'varsofttabstop' option is changed.
 const char *did_set_varsofttabstop(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
   char **varp = (char **)args->os_varp;
 
   if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
@@ -2367,8 +2367,8 @@ const char *did_set_varsofttabstop(optset_T *args)
 /// The 'varstabstop' option is changed.
 const char *did_set_vartabstop(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
-  win_T *win = (win_T *)args->os_win;
+  buf_T *buf = args->os_ctx.buf;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
 
   if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
@@ -2417,7 +2417,7 @@ const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED)
 /// The 'virtualedit' option is changed.
 const char *did_set_virtualedit(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
 
   char *ve = p_ve;
   unsigned *flags = &ve_flags;
@@ -2527,7 +2527,7 @@ const char *did_set_winbar(optset_T *args)
 /// The 'winhighlight' option is changed.
 const char *did_set_winhighlight(optset_T *args)
 {
-  win_T *win = (win_T *)args->os_win;
+  win_T *win = args->os_ctx.win;
   char **varp = (char **)args->os_varp;
   if (!parse_winhl_opt(*varp, varp == &win->w_p_winhl ? win : NULL)) {
     return e_invarg;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index cdce97fc01..dda77f8e1e 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -228,7 +228,7 @@ static Callback tfu_cb;          // 'tagfunc' callback function
 /// a function (string), or function() or funcref() or a lambda.
 const char *did_set_tagfunc(optset_T *args)
 {
-  buf_T *buf = (buf_T *)args->os_buf;
+  buf_T *buf = args->os_ctx.buf;
 
   callback_free(&tfu_cb);
   callback_free(&buf->b_tfu_cb);
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
index 3e791e2beb..78f3551087 100644
--- a/src/nvim/winfloat.c
+++ b/src/nvim/winfloat.c
@@ -411,8 +411,8 @@ win_T *win_float_create(bool enter, bool new_buf)
       return handle_error_and_cleanup(wp, &err);
     }
     buf->b_p_bl = false;  // unlist
-    set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL, 0,
-                          kOptScopeBuf, buf);
+    set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("wipe"),
+                          option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0);
     win_set_buf(wp, buf, &err);
     if (ERROR_SET(&err)) {
       return handle_error_and_cleanup(wp, &err);
diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua
index 92e798e7f3..a68a7722a4 100644
--- a/test/functional/lua/with_spec.lua
+++ b/test/functional/lua/with_spec.lua
@@ -882,11 +882,7 @@ describe('vim._with', function()
       eq({
         bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = -123456 },
         wo = { ve_cur = 'insert', ve_other = 'block', winbl_cur = 25, winbl_other = 10 },
-        -- Global `winbl` inside context ideally should be untouched and equal
-        -- to 50. It seems to be equal to 0 because `context.buf` uses
-        -- `aucmd_prepbuf` C approach which has no guarantees about window or
-        -- window option values inside context.
-        go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 0, lmap = 'xy,yx' },
+        go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 50, lmap = 'xy,yx' },
       }, out.inner)
       eq(out.before, out.after)
     end)
-- 
cgit 


From 557f2d970044b04a364134ec45060b21b0f0b108 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 27 Dec 2024 09:11:03 +0800
Subject: vim-patch:9b67a2e: runtime(vim): Update base-syntax, allow parens in
 default arguments (#31738)

Allow parentheses in default arguments specified in :def and :function
definitions.

fixes vim/vim#16243
closes: vim/vim#16269

https://github.com/vim/vim/commit/9b67a2e1ddf277faf01fa957bf72f7b804a7cb7f

Co-authored-by: Doug Kearns 
---
 runtime/syntax/vim.vim | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 2a4bb2c8c9..361bf6369d 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -309,8 +309,8 @@ syn match	vimFuncSID	contained	"\<[sg]:"
 syn keyword	vimFuncKey	contained	fu[nction]
 syn keyword	vimDefKey	contained	def
 
-syn region	vimFuncParams	contained	matchgroup=Delimiter start="(" skip=+\n\s*\\\|\n\s*"\\ + end=")" skipwhite skipempty nextgroup=vimFuncBody,vimFuncComment,vimEndfunction,vimFuncMod,vim9CommentError	contains=vimFuncParam,@vimContinue
-syn region	vimDefParams	contained	matchgroup=Delimiter start="("		   end=")" skipwhite skipempty nextgroup=vimDefBody,vimDefComment,vimEnddef,vimReturnType,vimCommentError	contains=vimDefParam,vim9Comment,vimFuncParamEquals
+syn region	vimFuncParams	contained	matchgroup=Delimiter start="(" skip=+\n\s*\\\|\n\s*"\\ + end=")" skipwhite skipempty nextgroup=vimFuncBody,vimFuncComment,vimEndfunction,vimFuncMod,vim9CommentError	contains=vimFuncParam,vimOperParen,@vimContinue
+syn region	vimDefParams	contained	matchgroup=Delimiter start="("		   end=")" skipwhite skipempty nextgroup=vimDefBody,vimDefComment,vimEnddef,vimReturnType,vimCommentError	contains=vimDefParam,vim9Comment,vimFuncParamEquals,vimOperParen
 syn match	vimFuncParam	contained	"\<\h\w*\>\|\.\.\."	skipwhite nextgroup=vimFuncParamEquals
 syn match	vimDefParam	contained	"\<\h\w*\>"		skipwhite nextgroup=vimParamType,vimFuncParamEquals
 
-- 
cgit 


From 46c7faa00b165a1ad1b7cef8245df30d3dc3ef56 Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Fri, 27 Dec 2024 14:23:06 +0800
Subject: vim-patch:9.1.0963: fuzzy-matching does not prefer full match
 (#31741)

Problem:  fuzzy-matching does not prefer full match
          (Maxim Kim)
Solution: add additional score for a full match
          (glepnir)

fixes: vim/vim#15654
closes: vim/vim#16300

https://github.com/vim/vim/commit/5a04999a7402201cf1b47ff10bc474dd1cdc24f4
---
 runtime/doc/pattern.txt              | 1 +
 src/nvim/search.c                    | 9 +++++++++
 test/old/testdir/test_matchfuzzy.vim | 6 ++++--
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt
index 7f0938be05..be913e941e 100644
--- a/runtime/doc/pattern.txt
+++ b/runtime/doc/pattern.txt
@@ -1485,6 +1485,7 @@ criteria:
     - Matches at a camel case character (e.g. Case in CamelCase)
     - Matches after a path separator or a hyphen.
     - The number of unmatched characters in a string.
+    - A full/exact match is preferred.
 The matching string with the highest score is returned first.
 
 For example, when you search for the "get pat" string using fuzzy matching, it
diff --git a/src/nvim/search.c b/src/nvim/search.c
index faa14dfaf4..5a53122739 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -2995,6 +2995,7 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz,
   assert(numMatches > 0);  // suppress clang "result of operation is garbage"
   // Initialize score
   int score = 100;
+  bool is_exact_match = true;
 
   // Apply leading letter penalty
   int penalty = LEADING_LETTER_PENALTY * (int)matches[0];
@@ -3048,6 +3049,14 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz,
       // First letter
       score += FIRST_LETTER_BONUS;
     }
+    // Check exact match condition
+    if (currIdx != (uint32_t)i) {
+      is_exact_match = false;
+    }
+  }
+  // Boost score for exact matches
+  if (is_exact_match && numMatches == strSz) {
+    score += 100;
   }
   return score;
 }
diff --git a/test/old/testdir/test_matchfuzzy.vim b/test/old/testdir/test_matchfuzzy.vim
index 90f3366b23..c2dc07ed10 100644
--- a/test/old/testdir/test_matchfuzzy.vim
+++ b/test/old/testdir/test_matchfuzzy.vim
@@ -23,6 +23,8 @@ func Test_matchfuzzy()
   call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
   call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
   call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
+  " full match has highest score
+  call assert_equal(['Cursor', 'lCursor'], matchfuzzy(["hello", "lCursor", "Cursor"], "Cursor"))
   " matches with same score should not be reordered
   let l = ['abc1', 'abc2', 'abc3']
   call assert_equal(l, l->matchfuzzy('abc'))
@@ -101,7 +103,7 @@ func Test_matchfuzzypos()
   call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
   call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
   call assert_equal([['hello', 'hello world hello world'],
-        \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
+        \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [375, 257]],
         \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
   call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
   call assert_equal([['a  b'], [[0, 3]], [219]], matchfuzzypos(['a  b'], 'a  b'))
@@ -136,7 +138,7 @@ func Test_matchfuzzypos()
   call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [326]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1}))
   call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
   call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
-  call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
+  call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [757]], ['grace']->matchfuzzypos('race ace grace'))
 
   let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
   call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
-- 
cgit 


From 69fbb58385d14a623be6652fcd2d4c3ff65de1d3 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Thu, 26 Dec 2024 18:56:31 +0100
Subject: vim-patch:9.1.0962: filetype: bun.lock file is not recognized

Problem:  filetype: bun.lock file is not recognized
Solution: detect 'bun.lock' file as jsonc filetype
          (Anton Kastritskii)

closes: vim/vim#16308

https://github.com/vim/vim/commit/f07ae5b3bdb7331ee0e65adcb74402eef74f0a2b

Co-authored-by: Anton Kastritskii 
---
 runtime/lua/vim/filetype.lua       | 1 +
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index ad74a16e09..d6a29ed84d 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1636,6 +1636,7 @@ local filename = {
   ['.luaurc'] = 'jsonc',
   ['.swrc'] = 'jsonc',
   ['.vsconfig'] = 'jsonc',
+  ['bun.lock'] = 'jsonc',
   ['.justfile'] = 'just',
   ['justfile'] = 'just',
   ['Justfile'] = 'just',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index f556d551dd..94bf208b67 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -391,7 +391,7 @@ func s:GetFilenameChecks() abort
     \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'],
     \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'],
     \ 'json5': ['file.json5'],
-    \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc'],
+    \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock'],
     \ 'jsonl': ['file.jsonl'],
     \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'],
     \ 'jsp': ['file.jsp'],
-- 
cgit 


From 6d2c67350ad89abf09c5ddaaf02bcccfc5fc466c Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 27 Dec 2024 11:41:44 +0100
Subject: build(deps): bump tree-sitter to v0.24.6

---
 cmake.deps/deps.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index a94c85bd4d..e6b5f5b989 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -50,8 +50,8 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a
 TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e
 TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz
 TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5
-TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.5.tar.gz
-TREESITTER_SHA256 b5ac48acf5a04fd82ccd4246ad46354d9c434be26c9606233917549711e4252c
+TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.6.tar.gz
+TREESITTER_SHA256 03c7ee1e6f9f4f3821fd4af0ae06e1da60433b304a73ff92ee9694933009121a
 
 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.2.tar.gz
 WASMTIME_SHA256 6d1c17c756b83f29f629963228e5fa208ba9d6578421ba2cd07132b6a120accb
-- 
cgit 


From 35247b00a44e838ed7d657a9b94964dc0664d28d Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Fri, 27 Dec 2024 10:09:22 -0600
Subject: feat(lsp): support function for client root_dir (#31630)

If root_dir is a function it is evaluated when the client is created to
determine the root directory.

This enables dynamically determining the root directory based on e.g.
project or directory structure (example: finding a parent Cargo.toml
file that contains "[workspace]" in a Rust project).
---
 runtime/doc/lsp.txt                 |  7 +++++++
 runtime/lua/vim/lsp.lua             | 27 ++++++++++++++++++++++-----
 test/functional/plugin/lsp_spec.lua | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 8b822daf9e..16f543088b 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -683,6 +683,13 @@ Lua module: vim.lsp                                                 *lsp-core*
                          the LSP server will base its workspaceFolders,
                          rootUri, and rootPath on initialization. Unused if
                          `root_dir` is provided.
+      • {root_dir}?      (`string|fun(cb:fun(string))`) Directory where the
+                         LSP server will base its workspaceFolders, rootUri,
+                         and rootPath on initialization. If a function, it
+                         accepts a single callback argument which must be
+                         called with the value of root_dir to use. The LSP
+                         server will not be started until the callback is
+                         called.
       • {reuse_client}?  (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`)
                          Predicate used to decide if a client should be
                          re-used. Used on all running clients. The default
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 19d0377585..1c8356d64d 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -334,6 +334,11 @@ end
 --- rootUri, and rootPath on initialization. Unused if `root_dir` is provided.
 --- @field root_markers? string[]
 ---
+--- Directory where the LSP server will base its workspaceFolders, rootUri, and rootPath on
+--- initialization. If a function, it accepts a single callback argument which must be called with
+--- the value of root_dir to use. The LSP server will not be started until the callback is called.
+--- @field root_dir? string|fun(cb:fun(string))
+---
 --- Predicate used to decide if a client should be re-used. Used on all
 --- running clients. The default implementation re-uses a client if name and
 --- root_dir matches.
@@ -499,6 +504,15 @@ local function lsp_enable_callback(bufnr)
     return true
   end
 
+  --- @param config vim.lsp.Config
+  local function start(config)
+    return vim.lsp.start(config, {
+      bufnr = bufnr,
+      reuse_client = config.reuse_client,
+      _root_markers = config.root_markers,
+    })
+  end
+
   for name in vim.spairs(lsp._enabled_configs) do
     local config = lsp._resolve_config(name)
 
@@ -507,11 +521,14 @@ local function lsp_enable_callback(bufnr)
       -- do not propagate back to the enabled configs.
       config = vim.deepcopy(config)
 
-      vim.lsp.start(config, {
-        bufnr = bufnr,
-        reuse_client = config.reuse_client,
-        _root_markers = config.root_markers,
-      })
+      if type(config.root_dir) == 'function' then
+        config.root_dir(function(root_dir)
+          config.root_dir = root_dir
+          start(config)
+        end)
+      else
+        start(config)
+      end
     end
   end
 end
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 1f246b0914..f396c837f9 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -6245,5 +6245,38 @@ describe('LSP', function()
         end)
       )
     end)
+
+    it('supports a function for root_dir', function()
+      exec_lua(create_server_definition)
+
+      local tmp1 = t.tmpname(true)
+
+      eq(
+        'some_dir',
+        exec_lua(function()
+          local server = _G._create_server({
+            handlers = {
+              initialize = function(_, _, callback)
+                callback(nil, { capabilities = {} })
+              end,
+            },
+          })
+
+          vim.lsp.config('foo', {
+            cmd = server.cmd,
+            filetypes = { 'foo' },
+            root_dir = function(cb)
+              cb('some_dir')
+            end,
+          })
+          vim.lsp.enable('foo')
+
+          vim.cmd.edit(assert(tmp1))
+          vim.bo.filetype = 'foo'
+
+          return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir
+        end)
+      )
+    end)
   end)
 end)
-- 
cgit 


From 48c09ed4d9edd92a7c665a62aed04e8597088e60 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 27 Dec 2024 16:26:03 +0100
Subject: vim-patch:6c57c30: runtime(compiler): include a basic bash syntax
 checker compiler

See @saccarosium 's suggestion at
https://github.com/vim/vim/pull/16311#issuecomment-2563447885

closes: vim/vim#16314

https://github.com/vim/vim/commit/6c57c30ad43f5e0d040f7d432ceb5d61fc6ab651

Co-authored-by: Konfekt 
---
 runtime/compiler/bash.vim | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 runtime/compiler/bash.vim

diff --git a/runtime/compiler/bash.vim b/runtime/compiler/bash.vim
new file mode 100644
index 0000000000..cbd76ae410
--- /dev/null
+++ b/runtime/compiler/bash.vim
@@ -0,0 +1,12 @@
+" Vim compiler file
+" Compiler:     Bash Syntax Checker
+" Maintainer:   @konfekt
+" Last Change:  2024 Dec 27
+
+if exists("current_compiler")
+   finish
+endif
+let current_compiler = "bash"
+
+CompilerSet makeprg=bash\ -n
+CompilerSet errorformat=%f:\ line\ %l:\ %m
-- 
cgit 


From 518070731003e30ea7eee96e76ccdf7b950c90da Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Fri, 5 Apr 2024 14:48:13 +0600
Subject: feat(lua): add `vim.fs.abspath`

Problem: There is currently no way to check if a given path is absolute or convert a relative path to an absolute path through the Lua stdlib. `vim.fs.joinpath` does not work when the path is absolute. There is also currently no way to resolve `C:foo\bar` style paths in Windows.

Solution: Add `vim.fs.abspath`, which allows converting any path to an absolute path. This also allows checking if current path is absolute by doing `vim.fs.abspath(path) == path`. It also has support for `C:foo\bar` style paths in Windows.
---
 runtime/doc/lua.txt             | 14 +++++++++
 runtime/doc/news.txt            |  1 +
 runtime/lua/vim/fs.lua          | 69 ++++++++++++++++++++++++++++++++++++-----
 test/functional/lua/fs_spec.lua | 34 ++++++++++++++++++++
 4 files changed, 110 insertions(+), 8 deletions(-)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 711607d14b..a2a83ef229 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2945,6 +2945,20 @@ Example: >lua
 <
 
 
+vim.fs.abspath({path})                                      *vim.fs.abspath()*
+    Convert path to an absolute path. A tilde (~) character at the beginning
+    of the path is expanded to the user's home directory. Does not check if
+    the path exists, normalize the path, resolve symlinks or hardlinks
+    (including `.` and `..`), or expand environment variables. If the path is
+    already absolute, it is returned unchanged. Also converts `\` path
+    separators to `/`.
+
+    Parameters: ~
+      • {path}  (`string`) Path
+
+    Return: ~
+        (`string`) Absolute path
+
 vim.fs.basename({file})                                    *vim.fs.basename()*
     Return the basename of the given path
 
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index c4ba4f0ad1..7ea65479f3 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -268,6 +268,7 @@ LUA
   is more performant and easier to read.
 • |vim.str_byteindex()| and |vim.str_utfindex()| gained overload signatures
   supporting two new parameters, `encoding` and `strict_indexing`.
+• |vim.fs.abspath()| converts paths to absolute paths.
 
 OPTIONS
 
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 2f007d97c3..f2cd210cac 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -505,6 +505,27 @@ local function path_resolve_dot(path)
   return (is_path_absolute and '/' or '') .. table.concat(new_path_components, '/')
 end
 
+--- Expand tilde (~) character at the beginning of the path to the user's home directory.
+---
+--- @param path string Path to expand.
+--- @param sep string|nil Path separator to use. Uses os_sep by default.
+--- @return string Expanded path.
+local function expand_home(path, sep)
+  sep = sep or os_sep
+
+  if vim.startswith(path, '~') then
+    local home = uv.os_homedir() or '~' --- @type string
+
+    if home:sub(-1) == sep then
+      home = home:sub(1, -2)
+    end
+
+    path = home .. path:sub(2)
+  end
+
+  return path
+end
+
 --- @class vim.fs.normalize.Opts
 --- @inlinedoc
 ---
@@ -568,14 +589,8 @@ function M.normalize(path, opts)
     return ''
   end
 
-  -- Expand ~ to users home directory
-  if vim.startswith(path, '~') then
-    local home = uv.os_homedir() or '~'
-    if home:sub(-1) == os_sep_local then
-      home = home:sub(1, -2)
-    end
-    path = home .. path:sub(2)
-  end
+  -- Expand ~ to user's home directory
+  path = expand_home(path, os_sep_local)
 
   -- Expand environment variables if `opts.expand_env` isn't `false`
   if opts.expand_env == nil or opts.expand_env then
@@ -679,4 +694,42 @@ function M.rm(path, opts)
   end
 end
 
+--- Convert path to an absolute path. A tilde (~) character at the beginning of the path is expanded
+--- to the user's home directory. Does not check if the path exists, normalize the path, resolve
+--- symlinks or hardlinks (including `.` and `..`), or expand environment variables. If the path is
+--- already absolute, it is returned unchanged. Also converts `\` path separators to `/`.
+---
+--- @param path string Path
+--- @return string Absolute path
+function M.abspath(path)
+  vim.validate('path', path, 'string')
+
+  -- Expand ~ to user's home directory
+  path = expand_home(path)
+
+  -- Convert path separator to `/`
+  path = path:gsub(os_sep, '/')
+
+  local prefix = ''
+
+  if iswin then
+    prefix, path = split_windows_path(path)
+  end
+
+  if vim.startswith(path, '/') then
+    -- Path is already absolute, do nothing
+    return prefix .. path
+  end
+
+  -- Windows allows paths like C:foo/bar, these paths are relative to the current working directory
+  -- of the drive specified in the path
+  local cwd = (iswin and prefix:match('^%w:$')) and uv.fs_realpath(prefix) or uv.cwd()
+  assert(cwd ~= nil)
+  -- Convert cwd path separator to `/`
+  cwd = cwd:gsub(os_sep, '/')
+
+  -- Prefix is not needed for expanding relative paths, as `cwd` already contains it.
+  return M.joinpath(cwd, path)
+end
+
 return M
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 89f6ad6a0e..63444f5ba1 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -454,4 +454,38 @@ describe('vim.fs', function()
       end)
     end)
   end)
+
+  describe('abspath', function()
+    local cwd = is_os('win') and vim.uv.cwd():gsub('\\', '/') or vim.uv.cwd()
+    local home = is_os('win') and vim.uv.os_homedir():gsub('\\', '/') or vim.uv.os_homedir()
+
+    it('works', function()
+      eq(cwd .. '/foo', vim.fs.abspath('foo'))
+      eq(cwd .. '/././foo', vim.fs.abspath('././foo'))
+      eq(cwd .. '/.././../foo', vim.fs.abspath('.././../foo'))
+    end)
+
+    it('works with absolute paths', function()
+      if is_os('win') then
+        eq([[C:/foo]], vim.fs.abspath([[C:\foo]]))
+        eq([[C:/foo/../.]], vim.fs.abspath([[C:\foo\..\.]]))
+      else
+        eq('/foo/../.', vim.fs.abspath('/foo/../.'))
+        eq('/foo/bar', vim.fs.abspath('/foo/bar'))
+      end
+    end)
+
+    it('expands ~', function()
+      eq(home .. '/foo', vim.fs.abspath('~/foo'))
+      eq(home .. '/./.././foo', vim.fs.abspath('~/./.././foo'))
+    end)
+
+    if is_os('win') then
+      it('works with drive-specific cwd on Windows', function()
+        local cwd_drive = cwd:match('^%w:')
+
+        eq(cwd .. '/foo', vim.fs.abspath(cwd_drive .. 'foo'))
+      end)
+    end
+  end)
 end)
-- 
cgit 


From bc624ccffd20ccb36cbcb0165541bf38c28d724e Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sat, 28 Dec 2024 19:06:18 +0800
Subject: vim-patch:e6ccb64: runtime(doc): fix doc error in :r behaviour
 (#31755)

closes: vim/vim#16316

https://github.com/vim/vim/commit/e6ccb643a63612f20906348f16485d724f8d7f6f

Co-authored-by: Martino Ischia 
---
 runtime/doc/insert.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index 48fd442b7e..d3b822e72b 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -2026,7 +2026,7 @@ the cursor is, or below the specified line.  To insert text above the first
 line use the command ":0r {name}".
 
 After the ":read" command, the cursor is left on the first non-blank in the
-first new line.  Unless in Ex mode, then the cursor is left on the last new
+first new line.  If in Ex mode, then the cursor is left on the last new
 line (sorry, this is Vi compatible).
 
 If a file name is given with ":r", it becomes the alternate file.  This can be
-- 
cgit 


From 2b07b14eacf3197754c63f42f9c880e85960eef2 Mon Sep 17 00:00:00 2001
From: Luca Saccarola <96259932+saccarosium@users.noreply.github.com>
Date: Sat, 28 Dec 2024 12:20:50 +0100
Subject: vim-patch:9.1.0965: filetype: sh filetype set when detecting the use
 of bash (#31749)

Problem:  filetype: sh filetype set when detecting the use of bash
Solution: when bash is detected, use 'bash' filetype instead
          (Luca Saccarola)

closes: vim/vim#16309

https://github.com/vim/vim/commit/b9b762c21f2b61e0e7d8fee43d4d3dc8ecffd721
---
 runtime/lua/vim/filetype/detect.lua   |  1 +
 test/functional/lua/filetype_spec.lua |  2 +-
 test/old/testdir/test_filetype.vim    | 16 +++++++++-------
 3 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 4f2fef5b1f..0d9c1ebc2b 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -1494,6 +1494,7 @@ local function sh(path, contents, name)
       vim.b[b].is_kornshell = nil
       vim.b[b].is_sh = nil
     end
+    return M.shell(path, contents, 'bash'), on_detect
     -- Ubuntu links sh to dash
   elseif matchregex(name, [[\<\(sh\|dash\)\>]]) then
     on_detect = function(b)
diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua
index b75ff75b05..d64c024d7f 100644
--- a/test/functional/lua/filetype_spec.lua
+++ b/test/functional/lua/filetype_spec.lua
@@ -119,7 +119,7 @@ describe('vim.filetype', function()
 
   it('works with contents #22180', function()
     eq(
-      'sh',
+      'bash',
       exec_lua(function()
         -- Needs to be set so detect#sh doesn't fail
         vim.g.ft_ignore_pat = '\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$'
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 94bf208b67..d61c0d380d 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -128,6 +128,9 @@ func s:GetFilenameChecks() abort
     \ 'ave': ['file.ave'],
     \ 'awk': ['file.awk', 'file.gawk'],
     \ 'b': ['file.mch', 'file.ref', 'file.imp'],
+    \ 'bash': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases',
+    \          '.bash-aliases', '.bash_history', '.bash-history', '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw',
+    \          'PKGBUILD', 'file.bash', 'file.bats',  'file.cygport'],
     \ 'basic': ['file.bas', 'file.bi', 'file.bm'],
     \ 'bass': ['file.bass'],
     \ 'bc': ['file.bc'],
@@ -682,11 +685,10 @@ func s:GetFilenameChecks() abort
     \ 'services': ['/etc/services', 'any/etc/services'],
     \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
     \ 'sexplib': ['file.sexp'],
-    \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '.bash_history', '.bash-history',
-    \        '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh',
-    \        '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', 'file.bats', '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile',
-    \        'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport', '.env', '.envrc', 'devscripts.conf',
-    \        '.devscripts', 'file.lo', 'file.la', 'file.lai'],
+    \ 'sh': ['/usr/share/doc/bash-completion/filter.sh', '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf',
+    \        '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', 'user-dirs.defaults', 'user-dirs.dirs',
+    \        'makepkg.conf', '.makepkg.conf', 'file.mdd', '.env', '.envrc', 'devscripts.conf', '.devscripts', 'file.lo',
+    \        'file.la', 'file.lai'],
     \ 'sieve': ['file.siv', 'file.sieve'],
     \ 'sil': ['file.sil'],
     \ 'simula': ['file.sim'],
@@ -980,11 +982,11 @@ func s:GetScriptChecks() abort
       \ 'clojure': [['#!/path/clojure']],
       \ 'scala': [['#!/path/scala']],
       \ 'sh':  [['#!/path/sh'],
-      \         ['#!/path/bash'],
-      \         ['#!/path/bash2'],
       \         ['#!/path/dash'],
       \         ['#!/path/ksh'],
       \         ['#!/path/ksh93']],
+      \ 'bash': [['#!/path/bash'],
+      \          ['#!/path/bash2']],
       \ 'csh': [['#!/path/csh']],
       \ 'tcsh': [['#!/path/tcsh']],
       \ 'zsh': [['#!/path/zsh']],
-- 
cgit 


From d3951be4a0df3ca8c9a4fce5c05f82fbdaac1df6 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 29 Dec 2024 08:56:32 +0800
Subject: vim-patch:9.1.0968: tests: GetFileNameChecks() isn't fully sorted by
 filetype name (#31763)

Problem:  tests: GetFileNameChecks() isn't fully sorted by filetype name
Solution: re-sort the list

closes: vim/vim#16322

https://github.com/vim/vim/commit/e51043ad9f8e07d4c0a1e0056a8f8853e08f6a30
---
 test/old/testdir/test_filetype.vim | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index d61c0d380d..2b1ed2bbe4 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -157,6 +157,7 @@ func s:GetFilenameChecks() abort
     \ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'],
     \ 'capnp': ['file.capnp'],
     \ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'],
+    \ 'cdc': ['file.cdc'],
     \ 'cdl': ['file.cdl'],
     \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'],
     \ 'cdrtoc': ['file.toc'],
@@ -199,8 +200,8 @@ func s:GetFilenameChecks() abort
     \ 'csdl': ['file.csdl'],
     \ 'csp': ['file.csp', 'file.fdr'],
     \ 'css': ['file.css'],
-    \ 'cterm': ['file.con'],
     \ 'csv': ['file.csv'],
+    \ 'cterm': ['file.con'],
     \ 'cucumber': ['file.feature'],
     \ 'cuda': ['file.cu', 'file.cuh'],
     \ 'cue': ['file.cue'],
@@ -215,11 +216,11 @@ func s:GetFilenameChecks() abort
     \ 'dart': ['file.dart', 'file.drt'],
     \ 'datascript': ['file.ds'],
     \ 'dcd': ['file.dcd'],
+    \ 'deb822sources': ['/etc/apt/sources.list.d/file.sources', 'any/etc/apt/sources.list.d/file.sources'],
     \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'],
     \ 'debcontrol': ['/debian/control', 'any/debian/control', 'any/DEBIAN/control'],
     \ 'debcopyright': ['/debian/copyright', 'any/debian/copyright'],
     \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'],
-    \ 'deb822sources': ['/etc/apt/sources.list.d/file.sources', 'any/etc/apt/sources.list.d/file.sources'],
     \ 'def': ['file.def'],
     \ 'denyhosts': ['denyhosts.conf'],
     \ 'desc': ['file.desc'],
@@ -356,9 +357,9 @@ func s:GetFilenameChecks() abort
     \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'],
     \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'],
     \ 'html': ['file.html', 'file.htm', 'file.cshtml', 'file.component.html'],
-    \ 'http': ['file.http'],
     \ 'htmlm4': ['file.html.m4'],
     \ 'httest': ['file.htt', 'file.htb'],
+    \ 'http': ['file.http'],
     \ 'hurl': ['file.hurl'],
     \ 'hy': ['file.hy', '.hy-history'],
     \ 'hyprlang': ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf', '/hypr/foo.conf'],
@@ -389,9 +390,9 @@ func s:GetFilenameChecks() abort
     \ 'jgraph': ['file.jgr'],
     \ 'jinja': ['file.jinja'],
     \ 'jj': ['file.jjdescription'],
-    \ 'jq': ['file.jq'],
     \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
     \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'],
+    \ 'jq': ['file.jq'],
     \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'],
     \ 'json5': ['file.json5'],
     \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock'],
@@ -417,8 +418,8 @@ func s:GetFilenameChecks() abort
     \ 'ldif': ['file.ldif'],
     \ 'lean': ['file.lean'],
     \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'],
-    \ 'less': ['file.less'],
     \ 'leo': ['file.leo'],
+    \ 'less': ['file.less'],
     \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'],
     \ 'lf': ['lfrc'],
     \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'],
@@ -429,13 +430,13 @@ func s:GetFilenameChecks() abort
     \ 'lilo': ['lilo.conf', 'lilo.conf-file'],
     \ 'lilypond': ['file.ly', 'file.ily'],
     \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
-    \ 'liquidsoap': ['file.liq'],
     \ 'liquid': ['file.liquid'],
+    \ 'liquidsoap': ['file.liq'],
     \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
     \ 'lite': ['file.lite', 'file.lt'],
     \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'],
-    \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'],
     \ 'livebook': ['file.livemd'],
+    \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'],
     \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'],
     \ 'logindefs': ['/etc/login.defs', 'any/etc/login.defs'],
     \ 'logtalk': ['file.lgt'],
@@ -507,6 +508,7 @@ func s:GetFilenameChecks() abort
     \ 'mmp': ['file.mmp'],
     \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'],
     \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'],
+    \ 'mojo': ['file.mojo', 'file.🔥'],
     \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'],
     \ 'moo': ['file.moo'],
     \ 'moonscript': ['file.moon'],
@@ -515,10 +517,9 @@ func s:GetFilenameChecks() abort
     \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'],
     \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'],
     \ 'msidl': ['file.odl', 'file.mof'],
-    \ 'mss': ['file.mss'],
-    \ 'msql': ['file.msql'],
-    \ 'mojo': ['file.mojo', 'file.🔥'],
     \ 'msmtp': ['.msmtprc'],
+    \ 'msql': ['file.msql'],
+    \ 'mss': ['file.mss'],
     \ 'mupad': ['file.mu'],
     \ 'mush': ['file.mush'],
     \ 'mustache': ['file.mustache'],
@@ -611,6 +612,7 @@ func s:GetFilenameChecks() abort
     \ 'promela': ['file.pml'],
     \ 'proto': ['file.proto'],
     \ 'protocols': ['/etc/protocols', 'any/etc/protocols'],
+    \ 'prql': ['file.prql'],
     \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'],
     \ 'ps1xml': ['file.ps1xml'],
     \ 'psf': ['file.psf'],
@@ -653,19 +655,20 @@ func s:GetFilenameChecks() abort
     \ 'rnc': ['file.rnc'],
     \ 'rng': ['file.rng'],
     \ 'rnoweb': ['file.rnw', 'file.snw'],
-    \ 'rpgle': ['file.rpgle', 'file.rpgleinc'],
     \ 'robot': ['file.robot', 'file.resource'],
     \ 'robots': ['robots.txt'],
     \ 'roc': ['file.roc'],
     \ 'ron': ['file.ron'],
     \ 'routeros': ['file.rsc'],
     \ 'rpcgen': ['file.x'],
+    \ 'rpgle': ['file.rpgle', 'file.rpgleinc'],
     \ 'rpl': ['file.rpl'],
     \ 'rrst': ['file.rrst', 'file.srst'],
     \ 'rst': ['file.rst'],
     \ 'rtf': ['file.rtf'],
     \ 'ruby': ['.irbrc', 'irbrc', '.irb_history', 'irb_history', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'],
     \ 'rust': ['file.rs'],
+    \ 'sage': ['file.sage'],
     \ 'salt': ['file.sls'],
     \ 'samba': ['smb.conf'],
     \ 'sas': ['file.sas'],
@@ -695,9 +698,7 @@ func s:GetFilenameChecks() abort
     \ 'sinda': ['file.sin', 'file.s85'],
     \ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'],
     \ 'skill': ['file.il', 'file.ils', 'file.cdf'],
-    \ 'cdc': ['file.cdc'],
     \ 'slang': ['file.sl'],
-    \ 'sage': ['file.sage'],
     \ 'slice': ['file.ice'],
     \ 'slint': ['file.slint'],
     \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'],
@@ -723,7 +724,6 @@ func s:GetFilenameChecks() abort
     \ 'spyce': ['file.spy', 'file.spi'],
     \ 'sql': ['file.tyb', 'file.tyc', 'file.pkb', 'file.pks', '.sqlite_history'],
     \ 'sqlj': ['file.sqlj'],
-    \ 'prql': ['file.prql'],
     \ 'sqr': ['file.sqr', 'file.sqi'],
     \ 'squid': ['squid.conf'],
     \ 'squirrel': ['file.nut'],
@@ -789,14 +789,13 @@ func s:GetFilenameChecks() abort
     \             'any/etc/systemd/system/file.d/.#-file',
     \             'any/etc/systemd/system/file.d/file.conf'],
     \ 'systemverilog': ['file.sv', 'file.svh'],
-    \ 'trace32': ['file.cmm', 'file.t32'],
+    \ 'tablegen': ['file.td'],
     \ 'tags': ['tags'],
     \ 'tak': ['file.tak'],
     \ 'tal': ['file.tal'],
     \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'],
     \ 'taskedit': ['file.task'],
     \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc', '.tclsh-history', '.xsctcmdhistory', '.xsdbcmdhistory'],
-    \ 'tablegen': ['file.td'],
     \ 'teal': ['file.tl'],
     \ 'templ': ['file.templ'],
     \ 'template': ['file.tmpl'],
@@ -816,6 +815,7 @@ func s:GetFilenameChecks() abort
     \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'],
     \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config', '.black'],
     \ 'tpp': ['file.tpp'],
+    \ 'trace32': ['file.cmm', 'file.t32'],
     \ 'treetop': ['file.treetop'],
     \ 'trustees': ['trustees.conf'],
     \ 'tsalt': ['file.slt'],
@@ -828,12 +828,12 @@ func s:GetFilenameChecks() abort
     \ 'typescript.glimmer': ['file.gts'],
     \ 'typescriptreact': ['file.tsx'],
     \ 'typespec': ['file.tsp'],
-    \ 'ungrammar': ['file.ungram'],
     \ 'uc': ['file.uc'],
     \ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'],
     \ 'udevperm': ['/etc/udev/permissions.d/file.permissions', 'any/etc/udev/permissions.d/file.permissions'],
     \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'],
     \ 'uil': ['file.uit', 'file.uil'],
+    \ 'ungrammar': ['file.ungram'],
     \ 'unison': ['file.u', 'file.uu'],
     \ 'updatedb': ['/etc/updatedb.conf', 'any/etc/updatedb.conf'],
     \ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override', 'any/.config/upstart/file.conf', 'any/.config/upstart/file.override', 'any/.init/file.conf', 'any/.init/file.override', 'any/etc/init/file.conf', 'any/etc/init/file.override', 'any/usr/share/upstart/file.conf', 'any/usr/share/upstart/file.override'],
-- 
cgit 


From 48acbc4d645fe99532b33051006a65a57d36b981 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sun, 29 Dec 2024 16:00:47 +0900
Subject: fix(treesitter.foldexpr): refresh in the buffers affected by
 OptionSet

---
 runtime/lua/vim/treesitter/_fold.lua | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 0cb5b497c7..10ba074ab5 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -378,9 +378,13 @@ api.nvim_create_autocmd('OptionSet', {
   pattern = { 'foldminlines', 'foldnestmax' },
   desc = 'Refresh treesitter folds',
   callback = function()
-    for bufnr, _ in pairs(foldinfos) do
+    local bufs = vim.v.option_type == 'local' and { api.nvim_get_current_buf() }
+      or vim.tbl_keys(foldinfos)
+    for _, bufnr in ipairs(bufs) do
       foldinfos[bufnr] = FoldInfo.new()
-      compute_folds_levels(bufnr, foldinfos[bufnr])
+      api.nvim_buf_call(bufnr, function()
+        compute_folds_levels(bufnr, foldinfos[bufnr])
+      end)
       foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr))
     end
   end,
-- 
cgit 


From d7784225bc4add13aa5fa36be565f7db375d381e Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Sun, 29 Dec 2024 20:50:38 +0800
Subject: vim-patch:f2e08a1: runtime(doc): Fix documentation typos (#31768)

closes: vim/vim#16333

https://github.com/vim/vim/commit/f2e08a1e54e1e6f594edac5cd971ac2e03896a07

Numbers with quotes are N/A.

Co-authored-by: h-east 
---
 runtime/doc/windows.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index d3c58a28a4..d3482ff3ec 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -984,6 +984,7 @@ CTRL-W g }						*CTRL-W_g}*
 		If [N] is not given, the current buffer remains being edited.
 		See |:buffer-!| for [!].  This will also edit a buffer that is
 		not in the buffer list, without setting the 'buflisted' flag.
+		Also see |+cmd|.
 
 							*:ped* *:pedit*
 :ped[it][!] [++opt] [+cmd] {file}
-- 
cgit 


From 76abe6bab5bc6c876c3a9697ec7d8c847c20010e Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 29 Dec 2024 15:40:07 +0100
Subject: vim-patch:df67fc0: runtime(sh): set shellcheck as the compiler for
 supported shells

closes: vim/vim#16311

https://github.com/vim/vim/commit/df67fc0e6994bc92f3d82edc6269e158875defad

Co-authored-by: Luca Saccarola 
---
 runtime/ftplugin/sh.vim | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/runtime/ftplugin/sh.vim b/runtime/ftplugin/sh.vim
index 4c7695dcc6..54ae73b675 100644
--- a/runtime/ftplugin/sh.vim
+++ b/runtime/ftplugin/sh.vim
@@ -5,6 +5,7 @@
 " Contributor:		Enno Nagel 
 "			Eisuke Kawashima
 " Last Change:		2024 Sep 19 by Vim Project (compiler shellcheck)
+"			2024 Dec 29 by Vim Project (improve setting shellcheck compiler)
 
 if exists("b:did_ftplugin")
   finish
@@ -44,7 +45,11 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
   let b:undo_ftplugin ..= " | unlet! b:browsefilter"
 endif
 
-if get(b:, "is_bash", 0)
+let s:is_sh = get(b:, "is_sh", get(g:, "is_sh", 0))
+let s:is_bash = get(b:, "is_bash", get(g:, "is_bash", 0))
+let s:is_kornshell = get(b:, "is_kornshell", get(g:, "is_kornshell", 0))
+
+if s:is_bash
   if exists(':terminal') == 2
     command! -buffer -nargs=1 ShKeywordPrg silent exe ':term bash -c "help "" 2>/dev/null || man """'
   else
@@ -52,14 +57,21 @@ if get(b:, "is_bash", 0)
   endif
   setlocal keywordprg=:ShKeywordPrg
   let b:undo_ftplugin ..= " | setl kp< | sil! delc -buffer ShKeywordPrg"
+endif
 
+if (s:is_sh || s:is_bash || s:is_kornshell) && executable('shellcheck')
   if !exists('current_compiler')
     compiler shellcheck
+    let b:undo_ftplugin ..= ' | compiler make'
+  endif
+elseif s:is_bash
+  if !exists('current_compiler')
+    compiler bash
+    let b:undo_ftplugin ..= ' | compiler make'
   endif
-  let b:undo_ftplugin .= ' | compiler make'
 endif
 
 let &cpo = s:save_cpo
-unlet s:save_cpo
+unlet s:save_cpo s:is_sh s:is_bash s:is_kornshell
 
 " vim: nowrap sw=2 sts=2 ts=8 noet:
-- 
cgit 


From 493b6899ee898ec43217a8ae270b24bcc3b47281 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 29 Dec 2024 15:27:01 +0100
Subject: vim-patch:9.1.0971: filetype: SLNX files are not recognized

Problem:  filetype: SLNX files are not recognized
Solution: detect '*.slnx' files as xml filetype
          (Gustav Eikaas)

References:
https://blog.ndepend.com/slnx-the-new-net-solution-xml-file-format/
https://blog.jetbrains.com/dotnet/2024/10/04/support-for-slnx-solution-files/

closes: vim/vim#16334

https://github.com/vim/vim/commit/3b3318b64043dcf29d6f06322739f695a5cc257e

Co-authored-by: GustavEikaas 
---
 runtime/lua/vim/filetype.lua       | 1 +
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index d6a29ed84d..f267e5ef6f 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1337,6 +1337,7 @@ local extension = {
   xlb = 'xml',
   xlc = 'xml',
   xba = 'xml',
+  slnx = 'xml',
   xpm = detect_line1('XPM2', 'xpm2', 'xpm'),
   xpm2 = 'xpm2',
   xqy = 'xquery',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 2b1ed2bbe4..88dcef423a 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -886,7 +886,7 @@ func s:GetFilenameChecks() abort
     \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui',
     \         'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old',
     \         'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd', 'fonts.conf', 'file.xcu', 'file.xlb', 'file.xlc', 'file.xba', 'file.xpr',
-    \         'file.xpfm', 'file.spfm', 'file.bxml', 'file.mmi'],
+    \         'file.xpfm', 'file.spfm', 'file.bxml', 'file.mmi', 'file.slnx'],
     \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'],
     \ 'xpm': ['file.xpm'],
     \ 'xpm2': ['file.xpm2'],
-- 
cgit 


From 0e880b5612b0dece2863c70ee608971726ec5f2c Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 29 Dec 2024 15:33:40 +0100
Subject: vim-patch:9.1.0972: filetype: TI linker map files are not recognized

Problem:  filetype: TI linker map files are not recognized
Solution: detect TI linker map files as lnkmap filetype
          (Wu, Zhenyu)

References:
https://downloads.ti.com/docs/esd/SPRUI03A/Content/SPRUI03A_HTML/linker_description.html

closes: vim/vim#16324

https://github.com/vim/vim/commit/5113831d16c05f3a8b47da0c6f95a641d5fc7b2e

Co-authored-by: Wu, Zhenyu 
---
 runtime/ftplugin/lnkmap.vim        | 16 ++++++++++++++++
 runtime/lua/vim/filetype.lua       |  2 +-
 runtime/syntax/lnkmap.vim          | 35 +++++++++++++++++++++++++++++++++++
 test/old/testdir/test_filetype.vim | 19 ++++++++++++++++++-
 4 files changed, 70 insertions(+), 2 deletions(-)
 create mode 100644 runtime/ftplugin/lnkmap.vim
 create mode 100644 runtime/syntax/lnkmap.vim

diff --git a/runtime/ftplugin/lnkmap.vim b/runtime/ftplugin/lnkmap.vim
new file mode 100644
index 0000000000..46fb070e71
--- /dev/null
+++ b/runtime/ftplugin/lnkmap.vim
@@ -0,0 +1,16 @@
+" Vim filetype plugin file
+" Language:	TI Linker map
+" Document:	https://downloads.ti.com/docs/esd/SPRUI03A/Content/SPRUI03A_HTML/linker_description.html
+" Maintainer:	Wu, Zhenyu 
+" Last Change:	2024 Dec 25
+
+if exists("b:did_ftplugin")
+  finish
+endif
+
+" Don't load another plugin for this buffer
+let b:did_ftplugin = 1
+
+let b:undo_ftplugin = "setl iskeyword<"
+
+setl iskeyword+=.
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index f267e5ef6f..ef77a560d8 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -736,7 +736,7 @@ local extension = {
   mk = detect.make,
   mak = detect.make,
   page = 'mallard',
-  map = 'map',
+  map = detect_line1('^%*+$', 'lnkmap', 'map'),
   mws = 'maple',
   mpl = 'maple',
   mv = 'maple',
diff --git a/runtime/syntax/lnkmap.vim b/runtime/syntax/lnkmap.vim
new file mode 100644
index 0000000000..a1d109ef0d
--- /dev/null
+++ b/runtime/syntax/lnkmap.vim
@@ -0,0 +1,35 @@
+" Vim syntax file
+" Language:	TI Linker map
+" Document:	https://downloads.ti.com/docs/esd/SPRUI03A/Content/SPRUI03A_HTML/linker_description.html
+" Maintainer:	Wu, Zhenyu 
+" Last Change:	2024 Dec 25
+
+if exists("b:current_syntax")
+  finish
+endif
+
+syn match lnkmapTime			">> .*$"
+syn region lnkmapHeadline		start="^\*\+$" end="^\*\+$"
+syn match lnkmapHeadline		"^[A-Z][-A-Z0-9 ']*\ze\%(:\|$\)"
+syn match lnkmapSectionDelim		"^=\+$"
+syn match lnkmapTableDelim		"\%(^\|\s\)\zs---*\ze\%($\|\s\)"
+syn match lnkmapNumber			"\%(^\|\s\)\zs[0-9a-f]\+\ze\%($\|\s\)"
+syn match lnkmapSections      		'\<\.\k\+\>'
+syn match lnkmapFile			'[^ =]\+\%(\.\S\+\)\+\>'
+syn match lnkmapLibFile			'[^ =]\+\.lib\>'
+syn match lnkmapAttrib			'\<[RWIX]\+\>'
+syn match lnkmapAttrib			'\s\zs--HOLE--\ze\%\(\s\|$\)'
+syn keyword lnkmapAttrib		UNINITIALIZED
+
+
+hi def link lnkmapTime			Comment
+hi def link lnkmapHeadline		Title
+hi def link lnkmapSectionDelim		PreProc
+hi def link lnkmapTableDelim		PreProc
+hi def link lnkmapNumber		Number
+hi def link lnkmapSections		Macro
+hi def link lnkmapFile			String
+hi def link lnkmapLibFile		Special
+hi def link lnkmapAttrib		Type
+
+let b:current_syntax = "lnkmap"
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 88dcef423a..75092f8ece 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -459,7 +459,6 @@ func s:GetFilenameChecks() abort
     \ 'mallard': ['file.page'],
     "\ 'man': ['file.man'],
     \ 'manconf': ['/etc/man.conf', 'man.config', 'any/etc/man.conf'],
-    \ 'map': ['file.map'],
     \ 'maple': ['file.mv', 'file.mpl', 'file.mws'],
     \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'],
     \ 'masm': ['file.masm'],
@@ -2765,6 +2764,24 @@ func Test_make_file()
   filetype off
 endfunc
 
+func Test_map_file()
+  filetype on
+
+  " TI linker map file
+  call writefile(['******************************************************************************', '               TMS320C6x Linker Unix v7.4.24                   ', '******************************************************************************'], 'Xfile.map', 'D')
+  split Xfile.map
+  call assert_equal('lnkmap', &filetype)
+  bwipe!
+
+  " TI linker map file
+  call writefile(['MAP', 'NAME "local-demo"', 'END'], 'Xfile.map', 'D')
+  split Xfile.map
+  call assert_equal('map', &filetype)
+  bwipe!
+
+  filetype off
+endfunc
+
 func Test_org_file()
   filetype on
 
-- 
cgit 


From 02097e43c8cf33b4302717a489d322ac47fa15c2 Mon Sep 17 00:00:00 2001
From: Maria José Solano 
Date: Sun, 29 Dec 2024 07:35:57 -0800
Subject: fix(lsp): check if sig_help window is focusable when configuring
 cycle keymap

---
 runtime/lua/vim/lsp/buf.lua | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 1926a0228d..69407bc6f8 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -329,7 +329,6 @@ local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help')
 --- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts
 --- @field silent? boolean
 
--- TODO(lewis6991): support multiple clients
 --- Displays signature information about the symbol under the cursor in a
 --- floating window.
 --- @param config? vim.lsp.buf.signature_help.Opts
@@ -356,6 +355,7 @@ function M.signature_help(config)
 
     local ft = vim.bo[ctx.bufnr].filetype
     local total = #signatures
+    local can_cycle = total > 1 and config.focusable
     local idx = 0
 
     --- @param update_win? integer
@@ -371,7 +371,7 @@ function M.signature_help(config)
         return
       end
 
-      local sfx = total > 1 and string.format(' (%d/%d) ( to cycle)', idx, total) or ''
+      local sfx = can_cycle and string.format(' (%d/%d) ( to cycle)', idx, total) or ''
       local title = string.format('Signature Help: %s%s', client.name, sfx)
       if config.border then
         config.title = title
@@ -402,7 +402,7 @@ function M.signature_help(config)
 
     local fbuf, fwin = show_signature()
 
-    if total > 1 then
+    if can_cycle then
       vim.keymap.set('n', '', function()
         show_signature(fwin)
       end, {
-- 
cgit 


From e4bc8b5967d22840c1e52c97acab0f77107cd48c Mon Sep 17 00:00:00 2001
From: Igor 
Date: Sun, 29 Dec 2024 12:23:24 -0300
Subject: fix(treesitter.foldexpr): only refresh valid buffers

Problem: autocmd to refresh folds always uses the current buffer if the
option type is local. However, the current buffer may not have a parser,
and thus the assert that checks for a parser could fail.

Solution: check if the foldinfo contains the buffer, and only refresh if
so.
---
 runtime/lua/vim/treesitter/_fold.lua | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 10ba074ab5..207ac1ab67 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -378,8 +378,10 @@ api.nvim_create_autocmd('OptionSet', {
   pattern = { 'foldminlines', 'foldnestmax' },
   desc = 'Refresh treesitter folds',
   callback = function()
-    local bufs = vim.v.option_type == 'local' and { api.nvim_get_current_buf() }
-      or vim.tbl_keys(foldinfos)
+    local buf = api.nvim_get_current_buf()
+    local bufs = vim.v.option_type == 'global' and vim.tbl_keys(foldinfos)
+      or foldinfos[buf] and { buf }
+      or {}
     for _, bufnr in ipairs(bufs) do
       foldinfos[bufnr] = FoldInfo.new()
       api.nvim_buf_call(bufnr, function()
-- 
cgit 


From 7b739248a183dc56e38441a79d1630205750924d Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 30 Dec 2024 11:26:43 +0100
Subject: vim-patch:9.1.0977: filetype: msbuild filetypes are not recognized

Problem:  filetype: msbuild filetypes are not recognized
Solution: detect msbuild files as xml filetype
          (Gustav Eikaas)

closes: vim/vim#16339

https://github.com/vim/vim/commit/32b7e3a8c99d369b02154df74cbe42a37c7c7e68

Co-authored-by: GustavEikaas 
---
 runtime/lua/vim/filetype.lua       | 3 +++
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index ef77a560d8..cef3d667d3 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1881,6 +1881,9 @@ local filename = {
   ['/etc/blkid.tab'] = 'xml',
   ['/etc/blkid.tab.old'] = 'xml',
   ['fonts.conf'] = 'xml',
+  ['Directory.Packages.props'] = 'xml',
+  ['Directory.Build.props'] = 'xml',
+  ['Directory.Build.targets'] = 'xml',
   ['.clangd'] = 'yaml',
   ['.clang-format'] = 'yaml',
   ['.clang-tidy'] = 'yaml',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 75092f8ece..0e9796388c 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -885,7 +885,7 @@ func s:GetFilenameChecks() abort
     \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui',
     \         'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old',
     \         'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd', 'fonts.conf', 'file.xcu', 'file.xlb', 'file.xlc', 'file.xba', 'file.xpr',
-    \         'file.xpfm', 'file.spfm', 'file.bxml', 'file.mmi', 'file.slnx'],
+    \         'file.xpfm', 'file.spfm', 'file.bxml', 'file.mmi', 'file.slnx', 'Directory.Packages.props', 'Directory.Build.targets', 'Directory.Build.props'],
     \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'],
     \ 'xpm': ['file.xpm'],
     \ 'xpm2': ['file.xpm2'],
-- 
cgit 


From d077e31cc9dda36f2eb030146a10a308bbc542af Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 30 Dec 2024 19:06:31 +0800
Subject: vim-patch:9.1.0978: GUI tests sometimes fail when setting 'scroll'
 options (#31785)

Problem:  GUI tests sometimes fail when setting 'scroll' options
Solution: decrease the 'scroll' and 'scrolljump' option value from 20 to
          15, in case the Gui window is not large enough to handle 20.

tests: decrease the scroll and scrolljump values

the gui tests sometimes fail with:

```
From test_options_all.vim:
Found errors in Test_opt_set_scroll():
Caught exception in Test_opt_set_scroll(): Vim(set):E49: Invalid scroll size: scroll=20 @ command line..script /home/runner/work/vim/vim/src/testdir/runtest.vim[617]..function RunTheTest[57]..Test_opt_set_scroll, line 7
Found errors in Test_opt_set_scrolljump():
Caught exception in Test_opt_set_scrolljump(): Vim(set):E49: Invalid scroll size: scrolljump=20 @ command line..script /home/runner/work/vim/vim/src/testdir/runtest.vim[617]..function RunTheTest[57]..Test_opt_set_scrolljump, line 9
```

closes: vim/vim#16337

https://github.com/vim/vim/commit/2e1f757f7b52a00b77eb19648b7ea46e932eff2b

Co-authored-by: Christian Brabandt 
---
 test/old/testdir/gen_opt_test.vim | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim
index a2ad1eb8b4..3802ee3fc9 100644
--- a/test/old/testdir/gen_opt_test.vim
+++ b/test/old/testdir/gen_opt_test.vim
@@ -120,8 +120,8 @@ let test_values = {
       \ 'numberwidth': [[1, 4, 8, 10, 11, 20], [-1, 0, 21]],
       \ 'regexpengine': [[0, 1, 2], [-1, 3, 999]],
       \ 'report': [[0, 1, 2, 9999], [-1]],
-      \ 'scroll': [[0, 1, 2, 20], [-1, 999]],
-      \ 'scrolljump': [[-100, -1, 0, 1, 2, 20], [-101, 999]],
+      \ 'scroll': [[0, 1, 2, 15], [-1, 999]],
+      \ 'scrolljump': [[-100, -1, 0, 1, 2, 15], [-101, 999]],
       \ 'scrolloff': [[0, 1, 8, 999], [-1]],
       \ 'shiftwidth': [[0, 1, 8, 999], [-1]],
       \ 'sidescroll': [[0, 1, 8, 999], [-1]],
-- 
cgit 


From 259573db831755ba55276f49f963679164dcb1b0 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 30 Dec 2024 11:28:25 +0100
Subject: vim-patch:48fa319: syntax(sh): Improve the recognition of bracket
 expressions

- Define a general non-"contained" "shBracketExpr" group,
  and replace with it the "contained" bracket variant of
  "shOperator", adjusting the patterns for the competing
  conditional commands "[" and "[[".
- Accommodate some unbalanced brackets (e.g. "[!][!]").
- Make the leading "!" (or "^") stand out in NON-matching
  bracket expressions.
- Support literal newlines in parametric patterns (along
  with pathname globbings and "case" patterns).
- Also match bracket expressions in:
  * parametric patterns (e.g. "${1#[ab]_}");
  * pathname globbings (e.g. "[ab]*.txt");
  * arguments for the "[[", "echo", and "print" commands.
- Recognise collating symbols (e.g. "[.a.]") and equivalence
  classes (e.g. "[=a=]").
- Recognise end patterns for a pattern substitution form of
  parameter expansion and match bracket expressions in such
  patterns (e.g. "${1/%[.!]/;}").

fixes vim/vim#15799
closes: vim/vim#15941

References:
https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap09.html#tag_09_03_05
https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_14
https://git.savannah.gnu.org/gitweb/?p=bash.git;a=blob_plain;f=doc/bash.html;hb=37b7e91d64ad10b1a1815d12128c9475636df670
http://www.mirbsd.org/htman/i386/man1/mksh.htm

https://github.com/vim/vim/commit/48fa3198b7118023fea4b15015a97c920887eb07

Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
---
 runtime/syntax/sh.vim | 117 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 93 insertions(+), 24 deletions(-)

diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim
index 97e74d205f..0a8fb47b7d 100644
--- a/runtime/syntax/sh.vim
+++ b/runtime/syntax/sh.vim
@@ -4,6 +4,7 @@
 " Previous Maintainers:	Charles E. Campbell
 " 		Lennart Schultz 
 " Last Change:		2024 Mar 04 by Vim Project
+"		2024 Nov 03 by Aliaksei Budavei <0x000c70 AT gmail DOT com> (improved bracket expressions, #15941)
 " Version:		208
 " Former URL:		http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH
 " For options and settings, please use:      :help ft-sh-syntax
@@ -127,6 +128,50 @@ else
  com! -nargs=* ShFoldIfDoFor 
 endif
 
+" Generate bracket expression items {{{1
+" =================================
+" Note that the following function can be invoked as many times as necessary
+" provided that these constraints hold for the passed dictionary argument:
+" - every time a unique group-name value is assigned to the "itemGroup" key;
+" - only ONCE either the "extraArgs" key is not entered or it is entered and
+"   its value does not have "contained" among other optional arguments (":help
+"   :syn-arguments").
+fun! s:GenerateBracketExpressionItems(dict) abort
+ let itemGroup = a:dict.itemGroup
+ let bracketGroup = a:dict.bracketGroup
+ let invGroup = itemGroup . 'Inv'
+ let skipLeftBracketGroup = itemGroup . 'SkipLeftBracket'
+ let skipRightBracketGroup = itemGroup . 'SkipRightBracket'
+ let extraArgs = has_key(a:dict, 'extraArgs') ? a:dict.extraArgs : ''
+
+ " Make the leading "[!^]" stand out in a NON-matching expression.
+ exec 'syn match ' . invGroup . ' contained "\[\@<=[!^]"'
+
+ " Set up indirections for unbalanced-bracket highlighting.
+ exec 'syn region ' . skipRightBracketGroup . ' contained matchgroup=' . bracketGroup . ' start="\[\%([!^]\=\\\=\]\)\@=" matchgroup=shCollSymb end="\[\.[^]]\{-}\][^]]\{-}\.\]" matchgroup=' . itemGroup . ' end="\]" contains=@shBracketExprList,shDoubleQuote,' . invGroup
+ exec 'syn region ' . skipLeftBracketGroup . ' contained matchgroup=' . bracketGroup . ' start="\[\%([!^]\=\\\=\]\)\@=" skip="[!^]\=\\\=\]\%(\[[^]]\+\]\|[^]]\)\{-}\%(\[[:.=]\@!\)\@=" matchgroup=' . itemGroup . ' end="\[[:.=]\@!" contains=@shBracketExprList,shDoubleQuote,' . invGroup
+
+ " Look for a general matching expression.
+ exec 'syn region ' . itemGroup . ' matchgroup=' . bracketGroup . ' start="\[\S\@=" end="\]" contains=@shBracketExprList,shDoubleQuote ' . extraArgs
+ " Look for a general NON-matching expression.
+ exec 'syn region ' . itemGroup . ' matchgroup=' . bracketGroup . ' start="\[[!^]\@=" end="\]" contains=@shBracketExprList,shDoubleQuote,' . invGroup . ' ' . extraArgs
+
+ " Accommodate unbalanced brackets in bracket expressions.  The supported
+ " syntax for a plain "]" can be: "[]ws]" and "[^]ws]"; or, "[ws[.xs]ys.]zs]"
+ " and "[^ws[.xs]ys.]zs]"; see §9.3.5 RE Bracket Expression (in XBD).
+ exec 'syn region ' . itemGroup . ' matchgroup=NONE start="\[[!^]\=\\\=\]" matchgroup=' . bracketGroup . ' end="\]" contains=@shBracketExprList,shDoubleQuote,' . skipRightBracketGroup . ' ' . extraArgs
+ " Strive to handle "[]...[]" etc.
+ exec 'syn region ' . itemGroup . ' matchgroup=NONE start="\[[!^]\=\\\=\]\%(\[[^]]\+\]\|[^]]\)\{-}\[[:.=]\@!" matchgroup=' . bracketGroup . ' end="\]" contains=@shBracketExprList,shDoubleQuote,' . skipLeftBracketGroup . ' ' . extraArgs
+
+ if !exists("g:skip_sh_syntax_inits")
+  exec 'hi def link ' . skipLeftBracketGroup . ' ' . itemGroup
+  exec 'hi def link ' . skipRightBracketGroup . ' ' . itemGroup
+  exec 'hi def link ' . invGroup . ' Underlined'
+ endif
+endfun
+
+call s:GenerateBracketExpressionItems({'itemGroup': 'shBracketExpr', 'bracketGroup': 'shBracketExprDelim'})
+
 " sh syntax is case sensitive {{{1
 syn case match
 
@@ -136,23 +181,24 @@ syn cluster shErrorList	contains=shDoError,shIfError,shInError,shCaseError,shEsa
 if exists("b:is_kornshell") || exists("b:is_bash")
  syn cluster ErrorList add=shDTestError
 endif
-syn cluster shArithParenList	contains=shArithmetic,shArithParen,shCaseEsac,shComment,shDeref,shDo,shDerefSimple,shEcho,shEscape,shNumber,shOperator,shPosnParm,shExSingleQuote,shExDoubleQuote,shHereString,shRedir,shSingleQuote,shDoubleQuote,shStatement,shVariable,shAlias,shTest,shCtrlSeq,shSpecial,shParen,bashSpecialVariables,bashStatement,shIf,shFor,shFunctionKey,shFunctionOne,shFunctionTwo
+syn cluster shArithParenList	contains=shArithmetic,shArithParen,shCaseEsac,shComment,shDeref,shDerefVarArray,shDo,shDerefSimple,shEcho,shEscape,shExpr,shNumber,shOperator,shPosnParm,shExSingleQuote,shExDoubleQuote,shHereString,shRedir,shSingleQuote,shDoubleQuote,shStatement,shVariable,shAlias,shTest,shCtrlSeq,shSpecial,shParen,bashSpecialVariables,bashStatement,shIf,shFor,shFunctionKey,shFunctionOne,shFunctionTwo
 syn cluster shArithList	contains=@shArithParenList,shParenError
+syn cluster shBracketExprList	contains=shCharClassOther,shCharClass,shCollSymb,shEqClass
 syn cluster shCaseEsacList	contains=shCaseStart,shCaseLabel,shCase,shCaseBar,shCaseIn,shComment,shDeref,shDerefSimple,shCaseCommandSub,shCaseExSingleQuote,shCaseSingleQuote,shCaseDoubleQuote,shCtrlSeq,@shErrorList,shStringSpecial,shCaseRange
 syn cluster shCaseList	contains=@shCommandSubList,shCaseEsac,shColon,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shComment,shDblBrace,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shCtrlSeq
 if exists("b:is_kornshell") || exists("b:is_bash")
  syn cluster shCaseList	add=shForPP,shDblParen
 endif
-syn cluster shCommandSubList	contains=shAlias,shArithmetic,shCmdParenRegion,shCommandSub,shComment,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shEcho,shEscape,shExDoubleQuote,shExpr,shExSingleQuote,shHereDoc,shNumber,shOperator,shOption,shPosnParm,shHereString,shRedir,shSingleQuote,shSpecial,shStatement,shSubSh,shTest,shVariable
+syn cluster shCommandSubList	contains=shAlias,shArithmetic,shBracketExpr,shCmdParenRegion,shCommandSub,shComment,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shEcho,shEscape,shExDoubleQuote,shExpr,shExSingleQuote,shHereDoc,shNumber,shOperator,shOption,shPosnParm,shHereString,shRedir,shSingleQuote,shSpecial,shStatement,shSubSh,shTest,shVariable
 syn cluster shCurlyList	contains=shNumber,shComma,shDeref,shDerefSimple,shDerefSpecial
 " COMBAK: removing shEscape from shDblQuoteList fails ksh04:43 -- Jun 09, 2022: I don't see the problem with ksh04, so am reinstating shEscape
 syn cluster shDblQuoteList	contains=shArithmetic,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shDeref,shDerefSimple,shEscape,shPosnParm,shCtrlSeq,shSpecial,shSpecialDQ
 syn cluster shDerefList	contains=shDeref,shDerefSimple,shDerefVar,shDerefSpecial,shDerefWordError,shDerefPSR,shDerefPPS
 syn cluster shDerefVarList	contains=shDerefOffset,shDerefOp,shDerefVarArray,shDerefOpError
-syn cluster shEchoList	contains=shArithmetic,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shDeref,shDerefSimple,shEscape,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shCtrlSeq,shEchoQuote
-syn cluster shExprList1	contains=shCharClass,shNumber,shOperator,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shDblBrace,shDeref,shDerefSimple,shCtrlSeq
+syn cluster shEchoList	contains=shArithmetic,shBracketExpr,shCommandSub,shCommandSubBQ,shDerefVarArray,shSubshare,shValsub,shDeref,shDerefSimple,shEscape,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shCtrlSeq,shEchoQuote
+syn cluster shExprList1	contains=shBracketExpr,shNumber,shOperator,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shDblBrace,shDeref,shDerefSimple,shCtrlSeq
 syn cluster shExprList2	contains=@shExprList1,@shCaseList,shTest
-syn cluster shFunctionList	contains=@shCommandSubList,shCaseEsac,shColon,shComment,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shOption,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shOperator,shCtrlSeq
+syn cluster shFunctionList	contains=shBracketExpr,@shCommandSubList,shCaseEsac,shColon,shComment,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shOption,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shOperator,shCtrlSeq
 if exists("b:is_kornshell") || exists("b:is_bash")
  syn cluster shFunctionList	add=shRepeat,shDblBrace,shDblParen,shForPP
  syn cluster shDerefList	add=shCommandSubList,shEchoDeref
@@ -160,16 +206,16 @@ endif
 syn cluster shHereBeginList	contains=@shCommandSubList
 syn cluster shHereList	contains=shBeginHere,shHerePayload
 syn cluster shHereListDQ	contains=shBeginHere,@shDblQuoteList,shHerePayload
-syn cluster shIdList	contains=shArithmetic,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shWrapLineOperator,shSetOption,shComment,shDeref,shDerefSimple,shHereString,shNumber,shOperator,shRedir,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shCtrlSeq,shStringSpecial,shAtExpr
+syn cluster shIdList	contains=shArithmetic,shCommandSub,shCommandSubBQ,shDerefVarArray,shSubshare,shValsub,shWrapLineOperator,shSetOption,shComment,shDeref,shDerefSimple,shHereString,shNumber,shOperator,shRedir,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shExpr,shCtrlSeq,shStringSpecial,shAtExpr
 syn cluster shIfList	contains=@shLoopList,shDblBrace,shDblParen,shFunctionKey,shFunctionOne,shFunctionTwo
 syn cluster shLoopList	contains=@shCaseList,@shErrorList,shCaseEsac,shConditional,shDblBrace,shExpr,shFor,shIf,shOption,shSet,shTest,shTestOpr,shTouch
 if exists("b:is_kornshell") || exists("b:is_bash")
  syn cluster shLoopList	add=shForPP,shDblParen
 endif
-syn cluster shPPSLeftList	contains=shAlias,shArithmetic,shCmdParenRegion,shCommandSub,shSubshare,shValsub,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shEcho,shEscape,shExDoubleQuote,shExpr,shExSingleQuote,shHereDoc,shNumber,shOperator,shOption,shPosnParm,shHereString,shRedir,shSingleQuote,shSpecial,shStatement,shSubSh,shTest,shVariable
+syn cluster shPPSLeftList	contains=shAlias,shArithmetic,shBracketExpr,shCmdParenRegion,shCommandSub,shSubshare,shValsub,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shEcho,shEscape,shExDoubleQuote,shExpr,shExSingleQuote,shHereDoc,shNumber,shOperator,shOption,shPosnParm,shHereString,shRedir,shSingleQuote,shSpecial,shStatement,shSubSh,shTest,shVariable
 syn cluster shPPSRightList	contains=shDeref,shDerefSimple,shEscape,shPosnParm
-syn cluster shSubShList	contains=@shCommandSubList,shCommandSubBQ,shSubshare,shValsub,shCaseEsac,shColon,shCommandSub,shComment,shDo,shEcho,shExpr,shFor,shIf,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shCtrlSeq,shOperator
-syn cluster shTestList	contains=shArithmetic,shCharClass,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shSpecialDQ,shExDoubleQuote,shExpr,shExSingleQuote,shNumber,shOperator,shSingleQuote,shTest,shTestOpr
+syn cluster shSubShList	contains=shBracketExpr,@shCommandSubList,shCommandSubBQ,shSubshare,shValsub,shCaseEsac,shColon,shCommandSub,shComment,shDo,shEcho,shExpr,shFor,shIf,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shCtrlSeq,shOperator
+syn cluster shTestList	contains=shArithmetic,shBracketExpr,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shSpecialDQ,shExDoubleQuote,shExpr,shExSingleQuote,shNumber,shOperator,shSingleQuote,shTest,shTestOpr
 syn cluster shNoZSList	contains=shSpecialNoZS
 syn cluster shForList	contains=shTestOpr,shNumber,shDerefSimple,shDeref,shCommandSub,shCommandSubBQ,shSubshare,shValsub,shArithmetic
 
@@ -190,7 +236,7 @@ endif
 syn match  shEchoQuote contained	'\%(\\\\\)*\\["`'()]'
 
 " This must be after the strings, so that ... \" will be correct
-syn region shEmbeddedEcho contained matchgroup=shStatement start="\" skip="\\$" matchgroup=shEchoDelim end="$" matchgroup=NONE end="[<>;&|`)]"me=e-1 end="\d[<>]"me=e-2 end="\s#"me=e-2 contains=shNumber,shExSingleQuote,shSingleQuote,shDeref,shDerefSimple,shSpecialVar,shOperator,shExDoubleQuote,shDoubleQuote,shCharClass,shCtrlSeq
+syn region shEmbeddedEcho contained matchgroup=shStatement start="\" skip="\\$" matchgroup=shEchoDelim end="$" matchgroup=NONE end="[<>;&|`)]"me=e-1 end="\d[<>]"me=e-2 end="\s#"me=e-2 contains=shBracketExpr,shNumber,shExSingleQuote,shSingleQuote,shDeref,shDerefSimple,shSpecialVar,shOperator,shExDoubleQuote,shDoubleQuote,shCtrlSeq
 
 " Alias: {{{1
 " =====
@@ -240,7 +286,6 @@ syn match      shRedir	"\d<<-\="
 " ==========
 syn match   shOperator	"<<\|>>"		contained
 syn match   shOperator	"[!&;|]"		contained
-syn match   shOperator	"\[[[^:]\|\]]"		contained
 syn match   shOperator	"[-=/*+%]\=="		skipwhite nextgroup=shPattern
 syn match   shPattern	"\<\S\+\())\)\@="	contained contains=shExSingleQuote,shSingleQuote,shExDoubleQuote,shDoubleQuote,shDeref
 
@@ -251,9 +296,9 @@ syn region shSubSh transparent matchgroup=shSubShRegion start="[^(]\zs(" end=")"
 
 " Tests: {{{1
 "=======
-syn region shExpr	matchgroup=shRange start="\[" skip=+\\\\\|\\$\|\[+ end="\]" contains=@shTestList,shSpecial
+syn region shExpr	matchgroup=shRange start="\[\s\@=" skip=+\\\\\|\\$\|\[+ end="\]" contains=@shTestList,shSpecial
 syn region shTest	transparent matchgroup=shStatement start="\"	contained
 syn match  shVariable	"\<\h\w*\ze="			nextgroup=shVarAssign
-syn match  shVarAssign	"="		contained	nextgroup=shCmdParenRegion,shPattern,shDeref,shDerefSimple,shDoubleQuote,shExDoubleQuote,shSingleQuote,shExSingleQuote,shVar
+if exists("b:is_bash")
+ " The subscript form for array values, e.g. "foo=([2]=10 [4]=100)".
+ syn region shArrayValue	contained	start="\[\%(..\{-}\]=\)\@=" end="\]=\@="	contains=@shArrayValueList nextgroup=shVarAssign
+ syn cluster shArrayValueList	contains=shArithmetic,shArithParen,shCommandSub,shDeref,shDerefSimple,shExpr,shNumber,shExSingleQuote,shExDoubleQuote,shSingleQuote,shDoubleQuote,shSpecial,shParen,bashSpecialVariables,shParenError
+endif
+if exists("b:is_bash") || exists("b:is_kornshell")
+ syn match shVariable	"\<\h\w*\%(\[..\{-}\]\)\=\ze\%([|^&*/%+-]\|[<^]<\|[>^]>\)\=="	contains=shDerefVarArray nextgroup=shVarAssign
+ syn match shVarAssign	contained	"\%([|^&*/%+-]\|[<^]<\|[>^]>\)\=="	nextgroup=shArrayRegion,shPattern,shDeref,shDerefSimple,shDoubleQuote,shExDoubleQuote,shSingleQuote,shExSingleQuote,shVar
+ syn region shArrayRegion	contained matchgroup=shShellVariables start="(" skip='\\\\\|\\.' end=")" contains=@shArrayValueList,shArrayValue,shComment
+else
+ syn match  shVarAssign	contained	"="	nextgroup=shPattern,shDeref,shDerefSimple,shDoubleQuote,shExDoubleQuote,shSingleQuote,shExSingleQuote,shVar
+endif
 syn match  shVar	contained	"\h\w*"
 syn region shAtExpr	contained	start="@(" end=")" contains=@shIdList
 if exists("b:is_bash")
@@ -571,8 +628,11 @@ syn match  shDerefOp	contained	":\=+"	nextgroup=@shDerefPatternList
 if exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix")
  syn match  shDerefOp	contained	"#\{1,2}"		nextgroup=@shDerefPatternList
  syn match  shDerefOp	contained	"%\{1,2}"		nextgroup=@shDerefPatternList
- syn match  shDerefPattern	contained	"[^{}]\+"		contains=shDeref,shDerefSimple,shDerefPattern,shDerefString,shCommandSub,shDerefEscape nextgroup=shDerefPattern
+ syn match  shDerefPattern	contained	"[^{}]\+"		contains=shDeref,shDerefSimple,shDerefPattern,shDerefString,shCommandSub,shDerefEscape nextgroup=shDerefPattern skipnl
  syn region shDerefPattern	contained	start="{" end="}"	contains=shDeref,shDerefSimple,shDerefString,shCommandSub nextgroup=shDerefPattern
+ " Match parametric bracket expressions with a leading whitespace character.
+ syn region shDerefPattern	contained	matchgroup=shBracketExprDelim start="\[" end="\]"	contains=@shBracketExprList,shDoubleQuote nextgroup=shDerefPattern
+ call s:GenerateBracketExpressionItems({'itemGroup': 'shDerefPattern', 'bracketGroup': 'shBracketExprDelim', 'extraArgs': 'contained nextgroup=shDerefPattern'})
  syn match  shDerefEscape	contained	'\%(\\\\\)*\\.'
 endif
 if exists("b:is_bash")
@@ -592,15 +652,16 @@ if exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix")
 endif
 
 if exists("b:is_bash")
+ " bash : ${parameter/pattern/string}
  " bash : ${parameter//pattern/string}
- " bash : ${parameter//pattern}
  syn match  shDerefPPS	contained	'/\{1,2}'	nextgroup=shDerefPPSleft
  syn region shDerefPPSleft	contained	start='.'	skip=@\%(\\\\\)*\\/@ matchgroup=shDerefOp	end='/' end='\ze}' end='"'	nextgroup=shDerefPPSright	contains=@shPPSLeftList
  syn region shDerefPPSright	contained	start='.'	skip=@\%(\\\\\)\+@		end='\ze}'				contains=@shPPSRightList
 
- " bash : ${parameter/#substring/replacement}
- syn match  shDerefPSR	contained	'/#'	nextgroup=shDerefPSRleft,shDoubleQuote,shSingleQuote
- syn region shDerefPSRleft	contained	start='[^"']'	skip=@\%(\\\\\)*\\/@ matchgroup=shDerefOp	end='/' end='\ze}'	nextgroup=shDerefPSRright
+ " bash : ${parameter/#pattern/string}
+ " bash : ${parameter/%pattern/string}
+ syn match  shDerefPSR	contained	'/[#%]'	nextgroup=shDerefPSRleft,shDoubleQuote,shSingleQuote
+ syn region shDerefPSRleft	contained	start='[^"']'	skip=@\%(\\\\\)*\\/@ matchgroup=shDerefOp	end='/' end='\ze}'	nextgroup=shDerefPSRright	contains=shBracketExpr
  syn region shDerefPSRright	contained	start='.'	skip=@\%(\\\\\)\+@		end='\ze}'
 endif
 
@@ -670,6 +731,7 @@ syn sync match shWhileSync	grouphere	shRepeat	"\"
 " =====================
 if !exists("skip_sh_syntax_inits")
  hi def link shArithRegion	shShellVariables
+ hi def link shArrayValue	shDeref
  hi def link shAstQuote	shDoubleQuote
  hi def link shAtExpr	shSetList
  hi def link shBkslshSnglQuote	shSingleQuote
@@ -764,7 +826,10 @@ if !exists("skip_sh_syntax_inits")
  endif
 
  hi def link shArithmetic		Special
+ hi def link shBracketExprDelim		Delimiter
  hi def link shCharClass		Identifier
+ hi def link shCollSymb		shCharClass
+ hi def link shEqClass		shCharClass
  hi def link shSnglCase		Statement
  hi def link shCommandSub		Special
  hi def link shCommandSubBQ		shCommandSub
@@ -814,6 +879,10 @@ delc ShFoldFunctions
 delc ShFoldHereDoc
 delc ShFoldIfDoFor
 
+" Delete the bracket expression function {{{1
+" ======================================
+delfun s:GenerateBracketExpressionItems
+
 " Set Current Syntax: {{{1
 " ===================
 if exists("b:is_bash")
-- 
cgit 


From e9c077d197a80a2ecd858821b18d0be3e3eb6d0b Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Mon, 30 Dec 2024 16:36:47 -0600
Subject: fix(termkey): fix null pointer dereference (#31792)

---
 src/nvim/tui/termkey/driver-csi.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/nvim/tui/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c
index 52349b0abd..ac2459beb2 100644
--- a/src/nvim/tui/termkey/driver-csi.c
+++ b/src/nvim/tui/termkey/driver-csi.c
@@ -528,7 +528,7 @@ TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, int *paramp, in
     if (c == ':') {
       if (length == 0) {
         *paramp = arg;
-      } else {
+      } else if (subparams != NULL) {
         subparams[length - 1] = arg;
       }
 
@@ -543,7 +543,7 @@ TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, int *paramp, in
 
   if (length == 0) {
     *paramp = arg;
-  } else {
+  } else if (subparams != NULL) {
     subparams[length - 1] = arg;
   }
 
-- 
cgit 


From 57f10abbc2c17453ffe584833abb7596e9897a6e Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 31 Dec 2024 08:41:54 +0800
Subject: vim-patch:9.1.0981: tests: typo in test_filetype.vim (#31794)

Problem:  tests: typo in test_filetype.vim
Solution: fix comment, update lnkmap syntax file and add
          DESCT keyword
          (Wu, Zhenyu)

closes: vim/vim#16348

https://github.com/vim/vim/commit/2bee7e43e137bcef62e6872791e2be7ce9c32703

Co-authored-by: Wu, Zhenyu 
---
 runtime/syntax/lnkmap.vim          | 4 ++--
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/runtime/syntax/lnkmap.vim b/runtime/syntax/lnkmap.vim
index a1d109ef0d..dc097f412f 100644
--- a/runtime/syntax/lnkmap.vim
+++ b/runtime/syntax/lnkmap.vim
@@ -2,7 +2,7 @@
 " Language:	TI Linker map
 " Document:	https://downloads.ti.com/docs/esd/SPRUI03A/Content/SPRUI03A_HTML/linker_description.html
 " Maintainer:	Wu, Zhenyu 
-" Last Change:	2024 Dec 25
+" Last Change:	2024 Dec 30
 
 if exists("b:current_syntax")
   finish
@@ -19,7 +19,7 @@ syn match lnkmapFile			'[^ =]\+\%(\.\S\+\)\+\>'
 syn match lnkmapLibFile			'[^ =]\+\.lib\>'
 syn match lnkmapAttrib			'\<[RWIX]\+\>'
 syn match lnkmapAttrib			'\s\zs--HOLE--\ze\%\(\s\|$\)'
-syn keyword lnkmapAttrib		UNINITIALIZED
+syn keyword lnkmapAttrib		UNINITIALIZED DESCT
 
 
 hi def link lnkmapTime			Comment
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 0e9796388c..462bf2e025 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -2773,7 +2773,7 @@ func Test_map_file()
   call assert_equal('lnkmap', &filetype)
   bwipe!
 
-  " TI linker map file
+  " UMN mapserver config file
   call writefile(['MAP', 'NAME "local-demo"', 'END'], 'Xfile.map', 'D')
   split Xfile.map
   call assert_equal('map', &filetype)
-- 
cgit 


From bdc0b5f5054afb8ba3418f9d6c9b1b3e944f3e3a Mon Sep 17 00:00:00 2001
From: glepnir 
Date: Tue, 31 Dec 2024 19:12:50 +0800
Subject: vim-patch:9.1.0983: not able to get the displayed items in
 complete_i… (#31796)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

vim-patch:9.1.0983: not able to get the displayed items in complete_info()

Problem:  not able to get the displayed items in complete_info()
          (Evgeni Chasnovski)
Solution: return the visible items via the "matches" key for
          complete_info() (glepnir)

fixes: vim/vim#10007
closes: vim/vim#16307

https://github.com/vim/vim/commit/d4088edae21659e14ab5f763c820f4eab9d36981
---
 runtime/doc/builtin.txt                |  9 +++++--
 runtime/doc/insert.txt                 |  1 +
 runtime/lua/vim/_meta/vimfn.lua        |  9 +++++--
 src/nvim/eval.lua                      |  9 +++++--
 src/nvim/insexpand.c                   | 23 ++++++++++++++----
 test/old/testdir/test_ins_complete.vim | 44 ++++++++++++++++++++++++++++++++++
 6 files changed, 84 insertions(+), 11 deletions(-)

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 0e9c1c8512..f321c880a4 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1174,10 +1174,15 @@ complete_info([{what}])                                        *complete_info()*
 				See |complete_info_mode| for the values.
 		   pum_visible	|TRUE| if popup menu is visible.
 				See |pumvisible()|.
-		   items	List of completion matches.  Each item is a
-				dictionary containing the entries "word",
+		   items	List of all completion candidates.  Each item
+				is a dictionary containing the entries "word",
 				"abbr", "menu", "kind", "info" and "user_data".
 				See |complete-items|.
+		   matches	Same as "items", but only returns items that
+				are matching current query. If both "matches"
+				and "items" are in "what", the returned list
+				will still be named "items", but each item
+				will have an additional "match" field.
 		   selected	Selected item index.  First index is zero.
 				Index is -1 if no item is selected (showing
 				typed text only, or the last completion after
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index d3b822e72b..bc0e9ba314 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -1189,6 +1189,7 @@ items:
 			|hl-PmenuKind| highlight group, allowing for the
 			customization of ctermfg and guifg properties for the
 			completion kind
+	match		See "matches" in |complete_info()|.
 
 All of these except "icase", "equal", "dup" and "empty" must be a string.  If
 an item does not meet these requirements then an error message is given and
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index b580357c85..6662fca84f 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -1023,10 +1023,15 @@ function vim.fn.complete_check() end
 ---     See |complete_info_mode| for the values.
 ---    pum_visible  |TRUE| if popup menu is visible.
 ---     See |pumvisible()|.
----    items  List of completion matches.  Each item is a
----     dictionary containing the entries "word",
+---    items  List of all completion candidates.  Each item
+---     is a dictionary containing the entries "word",
 ---     "abbr", "menu", "kind", "info" and "user_data".
 ---     See |complete-items|.
+---    matches  Same as "items", but only returns items that
+---     are matching current query. If both "matches"
+---     and "items" are in "what", the returned list
+---     will still be named "items", but each item
+---     will have an additional "match" field.
 ---    selected  Selected item index.  First index is zero.
 ---     Index is -1 if no item is selected (showing
 ---     typed text only, or the last completion after
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 72dabd53e9..c650dee306 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -1384,10 +1384,15 @@ M.funcs = {
       		See |complete_info_mode| for the values.
          pum_visible	|TRUE| if popup menu is visible.
       		See |pumvisible()|.
-         items	List of completion matches.  Each item is a
-      		dictionary containing the entries "word",
+         items	List of all completion candidates.  Each item
+      		is a dictionary containing the entries "word",
       		"abbr", "menu", "kind", "info" and "user_data".
       		See |complete-items|.
+         matches	Same as "items", but only returns items that
+      		are matching current query. If both "matches"
+      		and "items" are in "what", the returned list
+      		will still be named "items", but each item
+      		will have an additional "match" field.
          selected	Selected item index.  First index is zero.
       		Index is -1 if no item is selected (showing
       		typed text only, or the last completion after
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 7245b0d6ce..9b77644085 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -164,6 +164,7 @@ struct compl_S {
   int cp_flags;                  ///< CP_ values
   int cp_number;                 ///< sequence number
   int cp_score;                  ///< fuzzy match score
+  bool cp_in_match_array;        ///< collected by compl_match_array
   int cp_user_abbr_hlattr;       ///< highlight attribute for abbr
   int cp_user_kind_hlattr;       ///< highlight attribute for kind
 };
@@ -1223,6 +1224,7 @@ static int ins_compl_build_pum(void)
   int cur = -1;
 
   do {
+    comp->cp_in_match_array = false;
     // When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
     // set the cp_score for later comparisons.
     if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) {
@@ -1234,6 +1236,7 @@ static int ins_compl_build_pum(void)
             || ins_compl_equal(comp, compl_leader, (size_t)lead_len)
             || (compl_fuzzy_match && comp->cp_score > 0))) {
       compl_match_arraysize++;
+      comp->cp_in_match_array = true;
       if (match_head == NULL) {
         match_head = comp;
       } else {
@@ -2875,11 +2878,12 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
 #define CI_WHAT_ITEMS           0x04
 #define CI_WHAT_SELECTED        0x08
 #define CI_WHAT_INSERTED        0x10
+#define CI_WHAT_MATCHES         0x20
 #define CI_WHAT_ALL             0xff
   int what_flag;
 
   if (what_list == NULL) {
-    what_flag = CI_WHAT_ALL;
+    what_flag = CI_WHAT_ALL & ~CI_WHAT_MATCHES;
   } else {
     what_flag = 0;
     for (listitem_T *item = tv_list_first(what_list)
@@ -2897,6 +2901,8 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
         what_flag |= CI_WHAT_SELECTED;
       } else if (strcmp(what, "inserted") == 0) {
         what_flag |= CI_WHAT_INSERTED;
+      } else if (strcmp(what, "matches") == 0) {
+        what_flag |= CI_WHAT_MATCHES;
       }
     }
   }
@@ -2910,12 +2916,16 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
     ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible());
   }
 
-  if (ret == OK && (what_flag & CI_WHAT_ITEMS || what_flag & CI_WHAT_SELECTED)) {
+  if (ret == OK && (what_flag & CI_WHAT_ITEMS || what_flag & CI_WHAT_SELECTED
+                    || what_flag & CI_WHAT_MATCHES)) {
     list_T *li = NULL;
     int selected_idx = -1;
-    if (what_flag & CI_WHAT_ITEMS) {
+    bool has_items = what_flag & CI_WHAT_ITEMS;
+    bool has_matches = what_flag & CI_WHAT_MATCHES;
+    if (has_items || has_matches) {
       li = tv_list_alloc(kListLenMayKnow);
-      ret = tv_dict_add_list(retdict, S_LEN("items"), li);
+      const char *key = (has_matches && !has_items) ? "matches" : "items";
+      ret = tv_dict_add_list(retdict, key, strlen(key), li);
     }
     if (ret == OK && what_flag & CI_WHAT_SELECTED) {
       if (compl_curr_match != NULL && compl_curr_match->cp_number == -1) {
@@ -2927,7 +2937,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
       compl_T *match = compl_first_match;
       do {
         if (!match_at_original_text(match)) {
-          if (what_flag & CI_WHAT_ITEMS) {
+          if (has_items || (has_matches && match->cp_in_match_array)) {
             dict_T *di = tv_dict_alloc();
             tv_list_append_dict(li, di);
             tv_dict_add_str(di, S_LEN("word"), match->cp_str);
@@ -2935,6 +2945,9 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
             tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]);
             tv_dict_add_str(di, S_LEN("kind"), match->cp_text[CPT_KIND]);
             tv_dict_add_str(di, S_LEN("info"), match->cp_text[CPT_INFO]);
+            if (has_matches && has_items) {
+              tv_dict_add_bool(di, S_LEN("match"), match->cp_in_match_array);
+            }
             if (match->cp_user_data.v_type == VAR_UNKNOWN) {
               // Add an empty string for backwards compatibility
               tv_dict_add_str(di, S_LEN("user_data"), "");
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
index bf7477f088..52fd24881c 100644
--- a/test/old/testdir/test_ins_complete.vim
+++ b/test/old/testdir/test_ins_complete.vim
@@ -2870,4 +2870,48 @@ func Test_complete_fuzzy_match_tie()
   set completeopt&
 endfunc
 
+func Test_complete_info_matches()
+  let g:what = ['matches']
+  func ShownInfo()
+    let g:compl_info = complete_info(g:what)
+    return ''
+  endfunc
+  set completeopt+=noinsert
+
+  new
+  call setline(1, ['aaa', 'aab', 'aba', 'abb'])
+  inoremap  =ShownInfo()
+
+  call feedkeys("Go\\\\dd", 'tx')
+  call assert_equal([
+    \ {'word': 'aaa', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'aab', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'aba', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'abb', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+    \], g:compl_info['matches'])
+
+  call feedkeys("Goa\\b\\dd", 'tx')
+  call assert_equal([
+    \ {'word': 'aba', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'abb', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+    \], g:compl_info['matches'])
+
+  " items and matches both in what
+  let g:what = ['items', 'matches']
+  call feedkeys("Goa\\b\\dd", 'tx')
+  call assert_equal([
+    \ {'word': 'aaa', 'menu': '', 'user_data': '', 'match': v:false, 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'aab', 'menu': '', 'user_data': '', 'match': v:false, 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'aba', 'menu': '', 'user_data': '', 'match': v:true, 'info': '', 'kind': '', 'abbr': ''},
+    \ {'word': 'abb', 'menu': '', 'user_data': '', 'match': v:true, 'info': '', 'kind': '', 'abbr': ''},
+    \], g:compl_info['items'])
+  call assert_false(has_key(g:compl_info, 'matches'))
+
+  bw!
+  bw!
+  unlet g:what
+  delfunc ShownInfo
+  set cot&
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab nofoldenable
-- 
cgit 


From 7ecd348b3da3bd27a0dd8db07896bf17a86bb26c Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Tue, 31 Dec 2024 11:26:48 +0100
Subject: vim-patch:8dc98bf: runtime(chordpro): update syntax script

References:
https://chordpro.org/beta/directives-define/#defining-chords-for-keyboard-instruments

https://github.com/vim/vim/commit/8dc98bf427d62501d9ecbb48d20ae2633c3db187

Co-authored-by: nibo 
---
 runtime/syntax/chordpro.vim | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/syntax/chordpro.vim b/runtime/syntax/chordpro.vim
index 41a0a1e9d1..02c34b8466 100644
--- a/runtime/syntax/chordpro.vim
+++ b/runtime/syntax/chordpro.vim
@@ -2,6 +2,7 @@
 " Language:     ChordPro 6 (https://www.chordpro.org)
 " Maintainer:   Niels Bo Andersen 
 " Last Change:  2022-04-15
+" 2024 Dec 31:  add "keys" as syntax keyword (via: https://groups.google.com/g/vim_dev/c/vP4epus0euM/m/mNoDY6hsCQAJ)
 
 " Quit when a syntax file was already loaded
 if exists("b:current_syntax")
@@ -104,7 +105,7 @@ syn match chordproStandardMetadata /instrument\.description/ contained
 syn match chordproStandardMetadata /user\.name/ contained
 syn match chordproStandardMetadata /user\.fullname/ contained
 
-syn keyword chordproDefineKeyword contained frets fingers
+syn keyword chordproDefineKeyword contained frets fingers keys
 syn match chordproDefineKeyword /base-fret/ contained
 
 syn match chordproArgumentsNumber /\d\+/ contained
-- 
cgit 


From 1877cd5fcdc0bbe5f3d0685d42d4e295fb819724 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Tue, 31 Dec 2024 11:53:02 +0100
Subject: vim-patch:9.1.0982: TI linker files are not recognized

Problem:  TI linker files are not recognized
Solution: inspect '*.cmd' files and detect TI linker files
          as 'lnk' filetype, include a lnk ftplugin and syntax
          script (Wu, Zhenyu)

closes: vim/vim#16320

https://github.com/vim/vim/commit/39a4eb0b2ca901b59800fad086550053556e59dc

Co-authored-by: Wu, Zhenyu 
---
 runtime/ftplugin/lnk.vim            | 14 +++++++
 runtime/lua/vim/filetype.lua        |  2 +-
 runtime/lua/vim/filetype/detect.lua | 18 +++++++++
 runtime/syntax/cmacro.vim           | 77 +++++++++++++++++++++++++++++++++++++
 runtime/syntax/lnk.vim              | 40 +++++++++++++++++++
 test/old/testdir/test_filetype.vim  | 21 ++++++++++
 6 files changed, 171 insertions(+), 1 deletion(-)
 create mode 100644 runtime/ftplugin/lnk.vim
 create mode 100644 runtime/syntax/cmacro.vim
 create mode 100644 runtime/syntax/lnk.vim

diff --git a/runtime/ftplugin/lnk.vim b/runtime/ftplugin/lnk.vim
new file mode 100644
index 0000000000..8b280d9836
--- /dev/null
+++ b/runtime/ftplugin/lnk.vim
@@ -0,0 +1,14 @@
+" Vim filetype plugin file
+" Language:	TI linker command file
+" Document:	https://software-dl.ti.com/ccs/esd/documents/sdto_cgt_Linker-Command-File-Primer.html
+" Maintainer:	Wu, Zhenyu 
+" Last Change:	2024 Dec 31
+
+if exists("b:did_ftplugin") | finish | endif
+let b:did_ftplugin = 1
+
+setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,://
+setlocal commentstring=/*\ %s\ */
+setlocal iskeyword+=.
+
+let b:undo_ftplugin = "setl commentstring< comments< iskeyword<"
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index cef3d667d3..173de8b5d5 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1393,7 +1393,7 @@ local extension = {
   txt = detect.txt,
   xml = detect.xml,
   y = detect.y,
-  cmd = detect_line1('^/%*', 'rexx', 'dosbatch'),
+  cmd = detect.cmd,
   rul = detect.rul,
   cpy = detect_line1('^##', 'python', 'cobol'),
   dsl = detect_line1('^%s*
+" Last Change:	2024 Dec 31
+" modified from syntax/c.vim
+
+" C compiler has a preprocessor: `cpp -P test.txt`
+" test.txt doesn't need to be a C file
+if exists("b:current_syntax")
+  finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Accept %: for # (C99)
+syn region	cmacroPreCondit	start="^\s*\zs\%(%:\|#\)\s*\%(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" keepend contains=cmacroCppParen,cmacroNumbers
+syn match	cmacroPreConditMatch	display "^\s*\zs\%(%:\|#\)\s*\%(else\|endif\)\>"
+if !exists("c_no_if0")
+  syn cluster	cmacroCppOutInGroup	contains=cmacroCppInIf,cmacroCppInElse,cmacroCppInElse2,cmacroCppOutIf,cmacroCppOutIf2,cmacroCppOutElse,cmacroCppInSkip,cmacroCppOutSkip
+  syn region	cmacroCppOutWrapper	start="^\s*\zs\%(%:\|#\)\s*if\s\+0\+\s*\%($\|//\|/\*\|&\)" end=".\@=\|$" contains=cmacroCppOutIf,cmacroCppOutElse,@NoSpell fold
+  syn region	cmacroCppOutIf	contained start="0\+" matchgroup=cmacroCppOutWrapper end="^\s*\%(%:\|#\)\s*endif\>" contains=cmacroCppOutIf2,cmacroCppOutElse
+  if !exists("c_no_if0_fold")
+    syn region	cmacroCppOutIf2	contained matchgroup=cmacroCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cmacroCppOutSkip,@Spell fold
+  else
+    syn region	cmacroCppOutIf2	contained matchgroup=cmacroCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cmacroCppOutSkip,@Spell
+  endif
+  syn region	cmacroCppOutElse	contained matchgroup=cmacroCppOutWrapper start="^\s*\%(%:\|#\)\s*\%(else\|elif\)" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cmacroPreCondit
+  syn region	cmacroCppInWrapper	start="^\s*\zs\%(%:\|#\)\s*if\s\+0*[1-9]\d*\s*\%($\|//\|/\*\||\)" end=".\@=\|$" contains=cmacroCppInIf,cmacroCppInElse fold
+  syn region	cmacroCppInIf	contained matchgroup=cmacroCppInWrapper start="\d\+" end="^\s*\%(%:\|#\)\s*endif\>" contains=TOP,cmacroPreCondit
+  if !exists("c_no_if0_fold")
+    syn region	cmacroCppInElse	contained start="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cmacroCppInIf contains=cmacroCppInElse2 fold
+  else
+    syn region	cmacroCppInElse	contained start="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cmacroCppInIf contains=cmacroCppInElse2
+  endif
+  syn region	cmacroCppInElse2	contained matchgroup=cmacroCppInWrapper start="^\s*\%(%:\|#\)\s*\%(else\|elif\)\%([^/]\|/[^/*]\)*" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=cmacroCppOutSkip,@Spell
+  syn region	cmacroCppOutSkip	contained start="^\s*\%(%:\|#\)\s*\%(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\%(%:\|#\)\s*endif\>" contains=cmacroCppOutSkip
+  syn region	cmacroCppInSkip	contained matchgroup=cmacroCppInWrapper start="^\s*\%(%:\|#\)\s*\%(if\s\+\%(\d\+\s*\%($\|//\|/\*\||\|&\)\)\@!\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\%(%:\|#\)\s*endif\>" containedin=cmacroCppOutElse,cmacroCppInIf,cmacroCppInSkip contains=TOP,cmacroPreProc
+endif
+syn region	cmacroIncluded	display contained start=+"+ skip=+\\\\\|\\"+ end=+"+
+syn match	cmacroIncluded	display contained "<[^>]*>"
+syn match	cmacroInclude	display "^\s*\zs\%(%:\|#\)\s*include\>\s*["<]" contains=cmacroIncluded
+"syn match cmacroLineSkip	"\\$"
+syn cluster	cmacroPreProcmacroGroup	contains=cmacroPreCondit,cmacroIncluded,cmacroInclude,cmacroDefine,cmacroCppOutWrapper,cmacroCppInWrapper,@cmacroCppOutInGroup,cmacroNumbersCom,@cmacroCommentGroup,cmacroParen,cmacroBracket,cmacroMulti,cmacroBadBlock
+syn region	cmacroDefine		start="^\s*\zs\%(%:\|#\)\s*\%(define\|undef\)\>" skip="\\$" end="$" keepend contains=ALLBUT,@cmacroPreProcmacroGroup,@Spell
+syn region	cmacroPreProc	start="^\s*\zs\%(%:\|#\)\s*\%(pragma\>\|line\>\|warning\>\|warn\>\|error\>\)" skip="\\$" end="$" keepend contains=ALLBUT,@cmacroPreProcmacroGroup,@Spell
+
+" be able to fold #pragma regions
+syn region	cmacroPragma		start="^\s*#pragma\s\+region\>" end="^\s*#pragma\s\+endregion\>" transparent keepend extend fold
+
+syn keyword cmacroTodo			contained TODO FIXME XXX NOTE
+syn region  cmacroComment		start='/\*' end='\*/' contains=cmacroTodo,@Spell
+syn match   cmacroCommentError "\*/"
+syn region  cmacroComment		start='//' end='$' contains=cmacroTodo,@Spell
+
+" Define the default highlighting.
+" Only used when an item doesn't have highlighting yet
+hi def link cmacroInclude		Include
+hi def link cmacroPreProc		PreProc
+hi def link cmacroDefine		Macro
+hi def link cmacroIncluded		cmacroString
+hi def link cmacroCppInWrapper	cmacroCppOutWrapper
+hi def link cmacroCppOutWrapper	cmacroPreCondit
+hi def link cmacroPreConditMatch	cmacroPreCondit
+hi def link cmacroPreCondit		PreCondit
+hi def link cmacroCppOutSkip		cmacroCppOutIf2
+hi def link cmacroCppInElse2		cmacroCppOutIf2
+hi def link cmacroCppOutIf2		cmacroCppOut
+hi def link cmacroCppOut		Comment
+hi def link cmacroTodo			Todo
+hi def link cmacroComment		Comment
+hi def link cmacroCommentError		Error
+
+let b:current_syntax = "cmacro"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/syntax/lnk.vim b/runtime/syntax/lnk.vim
new file mode 100644
index 0000000000..45ca382aca
--- /dev/null
+++ b/runtime/syntax/lnk.vim
@@ -0,0 +1,40 @@
+" Vim syntax file
+" Language:	TI linker command file
+" Document:	https://downloads.ti.com/docs/esd/SPRUI03A/Content/SPRUI03A_HTML/linker_description.html
+" Document:	https://software-dl.ti.com/ccs/esd/documents/sdto_cgt_Linker-Command-File-Primer.html
+" Maintainer:	Wu, Zhenyu 
+" Last Change:	2024 Dec 31
+
+if exists("b:current_syntax")
+  finish
+endif
+
+runtime! syntax/cmacro.vim
+
+syn case ignore
+syn match lnkNumber		"0x[0-9a-f]\+"
+" Linker command files are ASCII files that contain one or more of the following:
+" Input filenames, which specify object files, archive libraries, or other command files.
+" Linker options, which can be used in the command file in the same manner that they are used on the command line
+syn match   lnkOption		"^[-+][-_a-zA-Z#@]\+"
+syn match   lnkOption		"^--[^ \t$=`'"|);]\+"
+syn match   lnkFile		'[^ =]\+\%(\.\S\+\)\+\>'
+syn match   lnkLibFile		'[^ =]\+\.lib\>'
+" The MEMORY and SECTIONS linker directives. The MEMORY directive defines the target memory configuration (see Section 8.5.4). The SECTIONS directive controls how sections are built and allocated (see Section 8.5.5.)
+syn keyword lnkKeyword	ADDRESS_MASK f LOAD ORIGIN START ALGORITHM FILL LOAD_END PAGE TABLE ALIGN GROUP LOAD_SIZE PALIGN TYPE ATTR HAMMING_MASK LOAD_START PARITY_MASK UNION BLOCK HIGH MEMORY RUN UNORDERED COMPRESSION INPUT_PAGE MIRRORING RUN_END VFILL COPY INPUT_RANGE NOINIT RUN_SIZE DSECT l NOLOAD RUN_START ECC LEN o SECTIONS END LENGTH ORG SIZE
+syn region  lnkLibrary		start=+<+ end=+>+
+syn match   lnkAttrib		'\<[RWXI]\+\>'
+syn match   lnkSections      	'\<\.\k\+'
+" Assignment statements, which define and assign values to global symbols
+syn case match
+
+hi def link lnkNumber		Number
+hi def link lnkOption		Special
+hi def link lnkKeyword		Keyword
+hi def link lnkLibrary		String
+hi def link lnkFile		String
+hi def link lnkLibFile		Special
+hi def link lnkAttrib		Type
+hi def link lnkSections		Macro
+
+let b:current_syntax = "lnk"
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 462bf2e025..1bf2bdb360 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -2300,6 +2300,27 @@ func Test_cls_file()
   filetype off
 endfunc
 
+func Test_cmd_file()
+  filetype on
+
+  call writefile(['--rom_model'], 'Xfile.cmd')
+  split Xfile.cmd
+  call assert_equal('lnk', &filetype)
+  bwipe!
+
+  call writefile(['/* comment */'], 'Xfile.cmd')
+  split Xfile.cmd
+  call assert_equal('rexx', &filetype)
+  bwipe!
+
+  call writefile(['REM comment'], 'Xfile.cmd')
+  split Xfile.cmd
+  call assert_equal('dosbatch', &filetype)
+  bwipe!
+
+  filetype off
+endfunc
+
 func Test_sig_file()
   filetype on
 
-- 
cgit 


From e00cd1ab4060915d86b8536b082e663818268b69 Mon Sep 17 00:00:00 2001
From: Mathias Fussenegger 
Date: Sun, 29 Dec 2024 13:44:42 +0100
Subject: feat(lsp): return resolved config for vim.lsp.config[name]

Allows to retrieve the configuration as it will be used by `lsp.enable`
- including the parts merged from `*` and rtp.

This is useful for explicit startup control
(`vim.lsp.start(vim.lsp.config[name])`)

Closes https://github.com/neovim/neovim/issues/31640
---
 runtime/lua/vim/lsp.lua             | 71 ++++++++++++++++---------------------
 runtime/lua/vim/lsp/health.lua      |  2 +-
 test/functional/plugin/lsp_spec.lua |  2 +-
 3 files changed, 32 insertions(+), 43 deletions(-)

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 1c8356d64d..5b92926a21 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -420,9 +420,33 @@ lsp.config = setmetatable({ _configs = {} }, {
   --- @return vim.lsp.Config
   __index = function(self, name)
     validate('name', name, 'string')
-    invalidate_enabled_config(name)
+
+    local rconfig = lsp._enabled_configs[name] or {}
     self._configs[name] = self._configs[name] or {}
-    return self._configs[name]
+
+    if not rconfig.resolved_config then
+      -- Resolve configs from lsp/*.lua
+      -- Calls to vim.lsp.config in lsp/* have a lower precedence than calls from other sites.
+      local rtp_config = {} ---@type vim.lsp.Config
+      for _, v in ipairs(api.nvim_get_runtime_file(('lsp/%s.lua'):format(name), true)) do
+        local config = assert(loadfile(v))() ---@type any?
+        if type(config) == 'table' then
+          rtp_config = vim.tbl_deep_extend('force', rtp_config, config)
+        else
+          log.warn(string.format('%s does not return a table, ignoring', v))
+        end
+      end
+
+      rconfig.resolved_config = vim.tbl_deep_extend(
+        'force',
+        lsp.config._configs['*'] or {},
+        rtp_config,
+        lsp.config._configs[name] or {}
+      )
+      rconfig.resolved_config.name = name
+    end
+
+    return rconfig.resolved_config
   end,
 
   --- @param self vim.lsp.config
@@ -446,44 +470,6 @@ lsp.config = setmetatable({ _configs = {} }, {
   end,
 })
 
---- @private
---- @param name string
---- @return vim.lsp.Config
-function lsp._resolve_config(name)
-  local econfig = lsp._enabled_configs[name] or {}
-
-  if not econfig.resolved_config then
-    -- Resolve configs from lsp/*.lua
-    -- Calls to vim.lsp.config in lsp/* have a lower precedence than calls from other sites.
-    local rtp_config = {} ---@type vim.lsp.Config
-    for _, v in ipairs(api.nvim_get_runtime_file(('lsp/%s.lua'):format(name), true)) do
-      local config = assert(loadfile(v))() ---@type any?
-      if type(config) == 'table' then
-        rtp_config = vim.tbl_deep_extend('force', rtp_config, config)
-      else
-        log.warn(string.format('%s does not return a table, ignoring', v))
-      end
-    end
-
-    local config = vim.tbl_deep_extend(
-      'force',
-      lsp.config._configs['*'] or {},
-      rtp_config,
-      lsp.config._configs[name] or {}
-    )
-
-    config.name = name
-
-    validate('cmd', config.cmd, { 'function', 'table' })
-    validate('cmd', config.reuse_client, 'function', true)
-    -- All other fields are validated in client.create
-
-    econfig.resolved_config = config
-  end
-
-  return assert(econfig.resolved_config)
-end
-
 local lsp_enable_autocmd_id --- @type integer?
 
 --- @param bufnr integer
@@ -514,7 +500,9 @@ local function lsp_enable_callback(bufnr)
   end
 
   for name in vim.spairs(lsp._enabled_configs) do
-    local config = lsp._resolve_config(name)
+    local config = lsp.config[name]
+    validate('cmd', config.cmd, { 'function', 'table' })
+    validate('cmd', config.reuse_client, 'function', true)
 
     if can_start(config) then
       -- Deepcopy config so changes done in the client
@@ -522,6 +510,7 @@ local function lsp_enable_callback(bufnr)
       config = vim.deepcopy(config)
 
       if type(config.root_dir) == 'function' then
+        ---@param root_dir string
         config.root_dir(function(root_dir)
           config.root_dir = root_dir
           start(config)
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index d94bc70a65..8af9f2f791 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -184,7 +184,7 @@ local function check_enabled_configs()
   vim.health.start('vim.lsp: Enabled Configurations')
 
   for name in vim.spairs(vim.lsp._enabled_configs) do
-    local config = vim.lsp._resolve_config(name)
+    local config = vim.lsp.config[name]
     local text = {} --- @type string[]
     text[#text + 1] = ('%s:'):format(name)
     for k, v in
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index f396c837f9..9cefe96e79 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -6147,7 +6147,7 @@ describe('LSP', function()
           vim.lsp.config('*', { root_markers = { '.git' } })
           vim.lsp.config('foo', { cmd = { 'foo' } })
 
-          return vim.lsp._resolve_config('foo')
+          return vim.lsp.config['foo']
         end)
       )
     end)
-- 
cgit 


From 4fb3b57a19cb83a99d4c5489982c22d96e28e8c2 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Tue, 31 Dec 2024 08:29:14 -0600
Subject: feat(tui): handle kitty key events in libtermkey (#31727)

Enable key event reporting in the kitty keyboard protocol. This causes
supporting terminals to send key events for presses, repeats, and key
releases. For now we ignore release events, but eventually we will
support users mapping those.
---
 src/nvim/tui/input.c                |  9 +++++++++
 src/nvim/tui/termkey/driver-csi.c   | 18 +++++++++++++++---
 src/nvim/tui/termkey/termkey.c      |  3 +++
 src/nvim/tui/termkey/termkey_defs.h |  8 ++++++++
 src/nvim/tui/tui.c                  |  7 +++++--
 5 files changed, 40 insertions(+), 5 deletions(-)

diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index bf00a6a82e..9d901e94ce 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -431,6 +431,15 @@ static void tk_getkeys(TermInput *input, bool force)
   TermKeyResult result;
 
   while ((result = tk_getkey(input->tk, &key, force)) == TERMKEY_RES_KEY) {
+    // Only press and repeat events are handled for now
+    switch (key.event) {
+    case TERMKEY_EVENT_PRESS:
+    case TERMKEY_EVENT_REPEAT:
+      break;
+    default:
+      continue;
+    }
+
     if (key.type == TERMKEY_TYPE_UNICODE && !key.modifiers) {
       forward_simple_utf8(input, &key);
     } else if (key.type == TERMKEY_TYPE_UNICODE
diff --git a/src/nvim/tui/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c
index ac2459beb2..3f54c874d1 100644
--- a/src/nvim/tui/termkey/driver-csi.c
+++ b/src/nvim/tui/termkey/driver-csi.c
@@ -177,9 +177,21 @@ static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, TermKey
         return TERMKEY_RES_ERROR;
       }
 
-      if (nsubparams > 0 && subparam != 1) {
-        // Not a press event. Ignore for now
-        return TERMKEY_RES_NONE;
+      if (nsubparams > 0) {
+        switch (subparam) {
+        case 1:
+          key->event = TERMKEY_EVENT_PRESS;
+          break;
+        case 2:
+          key->event = TERMKEY_EVENT_REPEAT;
+          break;
+        case 3:
+          key->event = TERMKEY_EVENT_RELEASE;
+          break;
+        default:
+          // Invalid event
+          return TERMKEY_RES_NONE;
+        }
       }
 
       key->modifiers = args[1] - 1;
diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c
index f5736e9b69..8c4a91e736 100644
--- a/src/nvim/tui/termkey/termkey.c
+++ b/src/nvim/tui/termkey/termkey.c
@@ -834,6 +834,9 @@ static TermKeyResult peekkey(TermKey *tk, TermKeyKey *key, int force, size_t *nb
     return TERMKEY_RES_ERROR;
   }
 
+  // Press is the default event type.
+  key->event = TERMKEY_EVENT_PRESS;
+
 #ifdef DEBUG
   fprintf(stderr, "getkey(force=%d): buffer ", force);
   print_buffer(tk);
diff --git a/src/nvim/tui/termkey/termkey_defs.h b/src/nvim/tui/termkey/termkey_defs.h
index 7c218ba7c2..09d3a5615a 100644
--- a/src/nvim/tui/termkey/termkey_defs.h
+++ b/src/nvim/tui/termkey/termkey_defs.h
@@ -123,6 +123,12 @@ typedef enum {
   TERMKEY_MOUSE_RELEASE,
 } TermKeyMouseEvent;
 
+typedef enum {
+  TERMKEY_EVENT_PRESS,
+  TERMKEY_EVENT_REPEAT,
+  TERMKEY_EVENT_RELEASE,
+} TermKeyEvent;
+
 enum {
   TERMKEY_KEYMOD_SHIFT = 1 << 0,
   TERMKEY_KEYMOD_ALT   = 1 << 1,
@@ -163,6 +169,8 @@ typedef struct {
 
   int modifiers;
 
+  TermKeyEvent event;
+
   // Any Unicode character can be UTF-8 encoded in no more than 6 bytes, plus
   // terminating NUL
   char utf8[7];
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 693b45e80e..f4337d5011 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -296,7 +296,10 @@ void tui_set_key_encoding(TUIData *tui)
 {
   switch (tui->input.key_encoding) {
   case kKeyEncodingKitty:
-    out(tui, S_LEN("\x1b[>1u"));
+    // Progressive enhancement flags:
+    //   0b01   (1) Disambiguate escape codes
+    //   0b10   (2) Report event types
+    out(tui, S_LEN("\x1b[>3u"));
     break;
   case kKeyEncodingXterm:
     out(tui, S_LEN("\x1b[>4;2m"));
@@ -311,7 +314,7 @@ static void tui_reset_key_encoding(TUIData *tui)
 {
   switch (tui->input.key_encoding) {
   case kKeyEncodingKitty:
-    out(tui, S_LEN("\x1b[<1u"));
+    out(tui, S_LEN("\x1b[4;0m"));
-- 
cgit 


From 19250002456f9029d1f4ffa8a2bc0c5e77ae604c Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Tue, 31 Dec 2024 14:19:30 +0100
Subject: test: skip flaky watchdirs() test on macos

---
 test/functional/lua/watch_spec.lua | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua
index ad16df8a7c..3b109b70d5 100644
--- a/test/functional/lua/watch_spec.lua
+++ b/test/functional/lua/watch_spec.lua
@@ -91,8 +91,7 @@ describe('vim._watch', function()
         skip(is_os('mac'), 'flaky test on mac')
         skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38')
       elseif watchfunc == 'watchdirs' and is_os('mac') then
-        -- Bump this (or fix the bug) if CI continues to fail in future versions of macos CI.
-        skip(is_ci() and vim.uv.os_uname().release == '24.0.0', 'weird failure for macOS arm 15 CI')
+        skip(true, 'weird failure since macOS 14 CI, see bbf208784ca279178ba0075b60d3e9c80f11da7a')
       else
         skip(
           is_os('bsd'),
-- 
cgit 


From b3bdba5cb10f9f0f1f2b40ff40e807f8a22f65c1 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Tue, 31 Dec 2024 13:55:38 +0100
Subject: ci(news): trigger job for `perf` commit type

There is a "performance" section in news.txt so it makes sense we should
also give a reminder to update news for performance improvements.
---
 .github/workflows/news.yml | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/news.yml b/.github/workflows/news.yml
index 8d21b86e8e..ca07876197 100644
--- a/.github/workflows/news.yml
+++ b/.github/workflows/news.yml
@@ -19,15 +19,15 @@ jobs:
             message=$(git log -n1 --pretty=format:%s $commit)
             type="$(echo "$message" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')"
             breaking="$(echo "$message" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')"
-            if [[ "$type" == "feat" ]] || [[ "$breaking" == "breaking-change" ]]; then
+            if [[ "$type" == "feat" ]] || [[ "$type" == "perf" ]] || [[ "$breaking" == "breaking-change" ]]; then
               ! git diff HEAD~${{ github.event.pull_request.commits }}..HEAD --quiet runtime/doc/news.txt ||
               {
                 echo "
-                  Pull request includes a new feature or a breaking change, but
-                  news.txt hasn't been updated yet. This is just a reminder
-                  that news.txt may need to be updated. You can ignore this CI
-                  failure if you think the change won't be of interest to
-                  users."
+                  Pull request includes a new feature, performance improvement
+                  or a breaking change, but news.txt hasn't been updated yet.
+                  This is just a reminder that news.txt may need to be updated.
+                  You can ignore this CI failure if you think the change won't
+                  be of interest to users."
                   exit 1
                }
             fi
-- 
cgit 


From 0bef3b911cc262a007fb4412d864c1832d1268ad Mon Sep 17 00:00:00 2001
From: Gustav Eikaas <46537983+GustavEikaas@users.noreply.github.com>
Date: Tue, 31 Dec 2024 16:40:05 +0100
Subject: fix(vim.fs): joinpath() does not normalize slashes on Windows #31782

---
 runtime/doc/lua.txt             |  7 +++++--
 runtime/lua/vim/fs.lua          | 11 ++++++++---
 test/functional/lua/fs_spec.lua | 14 ++++++++++++++
 3 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index a2a83ef229..022adb3da7 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -3062,8 +3062,11 @@ vim.fs.find({names}, {opts})                                   *vim.fs.find()*
         items
 
 vim.fs.joinpath({...})                                     *vim.fs.joinpath()*
-    Concatenate directories and/or file paths into a single path with
-    normalization (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`)
+    Concatenates partial paths into one path. Slashes are normalized
+    (redundant slashes are removed, and on Windows backslashes are replaced
+    with forward-slashes) (e.g., `"foo/"` and `"/bar"` get joined to
+    `"foo/bar"`) (windows: e.g `"a\foo\"` and `"\bar"` are joined to
+    `"a/foo/bar"`)
 
     Attributes: ~
         Since: 0.10.0
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index f2cd210cac..1b774d5cab 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -105,14 +105,19 @@ function M.basename(file)
   return file:match('/$') and '' or (file:match('[^/]*$'))
 end
 
---- Concatenate directories and/or file paths into a single path with normalization
---- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`)
+--- Concatenates partial paths into one path. Slashes are normalized (redundant slashes are removed, and on Windows backslashes are replaced with forward-slashes)
+--- (e.g., `"foo/"` and `"/bar"` get joined to `"foo/bar"`)
+--- (windows: e.g `"a\foo\"` and `"\bar"` are joined to `"a/foo/bar"`)
 ---
 ---@since 12
 ---@param ... string
 ---@return string
 function M.joinpath(...)
-  return (table.concat({ ... }, '/'):gsub('//+', '/'))
+  local path = table.concat({ ... }, '/')
+  if iswin then
+    path = path:gsub('\\', '/')
+  end
+  return (path:gsub('//+', '/'))
 end
 
 ---@alias Iterator fun(): string?, string?
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 63444f5ba1..3c306e6824 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -323,6 +323,20 @@ describe('vim.fs', function()
       eq('foo/bar/baz', vim.fs.joinpath('foo', 'bar', 'baz'))
       eq('foo/bar/baz', vim.fs.joinpath('foo', '/bar/', '/baz'))
     end)
+    it('rewrites backslashes on Windows', function()
+      if is_os('win') then
+        eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]]))
+      else
+        eq([[foo/\\bar\\\\baz/zub\]], vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]]))
+      end
+    end)
+    it('strips redundant slashes', function()
+      if is_os('win') then
+        eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo//]], [[\\bar\\\\baz]], [[zub\]]))
+      else
+        eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[//bar////baz]], [[zub/]]))
+      end
+    end)
   end)
 
   describe('normalize()', function()
-- 
cgit 


From a389dc2f950ef89492dfc2d8334e421d2252cddf Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Tue, 31 Dec 2024 09:59:03 -0600
Subject: feat(clipboard)!: use OSC 52 as fallback clipboard provider (#31730)

We currently enable the OSC 52 clipboard provider by setting g:clipboard
when a list of conditions are met, one of which is that $SSH_TTY must be
set. We include this condition because often OSC 52 is not the best
clipboard provider, so if there are "local" providers available Nvim
should prefer those over OSC 52.

However, if no other providers are available, Nvim should use OSC 52
even when $SSH_TTY is not set. When a user is in an SSH session then the
checks for the other clipboard providers will still (typically) fail, so
OSC 52 continues to be enabled by default in SSH sessions.

This is marked as a breaking change because there are some cases where
OSC 52 wasn't enabled before and is now (or vice versa).
---
 runtime/autoload/provider/clipboard.vim |  8 ++++++++
 runtime/doc/news.txt                    |  4 +++-
 runtime/doc/provider.txt                | 32 +++++++++++++++-----------------
 runtime/plugin/osc52.lua                | 31 ++++++++++++-------------------
 test/functional/terminal/tui_spec.lua   |  7 +------
 5 files changed, 39 insertions(+), 43 deletions(-)

diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index 848fa401f1..0bfd82f61d 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -169,6 +169,14 @@ function! provider#clipboard#Executable() abort
     let s:copy['*'] = s:copy['+']
     let s:paste['*'] = s:paste['+']
     return 'tmux'
+  elseif get(get(g:, 'termfeatures', {}), 'osc52') && &clipboard ==# ''
+    " Don't use OSC 52 when 'clipboard' is set. It can be slow and cause a lot
+    " of user prompts. Users can opt-in to it by setting g:clipboard manually.
+    let s:copy['+'] = v:lua.require'vim.ui.clipboard.osc52'.copy('+')
+    let s:copy['*'] = v:lua.require'vim.ui.clipboard.osc52'.copy('*')
+    let s:paste['+'] = v:lua.require'vim.ui.clipboard.osc52'.paste('+')
+    let s:paste['*'] = v:lua.require'vim.ui.clipboard.osc52'.paste('*')
+    return 'OSC 52'
   endif
 
   let s:err = 'clipboard: No clipboard tool. :help clipboard'
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 7ea65479f3..49d9bb5ce0 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -174,7 +174,9 @@ TREESITTER
 
 TUI
 
-• TODO
+• OSC 52 is used as a fallback clipboard provider when no other
+  |clipboard-tool| is found, even when not using SSH |clipboard-osc52|. To
+  disable OSC 52 queries, set the "osc52" key of |g:termfeatures| to false.
 
 VIMSCRIPT
 
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index c54a4df3d8..69ae0f20d1 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -259,23 +259,21 @@ For Windows WSL, try this g:clipboard definition:
 							    *clipboard-osc52*
 Nvim bundles a clipboard provider that allows copying to the system clipboard
 using OSC 52. OSC 52 is an Operating System Command control sequence that
-writes the copied text to the terminal emulator. If the terminal emulator
-supports OSC 52 then it will write the copied text into the system clipboard.
-
-Nvim will attempt to automatically determine if the host terminal emulator
-supports the OSC 52 sequence and enable the OSC 52 clipboard provider if it
-does as long as all of the following are true:
-
-  • Nvim is running in the |TUI|
-  • |g:clipboard| is unset
-  • 'clipboard' is not set to "unnamed" or "unnamedplus"
-  • $SSH_TTY is set
-
-If any of the above conditions are not met then the OSC 52 clipboard provider
-will not be used by default and Nvim will fall back to discovering a
-|clipboard-tool| through the usual process.
-
-To force Nvim to use the OSC 52 provider you can use the following
+causes the terminal emulator to write to or read from the system clipboard.
+
+When Nvim is running in the |TUI|, it will automatically attempt to determine if
+the host terminal emulator supports OSC 52. If it does, then Nvim will use OSC
+52 for copying and pasting if no other |clipboard-tool| is found and when
+'clipboard' is unset.
+
+							*g:termfeatures*
+To disable the automatic detection, set the "osc52" key of |g:termfeatures| to
+|v:false| in the |config| file. Example: >lua
+	local termfeatures = vim.g.termfeatures or {}
+	termfeatures.osc52 = false
+	vim.g.termfeatures = termfeatures
+<
+To force Nvim to always use the OSC 52 provider you can use the following
 |g:clipboard| definition: >lua
 
     vim.g.clipboard = {
diff --git a/runtime/plugin/osc52.lua b/runtime/plugin/osc52.lua
index 7ffd64342e..c7f1cbe2e3 100644
--- a/runtime/plugin/osc52.lua
+++ b/runtime/plugin/osc52.lua
@@ -6,7 +6,15 @@ for _, ui in ipairs(vim.api.nvim_list_uis()) do
   end
 end
 
-if not tty or vim.g.clipboard ~= nil or vim.o.clipboard ~= '' or not os.getenv('SSH_TTY') then
+-- Do not query when any of the following is true:
+--   * TUI is not attached
+--   * OSC 52 support is explicitly disabled via g:termfeatures
+--   * Using a badly behaved terminal
+if
+  not tty
+  or (vim.g.termfeatures ~= nil and vim.g.termfeatures.osc52 == false)
+  or vim.env.TERM_PROGRAM == 'Apple_Terminal'
+then
   return
 end
 
@@ -17,28 +25,13 @@ require('vim.termcap').query('Ms', function(cap, found, seq)
 
   assert(cap == 'Ms')
 
-  -- Check 'clipboard' and g:clipboard again to avoid a race condition
-  if vim.o.clipboard ~= '' or vim.g.clipboard ~= nil then
-    return
-  end
-
   -- If the terminal reports a sequence other than OSC 52 for the Ms capability
   -- then ignore it. We only support OSC 52 (for now)
   if not seq or not seq:match('^\027%]52') then
     return
   end
 
-  local osc52 = require('vim.ui.clipboard.osc52')
-
-  vim.g.clipboard = {
-    name = 'OSC 52',
-    copy = {
-      ['+'] = osc52.copy('+'),
-      ['*'] = osc52.copy('*'),
-    },
-    paste = {
-      ['+'] = osc52.paste('+'),
-      ['*'] = osc52.paste('*'),
-    },
-  }
+  local termfeatures = vim.g.termfeatures or {}
+  termfeatures.osc52 = true
+  vim.g.termfeatures = termfeatures
 end)
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index de92aefd5b..f9145f9b63 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -3184,7 +3184,6 @@ describe('TUI', function()
           local req = args.data
           local payload = req:match('^\027P%+q([%x;]+)$')
           if payload and vim.text.hexdecode(payload) == 'Ms' then
-            vim.g.xtgettcap = 'Ms'
             local resp = string.format('\027P1+r%s=%s\027\\', payload, vim.text.hexencode('\027]52;;\027\\'))
             vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp)
             return true
@@ -3202,9 +3201,6 @@ describe('TUI', function()
     }, {
       env = {
         VIMRUNTIME = os.getenv('VIMRUNTIME'),
-
-        -- Only queries when SSH_TTY is set
-        SSH_TTY = '/dev/pts/1',
       },
     })
 
@@ -3212,8 +3208,7 @@ describe('TUI', function()
 
     local child_session = n.connect(child_server)
     retry(nil, 1000, function()
-      eq('Ms', eval("get(g:, 'xtgettcap', '')"))
-      eq({ true, 'OSC 52' }, { child_session:request('nvim_eval', 'g:clipboard.name') })
+      eq({ true, { osc52 = true } }, { child_session:request('nvim_eval', 'g:termfeatures') })
     end)
   end)
 end)
-- 
cgit 


From b5cb69f8a4a310fb3f18e79bcf5c7ed41d635a48 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Tue, 31 Dec 2024 12:16:25 -0600
Subject: fix(tui): handle key events for arrow and function keys (#31804)

Arrow and function keys do not use CSI u with the kitty keyboard
protocol. For example, the Up arrow key uses CSI A, and the function
keys use a variety of different CSI sequences.

Until now, termkey only parsed subparams used by key events for CSI u
sequences. The result being that any key which did not use CSI u (e.g.
arrow and function keys) was being emitted twice by termkey since it was
not recognizing the separate press and release events.

This commit makes termkey also parse subparams for other key sequences
so that the release key events do not send duplicate keys.
---
 src/nvim/tui/termkey/driver-csi.c   | 50 ++++++++++++++++++++++++++-----------
 src/nvim/tui/termkey/termkey_defs.h |  1 +
 2 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/src/nvim/tui/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c
index 3f54c874d1..9741ec72b9 100644
--- a/src/nvim/tui/termkey/driver-csi.c
+++ b/src/nvim/tui/termkey/driver-csi.c
@@ -31,11 +31,20 @@ static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd,
 
   if (nparams > 1 && params[1].param != NULL) {
     int arg = 0;
-    result = termkey_interpret_csi_param(params[1], &arg, NULL, NULL);
+    int subparam = 0;
+    size_t nsubparams = 1;
+    result = termkey_interpret_csi_param(params[1], &arg, &subparam, &nsubparams);
     if (result != TERMKEY_RES_KEY) {
       return result;
     }
 
+    if (nsubparams > 0) {
+      key->event = parse_key_event(subparam);
+      if (key->event == TERMKEY_EVENT_UNKNOWN) {
+        return TERMKEY_RES_NONE;
+      }
+    }
+
     key->modifiers = arg - 1;
   } else {
     key->modifiers = 0;
@@ -103,11 +112,20 @@ static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermK
   int args[3];
 
   if (nparams > 1 && params[1].param != NULL) {
-    result = termkey_interpret_csi_param(params[1], &args[1], NULL, NULL);
+    int subparam = 0;
+    size_t nsubparams = 1;
+    result = termkey_interpret_csi_param(params[1], &args[1], &subparam, &nsubparams);
     if (result != TERMKEY_RES_KEY) {
       return result;
     }
 
+    if (nsubparams > 0) {
+      key->event = parse_key_event(subparam);
+      if (key->event == TERMKEY_EVENT_UNKNOWN) {
+        return TERMKEY_RES_NONE;
+      }
+    }
+
     key->modifiers = args[1] - 1;
   } else {
     key->modifiers = 0;
@@ -178,18 +196,8 @@ static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, TermKey
       }
 
       if (nsubparams > 0) {
-        switch (subparam) {
-        case 1:
-          key->event = TERMKEY_EVENT_PRESS;
-          break;
-        case 2:
-          key->event = TERMKEY_EVENT_REPEAT;
-          break;
-        case 3:
-          key->event = TERMKEY_EVENT_RELEASE;
-          break;
-        default:
-          // Invalid event
+        key->event = parse_key_event(subparam);
+        if (key->event == TERMKEY_EVENT_UNKNOWN) {
           return TERMKEY_RES_NONE;
         }
       }
@@ -430,6 +438,20 @@ TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, i
 
 #define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
 
+static TermKeyEvent parse_key_event(int n)
+{
+  switch (n) {
+  case 1:
+    return TERMKEY_EVENT_PRESS;
+  case 2:
+    return TERMKEY_EVENT_REPEAT;
+  case 3:
+    return TERMKEY_EVENT_RELEASE;
+  default:
+    return TERMKEY_EVENT_UNKNOWN;
+  }
+}
+
 static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len,
                                TermKeyCsiParam params[], size_t *nargs, unsigned *commandp)
 {
diff --git a/src/nvim/tui/termkey/termkey_defs.h b/src/nvim/tui/termkey/termkey_defs.h
index 09d3a5615a..87d3f63447 100644
--- a/src/nvim/tui/termkey/termkey_defs.h
+++ b/src/nvim/tui/termkey/termkey_defs.h
@@ -124,6 +124,7 @@ typedef enum {
 } TermKeyMouseEvent;
 
 typedef enum {
+  TERMKEY_EVENT_UNKNOWN,
   TERMKEY_EVENT_PRESS,
   TERMKEY_EVENT_REPEAT,
   TERMKEY_EVENT_RELEASE,
-- 
cgit 


From 59cbe640233eeb6ae96231e059449e73e15f9ea5 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Tue, 31 Dec 2024 22:32:19 +0100
Subject: build(deps): bump utf8proc to v2.10.0

---
 cmake.deps/deps.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index e6b5f5b989..f73023ae84 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -35,8 +35,8 @@ GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47f5c
 LIBICONV_URL https://github.com/neovim/deps/raw/b9bf36eb31f27e8136d907da38fa23518927737e/opt/libiconv-1.17.tar.gz
 LIBICONV_SHA256 8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313
 
-UTF8PROC_URL https://github.com/JuliaStrings/utf8proc/archive/3de4596fbe28956855df2ecb3c11c0bbc3535838.tar.gz
-UTF8PROC_SHA256 fb4a16bb659b58afb7f921fcc8928d0b3c1fcab135366c8a4f9ca7de1b1cfada
+UTF8PROC_URL https://github.com/JuliaStrings/utf8proc/archive/v2.10.0.tar.gz
+UTF8PROC_SHA256 6f4f1b639daa6dca9f80bc5db1233e9cbaa31a67790887106160b33ef743f136
 
 TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.23.2.tar.gz
 TREESITTER_C_SHA256 d8b9c1b2ffb6a42caf9bc76e07c52507d4e60b17175ed9beb0e779be8db1200c
-- 
cgit 


From 9d114b720514b0bde96dbdd9b6ef328d232a1589 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Wed, 1 Jan 2025 19:45:14 +0600
Subject: refactor(options): use `const` in more places (#31791)

---
 src/nvim/eval/vars.c | 14 +++++++++-----
 src/nvim/mapping.c   |  2 +-
 src/nvim/option.c    | 28 +++++++++++++++-------------
 src/nvim/os/env.c    |  2 +-
 4 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 6504d44eb6..b9b5a055fb 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -872,11 +872,15 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
       } else {
         newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n));
       }
-    } else if (!hidden && is_string
-               && curval.data.string.data != NULL && newval.data.string.data != NULL) {  // string
-      OptVal newval_old = newval;
-      newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
-      optval_free(newval_old);
+    } else if (!hidden && is_string) {  // string
+      const char *curval_data = curval.data.string.data;
+      const char *newval_data = newval.data.string.data;
+
+      if (curval_data != NULL && newval_data != NULL) {
+        OptVal newval_old = newval;
+        newval = CSTR_AS_OPTVAL(concat_str(curval_data, newval_data));
+        optval_free(newval_old);
+      }
     }
   }
 
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index d29a2c8e43..ca0349d1f6 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -1901,7 +1901,7 @@ int makemap(FILE *fd, buf_T *buf)
 // "what": 0 for :map lhs, 1 for :map rhs, 2 for :set
 //
 // return FAIL for failure, OK otherwise
-int put_escstr(FILE *fd, char *strstart, int what)
+int put_escstr(FILE *fd, const char *strstart, int what)
 {
   uint8_t *str = (uint8_t *)strstart;
 
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a59e55121f..9d9e70622a 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -533,8 +533,8 @@ static void set_string_default(OptIndex opt_idx, char *val, bool allocated)
 
 /// For an option value that contains comma separated items, find "newval" in
 /// "origval".  Return NULL if not found.
-static char *find_dup_item(char *origval, const char *newval, const size_t newvallen,
-                           uint32_t flags)
+static const char *find_dup_item(const char *origval, const char *newval, const size_t newvallen,
+                                 uint32_t flags)
   FUNC_ATTR_NONNULL_ARG(2)
 {
   if (origval == NULL) {
@@ -543,7 +543,7 @@ static char *find_dup_item(char *origval, const char *newval, const size_t newva
 
   int bs = 0;
 
-  for (char *s = origval; *s != NUL; s++) {
+  for (const char *s = origval; *s != NUL; s++) {
     if ((!(flags & kOptFlagComma) || s == origval || (s[-1] == ',' && !(bs & 1)))
         && strncmp(s, newval, newvallen) == 0
         && (!(flags & kOptFlagComma) || s[newvallen] == ',' || s[newvallen] == NUL)) {
@@ -727,7 +727,7 @@ void ex_set(exarg_T *eap)
 
 /// Copy the new string value into allocated memory for the option.
 /// Can't use set_option_direct(), because we need to remove the backslashes.
-static char *stropt_copy_value(char *origval, char **argp, set_op_T op,
+static char *stropt_copy_value(const char *origval, char **argp, set_op_T op,
                                uint32_t flags FUNC_ATTR_UNUSED)
 {
   char *arg = *argp;
@@ -774,7 +774,7 @@ static char *stropt_copy_value(char *origval, char **argp, set_op_T op,
 }
 
 /// Expand environment variables and ~ in string option value 'newval'.
-static char *stropt_expand_envvar(OptIndex opt_idx, char *origval, char *newval, set_op_T op)
+static char *stropt_expand_envvar(OptIndex opt_idx, const char *origval, char *newval, set_op_T op)
 {
   char *s = option_expand(opt_idx, newval);
   if (s == NULL) {
@@ -794,7 +794,7 @@ static char *stropt_expand_envvar(OptIndex opt_idx, char *origval, char *newval,
 
 /// Concatenate the original and new values of a string option, adding a "," if
 /// needed.
-static void stropt_concat_with_comma(char *origval, char *newval, set_op_T op, uint32_t flags)
+static void stropt_concat_with_comma(const char *origval, char *newval, set_op_T op, uint32_t flags)
 {
   int len = 0;
   int comma = ((flags & kOptFlagComma) && *origval != NUL && *newval != NUL);
@@ -820,7 +820,8 @@ static void stropt_concat_with_comma(char *origval, char *newval, set_op_T op, u
 
 /// Remove a value from a string option.  Copy string option value in "origval"
 /// to "newval" and then remove the string "strval" of length "len".
-static void stropt_remove_val(char *origval, char *newval, uint32_t flags, char *strval, int len)
+static void stropt_remove_val(const char *origval, char *newval, uint32_t flags, const char *strval,
+                              int len)
 {
   // Remove newval[] from origval[]. (Note: "len" has been set above
   // and is used here).
@@ -872,13 +873,13 @@ static void stropt_remove_dupflags(char *newval, uint32_t flags)
 ///     set {opt}={val}
 ///     set {opt}:{val}
 static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void *varp,
-                               char *origval, set_op_T *op_arg, uint32_t flags)
+                               const char *origval, set_op_T *op_arg, uint32_t flags)
 {
   char *arg = *argp;
   set_op_T op = *op_arg;
   char *save_arg = NULL;
   char *newval;
-  char *s = NULL;
+  const char *s = NULL;
 
   arg++;  // jump to after the '=' or ':'
 
@@ -1194,9 +1195,10 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr
     break;
   }
   case kOptValTypeString: {
-    char *oldval_str = oldval.data.string.data;
+    const char *oldval_str = oldval.data.string.data;
     // Get the new value for the option
-    char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags);
+    const char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op,
+                                               flags);
     newval = CSTR_AS_OPTVAL(newval_str);
     break;
   }
@@ -1568,7 +1570,7 @@ char *find_shada_parameter(int type)
 /// These string options cannot be indirect!
 /// If "val" is NULL expand the current value of the option.
 /// Return pointer to NameBuff, or NULL when not expanded.
-static char *option_expand(OptIndex opt_idx, char *val)
+static char *option_expand(OptIndex opt_idx, const char *val)
 {
   // if option doesn't need expansion nothing to do
   if (!(options[opt_idx].flags & kOptFlagExpand) || is_option_hidden(opt_idx)) {
@@ -4392,7 +4394,7 @@ static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
       return FAIL;
     }
 
-    char *value_str = value.data.string.data;
+    const char *value_str = value.data.string.data;
     char *buf = NULL;
     char *part = NULL;
 
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 733e52e69c..3126881266 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -580,7 +580,7 @@ void expand_env(char *src, char *dst, int dstlen)
 /// @param esc        Escape spaces in expanded variables
 /// @param one        `srcp` is a single filename
 /// @param prefix     Start again after this (can be NULL)
-void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool esc, bool one,
+void expand_env_esc(const char *restrict srcp, char *restrict dst, int dstlen, bool esc, bool one,
                     char *prefix)
   FUNC_ATTR_NONNULL_ARG(1, 2)
 {
-- 
cgit 


From dc692f553aae367a03f286e0d59561247941f96c Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Wed, 1 Jan 2025 12:29:51 -0800
Subject: docs: misc #31479

---
 runtime/doc/channel.txt              | 13 ++++---
 runtime/doc/gui.txt                  | 10 ++++--
 runtime/doc/intro.txt                | 22 ++++++++----
 runtime/doc/lsp.txt                  | 28 +++++----------
 runtime/doc/lua.txt                  | 16 +++++----
 runtime/doc/map.txt                  |  2 +-
 runtime/doc/news.txt                 |  6 ++--
 runtime/doc/repeat.txt               |  2 +-
 runtime/doc/treesitter.txt           | 69 ++++++++++++++++++++++--------------
 runtime/doc/vim_diff.txt             | 23 ++++++------
 runtime/lua/vim/_meta/json.lua       | 14 ++++----
 runtime/lua/vim/fs.lua               | 10 ++++--
 runtime/lua/vim/treesitter/query.lua | 51 ++++++++++++++++----------
 test/functional/lua/fs_spec.lua      |  2 +-
 test/functional/lua/json_spec.lua    |  2 +-
 15 files changed, 154 insertions(+), 116 deletions(-)

diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index dc9b0735ea..c2d220041c 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -11,21 +11,20 @@ Nvim asynchronous IO					*channel*
 ==============================================================================
 1. Introduction						    *channel-intro*
 
-Channels are nvim's way of communicating with external processes.
+Channels are Nvim's way of communicating with external processes.
 
 There are several ways to open a channel:
 
-  1. Through stdin/stdout when `nvim` is started with `--headless`, and a startup
-     script or --cmd  command opens the stdio channel using |stdioopen()|.
+  1. Through stdin/stdout when `nvim` is started with `--headless` and a startup
+     script or `--cmd` command opens the stdio channel using |stdioopen()|.
 
   2. Through stdin, stdout and stderr of a process spawned by |jobstart()|.
 
-  3. Through the PTY master end of a PTY opened with |nvim_open_term()| or
-     `jobstart(…, {'pty': v:true})`.
+  3. Through the PTY master end opened with `jobstart(…, {'pty': v:true})`.
 
   4. By connecting to a TCP/IP socket or named pipe with |sockconnect()|.
 
-  5. By another process connecting to a socket listened to by nvim. This only
+  5. By another process connecting to a socket listened to by Nvim. This only
      supports RPC channels, see |rpc-connecting|.
 
 Channels support multiple modes or protocols. In the most basic
@@ -146,7 +145,7 @@ from the host TTY, or if Nvim is |--headless| it uses default values: >vim
     :echo system('nvim --headless +"te stty -a" +"sleep 1" +"1,/^$/print" +q')
 
 ==============================================================================
-3. Communicating using msgpack-rpc			      *channel-rpc*
+3. Communicating with msgpack RPC			      *channel-rpc*
 
 When channels are opened with the `rpc` option set to true, the channel can be
 used for remote method calls in both directions, see |msgpack-rpc|. Note that
diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt
index 380fa71537..eb787af3c9 100644
--- a/runtime/doc/gui.txt
+++ b/runtime/doc/gui.txt
@@ -17,10 +17,14 @@ TUI and GUI (assuming the UI supports the given feature). See |TUI| for notes
 specific to the terminal UI. Help tags with the "gui-" prefix refer to UI
 features, whereas help tags with the "ui-" prefix refer to the |ui-protocol|.
 
-Nvim provides a default, builtin UI (the |TUI|), but there are many other
-(third-party) GUIs that you can use instead:
+==============================================================================
+Third-party GUIs                                *third-party-guis* *vscode*
+
+Nvim provides a builtin "terminal UI" (|TUI|), but also works with many
+(third-party) GUIs which may provide a fresh look or extra features on top of
+Nvim. For example, "vscode-neovim" essentally allows you to use VSCode as
+a Nvim GUI.
 
-                                                        *vscode*
 - vscode-neovim (Nvim in VSCode!) https://github.com/vscode-neovim/vscode-neovim
 - Firenvim (Nvim in your web browser!) https://github.com/glacambre/firenvim
 - Neovide https://neovide.dev/
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index ac019a4f25..0c654b8b30 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -32,8 +32,9 @@ and |user-manual|.
 Resources                                               *resources*
 
                                                 *internet* *www* *distribution*
-- Nvim home page: https://neovim.io/
-- Vim FAQ: https://vimhelp.org/vim_faq.txt.html
+Nvim home page:
+
+        https://neovim.io/
 
                                                 *book*
 There are many resources to learn Vi, Vim, and Nvim.  We recommend:
@@ -48,6 +49,7 @@ There are many resources to learn Vi, Vim, and Nvim.  We recommend:
 - For more information try one of these:
   - https://iccf-holland.org/click5.html
   - https://www.vim.org/iccf/click5.html
+- Vim FAQ: https://vimhelp.org/vim_faq.txt.html
 
                                                 *bugs* *bug-report* *feature-request*
 Report bugs and request features here: https://github.com/neovim/neovim/issues
@@ -67,18 +69,24 @@ To install or upgrade Nvim, you can...
 - Build from source:
   https://github.com/neovim/neovim/blob/master/INSTALL.md#install-from-source
 
-                                                *uninstall*
+------------------------------------------------------------------------------
+Un-installing Nvim                                      *uninstall*
+
 To uninstall Nvim:
-- If you downloaded a pre-built archive or built Nvim from source (e.g. `make
-  install`), just delete its files, typically located in: >
+- If you downloaded a pre-built archive or built Nvim from source (e.g.
+  `make install`), just delete its files, typically located in: >
   /usr/local/bin/nvim
   /usr/local/share/nvim
+<
+  - To find where Nvim is installed, run these commands: >
+    :echo v:progpath
+    :echo $VIMRUNTIME
 <
 - If you installed via package manager, read your package manager's
   documentation. Common examples:
   - APT (Debian, Ubuntu, …): `apt-get remove neovim`
-  - Homebrew (macOS): `brew install neovim`
-  - Scoop (Windows): `scoop install neovim`
+  - Homebrew (macOS): `brew uninstall neovim`
+  - Scoop (Windows): `scoop uninstall neovim`
 
 ==============================================================================
 Sponsor Vim/Nvim development                            *sponsor* *register*
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 16f543088b..71e8a12ca3 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -344,22 +344,17 @@ Each response handler has this signature: >
                                                       *lsp-handler-resolution*
 Handlers can be set by (in increasing priority):
 
-- Setting a field in vim.lsp.handlers.                      *vim.lsp.handlers*
-  `vim.lsp.handlers` is a global table that contains the default mapping of
-  |lsp-method| names to lsp-handlers.
-
+                                                            *vim.lsp.handlers*
+- Setting a field in `vim.lsp.handlers`. This global table contains the
+  default mappings of |lsp-method| names to handlers. (Note: only for
+  server-to-client requests/notifications, not client-to-server.)
   Example: >lua
-
     vim.lsp.handlers['textDocument/publishDiagnostics'] = my_custom_diagnostics_handler
 <
-  Note: this only applies for requests/notifications made by the
-  server to the client.
-
-- The {handlers} parameter of |vim.lsp.start()|. This sets the default
-  |lsp-handler| for a specific server.
-
+- Passing a {handlers} parameter to |vim.lsp.start()|. This sets the default
+  |lsp-handler| for a specific server. (Note: only for server-to-client
+  requests/notifications, not client-to-server.)
   Example: >lua
-
     vim.lsp.start {
       ..., -- Other configuration omitted.
       handlers = {
@@ -367,14 +362,9 @@ Handlers can be set by (in increasing priority):
       },
     }
 <
-  Note: this only applies for requests/notifications made by the
-  server to the client.
-
-- The {handler} parameter of |vim.lsp.buf_request_all()|. This sets
-  the |lsp-handler| ONLY for the given request(s).
-
+- Passing a {handler} parameter to |vim.lsp.buf_request_all()|. This sets the
+  |lsp-handler| ONLY for the given request(s).
   Example: >lua
-
     vim.lsp.buf_request_all(
       0,
       'textDocument/publishDiagnostics',
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 022adb3da7..25e58c0240 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -817,8 +817,8 @@ vim.json.encode({obj}, {opts})                             *vim.json.encode()*
     Parameters: ~
       • {obj}   (`any`)
       • {opts}  (`table?`) Options table with keys:
-                • escape_slash: (boolean) (default false) When true, escapes
-                  `/` character in JSON strings
+                • escape_slash: (boolean) (default false) Escape slash
+                  characters "/" in string values.
 
     Return: ~
         (`string`)
@@ -3062,11 +3062,13 @@ vim.fs.find({names}, {opts})                                   *vim.fs.find()*
         items
 
 vim.fs.joinpath({...})                                     *vim.fs.joinpath()*
-    Concatenates partial paths into one path. Slashes are normalized
-    (redundant slashes are removed, and on Windows backslashes are replaced
-    with forward-slashes) (e.g., `"foo/"` and `"/bar"` get joined to
-    `"foo/bar"`) (windows: e.g `"a\foo\"` and `"\bar"` are joined to
-    `"a/foo/bar"`)
+    Concatenates partial paths (one absolute or relative path followed by zero
+    or more relative paths). Slashes are normalized: redundant slashes are
+    removed, and (on Windows) backslashes are replaced with forward-slashes.
+
+    Examples:
+    • "foo/", "/bar" => "foo/bar"
+    • Windows: "a\foo\", "\bar" => "a/foo/bar"
 
     Attributes: ~
         Since: 0.10.0
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 34cf309576..1e472422ce 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -12,7 +12,7 @@ manual.
                                       Type |gO| to see the table of contents.
 
 ==============================================================================
-1. Key mapping			      *keybind* *key-mapping* *mapping* *macro*
+1. Key mapping				*keybind* *key-mapping* *mapping*
 
 Key mapping is used to change the meaning of typed keys.  The most common use
 is to define a sequence of commands for a function key.  Example: >
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 49d9bb5ce0..9ab0a01b99 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -67,7 +67,6 @@ DEFAULTS
   current buffer, respectively.
 • 'number', 'relativenumber', 'signcolumn', and 'foldcolumn' are disabled in
   |terminal| buffers. See |terminal-config| for an example of changing these defaults.
-• |vim.json.encode()| no longer escapes the forward slash symbol by default
 
 DIAGNOSTICS
 
@@ -137,6 +136,7 @@ LUA
 • Command-line completions for: `vim.g`, `vim.t`, `vim.w`, `vim.b`, `vim.v`,
   `vim.o`, `vim.wo`, `vim.bo`, `vim.opt`, `vim.opt_local`, `vim.opt_global`,
   and `vim.fn`.
+• |vim.json.encode()| no longer escapes forward slashes "/" by default
 
 OPTIONS
 
@@ -192,7 +192,6 @@ The following new features were added.
 API
 
 • |nvim__ns_set()| can set properties for a namespace
-• |vim.json.encode()| has an option to enable forward slash escaping
 
 DEFAULTS
 
@@ -270,6 +269,7 @@ LUA
   is more performant and easier to read.
 • |vim.str_byteindex()| and |vim.str_utfindex()| gained overload signatures
   supporting two new parameters, `encoding` and `strict_indexing`.
+• |vim.json.encode()| has an option to enable forward slash escaping
 • |vim.fs.abspath()| converts paths to absolute paths.
 
 OPTIONS
@@ -282,6 +282,8 @@ PERFORMANCE
 
 • Significantly reduced redraw time for long lines with treesitter
   highlighting.
+• LSP diagnostics and inlay hints are de-duplicated (new requests cancel
+  inflight requests). This greatly improves performance with slow LSP servers.
 
 PLUGINS
 
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index abeefb980e..e65caa72ed 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -111,7 +111,7 @@ To abort this type CTRL-C twice.
 ==============================================================================
 Complex repeats						*complex-repeat*
 
-							*q* *recording*
+							*q* *recording* *macro*
 q{0-9a-zA-Z"}		Record typed characters into register {0-9a-zA-Z"}
 			(uppercase to append).  The 'q' command is disabled
 			while executing a register, and it doesn't work inside
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index a0860c60a6..956fb80e73 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -81,8 +81,7 @@ that user config takes precedence over plugins, which take precedence over
 queries bundled with Nvim). If a query should extend other queries instead
 of replacing them, use |treesitter-query-modeline-extends|.
 
-See |lua-treesitter-query| for the list of available methods for working with
-treesitter queries from Lua.
+The Lua interface is described at |lua-treesitter-query|.
 
 
 TREESITTER QUERY PREDICATES                            *treesitter-predicates*
@@ -1195,6 +1194,10 @@ register({lang}, {filetype})              *vim.treesitter.language.register()*
 ==============================================================================
 Lua module: vim.treesitter.query                        *lua-treesitter-query*
 
+This Lua |treesitter-query| interface allows you to create queries and use
+them to parse text. See |vim.treesitter.query.parse()| for a working example.
+
+
                                         *vim.treesitter.query.add_directive()*
 add_directive({name}, {handler}, {opts})
     Adds a new directive to be used in queries
@@ -1325,21 +1328,30 @@ omnifunc({findstart}, {base})                *vim.treesitter.query.omnifunc()*
       • {base}       (`string`)
 
 parse({lang}, {query})                          *vim.treesitter.query.parse()*
-    Parse {query} as a string. (If the query is in a file, the caller should
-    read the contents into a string before calling).
-
-    Returns a `Query` (see |lua-treesitter-query|) object which can be used to
-    search nodes in the syntax tree for the patterns defined in {query} using
-    the `iter_captures` and `iter_matches` methods.
-
-    Exposes `info` and `captures` with additional context about {query}.
-    • `captures` contains the list of unique capture names defined in {query}.
-    • `info.captures` also points to `captures`.
-    • `info.patterns` contains information about predicates.
+    Parses a {query} string and returns a `Query` object
+    (|lua-treesitter-query|), which can be used to search the tree for the
+    query patterns (via |Query:iter_captures()|, |Query:iter_matches()|), or
+    inspect the query via these fields:
+    • `captures`: a list of unique capture names defined in the query (alias:
+      `info.captures`).
+    • `info.patterns`: information about predicates.
+
+    Example (select the code then run `:'<,'>lua` to try it): >lua
+        local query = vim.treesitter.query.parse('vimdoc', [[
+          ; query
+          ((h1) @str
+            (#trim! @str 1 1 1 1))
+        ]])
+        local tree = vim.treesitter.get_parser():parse()[1]
+        for id, node, metadata in query:iter_captures(tree:root(), 0) do
+           -- Print the node name and source text.
+           vim.print({node:type(), vim.treesitter.get_node_text(node, vim.api.nvim_get_current_buf())})
+        end
+<
 
     Parameters: ~
       • {lang}   (`string`) Language to use for the query
-      • {query}  (`string`) Query in s-expr syntax
+      • {query}  (`string`) Query text, in s-expr syntax
 
     Return: ~
         (`vim.treesitter.Query`) Parsed query
@@ -1349,18 +1361,23 @@ parse({lang}, {query})                          *vim.treesitter.query.parse()*
 
                                                        *Query:iter_captures()*
 Query:iter_captures({node}, {source}, {start}, {stop})
-    Iterate over all captures from all matches inside {node}
-
-    {source} is needed if the query contains predicates; then the caller must
-    ensure to use a freshly parsed tree consistent with the current text of
-    the buffer (if relevant). {start} and {stop} can be used to limit matches
-    inside a row range (this is typically used with root node as the {node},
-    i.e., to get syntax highlight matches in the current viewport). When
-    omitted, the {start} and {stop} row values are used from the given node.
-
-    The iterator returns four values: a numeric id identifying the capture,
-    the captured node, metadata from any directives processing the match, and
-    the match itself. The following example shows how to get captures by name: >lua
+    Iterates over all captures from all matches in {node}.
+
+    {source} is required if the query contains predicates; then the caller
+    must ensure to use a freshly parsed tree consistent with the current text
+    of the buffer (if relevant). {start} and {stop} can be used to limit
+    matches inside a row range (this is typically used with root node as the
+    {node}, i.e., to get syntax highlight matches in the current viewport).
+    When omitted, the {start} and {stop} row values are used from the given
+    node.
+
+    The iterator returns four values:
+    1. the numeric id identifying the capture
+    2. the captured node
+    3. metadata from any directives processing the match
+    4. the match itself
+
+    Example: how to get captures by name: >lua
         for id, node, metadata, match in query:iter_captures(tree:root(), bufnr, first, last) do
           local name = query.captures[id] -- name of the capture in the query
           -- typically useful info about the node:
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index c93d2b119e..9f28b373ee 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -24,7 +24,7 @@ User configuration and data files are found in standard |base-directories|
   session information.  |shada|
 
 ==============================================================================
-Defaults                                                    *nvim-defaults*
+Defaults                                            *defaults* *nvim-defaults*
 
 - Filetype detection is enabled by default. This can be disabled by adding
   ":filetype off" to |init.vim|.
@@ -291,7 +291,8 @@ Commands:
   User commands can support |:command-preview| to show results as you type
 - |:write| with "++p" flag creates parent directories.
 
-Events:
+Events (autocommands):
+- Fixed inconsistent behavior in execution of nested autocommands #23368
 - |RecordingEnter|
 - |RecordingLeave|
 - |SearchWrapped|
@@ -299,6 +300,8 @@ Events:
 - |TabNewEntered|
 - |TermClose|
 - |TermOpen|
+- |TermResponse| is fired for any OSC sequence received from the terminal,
+  instead of the Primary Device Attributes response. |v:termresponse|
 - |UIEnter|
 - |UILeave|
 
@@ -366,7 +369,7 @@ Options:
 - 'shortmess'
     - "F" flag does not affect output from autocommands.
     - "q" flag fully hides macro recording message.
-- 'signcolumn'  supports up to 9 dynamic/fixed columns
+- 'signcolumn'  can show multiple signs (dynamic or fixed columns)
 - 'statuscolumn' full control of columns using 'statusline' format
 - 'tabline'     middle-click on tabpage label closes tabpage,
                 and %@Func@foo%X can call any function on mouse-click
@@ -375,6 +378,10 @@ Options:
 - 'winblend'    pseudo-transparency in floating windows |api-floatwin|
 - 'winhighlight' window-local highlights
 
+Performance:
+- Signs are implemented using Nvim's internal "marktree" (btree) structure.
+- Folds are not updated during insert-mode.
+
 Providers:
 - If a Python interpreter is available on your `$PATH`, |:python| and
   |:python3| are always available. See |provider-python|.
@@ -392,6 +399,7 @@ Shell:
 - |system()| does not support writing/reading "backgrounded" commands. |E5677|
 
 Signs:
+- 'signcolumn' can show multiple signs.
 - Signs are removed if the associated line is deleted.
 - Signs placed twice with the same identifier in the same group are moved.
 
@@ -584,9 +592,6 @@ Mappings:
 Motion:
 - The |jumplist| avoids useless/phantom jumps.
 
-Performance:
-- Folds are not updated during insert-mode.
-
 Syntax highlighting:
 - syncolor.vim has been removed. Nvim now sets up default highlighting groups
   automatically for both light and dark backgrounds, regardless of whether or
@@ -611,12 +616,6 @@ Working directory (Vim implemented some of these after Nvim):
 - `getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global
   working directory. Use `getcwd(-1, -1)` to get the global working directory.
 
-Autocommands:
-- Fixed inconsistent behavior in execution of nested autocommands:
-  https://github.com/neovim/neovim/issues/23368
-- |TermResponse| is fired for any OSC sequence received from the terminal,
-  instead of the Primary Device Attributes response. |v:termresponse|
-
 Options:
 - 'titlestring' uses printf-style '%' items (see: 'statusline') to implement
   the default behaviour. The implementation is equivalent to setting
diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua
index 1a7e87db9c..0d59de5fa6 100644
--- a/runtime/lua/vim/_meta/json.lua
+++ b/runtime/lua/vim/_meta/json.lua
@@ -25,18 +25,18 @@ vim.json = {}
 ---
 ---@param str string Stringified JSON data.
 ---@param opts? table Options table with keys:
----                                 - luanil: (table) Table with keys:
----                                   * object: (boolean) When true, converts `null` in JSON objects
----                                                       to Lua `nil` instead of |vim.NIL|.
----                                   * array: (boolean) When true, converts `null` in JSON arrays
----                                                      to Lua `nil` instead of |vim.NIL|.
+---                               - luanil: (table) Table with keys:
+---                                 - object: (boolean) When true, converts `null` in JSON objects
+---                                   to Lua `nil` instead of |vim.NIL|.
+---                                 - array: (boolean) When true, converts `null` in JSON arrays
+---                                   to Lua `nil` instead of |vim.NIL|.
 ---@return any
 function vim.json.decode(str, opts) end
 
 --- Encodes (or "packs") Lua object {obj} as JSON in a Lua string.
 ---@param obj any
 ---@param opts? table Options table with keys:
----                                 - escape_slash: (boolean) (default false) When true, escapes `/`
----                                                           character in JSON strings
+---                                 - escape_slash: (boolean) (default false) Escape slash
+---                                   characters "/" in string values.
 ---@return string
 function vim.json.encode(obj, opts) end
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 1b774d5cab..d042df96e5 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -105,9 +105,13 @@ function M.basename(file)
   return file:match('/$') and '' or (file:match('[^/]*$'))
 end
 
---- Concatenates partial paths into one path. Slashes are normalized (redundant slashes are removed, and on Windows backslashes are replaced with forward-slashes)
---- (e.g., `"foo/"` and `"/bar"` get joined to `"foo/bar"`)
---- (windows: e.g `"a\foo\"` and `"\bar"` are joined to `"a/foo/bar"`)
+--- Concatenates partial paths (one absolute or relative path followed by zero or more relative
+--- paths). Slashes are normalized: redundant slashes are removed, and (on Windows) backslashes are
+--- replaced with forward-slashes.
+---
+--- Examples:
+--- - "foo/", "/bar" => "foo/bar"
+--- - Windows: "a\foo\", "\bar" => "a/foo/bar"
 ---
 ---@since 12
 ---@param ... string
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index dbe3d54c2f..f9c497337f 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -1,3 +1,6 @@
+--- @brief This Lua |treesitter-query| interface allows you to create queries and use them to parse
+--- text. See |vim.treesitter.query.parse()| for a working example.
+
 local api = vim.api
 local language = require('vim.treesitter.language')
 local memoize = vim.func._memoize
@@ -8,9 +11,9 @@ local M = {}
 ---Parsed query, see |vim.treesitter.query.parse()|
 ---
 ---@class vim.treesitter.Query
----@field lang string name of the language for this parser
+---@field lang string parser language name
 ---@field captures string[] list of (unique) capture names defined in query
----@field info vim.treesitter.QueryInfo contains information used in the query (e.g. captures, predicates, directives)
+---@field info vim.treesitter.QueryInfo query context (e.g. captures, predicates, directives)
 ---@field query TSQuery userdata query object
 local Query = {}
 Query.__index = Query
@@ -228,20 +231,28 @@ M.get = memoize('concat-2', function(lang, query_name)
   return M.parse(lang, query_string)
 end)
 
---- Parse {query} as a string. (If the query is in a file, the caller
---- should read the contents into a string before calling).
----
---- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
---- search nodes in the syntax tree for the patterns defined in {query}
---- using the `iter_captures` and `iter_matches` methods.
+--- Parses a {query} string and returns a `Query` object (|lua-treesitter-query|), which can be used
+--- to search the tree for the query patterns (via |Query:iter_captures()|, |Query:iter_matches()|),
+--- or inspect the query via these fields:
+---   - `captures`: a list of unique capture names defined in the query (alias: `info.captures`).
+---   - `info.patterns`: information about predicates.
 ---
---- Exposes `info` and `captures` with additional context about {query}.
----   - `captures` contains the list of unique capture names defined in {query}.
----   - `info.captures` also points to `captures`.
----   - `info.patterns` contains information about predicates.
+--- Example (select the code then run `:'<,'>lua` to try it):
+--- ```lua
+--- local query = vim.treesitter.query.parse('vimdoc', [[
+---   ; query
+---   ((h1) @str
+---     (#trim! @str 1 1 1 1))
+--- ]])
+--- local tree = vim.treesitter.get_parser():parse()[1]
+--- for id, node, metadata in query:iter_captures(tree:root(), 0) do
+---    -- Print the node name and source text.
+---    vim.print({node:type(), vim.treesitter.get_node_text(node, vim.api.nvim_get_current_buf())})
+--- end
+--- ```
 ---
 ---@param lang string Language to use for the query
----@param query string Query in s-expr syntax
+---@param query string Query text, in s-expr syntax
 ---
 ---@return vim.treesitter.Query : Parsed query
 ---
@@ -847,20 +858,22 @@ local function match_id_hash(_, match)
   return (match:info())
 end
 
---- Iterate over all captures from all matches inside {node}
+--- Iterates over all captures from all matches in {node}.
 ---
---- {source} is needed if the query contains predicates; then the caller
+--- {source} is required if the query contains predicates; then the caller
 --- must ensure to use a freshly parsed tree consistent with the current
 --- text of the buffer (if relevant). {start} and {stop} can be used to limit
 --- matches inside a row range (this is typically used with root node
 --- as the {node}, i.e., to get syntax highlight matches in the current
 --- viewport). When omitted, the {start} and {stop} row values are used from the given node.
 ---
---- The iterator returns four values: a numeric id identifying the capture,
---- the captured node, metadata from any directives processing the match,
---- and the match itself.
---- The following example shows how to get captures by name:
+--- The iterator returns four values:
+--- 1. the numeric id identifying the capture
+--- 2. the captured node
+--- 3. metadata from any directives processing the match
+--- 4. the match itself
 ---
+--- Example: how to get captures by name:
 --- ```lua
 --- for id, node, metadata, match in query:iter_captures(tree:root(), bufnr, first, last) do
 ---   local name = query.captures[id] -- name of the capture in the query
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 3c306e6824..140fe66961 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -469,7 +469,7 @@ describe('vim.fs', function()
     end)
   end)
 
-  describe('abspath', function()
+  describe('abspath()', function()
     local cwd = is_os('win') and vim.uv.cwd():gsub('\\', '/') or vim.uv.cwd()
     local home = is_os('win') and vim.uv.os_homedir():gsub('\\', '/') or vim.uv.os_homedir()
 
diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua
index 8df42fcaa1..e4a1df1d4c 100644
--- a/test/functional/lua/json_spec.lua
+++ b/test/functional/lua/json_spec.lua
@@ -152,7 +152,7 @@ describe('vim.json.encode()', function()
     clear()
   end)
 
-  it('dumps strings with & without escaped slash', function()
+  it('escape_slash', function()
     -- With slash
     eq('"Test\\/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = true })]]))
     eq(
-- 
cgit 


From 6dc0eb9f41e6453fe003dd3a28c58b701fd003c9 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Wed, 1 Jan 2025 13:13:40 +0100
Subject: fix(vim.fs.abspath): correctly handle UNC paths

---
 runtime/lua/vim/fs.lua          | 2 +-
 test/functional/lua/fs_spec.lua | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index d042df96e5..ee713e4b58 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -725,7 +725,7 @@ function M.abspath(path)
     prefix, path = split_windows_path(path)
   end
 
-  if vim.startswith(path, '/') then
+  if prefix == '//' or vim.startswith(path, '/') then
     -- Path is already absolute, do nothing
     return prefix .. path
   end
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 140fe66961..1c6ff5ac6d 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -361,8 +361,8 @@ describe('vim.fs', function()
     end)
 
     -- Opts required for testing posix paths and win paths
-    local posix_opts = is_os('win') and { win = false } or {}
-    local win_opts = is_os('win') and {} or { win = true }
+    local posix_opts = { win = false }
+    local win_opts = { win = true }
 
     it('preserves leading double slashes in POSIX paths', function()
       eq('//foo', vim.fs.normalize('//foo', posix_opts))
@@ -483,6 +483,7 @@ describe('vim.fs', function()
       if is_os('win') then
         eq([[C:/foo]], vim.fs.abspath([[C:\foo]]))
         eq([[C:/foo/../.]], vim.fs.abspath([[C:\foo\..\.]]))
+        eq('//foo/bar', vim.fs.abspath('\\\\foo\\bar'))
       else
         eq('/foo/../.', vim.fs.abspath('/foo/../.'))
         eq('/foo/bar', vim.fs.abspath('/foo/bar'))
-- 
cgit 


From efe1732c6fd802ca8fdccc1f0a26be87427f1e70 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Wed, 1 Jan 2025 18:43:16 -0600
Subject: fix(jobs): do not block UI when jobwait() doesn't block (#31803)

---
 src/nvim/eval/funcs.c             | 13 ++++++++++---
 test/functional/core/job_spec.lua | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index a0c18a4c95..fbaa6e679f 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -4177,8 +4177,6 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
     return;
   }
 
-  ui_busy_start();
-  ui_flush();
   list_T *args = argvars[0].vval.v_list;
   Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs));
   MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
@@ -4215,6 +4213,13 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
     before = os_hrtime();
   }
 
+  // Only mark the UI as busy when jobwait() blocks
+  const bool busy = remaining != 0;
+  if (busy) {
+    ui_busy_start();
+    ui_flush();
+  }
+
   for (i = 0; i < tv_list_len(args); i++) {
     if (remaining == 0) {
       break;  // Timeout.
@@ -4256,7 +4261,9 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
 
   multiqueue_free(waiting_jobs);
   xfree(jobs);
-  ui_busy_stop();
+  if (busy) {
+    ui_busy_stop();
+  }
   tv_list_ref(rv);
   rettv->v_type = VAR_LIST;
   rettv->vval.v_list = rv;
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 952437d80e..e833b5127d 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -973,6 +973,39 @@ describe('jobs', function()
       feed('')
       fn.jobstop(api.nvim_get_var('id'))
     end)
+
+    it('does not set UI busy with zero timeout #31712', function()
+      local screen = Screen.new(50, 6)
+      command([[let g:id = jobstart(['sleep', '0.3'])]])
+      local busy = 0
+      screen._handle_busy_start = (function(orig)
+        return function()
+          orig(screen)
+          busy = busy + 1
+        end
+      end)(screen._handle_busy_start)
+      source([[
+        func PrintAndPoll()
+          echon "aaa\nbbb"
+          call jobwait([g:id], 0)
+          echon "\nccc"
+        endfunc
+      ]])
+      feed_command('call PrintAndPoll()')
+      screen:expect {
+        grid = [[
+                                                          |
+        {3:                                                  }|
+        aaa                                               |
+        bbb                                               |
+        ccc                                               |
+        {6:Press ENTER or type command to continue}^           |
+      ]],
+      }
+      feed('')
+      fn.jobstop(api.nvim_get_var('id'))
+      eq(0, busy)
+    end)
   end)
 
   pending('exit event follows stdout, stderr', function()
-- 
cgit 


From 9d9ee3476e6478850ce8822c85154f0c98570371 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Thu, 2 Jan 2025 17:33:16 +0900
Subject: fix(lsp): ensure watcher cancel

* Cancel watcher in the "force" case.
* Cancel watcher outside the async callback. It seems nvim doesn't wait
  async jobs on quitting, leaving detached inotifywait processes.
* Clean up cancelling callbacks.
---
 runtime/lua/vim/lsp/_watchfiles.lua | 1 +
 runtime/lua/vim/lsp/client.lua      | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 248969885c..4711b3cc9b 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -174,6 +174,7 @@ function M.cancel(client_id)
       cancel()
     end
   end
+  cancels[client_id] = nil
 end
 
 return M
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index d51a45b473..5d11312c77 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -805,6 +805,8 @@ function Client:stop(force)
     return
   end
 
+  vim.lsp._watchfiles.cancel(self.id)
+
   if force or not self.initialized or self._graceful_shutdown_failed then
     rpc.terminate()
     return
@@ -819,7 +821,6 @@ function Client:stop(force)
       rpc.terminate()
       self._graceful_shutdown_failed = true
     end
-    vim.lsp._watchfiles.cancel(self.id)
   end)
 end
 
-- 
cgit 


From e3bfcf2fd4a4ebf00b104b082cfe83c8144a842d Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Wed, 18 Dec 2024 14:49:38 +0100
Subject: feat(terminal): support grapheme clusters, including emoji

---
 runtime/doc/news.txt                     |   3 +
 src/nvim/terminal.c                      |  13 +-
 src/vterm/fullwidth.inc                  | 111 -----------
 src/vterm/screen.c                       |  78 +++-----
 src/vterm/state.c                        | 138 ++++----------
 src/vterm/unicode.c                      | 313 -------------------------------
 src/vterm/vterm.h                        |  10 +-
 src/vterm/vterm_internal.h               |  10 +-
 test/functional/terminal/buffer_spec.lua |  19 +-
 test/unit/fixtures/vterm_test.c          |  37 +++-
 test/unit/vterm_spec.lua                 |  83 +++++---
 11 files changed, 184 insertions(+), 631 deletions(-)
 delete mode 100644 src/vterm/fullwidth.inc
 delete mode 100644 src/vterm/unicode.c

diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 9ab0a01b99..b88ae129b3 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -370,6 +370,9 @@ These existing features changed their behavior.
   more emoji characters than before, including those encoded with multiple
   emoji codepoints combined with ZWJ (zero width joiner) codepoints.
 
+  This also applies to :terminal output, where width of cells will be calculated
+  using the upgraded implementation.
+
 • Custom highlights in 'rulerformat', 'statuscolumn', 'statusline', 'tabline',
   'winbar' and the number column (through |:sign-define| `numhl`) now combine
   with their respective highlight groups, as opposed to |hl-Normal|.
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 673a6ef4be..0743eda374 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -65,6 +65,7 @@
 #include "nvim/ex_docmd.h"
 #include "nvim/getchar.h"
 #include "nvim/globals.h"
+#include "nvim/grid.h"
 #include "nvim/highlight.h"
 #include "nvim/highlight_defs.h"
 #include "nvim/highlight_group.h"
@@ -1347,7 +1348,7 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
   // copy to vterm state
   memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
   for (size_t col = cols_to_copy; col < (size_t)cols; col++) {
-    cells[col].chars[0] = 0;
+    cells[col].schar = 0;
     cells[col].width = 1;
   }
 
@@ -1857,12 +1858,8 @@ static void fetch_row(Terminal *term, int row, int end_col)
   while (col < end_col) {
     VTermScreenCell cell;
     fetch_cell(term, row, col, &cell);
-    if (cell.chars[0]) {
-      int cell_len = 0;
-      for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell.chars[i]; i++) {
-        cell_len += utf_char2bytes((int)cell.chars[i], ptr + cell_len);
-      }
-      ptr += cell_len;
+    if (cell.schar) {
+      schar_get_adv(&ptr, cell.schar);
       line_len = (size_t)(ptr - term->textbuf);
     } else {
       *ptr++ = ' ';
@@ -1883,7 +1880,7 @@ static bool fetch_cell(Terminal *term, int row, int col, VTermScreenCell *cell)
     } else {
       // fill the pointer with an empty cell
       *cell = (VTermScreenCell) {
-        .chars = { 0 },
+        .schar = 0,
         .width = 1,
       };
       return false;
diff --git a/src/vterm/fullwidth.inc b/src/vterm/fullwidth.inc
deleted file mode 100644
index a703529a76..0000000000
--- a/src/vterm/fullwidth.inc
+++ /dev/null
@@ -1,111 +0,0 @@
-  { 0x1100, 0x115f },
-  { 0x231a, 0x231b },
-  { 0x2329, 0x232a },
-  { 0x23e9, 0x23ec },
-  { 0x23f0, 0x23f0 },
-  { 0x23f3, 0x23f3 },
-  { 0x25fd, 0x25fe },
-  { 0x2614, 0x2615 },
-  { 0x2648, 0x2653 },
-  { 0x267f, 0x267f },
-  { 0x2693, 0x2693 },
-  { 0x26a1, 0x26a1 },
-  { 0x26aa, 0x26ab },
-  { 0x26bd, 0x26be },
-  { 0x26c4, 0x26c5 },
-  { 0x26ce, 0x26ce },
-  { 0x26d4, 0x26d4 },
-  { 0x26ea, 0x26ea },
-  { 0x26f2, 0x26f3 },
-  { 0x26f5, 0x26f5 },
-  { 0x26fa, 0x26fa },
-  { 0x26fd, 0x26fd },
-  { 0x2705, 0x2705 },
-  { 0x270a, 0x270b },
-  { 0x2728, 0x2728 },
-  { 0x274c, 0x274c },
-  { 0x274e, 0x274e },
-  { 0x2753, 0x2755 },
-  { 0x2757, 0x2757 },
-  { 0x2795, 0x2797 },
-  { 0x27b0, 0x27b0 },
-  { 0x27bf, 0x27bf },
-  { 0x2b1b, 0x2b1c },
-  { 0x2b50, 0x2b50 },
-  { 0x2b55, 0x2b55 },
-  { 0x2e80, 0x2e99 },
-  { 0x2e9b, 0x2ef3 },
-  { 0x2f00, 0x2fd5 },
-  { 0x2ff0, 0x2ffb },
-  { 0x3000, 0x303e },
-  { 0x3041, 0x3096 },
-  { 0x3099, 0x30ff },
-  { 0x3105, 0x312f },
-  { 0x3131, 0x318e },
-  { 0x3190, 0x31ba },
-  { 0x31c0, 0x31e3 },
-  { 0x31f0, 0x321e },
-  { 0x3220, 0x3247 },
-  { 0x3250, 0x4dbf },
-  { 0x4e00, 0xa48c },
-  { 0xa490, 0xa4c6 },
-  { 0xa960, 0xa97c },
-  { 0xac00, 0xd7a3 },
-  { 0xf900, 0xfaff },
-  { 0xfe10, 0xfe19 },
-  { 0xfe30, 0xfe52 },
-  { 0xfe54, 0xfe66 },
-  { 0xfe68, 0xfe6b },
-  { 0xff01, 0xff60 },
-  { 0xffe0, 0xffe6 },
-  { 0x16fe0, 0x16fe3 },
-  { 0x17000, 0x187f7 },
-  { 0x18800, 0x18af2 },
-  { 0x1b000, 0x1b11e },
-  { 0x1b150, 0x1b152 },
-  { 0x1b164, 0x1b167 },
-  { 0x1b170, 0x1b2fb },
-  { 0x1f004, 0x1f004 },
-  { 0x1f0cf, 0x1f0cf },
-  { 0x1f18e, 0x1f18e },
-  { 0x1f191, 0x1f19a },
-  { 0x1f200, 0x1f202 },
-  { 0x1f210, 0x1f23b },
-  { 0x1f240, 0x1f248 },
-  { 0x1f250, 0x1f251 },
-  { 0x1f260, 0x1f265 },
-  { 0x1f300, 0x1f320 },
-  { 0x1f32d, 0x1f335 },
-  { 0x1f337, 0x1f37c },
-  { 0x1f37e, 0x1f393 },
-  { 0x1f3a0, 0x1f3ca },
-  { 0x1f3cf, 0x1f3d3 },
-  { 0x1f3e0, 0x1f3f0 },
-  { 0x1f3f4, 0x1f3f4 },
-  { 0x1f3f8, 0x1f43e },
-  { 0x1f440, 0x1f440 },
-  { 0x1f442, 0x1f4fc },
-  { 0x1f4ff, 0x1f53d },
-  { 0x1f54b, 0x1f54e },
-  { 0x1f550, 0x1f567 },
-  { 0x1f57a, 0x1f57a },
-  { 0x1f595, 0x1f596 },
-  { 0x1f5a4, 0x1f5a4 },
-  { 0x1f5fb, 0x1f64f },
-  { 0x1f680, 0x1f6c5 },
-  { 0x1f6cc, 0x1f6cc },
-  { 0x1f6d0, 0x1f6d2 },
-  { 0x1f6d5, 0x1f6d5 },
-  { 0x1f6eb, 0x1f6ec },
-  { 0x1f6f4, 0x1f6fa },
-  { 0x1f7e0, 0x1f7eb },
-  { 0x1f90d, 0x1f971 },
-  { 0x1f973, 0x1f976 },
-  { 0x1f97a, 0x1f9a2 },
-  { 0x1f9a5, 0x1f9aa },
-  { 0x1f9ae, 0x1f9ca },
-  { 0x1f9cd, 0x1f9ff },
-  { 0x1fa70, 0x1fa73 },
-  { 0x1fa78, 0x1fa7a },
-  { 0x1fa80, 0x1fa82 },
-  { 0x1fa90, 0x1fa95 },
diff --git a/src/vterm/screen.c b/src/vterm/screen.c
index 1f5bb36114..7de345ca39 100644
--- a/src/vterm/screen.c
+++ b/src/vterm/screen.c
@@ -2,6 +2,7 @@
 
 #include 
 #include 
+#include "nvim/grid.h"
 #include "nvim/mbyte.h"
 #include "nvim/tui/termkey/termkey.h"
 
@@ -41,7 +42,7 @@ typedef struct
 /* Internal representation of a screen cell */
 typedef struct
 {
-  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+  schar_T schar;
   ScreenPen pen;
 } ScreenCell;
 
@@ -79,7 +80,7 @@ struct VTermScreen
 
 static inline void clearcell(const VTermScreen *screen, ScreenCell *cell)
 {
-  cell->chars[0] = 0;
+  cell->schar = 0;
   cell->pen = screen->pen;
 }
 
@@ -182,16 +183,13 @@ static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
   if(!cell)
     return 0;
 
-  int i;
-  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
-    cell->chars[i] = info->chars[i];
+  cell->schar = info->schar;
+  if (info->schar != 0) {
     cell->pen = screen->pen;
   }
-  if(i < VTERM_MAX_CHARS_PER_CELL)
-    cell->chars[i] = 0;
 
   for(int col = 1; col < info->width; col++)
-    getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
+    getcell(screen, pos.row, pos.col + col)->schar = (uint32_t)-1;
 
   VTermRect rect = {
     .start_row = pos.row,
@@ -284,7 +282,7 @@ static int erase_internal(VTermRect rect, int selective, void *user)
       if(selective && cell->pen.protected_cell)
         continue;
 
-      cell->chars[0] = 0;
+      cell->schar = 0;
       cell->pen = (ScreenPen){
         /* Only copy .fg and .bg; leave things like rv in reset state */
         .fg = screen->pen.fg,
@@ -504,7 +502,7 @@ static int bell(void *user)
 static int line_popcount(ScreenCell *buffer, int row, int rows, int cols)
 {
   int col = cols - 1;
-  while(col >= 0 && buffer[row * cols + col].chars[0] == 0)
+  while(col >= 0 && buffer[row * cols + col].schar == 0)
     col--;
   return col + 1;
 }
@@ -690,11 +688,7 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new
         VTermScreenCell *src = &screen->sb_buffer[pos.col];
         ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];
 
-        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
-          dst->chars[i] = src->chars[i];
-          if(!src->chars[i])
-            break;
-        }
+        dst->schar = src->schar;
 
         dst->pen.bold      = src->attrs.bold;
         dst->pen.underline = src->attrs.underline;
@@ -713,7 +707,7 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new
         dst->pen.uri = src->uri;
 
         if(src->width == 2 && pos.col < (new_cols-1))
-          (dst + 1)->chars[0] = (uint32_t) -1;
+          (dst + 1)->schar = (uint32_t) -1;
       }
       for( ; pos.col < new_cols; pos.col++)
         clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]);
@@ -914,49 +908,41 @@ void vterm_screen_reset(VTermScreen *screen, int hard)
   vterm_screen_flush_damage(screen);
 }
 
-static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
+size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, const VTermRect rect)
 {
   size_t outpos = 0;
   int padding = 0;
 
-#define PUT(c)                                             \
-  if(utf8) {                                               \
-    size_t thislen = utf_char2len(c);                      \
+#define PUT(bytes, thislen)                                \
+  if(true) {                                               \
     if(buffer && outpos + thislen <= len)                  \
-      outpos += fill_utf8((c), (char *)buffer + outpos);   \
-    else                                                   \
-      outpos += thislen;                                   \
+      memcpy((char *)buffer + outpos, bytes, thislen);     \
+    outpos += thislen;                                     \
   }                                                        \
-  else {                                                   \
-    if(buffer && outpos + 1 <= len)                        \
-      ((uint32_t*)buffer)[outpos++] = (c);                 \
-    else                                                   \
-      outpos++;                                            \
-  }
 
   for(int row = rect.start_row; row < rect.end_row; row++) {
     for(int col = rect.start_col; col < rect.end_col; col++) {
       ScreenCell *cell = getcell(screen, row, col);
 
-      if(cell->chars[0] == 0)
+      if(cell->schar == 0)
         // Erased cell, might need a space
         padding++;
-      else if(cell->chars[0] == (uint32_t)-1)
+      else if(cell->schar == (uint32_t)-1)
         // Gap behind a double-width char, do nothing
         ;
       else {
         while(padding) {
-          PUT(UNICODE_SPACE);
+          PUT(" ", 1);
           padding--;
         }
-        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
-          PUT(cell->chars[i]);
-        }
+        char buf[MAX_SCHAR_SIZE + 1];
+        size_t thislen = schar_get(buf, cell->schar);
+        PUT(buf, thislen);
       }
     }
 
     if(row < rect.end_row - 1) {
-      PUT(UNICODE_LINEFEED);
+      PUT("\n", 1);
       padding = 0;
     }
   }
@@ -964,16 +950,6 @@ static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer
   return outpos;
 }
 
-size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
-{
-  return _get_chars(screen, 0, chars, len, rect);
-}
-
-size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
-{
-  return _get_chars(screen, 1, str, len, rect);
-}
-
 /* Copy internal to external representation of a screen cell */
 int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
 {
@@ -981,11 +957,7 @@ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCe
   if(!intcell)
     return 0;
 
-  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
-    cell->chars[i] = intcell->chars[i];
-    if(!intcell->chars[i])
-      break;
-  }
+  cell->schar = intcell->schar;
 
   cell->attrs.bold      = intcell->pen.bold;
   cell->attrs.underline = intcell->pen.underline;
@@ -1007,7 +979,7 @@ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCe
   cell->uri = intcell->pen.uri;
 
   if(pos.col < (screen->cols - 1) &&
-     getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
+     getcell(screen, pos.row, pos.col + 1)->schar == (uint32_t)-1)
     cell->width = 2;
   else
     cell->width = 1;
@@ -1020,7 +992,7 @@ int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
   /* This cell is EOL if this and every cell to the right is black */
   for(; pos.col < screen->cols; pos.col++) {
     ScreenCell *cell = getcell(screen, pos.row, pos.col);
-    if(cell->chars[0] != 0)
+    if(cell->schar != 0)
       return 0;
   }
 
diff --git a/src/vterm/state.c b/src/vterm/state.c
index e09b39436a..d546672e67 100644
--- a/src/vterm/state.c
+++ b/src/vterm/state.c
@@ -3,6 +3,9 @@
 #include 
 #include 
 
+#include "nvim/grid.h"
+#include "nvim/mbyte.h"
+
 #define strneq(a,b,n) (strncmp(a,b,n)==0)
 
 #if defined(DEBUG) && DEBUG > 1
@@ -11,10 +14,10 @@
 
 /* Some convenient wrappers to make callback functions easier */
 
-static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
+static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos)
 {
   VTermGlyphInfo info = {
-    .chars = chars,
+    .schar = schar,
     .width = width,
     .protected_cell = state->protected_cell,
     .dwl = state->lineinfo[pos.row].doublewidth,
@@ -82,8 +85,7 @@ static VTermState *vterm_state_new(VTerm *vt)
 
   state->bold_is_highbright = 0;
 
-  state->combine_chars_size = 16;
-  state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
+  state->combine_pos.row = -1;
 
   state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
 
@@ -105,7 +107,6 @@ INTERNAL void vterm_state_free(VTermState *state)
   vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
   if(state->lineinfos[BUFIDX_ALTSCREEN])
     vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
-  vterm_allocator_free(state->vt, state->combine_chars);
   vterm_allocator_free(state->vt, state);
 }
 
@@ -171,19 +172,6 @@ static void linefeed(VTermState *state)
     state->pos.row++;
 }
 
-static void grow_combine_buffer(VTermState *state)
-{
-  size_t    new_size = state->combine_chars_size * 2;
-  uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
-
-  memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
-
-  vterm_allocator_free(state->vt, state->combine_chars);
-
-  state->combine_chars = new_chars;
-  state->combine_chars_size = new_size;
-}
-
 static void set_col_tabstop(VTermState *state, int col)
 {
   unsigned char mask = 1 << (col & 7);
@@ -301,88 +289,35 @@ static int on_text(const char bytes[], size_t len, void *user)
     state->gsingle_set = 0;
 
   int i = 0;
+  GraphemeState grapheme_state = GRAPHEME_STATE_INIT;
+  size_t grapheme_len = 0;
+  bool recombine = false;
 
+  /* See if the cursor has moved since */
+  if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
   /* This is a combining char. that needs to be merged with the previous
    * glyph output */
-  if(vterm_unicode_is_combining(codepoints[i])) {
-    /* See if the cursor has moved since */
-    if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
-#ifdef DEBUG_GLYPH_COMBINE
-      int printpos;
-      printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
-      for(printpos = 0; state->combine_chars[printpos]; printpos++)
-        printf("U+%04x ", state->combine_chars[printpos]);
-      printf("} + {");
-#endif
-
+    if(utf_iscomposing(state->grapheme_last, codepoints[i], &state->grapheme_state)) {
       /* Find where we need to append these combining chars */
-      int saved_i = 0;
-      while(state->combine_chars[saved_i])
-        saved_i++;
-
-      /* Add extra ones */
-      while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
-        if(saved_i >= state->combine_chars_size)
-          grow_combine_buffer(state);
-        state->combine_chars[saved_i++] = codepoints[i++];
-      }
-      if(saved_i >= state->combine_chars_size)
-        grow_combine_buffer(state);
-      state->combine_chars[saved_i] = 0;
-
-#ifdef DEBUG_GLYPH_COMBINE
-      for(; state->combine_chars[printpos]; printpos++)
-        printf("U+%04x ", state->combine_chars[printpos]);
-      printf("}\n");
-#endif
-
-      /* Now render it */
-      putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
-    }
-    else {
+      grapheme_len = state->grapheme_len;
+      grapheme_state = state->grapheme_state;
+      state->pos.col = state->combine_pos.col;
+      recombine = true;
+    } else {
       DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
     }
   }
 
-  for(; i < npoints; i++) {
+  while(i < npoints) {
     // Try to find combining characters following this
-    int glyph_starts = i;
-    int glyph_ends;
-    for(glyph_ends = i + 1;
-        (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL);
-        glyph_ends++)
-      if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
-        break;
-
-    int width = 0;
-
-    uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1];
-
-    for( ; i < glyph_ends; i++) {
-      chars[i - glyph_starts] = codepoints[i];
-      int this_width = vterm_unicode_width(codepoints[i]);
-#ifdef DEBUG
-      if(this_width < 0) {
-        fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
-        abort();
+    do {
+      if (grapheme_len < sizeof(state->grapheme_buf) - 4) {
+        grapheme_len += utf_char2bytes(codepoints[i], state->grapheme_buf + grapheme_len);
       }
-#endif
-      width += this_width;
-    }
-
-    while(i < npoints && vterm_unicode_is_combining(codepoints[i]))
       i++;
+    } while(i < npoints && utf_iscomposing(codepoints[i-1], codepoints[i], &grapheme_state));
 
-    chars[glyph_ends - glyph_starts] = 0;
-    i--;
-
-#ifdef DEBUG_GLYPH_COMBINE
-    int printpos;
-    printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
-    for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
-      printf("U+%04x ", chars[printpos]);
-    printf("}, onscreen width %d\n", width);
-#endif
+    int width = utf_ptr2cells_len(state->grapheme_buf, grapheme_len);
 
     if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
       linefeed(state);
@@ -391,7 +326,7 @@ static int on_text(const char bytes[], size_t len, void *user)
       state->lineinfo[state->pos.row].continuation = 1;
     }
 
-    if(state->mode.insert) {
+    if(state->mode.insert && !recombine) {
       /* TODO: This will be a little inefficient for large bodies of text, as
        * it'll have to 'ICH' effectively before every glyph. We should scan
        * ahead and ICH as many times as required
@@ -405,22 +340,20 @@ static int on_text(const char bytes[], size_t len, void *user)
       scroll(state, rect, 0, -1);
     }
 
-    putglyph(state, chars, width, state->pos);
+    schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len);
+    putglyph(state, sc, width, state->pos);
 
-    if(i == npoints - 1) {
+    if(i == npoints) {
       /* End of the buffer. Save the chars in case we have to combine with
        * more on the next call */
-      int save_i;
-      for(save_i = 0; chars[save_i]; save_i++) {
-        if(save_i >= state->combine_chars_size)
-          grow_combine_buffer(state);
-        state->combine_chars[save_i] = chars[save_i];
-      }
-      if(save_i >= state->combine_chars_size)
-        grow_combine_buffer(state);
-      state->combine_chars[save_i] = 0;
+      state->grapheme_len = grapheme_len;
+      state->grapheme_last = codepoints[i-1];
+      state->grapheme_state = grapheme_state;
       state->combine_width = width;
       state->combine_pos = state->pos;
+    } else {
+      grapheme_len = 0;
+      recombine = false;
     }
 
     if(state->pos.col + width >= THISROWWIDTH(state)) {
@@ -646,7 +579,7 @@ static int on_escape(const char *bytes, size_t len, void *user)
       case '8': // DECALN
       {
         VTermPos pos;
-        uint32_t E[] = { 'E', 0 };
+        schar_T E = schar_from_ascii('E');  // E
         for(pos.row = 0; pos.row < state->rows; pos.row++)
           for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
             putglyph(state, E, 1, pos);
@@ -1234,8 +1167,9 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha
     count = CSI_ARG_COUNT(args[0]);
     col = state->pos.col + count;
     UBOUND(col, row_width);
+    schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len);
     while (state->pos.col < col) {
-      putglyph(state, state->combine_chars, state->combine_width, state->pos);
+      putglyph(state, sc, state->combine_width, state->pos);
       state->pos.col += state->combine_width;
     }
     if (state->pos.col + state->combine_width >= row_width) {
diff --git a/src/vterm/unicode.c b/src/vterm/unicode.c
deleted file mode 100644
index 67a155c19f..0000000000
--- a/src/vterm/unicode.c
+++ /dev/null
@@ -1,313 +0,0 @@
-#include "vterm_internal.h"
-
-// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
-// With modifications:
-//   made functions static
-//   moved 'combining' table to file scope, so other functions can see it
-// ###################################################################
-
-/*
- * This is an implementation of wcwidth() and wcswidth() (defined in
- * IEEE Std 1002.1-2001) for Unicode.
- *
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
- *
- * In fixed-width output devices, Latin characters all occupy a single
- * "cell" position of equal width, whereas ideographic CJK characters
- * occupy two such cells. Interoperability between terminal-line
- * applications and (teletype-style) character terminals using the
- * UTF-8 encoding requires agreement on which character should advance
- * the cursor by how many cell positions. No established formal
- * standards exist at present on which Unicode character shall occupy
- * how many cell positions on character terminals. These routines are
- * a first attempt of defining such behavior based on simple rules
- * applied to data provided by the Unicode Consortium.
- *
- * For some graphical characters, the Unicode standard explicitly
- * defines a character-cell width via the definition of the East Asian
- * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
- * In all these cases, there is no ambiguity about which width a
- * terminal shall use. For characters in the East Asian Ambiguous (A)
- * class, the width choice depends purely on a preference of backward
- * compatibility with either historic CJK or Western practice.
- * Choosing single-width for these characters is easy to justify as
- * the appropriate long-term solution, as the CJK practice of
- * displaying these characters as double-width comes from historic
- * implementation simplicity (8-bit encoded characters were displayed
- * single-width and 16-bit ones double-width, even for Greek,
- * Cyrillic, etc.) and not any typographic considerations.
- *
- * Much less clear is the choice of width for the Not East Asian
- * (Neutral) class. Existing practice does not dictate a width for any
- * of these characters. It would nevertheless make sense
- * typographically to allocate two character cells to characters such
- * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
- * represented adequately with a single-width glyph. The following
- * routines at present merely assign a single-cell width to all
- * neutral characters, in the interest of simplicity. This is not
- * entirely satisfactory and should be reconsidered before
- * establishing a formal standard in this area. At the moment, the
- * decision which Not East Asian (Neutral) characters should be
- * represented by double-width glyphs cannot yet be answered by
- * applying a simple rule from the Unicode database content. Setting
- * up a proper standard for the behavior of UTF-8 character terminals
- * will require a careful analysis not only of each Unicode character,
- * but also of each presentation form, something the author of these
- * routines has avoided to do so far.
- *
- * http://www.unicode.org/unicode/reports/tr11/
- *
- * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
- *
- * Permission to use, copy, modify, and distribute this software
- * for any purpose and without fee is hereby granted. The author
- * disclaims all warranties with regard to this software.
- *
- * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
- */
-
-struct interval {
-  int first;
-  int last;
-};
-
-/* sorted list of non-overlapping intervals of non-spacing characters */
-/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
-static const struct interval combining[] = {
-  { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
-  { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-  { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
-  { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
-  { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
-  { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
-  { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
-  { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
-  { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
-  { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
-  { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
-  { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
-  { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
-  { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
-  { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
-  { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
-  { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
-  { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
-  { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
-  { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
-  { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
-  { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
-  { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
-  { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
-  { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
-  { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
-  { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
-  { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
-  { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
-  { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
-  { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
-  { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
-  { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
-  { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
-  { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
-  { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
-  { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
-  { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
-  { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
-  { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
-  { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
-  { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
-  { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
-  { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
-  { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
-  { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
-  { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
-  { 0xE0100, 0xE01EF }
-};
-
-
-/* auxiliary function for binary search in interval table */
-static int bisearch(uint32_t ucs, const struct interval *table, int max) {
-  int min = 0;
-  int mid;
-
-  if (ucs < (uint32_t) table[0].first || ucs > (uint32_t) table[max].last)
-    return 0;
-  while (max >= min) {
-    mid = (min + max) / 2;
-    if (ucs > (uint32_t) table[mid].last)
-      min = mid + 1;
-    else if (ucs < (uint32_t) table[mid].first)
-      max = mid - 1;
-    else
-      return 1;
-  }
-
-  return 0;
-}
-
-
-/* The following two functions define the column width of an ISO 10646
- * character as follows:
- *
- *    - The null character (U+0000) has a column width of 0.
- *
- *    - Other C0/C1 control characters and DEL will lead to a return
- *      value of -1.
- *
- *    - Non-spacing and enclosing combining characters (general
- *      category code Mn or Me in the Unicode database) have a
- *      column width of 0.
- *
- *    - SOFT HYPHEN (U+00AD) has a column width of 1.
- *
- *    - Other format characters (general category code Cf in the Unicode
- *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
- *
- *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
- *      have a column width of 0.
- *
- *    - Spacing characters in the East Asian Wide (W) or East Asian
- *      Full-width (F) category as defined in Unicode Technical
- *      Report #11 have a column width of 2.
- *
- *    - All remaining characters (including all printable
- *      ISO 8859-1 and WGL4 characters, Unicode control characters,
- *      etc.) have a column width of 1.
- *
- * This implementation assumes that uint32_t characters are encoded
- * in ISO 10646.
- */
-
-
-static int mk_wcwidth(uint32_t ucs)
-{
-  /* test for 8-bit control characters */
-  if (ucs == 0)
-    return 0;
-  if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
-    return -1;
-
-  /* binary search in table of non-spacing characters */
-  if (bisearch(ucs, combining,
-               sizeof(combining) / sizeof(struct interval) - 1))
-    return 0;
-
-  /* if we arrive here, ucs is not a combining or C0/C1 control character */
-
-  return 1 + 
-    (ucs >= 0x1100 &&
-     (ucs <= 0x115f ||                    /* Hangul Jamo init. consonants */
-      ucs == 0x2329 || ucs == 0x232a ||
-      (ucs >= 0x2e80 && ucs <= 0xa4cf &&
-       ucs != 0x303f) ||                  /* CJK ... Yi */
-      (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
-      (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
-      (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
-      (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
-      (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
-      (ucs >= 0xffe0 && ucs <= 0xffe6) ||
-      (ucs >= 0x20000 && ucs <= 0x2fffd) ||
-      (ucs >= 0x30000 && ucs <= 0x3fffd)));
-}
-
-
-#ifdef USE_MK_WCWIDTH_CJK
-
-/*
- * The following functions are the same as mk_wcwidth() and
- * mk_wcswidth(), except that spacing characters in the East Asian
- * Ambiguous (A) category as defined in Unicode Technical Report #11
- * have a column width of 2. This variant might be useful for users of
- * CJK legacy encodings who want to migrate to UCS without changing
- * the traditional terminal character-width behaviour. It is not
- * otherwise recommended for general use.
- */
-static int mk_wcwidth_cjk(uint32_t ucs)
-{
-  /* sorted list of non-overlapping intervals of East Asian Ambiguous
-   * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
-  static const struct interval ambiguous[] = {
-    { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
-    { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
-    { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
-    { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
-    { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
-    { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
-    { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
-    { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
-    { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
-    { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
-    { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
-    { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
-    { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
-    { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
-    { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
-    { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
-    { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
-    { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
-    { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
-    { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
-    { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
-    { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
-    { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
-    { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
-    { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
-    { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
-    { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
-    { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
-    { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
-    { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
-    { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
-    { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
-    { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
-    { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
-    { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
-    { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
-    { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
-    { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
-    { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
-    { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
-    { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
-    { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
-    { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
-    { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
-    { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
-    { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
-    { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
-    { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
-    { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
-    { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
-    { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
-    { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
-  };
-
-  /* binary search in table of non-spacing characters */
-  if (bisearch(ucs, ambiguous,
-               sizeof(ambiguous) / sizeof(struct interval) - 1))
-    return 2;
-
-  return mk_wcwidth(ucs);
-}
-
-#endif
-
-// ################################
-// ### The rest added by Paul Evans
-
-static const struct interval fullwidth[] = {
-#include "fullwidth.inc"
-};
-
-INTERNAL int vterm_unicode_width(uint32_t codepoint)
-{
-  if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1))
-    return 2;
-
-  return mk_wcwidth(codepoint);
-}
-
-INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)
-{
-  return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
-}
diff --git a/src/vterm/vterm.h b/src/vterm/vterm.h
index 89fe2a58bb..4de1d885b8 100644
--- a/src/vterm/vterm.h
+++ b/src/vterm/vterm.h
@@ -10,6 +10,7 @@ extern "C" {
 #include 
 
 #include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
 #include "vterm_keycodes.h"
 
 #define VTERM_VERSION_MAJOR 0
@@ -19,11 +20,6 @@ extern "C" {
 #define VTERM_CHECK_VERSION \
         vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
 
-/* Any cell can contain at most one basic printing character and 5 combining
- * characters. This number could be changed but will be ABI-incompatible if
- * you do */
-enum{ VTERM_MAX_CHARS_PER_CELL=6};
-
 typedef struct VTerm VTerm;
 typedef struct VTermState VTermState;
 typedef struct VTermScreen VTermScreen;
@@ -292,7 +288,7 @@ typedef enum {
 } VTermSelectionMask;
 
 typedef struct {
-  const uint32_t *chars;
+  schar_T schar;
   int             width;
   unsigned int    protected_cell:1;  /* DECSCA-protected against DECSEL/DECSED */
   unsigned int    dwl:1;             /* DECDWL or DECDHL double-width line */
@@ -528,7 +524,7 @@ enum {
 };
 
 typedef struct {
-  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+  schar_T schar;
   char     width;
   VTermScreenCellAttrs attrs;
   VTermColor fg, bg;
diff --git a/src/vterm/vterm_internal.h b/src/vterm/vterm_internal.h
index 53f9b5e100..8f1722dd93 100644
--- a/src/vterm/vterm_internal.h
+++ b/src/vterm/vterm_internal.h
@@ -4,6 +4,7 @@
 #include "vterm.h"
 
 #include 
+#include "nvim/mbyte.h"
 
 #if defined(__GNUC__)
 # define INTERNAL __attribute__((visibility("internal")))
@@ -101,8 +102,10 @@ struct VTermState
   enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol;
 
   /* Last glyph output, for Unicode recombining purposes */
-  uint32_t *combine_chars;
-  size_t combine_chars_size; // Number of ELEMENTS in the above
+  char grapheme_buf[MAX_SCHAR_SIZE];
+  size_t grapheme_len;
+  uint32_t grapheme_last;  // last added UTF-32 char
+  GraphemeState grapheme_state;
   int combine_width; // The width of the glyph above
   VTermPos combine_pos;   // Position before movement
 
@@ -292,7 +295,4 @@ void vterm_screen_free(VTermScreen *screen);
 
 VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);
 
-int vterm_unicode_width(uint32_t codepoint);
-int vterm_unicode_is_combining(uint32_t codepoint);
-
 #endif
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index e209ed9025..b6de687af9 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -400,15 +400,28 @@ describe(':terminal buffer', function()
     assert_alive()
   end)
 
-  it('truncates number of composing characters to 5', function()
+  it('truncates the size of grapheme clusters', function()
     local chan = api.nvim_open_term(0, {})
     local composing = ('a̳'):sub(2)
-    api.nvim_chan_send(chan, 'a' .. composing:rep(8))
+    api.nvim_chan_send(chan, 'a' .. composing:rep(20))
     retry(nil, nil, function()
-      eq('a' .. composing:rep(5), api.nvim_get_current_line())
+      eq('a' .. composing:rep(14), api.nvim_get_current_line())
     end)
   end)
 
+  it('handles extended grapheme clusters', function()
+    local screen = Screen.new(50, 7)
+    feed 'i'
+    local chan = api.nvim_open_term(0, {})
+    api.nvim_chan_send(chan, '🏴‍☠️ yarrr')
+    screen:expect([[
+      🏴‍☠️ yarrr^                                          |
+                                                        |*5
+      {5:-- TERMINAL --}                                    |
+    ]])
+    eq('🏴‍☠️ yarrr', api.nvim_get_current_line())
+  end)
+
   it('handles split UTF-8 sequences #16245', function()
     local screen = Screen.new(50, 7)
     fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true })
diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c
index f227ae4591..8755e32e7a 100644
--- a/test/unit/fixtures/vterm_test.c
+++ b/test/unit/fixtures/vterm_test.c
@@ -1,4 +1,6 @@
 #include 
+#include "nvim/grid.h"
+#include "nvim/mbyte.h"
 
 #include "vterm_test.h"
 
@@ -202,6 +204,26 @@ int selection_query(VTermSelectionMask mask, void *user)
   return 1;
 }
 
+static void print_schar(FILE *f, schar_T schar) {
+  char buf[MAX_SCHAR_SIZE];
+  schar_get(buf, schar);
+  StrCharInfo ci = utf_ptr2StrCharInfo(buf);
+  bool did = false;
+  while (*ci.ptr != 0) {
+    if (did) {
+      fprintf(f, ",");
+    }
+
+    if (ci.chr.len == 1 && ci.chr.value >= 0x80) {
+      fprintf(f, "??%x", ci.chr.value);
+    } else {
+      fprintf(f, "%x", ci.chr.value);
+    }
+    did = true;
+    ci = utf_ptr2StrCharInfo(ci.ptr + ci.chr.len);
+  }
+}
+
 bool want_state_putglyph;
 int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
 {
@@ -211,9 +233,7 @@ int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "putglyph ");
-  for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
-    fprintf(f, i ? ",%x" : "%x", info->chars[i]);
-  }
+  print_schar(f, info->schar);
   fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
   if (info->protected_cell) {
     fprintf(f, " prot");
@@ -443,14 +463,15 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
   }
 
   int eol = cols;
-  while (eol && !cells[eol - 1].chars[0]) {
+  while (eol && !cells[eol-1].schar) {
     eol--;
   }
 
   FILE *f = fopen(VTERM_TEST_FILE, "a");
   fprintf(f, "sb_pushline %d =", cols);
   for (int c = 0; c < eol; c++) {
-    fprintf(f, " %02X", cells[c].chars[0]);
+    fprintf(f, " "); 
+    print_schar(f, cells[c].schar);
   }
   fprintf(f, "\n");
 
@@ -467,10 +488,10 @@ int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
 
   // All lines of scrollback contain "ABCDE"
   for (int col = 0; col < cols; col++) {
-    if (col < 5) {
-      cells[col].chars[0] = (uint32_t)('A' + col);
+    if(col < 5) {
+      cells[col].schar = schar_from_ascii((uint32_t)('A' + col));
     } else {
-      cells[col].chars[0] = 0;
+      cells[col].schar = 0;
     }
 
     cells[col].width = 1;
diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua
index a05579b4ff..2457525fb7 100644
--- a/test/unit/vterm_spec.lua
+++ b/test/unit/vterm_spec.lua
@@ -17,7 +17,6 @@ local bit = require('bit')
 --- @field VTERM_KEY_NONE integer
 --- @field VTERM_KEY_TAB integer
 --- @field VTERM_KEY_UP integer
---- @field VTERM_MAX_CHARS_PER_CELL integer
 --- @field VTERM_MOD_ALT integer
 --- @field VTERM_MOD_CTRL integer
 --- @field VTERM_MOD_SHIFT integer
@@ -80,6 +79,8 @@ local bit = require('bit')
 --- @field vterm_state_set_selection_callbacks function
 --- @field vterm_state_set_unrecognised_fallbacks function
 local vterm = t.cimport(
+  './src/nvim/mbyte.h',
+  './src/nvim/grid.h',
   './src/vterm/vterm.h',
   './src/vterm/vterm_internal.h',
   './test/unit/fixtures/vterm_test.h'
@@ -302,16 +303,12 @@ local function screen_chars(start_row, start_col, end_row, end_col, expected, sc
   rect['end_row'] = end_row
   rect['end_col'] = end_col
 
-  local len = vterm.vterm_screen_get_chars(screen, nil, 0, rect)
-
-  local chars = t.ffi.new('uint32_t[?]', len)
-  vterm.vterm_screen_get_chars(screen, chars, len, rect)
+  local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
 
-  local actual = ''
-  for i = 0, tonumber(len) - 1 do
-    actual = actual .. string.char(chars[i])
-  end
+  local text = t.ffi.new('unsigned char[?]', len)
+  vterm.vterm_screen_get_text(screen, text, len, rect)
 
+  local actual = t.ffi.string(text, len)
   t.eq(expected, actual)
 end
 
@@ -349,7 +346,7 @@ local function screen_row(row, expected, screen, end_col)
   local text = t.ffi.new('unsigned char[?]', len)
   vterm.vterm_screen_get_text(screen, text, len, rect)
 
-  t.eq(expected, t.ffi.string(text))
+  t.eq(expected, t.ffi.string(text, len))
 end
 
 local function screen_cell(row, col, expected, screen)
@@ -360,14 +357,20 @@ local function screen_cell(row, col, expected, screen)
   local cell = t.ffi.new('VTermScreenCell')
   vterm.vterm_screen_get_cell(screen, pos, cell)
 
+  local buf = t.ffi.new('unsigned char[32]')
+  vterm.schar_get(buf, cell.schar)
+
   local actual = '{'
-  for i = 0, vterm.VTERM_MAX_CHARS_PER_CELL - 1 do
-    if cell['chars'][i] ~= 0 then
-      if i > 0 then
-        actual = actual .. ','
-      end
-      actual = string.format('%s%02x', actual, cell['chars'][i])
+  local i = 0
+  while buf[i] > 0 do
+    local char = vterm.utf_ptr2char(buf + i)
+    local charlen = vterm.utf_ptr2len(buf + i)
+    if i > 0 then
+      actual = actual .. ','
     end
+    local invalid = char >= 128 and charlen == 1
+    actual = string.format('%s%s%02x', actual, invalid and '?' or '', char)
+    i = i + charlen
   end
   actual = string.format('%s} width=%d attrs={', actual, cell['width'])
   actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '')
@@ -962,8 +965,8 @@ describe('vterm', function()
 
     -- Spare combining chars get truncated
     reset(state, nil)
-    push('e' .. string.rep('\xCC\x81', 10), vt)
-    expect('putglyph 65,301,301,301,301,301 1 0,0') -- and nothing more
+    push('e' .. string.rep('\xCC\x81', 20), vt)
+    expect('putglyph 65,301,301,301,301,301,301,301,301,301,301,301,301,301,301 1 0,0') -- and nothing more
 
     reset(state, nil)
     push('e', vt)
@@ -973,6 +976,34 @@ describe('vterm', function()
     push('\xCC\x82', vt)
     expect('putglyph 65,301,302 1 0,0')
 
+    -- emoji with ZWJ and variant selectors, as one chunk
+    reset(state, nil)
+    push('🏳️‍🌈🏳️‍⚧️🏴‍☠️', vt)
+    expect([[putglyph 1f3f3,fe0f,200d,1f308 2 0,0
+putglyph 1f3f3,fe0f,200d,26a7,fe0f 2 0,2
+putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
+
+    -- emoji, one code point at a time
+    reset(state, nil)
+    push('🏳', vt)
+    expect('putglyph 1f3f3 2 0,0')
+    push('\xef\xb8\x8f', vt)
+    expect('putglyph 1f3f3,fe0f 2 0,0')
+    push('\xe2\x80\x8d', vt)
+    expect('putglyph 1f3f3,fe0f,200d 2 0,0')
+    push('🌈', vt)
+    expect('putglyph 1f3f3,fe0f,200d,1f308 2 0,0')
+
+    -- modifier can change width
+    push('❤', vt)
+    expect('putglyph 2764 1 0,2')
+    push('\xef\xb8\x8f', vt)
+    expect('putglyph 2764,fe0f 2 0,2')
+
+    -- also works batched
+    push('❤️', vt)
+    expect('putglyph 2764,fe0f 2 0,4')
+
     -- DECSCA protected
     reset(state, nil)
     push('A\x1b[1"qB\x1b[2"qC', vt)
@@ -3046,7 +3077,7 @@ describe('vterm', function()
     screen_cell(
       0,
       0,
-      '{65,301,302,303,304,305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
+      '{65,301,302,303,304,305,306,307,308,309,30a} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
       screen
     )
 
@@ -3063,7 +3094,7 @@ describe('vterm', function()
     screen_cell(
       0,
       0,
-      '{65,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
+      '{65,301,301,301,301,301,301,301,301,301,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
       screen
     )
 
@@ -3072,6 +3103,16 @@ describe('vterm', function()
     push('\x1b[80G\xEF\xBC\x90', vt)
     screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
     screen_cell(1, 0, '{ff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
+
+    -- Outputting emoji with ZWJ and variant selectors
+    reset(nil, screen)
+    push('🏳️‍🌈🏳️‍⚧️🏴‍☠️', vt)
+
+    -- stylua: ignore start
+    screen_cell(0, 0, '{1f3f3,fe0f,200d,1f308} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
+    screen_cell(0, 2, '{1f3f3,fe0f,200d,26a7,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
+    screen_cell(0, 4, '{1f3f4,200d,2620,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
+    -- stylua: ignore end
   end)
 
   pending('62screen_damage', function() end)
@@ -3125,7 +3166,7 @@ describe('vterm', function()
     screen = wantscreen(vt, { b = true })
     resize(20, 80, vt)
     expect(
-      'sb_pushline 80 = 54 6F 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 ='
+      'sb_pushline 80 = 54 6f 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 ='
     )
     -- TODO(dundargoc): fix or remove
     -- screen_row( 0  , "",screen)
-- 
cgit 


From 48e2a73610ca5639408f79b3d8eebd3e5f57a327 Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Thu, 2 Jan 2025 14:51:03 +0100
Subject: feat(ui)!: emit prompt "messages" as cmdline events #31525

Problem:  Prompts are emitted as messages events, where cmdline events
          are more appropriate. The user input is also emitted as
          message events in fast context, so cannot be displayed with
          vim.ui_attach().
Solution: Prompt for user input through cmdline prompts.
---
 runtime/doc/lua.txt                                |   5 +-
 runtime/doc/news.txt                               |   9 +-
 runtime/doc/ui.txt                                 |   2 -
 runtime/lua/vim/_meta/builtin.lua                  |   5 +-
 src/nvim/bufwrite.c                                |   2 +-
 src/nvim/debugger.c                                |   3 +-
 src/nvim/eval/funcs.c                              |   6 +-
 src/nvim/ex_cmds.c                                 |  43 ++----
 src/nvim/ex_docmd.c                                |   2 +-
 src/nvim/ex_getln.c                                |  49 +++++--
 src/nvim/ex_getln_defs.h                           |   2 +
 src/nvim/input.c                                   | 146 +++++--------------
 src/nvim/memline.c                                 |   3 +-
 src/nvim/message.c                                 |  56 ++++----
 src/nvim/spellsuggest.c                            |  12 +-
 src/nvim/tag.c                                     |   2 +-
 src/nvim/ui.c                                      |   6 +-
 .../ex_cmds/swapfile_preserve_recover_spec.lua     |   5 +-
 test/functional/lua/ui_event_spec.lua              |  42 ------
 test/functional/ui/cmdline_spec.lua                |  29 ++--
 test/functional/ui/input_spec.lua                  |   4 +-
 test/functional/ui/messages_spec.lua               | 157 ++++++++++++---------
 test/functional/vimscript/null_spec.lua            |   2 +-
 test/old/testdir/test_spell.vim                    |  12 +-
 test/old/testdir/test_tagjump.vim                  |   4 +-
 25 files changed, 258 insertions(+), 350 deletions(-)

diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 25e58c0240..463389ed65 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1086,9 +1086,8 @@ vim.ui_attach({ns}, {options}, {callback})                   *vim.ui_attach()*
     |ui-popupmenu| and the sections below for event format for respective
     events.
 
-    Callbacks for `msg_show` events are executed in |api-fast| context unless
-    Nvim will wait for input, in which case messages should be shown
-    immediately.
+    Callbacks for `msg_show` events are executed in |api-fast| context;
+    showing the message should be scheduled.
 
     Excessive errors inside the callback will result in forced detachment.
 
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 9ab0a01b99..8a8ef00aa7 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -92,12 +92,15 @@ EVENTS
 • |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in
   |api-fast| context.
 • New/enhanced arguments in these existing UI events:
-  • `cmdline_show`: `hl_id` argument to highlight the prompt text.
   • `cmdline_hide`: `abort` argument indicating if the cmdline was aborted.
+  • `cmdline_show`:
+    • Prompts that were previously emitted as `msg_show` events, are now routed
+      through `cmdline_show`.
+    • `hl_id` argument to highlight the prompt text.
   • `msg_show`:
     • `history` argument indicating if the message was added to the history.
-    • new message kinds: "bufwrite", "completion", "list_cmd",
-      "lua_print", "number_prompt", "search_cmd", "undo", "wildlist".
+    • new message kinds: "bufwrite", "completion", "list_cmd", "lua_print",
+      "search_cmd", "undo", "wildlist".
 
 HIGHLIGHTS
 
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 1b11565eeb..8f25133e7a 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -792,7 +792,6 @@ must handle.
 		"" (empty)	Unknown (consider a |feature-request|)
 		"bufwrite"	|:write| message
 		"confirm"	|confirm()| or |:confirm| dialog
-		"confirm_sub"	|:substitute| confirm dialog |:s_c|
 		"emsg"		Error (|errors|, internal error, |:throw|, …)
 		"echo"		|:echo| message
 		"echomsg"	|:echomsg| message
@@ -802,7 +801,6 @@ must handle.
 		"lua_error"	Error in |:lua| code
 		"lua_print"	|print()| from |:lua| code
 		"rpc_error"	Error response from |rpcrequest()|
-		"number_prompt"	Number input prompt (|inputlist()|, |z=|, …)
 		"return_prompt"	|press-enter| prompt after a multiple messages
 		"quickfix"	Quickfix navigation message
 		"search_cmd"	Entered search command
diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua
index b8779b66fe..9fa2e242c4 100644
--- a/runtime/lua/vim/_meta/builtin.lua
+++ b/runtime/lua/vim/_meta/builtin.lua
@@ -233,9 +233,8 @@ function vim.wait(time, callback, interval, fast_only) end
 --- {callback} receives event name plus additional parameters. See |ui-popupmenu|
 --- and the sections below for event format for respective events.
 ---
---- Callbacks for `msg_show` events are executed in |api-fast| context unless
---- Nvim will wait for input, in which case messages should be shown
---- immediately.
+--- Callbacks for `msg_show` events are executed in |api-fast| context; showing
+--- the message should be scheduled.
 ---
 --- Excessive errors inside the callback will result in forced detachment.
 ---
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
index ced806e524..1afa10df63 100644
--- a/src/nvim/bufwrite.c
+++ b/src/nvim/bufwrite.c
@@ -350,7 +350,7 @@ static int check_mtime(buf_T *buf, FileInfo *file_info)
     msg_silent = 0;     // Must give this prompt.
     // Don't use emsg() here, don't want to flush the buffers.
     msg(_("WARNING: The file has been changed since reading it!!!"), HLF_E);
-    if (ask_yesno(_("Do you really want to write to it"), true) == 'n') {
+    if (ask_yesno(_("Do you really want to write to it")) == 'n') {
       return FAIL;
     }
     msg_scroll = false;  // Always overwrite the file message now.
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index b71ff23f57..f3e4ef0698 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -153,8 +153,7 @@ void do_debug(char *cmd)
     debug_break_level = -1;
 
     xfree(cmdline);
-    cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL,
-                                CALLBACK_NONE);
+    cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE, false, NULL);
 
     debug_break_level = n;
     if (typeahead_saved) {
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index fbaa6e679f..ed3efca0d7 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3557,10 +3557,10 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   });
 
   // Ask for choice.
-  bool mouse_used;
-  int selected = prompt_for_number(&mouse_used);
+  bool mouse_used = false;
+  int selected = prompt_for_input(NULL, 0, false, &mouse_used);
   if (mouse_used) {
-    selected -= lines_left;
+    selected = tv_list_len(argvars[0].vval.v_list) - (cmdline_row - mouse_row);
   }
 
   rettv->vval.v_number = selected;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index a1a6f13023..0510619825 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -3707,12 +3707,9 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
           // Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed.
           while (subflags.do_ask) {
             if (exmode_active) {
-              char *prompt;
-              char *resp;
-              colnr_T sc, ec;
-
               print_line_no_prefix(lnum, subflags.do_number, subflags.do_list);
 
+              colnr_T sc, ec;
               getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL);
               curwin->w_cursor.col = MAX(regmatch.endpos[0].col - 1, 0);
 
@@ -3724,10 +3721,11 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
                 ec += numw;
               }
 
-              prompt = xmallocz((size_t)ec + 1);
+              char *prompt = xmallocz((size_t)ec + 1);
               memset(prompt, ' ', (size_t)sc);
               memset(prompt + sc, '^', (size_t)(ec - sc) + 1);
-              resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE);
+              char *resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL,
+                                             CALLBACK_NONE, false, NULL);
               msg_putchar('\n');
               xfree(prompt);
               if (resp != NULL) {
@@ -3794,35 +3792,14 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
               redraw_later(curwin, UPD_SOME_VALID);
 
               curwin->w_p_fen = save_p_fen;
-              if (msg_row == Rows - 1) {
-                msg_didout = false;                     // avoid a scroll-up
-              }
-              msg_starthere();
-              i = msg_scroll;
-              msg_scroll = 0;                           // truncate msg when
-                                                        // needed
-              msg_no_more = true;
-              msg_ext_set_kind("confirm_sub");
-              // Same highlight as wait_return().
-              smsg(HLF_R, _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
-              msg_no_more = false;
-              msg_scroll = i;
-              if (!ui_has(kUIMessages)) {
-                ui_cursor_goto(msg_row, msg_col);
-              }
-              RedrawingDisabled = temp;
 
-              no_mapping++;                     // don't map this key
-              allow_keys++;                     // allow special keys
-              typed = plain_vgetc();
-              no_mapping--;
-              allow_keys--;
+              snprintf(IObuff, IOSIZE, _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
+              char *prompt = xstrdup(IObuff);
+              typed = prompt_for_input(prompt, HLF_R, true, NULL);
+              xfree(prompt);
 
-              // clear the question
-              msg_didout = false;               // don't scroll up
-              msg_col = 0;
-              gotocmdline(true);
               p_lz = save_p_lz;
+              RedrawingDisabled = temp;
 
               // restore the line
               if (orig_line != NULL) {
@@ -4808,7 +4785,7 @@ void ex_oldfiles(exarg_T *eap)
   // File selection prompt on ":browse oldfiles"
   if (cmdmod.cmod_flags & CMOD_BROWSE) {
     quit_more = false;
-    nr = prompt_for_number(false);
+    nr = prompt_for_input(NULL, 0, false, NULL);
     msg_starthere();
     if (nr > 0 && nr <= tv_list_len(l)) {
       const char *const p = tv_list_find_str(l, nr - 1);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 551e8fcb1d..6f9f0f07c9 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -2201,7 +2201,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
           errormsg = _("E493: Backwards range given");
           goto doend;
         }
-        if (ask_yesno(_("Backwards range given, OK to swap"), false) != 'y') {
+        if (ask_yesno(_("Backwards range given, OK to swap")) != 'y') {
           goto doend;
         }
       }
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 7f2f739e00..09006484ca 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -120,7 +120,7 @@ typedef struct {
   int indent;
   int c;
   bool gotesc;                          // true when  just typed
-  int do_abbr;                          // when true check for abbr.
+  bool do_abbr;                         // when true check for abbr.
   char *lookfor;                        // string to match
   int lookforlen;
   int hiscnt;                           // current history line in use
@@ -128,17 +128,17 @@ typedef struct {
                                         // to jump to next match
   int histype;                          // history type to be used
   incsearch_state_T is_state;
-  int did_wild_list;                    // did wild_list() recently
+  bool did_wild_list;                   // did wild_list() recently
   int wim_index;                        // index in wim_flags[]
   int save_msg_scroll;
   int save_State;                       // remember State when called
   int prev_cmdpos;
   char *save_p_icm;
-  int some_key_typed;                   // one of the keys was typed
+  bool some_key_typed;                  // one of the keys was typed
   // mouse drag and release events are ignored, unless they are
   // preceded with a mouse down event
-  int ignore_drag_release;
-  int break_ctrl_c;
+  bool ignore_drag_release;
+  bool break_ctrl_c;
   expand_T xpc;
   OptInt *b_im_ptr;
   buf_T *b_im_ptr_buf;  ///< buffer where b_im_ptr is valid
@@ -1848,6 +1848,12 @@ static int command_line_browse_history(CommandLineState *s)
 
 static int command_line_handle_key(CommandLineState *s)
 {
+  // For one key prompt, avoid putting ESC and Ctrl_C onto cmdline.
+  // For all other keys, just put onto cmdline and exit.
+  if (ccline.one_key && s->c != ESC && s->c != Ctrl_C) {
+    goto end;
+  }
+
   // Big switch for a typed command line character.
   switch (s->c) {
   case K_BS:
@@ -1998,6 +2004,12 @@ static int command_line_handle_key(CommandLineState *s)
     }
     FALLTHROUGH;
   case K_LEFTMOUSE:
+    // Return on left click above number prompt
+    if (ccline.mouse_used && mouse_row < cmdline_row) {
+      *ccline.mouse_used = true;
+      return 0;
+    }
+    FALLTHROUGH;
   case K_RIGHTMOUSE:
     command_line_left_right_mouse(s);
     return command_line_not_changed(s);
@@ -2155,6 +2167,14 @@ static int command_line_handle_key(CommandLineState *s)
     }
     return command_line_not_changed(s);
 
+  case 'q':
+    // Number prompts use the mouse and return on 'q' press
+    if (ccline.mouse_used) {
+      *ccline.cmdbuff = NUL;
+      return 0;
+    }
+    FALLTHROUGH;
+
   default:
     // Normal character with no special meaning.  Just set mod_mask
     // to 0x0 so that typing Shift-Space in the GUI doesn't enter
@@ -2175,6 +2195,7 @@ static int command_line_handle_key(CommandLineState *s)
     return command_line_changed(s);
   }
 
+end:
   // put the character in the command line
   if (IS_SPECIAL(s->c) || mod_mask != 0) {
     put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
@@ -2183,7 +2204,7 @@ static int command_line_handle_key(CommandLineState *s)
     IObuff[j] = NUL;                // exclude composing chars
     put_on_cmdline(IObuff, j, true);
   }
-  return command_line_changed(s);
+  return ccline.one_key ? 0 : command_line_changed(s);
 }
 
 static int command_line_not_changed(CommandLineState *s)
@@ -2721,8 +2742,11 @@ static void abandon_cmdline(void)
   if (msg_scrolled == 0) {
     compute_cmdrow();
   }
-  msg("", 0);
-  redraw_cmdline = true;
+  // Avoid overwriting key prompt
+  if (!ccline.one_key) {
+    msg("", 0);
+    redraw_cmdline = true;
+  }
 }
 
 /// getcmdline() - accept a command line starting with firstc.
@@ -2761,11 +2785,13 @@ char *getcmdline(int firstc, int count, int indent, bool do_concat FUNC_ATTR_UNU
 /// @param[in]  xp_context  Type of expansion.
 /// @param[in]  xp_arg  User-defined expansion argument.
 /// @param[in]  highlight_callback  Callback used for highlighting user input.
+/// @param[in]  one_key  Return after one key press for button prompt.
+/// @param[in]  mouse_used  Set to true when returning after right mouse click.
 ///
 /// @return [allocated] Command line or NULL.
 char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl_id,
                         const int xp_context, const char *const xp_arg,
-                        const Callback highlight_callback)
+                        const Callback highlight_callback, bool one_key, bool *mouse_used)
   FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
 {
   const int msg_col_save = msg_col;
@@ -2786,11 +2812,14 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl
   ccline.xp_arg = (char *)xp_arg;
   ccline.input_fn = (firstc == '@');
   ccline.highlight_callback = highlight_callback;
+  ccline.one_key = one_key;
+  ccline.mouse_used = mouse_used;
 
   int msg_silent_saved = msg_silent;
   msg_silent = 0;
 
   char *const ret = (char *)command_line_enter(firstc, 1, 0, false);
+  ccline.redraw_state = kCmdRedrawNone;
 
   if (did_save_ccline) {
     restore_cmdline(&save_ccline);
@@ -4787,7 +4816,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
   const int save_ex_normal_busy = ex_normal_busy;
   ex_normal_busy = 0;
   rettv->vval.v_string = getcmdline_prompt(secret ? NUL : '@', p, get_echo_hl_id(),
-                                           xp_type, xp_arg, input_callback);
+                                           xp_type, xp_arg, input_callback, false, NULL);
   ex_normal_busy = save_ex_normal_busy;
   callback_free(&input_callback);
 
diff --git a/src/nvim/ex_getln_defs.h b/src/nvim/ex_getln_defs.h
index 584c360450..e05d8f27db 100644
--- a/src/nvim/ex_getln_defs.h
+++ b/src/nvim/ex_getln_defs.h
@@ -65,4 +65,6 @@ struct cmdline_info {
   char special_char;            ///< last putcmdline char (used for redraws)
   bool special_shift;           ///< shift of last putcmdline char
   CmdRedraw redraw_state;       ///< needed redraw for external cmdline
+  bool one_key;                 ///< return after one key press for button prompt
+  bool *mouse_used;             ///< mouse clicked in prompt
 };
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 5a3a791de4..ca2a13e016 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -6,6 +6,7 @@
 #include 
 
 #include "nvim/ascii_defs.h"
+#include "nvim/ex_getln.h"
 #include "nvim/getchar.h"
 #include "nvim/gettext_defs.h"
 #include "nvim/globals.h"
@@ -32,44 +33,36 @@
 /// No other characters are accepted, the message is repeated until a valid
 /// reply is entered or  is hit.
 ///
-/// @param[in]  str  Prompt: question to ask user. Is always followed by
-///                  " (y/n)?".
-/// @param[in]  direct  Determines what function to use to get user input. If
-///                     true then input_get() will be used, otherwise vgetc().
-///                     I.e. when direct is true then characters are obtained
-///                     directly from the user without buffers involved.
+/// @param[in]  str  Prompt: question to ask user. Is always followed by " (y/n)?".
 ///
 /// @return 'y' or 'n'. Last is also what will be returned in case of interrupt.
-int ask_yesno(const char *const str, const bool direct)
+int ask_yesno(const char *const str)
 {
   const int save_State = State;
 
   no_wait_return++;
   State = MODE_CONFIRM;  // Mouse behaves like with :confirm.
   setmouse();  // Disable mouse in xterm.
-  no_mapping++;
-  allow_keys++;  // no mapping here, but recognize keys
+  snprintf(IObuff, IOSIZE, _("%s (y/n)?"), str);
+  char *prompt = xstrdup(IObuff);
 
   int r = ' ';
   while (r != 'y' && r != 'n') {
     // same highlighting as for wait_return()
-    smsg(HLF_R, "%s (y/n)?", str);
-    if (direct) {
-      r = get_keystroke(NULL);
-    } else {
-      r = plain_vgetc();
-    }
+    r = prompt_for_input(prompt, HLF_R, true, NULL);
     if (r == Ctrl_C || r == ESC) {
       r = 'n';
+      if (!ui_has(kUIMessages)) {
+        msg_putchar(r);
+      }
     }
-    msg_putchar(r);  // Show what you typed.
-    ui_flush();
   }
+
+  need_wait_return = msg_scrolled;
   no_wait_return--;
   State = save_State;
   setmouse();
-  no_mapping--;
-  allow_keys--;
+  xfree(prompt);
 
   return r;
 }
@@ -155,105 +148,42 @@ int get_keystroke(MultiQueue *events)
   return n;
 }
 
-/// Get a number from the user.
-/// When "mouse_used" is not NULL allow using the mouse.
+/// Ask the user for input through a cmdline prompt.
 ///
-/// @param colon  allow colon to abort
-int get_number(int colon, bool *mouse_used)
+/// @param one_key Return from cmdline after one key press.
+/// @param mouse_used When not NULL, allow using the mouse to press a number.
+int prompt_for_input(char *prompt, int hl_id, bool one_key, bool *mouse_used)
 {
-  int n = 0;
-  int typed = 0;
+  int ret = one_key ? ESC : 0;
+  char *kmsg = keep_msg ? xstrdup(keep_msg) : NULL;
 
-  if (mouse_used != NULL) {
-    *mouse_used = false;
+  if (prompt == NULL) {
+    if (mouse_used != NULL) {
+      prompt = _("Type number and  or click with the mouse (q or empty cancels):");
+    } else {
+      prompt = _("Type number and  (q or empty cancels):");
+    }
   }
 
-  // When not printing messages, the user won't know what to type, return a
-  // zero (as if CR was hit).
-  if (msg_silent != 0) {
-    return 0;
-  }
+  cmdline_row = msg_row;
+  ui_flush();
 
-  no_mapping++;
-  allow_keys++;  // no mapping here, but recognize keys
-  while (true) {
-    ui_cursor_goto(msg_row, msg_col);
-    int c = safe_vgetc();
-    if (ascii_isdigit(c)) {
-      if (vim_append_digit_int(&n, c - '0') == FAIL) {
-        return 0;
-      }
-      msg_putchar(c);
-      typed++;
-    } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) {
-      if (typed > 0) {
-        msg_puts("\b \b");
-        typed--;
-      }
-      n /= 10;
-    } else if (mouse_used != NULL && c == K_LEFTMOUSE) {
-      *mouse_used = true;
-      n = mouse_row + 1;
-      break;
-    } else if (n == 0 && c == ':' && colon) {
-      stuffcharReadbuff(':');
-      if (!exmode_active) {
-        cmdline_row = msg_row;
-      }
-      skip_redraw = true;           // skip redraw once
-      do_redraw = false;
-      break;
-    } else if (c == Ctrl_C || c == ESC || c == 'q') {
-      n = 0;
-      break;
-    } else if (c == CAR || c == NL) {
-      break;
-    }
-  }
-  no_mapping--;
+  no_mapping++;  // don't map prompt input
+  allow_keys++;  // allow special keys
+  char *resp = getcmdline_prompt(-1, prompt, hl_id, EXPAND_NOTHING, NULL,
+                                 CALLBACK_NONE, one_key, mouse_used);
   allow_keys--;
-  return n;
-}
+  no_mapping--;
 
-/// Ask the user to enter a number.
-///
-/// When "mouse_used" is not NULL allow using the mouse and in that case return
-/// the line number.
-int prompt_for_number(bool *mouse_used)
-{
-  msg_ext_set_kind("number_prompt");
-  // When using ":silent" assume that  was entered.
-  if (mouse_used != NULL) {
-    msg_puts(_("Type number and  or click with the mouse "
-               "(q or empty cancels): "));
-  } else {
-    msg_puts(_("Type number and  (q or empty cancels): "));
+  if (resp) {
+    ret = one_key ? (int)(*resp) : atoi(resp);
+    xfree(resp);
   }
 
-  // Set the state such that text can be selected/copied/pasted and we still
-  // get mouse events.
-  int save_cmdline_row = cmdline_row;
-  cmdline_row = 0;
-  int save_State = State;
-  State = MODE_ASKMORE;  // prevents a screen update when using a timer
-  // May show different mouse shape.
-  setmouse();
-
-  int i = get_number(true, mouse_used);
-  if (KeyTyped) {
-    // don't call wait_return() now
-    if (msg_row > 0) {
-      cmdline_row = msg_row - 1;
-    }
-    need_wait_return = false;
-    msg_didany = false;
-    msg_didout = false;
-  } else {
-    cmdline_row = save_cmdline_row;
+  if (kmsg != NULL) {
+    set_keep_msg(kmsg, keep_msg_hl_id);
+    xfree(kmsg);
   }
-  State = save_State;
-  // May need to restore mouse shape.
-  setmouse();
 
-  return i;
+  return ret;
 }
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index a2b8aa34ef..ce04362a3e 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -804,8 +804,7 @@ void ml_recover(bool checkext)
       // list the names of the swapfiles
       recover_names(fname, true, NULL, 0, NULL);
       msg_putchar('\n');
-      msg_puts(_("Enter number of swap file to use (0 to quit): "));
-      i = get_number(false, NULL);
+      i = prompt_for_input(_("Enter number of swap file to use (0 to quit): "), 0, false, NULL);
       if (i < 1 || i > len) {
         goto theend;
       }
diff --git a/src/nvim/message.c b/src/nvim/message.c
index f86f0feca5..69e8f66bbe 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -92,7 +92,7 @@ static int confirm_msg_used = false;            // displaying confirm_msg
 # include "message.c.generated.h"
 #endif
 static char *confirm_msg = NULL;            // ":confirm" message
-static char *confirm_msg_tail;              // tail of confirm_msg
+static char *confirm_buttons;               // ":confirm" buttons sent to cmdline as prompt
 
 MessageHistoryEntry *first_msg_hist = NULL;
 MessageHistoryEntry *last_msg_hist = NULL;
@@ -2286,7 +2286,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse
         if (p_more && lines_left == 0 && State != MODE_HITRETURN
             && !msg_no_more && !exmode_active) {
           if (do_more_prompt(NUL)) {
-            s = confirm_msg_tail;
+            s = confirm_buttons;
           }
           if (quit_more) {
             return;
@@ -2778,7 +2778,7 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
 /// When at hit-enter prompt "typed_char" is the already typed character,
 /// otherwise it's NUL.
 ///
-/// @return  true when jumping ahead to "confirm_msg_tail".
+/// @return  true when jumping ahead to "confirm_buttons".
 static bool do_more_prompt(int typed_char)
 {
   static bool entered = false;
@@ -3502,10 +3502,10 @@ int do_dialog(int type, const char *title, const char *message, const char *butt
     }
 
     // Get a typed character directly from the user.
-    int c = get_keystroke(NULL);
+    int c = prompt_for_input(confirm_buttons, HLF_M, true, NULL);
     switch (c) {
     case CAR:                 // User accepts default option
-    case NL:
+    case NUL:
       retval = dfltbutton;
       break;
     case Ctrl_C:              // User aborts/cancels
@@ -3514,6 +3514,7 @@ int do_dialog(int type, const char *title, const char *message, const char *butt
       break;
     default:                  // Could be a hotkey?
       if (c < 0) {            // special keys are ignored here
+        msg_didout = msg_didany = false;
         continue;
       }
       if (c == ':' && ex_cmd) {
@@ -3536,6 +3537,7 @@ int do_dialog(int type, const char *title, const char *message, const char *butt
         break;
       }
       // No hotkey match, so keep waiting
+      msg_didout = msg_didany = false;
       continue;
     }
     break;
@@ -3589,19 +3591,20 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool
   has_hotkey[0] = false;
 
   // Compute the size of memory to allocate.
-  int len = 0;
+  int msg_len = 0;
+  int button_len = 0;
   int idx = 0;
   const char *r = buttons;
   while (*r) {
     if (*r == DLG_BUTTON_SEP) {
-      len += 3;                         // '\n' -> ', '; 'x' -> '(x)'
+      button_len += 3;                  // '\n' -> ', '; 'x' -> '(x)'
       lenhotkey += HOTK_LEN;            // each button needs a hotkey
       if (idx < HAS_HOTKEY_LEN - 1) {
         has_hotkey[++idx] = false;
       }
     } else if (*r == DLG_HOTKEY_CHAR) {
       r++;
-      len++;                    // '&a' -> '[a]'
+      button_len++;                     // '&a' -> '[a]'
       if (idx < HAS_HOTKEY_LEN - 1) {
         has_hotkey[idx] = true;
       }
@@ -3611,21 +3614,22 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool
     MB_PTR_ADV(r);
   }
 
-  len += (int)(strlen(message)
-               + 2                          // for the NL's
-               + strlen(buttons)
-               + 3);                        // for the ": " and NUL
-  lenhotkey++;                               // for the NUL
+  msg_len += (int)strlen(message) + 3;     // for the NL's and NUL
+  button_len += (int)strlen(buttons) + 3;  // for the ": " and NUL
+  lenhotkey++;                             // for the NUL
 
   // If no hotkey is specified, first char is used.
   if (!has_hotkey[0]) {
-    len += 2;                                // "x" -> "[x]"
+    button_len += 2;                       // "x" -> "[x]"
   }
 
   // Now allocate space for the strings
   xfree(confirm_msg);
-  confirm_msg = xmalloc((size_t)len);
-  *confirm_msg = NUL;
+  confirm_msg = xmalloc((size_t)msg_len);
+  snprintf(confirm_msg, (size_t)msg_len, "\n%s\n", message);
+
+  xfree(confirm_buttons);
+  confirm_buttons = xmalloc((size_t)button_len);
 
   return xmalloc((size_t)lenhotkey);
 }
@@ -3643,42 +3647,34 @@ static char *msg_show_console_dialog(const char *message, const char *buttons, i
   bool has_hotkey[HAS_HOTKEY_LEN] = { false };
   char *hotk = console_dialog_alloc(message, buttons, has_hotkey);
 
-  copy_hotkeys_and_msg(message, buttons, dfltbutton, has_hotkey, hotk);
+  copy_confirm_hotkeys(buttons, dfltbutton, has_hotkey, hotk);
 
   display_confirm_msg();
   return hotk;
 }
 
-/// Copies hotkeys & dialog message into the memory allocated for it
+/// Copies hotkeys into the memory allocated for it
 ///
-/// @param message Message which will be part of the confirm_msg
 /// @param buttons String containing button names
 /// @param default_button_idx Number of default button
 /// @param has_hotkey An element in this array is true if corresponding button
 ///                   has a hotkey
 /// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied
-static void copy_hotkeys_and_msg(const char *message, const char *buttons, int default_button_idx,
+static void copy_confirm_hotkeys(const char *buttons, int default_button_idx,
                                  const bool has_hotkey[], char *hotkeys_ptr)
 {
-  *confirm_msg = '\n';
-  STRCPY(confirm_msg + 1, message);
-
-  char *msgp = confirm_msg + 1 + strlen(message);
-
   // Define first default hotkey. Keep the hotkey string NUL
   // terminated to avoid reading past the end.
   hotkeys_ptr[copy_char(buttons, hotkeys_ptr, true)] = NUL;
 
-  // Remember where the choices start, displaying starts here when
-  // "hotkeys_ptr" typed at the more prompt.
-  confirm_msg_tail = msgp;
-  *msgp++ = '\n';
-
   bool first_hotkey = false;  // Is the first char of button a hotkey
   if (!has_hotkey[0]) {
     first_hotkey = true;     // If no hotkey is specified, first char is used
   }
 
+  // Remember where the choices start, sent as prompt to cmdline.
+  char *msgp = confirm_buttons;
+
   int idx = 0;
   const char *r = buttons;
   while (*r) {
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
index 3a985ab004..21bfa367bf 100644
--- a/src/nvim/spellsuggest.c
+++ b/src/nvim/spellsuggest.c
@@ -444,7 +444,7 @@ void spell_suggest(int count)
   char wcopy[MAXWLEN + 2];
   suginfo_T sug;
   suggest_T *stp;
-  bool mouse_used;
+  bool mouse_used = false;
   int selected = count;
   int badlen = 0;
   int msg_scroll_save = msg_scroll;
@@ -594,15 +594,11 @@ void spell_suggest(int count)
     cmdmsg_rl = false;
     msg_col = 0;
     // Ask for choice.
-    selected = prompt_for_number(&mouse_used);
-
-    if (ui_has(kUIMessages)) {
-      ui_call_msg_clear();
-    }
-
+    selected = prompt_for_input(NULL, 0, false, &mouse_used);
     if (mouse_used) {
-      selected -= lines_left;
+      selected = sug.su_ga.ga_len + 1 - (cmdline_row - mouse_row);
     }
+
     lines_left = Rows;                  // avoid more prompt
     // don't delay for 'smd' in normal_cmd()
     msg_scroll = msg_scroll_save;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index cdce97fc01..c676b00986 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -668,7 +668,7 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose)
 
       if (ask_for_selection) {
         // Ask to select a tag from the list.
-        int i = prompt_for_number(NULL);
+        int i = prompt_for_input(NULL, 0, false, NULL);
         if (i <= 0 || i > num_matches || got_int) {
           // no valid choice: don't change anything
           if (use_tagstack) {
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 4f443028b3..d242baf83b 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -717,10 +717,10 @@ void ui_call_event(char *name, bool fast, Array args)
   bool handled = false;
   UIEventCallback *event_cb;
 
-  // Prompt messages should be shown immediately so must be safe
+  // Return prompt is still a non-fast event, other prompt messages are
+  // followed by a "cmdline_show" event.
   if (strcmp(name, "msg_show") == 0) {
-    char *kind = args.items[0].data.string.data;
-    fast = !kind || ((strncmp(kind, "confirm", 7) != 0 && strstr(kind, "_prompt") == NULL));
+    fast = !strequal(args.items[0].data.string.data, "return_prompt");
   }
 
   map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, {
diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
index 7234985009..08f7663075 100644
--- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
+++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
@@ -220,6 +220,7 @@ describe('swapfile detection', function()
         .. [[%.swp"]],
     }
     feed('e') -- Chose "Edit" at the swap dialog.
+    screen2:expect({ any = pesc('E5555: API call: Vim(edit):E325: ATTENTION') })
     feed('')
     screen2:expect(expected_no_dialog)
 
@@ -535,10 +536,6 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
       )
     end)
     api.nvim_chan_send(chan, 'q')
-    retry(nil, nil, function()
-      eq('Press ENTER or type command to continue', eval("getline('$')->trim(' ', 2)"))
-    end)
-    api.nvim_chan_send(chan, '\r')
     retry(nil, nil, function()
       eq(
         { '', '[Process exited 1]', '' },
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
index 7e890e8ae0..27640d6066 100644
--- a/test/functional/lua/ui_event_spec.lua
+++ b/test/functional/lua/ui_event_spec.lua
@@ -270,48 +270,6 @@ describe('vim.ui_attach', function()
         },
       },
     })
-    -- No fast context for prompt message kinds
-    feed(':%s/Function/Replacement/c')
-    screen:expect({
-      grid = [[
-        ^E122: {10:Function} Foo already exists, add !|
-         to replace it                          |
-        replace with Replacement (y/n/a/q/l/^E/^|
-        Y)?                                     |
-        {1:~                                       }|
-      ]],
-      cmdline = { { abort = false } },
-      messages = {
-        {
-          content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 18 } },
-          history = true,
-          kind = 'confirm_sub',
-        },
-      },
-    })
-    feed(':call inputlist(["Select:", "One", "Two"])')
-    screen:expect({
-      grid = [[
-        E122: {10:Function} Foo already exists, add !|
-         to replace it                          |
-        Type number and  or click with th|
-        e mouse (q or empty cancels):           |
-        {1:^~                                       }|
-      ]],
-      cmdline = { { abort = false } },
-      messages = {
-        {
-          content = { { 'Select:\nOne\nTwo\n' } },
-          history = false,
-          kind = 'list_cmd',
-        },
-        {
-          content = { { 'Type number and  or click with the mouse (q or empty cancels): ' } },
-          history = false,
-          kind = 'number_prompt',
-        },
-      },
-    })
   end)
 end)
 
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 63764a9b6c..1f7d5525f3 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -1485,31 +1485,28 @@ describe('cmdheight=0', function()
   it('when substitute text', function()
     command('set cmdheight=0 noruler laststatus=3')
     feed('ifoo')
-    screen:expect {
-      grid = [[
+    screen:try_resize(screen._width, 6)
+    screen:expect([[
       fo^o                      |
-      {1:~                        }|*3
+      {1:~                        }|*4
       {3:[No Name] [+]            }|
-    ]],
-    }
+    ]])
 
     feed(':%s/foo/bar/gc')
-    screen:expect {
-      grid = [[
+    screen:expect([[
       {2:foo}                      |
-      {1:~                        }|*3
-      {6:replace wi...q/l/^E/^Y)?}^ |
-    ]],
-    }
+      {3:                         }|
+                               |*2
+      {6:replace with bar (y/n/a/q}|
+      {6:/l/^E/^Y)?}^               |
+    ]])
 
     feed('y')
-    screen:expect {
-      grid = [[
+    screen:expect([[
       ^bar                      |
-      {1:~                        }|*3
+      {1:~                        }|*4
       {3:[No Name] [+]            }|
-    ]],
-    }
+    ]])
 
     assert_alive()
   end)
diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua
index 90e0b3e380..98312c42c9 100644
--- a/test/functional/ui/input_spec.lua
+++ b/test/functional/ui/input_spec.lua
@@ -368,7 +368,7 @@ describe('input non-printable chars', function()
       "Xtest-overwrite"                                           |
       {9:WARNING: The file has been changed since reading it!!!}      |
       {6:Do you really want to write to it (y/n)?}u                   |
-      {6:Do you really want to write to it (y/n)?}                    |
+      {6:Do you really want to write to it (y/n)?}{18:^E}                  |
       {6:Do you really want to write to it (y/n)?}^                    |
     ]])
 
@@ -379,7 +379,7 @@ describe('input non-printable chars', function()
       "Xtest-overwrite"                                           |
       {9:WARNING: The file has been changed since reading it!!!}      |
       {6:Do you really want to write to it (y/n)?}u                   |
-      {6:Do you really want to write to it (y/n)?}                    |
+      {6:Do you really want to write to it (y/n)?}{18:^E}                  |
       {6:Do you really want to write to it (y/n)?}n                   |
       {6:Press ENTER or type command to continue}^                     |
     ]])
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 8acf8495c6..56b2c61fb8 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -42,55 +42,75 @@ describe('ui/ext_messages', function()
   it('msg_clear follows msg_show kind of confirm', function()
     feed('iline 1')
     feed(':call confirm("test")')
-    screen:expect {
+    screen:expect({
       grid = [[
-      line ^1                   |
-      {1:~                        }|*4
-    ]],
-      cmdline = { { abort = false } },
+        line ^1                   |
+        {1:~                        }|*4
+      ]],
+      cmdline = {
+        {
+          content = { { '' } },
+          hl_id = 10,
+          pos = 0,
+          prompt = '[O]k: ',
+        },
+      },
       messages = {
         {
-          content = { { '\ntest\n[O]k: ', 6, 10 } },
+          content = { { '\ntest\n', 6, 10 } },
           history = false,
           kind = 'confirm',
         },
       },
-    }
-
+    })
     feed('')
-    screen:expect {
+    screen:expect({
       grid = [[
-      line ^1                   |
-      {1:~                        }|*4
-    ]],
-    }
+        line ^1                   |
+        {1:~                        }|*4
+      ]],
+      cmdline = { { abort = false } },
+    })
   end)
 
   it('msg_show kinds', function()
     feed('iline 1\nline 2')
 
-    -- kind=confirm
+    -- confirm is now cmdline prompt
     feed(':echo confirm("test")')
-    screen:expect {
+    screen:expect({
       grid = [[
-      line 1                   |
-      line ^2                   |
-      {1:~                        }|*3
-    ]],
-      cmdline = { { abort = false } },
+        line 1                   |
+        line ^2                   |
+        {1:~                        }|*3
+      ]],
+      cmdline = {
+        {
+          content = { { '' } },
+          hl_id = 10,
+          pos = 0,
+          prompt = '[O]k: ',
+        },
+      },
       messages = {
         {
-          content = { { '\ntest\n[O]k: ', 6, 10 } },
+          content = { { '\ntest\n', 6, 10 } },
           history = false,
           kind = 'confirm',
         },
       },
-    }
-    feed('')
-    screen:expect {
+    })
+    feed('')
+    screen:expect({
+      grid = [[
+        line 1                   |
+        line ^2                   |
+        {1:~                        }|*3
+      ]],
+      cmdline = { { abort = false } },
       messages = {
         {
-          content = { { '\ntest\n[O]k: ', 6, 10 } },
+          content = { { '\ntest\n', 6, 10 } },
           history = false,
           kind = 'confirm',
         },
@@ -105,38 +125,39 @@ describe('ui/ext_messages', function()
           kind = 'return_prompt',
         },
       },
-    }
-    feed('')
+    })
+    feed('')
 
-    -- kind=confirm_sub
+    -- :substitute confirm is now cmdline prompt
     feed(':%s/i/X/gc')
-    screen:expect {
+    screen:expect({
       grid = [[
-      l{2:i}ne 1                   |
-      l{10:i}ne ^2                   |
-      {1:~                        }|*3
-    ]],
-      cmdline = { { abort = false } },
-      messages = {
+        l{2:^i}ne 1                   |
+        l{10:i}ne 2                   |
+        {1:~                        }|*3
+      ]],
+      cmdline = {
         {
-          content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 18 } },
-          history = true,
-          kind = 'confirm_sub',
+          content = { { '' } },
+          hl_id = 18,
+          pos = 0,
+          prompt = 'replace with X (y/n/a/q/l/^E/^Y)?',
         },
       },
-    }
+    })
     feed('nq')
 
     -- kind=wmsg (editing readonly file)
     command('write ' .. fname)
     command('set readonly nohls')
     feed('G$x')
-    screen:expect {
+    screen:expect({
       grid = [[
         line 1                   |
-        line ^2                   |
+        line^                     |
         {1:~                        }|*3
       ]],
+      cmdline = { { abort = false } },
       messages = {
         {
           content = { { 'W10: Warning: Changing a readonly file', 19, 26 } },
@@ -144,7 +165,7 @@ describe('ui/ext_messages', function()
           kind = 'wmsg',
         },
       },
-    }
+    })
 
     -- kind=wmsg ('wrapscan' after search reaches EOF)
     feed('uG$/i')
@@ -1122,57 +1143,57 @@ stack traceback:
     feed('z=')
     screen:expect({
       grid = [[
-        {100:helllo}                   |
-        {1:~                        }|*3
-        {1:^~                        }|
+        {100:^helllo}                   |
+        {1:~                        }|*4
       ]],
+      cmdline = {
+        {
+          content = { { '' } },
+          hl_id = 0,
+          pos = 0,
+          prompt = 'Type number and  or click with the mouse (q or empty cancels):',
+        },
+      },
       messages = {
         {
           content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } },
           history = false,
           kind = 'list_cmd',
         },
-        {
-          content = { { 'Type number and  or click with the mouse (q or empty cancels): ' } },
-          history = false,
-          kind = 'number_prompt',
-        },
       },
     })
 
     feed('1')
     screen:expect({
       grid = [[
-        {100:helllo}                   |
-        {1:~                        }|*3
-        {1:^~                        }|
+        {100:^helllo}                   |
+        {1:~                        }|*4
       ]],
+      cmdline = {
+        {
+          content = { { '1' } },
+          hl_id = 0,
+          pos = 1,
+          prompt = 'Type number and  or click with the mouse (q or empty cancels):',
+        },
+      },
       messages = {
         {
           content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } },
           history = false,
           kind = 'list_cmd',
         },
-        {
-          content = { { 'Type number and  or click with the mouse (q or empty cancels): ' } },
-          history = false,
-          kind = 'number_prompt',
-        },
-        {
-          content = { { '1' } },
-          history = false,
-          kind = '',
-        },
       },
     })
 
     feed('')
-    screen:expect {
+    screen:expect({
       grid = [[
-      ^Hello                    |
-      {1:~                        }|*4
-    ]],
-    }
+        ^Hello                    |
+        {1:~                        }|*4
+      ]],
+      cmdline = { { abort = false } },
+    })
   end)
 
   it('supports nvim_echo messages with multiple attrs', function()
diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua
index 9a27239a6d..afd50f7cf9 100644
--- a/test/functional/vimscript/null_spec.lua
+++ b/test/functional/vimscript/null_spec.lua
@@ -116,7 +116,7 @@ describe('NULL', function()
     null_expr_test(
       'is accepted as an empty list by inputlist()',
       '[feedkeys("\\n"), inputlist(L)]',
-      'Type number and  or click with the mouse (q or empty cancels): ',
+      '',
       { 0, 0 }
     )
     null_expr_test(
diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim
index bdd8a673fd..a5ae653369 100644
--- a/test/old/testdir/test_spell.vim
+++ b/test/old/testdir/test_spell.vim
@@ -471,7 +471,9 @@ func Test_spellsuggest_option_number()
   \ .. "Change \"baord\" to:\n"
   \ .. " 1 \"board\"\n"
   \ .. " 2 \"bard\"\n"
-  \ .. "Type number and  or click with the mouse (q or empty cancels): ", a)
+  "\ Nvim: Prompt message is sent to cmdline prompt.
+  "\ .. "Type number and  or click with the mouse (q or empty cancels): ", a)
+  \ , a)
 
   set spell spellsuggest=0
   call assert_equal("\nSorry, no suggestions", execute('norm $z='))
@@ -509,7 +511,9 @@ func Test_spellsuggest_option_expr()
   \ .. " 1 \"BARD\"\n"
   \ .. " 2 \"BOARD\"\n"
   \ .. " 3 \"BROAD\"\n"
-  \ .. "Type number and  or click with the mouse (q or empty cancels): ", a)
+  "\ Nvim: Prompt message is sent to cmdline prompt.
+  "\ .. "Type number and  or click with the mouse (q or empty cancels): ", a)
+  \ , a)
 
   " With verbose, z= should show the score i.e. word length with
   " our SpellSuggest() function.
@@ -521,7 +525,9 @@ func Test_spellsuggest_option_expr()
   \ .. " 1 \"BARD\"                      (4 - 0)\n"
   \ .. " 2 \"BOARD\"                     (5 - 0)\n"
   \ .. " 3 \"BROAD\"                     (5 - 0)\n"
-  \ .. "Type number and  or click with the mouse (q or empty cancels): ", a)
+  "\ Nvim: Prompt message is sent to cmdline prompt.
+  "\ .. "Type number and  or click with the mouse (q or empty cancels): ", a)
+  \ , a)
 
   set spell& spellsuggest& verbose&
   bwipe!
diff --git a/test/old/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim
index 470c5c43b4..efc5e4cebe 100644
--- a/test/old/testdir/test_tagjump.vim
+++ b/test/old/testdir/test_tagjump.vim
@@ -1231,8 +1231,10 @@ func Test_tselect_listing()
   2 FS  v    first             Xfoo
                typeref:typename:char 
                2
-Type number and  (q or empty cancels): 
 [DATA]
+" Type number and  (q or empty cancels):
+" Nvim: Prompt message is sent to cmdline prompt.
+
   call assert_equal(expected, l)
 
   call delete('Xtags')
-- 
cgit 


From 43d552c56648bc3125c7509b3d708b6bf6c0c09c Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Thu, 2 Jan 2025 15:40:39 +0100
Subject: feat(ui): more intuitive :substitute confirm prompt #31787

Problem:  Unknown key mappings listed in substitute confirm message.
Solution: Include hints as to what the key mappings do.
---
 runtime/doc/usr_10.txt                     |  2 +-
 src/nvim/ex_cmds.c                         |  9 +++++----
 src/nvim/po/af.po                          |  4 ++--
 src/nvim/po/ca.po                          |  4 ++--
 src/nvim/po/cs.cp1250.po                   |  4 ++--
 src/nvim/po/cs.po                          |  4 ++--
 src/nvim/po/da.po                          |  4 ++--
 src/nvim/po/de.po                          |  4 ++--
 src/nvim/po/en_GB.po                       |  4 ++--
 src/nvim/po/eo.po                          |  4 ++--
 src/nvim/po/es.po                          |  4 ++--
 src/nvim/po/fi.po                          |  4 ++--
 src/nvim/po/fr.po                          |  4 ++--
 src/nvim/po/ga.po                          |  4 ++--
 src/nvim/po/it.po                          |  4 ++--
 src/nvim/po/ja.euc-jp.po                   |  4 ++--
 src/nvim/po/ja.po                          |  4 ++--
 src/nvim/po/ko.UTF-8.po                    |  4 ++--
 src/nvim/po/nb.po                          |  4 ++--
 src/nvim/po/nl.po                          |  4 ++--
 src/nvim/po/no.po                          |  4 ++--
 src/nvim/po/pl.UTF-8.po                    |  4 ++--
 src/nvim/po/pt_BR.po                       |  4 ++--
 src/nvim/po/ru.po                          |  4 ++--
 src/nvim/po/sk.cp1250.po                   |  4 ++--
 src/nvim/po/sk.po                          |  4 ++--
 src/nvim/po/sr.po                          |  4 ++--
 src/nvim/po/sv.po                          |  4 ++--
 src/nvim/po/tr.po                          |  4 ++--
 src/nvim/po/uk.po                          |  4 ++--
 src/nvim/po/vi.po                          |  4 ++--
 src/nvim/po/zh_CN.UTF-8.po                 |  4 ++--
 src/nvim/po/zh_TW.UTF-8.po                 |  4 ++--
 test/functional/legacy/substitute_spec.lua |  6 ++++--
 test/functional/ui/cmdline_spec.lua        | 11 ++++++-----
 test/functional/ui/messages_spec.lua       |  2 +-
 36 files changed, 79 insertions(+), 75 deletions(-)

diff --git a/runtime/doc/usr_10.txt b/runtime/doc/usr_10.txt
index 2f55aadef0..98337e4b76 100644
--- a/runtime/doc/usr_10.txt
+++ b/runtime/doc/usr_10.txt
@@ -192,7 +192,7 @@ following: >
 Vim finds the first occurrence of "Professor" and displays the text it is
 about to change.  You get the following prompt: >
 
-	replace with Teacher (y/n/a/q/l/^E/^Y)?
+	replace with Teacher? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)
 
 At this point, you must enter one of the following answers:
 
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 0510619825..073b9594ed 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -3793,10 +3793,11 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
 
               curwin->w_p_fen = save_p_fen;
 
-              snprintf(IObuff, IOSIZE, _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
-              char *prompt = xstrdup(IObuff);
-              typed = prompt_for_input(prompt, HLF_R, true, NULL);
-              xfree(prompt);
+              char *p = _("replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)");
+              snprintf(IObuff, IOSIZE, p, sub);
+              p = xstrdup(IObuff);
+              typed = prompt_for_input(p, HLF_R, true, NULL);
+              xfree(p);
 
               p_lz = save_p_lz;
               RedrawingDisabled = temp;
diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po
index 64acd93f57..3d537a51f2 100644
--- a/src/nvim/po/af.po
+++ b/src/nvim/po/af.po
@@ -1353,8 +1353,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Patrone kan nie deur letters afgebaken word nie"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "vervang met %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "vervang met %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up (^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Onderbreek) "
diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po
index 44975d9161..2162f13592 100644
--- a/src/nvim/po/ca.po
+++ b/src/nvim/po/ca.po
@@ -1241,8 +1241,8 @@ msgstr "E146: Les expressions regulars no poden estar delimitades per lletres"
 # amb o per + tecles.  eac
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "substituir amb %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "substituir amb %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po
index 43b6c82960..4939a150ac 100644
--- a/src/nvim/po/cs.cp1250.po
+++ b/src/nvim/po/cs.cp1250.po
@@ -1255,8 +1255,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "nahradit za %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "nahradit za %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po
index 7df69e061f..9cb552c6de 100644
--- a/src/nvim/po/cs.po
+++ b/src/nvim/po/cs.po
@@ -1255,8 +1255,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "nahradit za %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "nahradit za %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po
index ed9e357092..8a7bffdd53 100644
--- a/src/nvim/po/da.po
+++ b/src/nvim/po/da.po
@@ -984,8 +984,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Regulære udtryk kan ikke afgrænses af bogstaver"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "erstat med %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "erstat med %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Afbrudt) "
diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po
index 0d1195eecf..0eb72b9565 100644
--- a/src/nvim/po/de.po
+++ b/src/nvim/po/de.po
@@ -666,8 +666,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3958
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "ersetze durch %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "ersetze durch %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4373
 msgid "(Interrupted) "
diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po
index 7b849d4e68..f93dcad206 100644
--- a/src/nvim/po/en_GB.po
+++ b/src/nvim/po/en_GB.po
@@ -1199,8 +1199,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Regular expressions cannot be delimited by letters"
 
 #: ../ex_cmds.c:3964
-#, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
+#, c-format"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 msgstr ""
 
 #: ../ex_cmds.c:4379
diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po
index 5cd18ad24b..4033ff8a43 100644
--- a/src/nvim/po/eo.po
+++ b/src/nvim/po/eo.po
@@ -950,8 +950,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Ne eblas limigi regulesprimon per literoj"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "ĉu anstataŭigi per %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "ĉu anstataŭigi per %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Interrompita) "
diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po
index 292ca15264..b67c275bf3 100644
--- a/src/nvim/po/es.po
+++ b/src/nvim/po/es.po
@@ -1243,8 +1243,8 @@ msgstr "E146: Las expresiones regulares no se pueden delimitar con letras"
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "¿Reemplazar con %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "¿Reemplazar con %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index c9cad6cc69..e1700d61ff 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -1521,8 +1521,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Säännöllistä ilmausta ei voi rajata kirjaimilla"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "korvaa kohteella %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "korvaa kohteella %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Keskeytetty)"
diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po
index b561347ab8..900f5bde03 100644
--- a/src/nvim/po/fr.po
+++ b/src/nvim/po/fr.po
@@ -855,8 +855,8 @@ msgstr ""
 "lettres"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "remplacer par %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "remplacer par %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Interrompu) "
diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po
index 7a5893b5f2..902bf9fae1 100644
--- a/src/nvim/po/ga.po
+++ b/src/nvim/po/ga.po
@@ -972,8 +972,8 @@ msgstr ""
 "E146: N cheadatear litreacha mar theormharcir ar shloinn ionadaochta"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "cuir %s ina ionad (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "cuir %s? ina ionad (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Idirbhriste) "
diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po
index d02052ec39..5caaa3c1f0 100644
--- a/src/nvim/po/it.po
+++ b/src/nvim/po/it.po
@@ -1227,8 +1227,8 @@ msgstr "E146: Le espressioni regolari non possono essere delimitate da lettere"
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "sostituire con %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "sostituire con %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po
index 78f927c21d..02bd36efbb 100644
--- a/src/nvim/po/ja.euc-jp.po
+++ b/src/nvim/po/ja.euc-jp.po
@@ -867,8 +867,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: ɽʸǶڤ뤳ȤǤޤ"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "%s ִޤ? (y/n/a/q/l/^E/^Y)"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "%s ִޤ? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(ޤޤ) "
diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po
index 1a493c0336..8d417d222a 100644
--- a/src/nvim/po/ja.po
+++ b/src/nvim/po/ja.po
@@ -2998,8 +2998,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: 正規表現は文字で区切ることができません"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "%s に置換しますか? (y/n/a/q/l/^E/^Y)"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "%s に置換しますか? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(割込まれました) "
diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po
index 03dff518f3..66463713d7 100644
--- a/src/nvim/po/ko.UTF-8.po
+++ b/src/nvim/po/ko.UTF-8.po
@@ -1221,8 +1221,8 @@ msgstr "E146: 정규표현식은 글자로 구분될 수 없습니다"
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "%s(으)로 바꿈 (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "%s(으)로 바꿈? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po
index d512789246..6f10dcc232 100644
--- a/src/nvim/po/nb.po
+++ b/src/nvim/po/nb.po
@@ -1236,8 +1236,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "Erstatt med %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "Erstatt med %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po
index f6ce5ddc9f..75d516a67d 100644
--- a/src/nvim/po/nl.po
+++ b/src/nvim/po/nl.po
@@ -1223,8 +1223,8 @@ msgstr "E146: reguliere expressies kunnen niet begrensd worden door letters"
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "vervang door %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "vervang door %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po
index d512789246..6f10dcc232 100644
--- a/src/nvim/po/no.po
+++ b/src/nvim/po/no.po
@@ -1236,8 +1236,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "Erstatt med %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "Erstatt med %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po
index b7200a7ba8..521edb383d 100644
--- a/src/nvim/po/pl.UTF-8.po
+++ b/src/nvim/po/pl.UTF-8.po
@@ -1207,8 +1207,8 @@ msgstr "E146: Wzorce regularne nie mogą być rozgraniczane literami"
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "zamień na %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "zamień na %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po
index 3f2f87991c..4e7225fc9c 100644
--- a/src/nvim/po/pt_BR.po
+++ b/src/nvim/po/pt_BR.po
@@ -4216,8 +4216,8 @@ msgstr "E146: Express
 
 #: ../ex_cmds.c:3958
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "substituir por %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "substituir por %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4373
 msgid "(Interrupted) "
diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po
index 64c18f890c..90dc5b2d8e 100644
--- a/src/nvim/po/ru.po
+++ b/src/nvim/po/ru.po
@@ -1215,8 +1215,8 @@ msgstr "E146: Регулярные выражения не могут разде
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "заменить на %s? (y/n/a/q/l/^E/^Y)"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "заменить на %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po
index 9510a357f0..394b5aff01 100644
--- a/src/nvim/po/sk.cp1250.po
+++ b/src/nvim/po/sk.cp1250.po
@@ -1224,8 +1224,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "nahradi %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "nahradi %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po
index 21791e0061..79ed3e9539 100644
--- a/src/nvim/po/sk.po
+++ b/src/nvim/po/sk.po
@@ -1224,8 +1224,8 @@ msgstr "E146: Regul
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "nahradi %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "nahradi %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po
index 1cbb37eeb1..79b6747921 100644
--- a/src/nvim/po/sr.po
+++ b/src/nvim/po/sr.po
@@ -1201,8 +1201,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Регуларни изрази не могу да се раздвајају словима"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "заменити са %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "заменити са %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Прекинуто)"
diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po
index 89a7717746..285dadb595 100644
--- a/src/nvim/po/sv.po
+++ b/src/nvim/po/sv.po
@@ -2628,8 +2628,8 @@ msgstr "E146: Regulj
 
 #: ../ex_cmds.c:3958
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "erstt med %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "erstt med %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4373
 msgid "(Interrupted) "
diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po
index a4c7926c67..e73c033a4e 100644
--- a/src/nvim/po/tr.po
+++ b/src/nvim/po/tr.po
@@ -1867,8 +1867,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Düzenli ifadeler harflerle sınırlandırılamaz"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "%s ile değiştir (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "%s ile değiştir? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Yarıda kesildi) "
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index ff6fe9a33e..25b31dac9b 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -3008,8 +3008,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Регулярні вирази не можна розділяти літерами"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "Замінити на %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "Замінити на %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(Перервано) "
diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po
index bcc1e0af8e..b976d39647 100644
--- a/src/nvim/po/vi.po
+++ b/src/nvim/po/vi.po
@@ -779,8 +779,8 @@ msgid "E146: Regular expressions can't be delimited by letters"
 msgstr "E146: Không thể phân cách biểu thức chính quy bằng chữ cái"
 
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "thay thế bằng %s? (y/n/a/q/l/^E/^Y)"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "thay thế bằng %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 msgid "(Interrupted) "
 msgstr "(bị dừng)"
diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po
index e30fc47806..3593175572 100644
--- a/src/nvim/po/zh_CN.UTF-8.po
+++ b/src/nvim/po/zh_CN.UTF-8.po
@@ -1991,8 +1991,8 @@ msgstr "E146: 正则表达式不能用字母作分界"
 #. Same highlight as wait_return().
 #: ../ex_cmds.c:3952
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "替换为 %s (y/n/a/q/l/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "替换为 %s?(y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4462
 msgid "(Interrupted) "
diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po
index cbbc4ad6eb..04e11a9193 100644
--- a/src/nvim/po/zh_TW.UTF-8.po
+++ b/src/nvim/po/zh_TW.UTF-8.po
@@ -1267,8 +1267,8 @@ msgstr "E146: Regular expression 無法用字母分隔 (?)"
 
 #: ../ex_cmds.c:3964
 #, c-format
-msgid "replace with %s (y/n/a/q/l/^E/^Y)?"
-msgstr "取代為 %s (y/n/a/q/^E/^Y)?"
+msgid "replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
+msgstr "取代為 %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)"
 
 #: ../ex_cmds.c:4379
 msgid "(Interrupted) "
diff --git a/test/functional/legacy/substitute_spec.lua b/test/functional/legacy/substitute_spec.lua
index 0081371f7f..30ff13140f 100644
--- a/test/functional/legacy/substitute_spec.lua
+++ b/test/functional/legacy/substitute_spec.lua
@@ -220,8 +220,10 @@ describe(':substitute', function()
       {2:o}ne                                                         |
       two                                                         |
       three                                                       |
-      {1:~                                                           }|*4
-      {6:replace with     (y/n/a/q/l/^E/^Y)?}^                         |
+      {1:~                                                           }|*2
+      {3:                                                            }|
+      {6:replace with    ? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^}|
+      {6:E)/down(^Y)}^                                                 |
     ]])
   end)
 end)
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 1f7d5525f3..93ea2b9186 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -1485,10 +1485,10 @@ describe('cmdheight=0', function()
   it('when substitute text', function()
     command('set cmdheight=0 noruler laststatus=3')
     feed('ifoo')
-    screen:try_resize(screen._width, 6)
+    screen:try_resize(screen._width, 7)
     screen:expect([[
       fo^o                      |
-      {1:~                        }|*4
+      {1:~                        }|*5
       {3:[No Name] [+]            }|
     ]])
 
@@ -1497,14 +1497,15 @@ describe('cmdheight=0', function()
       {2:foo}                      |
       {3:                         }|
                                |*2
-      {6:replace with bar (y/n/a/q}|
-      {6:/l/^E/^Y)?}^               |
+      {6:replace with bar? (y)es/(}|
+      {6:n)o/(a)ll/(q)uit/(l)ast/s}|
+      {6:croll up(^E)/down(^Y)}^    |
     ]])
 
     feed('y')
     screen:expect([[
       ^bar                      |
-      {1:~                        }|*4
+      {1:~                        }|*5
       {3:[No Name] [+]            }|
     ]])
 
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 56b2c61fb8..1e51652c4f 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -141,7 +141,7 @@ describe('ui/ext_messages', function()
           content = { { '' } },
           hl_id = 18,
           pos = 0,
-          prompt = 'replace with X (y/n/a/q/l/^E/^Y)?',
+          prompt = 'replace with X? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)',
         },
       },
     })
-- 
cgit 


From f1f42ec063795f9461d9744d62eaaa6cc978ea56 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 3 Jan 2025 10:42:40 +0100
Subject: vim-patch:bde76da: runtime(jj): Support diffs in jj syntax

related: vim/vim#16364

https://github.com/vim/vim/commit/bde76da4d02d93d8ea9e523d1057d59a19de276a

Co-authored-by: Gregory Anders 
---
 runtime/syntax/jj.vim | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/runtime/syntax/jj.vim b/runtime/syntax/jj.vim
index a2911a0268..0b2d29e878 100644
--- a/runtime/syntax/jj.vim
+++ b/runtime/syntax/jj.vim
@@ -6,7 +6,6 @@
 if exists('b:current_syntax')
   finish
 endif
-let b:current_syntax = 'jj'
 
 syn match jjAdded "A .*" contained
 syn match jjRemoved "D .*" contained
@@ -14,7 +13,12 @@ syn match jjChanged "M .*" contained
 
 syn region jjComment start="^JJ: " end="$" contains=jjAdded,jjRemoved,jjChanged
 
+syn include @jjCommitDiff syntax/diff.vim
+syn region jjCommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@jjCommitDiff
+
 hi def link jjComment Comment
 hi def link jjAdded Added
 hi def link jjRemoved Removed
 hi def link jjChanged Changed
+
+let b:current_syntax = 'jj'
-- 
cgit 


From b365036ab3f5e91439a5397ed0f32b651d60f08c Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 3 Jan 2025 10:42:52 +0100
Subject: vim-patch:9.1.0986: filetype: 'jj' filetype is a bit imprecise

Problem:  filetype: 'jj' filetype is a bit imprecise
Solution: rename 'jj' filetype to 'jjdescription'
          (Gregory Anders)

closes: vim/vim#16364

https://github.com/vim/vim/commit/58c44e8833365e1a777330491c2799ae324ed893

Co-authored-by: Gregory Anders 
---
 runtime/ftplugin/jj.vim            | 19 -------------------
 runtime/ftplugin/jjdescription.vim | 19 +++++++++++++++++++
 runtime/lua/vim/filetype.lua       |  2 +-
 runtime/syntax/jj.vim              | 24 ------------------------
 runtime/syntax/jjdescription.vim   | 24 ++++++++++++++++++++++++
 test/old/testdir/test_filetype.vim |  2 +-
 6 files changed, 45 insertions(+), 45 deletions(-)
 delete mode 100644 runtime/ftplugin/jj.vim
 create mode 100644 runtime/ftplugin/jjdescription.vim
 delete mode 100644 runtime/syntax/jj.vim
 create mode 100644 runtime/syntax/jjdescription.vim

diff --git a/runtime/ftplugin/jj.vim b/runtime/ftplugin/jj.vim
deleted file mode 100644
index cc5d700a30..0000000000
--- a/runtime/ftplugin/jj.vim
+++ /dev/null
@@ -1,19 +0,0 @@
-" Vim filetype plugin
-" Language:	jj description
-" Maintainer:	Gregory Anders 
-" Last Change:	2024 May 8
-
-if exists('b:did_ftplugin')
-  finish
-endif
-let b:did_ftplugin = 1
-
-" Use the same formatoptions and textwidth as the gitcommit ftplugin
-setlocal nomodeline formatoptions+=tl textwidth=72
-setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions-=q formatoptions+=n
-setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}]\\s\\+\\\|^\\s*[-*+]\\s\\+
-
-setlocal comments=b:JJ:
-setlocal commentstring=JJ:\ %s
-
-let b:undo_ftplugin = 'setl modeline< formatoptions< textwidth< formatlistpat< comments< commentstring<'
diff --git a/runtime/ftplugin/jjdescription.vim b/runtime/ftplugin/jjdescription.vim
new file mode 100644
index 0000000000..cc5d700a30
--- /dev/null
+++ b/runtime/ftplugin/jjdescription.vim
@@ -0,0 +1,19 @@
+" Vim filetype plugin
+" Language:	jj description
+" Maintainer:	Gregory Anders 
+" Last Change:	2024 May 8
+
+if exists('b:did_ftplugin')
+  finish
+endif
+let b:did_ftplugin = 1
+
+" Use the same formatoptions and textwidth as the gitcommit ftplugin
+setlocal nomodeline formatoptions+=tl textwidth=72
+setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions-=q formatoptions+=n
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}]\\s\\+\\\|^\\s*[-*+]\\s\\+
+
+setlocal comments=b:JJ:
+setlocal commentstring=JJ:\ %s
+
+let b:undo_ftplugin = 'setl modeline< formatoptions< textwidth< formatlistpat< comments< commentstring<'
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 173de8b5d5..849bc4af92 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -631,7 +631,7 @@ local extension = {
   clp = 'jess',
   jgr = 'jgraph',
   jinja = 'jinja',
-  jjdescription = 'jj',
+  jjdescription = 'jjdescription',
   j73 = 'jovial',
   jov = 'jovial',
   jovial = 'jovial',
diff --git a/runtime/syntax/jj.vim b/runtime/syntax/jj.vim
deleted file mode 100644
index 0b2d29e878..0000000000
--- a/runtime/syntax/jj.vim
+++ /dev/null
@@ -1,24 +0,0 @@
-" Vim syntax file
-" Language:	jj description
-" Maintainer:	Gregory Anders 
-" Last Change:	2024 May 8
-
-if exists('b:current_syntax')
-  finish
-endif
-
-syn match jjAdded "A .*" contained
-syn match jjRemoved "D .*" contained
-syn match jjChanged "M .*" contained
-
-syn region jjComment start="^JJ: " end="$" contains=jjAdded,jjRemoved,jjChanged
-
-syn include @jjCommitDiff syntax/diff.vim
-syn region jjCommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@jjCommitDiff
-
-hi def link jjComment Comment
-hi def link jjAdded Added
-hi def link jjRemoved Removed
-hi def link jjChanged Changed
-
-let b:current_syntax = 'jj'
diff --git a/runtime/syntax/jjdescription.vim b/runtime/syntax/jjdescription.vim
new file mode 100644
index 0000000000..04848bcb3b
--- /dev/null
+++ b/runtime/syntax/jjdescription.vim
@@ -0,0 +1,24 @@
+" Vim syntax file
+" Language:	jj description
+" Maintainer:	Gregory Anders 
+" Last Change:	2024 May 8
+
+if exists('b:current_syntax')
+  finish
+endif
+
+syn match jjAdded "A .*" contained
+syn match jjRemoved "D .*" contained
+syn match jjChanged "M .*" contained
+
+syn region jjComment start="^JJ: " end="$" contains=jjAdded,jjRemoved,jjChanged
+
+syn include @jjCommitDiff syntax/diff.vim
+syn region jjCommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@jjCommitDiff
+
+hi def link jjComment Comment
+hi def link jjAdded Added
+hi def link jjRemoved Removed
+hi def link jjChanged Changed
+
+let b:current_syntax = 'jjdescription'
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 1bf2bdb360..d5853adf49 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -389,7 +389,7 @@ func s:GetFilenameChecks() abort
     \ 'jess': ['file.clp'],
     \ 'jgraph': ['file.jgr'],
     \ 'jinja': ['file.jinja'],
-    \ 'jj': ['file.jjdescription'],
+    \ 'jjdescription': ['file.jjdescription'],
     \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
     \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'],
     \ 'jq': ['file.jq'],
-- 
cgit 


From fa298fd2f4b0c46df434938cbc17467e263cc094 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 3 Jan 2025 10:46:22 +0100
Subject: vim-patch:9.1.0987: filetype: cake files are not recognized

Problem:  filetype: cake files are not recognized
Solution: detect '*.cake' files as cs filetype
          (Zoe Roux)

References:
https://cakebuild.net/

closes: vim/vim#16367

https://github.com/vim/vim/commit/a407573f30a978b3aa61532bbd9b0ae94a87dc32

Co-authored-by: Zoe Roux 
---
 runtime/lua/vim/filetype.lua       | 1 +
 test/old/testdir/test_filetype.vim | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 849bc4af92..b6d6906589 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -353,6 +353,7 @@ local extension = {
   cql = 'cqlang',
   crm = 'crm',
   cr = 'crystal',
+  cake = 'cs',
   csx = 'cs',
   cs = 'cs',
   csc = 'csc',
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index d5853adf49..685f700130 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -195,7 +195,7 @@ func s:GetFilenameChecks() abort
     \ 'crm': ['file.crm'],
     \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'],
     \ 'crystal': ['file.cr'],
-    \ 'cs': ['file.cs', 'file.csx'],
+    \ 'cs': ['file.cs', 'file.csx', 'file.cake'],
     \ 'csc': ['file.csc'],
     \ 'csdl': ['file.csdl'],
     \ 'csp': ['file.csp', 'file.fdr'],
-- 
cgit 


From c26951b1d6d4d7ff8fe431e8bfb16744ff56af1c Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Fri, 3 Jan 2025 19:20:21 +0800
Subject: vim-patch:fc61cfd: runtime(vim): Update matchit pattern, no Vim9
 short names (#31843)

Abbreviated :enum and :interface commands are no longer supported.

https://github.com/vim/vim/commit/fc61cfd60e6d99765d1a68d39d6613e0917c7c56

Co-authored-by: Doug Kearns 
---
 runtime/ftplugin/vim.vim | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim
index b5e8e693f6..3eaf748996 100644
--- a/runtime/ftplugin/vim.vim
+++ b/runtime/ftplugin/vim.vim
@@ -1,9 +1,9 @@
 " Vim filetype plugin
 " Language:		Vim
 " Maintainer:		Doug Kearns 
-" Last Change:		2024 Apr 13
-" 			2024 May 23 by Riley Bruins  ('commentstring')
+" Last Change:		2025 Jan 3
 " Former Maintainer:	Bram Moolenaar 
+" Contributors:		Riley Bruins  ('commentstring')
 
 " Only do this when not done yet for this buffer
 if exists("b:did_ftplugin")
@@ -103,8 +103,8 @@ if exists("loaded_matchit")
 	\ '\:\%(\%(^\||\)\s*\)\@<=\:\%(\%(^\||\)\s*\)\@<=\:\%(\%(^\||\)\s*\)\@<=\,' ..
 	\ '\\)\@!\S:\,' ..
 	\ '\:\,' ..
-	\ '\:\,' ..
-	\ '\:\,'
+	\ '\:\,' ..
+	\ '\:\,'
 
   " Ignore syntax region commands and settings, any 'en*' would clobber
   " if-endif.
-- 
cgit 


From 21718c67dd9625c53d519a63725db55be6abe2ff Mon Sep 17 00:00:00 2001
From: luukvbaal 
Date: Fri, 3 Jan 2025 17:25:06 +0100
Subject: fix(messages): better formatting for ext_messages #31839

Problem:  Message grid newline formatting based on `msg_col` is not
          utilized with ext_messages.
Solution: Increment `msg_col` with the cell width of the chunk. Allowing
          message code that uses `msg_col` to determine when to place a
          newline to do so. E.g. when the message goes beyond `Columns`;
          this is not necessarily where the ext_messages implementation
          would want to place a newline, but it is a best guess. Message
          parsing and manipulation is still possible.
---
 src/nvim/digraph.c                   |  1 +
 src/nvim/message.c                   |  1 +
 test/functional/ui/messages_spec.lua | 17 ++++++++++++++++-
 3 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 876d38ea83..aaa77f5fcf 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -1714,6 +1714,7 @@ void listdigraphs(bool use_headers)
 {
   result_T previous = 0;
 
+  msg_ext_set_kind("list_cmd");
   msg_putchar('\n');
 
   const digr_T *dp = digraphdefault;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 69e8f66bbe..661d0754d4 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2222,6 +2222,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse
     size_t len = maxlen < 0 ? strlen(str) : strnlen(str, (size_t)maxlen);
     ga_concat_len(&msg_ext_last_chunk, str, len);
     msg_ext_cur_len += len;
+    msg_col += (int)mb_string2cells(str);
     // When message ends in newline, reset variables used to format message: msg_advance().
     assert(len > 0);
     if (str[len - 1] == '\n') {
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 1e51652c4f..77ffc475b0 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -1121,7 +1121,7 @@ stack traceback:
     ]],
       messages = {
         {
-          content = { { 'wildmenu  wildmode' } },
+          content = { { 'wildmenu  wildmode\n' } },
           history = false,
           kind = 'wildlist',
         },
@@ -1335,6 +1335,21 @@ stack traceback:
     feed('i')
     n.assert_alive()
   end)
+
+  it(':digraph contains newlines', function()
+    command('digraph')
+    screen:expect({
+      condition = function()
+        local nl = 0
+        eq('list_cmd', screen.messages[1].kind)
+        for _, chunk in ipairs(screen.messages[1].content) do
+          nl = nl + (chunk[2]:find('\n') and 1 or 0)
+        end
+        eq(682, nl)
+        screen.messages = {}
+      end,
+    })
+  end)
 end)
 
 describe('ui/builtin messages', function()
-- 
cgit 


From b52531a9cbbd1843490333452cd124e8be070690 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Fri, 3 Jan 2025 08:29:36 -0800
Subject: docs: misc #31822

* docs: drop "lua-" prefix from most treesitter tags
* docs: move mouse section from tui.txt to gui.txt
* docs: misc
---
 runtime/doc/gui.txt          | 186 ++++++++++++++++++++++++++++++++++++++++++
 runtime/doc/lua.txt          |   6 +-
 runtime/doc/treesitter.txt   |  10 +--
 runtime/doc/tui.txt          | 188 +------------------------------------------
 runtime/lua/vim/_options.lua |   6 +-
 scripts/gen_vimdoc.lua       |   4 +-
 6 files changed, 199 insertions(+), 201 deletions(-)

diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt
index eb787af3c9..6fc5b27580 100644
--- a/runtime/doc/gui.txt
+++ b/runtime/doc/gui.txt
@@ -67,6 +67,192 @@ Example: this sets "g:gui" to the value of the UI's "rgb" field:    >
                 Set the window height to {width} by {height} characters.
                 Obsolete, use ":set lines=11 columns=22".
 
+==============================================================================
+Using the mouse                                         *mouse-using*
+
+                                        *mouse-mode-table* *mouse-overview*
+Overview of what the mouse buttons do, when 'mousemodel' is "extend":
+
+               ** ** ** **
+                                                 ** **
+Normal Mode: >
+  event         position     selection        change  action
+                 cursor                       window
+  ---------------------------------------------------------------------------
+       yes          end              yes
+     yes          end              yes    "CTRL-]" (2)
+     yes       no change           yes    "*" (2)
+        yes     start or extend (1)   no
+     yes     start or extend (1)   no
+     yes       if not active       no     put
+     yes       if active           no     yank and put
+      yes     start or extend       yes
+    yes start or extend blockw.   yes
+    yes        no change          yes    "#" (2)
+    no         no change          no     "CTRL-T"
+       yes         extend            no
+    yes         extend            no
+
+Insert or Replace Mode: >
+  event         position     selection        change  action
+                 cursor                       window
+  ---------------------------------------------------------------------------
+       yes     (cannot be active)    yes
+     yes     (cannot be active)    yes    "CTRL-O^]" (2)
+     yes     (cannot be active)    yes    "CTRL-O*" (2)
+        yes     start or extend (1)   no     like CTRL-O (1)
+     yes     start or extend (1)   no     like CTRL-O (1)
+     no      (cannot be active)    no     put register
+      yes     start or extend       yes    like CTRL-O
+    yes start or extend blockw.   yes
+    yes     (cannot be active)    yes    "CTRL-O#" (2)
+    no      (cannot be active)    no     "CTRL-O CTRL-T"
+
+In a help window: >
+  event         position     selection        change  action
+                 cursor                       window
+  ---------------------------------------------------------------------------
+  <2-LeftMouse>   yes     (cannot be active)    no     "^]" (jump to help tag)
+
+When 'mousemodel' is "popup", these are different:
+
+                                                               **
+Normal Mode: >
+  event         position     selection        change  action
+                 cursor                       window
+  ---------------------------------------------------------------------------
+     yes     start or extend (1)   no
+     yes     start/extend blockw   no
+      no      popup menu            no
+
+Insert or Replace Mode: >
+  event         position     selection        change  action
+                 cursor                       window
+  ---------------------------------------------------------------------------
+     yes     start or extend (1)   no     like CTRL-O (1)
+     yes     start/extend blockw   no
+      no      popup menu            no
+
+(1) only if mouse pointer moved since press
+(2) only if click is in same buffer
+
+Clicking the left mouse button causes the cursor to be positioned.  If the
+click is in another window that window is made the active window.  When
+editing the command-line the cursor can only be positioned on the
+command-line.  When in Insert mode Vim remains in Insert mode.  If 'scrolloff'
+is set, and the cursor is positioned within 'scrolloff' lines from the window
+border, the text is scrolled.
+
+A selection can be started by pressing the left mouse button on the first
+character, moving the mouse to the last character, then releasing the mouse
+button.  You will not always see the selection until you release the button,
+only in some versions (GUI, Win32) will the dragging be shown immediately.
+Note that you can make the text scroll by moving the mouse at least one
+character in the first/last line in the window when 'scrolloff' is non-zero.
+
+In Normal, Visual and Select mode clicking the right mouse button causes the
+Visual area to be extended.  When 'mousemodel' is "popup", the left button has
+to be used while keeping the shift key pressed.  When clicking in a window
+which is editing another buffer, the Visual or Select mode is stopped.
+
+In Normal, Visual and Select mode clicking the right mouse button with the alt
+key pressed causes the Visual area to become blockwise.  When 'mousemodel' is
+"popup" the left button has to be used with the alt key.  Note that this won't
+work on systems where the window manager consumes the mouse events when the
+alt key is pressed (it may move the window).
+
+                *double-click* *<2-LeftMouse>* *<3-LeftMouse>* *<4-LeftMouse>*
+Double, triple and quadruple clicks are supported.  For selecting text, extra
+clicks extend the selection: >
+
+        click           select
+        ---------------------------------
+        double          word or % match
+        triple          line
+        quadruple       rectangular block
+
+Exception: In a :help window, double-click jumps to help for the word that is
+clicked on.
+
+Double-click on a word selects that word.  'iskeyword' is used to specify
+which characters are included in a word.  Double-click on a character that has
+a match selects until that match (like using "v%").  If the match is an
+#if/#else/#endif block, the selection becomes linewise. The time for
+double-clicking can be set with the 'mousetime' option.
+
+Example: configure double-click to jump to the tag under the cursor: >vim
+        :map <2-LeftMouse> :exe "tag " .. expand("")
+
+Dragging the mouse with a double-click (button-down, button-up, button-down
+and then drag) will result in whole words to be selected.  This continues
+until the button is released, at which point the selection is per character
+again.
+
+For scrolling with the mouse see |scroll-mouse-wheel|.
+
+In Insert mode, when a selection is started, Vim goes into Normal mode
+temporarily.  When Visual or Select mode ends, it returns to Insert mode.
+This is like using CTRL-O in Insert mode.  Select mode is used when the
+'selectmode' option contains "mouse".
+
+                                                *X1Mouse* *X1Drag* *X1Release*
+                                                *X2Mouse* *X2Drag* *X2Release*
+                                              ** **
+Mouse clicks can be mapped using these |keycodes|: >
+      code           mouse button              normal action
+  ---------------------------------------------------------------------------
+       left pressed               set cursor position
+        left moved while pressed   extend selection
+     left released              set selection end
+     middle pressed             paste text at cursor position
+      middle moved while pressed -
+   middle released            -
+      right pressed              extend selection
+       right moved while pressed  extend selection
+    right released             set selection end
+         X1 button pressed          -
+          X1 moved while pressed     -
+       X1 button release          -
+         X2 button pressed          -
+          X2 moved while pressed     -
+       X2 button release          -
+
+The X1 and X2 buttons refer to the extra buttons found on some mice (e.g. the
+right thumb).
+
+Examples: >vim
+        :noremap  
+Paste at the position of the middle mouse button click (otherwise the paste
+would be done at the cursor position). >vim
+
+        :noremap  y
+Immediately yank the selection, when using Visual mode.
+
+Note the use of ":noremap" instead of "map" to avoid a recursive mapping.
+>vim
+        :map  
+        :map  
+Map the X1 and X2 buttons to go forwards and backwards in the jump list, see
+|CTRL-O| and |CTRL-I|.
+
+                                                *mouse-swap-buttons*
+To swap the meaning of the left and right mouse buttons: >vim
+        :noremap             
+        :noremap              
+        :noremap           
+        :noremap            
+        :noremap             
+        :noremap          
+        :noremap        g    
+        :noremap        g   
+        :noremap!            
+        :noremap!             
+        :noremap!          
+        :noremap!           
+        :noremap!            
+        :noremap!         
+<
+
 ==============================================================================
 Scrollbars                                              *gui-scrollbars*
 
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 463389ed65..b7c5a50443 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1473,10 +1473,8 @@ vim.go                                                                *vim.go*
 <
 
 vim.o                                                                  *vim.o*
-    Get or set |options|. Like `:set`. Invalid key is an error.
-
-    Note: this works on both buffer-scoped and window-scoped options using the
-    current buffer and window.
+    Get or set |options|. Works like `:set`, so buffer/window-scoped options
+    target the current buffer/window. Invalid key is an error.
 
     Example: >lua
         vim.o.cmdheight = 4
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 956fb80e73..28fe943359 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -157,12 +157,12 @@ The following predicates are built in:
                  (field_identifier) @method)) @_parent
              (#has-parent? @_parent template_method function_declarator))
 <
-                                                 *lua-treesitter-not-predicate*
+                                                    *treesitter-predicate-not*
 Each predicate has a `not-` prefixed predicate that is just the negation of
 the predicate.
 
-                                                 *lua-treesitter-all-predicate*
-                                                 *lua-treesitter-any-predicate*
+                                                    *treesitter-predicate-all*
+                                                    *treesitter-predicate-any*
 Queries can use quantifiers to capture multiple nodes. When a capture contains
 multiple nodes, predicates match only if ALL nodes contained by the capture
 match the predicate. Some predicates (`eq?`, `match?`, `lua-match?`,
@@ -1114,7 +1114,7 @@ stop({bufnr})                                          *vim.treesitter.stop()*
 
 
 ==============================================================================
-Lua module: vim.treesitter.language                  *lua-treesitter-language*
+Lua module: vim.treesitter.language                      *treesitter-language*
 
 add({lang}, {opts})                            *vim.treesitter.language.add()*
     Load parser with name {lang}
@@ -1463,7 +1463,7 @@ set({lang}, {query_name}, {text})                 *vim.treesitter.query.set()*
 
 
 ==============================================================================
-Lua module: vim.treesitter.languagetree          *lua-treesitter-languagetree*
+Lua module: vim.treesitter.languagetree              *treesitter-languagetree*
 
 A *LanguageTree* contains a tree of parsers: the root treesitter parser for
 {lang} and any "injected" language parsers, which themselves may inject other
diff --git a/runtime/doc/tui.txt b/runtime/doc/tui.txt
index 9493f91b1e..96ac54abc5 100644
--- a/runtime/doc/tui.txt
+++ b/runtime/doc/tui.txt
@@ -280,191 +280,5 @@ colours of whitespace are immaterial, in practice they change the colours of
 cursors and selections that cross them.  This may have a visible, but minor,
 effect on some UIs.
 
-==============================================================================
-Using the mouse						*mouse-using*
-
-					*mouse-mode-table* *mouse-overview*
-Overview of what the mouse buttons do, when 'mousemodel' is "extend":
-
-               ** ** ** **
-                                                 ** **
-Normal Mode: >
-  event         position     selection        change  action
-                 cursor                       window
-  ---------------------------------------------------------------------------
-       yes          end              yes
-     yes          end              yes    "CTRL-]" (2)
-     yes       no change           yes    "*" (2)
-        yes     start or extend (1)   no
-     yes     start or extend (1)   no
-     yes       if not active       no     put
-     yes       if active           no     yank and put
-      yes     start or extend       yes
-    yes start or extend blockw.   yes
-    yes        no change          yes    "#" (2)
-    no         no change          no     "CTRL-T"
-       yes         extend            no
-    yes         extend            no
-
-Insert or Replace Mode: >
-  event         position     selection        change  action
-                 cursor                       window
-  ---------------------------------------------------------------------------
-       yes     (cannot be active)    yes
-     yes     (cannot be active)    yes    "CTRL-O^]" (2)
-     yes     (cannot be active)    yes    "CTRL-O*" (2)
-        yes     start or extend (1)   no     like CTRL-O (1)
-     yes     start or extend (1)   no     like CTRL-O (1)
-     no      (cannot be active)    no     put register
-      yes     start or extend       yes    like CTRL-O
-    yes start or extend blockw.   yes
-    yes     (cannot be active)    yes    "CTRL-O#" (2)
-    no      (cannot be active)    no     "CTRL-O CTRL-T"
-
-In a help window: >
-  event         position     selection        change  action
-                 cursor                       window
-  ---------------------------------------------------------------------------
-  <2-LeftMouse>   yes     (cannot be active)    no     "^]" (jump to help tag)
-
-When 'mousemodel' is "popup", these are different:
-
-                                                               **
-Normal Mode: >
-  event         position     selection        change  action
-                 cursor                       window
-  ---------------------------------------------------------------------------
-     yes     start or extend (1)   no
-     yes     start/extend blockw   no
-      no      popup menu            no
-
-Insert or Replace Mode: >
-  event         position     selection        change  action
-                 cursor                       window
-  ---------------------------------------------------------------------------
-     yes     start or extend (1)   no     like CTRL-O (1)
-     yes     start/extend blockw   no
-      no      popup menu            no
-
-(1) only if mouse pointer moved since press
-(2) only if click is in same buffer
-
-Clicking the left mouse button causes the cursor to be positioned.  If the
-click is in another window that window is made the active window.  When
-editing the command-line the cursor can only be positioned on the
-command-line.  When in Insert mode Vim remains in Insert mode.  If 'scrolloff'
-is set, and the cursor is positioned within 'scrolloff' lines from the window
-border, the text is scrolled.
-
-A selection can be started by pressing the left mouse button on the first
-character, moving the mouse to the last character, then releasing the mouse
-button.  You will not always see the selection until you release the button,
-only in some versions (GUI, Win32) will the dragging be shown immediately.
-Note that you can make the text scroll by moving the mouse at least one
-character in the first/last line in the window when 'scrolloff' is non-zero.
-
-In Normal, Visual and Select mode clicking the right mouse button causes the
-Visual area to be extended.  When 'mousemodel' is "popup", the left button has
-to be used while keeping the shift key pressed.  When clicking in a window
-which is editing another buffer, the Visual or Select mode is stopped.
-
-In Normal, Visual and Select mode clicking the right mouse button with the alt
-key pressed causes the Visual area to become blockwise.  When 'mousemodel' is
-"popup" the left button has to be used with the alt key.  Note that this won't
-work on systems where the window manager consumes the mouse events when the
-alt key is pressed (it may move the window).
-
-                *double-click* *<2-LeftMouse>* *<3-LeftMouse>* *<4-LeftMouse>*
-Double, triple and quadruple clicks are supported when the GUI is active, for
-Win32 and for an xterm.  For selecting text, extra clicks extend the
-selection: >
-
-        click           select
-        ---------------------------------
-        double          word or % match
-        triple          line
-        quadruple       rectangular block
-
-Exception: In a Help window a double click jumps to help for the word that is
-clicked on.
-
-A double click on a word selects that word.  'iskeyword' is used to specify
-which characters are included in a word.  A double click on a character
-that has a match selects until that match (like using "v%").  If the match is
-an #if/#else/#endif block, the selection becomes linewise.
-For MS-Windows and xterm the time for double clicking can be set with the
-'mousetime' option. For the other systems this time is defined outside of Vim.
-An example, for using a double click to jump to the tag under the cursor: >vim
-        :map <2-LeftMouse> :exe "tag " .. expand("")
-
-Dragging the mouse with a double click (button-down, button-up, button-down
-and then drag) will result in whole words to be selected.  This continues
-until the button is released, at which point the selection is per character
-again.
-
-For scrolling with the mouse see |scroll-mouse-wheel|.
-
-In Insert mode, when a selection is started, Vim goes into Normal mode
-temporarily.  When Visual or Select mode ends, it returns to Insert mode.
-This is like using CTRL-O in Insert mode.  Select mode is used when the
-'selectmode' option contains "mouse".
-
-                                                *X1Mouse* *X1Drag* *X1Release*
-                                                *X2Mouse* *X2Drag* *X2Release*
-                                              ** **
-Mouse clicks can be mapped.  The codes for mouse clicks are: >
-      code           mouse button              normal action
-  ---------------------------------------------------------------------------
-       left pressed               set cursor position
-        left moved while pressed   extend selection
-     left released              set selection end
-     middle pressed             paste text at cursor position
-      middle moved while pressed -
-   middle released            -
-      right pressed              extend selection
-       right moved while pressed  extend selection
-    right released             set selection end
-         X1 button pressed          -
-          X1 moved while pressed     -
-       X1 button release          -
-         X2 button pressed          -
-          X2 moved while pressed     -
-       X2 button release          -
-
-The X1 and X2 buttons refer to the extra buttons found on some mice.  The
-'Microsoft Explorer' mouse has these buttons available to the right thumb.
-Currently X1 and X2 only work on Win32 and X11 environments.
-
-Examples: >vim
-        :noremap  
-Paste at the position of the middle mouse button click (otherwise the paste
-would be done at the cursor position). >vim
-
-        :noremap  y
-Immediately yank the selection, when using Visual mode.
-
-Note the use of ":noremap" instead of "map" to avoid a recursive mapping.
->vim
-        :map  
-        :map  
-Map the X1 and X2 buttons to go forwards and backwards in the jump list, see
-|CTRL-O| and |CTRL-I|.
-
-						*mouse-swap-buttons*
-To swap the meaning of the left and right mouse buttons: >vim
-        :noremap             
-        :noremap              
-        :noremap           
-        :noremap            
-        :noremap             
-        :noremap          
-        :noremap        g    
-        :noremap        g   
-        :noremap!            
-        :noremap!             
-        :noremap!          
-        :noremap!           
-        :noremap!            
-        :noremap!         
-<
+
  vim:et:sw=2:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
index 77d7054626..dc37595578 100644
--- a/runtime/lua/vim/_options.lua
+++ b/runtime/lua/vim/_options.lua
@@ -229,10 +229,8 @@ end
 --- global value of a |global-local| option, see |:setglobal|.
 --- 
---- Get or set |options|. Like `:set`. Invalid key is an error. ---- ---- Note: this works on both buffer-scoped and window-scoped options using the ---- current buffer and window. +--- Get or set |options|. Works like `:set`, so buffer/window-scoped options target the current +--- buffer/window. Invalid key is an error. --- --- Example: --- diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 34f1dc9e38..d200050fe1 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -350,12 +350,14 @@ local config = { helptag_fmt = function(name) if name:lower() == 'treesitter' then return 'lua-treesitter-core' + elseif name:lower() == 'query' then + return 'lua-treesitter-query' elseif name:lower() == 'tstree' then return { 'treesitter-tree', 'TSTree' } elseif name:lower() == 'tsnode' then return { 'treesitter-node', 'TSNode' } end - return 'lua-treesitter-' .. name:lower() + return 'treesitter-' .. name:lower() end, }, editorconfig = { -- cgit From fe87656f29e933b63f5d4dd03b3c0be3ed4ecf5f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 2 Jan 2025 21:17:27 +0100 Subject: fix(grid): grid_line_start NULL access with 'redrawdebug' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: This test causes a null pointer dereference: local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua') RUN T1565 startup -l Lua Lua-error sets Nvim exitcode: 241.00 ms OK ==================== File …/build/log/asan.13763 ==================== = …/src/nvim/grid.c:389:12: runtime error: null pointer passed as argument 1, which is declared to never be null = /usr/include/string.h:61:62: note: nonnull attribute specified here = 0 0x55cc2d869762 in grid_line_start …/src/nvim/grid.c:389:5 = 1 0x55cc2d8717ca in grid_clear …/src/nvim/grid.c:618:5 = 2 0x55cc2dbe0f6f in msg_clr_eos_force …/src/nvim/message.c:3085:3 = 3 0x55cc2dbbbdec in msg_clr_eos …/src/nvim/message.c:3061:5 = 4 0x55cc2dbbae2c in msg_multiline …/src/nvim/message.c:281:9 = 5 0x55cc2dbba2b4 in msg_keep …/src/nvim/message.c:364:5 = 6 0x55cc2dbc4992 in emsg_multiline …/src/nvim/message.c:773:10 = 7 0x55cc2dbc5d43 in semsg_multiline …/src/nvim/message.c:824:9 = 8 0x55cc2d9c5945 in nlua_error …/src/nvim/lua/executor.c:158:5 = 9 0x55cc2d9c89fd in nlua_exec_file …/src/nvim/lua/executor.c:1862:5 = 10 0x55cc2d9f4d69 in main …/src/nvim/main.c:637:19 = 11 0x7f319b62a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 = 12 0x7f319b62a28a in __libc_start_main csu/../csu/libc-start.c:360:3 = 13 0x55cc2ced0f64 in _start (…/build/bin/nvim+0xc48f64) (BuildId: 309c83f8d74297c89719dae9c271dd8ec23e64c3) Cause: The tests use `redrawdebug=invalid` by default, but `default_grid_alloc` skips calling `grid_alloc` when not `full_screen`. Solution: Check for `full_screen`. --- src/nvim/grid.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/grid.c b/src/nvim/grid.c index e863cb3476..df93ad1655 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -383,7 +383,8 @@ void grid_line_start(ScreenGrid *grid, int row) assert((size_t)grid_line_maxcol <= linebuf_size); - if (rdb_flags & kOptRdbFlagInvalid) { + if (full_screen && (rdb_flags & kOptRdbFlagInvalid)) { + assert(linebuf_char); // Current batch must not depend on previous contents of linebuf_char. // Set invalid values which will cause assertion failures later if they are used. memset(linebuf_char, 0xFF, sizeof(schar_T) * linebuf_size); -- cgit From a1ba655dee0f89230ea09712e4df981cc3b15bea Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 12 Sep 2024 03:04:33 +0200 Subject: test: spawn_wait() starts a non-RPC Nvim process Problem: Can't use `n.clear()` to test non-RPC `nvim` invocations. So tests end up creating ad-hoc wrappers around `system()` or `jobstart()`. Solution: - Introduce `n.spawn_wait()` - TODO (followup PR): Rename `n.spawn()` and `n.spawn_wait()`. It's misleading that `n.spawn()` returns a RPC session... --- MAINTAIN.md | 2 + runtime/lua/coxpcall.lua | 4 ++ src/nvim/main.c | 1 + test/client/msgpack_rpc_stream.lua | 105 ------------------------------- test/client/rpc_stream.lua | 112 ++++++++++++++++++++++++++++++++++ test/client/session.lua | 52 ++++++++++++---- test/client/uv_stream.lua | 99 ++++++++++++++++++++++++------ test/functional/core/startup_spec.lua | 14 ++--- test/functional/testnvim.lua | 71 ++++++++++++++------- 9 files changed, 297 insertions(+), 163 deletions(-) delete mode 100644 test/client/msgpack_rpc_stream.lua create mode 100644 test/client/rpc_stream.lua diff --git a/MAINTAIN.md b/MAINTAIN.md index cd3dacb964..1442faeff8 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -143,6 +143,8 @@ These dependencies are "vendored" (inlined), we must update the sources manually * `src/mpack/`: [libmpack](https://github.com/libmpack/libmpack) * send improvements upstream! +* `src/mpack/lmpack.c`: [libmpack-lua](https://github.com/libmpack/libmpack-lua) + * send improvements upstream! * `src/xdiff/`: [xdiff](https://github.com/git/git/tree/master/xdiff) * `src/cjson/`: [lua-cjson](https://github.com/openresty/lua-cjson) * `src/klib/`: [Klib](https://github.com/attractivechaos/klib) diff --git a/runtime/lua/coxpcall.lua b/runtime/lua/coxpcall.lua index 6b179f1ef0..75e7a43567 100644 --- a/runtime/lua/coxpcall.lua +++ b/runtime/lua/coxpcall.lua @@ -1,6 +1,10 @@ ------------------------------------------------------------------------------- +-- (Not needed for LuaJIT or Lua 5.2+) +-- -- Coroutine safe xpcall and pcall versions -- +-- https://keplerproject.github.io/coxpcall/ +-- -- Encapsulates the protected calls with a coroutine based loop, so errors can -- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines -- yielding inside the call to pcall or xpcall. diff --git a/src/nvim/main.c b/src/nvim/main.c index 348f246d27..2b55a48c12 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -634,6 +634,7 @@ int main(int argc, char **argv) if (params.luaf != NULL) { // Like "--cmd", "+", "-c" and "-S", don't truncate messages. msg_scroll = true; + DLOG("executing Lua -l script"); bool lua_ok = nlua_exec_file(params.luaf); TIME_MSG("executing Lua -l script"); if (msg_didout) { diff --git a/test/client/msgpack_rpc_stream.lua b/test/client/msgpack_rpc_stream.lua deleted file mode 100644 index 7131940a58..0000000000 --- a/test/client/msgpack_rpc_stream.lua +++ /dev/null @@ -1,105 +0,0 @@ -local mpack = vim.mpack - -local Response = {} -Response.__index = Response - -function Response.new(msgpack_rpc_stream, request_id) - return setmetatable({ - _msgpack_rpc_stream = msgpack_rpc_stream, - _request_id = request_id, - }, Response) -end - -function Response:send(value, is_error) - local data = self._msgpack_rpc_stream._session:reply(self._request_id) - if is_error then - data = data .. self._msgpack_rpc_stream._pack(value) - data = data .. self._msgpack_rpc_stream._pack(mpack.NIL) - else - data = data .. self._msgpack_rpc_stream._pack(mpack.NIL) - data = data .. self._msgpack_rpc_stream._pack(value) - end - self._msgpack_rpc_stream._stream:write(data) -end - ---- @class test.MsgpackRpcStream ---- @field private _stream test.Stream ---- @field private __pack table -local MsgpackRpcStream = {} -MsgpackRpcStream.__index = MsgpackRpcStream - -function MsgpackRpcStream.new(stream) - return setmetatable({ - _stream = stream, - _pack = mpack.Packer(), - _session = mpack.Session({ - unpack = mpack.Unpacker({ - ext = { - -- Buffer - [0] = function(_c, s) - return mpack.decode(s) - end, - -- Window - [1] = function(_c, s) - return mpack.decode(s) - end, - -- Tabpage - [2] = function(_c, s) - return mpack.decode(s) - end, - }, - }), - }), - }, MsgpackRpcStream) -end - -function MsgpackRpcStream:write(method, args, response_cb) - local data - if response_cb then - assert(type(response_cb) == 'function') - data = self._session:request(response_cb) - else - data = self._session:notify() - end - - data = data .. self._pack(method) .. self._pack(args) - self._stream:write(data) -end - -function MsgpackRpcStream:read_start(request_cb, notification_cb, eof_cb) - self._stream:read_start(function(data) - if not data then - return eof_cb() - end - local type, id_or_cb, method_or_error, args_or_result - local pos = 1 - local len = #data - while pos <= len do - type, id_or_cb, method_or_error, args_or_result, pos = self._session:receive(data, pos) - if type == 'request' or type == 'notification' then - if type == 'request' then - request_cb(method_or_error, args_or_result, Response.new(self, id_or_cb)) - else - notification_cb(method_or_error, args_or_result) - end - elseif type == 'response' then - if method_or_error == mpack.NIL then - method_or_error = nil - else - args_or_result = nil - end - id_or_cb(method_or_error, args_or_result) - end - end - end) -end - -function MsgpackRpcStream:read_stop() - self._stream:read_stop() -end - -function MsgpackRpcStream:close(signal) - self._stream:close(signal) -end - -return MsgpackRpcStream diff --git a/test/client/rpc_stream.lua b/test/client/rpc_stream.lua new file mode 100644 index 0000000000..9f2672bcf9 --- /dev/null +++ b/test/client/rpc_stream.lua @@ -0,0 +1,112 @@ +--- +--- Reading/writing of msgpack over any of the stream types from `uv_stream.lua`. +--- Does not implement the RPC protocol, see `session.lua` for that. +--- + +local mpack = vim.mpack + +local Response = {} +Response.__index = Response + +function Response.new(rpc_stream, request_id) + return setmetatable({ + _rpc_stream = rpc_stream, + _request_id = request_id, + }, Response) +end + +function Response:send(value, is_error) + local data = self._rpc_stream._session:reply(self._request_id) + if is_error then + data = data .. self._rpc_stream._pack(value) + data = data .. self._rpc_stream._pack(mpack.NIL) + else + data = data .. self._rpc_stream._pack(mpack.NIL) + data = data .. self._rpc_stream._pack(value) + end + self._rpc_stream._stream:write(data) +end + +--- Nvim msgpack RPC stream. +--- +--- @class test.RpcStream +--- @field private _stream test.Stream +--- @field private __pack table +local RpcStream = {} +RpcStream.__index = RpcStream + +function RpcStream.new(stream) + return setmetatable({ + _stream = stream, + _pack = mpack.Packer(), + _session = mpack.Session({ + unpack = mpack.Unpacker({ + ext = { + -- Buffer + [0] = function(_c, s) + return mpack.decode(s) + end, + -- Window + [1] = function(_c, s) + return mpack.decode(s) + end, + -- Tabpage + [2] = function(_c, s) + return mpack.decode(s) + end, + }, + }), + }), + }, RpcStream) +end + +function RpcStream:write(method, args, response_cb) + local data + if response_cb then + assert(type(response_cb) == 'function') + data = self._session:request(response_cb) + else + data = self._session:notify() + end + + data = data .. self._pack(method) .. self._pack(args) + self._stream:write(data) +end + +function RpcStream:read_start(on_request, on_notification, on_eof) + self._stream:read_start(function(data) + if not data then + return on_eof() + end + local type, id_or_cb, method_or_error, args_or_result + local pos = 1 + local len = #data + while pos <= len do + type, id_or_cb, method_or_error, args_or_result, pos = self._session:receive(data, pos) + if type == 'request' or type == 'notification' then + if type == 'request' then + on_request(method_or_error, args_or_result, Response.new(self, id_or_cb)) + else + on_notification(method_or_error, args_or_result) + end + elseif type == 'response' then + if method_or_error == mpack.NIL then + method_or_error = nil + else + args_or_result = nil + end + id_or_cb(method_or_error, args_or_result) + end + end + end) +end + +function RpcStream:read_stop() + self._stream:read_stop() +end + +function RpcStream:close(signal) + self._stream:close(signal) +end + +return RpcStream diff --git a/test/client/session.lua b/test/client/session.lua index f1f46c5efe..5b7f1a7caa 100644 --- a/test/client/session.lua +++ b/test/client/session.lua @@ -1,13 +1,21 @@ +--- +--- Nvim msgpack-RPC protocol session. Manages requests/notifications/responses. +--- + local uv = vim.uv -local MsgpackRpcStream = require('test.client.msgpack_rpc_stream') +local RpcStream = require('test.client.rpc_stream') +--- Nvim msgpack-RPC protocol session. Manages requests/notifications/responses. +--- --- @class test.Session ---- @field private _pending_messages string[] ---- @field private _msgpack_rpc_stream test.MsgpackRpcStream +--- @field private _pending_messages string[] Requests/notifications received from the remote end. +--- @field private _rpc_stream test.RpcStream --- @field private _prepare uv.uv_prepare_t --- @field private _timer uv.uv_timer_t ---- @field private _is_running boolean --- @field exec_lua_setup boolean +--- @field private _is_running boolean true during `Session:run()` scope. +--- @field private _stdout_buffer string[] Stores stdout chunks +--- @field public stdout string Full stdout after the process exits local Session = {} Session.__index = Session if package.loaded['jit'] then @@ -51,9 +59,10 @@ local function coroutine_exec(func, ...) end)) end +--- Creates a new msgpack-RPC session. function Session.new(stream) return setmetatable({ - _msgpack_rpc_stream = MsgpackRpcStream.new(stream), + _rpc_stream = RpcStream.new(stream), _pending_messages = {}, _prepare = uv.new_prepare(), _timer = uv.new_timer(), @@ -91,10 +100,13 @@ function Session:next_message(timeout) return table.remove(self._pending_messages, 1) end +--- Sends a notification to the RPC endpoint. function Session:notify(method, ...) - self._msgpack_rpc_stream:write(method, { ... }) + self._rpc_stream:write(method, { ... }) end +--- Sends a request to the RPC endpoint. +--- --- @param method string --- @param ... any --- @return boolean, table @@ -114,8 +126,16 @@ function Session:request(method, ...) return true, result end ---- Runs the event loop. +--- Processes incoming RPC requests/notifications until exhausted. +--- +--- TODO(justinmk): luaclient2 avoids this via uvutil.cb_wait() + uvutil.add_idle_call()? +--- +--- @param request_cb function Handles requests from the sever to the local end. +--- @param notification_cb function Handles notifications from the sever to the local end. +--- @param setup_cb function +--- @param timeout number function Session:run(request_cb, notification_cb, setup_cb, timeout) + --- Handles an incoming request. local function on_request(method, args, response) coroutine_exec(request_cb, method, args, function(status, result, flag) if status then @@ -126,6 +146,7 @@ function Session:run(request_cb, notification_cb, setup_cb, timeout) end) end + --- Handles an incoming notification. local function on_notification(method, args) coroutine_exec(notification_cb, method, args) end @@ -160,39 +181,45 @@ function Session:close(signal) if not self._prepare:is_closing() then self._prepare:close() end - self._msgpack_rpc_stream:close(signal) + self._rpc_stream:close(signal) self.closed = true end +--- Sends a request to the RPC endpoint, without blocking (schedules a coroutine). function Session:_yielding_request(method, args) return coroutine.yield(function(co) - self._msgpack_rpc_stream:write(method, args, function(err, result) + self._rpc_stream:write(method, args, function(err, result) resume(co, err, result) end) end) end +--- Sends a request to the RPC endpoint, and blocks (polls event loop) until a response is received. function Session:_blocking_request(method, args) local err, result + -- Invoked when a request is received from the remote end. local function on_request(method_, args_, response) table.insert(self._pending_messages, { 'request', method_, args_, response }) end + -- Invoked when a notification is received from the remote end. local function on_notification(method_, args_) table.insert(self._pending_messages, { 'notification', method_, args_ }) end - self._msgpack_rpc_stream:write(method, args, function(e, r) + self._rpc_stream:write(method, args, function(e, r) err = e result = r uv.stop() end) + -- Poll for incoming requests/notifications received from the remote end. self:_run(on_request, on_notification) return (err or self.eof_err), result end +--- Polls for incoming requests/notifications received from the remote end. function Session:_run(request_cb, notification_cb, timeout) if type(timeout) == 'number' then self._prepare:start(function() @@ -202,14 +229,15 @@ function Session:_run(request_cb, notification_cb, timeout) self._prepare:stop() end) end - self._msgpack_rpc_stream:read_start(request_cb, notification_cb, function() + self._rpc_stream:read_start(request_cb, notification_cb, function() uv.stop() self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' } end) uv.run() self._prepare:stop() self._timer:stop() - self._msgpack_rpc_stream:read_stop() + self._rpc_stream:read_stop() end +--- Nvim msgpack-RPC session. return Session diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index adf002ba1e..ac84cbf9bc 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -1,3 +1,8 @@ +--- +--- Basic stream types. +--- See `rpc_stream.lua` for the msgpack layer. +--- + local uv = vim.uv --- @class test.Stream @@ -6,6 +11,8 @@ local uv = vim.uv --- @field read_stop fun(self) --- @field close fun(self, signal?: string) +--- Stream over given pipes. +--- --- @class vim.StdioStream : test.Stream --- @field private _in uv.uv_pipe_t --- @field private _out uv.uv_pipe_t @@ -45,6 +52,8 @@ function StdioStream:close() self._out:close() end +--- Stream over a named pipe or TCP socket. +--- --- @class test.SocketStream : test.Stream --- @field package _stream_error? string --- @field package _socket uv.uv_pipe_t @@ -109,26 +118,46 @@ function SocketStream:close() uv.close(self._socket) end ---- @class test.ChildProcessStream : test.Stream +--- Stream over child process stdio. +--- +--- @class test.ProcStream : test.Stream --- @field private _proc uv.uv_process_t --- @field private _pid integer --- @field private _child_stdin uv.uv_pipe_t --- @field private _child_stdout uv.uv_pipe_t +--- @field private _child_stderr uv.uv_pipe_t +--- @field stdout string +--- @field stderr string +--- @field stdout_eof boolean +--- @field stderr_eof boolean +--- @field private collect_output boolean +--- Exit code --- @field status integer --- @field signal integer -local ChildProcessStream = {} -ChildProcessStream.__index = ChildProcessStream +local ProcStream = {} +ProcStream.__index = ProcStream +--- Starts child process specified by `argv`. +--- --- @param argv string[] --- @param env string[]? --- @param io_extra uv.uv_pipe_t? ---- @return test.ChildProcessStream -function ChildProcessStream.spawn(argv, env, io_extra) +--- @return test.ProcStream +function ProcStream.spawn(argv, env, io_extra) local self = setmetatable({ - _child_stdin = uv.new_pipe(false), - _child_stdout = uv.new_pipe(false), + collect_output = false, + output = '', + stdout = '', + stderr = '', + stdout_error = nil, -- TODO: not used, remove + stderr_error = nil, -- TODO: not used, remove + stdout_eof = false, + stderr_eof = false, + _child_stdin = assert(uv.new_pipe(false)), + _child_stdout = assert(uv.new_pipe(false)), + _child_stderr = assert(uv.new_pipe(false)), _exiting = false, - }, ChildProcessStream) + }, ProcStream) local prog = argv[1] local args = {} --- @type string[] for i = 2, #argv do @@ -136,13 +165,14 @@ function ChildProcessStream.spawn(argv, env, io_extra) end --- @diagnostic disable-next-line:missing-fields self._proc, self._pid = uv.spawn(prog, { - stdio = { self._child_stdin, self._child_stdout, 1, io_extra }, + stdio = { self._child_stdin, self._child_stdout, self._child_stderr, io_extra }, args = args, --- @diagnostic disable-next-line:assign-type-mismatch env = env, }, function(status, signal) - self.status = status self.signal = signal + -- "Abort" exit may not set status; force to nonzero in that case. + self.status = (0 ~= (status or 0) or 0 == (signal or 0)) and status or (128 + (signal or 0)) end) if not self._proc then @@ -153,24 +183,56 @@ function ChildProcessStream.spawn(argv, env, io_extra) return self end -function ChildProcessStream:write(data) +function ProcStream:write(data) self._child_stdin:write(data) end -function ChildProcessStream:read_start(cb) - self._child_stdout:read_start(function(err, chunk) - if err then - error(err) +function ProcStream:on_read(stream, cb, err, chunk) + if err then + -- stderr_error/stdout_error + self[stream .. '_error'] = err ---@type string + -- error(err) + elseif chunk then + -- 'stderr' or 'stdout' + if self.collect_output then + self[stream] = self[stream] .. chunk ---@type string + --- Collects both stdout + stderr. + self.output = self[stream] .. chunk ---@type string end + else + -- stderr_eof/stdout_eof + self[stream .. '_eof'] = true ---@type boolean + end + + -- Handler provided by the caller. + if cb then cb(chunk) + end +end + +--- Collects output until the process exits. +function ProcStream:wait() + self.collect_output = true + while not (self.stdout_eof and self.stderr_eof and (self.status or self.signal)) do + uv.run('once') + end +end + +function ProcStream:read_start(on_stdout, on_stderr) + self._child_stdout:read_start(function(err, chunk) + self:on_read('stdout', on_stdout, err, chunk) + end) + self._child_stderr:read_start(function(err, chunk) + self:on_read('stderr', on_stderr, err, chunk) end) end -function ChildProcessStream:read_stop() +function ProcStream:read_stop() self._child_stdout:read_stop() + self._child_stderr:read_stop() end -function ChildProcessStream:close(signal) +function ProcStream:close(signal) if self._closed then return end @@ -178,6 +240,7 @@ function ChildProcessStream:close(signal) self:read_stop() self._child_stdin:close() self._child_stdout:close() + self._child_stderr:close() if type(signal) == 'string' then self._proc:kill('sig' .. signal) end @@ -189,6 +252,6 @@ end return { StdioStream = StdioStream, - ChildProcessStream = ChildProcessStream, + ProcStream = ProcStream, SocketStream = SocketStream, } diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 76b0755441..c9ea280646 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -154,8 +154,9 @@ describe('startup', function() it('failure modes', function() -- nvim -l - matches('nvim%.?e?x?e?: Argument missing after: "%-l"', fn.system({ nvim_prog, '-l' })) - eq(1, eval('v:shell_error')) + local proc = n.spawn_wait('-l') + matches('nvim%.?e?x?e?: Argument missing after: "%-l"', proc.stderr) + eq(1, proc.status) end) it('os.exit() sets Nvim exitcode', function() @@ -182,12 +183,11 @@ describe('startup', function() end) it('Lua-error sets Nvim exitcode', function() + local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua') + matches('E5113: .* my pearls!!', proc.output) + eq(1, proc.status) + eq(0, eval('v:shell_error')) - matches( - 'E5113: .* my pearls!!', - fn.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' }) - ) - eq(1, eval('v:shell_error')) matches( 'E5113: .* %[string "error%("whoa"%)"%]:1: whoa', fn.system({ nvim_prog, '-l', '-' }, 'error("whoa")') diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index 675ad9e3d7..d65cbf685e 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -4,7 +4,7 @@ local t = require('test.testutil') local Session = require('test.client.session') local uv_stream = require('test.client.uv_stream') local SocketStream = uv_stream.SocketStream -local ChildProcessStream = uv_stream.ChildProcessStream +local ProcStream = uv_stream.ProcStream local check_cores = t.check_cores local check_logs = t.check_logs @@ -465,10 +465,12 @@ function M.check_close() session = nil end +--- Starts `argv` process as a Nvim msgpack-RPC session. +--- --- @param argv string[] --- @param merge boolean? --- @param env string[]? ---- @param keep boolean? +--- @param keep boolean? Don't close the current global session. --- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option --- @return test.Session function M.spawn(argv, merge, env, keep, io_extra) @@ -476,9 +478,8 @@ function M.spawn(argv, merge, env, keep, io_extra) M.check_close() end - local child_stream = - ChildProcessStream.spawn(merge and M.merge_args(prepend_argv, argv) or argv, env, io_extra) - return Session.new(child_stream) + local proc = ProcStream.spawn(merge and M.merge_args(prepend_argv, argv) or argv, env, io_extra) + return Session.new(proc) end -- Creates a new Session connected by domain socket (named pipe) or TCP. @@ -489,31 +490,59 @@ function M.connect(file_or_address) return Session.new(stream) end --- Starts (and returns) a new global Nvim session. --- --- Parameters are interpreted as startup args, OR a map with these keys: --- args: List: Args appended to the default `nvim_argv` set. --- args_rm: List: Args removed from the default set. All cases are --- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" --- (and its value) from the default set. --- env: Map: Defines the environment of the new session. --- --- Example: --- clear('-e') --- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} +--- Starts (and returns) a new global Nvim session. +--- +--- Use `spawn_argv()` to get a new session without replacing the current global session. +--- +--- Parameters are interpreted as startup args, OR a map with these keys: +--- - args: List: Args appended to the default `nvim_argv` set. +--- - args_rm: List: Args removed from the default set. All cases are +--- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" +--- (and its value) from the default set. +--- - env: Map: Defines the environment of the new session. +--- +--- Example: +--- ``` +--- clear('-e') +--- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} +--- ``` +--- +--- @param ... string Nvim CLI args +--- @return test.Session +--- @overload fun(opts: test.new_argv.Opts): test.Session function M.clear(...) M.set_session(M.spawn_argv(false, ...)) return M.get_session() end ---- same params as clear, but does returns the session instead ---- of replacing the default session +--- Same as clear(), but doesn't replace the current global session. +--- +--- @param keep boolean Don't close the current global session. +--- @param ... string Nvim CLI args --- @return test.Session +--- @overload fun(opts: test.new_argv.Opts): test.Session function M.spawn_argv(keep, ...) local argv, env, io_extra = M.new_argv(...) return M.spawn(argv, nil, env, keep, io_extra) end +--- Starts a (`--headless`, non-RPC) Nvim process, waits for exit, and returns output + info. +--- +--- @param ... string Nvim CLI args +--- @return test.ProcStream +--- @overload fun(opts: test.new_argv.Opts): test.ProcStream +function M.spawn_wait(...) + local opts = type(...) == 'string' and { args = { ... } } or ... + opts.args_rm = opts.args_rm and opts.args_rm or {} + table.insert(opts.args_rm, '--embed') + local argv, env, io_extra = M.new_argv(opts) + local proc = ProcStream.spawn(argv, env, io_extra) + proc:read_start() + proc:wait() + proc:close() + return proc +end + --- @class test.new_argv.Opts --- @field args? string[] --- @field args_rm? string[] @@ -522,11 +551,11 @@ end --- Builds an argument list for use in clear(). --- ---- @see clear() for parameters. ---- @param ... string +--- @param ... string See clear(). --- @return string[] --- @return string[]? --- @return uv.uv_pipe_t? +--- @overload fun(opts: test.new_argv.Opts): string[], string[]?, uv.uv_pipe_t? function M.new_argv(...) local args = { unpack(M.nvim_argv) } table.insert(args, '--headless') -- cgit From 700a25e6218e016b5adb0ddee740be4618d717a2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 3 Jan 2025 18:39:42 +0100 Subject: test: include stderr in EOF failure message --- test/client/session.lua | 12 ++++++++---- test/client/uv_stream.lua | 18 ++++++++++-------- test/functional/testnvim.lua | 4 ++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/test/client/session.lua b/test/client/session.lua index 5b7f1a7caa..a5839e012a 100644 --- a/test/client/session.lua +++ b/test/client/session.lua @@ -12,10 +12,8 @@ local RpcStream = require('test.client.rpc_stream') --- @field private _rpc_stream test.RpcStream --- @field private _prepare uv.uv_prepare_t --- @field private _timer uv.uv_timer_t ---- @field exec_lua_setup boolean --- @field private _is_running boolean true during `Session:run()` scope. ---- @field private _stdout_buffer string[] Stores stdout chunks ---- @field public stdout string Full stdout after the process exits +--- @field exec_lua_setup boolean local Session = {} Session.__index = Session if package.loaded['jit'] then @@ -231,7 +229,13 @@ function Session:_run(request_cb, notification_cb, timeout) end self._rpc_stream:read_start(request_cb, notification_cb, function() uv.stop() - self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' } + + --- @diagnostic disable-next-line: invisible + local stderr = self._rpc_stream._stream.stderr --[[@as string?]] + -- See if `ProcStream.stderr` has anything useful. + stderr = '' ~= ((stderr or ''):match('^%s*(.*%S)') or '') and ' stderr:\n' .. stderr or '' + + self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' .. stderr } end) uv.run() self._prepare:stop() diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index ac84cbf9bc..a9ef2db115 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -127,9 +127,11 @@ end --- @field private _child_stdout uv.uv_pipe_t --- @field private _child_stderr uv.uv_pipe_t --- @field stdout string +--- stderr is always collected in this field, regardless of `collect_output`. --- @field stderr string --- @field stdout_eof boolean --- @field stderr_eof boolean +--- Collects stdout in the `stdout` field, and stdout+stderr in `output` field. --- @field private collect_output boolean --- Exit code --- @field status integer @@ -149,8 +151,6 @@ function ProcStream.spawn(argv, env, io_extra) output = '', stdout = '', stderr = '', - stdout_error = nil, -- TODO: not used, remove - stderr_error = nil, -- TODO: not used, remove stdout_eof = false, stderr_eof = false, _child_stdin = assert(uv.new_pipe(false)), @@ -189,14 +189,16 @@ end function ProcStream:on_read(stream, cb, err, chunk) if err then - -- stderr_error/stdout_error - self[stream .. '_error'] = err ---@type string - -- error(err) + error(err) -- stream read failed? elseif chunk then - -- 'stderr' or 'stdout' + -- Always collect stderr, in case it gives useful info on failure. + if stream == 'stderr' then + self.stderr = self.stderr .. chunk ---@type string + end + -- Collect stdout if `collect_output` is enabled. if self.collect_output then - self[stream] = self[stream] .. chunk ---@type string - --- Collects both stdout + stderr. + self.stdout = self.stdout .. chunk ---@type string + --- Collect both stdout + stderr in the `output` field. self.output = self[stream] .. chunk ---@type string end else diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index d65cbf685e..c8f1c35555 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -333,9 +333,9 @@ end function M.expect_exit(fn_or_timeout, ...) local eof_err_msg = 'EOF was received from Nvim. Likely the Nvim process crashed.' if type(fn_or_timeout) == 'function' then - eq(eof_err_msg, t.pcall_err(fn_or_timeout, ...)) + t.matches(eof_err_msg, t.pcall_err(fn_or_timeout, ...)) else - eq( + t.matches( eof_err_msg, t.pcall_err(function(timeout, fn, ...) fn(...) -- cgit From 7ddadd0feec663fcd9af1f30f055018a872cf2ba Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 3 Jan 2025 17:41:37 +0100 Subject: vim-patch:cd96075: runtime(java): Quietly opt out for unsupported markdown.vim versions fixes vim/vim#16349 closes: vim/vim#16369 https://github.com/vim/vim/commit/cd96075cdee6b606c6e39aa1aed19b7cdc867df8 Co-authored-by: Aliaksei Budavei <32549825+zzzyxwvut@users.noreply.github.com> --- runtime/syntax/java.vim | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/runtime/syntax/java.vim b/runtime/syntax/java.vim index b3e17b55f6..9b38ccd4dc 100644 --- a/runtime/syntax/java.vim +++ b/runtime/syntax/java.vim @@ -3,7 +3,7 @@ " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Former Maintainer: Claudio Fleiner " Repository: https://github.com/zzzyxwvut/java-vim.git -" Last Change: 2024 Oct 10 +" Last Change: 2025 Jan 02 " Please check ":help java.vim" for comments on some of the options " available. @@ -391,18 +391,32 @@ if !exists("g:java_ignore_javadoc") && (s:with_html || s:with_markdown) && g:mai if s:with_markdown try syntax include @javaMarkdown syntax/markdown.vim - let s:ff.WithMarkdown = s:ff.LeftConstant + + try + syn clear markdownId markdownLineStart markdownH1 markdownH2 markdownHeadingRule markdownRule markdownCode markdownCodeBlock markdownIdDeclaration + let s:ff.WithMarkdown = s:ff.LeftConstant + catch /\ Date: Sat, 4 Jan 2025 06:29:13 -0800 Subject: test: use spawn_wait() instead of system() #31852 Problem: Tests that need to check `nvim` CLI behavior (no RPC session) create their own ad-hoc `system()` wrappers. Solution: - Use `n.spawn_wait` instead of `system()`. - Bonus: this also improves the tests by explicitly checking for `stdout` or `stderr`. And if a signal is raised, `ProcStream.status` will reflect it. --- test/client/uv_stream.lua | 30 +++++---- test/functional/core/exit_spec.lua | 6 +- test/functional/core/main_spec.lua | 100 +++++++++++----------------- test/functional/core/startup_spec.lua | 43 +++--------- test/functional/lua/ui_event_spec.lua | 13 ++-- test/functional/options/defaults_spec.lua | 16 ++--- test/functional/terminal/highlight_spec.lua | 3 +- test/functional/testnvim.lua | 13 +--- 8 files changed, 78 insertions(+), 146 deletions(-) diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index a9ef2db115..6e1a6995be 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -126,13 +126,16 @@ end --- @field private _child_stdin uv.uv_pipe_t --- @field private _child_stdout uv.uv_pipe_t --- @field private _child_stderr uv.uv_pipe_t +--- Collects stdout (if `collect_text=true`). Treats data as text (CRLF converted to LF). --- @field stdout string ---- stderr is always collected in this field, regardless of `collect_output`. +--- Collects stderr as raw data. --- @field stderr string +--- Gets stderr+stdout as text (CRLF converted to LF). +--- @field output fun(): string --- @field stdout_eof boolean --- @field stderr_eof boolean ---- Collects stdout in the `stdout` field, and stdout+stderr in `output` field. ---- @field private collect_output boolean +--- Collects text into the `stdout` field. +--- @field collect_text boolean --- Exit code --- @field status integer --- @field signal integer @@ -147,8 +150,13 @@ ProcStream.__index = ProcStream --- @return test.ProcStream function ProcStream.spawn(argv, env, io_extra) local self = setmetatable({ - collect_output = false, - output = '', + collect_text = false, + output = function(self) + if not self.collect_text then + error('set collect_text=true') + end + return (self.stderr .. self.stdout):gsub('\r\n', '\n') + end, stdout = '', stderr = '', stdout_eof = false, @@ -193,13 +201,10 @@ function ProcStream:on_read(stream, cb, err, chunk) elseif chunk then -- Always collect stderr, in case it gives useful info on failure. if stream == 'stderr' then - self.stderr = self.stderr .. chunk ---@type string - end - -- Collect stdout if `collect_output` is enabled. - if self.collect_output then - self.stdout = self.stdout .. chunk ---@type string - --- Collect both stdout + stderr in the `output` field. - self.output = self[stream] .. chunk ---@type string + self.stderr = self.stderr .. chunk --[[@as string]] + elseif stream == 'stdout' and self.collect_text then + -- Set `stdout` and convert CRLF => LF. + self.stdout = (self.stdout .. chunk):gsub('\r\n', '\n') end else -- stderr_eof/stdout_eof @@ -214,7 +219,6 @@ end --- Collects output until the process exits. function ProcStream:wait() - self.collect_output = true while not (self.stdout_eof and self.stderr_eof and (self.status or self.signal)) do uv.run('once') end diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 34c3eedbd2..65f6bc28a6 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -8,8 +8,6 @@ local feed = n.feed local eval = n.eval local eq = t.eq local run = n.run -local fn = n.fn -local nvim_prog = n.nvim_prog local pcall_err = t.pcall_err local exec_capture = n.exec_capture local poke_eventloop = n.poke_eventloop @@ -69,8 +67,8 @@ describe(':cquit', function() poke_eventloop() assert_alive() else - fn.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline }) - eq(exit_code, eval('v:shell_error')) + local p = n.spawn_wait('--cmd', cmdline) + eq(exit_code, p.status) end end diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index ce4ba1905f..6add49ceae 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -9,7 +9,6 @@ local feed = n.feed local eval = n.eval local clear = n.clear local fn = n.fn -local nvim_prog_abs = n.nvim_prog_abs local write_file = t.write_file local is_os = t.is_os local skip = t.skip @@ -35,7 +34,7 @@ describe('command-line option', function() it('treats - as stdin', function() eq(nil, uv.fs_stat(fname)) fn.system({ - nvim_prog_abs(), + n.nvim_prog, '-u', 'NONE', '-i', @@ -56,41 +55,29 @@ describe('command-line option', function() eq(nil, uv.fs_stat(fname)) eq(true, not not dollar_fname:find('%$%w+')) write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n') - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', + local p = n.spawn_wait( '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', '-s', dollar_fname, - fname, - }) - eq(0, eval('v:shell_error')) + fname + ) + eq(0, p.status) local attrs = uv.fs_stat(fname) eq(#'100500\n', attrs.size) end) - it('does not crash when run completion in ex mode', function() - fn.system({ - nvim_prog_abs(), - '--clean', - '-e', - '-s', - '--cmd', - 'exe "norm! i\\\\"', - }) - eq(0, eval('v:shell_error')) + it('does not crash when run completion in Ex mode', function() + local p = + n.spawn_wait('--clean', '-e', '-s', '--cmd', 'exe "norm! i\\\\"', '--cmd', 'qa!') + eq(0, p.status) end) it('does not crash after reading from stdin in non-headless mode', function() skip(is_os('win')) local screen = Screen.new(40, 8) local args = { - nvim_prog_abs(), + n.nvim_prog, '-u', 'NONE', '-i', @@ -138,51 +125,40 @@ describe('command-line option', function() ]=] end) - it('errors out when trying to use nonexistent file with -s', function() + it('fails when trying to use nonexistent file with -s', function() + local p = n.spawn_wait( + '--cmd', + 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', + 'language C', + '-s', + nonexistent_fname + ) eq( 'Cannot open for reading: "' .. nonexistent_fname .. '": no such file or directory\n', - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - 'set noswapfile shortmess+=IFW fileformats=unix', - '--cmd', - 'language C', - '-s', - nonexistent_fname, - }) + --- TODO(justinmk): using `p.output` because Nvim emits CRLF even on non-Win. Emit LF instead? + p:output() ) - eq(2, eval('v:shell_error')) + eq(2, p.status) end) it('errors out when trying to use -s twice', function() write_file(fname, ':call setline(1, "1")\n:wqall!\n') write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n') - eq( - 'Attempt to open script file again: "-s ' .. dollar_fname .. '"\n', - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - 'set noswapfile shortmess+=IFW fileformats=unix', - '--cmd', - 'language C', - '-s', - fname, - '-s', - dollar_fname, - fname_2, - }) + local p = n.spawn_wait( + '--cmd', + 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', + 'language C', + '-s', + fname, + '-s', + dollar_fname, + fname_2 ) - eq(2, eval('v:shell_error')) + --- TODO(justinmk): using `p.output` because Nvim emits CRLF even on non-Win. Emit LF instead? + eq('Attempt to open script file again: "-s ' .. dollar_fname .. '"\n', p:output()) + eq(2, p.status) eq(nil, uv.fs_stat(fname_2)) end) end) @@ -190,8 +166,8 @@ describe('command-line option', function() it('nvim -v, :version', function() matches('Run ":verbose version"', fn.execute(':version')) matches('fall%-back for %$VIM: .*Run :checkhealth', fn.execute(':verbose version')) - matches('Run "nvim %-V1 %-v"', fn.system({ nvim_prog_abs(), '-v' })) - matches('fall%-back for %$VIM: .*Run :checkhealth', fn.system({ nvim_prog_abs(), '-V1', '-v' })) + matches('Run "nvim %-V1 %-v"', n.spawn_wait('-v').stdout) + matches('fall%-back for %$VIM: .*Run :checkhealth', n.spawn_wait('-V1', '-v').stdout) end) if is_os('win') then @@ -205,7 +181,7 @@ describe('command-line option', function() eq( 'some text', fn.system({ - nvim_prog_abs(), + n.nvim_prog, '-es', '+%print', '+q', diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index c9ea280646..642e2b0287 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -77,22 +77,9 @@ describe('startup', function() end) it('--startuptime does not crash on error #31125', function() - eq( - "E484: Can't open file .", - fn.system({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--startuptime', - '.', - '-c', - '42cquit', - }) - ) - eq(42, api.nvim_get_vvar('shell_error')) + local p = n.spawn_wait('--startuptime', '.', '-c', '42cquit') + eq("E484: Can't open file .", p.stderr) + eq(42, p.status) end) it('-D does not hang #12647', function() @@ -184,7 +171,7 @@ describe('startup', function() it('Lua-error sets Nvim exitcode', function() local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua') - matches('E5113: .* my pearls!!', proc.output) + matches('E5113: .* my pearls!!', proc:output()) eq(1, proc.status) eq(0, eval('v:shell_error')) @@ -606,15 +593,15 @@ describe('startup', function() it('fails on --embed with -es/-Es/-l', function() matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-es' }) + n.spawn_wait('--embed', '-es').stderr ) matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-Es' }) + n.spawn_wait('--embed', '-Es').stderr ) matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-l', 'foo.lua' }) + n.spawn_wait('--embed', '-l', 'foo.lua').stderr ) end) @@ -698,20 +685,8 @@ describe('startup', function() end) it('get command line arguments from v:argv', function() - local out = fn.system({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - nvim_set, - '-c', - [[echo v:argv[-1:] len(v:argv) > 1]], - '+q', - }) - eq("['+q'] 1", out) + local p = n.spawn_wait('--cmd', nvim_set, '-c', [[echo v:argv[-1:] len(v:argv) > 1]], '+q') + eq("['+q'] 1", p.stderr) end) end) diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 27640d6066..af6b2ceac3 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -106,20 +106,15 @@ describe('vim.ui_attach', function() end) it('does not crash on exit', function() - fn.system({ - n.nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', + local p = n.spawn_wait( '--cmd', [[ lua ns = vim.api.nvim_create_namespace 'testspace' ]], '--cmd', [[ lua vim.ui_attach(ns, {ext_popupmenu=true}, function() end) ]], '--cmd', - 'quitall!', - }) - eq(0, n.eval('v:shell_error')) + 'quitall!' + ) + eq(0, p.status) end) it('can receive accurate message kinds even if they are history', function() diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index e7f47ef4e9..a82279e775 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -14,7 +14,6 @@ local api = n.api local command = n.command local clear = n.clear local exc_exec = n.exc_exec -local exec_lua = n.exec_lua local eval = n.eval local eq = t.eq local ok = t.ok @@ -929,17 +928,12 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 -- Check that Nvim rejects invalid APPNAMEs - -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. local function test_appname(testAppname, expected_exitcode) - local lua_code = string.format( - [[ - local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } }) - return vim.fn.jobwait({ child }, %d)[1] - ]], - testAppname, - 3000 - ) - eq(expected_exitcode, exec_lua(lua_code)) + local p = n.spawn_wait({ + args = { '--listen', 'x', '+qall!' }, + env = { NVIM_APPNAME = testAppname }, + }) + eq(expected_exitcode, p.status) end -- Invalid appnames: test_appname('a/../b', 1) diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index c43d139f70..0afbd010f7 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -6,7 +6,6 @@ local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local api = n.api local testprg, command = n.testprg, n.command -local nvim_prog_abs = n.nvim_prog_abs local fn = n.fn local nvim_set = n.nvim_set local is_os = t.is_os @@ -151,7 +150,7 @@ it(':terminal highlight has lower precedence than editor #9964', function() }) -- Child nvim process in :terminal (with cterm colors). fn.jobstart({ - nvim_prog_abs(), + n.nvim_prog, '-n', '-u', 'NORC', diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index c8f1c35555..aa66e07ad0 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -318,16 +318,6 @@ function M.stop() assert(session):stop() end -function M.nvim_prog_abs() - -- system(['build/bin/nvim']) does not work for whatever reason. It must - -- be executable searched in $PATH or something starting with / or ./. - if M.nvim_prog:match('[/\\]') then - return M.request('nvim_call_function', 'fnamemodify', { M.nvim_prog, ':p' }) - else - return M.nvim_prog - end -end - -- Use for commands which expect nvim to quit. -- The first argument can also be a timeout. function M.expect_exit(fn_or_timeout, ...) @@ -526,7 +516,7 @@ function M.spawn_argv(keep, ...) return M.spawn(argv, nil, env, keep, io_extra) end ---- Starts a (`--headless`, non-RPC) Nvim process, waits for exit, and returns output + info. +--- Starts a (non-RPC, `--headless --listen "Tx"`) Nvim process, waits for exit, and returns result. --- --- @param ... string Nvim CLI args --- @return test.ProcStream @@ -537,6 +527,7 @@ function M.spawn_wait(...) table.insert(opts.args_rm, '--embed') local argv, env, io_extra = M.new_argv(opts) local proc = ProcStream.spawn(argv, env, io_extra) + proc.collect_text = true proc:read_start() proc:wait() proc:close() -- cgit From 4eaf9371ca91445e5e49d4e943f74e7f746f7c6e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 4 Jan 2025 12:39:57 -0600 Subject: docs: cleanup news.txt (#31854) Move non-breaking changes out of the breaking changes section --- runtime/doc/news.txt | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index c2f1a6a2ee..d573b01baa 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -40,7 +40,6 @@ These changes may require adaptations in your config or plugins. API -• Improved API "meta" docstrings and :help documentation. • `vim.rpcnotify(0)` and `rpcnotify(0)` broadcast to ALL channels. Previously they would "multicast" only to subscribed channels (controlled by `nvim_subscribe()`). Plugins and clients that want "multicast" behavior must @@ -62,16 +61,11 @@ On Windows, only building with the UCRT runtime is supported. DEFAULTS -• |]d-default| and |[d-default| accept a count. -• |[D-default| and |]D-default| jump to the first and last diagnostic in the - current buffer, respectively. • 'number', 'relativenumber', 'signcolumn', and 'foldcolumn' are disabled in |terminal| buffers. See |terminal-config| for an example of changing these defaults. DIAGNOSTICS -• |vim.diagnostic.config()| accepts a "jump" table to specify defaults for - |vim.diagnostic.jump()|. • The "underline" diagnostics handler sorts diagnostics by severity when using the "severity_sort" option. @@ -109,11 +103,6 @@ HIGHLIGHTS LSP -• Improved rendering of LSP hover docs. |K-lsp-default| -• |vim.lsp.completion.enable()| gained the `convert` callback which enables - customizing the transformation of an LSP CompletionItem to |complete-items|. -• |vim.lsp.diagnostic.from()| can be used to convert a list of - |vim.Diagnostic| objects into their LSP diagnostic representation. • |vim.lsp.buf.references()|, |vim.lsp.buf.declaration()|, |vim.lsp.buf.definition()|, |vim.lsp.buf.type_definition()|, |vim.lsp.buf.implementation()| and |vim.lsp.buf.hover()| now support merging the results of multiple clients @@ -130,15 +119,11 @@ LSP • |vim.lsp.util.make_position_params()|, |vim.lsp.util.make_range_params()| and |vim.lsp.util.make_given_range_params()| now require the `position_encoding` parameter. -• `:checkhealth vim.lsp` displays the server version (if available). LUA • API functions now consistently return an empty dictionary as |vim.empty_dict()|. Earlier, a |lua-special-tbl| was sometimes used. -• Command-line completions for: `vim.g`, `vim.t`, `vim.w`, `vim.b`, `vim.v`, - `vim.o`, `vim.wo`, `vim.bo`, `vim.opt`, `vim.opt_local`, `vim.opt_global`, - and `vim.fn`. • |vim.json.encode()| no longer escapes forward slashes "/" by default OPTIONS @@ -171,9 +156,6 @@ TREESITTER if no languages are explicitly registered. • |vim.treesitter.language.add()| returns `true` if a parser was loaded successfully and `nil,errmsg` otherwise instead of throwing an error. -• New |TSNode:child_with_descendant()|, which is nearly identical to - |TSNode:child_containing_descendant()| except that it can return the - descendant itself. TUI @@ -194,6 +176,7 @@ The following new features were added. API +• Improved API "meta" docstrings and :help documentation. • |nvim__ns_set()| can set properties for a namespace DEFAULTS @@ -212,6 +195,9 @@ DEFAULTS on a URL. • Mouse |popup-menu| includes a "Go to definition" item when LSP is active in the buffer. + • |]d-default| and |[d-default| accept a count. + • |[D-default| and |]D-default| jump to the first and last diagnostic in the + current buffer, respectively. • Mappings inspired by Tim Pope's vim-unimpaired: • |[q|, |]q|, |[Q|, |]Q|, |[CTRL-Q|, |]CTRL-Q| navigate through the |quickfix| list • |[l|, |]l|, |[L|, |]L|, |[CTRL-L|, |]CTRL-L| navigate through the |location-list| @@ -226,6 +212,11 @@ DEFAULTS • `` in Insert and Select mode maps to `vim.snippet.jump({ direction = -1 })` when a snippet is active and jumpable backwards. +DIAGNOSTICS + +• |vim.diagnostic.config()| accepts a "jump" table to specify defaults for + |vim.diagnostic.jump()|. + EDITOR • Improved |paste| handling for redo (dot-repeat) and macros (|recording|): @@ -245,6 +236,12 @@ EVENTS LSP +• Improved rendering of LSP hover docs. |K-lsp-default| +• |vim.lsp.completion.enable()| gained the `convert` callback which enables + customizing the transformation of an LSP CompletionItem to |complete-items|. +• |vim.lsp.diagnostic.from()| can be used to convert a list of + |vim.Diagnostic| objects into their LSP diagnostic representation. +• `:checkhealth vim.lsp` displays the server version (if available). • Completion side effects (including snippet expansion, execution of commands and application of additional text edits) is now built-in. • |vim.lsp.util.locations_to_items()| sets `end_col` and `end_lnum` fields. @@ -267,6 +264,9 @@ LSP LUA +• Command-line completions for: `vim.g`, `vim.t`, `vim.w`, `vim.b`, `vim.v`, + `vim.o`, `vim.wo`, `vim.bo`, `vim.opt`, `vim.opt_local`, `vim.opt_global`, + and `vim.fn`. • |vim.fs.rm()| can delete files and directories. • |vim.validate()| now has a new signature which uses less tables, is more performant and easier to read. @@ -325,6 +325,9 @@ TREESITTER • |treesitter-directive-trim!| can trim all whitespace (not just empty lines) from both sides of a node. • |vim.treesitter.get_captures_at_pos()| now returns the `id` of each capture +• New |TSNode:child_with_descendant()|, which is nearly identical to + |TSNode:child_containing_descendant()| except that it can return the + descendant itself. TUI -- cgit From 69aa33d890468c1024beef0d97d0f9424516c9ef Mon Sep 17 00:00:00 2001 From: Emilia Simmons Date: Sun, 15 Dec 2024 13:28:16 -0500 Subject: fix(runtime): let matchit and matchparen skips fallback on treesitter captures When treesitter is enabled, by default syntax groups are not defined, but these groups are used to identify where to skip matches in matchit and matchparen. This patch does three things: 1. If syntax is enabled regardless of treesitter (`vim.bo.syntax='on'`): Use original implementation. 2. If treesitter is enabled and syntax is not: Match the syntax groups (i.e. `comment\|string`) against treesitter captures to check for skipped groups. 3. Add an explicit treesitter syntax for marking captures to skip: matchit uses `b:match_skip` to determine what counts as skippable Where 's:comment\|string' uses a match of the named syntax groups against a regex match of comment\|string, 't:comment\|string' now uses vim regex to match against the names of the treesitter capture groups. --- runtime/pack/dist/opt/matchit/autoload/matchit.vim | 10 +++++++++- runtime/pack/dist/opt/matchit/doc/matchit.txt | 2 ++ runtime/plugin/matchparen.vim | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/runtime/pack/dist/opt/matchit/autoload/matchit.vim b/runtime/pack/dist/opt/matchit/autoload/matchit.vim index aa977488e5..8a220eeb9b 100644 --- a/runtime/pack/dist/opt/matchit/autoload/matchit.vim +++ b/runtime/pack/dist/opt/matchit/autoload/matchit.vim @@ -222,6 +222,7 @@ function matchit#Match_wrapper(word, forward, mode) range let view = winsaveview() call cursor(0, curcol + 1) if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) + \ || skip =~ 'v:lua.vim.treesitter' && !exists('b:ts_highlight') let skip = "0" else execute "if " .. skip .. "| let skip = '0' | endif" @@ -678,6 +679,7 @@ fun! matchit#MultiMatch(spflag, mode) let middlepat = substitute(middlepat, ',', '\\|', 'g') if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) + \ || skip =~ 'v:lua.vim.treesitter' && !exists('b:ts_highlight') let skip = '0' else try @@ -760,10 +762,16 @@ endfun " S:foo becomes (current syntax item) !~ foo " r:foo becomes (line before cursor) =~ foo " R:foo becomes (line before cursor) !~ foo +" t:foo becomes (current treesitter captures) =~ foo +" T:foo becomes (current treesitter captures) !~ foo fun! s:ParseSkip(str) let skip = a:str if skip[1] == ":" - if skip[0] ==# "s" + if skip[0] ==# "t" || skip[0] ==# "s" && &syntax != 'on' && exists("b:ts_highlight") + let skip = "match(v:lua.vim.treesitter.get_captures_at_cursor(), '" .. strpart(skip,2) .. "') != -1" + elseif skip[0] ==# "T" || skip[0] ==# "S" && &syntax != 'on' && exists("b:ts_highlight") + let skip = "match(v:lua.vim.treesitter.get_captures_at_cursor(), '" .. strpart(skip,2) .. "') == -1" + elseif skip[0] ==# "s" let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .. \ strpart(skip,2) .. "'" elseif skip[0] ==# "S" diff --git a/runtime/pack/dist/opt/matchit/doc/matchit.txt b/runtime/pack/dist/opt/matchit/doc/matchit.txt index 8d56df6ddc..58031d8a8a 100644 --- a/runtime/pack/dist/opt/matchit/doc/matchit.txt +++ b/runtime/pack/dist/opt/matchit/doc/matchit.txt @@ -237,6 +237,8 @@ supported by matchit.vim: S:foo becomes (current syntax item) !~ foo r:foo becomes (line before cursor) =~ foo R:foo becomes (line before cursor) !~ foo + t:foo becomes (current treesitter captures) =~ foo + T:foo becomes (current treesitter captures) !~ foo (The "s" is meant to suggest "syntax", and the "r" is meant to suggest "regular expression".) diff --git a/runtime/plugin/matchparen.vim b/runtime/plugin/matchparen.vim index 661a34b578..13d1b6824f 100644 --- a/runtime/plugin/matchparen.vim +++ b/runtime/plugin/matchparen.vim @@ -109,6 +109,10 @@ func s:Highlight_Matching_Pair() if !has("syntax") || !exists("g:syntax_on") let s_skip = "0" + elseif exists("b:ts_highlight") && &syntax != 'on' + let s_skip = "match(v:lua.vim.treesitter.get_captures_at_cursor(), '" + \ .. 'string\|character\|singlequote\|escape\|symbol\|comment' + \ .. "') != -1" else " Build an expression that detects whether the current cursor position is " in certain syntax types (string, comment, etc.), for use as -- cgit From a8ace2c58a318552869462a36859aabf1cdfaa68 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Thu, 2 Jan 2025 16:29:00 +0100 Subject: fix(vim.fs.normalize): normalize case for windows drive letter Also add tests for the current path casing behavior so it doesn't get accidentally changed. --- runtime/lua/vim/fs.lua | 4 ++-- test/functional/lua/fs_spec.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index ee713e4b58..04a6e43db1 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -629,8 +629,8 @@ function M.normalize(path, opts) return prefix .. path end - -- Remove extraneous slashes from the prefix - prefix = prefix:gsub('/+', '/') + -- Ensure capital drive and remove extraneous slashes from the prefix + prefix = prefix:gsub('^%a:', string.upper):gsub('/+', '/') end if not opts._fast then diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 1c6ff5ac6d..218f9bbc46 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -340,6 +340,9 @@ describe('vim.fs', function() end) describe('normalize()', function() + -- local function vim.fs.normalize(path, opts) + -- return exec_lua([[return vim.fs.vim.fs.normalize(...)]], path, opts) + -- end it('removes trailing /', function() eq('/home/user', vim.fs.normalize('/home/user/')) end) @@ -373,6 +376,29 @@ describe('vim.fs', function() eq('/foo/bar', vim.fs.normalize('/foo//bar////', posix_opts)) end) + it('normalizes drive letter', function() + eq('C:/', vim.fs.normalize('C:/', win_opts)) + eq('C:/', vim.fs.normalize('c:/', win_opts)) + eq('D:/', vim.fs.normalize('d:/', win_opts)) + eq('C:', vim.fs.normalize('C:', win_opts)) + eq('C:', vim.fs.normalize('c:', win_opts)) + eq('D:', vim.fs.normalize('d:', win_opts)) + eq('C:/foo/test', vim.fs.normalize('C:/foo/test/', win_opts)) + eq('C:/foo/test', vim.fs.normalize('c:/foo/test/', win_opts)) + eq('D:foo/test', vim.fs.normalize('D:foo/test/', win_opts)) + eq('D:foo/test', vim.fs.normalize('d:foo/test/', win_opts)) + end) + + it('does not change case on paths, see #31833', function() + eq('TEST', vim.fs.normalize('TEST', win_opts)) + eq('test', vim.fs.normalize('test', win_opts)) + eq('C:/FOO/test', vim.fs.normalize('C:/FOO/test', win_opts)) + eq('C:/foo/test', vim.fs.normalize('C:/foo/test', win_opts)) + eq('//SERVER/SHARE/FOO/BAR', vim.fs.normalize('//SERVER/SHARE/FOO/BAR', win_opts)) + eq('//server/share/foo/bar', vim.fs.normalize('//server/share/foo/bar', win_opts)) + eq('C:/FOO/test', vim.fs.normalize('c:/FOO/test', win_opts)) + end) + it('allows backslashes on unix-based os', function() eq('/home/user/hello\\world', vim.fs.normalize('/home/user/hello\\world', posix_opts)) end) -- cgit From 64b0e6582ae8c273efbe39109bcb261079c2bcd4 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 4 Jan 2025 16:48:00 -0800 Subject: refactor(tests): merge n.spawn/n.spawn_argv into n.new_session #31859 Problem: - `n.spawn()` is misleading because it also connects RPC, it's not just "spawning" a process. - It's confusing that `n.spawn()` and `n.spawn_argv()` are separate. Solution: - Replace `n.spawn()`/`n.spawn_argv()` with a single function `n.new_session()`. This name aligns with the existing functions `n.set_session`/`n.get_session`. - Note: removes direct handling of `prepend_argv`, but I doubt that was important or intentional. If callers want to control use of `prepend_argv` then we should add a new flag to `test.session.Opts`. - Move `keep` to first parameter of `n.new_session()`. - Add a `merge` flag to `test.session.Opts` - Mark `_new_argv()` as private. Test should use clear/new_session/spawn_wait instead. --- test/functional/api/server_requests_spec.lua | 16 ++-- test/functional/core/channels_spec.lua | 7 +- test/functional/core/fileio_spec.lua | 13 ++- test/functional/core/remote_spec.lua | 18 +--- .../ex_cmds/swapfile_preserve_recover_spec.lua | 18 ++-- test/functional/ex_cmds/wundo_spec.lua | 16 ++-- test/functional/shada/marks_spec.lua | 10 ++- test/functional/shada/shada_spec.lua | 18 ++-- test/functional/terminal/tui_spec.lua | 13 ++- test/functional/testnvim.lua | 97 ++++++++++++---------- test/functional/ui/screen_basic_spec.lua | 6 +- 11 files changed, 110 insertions(+), 122 deletions(-) diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index bdd340f6c6..c022ba28de 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -9,7 +9,6 @@ local nvim_prog, command, fn = n.nvim_prog, n.command, n.fn local source, next_msg = n.source, n.next_msg local ok = t.ok local api = n.api -local spawn, merge_args = n.spawn, n.merge_args local set_session = n.set_session local pcall_err = t.pcall_err local assert_alive = n.assert_alive @@ -281,10 +280,9 @@ describe('server -> client', function() end) describe('connecting to another (peer) nvim', function() - local nvim_argv = merge_args(n.nvim_argv, { '--headless' }) local function connect_test(server, mode, address) local serverpid = fn.getpid() - local client = spawn(nvim_argv, false, nil, true) + local client = n.new_session(true) set_session(client) local clientpid = fn.getpid() @@ -312,7 +310,7 @@ describe('server -> client', function() end it('via named pipe', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverlist()[1] local first = string.sub(address, 1, 1) @@ -321,7 +319,7 @@ describe('server -> client', function() end) it('via ipv4 address', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local status, address = pcall(fn.serverstart, '127.0.0.1:') if not status then @@ -332,7 +330,7 @@ describe('server -> client', function() end) it('via ipv6 address', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local status, address = pcall(fn.serverstart, '::1:') if not status then @@ -343,7 +341,7 @@ describe('server -> client', function() end) it('via hostname', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverstart('localhost:') eq('localhost:', string.sub(address, 1, 10)) @@ -351,10 +349,10 @@ describe('server -> client', function() end) it('does not crash on receiving UI events', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverlist()[1] - local client = spawn(nvim_argv, false, nil, true) + local client = n.new_session(true) set_session(client) local id = fn.sockconnect('pipe', address, { rpc = true }) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index dee13d19ae..7b10eb05ef 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -5,7 +5,6 @@ local clear, eq, eval, next_msg, ok, source = n.clear, t.eq, n.eval, n.next_msg, local command, fn, api = n.command, n.fn, n.api local matches = t.matches local sleep = vim.uv.sleep -local spawn, nvim_argv = n.spawn, n.nvim_argv local get_session, set_session = n.get_session, n.set_session local nvim_prog = n.nvim_prog local is_os = t.is_os @@ -33,10 +32,10 @@ describe('channels', function() end) pending('can connect to socket', function() - local server = spawn(nvim_argv, nil, nil, true) + local server = n.new_session(true) set_session(server) local address = fn.serverlist()[1] - local client = spawn(nvim_argv, nil, nil, true) + local client = n.new_session(true) set_session(client) source(init) @@ -63,7 +62,7 @@ describe('channels', function() it('dont crash due to garbage in rpc #23781', function() local client = get_session() - local server = spawn(nvim_argv, nil, nil, true) + local server = n.new_session(true) set_session(server) local address = fn.serverlist()[1] set_session(client) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index cf9715f848..b1a8e21762 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -31,7 +31,6 @@ local feed_command = n.feed_command local skip = t.skip local is_os = t.is_os local is_ci = t.is_ci -local spawn = n.spawn local set_session = n.set_session describe('fileio', function() @@ -51,12 +50,11 @@ describe('fileio', function() rmdir('Xtest_backupdir with spaces') end) - local args = { nvim_prog, '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } + local args = { '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } --- Starts a new nvim session and returns an attached screen. - local function startup(extra_args) - extra_args = extra_args or {} - local argv = vim.iter({ args, '--embed', extra_args }):flatten():totable() - local screen_nvim = spawn(argv) + local function startup() + local argv = vim.iter({ args, '--embed' }):flatten():totable() + local screen_nvim = n.new_session(false, { args = argv, merge = false }) set_session(screen_nvim) local screen = Screen.new(70, 10) screen:set_default_attr_ids({ @@ -100,7 +98,8 @@ describe('fileio', function() eq('foozubbaz', trim(read_file('Xtest_startup_file1'))) -- 4. Exit caused by deadly signal (+ 'swapfile'). - local j = fn.jobstart(vim.iter({ args, '--embed' }):flatten():totable(), { rpc = true }) + local j = + fn.jobstart(vim.iter({ nvim_prog, args, '--embed' }):flatten():totable(), { rpc = true }) fn.rpcrequest( j, 'nvim_exec2', diff --git a/test/functional/core/remote_spec.lua b/test/functional/core/remote_spec.lua index 6cc28ddeef..1cfa0535f6 100644 --- a/test/functional/core/remote_spec.lua +++ b/test/functional/core/remote_spec.lua @@ -10,10 +10,8 @@ local expect = n.expect local fn = n.fn local insert = n.insert local nvim_prog = n.nvim_prog -local new_argv = n.new_argv local neq = t.neq local set_session = n.set_session -local spawn = n.spawn local tmpname = t.tmpname local write_file = t.write_file @@ -32,8 +30,7 @@ describe('Remote', function() describe('connect to server and', function() local server before_each(function() - server = spawn(new_argv(), true) - set_session(server) + server = n.clear() end) after_each(function() @@ -49,7 +46,7 @@ describe('Remote', function() -- to wait for the remote instance to exit and calling jobwait blocks -- the event loop. If the server event loop is blocked, it can't process -- our incoming --remote calls. - local client_starter = spawn(new_argv(), false, nil, true) + local client_starter = n.new_session(true) set_session(client_starter) -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. eq( @@ -144,15 +141,8 @@ describe('Remote', function() describe('exits with error on', function() local function run_and_check_exit_code(...) - local bogus_argv = new_argv(...) - - -- Create an nvim instance just to run the remote-invoking nvim. We want - -- to wait for the remote instance to exit and calling jobwait blocks - -- the event loop. If the server event loop is blocked, it can't process - -- our incoming --remote calls. - clear() - -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. - eq({ 2 }, exec_lua([[return vim.fn.jobwait({ vim.fn.jobstart(...) })]], bogus_argv)) + local p = n.spawn_wait { args = { ... } } + eq(2, p.status) end it('bogus subcommand', function() run_and_check_exit_code('--remote-bogus') diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 08f7663075..d1f598a9d8 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -12,12 +12,10 @@ local fn = n.fn local nvim_prog = n.nvim_prog local ok = t.ok local rmdir = n.rmdir -local new_argv = n.new_argv local new_pipename = n.new_pipename local pesc = vim.pesc local os_kill = n.os_kill local set_session = n.set_session -local spawn = n.spawn local async_meths = n.async_meths local expect_msg_seq = n.expect_msg_seq local pcall_err = t.pcall_err @@ -56,7 +54,7 @@ describe("preserve and (R)ecover with custom 'directory'", function() local nvim0 before_each(function() - nvim0 = spawn(new_argv()) + nvim0 = n.new_session(false) set_session(nvim0) rmdir(swapdir) mkdir(swapdir) @@ -76,7 +74,8 @@ describe("preserve and (R)ecover with custom 'directory'", function() local function test_recover(swappath1) -- Start another Nvim instance. - local nvim2 = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed' }, true) + local nvim2 = + n.new_session(false, { args = { '-u', 'NONE', '-i', 'NONE', '--embed' }, merge = false }) set_session(nvim2) exec(init) @@ -141,7 +140,7 @@ describe('swapfile detection', function() set swapfile fileformat=unix nomodified undolevels=-1 nohidden ]] before_each(function() - nvim0 = spawn(new_argv()) + nvim0 = n.new_session(false) set_session(nvim0) rmdir(swapdir) mkdir(swapdir) @@ -168,7 +167,8 @@ describe('swapfile detection', function() command('preserve') -- Start another Nvim instance. - local nvim2 = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed' }, true, nil, true) + local nvim2 = + n.new_session(true, { args = { '-u', 'NONE', '-i', 'NONE', '--embed' }, merge = false }) set_session(nvim2) local screen2 = Screen.new(256, 40) screen2._default_attr_ids = nil @@ -251,7 +251,7 @@ describe('swapfile detection', function() command('preserve') -- Make sure the swap file exists. local nvimpid = fn.getpid() - local nvim1 = spawn(new_argv(), true, nil, true) + local nvim1 = n.new_session(true) set_session(nvim1) local screen = Screen.new(75, 18) exec(init) @@ -273,7 +273,7 @@ describe('swapfile detection', function() [1] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg }) - local nvim1 = spawn(new_argv(), true, nil, true) + local nvim1 = n.new_session(true) set_session(nvim1) screen:attach() exec(init) @@ -292,7 +292,7 @@ describe('swapfile detection', function() ]]) nvim1:close() - local nvim2 = spawn(new_argv(), true, nil, true) + local nvim2 = n.new_session(true) set_session(nvim2) screen:attach() exec(init) diff --git a/test/functional/ex_cmds/wundo_spec.lua b/test/functional/ex_cmds/wundo_spec.lua index 2299f33f06..9b81c6d06d 100644 --- a/test/functional/ex_cmds/wundo_spec.lua +++ b/test/functional/ex_cmds/wundo_spec.lua @@ -5,8 +5,6 @@ local n = require('test.functional.testnvim')() local command = n.command local clear = n.clear local eval = n.eval -local spawn = n.spawn -local nvim_prog = n.nvim_prog local set_session = n.set_session describe(':wundo', function() @@ -24,15 +22,11 @@ end) describe('u_* functions', function() it('safely fail on new, non-empty buffer', function() - local session = spawn({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--embed', - '-c', - 'set undodir=. undofile', + local session = n.new_session(false, { + args = { + '-c', + 'set undodir=. undofile', + }, }) set_session(session) command('echo "True"') -- Should not error out due to crashed Neovim diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index d680f0b83b..837978dee1 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -218,7 +218,7 @@ describe('ShaDa support code', function() -- -c temporary sets lnum to zero to make `+/pat` work, so calling setpcmark() -- during -c used to add item with zero lnum to jump list. it('does not create incorrect file for non-existent buffers when writing from -c', function() - local argv = n.new_argv { + local p = n.spawn_wait { args_rm = { '-i', '--embed', -- no --embed @@ -232,12 +232,13 @@ describe('ShaDa support code', function() 'qall', }, } - eq('', fn.system(argv)) + eq('', p:output()) + eq(0, p.status) eq(0, exc_exec('rshada')) end) it('does not create incorrect file for non-existent buffers opened from -c', function() - local argv = n.new_argv { + local p = n.spawn_wait { args_rm = { '-i', '--embed', -- no --embed @@ -251,7 +252,8 @@ describe('ShaDa support code', function() 'autocmd VimEnter * qall', }, } - eq('', fn.system(argv)) + eq('', p:output()) + eq(0, p.status) eq(0, exc_exec('rshada')) end) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 5debdc6c77..78fe19b984 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -6,8 +6,7 @@ local uv = vim.uv local paths = t.paths local api, nvim_command, fn, eq = n.api, n.command, n.fn, t.eq -local write_file, spawn, set_session, nvim_prog, exc_exec = - t.write_file, n.spawn, n.set_session, n.nvim_prog, n.exc_exec +local write_file, set_session, exc_exec = t.write_file, n.set_session, n.exc_exec local is_os = t.is_os local skip = t.skip @@ -254,7 +253,7 @@ describe('ShaDa support code', function() it('does not crash when ShaDa file directory is not writable', function() skip(is_os('win')) - fn.mkdir(dirname, '', 0) + fn.mkdir(dirname, '', '0') eq(0, fn.filewritable(dirname)) reset { shadafile = dirshada, args = { '--cmd', 'set shada=' } } api.nvim_set_option_value('shada', "'10", {}) @@ -270,10 +269,10 @@ end) describe('ShaDa support code', function() it('does not write NONE file', function() - local session = spawn( - { nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', '--headless', '--cmd', 'qall' }, - true - ) + local session = n.new_session(false, { + merge = false, + args = { '-u', 'NONE', '-i', 'NONE', '--embed', '--headless', '--cmd', 'qall' }, + }) session:close() eq(nil, uv.fs_stat('NONE')) eq(nil, uv.fs_stat('NONE.tmp.a')) @@ -281,7 +280,10 @@ describe('ShaDa support code', function() it('does not read NONE file', function() write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-') - local session = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', '--headless' }, true) + local session = n.new_session( + false, + { merge = false, args = { '-u', 'NONE', '-i', 'NONE', '--embed', '--headless' } } + ) set_session(session) eq('', fn.getreg('a')) session:close() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index f9145f9b63..3624a7bc2b 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -26,7 +26,6 @@ local api = n.api local is_ci = t.is_ci local is_os = t.is_os local new_pipename = n.new_pipename -local spawn_argv = n.spawn_argv local set_session = n.set_session local write_file = t.write_file local eval = n.eval @@ -3320,8 +3319,8 @@ describe('TUI as a client', function() end) it('connects to remote instance (with its own TUI)', function() - local server_super = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true) + local server_super = n.new_session(false) + local client_super = n.new_session(true) set_session(server_super) local server_pipe = new_pipename() @@ -3395,8 +3394,8 @@ describe('TUI as a client', function() end) it('connects to remote instance (--headless)', function() - local server = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true, { env = { NVIM_LOG_FILE = testlog } }) + local server = n.new_session(false) + local client_super = n.new_session(true, { env = { NVIM_LOG_FILE = testlog } }) set_session(server) local server_pipe = api.nvim_get_vvar('servername') @@ -3462,8 +3461,8 @@ describe('TUI as a client', function() end) local function test_remote_tui_quit(status) - local server_super = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true) + local server_super = n.new_session(false) + local client_super = n.new_session(true) set_session(server_super) local server_pipe = new_pipename() diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index aa66e07ad0..59cb593cf7 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -455,23 +455,6 @@ function M.check_close() session = nil end ---- Starts `argv` process as a Nvim msgpack-RPC session. ---- ---- @param argv string[] ---- @param merge boolean? ---- @param env string[]? ---- @param keep boolean? Don't close the current global session. ---- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option ---- @return test.Session -function M.spawn(argv, merge, env, keep, io_extra) - if not keep then - M.check_close() - end - - local proc = ProcStream.spawn(merge and M.merge_args(prepend_argv, argv) or argv, env, io_extra) - return Session.new(proc) -end - -- Creates a new Session connected by domain socket (named pipe) or TCP. function M.connect(file_or_address) local addr, port = string.match(file_or_address, '(.*):(%d+)') @@ -480,9 +463,9 @@ function M.connect(file_or_address) return Session.new(stream) end ---- Starts (and returns) a new global Nvim session. +--- Starts a new, global Nvim session and clears the current one. --- ---- Use `spawn_argv()` to get a new session without replacing the current global session. +--- Note: Use `new_session()` to start a session without replacing the current one. --- --- Parameters are interpreted as startup args, OR a map with these keys: --- - args: List: Args appended to the default `nvim_argv` set. @@ -499,33 +482,41 @@ end --- --- @param ... string Nvim CLI args --- @return test.Session ---- @overload fun(opts: test.new_argv.Opts): test.Session +--- @overload fun(opts: test.session.Opts): test.Session function M.clear(...) - M.set_session(M.spawn_argv(false, ...)) + M.set_session(M.new_session(false, ...)) return M.get_session() end ---- Same as clear(), but doesn't replace the current global session. +--- Starts a new Nvim process with the given args and returns a msgpack-RPC session. --- ---- @param keep boolean Don't close the current global session. ---- @param ... string Nvim CLI args +--- Does not replace the current global session, unlike `clear()`. +--- +--- @param keep boolean (default: false) Don't close the current global session. +--- @param ... string Nvim CLI args (or see overload) --- @return test.Session ---- @overload fun(opts: test.new_argv.Opts): test.Session -function M.spawn_argv(keep, ...) - local argv, env, io_extra = M.new_argv(...) - return M.spawn(argv, nil, env, keep, io_extra) +--- @overload fun(keep: boolean, opts: test.session.Opts): test.Session +function M.new_session(keep, ...) + if not keep then + M.check_close() + end + + local argv, env, io_extra = M._new_argv(...) + + local proc = ProcStream.spawn(argv, env, io_extra) + return Session.new(proc) end --- Starts a (non-RPC, `--headless --listen "Tx"`) Nvim process, waits for exit, and returns result. --- ---- @param ... string Nvim CLI args +--- @param ... string Nvim CLI args, or `test.session.Opts` table. --- @return test.ProcStream ---- @overload fun(opts: test.new_argv.Opts): test.ProcStream +--- @overload fun(opts: test.session.Opts): test.ProcStream function M.spawn_wait(...) local opts = type(...) == 'string' and { args = { ... } } or ... opts.args_rm = opts.args_rm and opts.args_rm or {} table.insert(opts.args_rm, '--embed') - local argv, env, io_extra = M.new_argv(opts) + local argv, env, io_extra = M._new_argv(opts) local proc = ProcStream.spawn(argv, env, io_extra) proc.collect_text = true proc:read_start() @@ -534,36 +525,50 @@ function M.spawn_wait(...) return proc end ---- @class test.new_argv.Opts +--- @class test.session.Opts +--- Nvim CLI args --- @field args? string[] +--- Remove these args from the default `nvim_argv` args set. Ignored if `merge=false`. --- @field args_rm? string[] +--- (default: true) Merge `args` with the default set. Else use only the provided `args`. +--- @field merge? boolean +--- Environment variables --- @field env? table +--- Used for stdin_fd, see `:help ui-option` --- @field io_extra? uv.uv_pipe_t ---- Builds an argument list for use in clear(). +--- @private --- ---- @param ... string See clear(). +--- Builds an argument list for use in `new_session()`, `clear()`, and `spawn_wait()`. +--- +--- @param ... string Nvim CLI args, or `test.session.Opts` table. --- @return string[] --- @return string[]? --- @return uv.uv_pipe_t? ---- @overload fun(opts: test.new_argv.Opts): string[], string[]?, uv.uv_pipe_t? -function M.new_argv(...) - local args = { unpack(M.nvim_argv) } - table.insert(args, '--headless') - if _G._nvim_test_id then - -- Set the server name to the test-id for logging. #8519 - table.insert(args, '--listen') - table.insert(args, _G._nvim_test_id) +--- @overload fun(opts: test.session.Opts): string[], string[]?, uv.uv_pipe_t? +function M._new_argv(...) + --- @type test.session.Opts|string + local opts = select(1, ...) + local merge = type(opts) ~= 'table' and true or opts.merge ~= false + + local args = merge and { unpack(M.nvim_argv) } or { M.nvim_prog } + if merge then + table.insert(args, '--headless') + if _G._nvim_test_id then + -- Set the server name to the test-id for logging. #8519 + table.insert(args, '--listen') + table.insert(args, _G._nvim_test_id) + end end + local new_args --- @type string[] local io_extra --- @type uv.uv_pipe_t? - local env --- @type string[]? - --- @type test.new_argv.Opts|string - local opts = select(1, ...) + local env --- @type string[]? List of "key=value" env vars. + if type(opts) ~= 'table' then new_args = { ... } else - args = remove_args(args, opts.args_rm) + args = merge and remove_args(args, opts.args_rm) or args if opts.env then local env_opt = {} --- @type table for k, v in pairs(opts.env) do diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 666d98c3b2..295c40b9b6 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -2,7 +2,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local spawn, set_session, clear = n.spawn, n.set_session, n.clear +local set_session, clear = n.set_session, n.clear local feed, command = n.feed, n.command local exec = n.exec local insert = n.insert @@ -26,7 +26,7 @@ describe('screen', function() } before_each(function() - local screen_nvim = spawn(nvim_argv) + local screen_nvim = n.new_session(false, { args = nvim_argv, merge = false }) set_session(screen_nvim) screen = Screen.new() end) @@ -766,7 +766,7 @@ describe('Screen default colors', function() 'colorscheme vim', '--embed', } - local screen_nvim = spawn(nvim_argv) + local screen_nvim = n.new_session(false, { args = nvim_argv, merge = false }) set_session(screen_nvim) screen = Screen.new(53, 14, { rgb = true, ext_termcolors = termcolors or nil }) end -- cgit From 5e9040648739252b1b8d38c9b46b111767f34cf2 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 4 Jan 2025 15:27:36 -0800 Subject: fix(health): set nomodifiable in checkhealth buffers --- runtime/lua/vim/health.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 52a7a13966..3e831371b8 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -407,6 +407,9 @@ function M._check(mods, plugin_names) -- Clear the 'Running healthchecks...' message. vim.cmd.redraw() vim.print('') + + -- Once we're done writing checks, set nomodifiable. + vim.bo[bufnr].modifiable = false end return M -- cgit From 54ac406649b9e93d756ea62c1a6a587db462039c Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Sun, 5 Jan 2025 12:09:38 +0100 Subject: vim-patch:9.1.0990: Inconsistent behavior when changing cmdheight (#31830) Problem: Inconsistent behavior when changing cmdheight by resizing the topframe through wincmds and dragging laststatus. Changing cmdheight by resizing the topframe does not trigger OptionSet. Solution: Consolidate logic for changing the cmdheight, set the option value to handle side-effects (Luuk van Baal) https://github.com/vim/vim/commit/e15cbc1af47e9dea90448c714eb4908e5d4302fc vim-patch:9.0.0187: command line height changes when maximizing window height Problem: Command line height changes when maximizing window height. Solution: Do not change the command line height. (closes vim/vim#10885) https://github.com/vim/vim/commit/96bde99bf890acd9952863a02c1d15edca2000e1 --- src/nvim/option.c | 4 +- src/nvim/window.c | 211 ++++++++++++----------------- test/functional/legacy/cmdline_spec.lua | 46 +++++++ test/functional/legacy/messages_spec.lua | 2 +- test/functional/legacy/window_cmd_spec.lua | 2 +- test/old/testdir/test_autocmd.vim | 24 ++++ test/old/testdir/test_cmdline.vim | 7 +- test/old/testdir/test_window_cmd.vim | 1 - 8 files changed, 161 insertions(+), 136 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 9d9e70622a..824c9988f4 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2120,14 +2120,14 @@ static const char *did_set_laststatus(optset_T *args) // When switching to global statusline, decrease topframe height // Also clear the cmdline to remove the ruler if there is one if (value == 3 && old_value != 3) { - frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false); + frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false, false); win_comp_pos(); clear_cmdline = true; } // When switching from global statusline, increase height of topframe by STATUS_HEIGHT // in order to to re-add the space that was previously taken by the global statusline if (old_value == 3 && value != 3) { - frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false); + frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false, false); win_comp_pos(); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 8232d0aa6c..d459a046ca 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1462,7 +1462,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl frame_add_statusline(curfrp); } } - frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false); + frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false, false); } else { win_new_height(oldwin, oldwin_height - (new_size + STATUS_HEIGHT)); } @@ -2136,7 +2136,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int || topfr->fr_width != width || topfr->fr_win->w_wincol != col) { topfr->fr_win->w_winrow = row; - frame_new_height(topfr, height, false, false); + frame_new_height(topfr, height, false, false, false); topfr->fr_win->w_wincol = col; frame_new_width(topfr, width, false, false); redraw_all_later(UPD_NOT_VALID); @@ -3154,7 +3154,7 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al if (*dirp == 'v') { frame_new_height(altfr, altfr->fr_height + frp_close->fr_height, - altfr == frp_close->fr_next, false); + altfr == frp_close->fr_next, false, false); } else { assert(*dirp == 'h'); frame_new_width(altfr, altfr->fr_width + frp_close->fr_width, @@ -3356,7 +3356,7 @@ void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) // adjusts window sizes to fit restored statuslines/separators, if needed. if (dir == 'v') { frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height, - unflat_altfr == frp->fr_next, false); + unflat_altfr == frp->fr_next, false, false); } else if (dir == 'h') { frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width, unflat_altfr == frp->fr_next, false); @@ -3499,15 +3499,27 @@ static bool is_bottom_win(win_T *wp) } return true; } + /// Set a new height for a frame. Recursively sets the height for contained /// frames and windows. Caller must take care of positions. /// /// @param topfirst resize topmost contained frame first. /// @param wfh obey 'winfixheight' when there is a choice; /// may cause the height not to be set. -void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) +/// @param set_ch set 'cmdheight' to resize topframe. +void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh, bool set_ch) FUNC_ATTR_NONNULL_ALL { + if (topfrp->fr_parent == NULL && set_ch) { + // topframe: update the command line height, with side effects. + OptInt new_ch = MAX(!p_ch_was_zero, p_ch + topfrp->fr_height - height); + if (new_ch != p_ch) { + const bool was_zero = p_ch_was_zero; + set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0); + p_ch_was_zero = was_zero; + } + height = (int)MIN(ROWS_AVAIL, height); + } if (topfrp->fr_win != NULL) { // Simple case: just one window. win_T *wp = topfrp->fr_win; @@ -3520,7 +3532,7 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) do { // All frames in this row get the same new height. FOR_ALL_FRAMES(frp, topfrp->fr_child) { - frame_new_height(frp, height, topfirst, wfh); + frame_new_height(frp, height, topfirst, wfh, set_ch); if (frp->fr_height > height) { // Could not fit the windows, make the whole row higher. height = frp->fr_height; @@ -3562,10 +3574,9 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) int h = frame_minheight(frp, NULL); if (frp->fr_height + extra_lines < h) { extra_lines += frp->fr_height - h; - frame_new_height(frp, h, topfirst, wfh); + frame_new_height(frp, h, topfirst, wfh, set_ch); } else { - frame_new_height(frp, frp->fr_height + extra_lines, - topfirst, wfh); + frame_new_height(frp, frp->fr_height + extra_lines, topfirst, wfh, set_ch); break; } if (topfirst) { @@ -3584,7 +3595,7 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) } } else if (extra_lines > 0) { // increase height of bottom or top frame - frame_new_height(frp, frp->fr_height + extra_lines, topfirst, wfh); + frame_new_height(frp, frp->fr_height + extra_lines, topfirst, wfh, set_ch); } } topfrp->fr_height = height; @@ -4001,6 +4012,10 @@ void unuse_tabpage(tabpage_T *tp) tp->tp_curwin = curwin; } +// When switching tabpage, handle other side-effects in command_height(), but +// avoid setting frame sizes which are still correct. +static bool command_frame_height = true; + /// Set the relevant pointers to use tab page "tp". May want to call /// unuse_tabpage() first. void use_tabpage(tabpage_T *tp) @@ -4402,6 +4417,16 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a use_tabpage(tp); + if (p_ch != curtab->tp_ch_used) { + // Use the stored value of p_ch, so that it can be different for each tab page. + // Handle other side-effects but avoid setting frame sizes, which are still correct. + OptInt new_ch = curtab->tp_ch_used; + curtab->tp_ch_used = p_ch; + command_frame_height = false; + set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0); + command_frame_height = true; + } + if (old_curtab != curtab) { tabpage_check_windows(old_curtab); } @@ -4415,27 +4440,9 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a prevwin = next_prevwin; last_status(false); // status line may appear or disappear - const int row = win_comp_pos(); // recompute w_winrow for all windows + win_comp_pos(); // recompute w_winrow for all windows diff_need_scrollbind = true; - // Use the stored value of p_ch, so that it can be different for each tab page. - if (p_ch != curtab->tp_ch_used) { - clear_cmdline = true; - if (msg_grid.chars && p_ch < curtab->tp_ch_used) { - // TODO(bfredl): a bit expensive, should be enough to invalidate the - // region between the old and the new p_ch. - grid_invalidate(&msg_grid); - } - } - p_ch = curtab->tp_ch_used; - - // When cmdheight is changed in a tab page with '-', cmdline_row is - // changed but p_ch and tp_ch_used are not changed. Thus we also need to - // check cmdline_row. - if (row < cmdline_row && cmdline_row <= Rows - p_ch) { - clear_cmdline = true; - } - // If there was a click in a window, it won't be usable for a following // drag. reset_dragwin(); @@ -5407,9 +5414,9 @@ void win_new_screen_rows(void) // First try setting the heights of windows with 'winfixheight'. If // that doesn't result in the right height, forget about that option. - frame_new_height(topframe, h, false, true); + frame_new_height(topframe, h, false, true, false); if (!frame_check_height(topframe, h)) { - frame_new_height(topframe, h, false, false); + frame_new_height(topframe, h, false, false, false); } win_comp_pos(); // recompute w_winrow and w_wincol @@ -5844,22 +5851,7 @@ void win_setheight_win(int height, win_T *win) frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height); // recompute the window positions - int row = win_comp_pos(); - - // If there is extra space created between the last window and the command - // line, clear it. - if (full_screen && msg_scrolled == 0 && row < cmdline_row) { - grid_clear(&default_grid, row, cmdline_row, 0, Columns, 0); - if (msg_grid.chars) { - clear_cmdline = true; - } - } - cmdline_row = row; - p_ch = MAX(Rows - cmdline_row, 0); - curtab->tp_ch_used = p_ch; - msg_row = row; - msg_col = 0; - + win_comp_pos(); win_fix_scroll(true); redraw_all_later(UPD_NOT_VALID); @@ -5887,14 +5879,8 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line height - if (height > ROWS_AVAIL) { - // If height is greater than the available space, try to create space for - // the frame by reducing 'cmdheight' if possible, while making sure - // `cmdheight` doesn't go below 1 if it wasn't set to 0 explicitly. - height = (int)MIN(ROWS_AVAIL + p_ch - !p_ch_was_zero, height); - } if (height > 0) { - frame_new_height(curfrp, height, false, false); + frame_new_height(curfrp, height, false, false, true); } } else if (curfrp->fr_parent->fr_layout == FR_ROW) { // Row of frames: Also need to resize frames left and right of this @@ -5973,7 +5959,7 @@ static void frame_setheight(frame_T *curfrp, int height) } // set the current frame to the new height - frame_new_height(curfrp, height, false, false); + frame_new_height(curfrp, height, false, false, true); // First take lines from the frames after the current frame. If // that is not enough, takes lines from frames above the current @@ -5995,15 +5981,15 @@ static void frame_setheight(frame_T *curfrp, int height) room_reserved = frp->fr_height - take; } take -= frp->fr_height - room_reserved; - frame_new_height(frp, room_reserved, false, false); + frame_new_height(frp, room_reserved, false, false, true); room_reserved = 0; } } else { if (frp->fr_height - take < h) { take -= frp->fr_height - h; - frame_new_height(frp, h, false, false); + frame_new_height(frp, h, false, false, true); } else { - frame_new_height(frp, frp->fr_height - take, false, false); + frame_new_height(frp, frp->fr_height - take, false, false, true); take = 0; } } @@ -6281,7 +6267,7 @@ void win_drag_status_line(win_T *dragwin, int offset) // Grow frame fr by "offset" lines. // Doesn't happen when dragging the last status line up. if (fr != NULL) { - frame_new_height(fr, fr->fr_height + offset, up, false); + frame_new_height(fr, fr->fr_height + offset, up, false, true); } if (up) { @@ -6294,9 +6280,9 @@ void win_drag_status_line(win_T *dragwin, int offset) int n = frame_minheight(fr, NULL); if (fr->fr_height - offset <= n) { offset -= fr->fr_height - n; - frame_new_height(fr, n, !up, false); + frame_new_height(fr, n, !up, false, true); } else { - frame_new_height(fr, fr->fr_height - offset, !up, false); + frame_new_height(fr, fr->fr_height - offset, !up, false, true); break; } if (up) { @@ -6305,15 +6291,7 @@ void win_drag_status_line(win_T *dragwin, int offset) fr = fr->fr_next; } } - int row = win_comp_pos(); - grid_clear(&default_grid, row, cmdline_row, 0, Columns, 0); - if (msg_grid.chars) { - clear_cmdline = true; - } - cmdline_row = row; - p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1); - curtab->tp_ch_used = p_ch; - + win_comp_pos(); win_fix_scroll(true); redraw_all_later(UPD_SOME_VALID); @@ -6750,21 +6728,6 @@ void command_height(void) { int old_p_ch = (int)curtab->tp_ch_used; - // Use the value of p_ch that we remembered. This is needed for when the - // GUI starts up, we can't be sure in what order things happen. And when - // p_ch was changed in another tab page. - curtab->tp_ch_used = p_ch; - - // Update cmdline_row to what it should be: just below the last window. - cmdline_row = topframe->fr_height + tabline_height() + global_stl_height(); - - // If cmdline_row is smaller than what it is supposed to be for 'cmdheight' - // then set old_p_ch to what it would be, so that the windows get resized - // properly for the new value. - if (cmdline_row < Rows - p_ch) { - old_p_ch = Rows - cmdline_row; - } - // Find bottom frame with width of screen. frame_T *frp = lastwin_nofloating()->w_frame; while (frp->fr_width != Columns && frp->fr_parent != NULL) { @@ -6772,60 +6735,52 @@ void command_height(void) } // Avoid changing the height of a window with 'winfixheight' set. - while (frp->fr_prev != NULL && frp->fr_layout == FR_LEAF - && frp->fr_win->w_p_wfh) { + while (frp->fr_prev != NULL && frp->fr_layout == FR_LEAF && frp->fr_win->w_p_wfh) { frp = frp->fr_prev; } - if (starting != NO_SCREEN) { - cmdline_row = Rows - (int)p_ch; - - if (p_ch > old_p_ch) { // p_ch got bigger - while (p_ch > old_p_ch) { - if (frp == NULL) { - emsg(_(e_noroom)); - p_ch = old_p_ch; - curtab->tp_ch_used = p_ch; - cmdline_row = Rows - (int)p_ch; - break; - } - int h = frp->fr_height - frame_minheight(frp, NULL); - h = MIN(h, (int)p_ch - old_p_ch); - old_p_ch += h; - frame_add_height(frp, -h); - frp = frp->fr_prev; - } - - // Recompute window positions. - win_comp_pos(); - - if (!need_wait_return) { - // clear the lines added to cmdline - if (full_screen) { - grid_clear(&default_grid, cmdline_row, Rows, 0, Columns, 0); - } - msg_row = cmdline_row; - } - redraw_cmdline = true; - return; + while (p_ch > old_p_ch && command_frame_height) { + if (frp == NULL) { + emsg(_(e_noroom)); + p_ch = old_p_ch; + break; } - - msg_row = MAX(msg_row, cmdline_row); - redraw_cmdline = true; + int h = MIN((int)(p_ch - old_p_ch), frp->fr_height - frame_minheight(frp, NULL)); + frame_add_height(frp, -h); + old_p_ch += h; + frp = frp->fr_prev; + } + if (p_ch < old_p_ch && command_frame_height) { + frame_add_height(frp, (int)(old_p_ch - p_ch)); } - frame_add_height(frp, (int)(old_p_ch - p_ch)); // Recompute window positions. - if (frp != lastwin->w_frame) { - win_comp_pos(); + win_comp_pos(); + cmdline_row = Rows - (int)p_ch; + redraw_cmdline = true; + + // Clear the cmdheight area. + if (msg_scrolled == 0 && full_screen) { + ScreenGrid *grid = &default_grid; + if (!ui_has(kUIMessages)) { + msg_grid_validate(); + grid = &msg_grid_adj; + } + grid_clear(grid, cmdline_row, Rows, 0, Columns, 0); + msg_row = cmdline_row; } + + // Use the value of p_ch that we remembered. This is needed for when the + // GUI starts up, we can't be sure in what order things happen. And when + // p_ch was changed in another tab page. + curtab->tp_ch_used = p_ch; } // Resize frame "frp" to be "n" lines higher (negative for less high). // Also resize the frames it is contained in. static void frame_add_height(frame_T *frp, int n) { - frame_new_height(frp, frp->fr_height + n, false, false); + frame_new_height(frp, frp->fr_height + n, false, false, false); while (true) { frp = frp->fr_parent; if (frp == NULL) { @@ -6895,7 +6850,7 @@ static bool resize_frame_for_status(frame_T *fr) emsg(_(e_noroom)); return false; } else if (fp != fr) { - frame_new_height(fp, fp->fr_height - 1, false, false); + frame_new_height(fp, fp->fr_height - 1, false, false, false); frame_fix_height(wp); win_comp_pos(); } else { @@ -6916,7 +6871,7 @@ static bool resize_frame_for_winbar(frame_T *fr) emsg(_(e_noroom)); return false; } - frame_new_height(fp, fp->fr_height - 1, false, false); + frame_new_height(fp, fp->fr_height - 1, false, false, false); win_new_height(wp, wp->w_height + 1); frame_fix_height(wp); win_comp_pos(); @@ -7305,7 +7260,7 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) fr->fr_height = sn->fr_height; fr->fr_width = sn->fr_width; if (fr->fr_layout == FR_LEAF) { - frame_new_height(fr, fr->fr_height, false, false); + frame_new_height(fr, fr->fr_height, false, false, false); frame_new_width(fr, fr->fr_width, false, false); wp = sn->fr_win; } diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index 3addcb957c..22700990c1 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -159,6 +159,52 @@ describe('cmdline', function() endfunc ]]) + feed(':resize -3') + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] }| + |*4 + ]]) + + -- :resize now also changes 'cmdheight' accordingly + feed(':set cmdheight+=1') + screen:expect([[ + ^ | + {1:~ }| + {3:[No Name] }| + |*5 + ]]) + feed(':set cmdheight-=1') + + -- using more space moves the status line up + feed(':set cmdheight+=1') + screen:expect([[ + ^ | + {1:~ }| + {3:[No Name] }| + |*5 + ]]) + + -- reducing cmdheight moves status line down + feed(':set cmdheight-=2') + screen:expect([[ + ^ | + {1:~ }|*3 + {3:[No Name] }| + |*3 + ]]) + + -- reducing window size and then setting cmdheight + feed(':resize -1') + feed(':set cmdheight=1') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + -- setting 'cmdheight' works after outputting two messages feed(':call EchoTwo()') screen:expect([[ diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index adf75c2836..db5e3f6857 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -82,7 +82,7 @@ describe('messages', function() NoSuchFil^e | three | {1:~ }|*5 - from DebugSilent visual | + | {9:E447: Can't find file "NoSuchFile" in path} | ]]) end) diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index b58bf0bf43..fac982354c 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -299,7 +299,7 @@ describe('splitkeep', function() c | {1:~ }| {3:[No Name] }| - | + :call win_move_statusline(win, 1) | ]]) end) diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 40c09e61ac..d5f7c928de 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -4181,4 +4181,28 @@ func Test_autocmd_BufWinLeave_with_vsp() exe "bw! " .. dummy endfunc +func Test_OptionSet_cmdheight() + set mouse=a laststatus=2 + au OptionSet cmdheight :let &l:ch = v:option_new + + resize -1 + call assert_equal(2, &l:ch) + resize +1 + call assert_equal(1, &l:ch) + + call Ntest_setmouse(&lines - 1, 1) + call feedkeys("\", 'xt') + call Ntest_setmouse(&lines - 2, 1) + call feedkeys("\", 'xt') + call assert_equal(2, &l:ch) + + tabnew | resize +1 + call assert_equal(1, &l:ch) + tabfirst + call assert_equal(2, &l:ch) + + tabonly + set cmdheight& mouse& laststatus& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index cbce0e908d..3fce086040 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -291,9 +291,10 @@ func Test_changing_cmdheight() call term_sendkeys(buf, ":resize -3\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {}) - " using the space available doesn't change the status line - call term_sendkeys(buf, ":set cmdheight+=3\") + " :resize now also changes 'cmdheight' accordingly + call term_sendkeys(buf, ":set cmdheight+=1\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {}) + call term_sendkeys(buf, ":set cmdheight-=1\") " using more space moves the status line up call term_sendkeys(buf, ":set cmdheight+=1\") @@ -312,7 +313,7 @@ func Test_changing_cmdheight() call term_sendkeys(buf, ":call EchoTwo()\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_6', {}) - " increasing 'cmdheight' doesn't clear the messages that need hit-enter + " decreasing 'cmdheight' doesn't clear the messages that need hit-enter call term_sendkeys(buf, ":call EchoOne()\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_7', {}) diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 343bc9fd83..24517f90cb 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -56,7 +56,6 @@ func Test_window_cmd_cmdwin_with_vsp() endfunc func Test_cmdheight_not_changed() - throw 'Skipped: N/A' set cmdheight=2 set winminheight=0 augroup Maximize -- cgit From b61051ccb4c23958d43d285b8b801af11620264f Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 1 Sep 2024 16:54:30 -0700 Subject: feat(func): allow manual cache invalidation for _memoize This commit also adds some tests for the existing memoization functionality. --- runtime/lua/vim/func.lua | 14 ++-- runtime/lua/vim/func/_memoize.lua | 56 ++++++++++---- runtime/lua/vim/treesitter/query.lua | 4 +- test/functional/func/memoize_spec.lua | 142 ++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 test/functional/func/memoize_spec.lua diff --git a/runtime/lua/vim/func.lua b/runtime/lua/vim/func.lua index f71659ffb4..fc8fa62c71 100644 --- a/runtime/lua/vim/func.lua +++ b/runtime/lua/vim/func.lua @@ -3,9 +3,6 @@ local M = {} -- TODO(lewis6991): Private for now until: -- - There are other places in the codebase that could benefit from this -- (e.g. LSP), but might require other changes to accommodate. --- - Invalidation of the cache needs to be controllable. Using weak tables --- is an acceptable invalidation policy, but it shouldn't be the only --- one. -- - I don't think the story around `hash` is completely thought out. We -- may be able to have a good default hash by hashing each argument, -- so basically a better 'concat'. @@ -17,6 +14,10 @@ local M = {} --- Internally uses a |lua-weaktable| to cache the results of {fn} meaning the --- cache will be invalidated whenever Lua does garbage collection. --- +--- The cache can also be manually invalidated by calling `:clear()` on the returned object. +--- Calling this function with no arguments clears the entire cache; otherwise, the arguments will +--- be interpreted as function inputs, and only the cache entry at their hash will be cleared. +--- --- The memoized function returns shared references so be wary about --- mutating return values. --- @@ -32,11 +33,12 @@ local M = {} --- first n arguments passed to {fn}. --- --- @param fn F Function to memoize. ---- @param strong? boolean Do not use a weak table +--- @param weak? boolean Use a weak table (default `true`) --- @return F # Memoized version of {fn} --- @nodoc -function M._memoize(hash, fn, strong) - return require('vim.func._memoize')(hash, fn, strong) +function M._memoize(hash, fn, weak) + -- this is wrapped in a function to lazily require the module + return require('vim.func._memoize')(hash, fn, weak) end return M diff --git a/runtime/lua/vim/func/_memoize.lua b/runtime/lua/vim/func/_memoize.lua index 6e557905a7..c46f878067 100644 --- a/runtime/lua/vim/func/_memoize.lua +++ b/runtime/lua/vim/func/_memoize.lua @@ -1,5 +1,7 @@ --- Module for private utility functions +--- @alias vim.func.MemoObj { _hash: (fun(...): any), _weak: boolean?, _cache: table } + --- @param argc integer? --- @return fun(...): any local function concat_hash(argc) @@ -33,29 +35,49 @@ local function resolve_hash(hash) return hash end +--- @param weak boolean? +--- @return table +local create_cache = function(weak) + return setmetatable({}, { + __mode = weak ~= false and 'kv', + }) +end + --- @generic F: function --- @param hash integer|string|fun(...): any --- @param fn F ---- @param strong? boolean +--- @param weak? boolean --- @return F -return function(hash, fn, strong) +return function(hash, fn, weak) vim.validate('hash', hash, { 'number', 'string', 'function' }) vim.validate('fn', fn, 'function') + vim.validate('weak', weak, 'boolean', true) - ---@type table> - local cache = {} - if not strong then - setmetatable(cache, { __mode = 'kv' }) - end - - hash = resolve_hash(hash) + --- @type vim.func.MemoObj + local obj = { + _cache = create_cache(weak), + _hash = resolve_hash(hash), + _weak = weak, + --- @param self vim.func.MemoObj + clear = function(self, ...) + if select('#', ...) == 0 then + self._cache = create_cache(self._weak) + return + end + local key = self._hash(...) + self._cache[key] = nil + end, + } - return function(...) - local key = hash(...) - if cache[key] == nil then - cache[key] = vim.F.pack_len(fn(...)) - end - - return vim.F.unpack_len(cache[key]) - end + return setmetatable(obj, { + --- @param self vim.func.MemoObj + __call = function(self, ...) + local key = self._hash(...) + local cache = self._cache + if cache[key] == nil then + cache[key] = vim.F.pack_len(fn(...)) + end + return vim.F.unpack_len(cache[key]) + end, + }) end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index f9c497337f..2b3b9096a6 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -902,8 +902,8 @@ function Query:iter_captures(node, source, start, stop) local cursor = vim._create_ts_querycursor(node, self.query, start, stop, { match_limit = 256 }) - local apply_directives = memoize(match_id_hash, self.apply_directives, true) - local match_preds = memoize(match_id_hash, self.match_preds, true) + local apply_directives = memoize(match_id_hash, self.apply_directives, false) + local match_preds = memoize(match_id_hash, self.match_preds, false) local function iter(end_line) local capture, captured_node, match = cursor:next_capture() diff --git a/test/functional/func/memoize_spec.lua b/test/functional/func/memoize_spec.lua new file mode 100644 index 0000000000..ca518ab88d --- /dev/null +++ b/test/functional/func/memoize_spec.lua @@ -0,0 +1,142 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local clear = n.clear +local exec_lua = n.exec_lua +local eq = t.eq + +describe('vim.func._memoize', function() + before_each(clear) + + it('caches function results based on their parameters', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('caches function results using a weak table by default', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(3, exec_lua([[return _G.count]])) + end) + + it('can cache using a strong table', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end, false) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('can clear a single cache entry', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder:clear(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) + + it('can clear the entire cache', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder:clear() + adder(1, 2) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(4, exec_lua([[return _G.count]])) + end) + + it('can cache functions that return nil', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return nil + end) + + collectgarbage('stop') + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder:clear() + adder(1, 2) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) +end) -- cgit From d288f7003d256f0f4f85254974239c5f47003ede Mon Sep 17 00:00:00 2001 From: glepnir Date: Sun, 5 Jan 2025 21:52:50 +0800 Subject: fix(popup): wrong extmark data sync when lines changed in popup preview #30246 Problem: when popup preview buffer has filetype like markdown and ts is enabled, the extmark clean and update not correct, if add the extmark sync there has lots of duplicate codes like nvim_buf_set_lines. Solution: use nvim_buf_set_lines api internally to set info to popup preview buffer. --- src/nvim/popupmenu.c | 70 +++++++++++------------ test/functional/ui/popupmenu_spec.lua | 105 +++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 38 deletions(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 75e5a52829..2b1dd22b1a 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -7,6 +7,7 @@ #include #include +#include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" @@ -32,6 +33,7 @@ #include "nvim/highlight_defs.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -787,36 +789,41 @@ void pum_redraw(void) } } -/// set info text to preview buffer. +/// Set the informational text in the preview buffer when the completion +/// item does not include a dedicated preview or popup window. +/// +/// @param[in] buf Buffer where the text will be set. +/// @param[in] info Informational text to display in the preview buffer. +/// @param[in] lnum Where to start the text. Incremented for each added line. +/// @param[out] max_width Maximum width of the displayed text. static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *max_width) { - bcount_t inserted_bytes = 0; - for (char *p = info; *p != NUL;) { - int text_width = 0; - char *e = vim_strchr(p, '\n'); - if (e == NULL) { - ml_append_buf(buf, (*lnum)++, p, 0, false); - text_width = (int)mb_string2cells(p); - if (text_width > *max_width) { - *max_width = text_width; - } - break; - } - *e = NUL; - ml_append_buf(buf, (*lnum)++, p, (int)(e - p + 1), false); - inserted_bytes += (bcount_t)strlen(p) + 1; - text_width = (int)mb_string2cells(p); - if (text_width > *max_width) { - *max_width = text_width; - } - *e = '\n'; - p = e + 1; + Error err = ERROR_INIT; + Arena arena = ARENA_EMPTY; + Array replacement = ARRAY_DICT_INIT; + char *token = NULL; + char *line = os_strtok(info, "\n", &token); + buf->b_p_ma = true; + while (line != NULL) { + ADD(replacement, STRING_OBJ(cstr_to_string(line))); + (*lnum)++; + (*max_width) = MAX(*max_width, (int)mb_string2cells(line)); + line = os_strtok(NULL, "\n", &token); } - // delete the empty last line - ml_delete_buf(buf, buf->b_ml.ml_line_count, false); - if (get_cot_flags() & kOptCotFlagPopup) { - extmark_splice(buf, 1, 0, 1, 0, 0, buf->b_ml.ml_line_count, 0, inserted_bytes, kExtmarkNoUndo); + + int original_textlock = textlock; + if (textlock > 0) { + textlock = 0; + } + nvim_buf_set_lines(0, buf->handle, 0, -1, false, replacement, &arena, &err); + textlock = original_textlock; + if (ERROR_SET(&err)) { + emsg(err.msg); + api_clear_error(&err); } + arena_mem_free(arena_finish(&arena)); + api_free_array(replacement); + buf->b_p_ma = false; } /// adjust floating info preview window position @@ -866,14 +873,6 @@ win_T *pum_set_info(int selected, char *info) if (!wp) { return NULL; } - } else { - // clean exist buffer - linenr_T count = wp->w_buffer->b_ml.ml_line_count; - while (!buf_is_empty(wp->w_buffer)) { - ml_delete_buf(wp->w_buffer, 1, false); - } - bcount_t deleted_bytes = get_region_bytecount(wp->w_buffer, 1, count, 0, 0); - extmark_splice(wp->w_buffer, 1, 0, count, 0, deleted_bytes, 1, 0, 0, kExtmarkNoUndo); } linenr_T lnum = 0; int max_info_width = 0; @@ -1011,7 +1010,8 @@ static bool pum_set_selected(int n, int repeat) && (curbuf->b_nwindows == 1) && (curbuf->b_fname == NULL) && bt_nofile(curbuf) - && (curbuf->b_p_bh[0] == 'w')) { + && (curbuf->b_p_bh[0] == 'w') + && !use_float) { // Already a "wipeout" buffer, make it empty. while (!buf_is_empty(curbuf)) { ml_delete(1, false); diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index d1228d3607..60d59190ce 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -1680,7 +1680,7 @@ describe('builtin popupmenu', function() end) end - describe('floating window preview #popup', function() + describe('floating window preview popup', function() it('pum popup preview', function() --row must > 10 screen:try_resize(40, 11) @@ -1693,14 +1693,29 @@ describe('builtin popupmenu', function() endfunc set omnifunc=Omni_test set completeopt=menu,popup - funct Set_info() let comp_info = complete_info() if comp_info['selected'] == 2 call nvim__complete_set(comp_info['selected'], {"info": "3info"}) endif endfunc - autocmd CompleteChanged * call Set_info() + funct TsHl() + let comp_info = complete_info() + if get(comp_info, 'previewbufnr', 0) > 0 + call v:lua.vim.treesitter.start(comp_info['preview_bufnr'], 'markdown') + endif + if comp_info['selected'] == 0 + call nvim__complete_set(comp_info['selected'], {"info": "```lua\nfunction test()\n print('foo')\nend\n```"}) + endif + endfunc + augroup Group + au! + autocmd CompleteChanged * :call Set_info() + augroup END + funct TestTs() + autocmd! Group + autocmd CompleteChanged * call TsHl() + endfunc ]]) feed('Gi') --floating preview in right @@ -2004,6 +2019,90 @@ describe('builtin popupmenu', function() ]], } end + feed('') + + -- works when scroll with treesitter highlight + command('call TestTs()') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + one^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| + ## grid 9 + {n:```lua }| + {n:function test()}| + {n: print('foo') }| + {n:end }| + {n:``` }| + {n: }| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 0, false, 100 }, + [9] = { 1005, 'NW', 1, 1, 19, false, 50 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 3, + linecount = 1, + sum_scroll_delta = 0, + }, + [9] = { + win = 1005, + topline = 0, + botline = 6, + curline = 0, + curcol = 0, + linecount = 5, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [9] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1005, + }, + }, + }) + else + screen:expect({ + grid = [[ + one^ | + {s:one }{n:```lua }{1: }| + {n:two function test()}{1: }| + {n:looooooooooooooong print('foo') }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }{n: }{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 3} | + ]], + }) + end end) end) -- cgit From bf48dfadeccc37527e9b59b1c0f529ea889bf735 Mon Sep 17 00:00:00 2001 From: glepnir Date: Sun, 5 Jan 2025 21:57:53 +0800 Subject: fix(api): nvim__complete_set requires completeopt=popup #31177 Problem: If completeopt does not include "popup" flag, nvim__complete_set still auto-creates a floating preview window. Solution: Fail if completeopt does not include the "popup" flag. --- src/nvim/api/vim.c | 7 ++++++- test/functional/editor/completion_spec.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 25f44bb4eb..e3e69f4ff6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -44,6 +44,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/log.h" #include "nvim/lua/executor.h" @@ -2255,9 +2256,13 @@ void nvim_error_event(uint64_t channel_id, Integer lvl, String data) /// @return Dict containing these keys: /// - winid: (number) floating window id /// - bufnr: (number) buffer id in floating window -Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena) +Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena, Error *err) { Dict rv = arena_dict(arena, 2); + if ((get_cot_flags() & kOptCotFlagPopup) == 0) { + api_set_error(err, kErrorTypeException, "completeopt option does not include popup"); + return rv; + } if (HAS_KEY(opts, complete_set, info)) { win_T *wp = pum_set_info((int)index, opts->info.data); if (wp) { diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index c20d925713..7c68de272b 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -10,6 +10,7 @@ local fn = n.fn local command = n.command local api = n.api local poke_eventloop = n.poke_eventloop +local exec_lua = n.exec_lua describe('completion', function() local screen @@ -1326,4 +1327,29 @@ describe('completion', function() ]], }) end) + + describe('nvim__complete_set', function() + it("fails when 'completeopt' does not include popup", function() + exec_lua([[ + function _G.omni_test(findstart, base) + if findstart == 1 then + return vim.fn.col('.') - 1 + end + return { { word = 'one' } } + end + vim.api.nvim_create_autocmd('CompleteChanged', { + callback = function() + local ok, err = pcall(vim.api.nvim__complete_set, 0, { info = '1info' }) + if not ok then + vim.g.err_msg = err + end + end, + }) + vim.opt.completeopt = 'menu,menuone' + vim.opt.omnifunc = 'v:lua.omni_test' + ]]) + feed('S') + eq('completeopt option does not include popup', api.nvim_get_var('err_msg')) + end) + end) end) -- cgit From 847c28f6f6ccdfa6d7887605b84137e00e5f7968 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Sun, 5 Jan 2025 14:59:50 +0100 Subject: fix(cmdline): always show cmdline when it is a prompt #31866 Cmdline prompts should ignore `cmd_silent`. --- src/nvim/ex_getln.c | 7 +++---- test/functional/ui/cmdline_spec.lua | 12 ++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 09006484ca..b26a2ef1bc 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2815,8 +2815,10 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl ccline.one_key = one_key; ccline.mouse_used = mouse_used; + const bool cmd_silent_saved = cmd_silent; int msg_silent_saved = msg_silent; msg_silent = 0; + cmd_silent = false; // Want to see the prompt. char *const ret = (char *)command_line_enter(firstc, 1, 0, false); ccline.redraw_state = kCmdRedrawNone; @@ -2825,6 +2827,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl restore_cmdline(&save_ccline); } msg_silent = msg_silent_saved; + cmd_silent = cmd_silent_saved; // Restore msg_col, the prompt from input() may have changed it. // But only if called recursively and the commandline is therefore being // restored to an old one; if not, the input() prompt stays on the screen, @@ -4792,9 +4795,6 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const } } - const bool cmd_silent_save = cmd_silent; - - cmd_silent = false; // Want to see the prompt. // Only the part of the message after the last NL is considered as // prompt for the command line, unlsess cmdline is externalized const char *p = prompt; @@ -4829,5 +4829,4 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const // Since the user typed this, no need to wait for return. need_wait_return = false; msg_didout = false; - cmd_silent = cmd_silent_save; } diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 93ea2b9186..a2722a4139 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1038,6 +1038,18 @@ describe('cmdline redraw', function() ]], } end) + + it('silent prompt', function() + command([[nmap T :call confirm("Save changes?", "&Yes\n&No\n&Cancel")]]) + feed('T') + screen:expect([[ + | + {3: }| + | + {6:Save changes?} | + {6:[Y]es, (N)o, (C)ancel: }^ | + ]]) + end) end) describe('statusline is redrawn on entering cmdline', function() -- cgit From bd2a4edf1b9390521d822fc990369f00694ba320 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 5 Jan 2025 16:45:54 +0100 Subject: vim-patch:202ebc6: runtime(zsh): sync syntax script with upstream repo fixes: vim/vim#16371 https://github.com/vim/vim/commit/202ebc6ced6c5d7c0cdd9a79867af14aab39f75d Co-authored-by: Christian Brabandt --- runtime/syntax/zsh.vim | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/runtime/syntax/zsh.vim b/runtime/syntax/zsh.vim index 084f8cdb41..04b39aeac0 100644 --- a/runtime/syntax/zsh.vim +++ b/runtime/syntax/zsh.vim @@ -2,7 +2,7 @@ " Language: Zsh shell script " Maintainer: Christian Brabandt " Previous Maintainer: Nikolai Weibull -" Latest Revision: 2022-07-26 +" Latest Revision: 2024 Jan 04 " License: Vim (see :h license) " Repository: https://github.com/chrisbra/vim-zsh @@ -48,8 +48,9 @@ syn match zshPOSIXQuoted '\\u[0-9a-fA-F]\{1,4}' syn match zshPOSIXQuoted '\\U[1-9a-fA-F]\{1,8}' syn region zshString matchgroup=zshStringDelimiter start=+"+ end=+"+ - \ contains=zshQuoted,@zshDerefs,@zshSubstQuoted fold + \ contains=@Spell,zshQuoted,@zshDerefs,@zshSubstQuoted fold syn region zshString matchgroup=zshStringDelimiter start=+'+ end=+'+ fold + \ contains=@Spell syn region zshPOSIXString matchgroup=zshStringDelimiter start=+\$'+ \ skip=+\\[\\']+ end=+'+ contains=zshPOSIXQuoted,zshQuoted syn match zshJobSpec '%\(\d\+\|?\=\w\+\|[%+-]\)' @@ -68,7 +69,7 @@ syn keyword zshConditional if then elif else fi esac select syn keyword zshCase case nextgroup=zshCaseWord skipwhite syn match zshCaseWord /\S\+/ nextgroup=zshCaseIn skipwhite contained transparent -syn keyword zshCaseIn in nextgroup=zshCasePattern skipwhite skipnl contained +syn keyword zshCaseIn in nextgroup=zshComment,zshCasePattern skipwhite skipnl contained syn match zshCasePattern /\S[^)]*)/ contained syn keyword zshRepeat while until repeat @@ -94,22 +95,24 @@ syn match zshRedir '|\@1' - \ contains=@zshSubst,@zshDerefs,zshQuoted,zshPOSIXString + \ end='^\z1$' + \ contains=@Spell,@zshSubst,@zshDerefs,zshQuoted,zshPOSIXString syn region zshHereDoc matchgroup=zshRedir \ start='<\@' - \ contains=@zshSubst,@zshDerefs,zshQuoted,zshPOSIXString + \ end='^\z1$' + \ contains=@Spell syn region zshHereDoc matchgroup=zshRedir \ start='<\@' - \ contains=@zshSubst,@zshDerefs,zshQuoted,zshPOSIXString + \ end='^\t*\z1$' + \ contains=@Spell syn region zshHereDoc matchgroup=zshRedir \ start=+<\@' + \ end='^\z1$' + \ contains=@Spell syn region zshHereDoc matchgroup=zshRedir \ start=+<\@' + \ end='^\t*\z1$' + \ contains=@Spell syn match zshVariable '\<\h\w*' contained -- cgit From 548f19ccc3018aa563d25cf99a9ce70a56b115fe Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 5 Jan 2025 09:51:51 -0800 Subject: feat(health): close checkhealth buffers with q #31870 --- runtime/lua/vim/health.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 3e831371b8..ebad000c5b 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -408,6 +408,15 @@ function M._check(mods, plugin_names) vim.cmd.redraw() vim.print('') + -- Quit with 'q' inside healthcheck buffers. + vim.api.nvim_buf_set_keymap( + bufnr, + 'n', + 'q', + 'q', + { silent = true, noremap = true, nowait = true } + ) + -- Once we're done writing checks, set nomodifiable. vim.bo[bufnr].modifiable = false end -- cgit From ac5a6d9ff56b451f5f24dfd3c46a6447375d3394 Mon Sep 17 00:00:00 2001 From: Daiki Noda Date: Mon, 6 Jan 2025 04:28:31 +0900 Subject: build: fix RelWithDebInfo optimization flags #31802 Problem: RelWithDebInfo generates redundant flags: Compilation: /usr/bin/cc -O2 -g -Og -g The `CMAKE_C_FLAGS_RELWITHDEBINFO` variable is being modified in a way that caused duplicate `-Og` and `-g` flags to be added. The resulting flags were `-O2 -g -Og -g`. - `-Og` (Optimize for debugging) and `-O2` (Optimize for performance) are different optimization levels. We can't use both at once. - The duplicate `-g` flag is redundant and no effect. multiple -O flags has no effect for code, just redundant. > If you use multiple -O options, with or without level numbers, the last such option is the one that is effective. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html Solution: Adjust the flags to use the more appropriate `-O2 -g`. Compilation: /usr/bin/cc -O2 -g BEFORE: ``` :verbose version NVIM v0.11.0-dev-1443+ge00cd1ab40 Build type: RelWithDebInfo LuaJIT 2.1.1734355927 Compilation: /usr/bin/cc -O2 -g -Og -g -flto -fno-fat-lto-ob jects -Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict ... ``` AFTER: ``` :verbose version NVIM v0.11.0-dev-e00cd1ab4-dirty Build type: RelWithDebInfo LuaJIT 2.1.1734355927 Compilation: /usr/bin/cc -O2 -g -flto -fno-fat-lto-objects - Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-protot ... ``` --- CMakeLists.txt | 5 ----- CMakePresets.json | 2 +- contrib/local.mk.example | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a19c04c26f..b9ceb05aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,11 +149,6 @@ set(NVIM_API_LEVEL 13) # Bump this after any API/stdlib change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. set(NVIM_API_PRERELEASE true) -# Build-type: RelWithDebInfo -# /Og means something different in MSVC -if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Og -g") -endif() # We _want_ assertions in RelWithDebInfo build-type. if(CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES DNDEBUG) string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") diff --git a/CMakePresets.json b/CMakePresets.json index 1d214d7801..b863d88608 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -10,7 +10,7 @@ { "name": "default", "displayName": "RelWithDebInfo", - "description": "Enables optimizations (-Og or -O2) with debug information", + "description": "Enables optimizations (-O2) with debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }, diff --git a/contrib/local.mk.example b/contrib/local.mk.example index 718bf9f2a3..f3024a147c 100644 --- a/contrib/local.mk.example +++ b/contrib/local.mk.example @@ -14,7 +14,7 @@ # # - Debug: Disables optimizations (-O0), enables debug information. # -# - RelWithDebInfo: Enables optimizations (-Og or -O2) with debug information. +# - RelWithDebInfo: Enables optimizations (-O2) with debug information. # # - MinSizeRel: Enables all -O2 optimization that do not typically # increase code size, and performs further optimizations -- cgit From 570a8da01b55c3aad1f057be236f55ccf82ed8af Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 5 Jan 2025 11:31:39 -0800 Subject: fix(health): "q" should not close last window #31876 --- runtime/lua/vim/health.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index ebad000c5b..01166628b1 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -409,13 +409,12 @@ function M._check(mods, plugin_names) vim.print('') -- Quit with 'q' inside healthcheck buffers. - vim.api.nvim_buf_set_keymap( - bufnr, - 'n', - 'q', - 'q', - { silent = true, noremap = true, nowait = true } - ) + vim.keymap.set('n', 'q', function() + local ok, _ = pcall(vim.cmd.close) + if not ok then + vim.cmd.bdelete() + end + end, { buffer = bufnr, silent = true, noremap = true, nowait = true }) -- Once we're done writing checks, set nomodifiable. vim.bo[bufnr].modifiable = false -- cgit From 5e02a2c47072ec08279b830daa6f82e39ba86c6e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 5 Jan 2025 17:10:16 -0800 Subject: "nvim -es": disable shada #21723 Problem: `nvim -es` (and `nvim -Es`) is the recommended way to non-interactively run commands/vimscript. But it enables shada by default, which is usually not wanted. Solution: - Disable shada by default for `nvim -es/-Es`. This can be overridden by `-i foo` if needed. - Do NOT change the 'loadplugins' default. - User config + packages _should_ be enabled by default, for both `nvim -es` and `nvim -l`. Else any Lua packages you have can't be accessed without `-u path/to/config`, which is clumsy. - Use-cases: ``` nvim --headless "+Lazy! sync" +qa would become: nvim -es "+Lazy! sync" nvim --headless +PlugInstall +qall would become: nvim -es +PlugInstall ``` - Opt-out (`--clean` or `-u NONE`) is much easier than opt-in (`-u path/to/config`). - User config/packages are analogous to pip packages, which are expected when doing `python -c ...`. related: 7c94bcd2d77e2e54b8836ab8325460a367b79eae related: ddd0eb6f5120a09b97867d2561ea61309038ccd2 --- runtime/doc/lua.txt | 14 ++++++------- runtime/doc/news.txt | 1 + runtime/doc/starting.txt | 19 ++++++++++------- runtime/doc/vim_diff.txt | 2 ++ src/nvim/main.c | 6 ++++-- src/nvim/message.c | 2 +- test/functional/core/startup_spec.lua | 39 ++++++++++++++++++++++++++++------- 7 files changed, 58 insertions(+), 25 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index b7c5a50443..6547a76f56 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -618,20 +618,20 @@ Example: TCP echo-server *tcp-server* Multithreading *lua-loop-threading* Plugins can perform work in separate (os-level) threads using the threading -APIs in luv, for instance `vim.uv.new_thread`. Note that every thread -gets its own separate Lua interpreter state, with no access to Lua globals -in the main thread. Neither can the state of the editor (buffers, windows, -etc) be directly accessed from threads. +APIs in luv, for instance `vim.uv.new_thread`. Each thread has its own +separate Lua interpreter state, with no access to Lua globals on the main +thread. Neither can the editor state (buffers, windows, etc) be directly +accessed from threads. -A subset of the `vim.*` API is available in threads. This includes: +A subset of the `vim.*` stdlib is available in threads, including: - `vim.uv` with a separate event loop per thread. - `vim.mpack` and `vim.json` (useful for serializing messages between threads) - `require` in threads can use Lua packages from the global |package.path| - `print()` and `vim.inspect` - `vim.diff` -- most utility functions in `vim.*` for working with pure Lua values - like `vim.split`, `vim.tbl_*`, `vim.list_*`, and so on. +- Most utility functions in `vim.*` that work with pure Lua values, like + `vim.split`, `vim.tbl_*`, `vim.list_*`, etc. - `vim.is_thread()` returns true from a non-main thread. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index d573b01baa..b7606e65f5 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -295,6 +295,7 @@ PLUGINS STARTUP +• |-es| ("script mode") disables shada by default. • Nvim will fail if the |--listen| or |$NVIM_LISTEN_ADDRESS| address is invalid, instead of silently skipping an invalid address. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 3b0fa2b371..0bfbea75fb 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -207,12 +207,12 @@ argument. :print :set With |:verbose| or 'verbose', other commands display on stderr: > - nvim -es +":verbose echo 'foo'" - nvim -V1 -es +foo - -< User |config| is skipped unless |-u| was given. - Swap file is skipped (like |-n|). - User |shada| is loaded (unless "-i NONE" is given). + nvim -es +"verbose echo 'foo'" + nvim -V1 -es +"echo 'foo'" +< + Skips user |config| unless |-u| was given. + Disables |shada| unless |-i| was given. + Disables swapfile (like |-n|). *-l* -l {script} [args] @@ -235,6 +235,11 @@ argument. nvim +q -l foo.lua < This loads Lua module "bar" before executing "foo.lua": > nvim +"lua require('bar')" -l foo.lua +< *lua-shebang* + You can set the "shebang" of the script so that Nvim executes + the script when called with "./" from a shell (remember to + "chmod u+x"): > + #!/usr/bin/env -S nvim -l < Skips user |config| unless |-u| was given. Disables plugins unless 'loadplugins' was set. @@ -243,7 +248,7 @@ argument. *-ll* -ll {script} [args] - Execute a Lua script, similarly to |-l|, but the editor is not + Executes a Lua script, similarly to |-l|, but the editor is not initialized. This gives a Lua environment similar to a worker thread. See |lua-loop-threading|. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 9f28b373ee..a92ddf33e6 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -409,6 +409,8 @@ Startup: - |-es| and |-Es| have improved behavior: - Quits automatically, don't need "-c qa!". - Skips swap-file dialog. + - Optimized for non-interactive scripts: disables swapfile, shada. +- |-l| Executes Lua scripts non-interactively. - |-s| reads Normal commands from stdin if the script name is "-". - Reading text (instead of commands) from stdin |--|: - works by default: "-" file is optional diff --git a/src/nvim/main.c b/src/nvim/main.c index 2b55a48c12..58d110e8b2 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1228,6 +1228,9 @@ static void command_line_scan(mparm_T *parmp) if (exmode_active) { // "-es" silent (batch) Ex-mode silent_mode = true; parmp->no_swap_file = true; + if (p_shadafile == NULL || *p_shadafile == NUL) { + set_option_value_give_err(kOptShadafile, STATIC_CSTR_AS_OPTVAL("NONE"), 0); + } } else { // "-s {scriptin}" read from script file want_argument = true; } @@ -2085,8 +2088,7 @@ static void source_startup_scripts(const mparm_T *const parmp) { // If -u given, use only the initializations from that file and nothing else. if (parmp->use_vimrc != NULL) { - if (strequal(parmp->use_vimrc, "NONE") - || strequal(parmp->use_vimrc, "NORC")) { + if (strequal(parmp->use_vimrc, "NONE") || strequal(parmp->use_vimrc, "NORC")) { // Do nothing. } else { if (do_source(parmp->use_vimrc, false, DOSO_NONE, NULL) != OK) { diff --git a/src/nvim/message.c b/src/nvim/message.c index 661d0754d4..d45bc147cb 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2719,7 +2719,7 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) } /// @return true when messages should be printed to stdout/stderr: -/// - "batch mode" ("silent mode", -es/-Es) +/// - "batch mode" ("silent mode", -es/-Es/-l) /// - no UI and not embedded int msg_use_printf(void) { diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 642e2b0287..8ecd3dca97 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -136,7 +136,11 @@ describe('startup', function() vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') }) vim.list_extend(args, lua_args or {}) local out = fn.system(args, input):gsub('\r\n', '\n') - return eq(dedent(expected), out) + if type(expected) == 'function' then + return expected(out) + else + return eq(dedent(expected), out) + end end it('failure modes', function() @@ -172,6 +176,7 @@ describe('startup', function() it('Lua-error sets Nvim exitcode', function() local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua') matches('E5113: .* my pearls!!', proc:output()) + eq(0, proc.signal) eq(1, proc.status) eq(0, eval('v:shell_error')) @@ -282,14 +287,30 @@ describe('startup', function() eq(0, eval('v:shell_error')) end) - it('disables swapfile/shada/config/plugins', function() + it('disables swapfile/shada/config/plugins unless overridden', function() + local script = [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]] + finally(function() + os.remove('Xtest_shada') + end) + assert_l_out( 'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n', nil, nil, '-', - [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( - vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]] + script + ) + + -- User can override. + assert_l_out( + function(out) + return matches('updatecount=99 shadafile=Xtest_shada loadplugins=true scripts=2%d\n', out) + end, + { '+set updatecount=99', '-i', 'Xtest_shada', '+set loadplugins', '-u', 'NORC' }, + nil, + '-', + script ) end) end) @@ -572,19 +593,21 @@ describe('startup', function() eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' })) end) - it('-es/-Es disables swapfile, user config #8540', function() + it('-es/-Es disables swapfile/shada/config #8540', function() for _, arg in ipairs({ '-es', '-Es' }) do local out = fn.system({ nvim_prog, arg, - '+set swapfile? updatecount? shadafile?', + '+set updatecount? shadafile? loadplugins?', '+put =map(getscriptinfo(), {-> v:val.name})', '+%print', }) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. - eq(' swapfile updatecount=0 shadafile=\n', line1) - -- Standard plugins were loaded, but not user config. + eq(' updatecount=0 shadafile=NONE loadplugins\n', line1) + -- Standard plugins were loaded, but not user config. #31878 + local nrlines = #vim.split(out, '\n') + ok(nrlines > 20, '>20', nrlines) ok(string.find(out, 'man.lua') ~= nil) ok(string.find(out, 'init.vim') == nil) end -- cgit From b23c28845f9d067c63acb6fb0748c6ef91f0b8f2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 6 Jan 2025 09:11:48 +0800 Subject: vim-patch:21c37d7: runtime(vim): update base-syntax after v9.1.0936 https://github.com/vim/vim/commit/21c37d7f695077efe6df57806ff35da79adce1d5 Co-authored-by: Christian Brabandt --- runtime/syntax/vim.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 361bf6369d..02827f4914 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -60,7 +60,7 @@ syn case ignore syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo " Default highlighting groups {{{2 -syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit DiffText PmenuSbar TabLineSel TabLineFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC CursorIM LineNrAbove LineNrBelow +syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit DiffText PmenuSbar TabLineSel TabLineFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel ComplMatchIns Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC CursorIM LineNrAbove LineNrBelow syn match vimHLGroup contained "\" syn keyword vimOnlyHLGroup contained Menu Scrollbar ToolbarButton ToolbarLine Tooltip VisualNOS syn keyword nvimHLGroup contained FloatBorder FloatFooter FloatTitle MsgSeparator NormalFloat NormalNC Substitute TermCursor VisualNC Whitespace WinBar WinBarNC WinSeparator -- cgit From 53f5d528cf655e8ae2c4467d3a6701e66c60bbfb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 6 Jan 2025 09:13:00 +0800 Subject: vim-patch:6139766: runtime(vim): fix failing vim syntax test after v9.1.0985 related: vim/vim#16356 https://github.com/vim/vim/commit/6139766e825ca34948223cb4c88d3900b1940a17 Co-authored-by: h-east N/A patch: vim-patch:8a27d97: runtime(doc): Capitalise the mnemonic "Zero" for the 'z' flag of search() --- runtime/syntax/vim.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 02827f4914..5bea65436d 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -196,7 +196,7 @@ syn case match " All vimCommands are contained by vimIsCommand. {{{2 syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimCall,vimCatch,vimConst,vimDef,vimDefFold,vimDelcommand,@vimEcho,vimEnddef,vimEndfunction,vimExecute,vimIsCommand,vimExtCmd,vimFor,vimFunction,vimFuncFold,vimGlobal,vimHighlight,vimLet,vimLoadkeymap,vimMap,vimMark,vimMatch,vimNotFunc,vimNormal,vimSet,vimSleep,vimSyntax,vimThrow,vimUnlet,vimUnmap,vimUserCmd,vimMenu,vimMenutranslate,@vim9CmdList -syn cluster vim9CmdList contains=vim9Class,vim9Const,vim9Enum,vim9Export,vim9Final,vim9For,vim9Interface,vim9Type,vim9Var +syn cluster vim9CmdList contains=vim9Abstract,vim9Class,vim9Const,vim9Enum,vim9Export,vim9Final,vim9For,vim9Interface,vim9Type,vim9Var syn match vimCmdSep "[:|]\+" skipwhite nextgroup=@vimCmdList,vimSubst1 syn match vimIsCommand "\<\%(\h\w*\|[23]mat\%[ch]\)\>" contains=vimCommand syn match vimBang contained "!" -- cgit From 22dadf4c33740a67a2c7bc57435076aa7eadc01a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 6 Jan 2025 09:49:56 +0800 Subject: vim-patch:0c3e57b: runtime(doc): update index.txt, windows.txt and version9.txt closes: vim/vim#16357 https://github.com/vim/vim/commit/0c3e57b403e0e3a1fefca7bbd5ad4cb950eea616 Co-authored-by: h-east Co-authored-by: Aliaksei Budavei <32549825+zzzyxwvut@users.noreply.github.com> --- runtime/doc/index.txt | 17 +++++++++-------- runtime/doc/windows.txt | 8 ++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 0182123a12..51d1484c57 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1190,7 +1190,7 @@ tag command action ~ |:breakdel| :breakd[el] delete a debugger breakpoint |:breaklist| :breakl[ist] list debugger breakpoints |:browse| :bro[wse] use file selection dialog -|:bufdo| :bufdo execute command in each listed buffer +|:bufdo| :bufd[o] execute command in each listed buffer |:buffers| :buffers list all files in the buffer list |:bunload| :bun[load] unload a specific buffer |:bwipeout| :bw[ipeout] really delete a buffer @@ -1206,7 +1206,7 @@ tag command action ~ |:cafter| :caf[ter] go to error after current cursor |:call| :cal[l] call a function |:catch| :cat[ch] part of a :try command -|:cbefore| :cbef[ore] go to error before current cursor +|:cbefore| :cbe[fore] go to error before current cursor |:cbelow| :cbel[ow] go to error below current line |:cbottom| :cbo[ttom] scroll to the bottom of the quickfix window |:cbuffer| :cb[uffer] parse error messages and jump to first error @@ -1262,6 +1262,7 @@ tag command action ~ |:delete| :d[elete] delete lines |:debug| :deb[ug] run a command in debugging mode |:debuggreedy| :debugg[reedy] read debug mode commands from normal input +|:defer| :defe[r] call function when current function is done |:delcommand| :delc[ommand] delete user-defined command |:delfunction| :delf[unction] delete a user function |:delmarks| :delm[arks] delete marks @@ -1271,7 +1272,7 @@ tag command action ~ |:diffpatch| :diffp[atch] apply a patch and show differences |:diffput| :diffpu[t] remove differences in other buffer |:diffsplit| :diffs[plit] show differences with another file -|:diffthis| :diffthis make current window a diff window +|:diffthis| :difft[his] make current window a diff window |:digraphs| :dig[raphs] show or enter digraphs |:display| :di[splay] display registers |:djump| :dj[ump] jump to #define @@ -1334,7 +1335,7 @@ tag command action ~ |:highlight| :hi[ghlight] specify highlighting methods |:hide| :hid[e] hide current buffer for a command |:history| :his[tory] print a history list -|:horizontal| :hor[izontal] following window command work horizontally +|:horizontal| :ho[rizontal] following window command work horizontally |:insert| :i[nsert] insert text |:iabbrev| :ia[bbrev] like ":abbrev" but for Insert mode |:iabclear| :iabc[lear] like ":abclear" but for Insert mode @@ -1372,7 +1373,7 @@ tag command action ~ |:last| :la[st] go to the last file in the argument list |:language| :lan[guage] set the language (locale) |:later| :lat[er] go to newer change, redo -|:lbefore| :lbef[ore] go to location before current cursor +|:lbefore| :lbe[fore] go to location before current cursor |:lbelow| :lbel[ow] go to location below current line |:lbottom| :lbo[ttom] scroll to the bottom of the location window |:lbuffer| :lb[uffer] parse locations and jump to first location @@ -1410,7 +1411,7 @@ tag command action ~ |:lockmarks| :loc[kmarks] following command keeps marks where they are |:lockvar| :lockv[ar] lock variables |:lolder| :lol[der] go to older location list -|:lopen| :lope[n] open location window +|:lopen| :lop[en] open location window |:lprevious| :lp[revious] go to previous location |:lpfile| :lpf[ile] go to last location in previous file |:lrewind| :lr[ewind] go to the specified location, default first one @@ -1618,7 +1619,7 @@ tag command action ~ |:tNext| :tN[ext] jump to previous matching tag |:tabNext| :tabN[ext] go to previous tab page |:tabclose| :tabc[lose] close current tab page -|:tabdo| :tabdo execute command in each tab page +|:tabdo| :tabd[o] execute command in each tab page |:tabedit| :tabe[dit] edit a file in a new tab page |:tabfind| :tabf[ind] find file in 'path', edit it in a new tab page |:tabfirst| :tabfir[st] go to first tab page @@ -1687,7 +1688,7 @@ tag command action ~ |:vsplit| :vs[plit] split current window vertically |:vunmap| :vu[nmap] like ":unmap" but for Visual+Select mode |:vunmenu| :vunme[nu] remove menu for Visual+Select mode -|:windo| :windo execute command in each window +|:windo| :wind[o] execute command in each window |:write| :w[rite] write to a file |:wNext| :wN[ext] write to a file and go to previous file in argument list diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index d3482ff3ec..08dcf691c0 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -253,8 +253,8 @@ and 'winminwidth' are relevant. will be equalized only vertically. Doesn't work for |:execute| and |:normal|. - *:hor* *:horizontal* -:hor[izontal] {cmd} + *:ho* *:horizontal* +:ho[rizontal] {cmd} Execute {cmd}. Currently only makes a difference for the following commands: - `:wincmd =`: equalize windows only horizontally. @@ -941,8 +941,8 @@ set in the preview window to be able to recognize it. The 'winfixheight' option is set to have it keep the same height when opening/closing other windows. - *:pta* *:ptag* -:pta[g][!] [tagname] + *:pt* *:ptag* +:pt[ag][!] [tagname] Does ":tag[!] [tagname]" and shows the found tag in a "Preview" window without changing the current buffer or cursor position. If a "Preview" window already exists, it is re-used -- cgit From e8c0b87c1f00dafe8bc197a474b4f9d5fe87a3a1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 6 Jan 2025 09:54:01 +0800 Subject: vim-patch:fd77161: runtime(doc): update doc for :horizontal Revert the documentation for :horizontal from commit 0c3e57b403e0e3a1fefc because :horizontal cannot be shortened to :ho closes: vim/vim#16362 https://github.com/vim/vim/commit/fd771613b3e59923b1a82a5ed9036c82899d133b Co-authored-by: h-east --- runtime/doc/index.txt | 2 +- runtime/doc/windows.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 51d1484c57..0256707420 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1335,7 +1335,7 @@ tag command action ~ |:highlight| :hi[ghlight] specify highlighting methods |:hide| :hid[e] hide current buffer for a command |:history| :his[tory] print a history list -|:horizontal| :ho[rizontal] following window command work horizontally +|:horizontal| :hor[izontal] following window command work horizontally |:insert| :i[nsert] insert text |:iabbrev| :ia[bbrev] like ":abbrev" but for Insert mode |:iabclear| :iabc[lear] like ":abclear" but for Insert mode diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 08dcf691c0..6dd90f7e49 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -253,8 +253,8 @@ and 'winminwidth' are relevant. will be equalized only vertically. Doesn't work for |:execute| and |:normal|. - *:ho* *:horizontal* -:ho[rizontal] {cmd} + *:hor* *:horizontal* +:hor[izontal] {cmd} Execute {cmd}. Currently only makes a difference for the following commands: - `:wincmd =`: equalize windows only horizontally. -- cgit From ef77845b977f7a36d50091a92b75b5b4e8421602 Mon Sep 17 00:00:00 2001 From: vanaigr Date: Wed, 18 Dec 2024 12:41:07 -0600 Subject: test: benchmark treesitter highlighing --- test/benchmark/decor_spec.lua | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/test/benchmark/decor_spec.lua b/test/benchmark/decor_spec.lua index 0994023c2d..42b2d1e744 100644 --- a/test/benchmark/decor_spec.lua +++ b/test/benchmark/decor_spec.lua @@ -6,8 +6,7 @@ describe('decor perf', function() before_each(n.clear) it('can handle long lines', function() - local screen = Screen.new(100, 101) - screen:attach() + Screen.new(100, 101) local result = exec_lua [==[ local ephemeral_pattern = { @@ -99,4 +98,44 @@ describe('decor perf', function() print('\nTotal ' .. fmt(total) .. '\nDecoration provider: ' .. fmt(provider)) end) + + + it('can handle full screen of highlighting', function() + Screen.new(100, 51) + + local result = exec_lua(function() + local long_line = 'local a={' .. ('a=5,'):rep(22) .. '}' + local lines = {} + for _ = 1, 50 do + table.insert(lines, long_line) + end + vim.api.nvim_buf_set_lines(0, 0, 0, false, lines) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.treesitter.start(0, 'lua') + + local total = {} + for _ = 1, 100 do + local tic = vim.uv.hrtime() + vim.cmd 'redraw!' + local toc = vim.uv.hrtime() + table.insert(total, toc - tic) + end + + return { total } + end) + + local total = unpack(result) + table.sort(total) + + local ms = 1 / 1000000 + local res = string.format( + 'min, 25%%, median, 75%%, max:\n\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms', + total[1] * ms, + total[1 + math.floor(#total * 0.25)] * ms, + total[1 + math.floor(#total * 0.5)] * ms, + total[1 + math.floor(#total * 0.75)] * ms, + total[#total] * ms + ) + print('\nTotal ' .. res) + end) end) -- cgit From 8d2ee542a82a0d162198f27de316ddfc81e8761c Mon Sep 17 00:00:00 2001 From: vanaigr Date: Wed, 18 Dec 2024 12:23:28 -0600 Subject: perf(decor): join predicates and matches cache --- runtime/lua/vim/treesitter/query.lua | 81 ++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 2b3b9096a6..01fdb708eb 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -762,16 +762,7 @@ end ---@private ---@param match TSQueryMatch ---@param source integer|string -function Query:match_preds(match, source) - local _, pattern = match:info() - local preds = self.info.patterns[pattern] - - if not preds then - return true - end - - local captures = match:captures() - +function Query:match_preds(preds, pattern, captures, source) for _, pred in pairs(preds) do -- Here we only want to return if a predicate DOES NOT match, and -- continue on the other case. This way unknown predicates will not be considered, @@ -807,17 +798,9 @@ end ---@private ---@param match TSQueryMatch ---@return vim.treesitter.query.TSMetadata metadata -function Query:apply_directives(match, source) +function Query:apply_directives(preds, pattern, captures, source) ---@type vim.treesitter.query.TSMetadata local metadata = {} - local _, pattern = match:info() - local preds = self.info.patterns[pattern] - - if not preds then - return metadata - end - - local captures = match:captures() for _, pred in pairs(preds) do if is_directive(pred[1]) then @@ -902,8 +885,10 @@ function Query:iter_captures(node, source, start, stop) local cursor = vim._create_ts_querycursor(node, self.query, start, stop, { match_limit = 256 }) - local apply_directives = memoize(match_id_hash, self.apply_directives, false) - local match_preds = memoize(match_id_hash, self.match_preds, false) + -- For faster checks that a match is not in the cache. + local highest_cached_match_id = -1 + ---@type table + local match_cache = {} local function iter(end_line) local capture, captured_node, match = cursor:next_capture() @@ -912,16 +897,35 @@ function Query:iter_captures(node, source, start, stop) return end - if not match_preds(self, match, source) then - local match_id = match:info() - cursor:remove_match(match_id) - if end_line and captured_node:range() > end_line then - return nil, captured_node, nil, nil - end - return iter(end_line) -- tail call: try next match + local match_id, pattern = match:info() + + --- @type vim.treesitter.query.TSMetadata + local metadata + if match_id <= highest_cached_match_id then + metadata = match_cache[match_id] end - local metadata = apply_directives(self, match, source) + if not metadata then + local preds = self.info.patterns[pattern] + if preds then + local captures = match:captures() + + if not self:match_preds(preds, pattern, captures, source) then + cursor:remove_match(match_id) + if end_line and captured_node:range() > end_line then + return nil, captured_node, nil, nil + end + return iter(end_line) -- tail call: try next match + end + + metadata = self:apply_directives(preds, pattern, captures, source) + else + metadata = {} + end + + highest_cached_match_id = math.max(highest_cached_match_id, match_id) + match_cache[match_id] = metadata + end return capture, captured_node, metadata, match end @@ -985,16 +989,21 @@ function Query:iter_matches(node, source, start, stop, opts) end local match_id, pattern = match:info() + local preds = self.info.patterns[pattern] + local captures = match:captures() - if not self:match_preds(match, source) then - cursor:remove_match(match_id) - return iter() -- tail call: try next match + --- @type vim.treesitter.query.TSMetadata + local metadata + if preds then + if not self:match_preds(preds, pattern, captures, source) then + cursor:remove_match(match_id) + return iter() -- tail call: try next match + end + metadata = self:apply_directives(preds, pattern, captures, source) + else + metadata = {} end - local metadata = self:apply_directives(match, source) - - local captures = match:captures() - if opts.all == false then -- Convert the match table into the old buggy version for backward -- compatibility. This is slow, but we only do it when the caller explicitly opted into it by -- cgit From dd234135ad20119917831fd8ffcb19d8562022ca Mon Sep 17 00:00:00 2001 From: vanaigr Date: Wed, 18 Dec 2024 01:06:41 -0600 Subject: refactor: split predicates and directives --- runtime/lua/vim/treesitter/highlighter.lua | 4 +- runtime/lua/vim/treesitter/query.lua | 172 +++++++++++++++++------------ test/benchmark/decor_spec.lua | 1 - test/functional/treesitter/query_spec.lua | 4 +- 4 files changed, 108 insertions(+), 73 deletions(-) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 8ce8652f7d..96503c38ea 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -299,6 +299,8 @@ local function on_line_impl(self, buf, line, is_spell_nav) state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) end + local captures = state.highlighter_query:query().captures + while line >= state.next_row do local capture, node, metadata, match = state.iter(line) @@ -311,7 +313,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) if capture then local hl = state.highlighter_query:get_hl_from_capture(capture) - local capture_name = state.highlighter_query:query().captures[capture] + local capture_name = captures[capture] local spell, spell_pri_offset = get_spell(capture_name) diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 01fdb708eb..1fc001b39f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -7,6 +7,59 @@ local memoize = vim.func._memoize local M = {} +local function is_directive(name) + return string.sub(name, -1) == '!' +end + +---@nodoc +---@class vim.treesitter.query.ProcessedPredicate +---@field [1] string predicate name +---@field [2] boolean should match +---@field [3] (integer|string)[] the original predicate + +---@alias vim.treesitter.query.ProcessedDirective (integer|string)[] + +---@nodoc +---@class vim.treesitter.query.ProcessedPattern { +---@field predicates vim.treesitter.query.ProcessedPredicate[] +---@field directives vim.treesitter.query.ProcessedDirective[] + +--- Splits the query patterns into predicates and directives. +---@param patterns table +---@return table +local function process_patterns(patterns) + ---@type table + local processed_patterns = {} + + for k, pattern_list in pairs(patterns) do + ---@type vim.treesitter.query.ProcessedPredicate[] + local predicates = {} + ---@type vim.treesitter.query.ProcessedDirective[] + local directives = {} + + for _, pattern in ipairs(pattern_list) do + -- Note: tree-sitter strips the leading # from predicates for us. + local pred_name = pattern[1] + ---@cast pred_name string + + if is_directive(pred_name) then + table.insert(directives, pattern) + else + local should_match = true + if pred_name:match('^not%-') then + pred_name = pred_name:sub(5) + should_match = false + end + table.insert(predicates, { pred_name, should_match, pattern }) + end + end + + processed_patterns[k] = { predicates = predicates, directives = directives } + end + + return processed_patterns +end + ---@nodoc ---Parsed query, see |vim.treesitter.query.parse()| --- @@ -15,6 +68,7 @@ local M = {} ---@field captures string[] list of (unique) capture names defined in query ---@field info vim.treesitter.QueryInfo query context (e.g. captures, predicates, directives) ---@field query TSQuery userdata query object +---@field private _processed_patterns table local Query = {} Query.__index = Query @@ -33,6 +87,7 @@ function Query.new(lang, ts_query) patterns = query_info.patterns, } self.captures = self.info.captures + self._processed_patterns = process_patterns(self.info.patterns) return self end @@ -751,67 +806,50 @@ function M.list_predicates() return vim.tbl_keys(predicate_handlers) end -local function xor(x, y) - return (x or y) and not (x and y) -end - -local function is_directive(name) - return string.sub(name, -1) == '!' -end - ---@private ----@param match TSQueryMatch +---@param pattern_i integer +---@param predicates vim.treesitter.query.ProcessedPredicate[] +---@param captures table ---@param source integer|string -function Query:match_preds(preds, pattern, captures, source) - for _, pred in pairs(preds) do - -- Here we only want to return if a predicate DOES NOT match, and - -- continue on the other case. This way unknown predicates will not be considered, - -- which allows some testing and easier user extensibility (#12173). - -- Also, tree-sitter strips the leading # from predicates for us. - local is_not = false - - -- Skip over directives... they will get processed after all the predicates. - if not is_directive(pred[1]) then - local pred_name = pred[1] - if pred_name:match('^not%-') then - pred_name = pred_name:sub(5) - is_not = true - end - - local handler = predicate_handlers[pred_name] - - if not handler then - error(string.format('No handler for %s', pred[1])) - return false - end - - local pred_matches = handler(captures, pattern, source, pred) +---@return boolean whether the predicates match +function Query:_match_predicates(predicates, pattern_i, captures, source) + for _, predicate in ipairs(predicates) do + local processed_name = predicate[1] + local should_match = predicate[2] + local orig_predicate = predicate[3] + + local handler = predicate_handlers[processed_name] + if not handler then + error(string.format('No handler for %s', orig_predicate[1])) + return false + end - if not xor(is_not, pred_matches) then - return false - end + local does_match = handler(captures, pattern_i, source, orig_predicate) + if does_match ~= should_match then + return false end end return true end ---@private ----@param match TSQueryMatch +---@param pattern_i integer +---@param directives vim.treesitter.query.ProcessedDirective[] +---@param source integer|string +---@param captures table ---@return vim.treesitter.query.TSMetadata metadata -function Query:apply_directives(preds, pattern, captures, source) +function Query:_apply_directives(directives, pattern_i, captures, source) ---@type vim.treesitter.query.TSMetadata local metadata = {} - for _, pred in pairs(preds) do - if is_directive(pred[1]) then - local handler = directive_handlers[pred[1]] + for _, directive in pairs(directives) do + local handler = directive_handlers[directive[1]] - if not handler then - error(string.format('No handler for %s', pred[1])) - end - - handler(captures, pattern, source, pred, metadata) + if not handler then + error(string.format('No handler for %s', directive[1])) end + + handler(captures, pattern_i, source, directive, metadata) end return metadata @@ -835,12 +873,6 @@ local function value_or_node_range(start, stop, node) return start, stop end ---- @param match TSQueryMatch ---- @return integer -local function match_id_hash(_, match) - return (match:info()) -end - --- Iterates over all captures from all matches in {node}. --- --- {source} is required if the query contains predicates; then the caller @@ -897,7 +929,7 @@ function Query:iter_captures(node, source, start, stop) return end - local match_id, pattern = match:info() + local match_id, pattern_i = match:info() --- @type vim.treesitter.query.TSMetadata local metadata @@ -906,11 +938,14 @@ function Query:iter_captures(node, source, start, stop) end if not metadata then - local preds = self.info.patterns[pattern] - if preds then + metadata = {} + + local processed_pattern = self._processed_patterns[pattern_i] + if processed_pattern then local captures = match:captures() - if not self:match_preds(preds, pattern, captures, source) then + local predicates = processed_pattern.predicates + if not self:_match_predicates(predicates, pattern_i, captures, source) then cursor:remove_match(match_id) if end_line and captured_node:range() > end_line then return nil, captured_node, nil, nil @@ -918,9 +953,8 @@ function Query:iter_captures(node, source, start, stop) return iter(end_line) -- tail call: try next match end - metadata = self:apply_directives(preds, pattern, captures, source) - else - metadata = {} + local directives = processed_pattern.directives + metadata = self:_apply_directives(directives, pattern_i, captures, source) end highest_cached_match_id = math.max(highest_cached_match_id, match_id) @@ -988,20 +1022,20 @@ function Query:iter_matches(node, source, start, stop, opts) return end - local match_id, pattern = match:info() - local preds = self.info.patterns[pattern] + local match_id, pattern_i = match:info() + local processed_pattern = self._processed_patterns[pattern_i] local captures = match:captures() --- @type vim.treesitter.query.TSMetadata - local metadata - if preds then - if not self:match_preds(preds, pattern, captures, source) then + local metadata = {} + if processed_pattern then + local predicates = processed_pattern.predicates + if not self:_match_predicates(predicates, pattern_i, captures, source) then cursor:remove_match(match_id) return iter() -- tail call: try next match end - metadata = self:apply_directives(preds, pattern, captures, source) - else - metadata = {} + local directives = processed_pattern.directives + metadata = self:_apply_directives(directives, pattern_i, captures, source) end if opts.all == false then @@ -1012,11 +1046,11 @@ function Query:iter_matches(node, source, start, stop, opts) for k, v in pairs(captures or {}) do old_match[k] = v[#v] end - return pattern, old_match, metadata + return pattern_i, old_match, metadata end -- TODO(lewis6991): create a new function that returns {match, metadata} - return pattern, captures, metadata + return pattern_i, captures, metadata end return iter end diff --git a/test/benchmark/decor_spec.lua b/test/benchmark/decor_spec.lua index 42b2d1e744..1b7e763a09 100644 --- a/test/benchmark/decor_spec.lua +++ b/test/benchmark/decor_spec.lua @@ -99,7 +99,6 @@ describe('decor perf', function() print('\nTotal ' .. fmt(total) .. '\nDecoration provider: ' .. fmt(provider)) end) - it('can handle full screen of highlighting', function() Screen.new(100, 51) diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 634f8af83d..6e21ed1d99 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -835,9 +835,9 @@ void ui_refresh(void) local result = exec_lua(function() local query0 = vim.treesitter.query.parse('c', query) - local match_preds = query0.match_preds + local match_preds = query0._match_predicates local called = 0 - function query0:match_preds(...) + function query0:_match_predicates(...) called = called + 1 return match_preds(self, ...) end -- cgit From 86770108e2c6e08c2b8b95f1611923ba99b854dd Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 6 Jan 2025 15:05:50 +0100 Subject: fix(lsp): open_floating_preview() zindex relative to current window #31886 Problem: open_floating_preview() may be hidden behind current window if that is floating and has a higher zindex. Solution: Open floating preview with zindex higher than current window. --- runtime/lua/vim/lsp/util.lua | 2 +- test/functional/plugin/lsp/utils_spec.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 6bee5bc31f..89b774816b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -883,7 +883,7 @@ function M.make_floating_popup_options(width, height, opts) style = 'minimal', width = width, border = opts.border or default_border, - zindex = opts.zindex or 50, + zindex = opts.zindex or (api.nvim_win_get_config(0).zindex or 49) + 1, title = title, title_pos = title_pos, } diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index ce6e6b2535..1e3e759e0b 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -301,4 +301,32 @@ describe('vim.lsp.util', function() end) end) end) + + it('open_floating_preview zindex greater than current window', function() + local screen = Screen.new() + exec_lua(function() + vim.api.nvim_open_win(0, true, { + relative = 'editor', + border = 'single', + height = 11, + width = 51, + row = 2, + col = 2, + }) + vim.keymap.set('n', 'K', function() + vim.lsp.util.open_floating_preview({ 'foo' }, '', { border = 'single' }) + end, {}) + end) + feed('K') + screen:expect([[ + ┌───────────────────────────────────────────────────┐| + │{4:^ }│| + │┌───┐{11: }│| + ││{4:foo}│{11: }│| + │└───┘{11: }│| + │{11:~ }│|*7 + └───────────────────────────────────────────────────┘| + | + ]]) + end) end) -- cgit From b6ab294838421afb6932c52dd6e6d35d571e621d Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 6 Jan 2025 20:29:55 +0100 Subject: fix: fix incorrect search code --- src/nvim/ex_getln.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index b26a2ef1bc..0b5d0864e5 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -400,7 +400,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s parse_cmd_address(&ea, &dummy, true); if (ea.addr_count > 0) { // Allow for reverse match. - search_first_line = MIN(ea.line1, ea.line1); + search_first_line = MIN(ea.line2, ea.line1); search_last_line = MAX(ea.line2, ea.line1); } else if (cmd[0] == 's' && cmd[1] != 'o') { // :s defaults to the current line -- cgit From 30de00687b899824bb319dfb3f7989ea3f936617 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Mon, 6 Jan 2025 16:56:53 -0800 Subject: refactor(treesitter): simplify condition #31889 --- runtime/lua/vim/treesitter/languagetree.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4b42164dc8..330eb45749 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -443,7 +443,7 @@ function LanguageTree:parse(range) end end - if not self._injections_processed and range ~= false and range ~= nil then + if not self._injections_processed and range then query_time = self:_add_injections() self._injections_processed = true end -- cgit From 06ff5480ce274daf3b7ad9950a587099200dc8ff Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 7 Jan 2025 02:00:09 +0100 Subject: vim-patch:9.1.0993: New 'cmdheight' behavior may be surprising #31892 Problem: Although patch 9.1.0990 fixed a real problem/inconsistency, it also introduced new behavior that may break BWC and/or be unexpected. Before 9.1.0990, window commands could make the topframe smaller (without changing 'cmdheight'; quirk that is now fixed), but did not allow extending the topframe beyond the 'cmdheight' set by the user. After 9.1.0990, the user can reduce the 'cmdheight' below the value they set explicitly, through window commands, which may lead to confusion. (aftere v9.1.0990) Solution: Store the value explicitly set by the user and clamp the 'cmdheight' when resizing the topframe. This also applies to dragging laststatus, which in contrast to window commands _did_ allow reducing the 'cmdheight' to values below the one set by the user. So with this patch there is still new behavior, but I think in a way that is less surprising. While at it, also fix a Coverity warning, introduced in v9.1.0990 (Luuk van Baal) https://github.com/vim/vim/commit/c97e8695353565d6b20adffa48aad47f6e09967f --- src/nvim/option.c | 2 -- src/nvim/window.c | 15 ++++++++++----- src/nvim/window.h | 3 --- test/functional/legacy/cmdline_spec.lua | 16 ++++++++++++---- test/old/testdir/test_cmdline.vim | 11 +++++++---- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 824c9988f4..551ea0be20 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2872,8 +2872,6 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e case kOptCmdheight: if (value < 0) { return e_positive; - } else { - p_ch_was_zero = value == 0; } break; case kOptHistory: diff --git a/src/nvim/window.c b/src/nvim/window.c index d459a046ca..8c8df72590 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3500,6 +3500,10 @@ static bool is_bottom_win(win_T *wp) return true; } +// 'cmdheight' value explicitly set by the user: window commands are allowed to +// resize the topframe to values higher than this minimum, but not lower. +static OptInt min_set_ch = 1; + /// Set a new height for a frame. Recursively sets the height for contained /// frames and windows. Caller must take care of positions. /// @@ -3512,11 +3516,11 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh, bool { if (topfrp->fr_parent == NULL && set_ch) { // topframe: update the command line height, with side effects. - OptInt new_ch = MAX(!p_ch_was_zero, p_ch + topfrp->fr_height - height); + OptInt new_ch = MAX(min_set_ch, p_ch + topfrp->fr_height - height); if (new_ch != p_ch) { - const bool was_zero = p_ch_was_zero; + const OptInt save_ch = min_set_ch; set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0); - p_ch_was_zero = was_zero; + min_set_ch = save_ch; } height = (int)MIN(ROWS_AVAIL, height); } @@ -6247,7 +6251,7 @@ void win_drag_status_line(win_T *dragwin, int offset) room = Rows - cmdline_row; if (curfr->fr_next != NULL) { room -= (int)p_ch + global_stl_height(); - } else if (!p_ch_was_zero) { + } else if (min_set_ch > 0) { room--; } room = MAX(room, 0); @@ -6750,7 +6754,7 @@ void command_height(void) old_p_ch += h; frp = frp->fr_prev; } - if (p_ch < old_p_ch && command_frame_height) { + if (p_ch < old_p_ch && command_frame_height && frp != NULL) { frame_add_height(frp, (int)(old_p_ch - p_ch)); } @@ -6774,6 +6778,7 @@ void command_height(void) // GUI starts up, we can't be sure in what order things happen. And when // p_ch was changed in another tab page. curtab->tp_ch_used = p_ch; + min_set_ch = p_ch; } // Resize frame "frp" to be "n" lines higher (negative for less high). diff --git a/src/nvim/window.h b/src/nvim/window.h index 9618ff1c2a..b5808d3451 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -35,9 +35,6 @@ enum { EXTERN int tabpage_move_disallowed INIT( = 0); ///< moving tabpages around disallowed -/// Set to true if 'cmdheight' was explicitly set to 0. -EXTERN bool p_ch_was_zero INIT( = false); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "window.h.generated.h" #endif diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index 22700990c1..819b40323a 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -175,19 +175,17 @@ describe('cmdline', function() {3:[No Name] }| |*5 ]]) - feed(':set cmdheight-=1') -- using more space moves the status line up feed(':set cmdheight+=1') screen:expect([[ ^ | - {1:~ }| {3:[No Name] }| - |*5 + |*6 ]]) -- reducing cmdheight moves status line down - feed(':set cmdheight-=2') + feed(':set cmdheight-=3') screen:expect([[ ^ | {1:~ }|*3 @@ -231,6 +229,16 @@ describe('cmdline', function() bar | {6:Press ENTER or type command to continue}^ | ]]) + + -- window commands do not reduce 'cmdheight' to value lower than :set by user + feed(':wincmd _') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + :wincmd _ | + | + ]]) end) -- oldtest: Test_cmdheight_tabline() diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 3fce086040..d4ad63d43e 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -294,17 +294,16 @@ func Test_changing_cmdheight() " :resize now also changes 'cmdheight' accordingly call term_sendkeys(buf, ":set cmdheight+=1\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {}) - call term_sendkeys(buf, ":set cmdheight-=1\") " using more space moves the status line up call term_sendkeys(buf, ":set cmdheight+=1\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {}) " reducing cmdheight moves status line down - call term_sendkeys(buf, ":set cmdheight-=2\") + call term_sendkeys(buf, ":set cmdheight-=3\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {}) - " reducing window size and then setting cmdheight + " reducing window size and then setting cmdheight call term_sendkeys(buf, ":resize -1\") call term_sendkeys(buf, ":set cmdheight=1\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {}) @@ -313,10 +312,14 @@ func Test_changing_cmdheight() call term_sendkeys(buf, ":call EchoTwo()\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_6', {}) - " decreasing 'cmdheight' doesn't clear the messages that need hit-enter + " increasing 'cmdheight' doesn't clear the messages that need hit-enter call term_sendkeys(buf, ":call EchoOne()\") call VerifyScreenDump(buf, 'Test_changing_cmdheight_7', {}) + " window commands do not reduce 'cmdheight' to value lower than :set by user + call term_sendkeys(buf, "\:wincmd _\") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_8', {}) + " clean up call StopVimInTerminal(buf) endfunc -- cgit From d5308637bf1aac2b97fccf73a0ffdef304eaa1d6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 3 Jan 2025 20:12:15 +0800 Subject: vim-patch:9.1.0984: exception handling can be improved Problem: exception handling can be improved Solution: add v:stacktrace and getstacktrace() closes: vim/vim#16360 https://github.com/vim/vim/commit/663d18d6102f40d14e36096ec590445e61026ed6 Co-authored-by: ichizok Co-authored-by: Naruhiko Nishino --- runtime/doc/builtin.txt | 15 +++++ runtime/doc/eval.txt | 3 +- runtime/doc/usr_41.txt | 3 +- runtime/doc/vvars.txt | 15 ++++- runtime/lua/vim/_meta/vimfn.lua | 14 +++++ runtime/lua/vim/_meta/vvars.lua | 12 +++- src/nvim/eval.c | 1 + src/nvim/eval.h | 1 + src/nvim/eval.lua | 19 +++++++ src/nvim/eval/typval.c | 24 ++++++++ src/nvim/ex_eval.c | 7 +++ src/nvim/ex_eval_defs.h | 2 + src/nvim/runtime.c | 66 +++++++++++++++++++++ src/nvim/vvars.lua | 14 ++++- test/old/testdir/test_stacktrace.vim | 107 +++++++++++++++++++++++++++++++++++ 15 files changed, 294 insertions(+), 9 deletions(-) create mode 100644 test/old/testdir/test_stacktrace.vim diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index f321c880a4..70c7a7b802 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4182,6 +4182,21 @@ getscriptinfo([{opts}]) *getscriptinfo()* Return: ~ (`vim.fn.getscriptinfo.ret[]`) +getstacktrace() *getstacktrace()* + Returns the current stack trace of Vim scripts. + Stack trace is a |List|, of which each item is a |Dictionary| + with the following items: + funcref The funcref if the stack is at the function, + otherwise this item is not exist. + event The string of the event description if the + stack is at autocmd event, otherwise this item + is not exist. + lnum The line number of the script on the stack. + filepath The file path of the script on the stack. + + Return: ~ + (`table[]`) + gettabinfo([{tabnr}]) *gettabinfo()* If {tabnr} is not specified, then information about all the tab pages is returned as a |List|. Each List item is a diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index e0c45503cc..60238bc90d 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2848,7 +2848,8 @@ in the variable |v:exception|: > : echo "Number thrown. Value is" v:exception You may also be interested where an exception was thrown. This is stored in -|v:throwpoint|. Note that "v:exception" and "v:throwpoint" are valid for the +|v:throwpoint|. And you can obtain the stack trace from |v:stacktrace|. +Note that "v:exception", "v:stacktrace" and "v:throwpoint" are valid for the exception most recently caught as long it is not finished. Example: > diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 3202a70b76..f958491ccf 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1103,7 +1103,8 @@ Various: *various-functions* did_filetype() check if a FileType autocommand was used eventhandler() check if invoked by an event handler getpid() get process ID of Vim - getscriptinfo() get list of sourced vim scripts + getscriptinfo() get list of sourced Vim scripts + getstacktrace() get current stack trace of Vim scripts libcall() call a function in an external library libcallnr() idem, returning a number diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 32f3b96269..0ebb54e38a 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -6,7 +6,8 @@ Predefined variables *vvars* -Some variables can be set by the user, but the type cannot be changed. +Most variables are read-only, when a variable can be set by the user, it will +be mentioned at the variable description below. The type cannot be changed. Type |gO| to see the table of contents. @@ -195,7 +196,8 @@ v:event *v:exception* *exception-variable* v:exception The value of the exception most recently caught and not - finished. See also |v:throwpoint| and |throw-variables|. + finished. See also |v:stacktrace|, |v:throwpoint|, and + |throw-variables|. Example: >vim try throw "oops" @@ -586,6 +588,13 @@ v:shell_error endif < + *v:stacktrace* *stacktrace-variable* +v:stacktrace + The stack trace of the exception most recently caught and + not finished. Refer to |getstacktrace()| for the structure of + stack trace. See also |v:exception|, |v:throwpoint|, and + |throw-variables|. + *v:statusmsg* *statusmsg-variable* v:statusmsg Last given status message. @@ -679,7 +688,7 @@ v:this_session v:throwpoint The point where the exception most recently caught and not finished was thrown. Not set when commands are typed. See - also |v:exception| and |throw-variables|. + also |v:exception|, |v:stacktrace|, and |throw-variables|. Example: >vim try throw "oops" diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 6662fca84f..3de8b9951c 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3770,6 +3770,20 @@ function vim.fn.getregtype(regname) end --- @return vim.fn.getscriptinfo.ret[] function vim.fn.getscriptinfo(opts) end +--- Returns the current stack trace of Vim scripts. +--- Stack trace is a |List|, of which each item is a |Dictionary| +--- with the following items: +--- funcref The funcref if the stack is at the function, +--- otherwise this item is not exist. +--- event The string of the event description if the +--- stack is at autocmd event, otherwise this item +--- is not exist. +--- lnum The line number of the script on the stack. +--- filepath The file path of the script on the stack. +--- +--- @return table[] +function vim.fn.getstacktrace() end + --- If {tabnr} is not specified, then information about all the --- tab pages is returned as a |List|. Each List item is a --- |Dictionary|. Otherwise, {tabnr} specifies the tab page diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index 445da4e02f..c1b8695bbf 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -203,7 +203,8 @@ vim.v.errors = ... vim.v.event = ... --- The value of the exception most recently caught and not ---- finished. See also `v:throwpoint` and `throw-variables`. +--- finished. See also `v:stacktrace`, `v:throwpoint`, and +--- `throw-variables`. --- Example: --- --- ```vim @@ -616,6 +617,13 @@ vim.v.servername = ... --- @type integer vim.v.shell_error = ... +--- The stack trace of the exception most recently caught and +--- not finished. Refer to `getstacktrace()` for the structure of +--- stack trace. See also `v:exception`, `v:throwpoint`, and +--- `throw-variables`. +--- @type table[] +vim.v.stacktrace = ... + --- Last given status message. --- Modifiable (can be set). --- @type string @@ -718,7 +726,7 @@ vim.v.this_session = ... --- The point where the exception most recently caught and not --- finished was thrown. Not set when commands are typed. See ---- also `v:exception` and `throw-variables`. +--- also `v:exception`, `v:stacktrace`, and `throw-variables`. --- Example: --- --- ```vim diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8bdd8dad4c..a90f275713 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -270,6 +270,7 @@ static struct vimvar { VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO), + VV(VV_STACKTRACE, "stacktrace", VAR_LIST, VV_RO), // Neovim VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), diff --git a/src/nvim/eval.h b/src/nvim/eval.h index bb9b00abc7..8b4aa8101a 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -167,6 +167,7 @@ typedef enum { VV_COLLATE, VV_EXITING, VV_MAXCOL, + VV_STACKTRACE, // Nvim VV_STDERR, VV_MSGPACK_TYPES, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index c650dee306..5901ed5766 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4670,6 +4670,25 @@ M.funcs = { returns = 'vim.fn.getscriptinfo.ret[]', signature = 'getscriptinfo([{opts}])', }, + getstacktrace = { + args = 0, + desc = [=[ + Returns the current stack trace of Vim scripts. + Stack trace is a |List|, of which each item is a |Dictionary| + with the following items: + funcref The funcref if the stack is at the function, + otherwise this item is not exist. + event The string of the event description if the + stack is at autocmd event, otherwise this item + is not exist. + lnum The line number of the script on the stack. + filepath The file path of the script on the stack. + ]=], + name = 'getstacktrace', + params = {}, + returns = 'table[]', + signature = 'getstacktrace()', + }, gettabinfo = { args = { 0, 1 }, base = 1, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index cbb6b5644f..ed1031577c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2633,6 +2633,30 @@ int tv_dict_add_allocated_str(dict_T *const d, const char *const key, const size return OK; } +/// Add a function entry to dictionary. +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] fp Function to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_len, + ufunc_T *const fp) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_type = VAR_FUNC; + item->di_tv.vval.v_string = xstrdup(fp->uf_name); + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + func_ref(item->di_tv.vval.v_string); + return OK; +} + //{{{2 Operations on the whole dict /// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f9936dd88e..18c691d076 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -479,6 +479,9 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) excp->throw_lnum = SOURCING_LNUM; } + excp->stacktrace = stacktrace_create(); + tv_list_ref(excp->stacktrace); + if (p_verbose >= 13 || debug_break_level > 0) { int save_msg_silent = msg_silent; @@ -563,6 +566,7 @@ static void discard_exception(except_T *excp, bool was_finished) free_msglist(excp->messages); } xfree(excp->throw_name); + tv_list_unref(excp->stacktrace); xfree(excp); } @@ -584,6 +588,7 @@ static void catch_exception(except_T *excp) excp->caught = caught_stack; caught_stack = excp; set_vim_var_string(VV_EXCEPTION, excp->value, -1); + set_vim_var_list(VV_STACKTRACE, excp->stacktrace); if (*excp->throw_name != NUL) { if (excp->throw_lnum != 0) { vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64), @@ -633,6 +638,7 @@ static void finish_exception(except_T *excp) caught_stack = caught_stack->caught; if (caught_stack != NULL) { set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); + set_vim_var_list(VV_STACKTRACE, caught_stack->stacktrace); if (*caught_stack->throw_name != NUL) { if (caught_stack->throw_lnum != 0) { vim_snprintf(IObuff, IOSIZE, @@ -651,6 +657,7 @@ static void finish_exception(except_T *excp) } else { set_vim_var_string(VV_EXCEPTION, NULL, -1); set_vim_var_string(VV_THROWPOINT, NULL, -1); + set_vim_var_list(VV_STACKTRACE, NULL); } // Discard the exception, but use the finish message for 'verbose'. diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h index 3f5e510a20..e0d06f3e93 100644 --- a/src/nvim/ex_eval_defs.h +++ b/src/nvim/ex_eval_defs.h @@ -2,6 +2,7 @@ #include +#include "nvim/eval/typval_defs.h" #include "nvim/pos_defs.h" /// A list used for saving values of "emsg_silent". Used by ex_try() to save the @@ -107,6 +108,7 @@ struct vim_exception { msglist_T *messages; ///< message(s) causing error exception char *throw_name; ///< name of the throw point linenr_T throw_lnum; ///< line number of the throw point + list_T *stacktrace; ///< stacktrace except_T *caught; ///< next exception on the caught stack }; diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index d849a18879..cdedf86977 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -228,6 +228,72 @@ char *estack_sfile(estack_arg_T which) return (char *)ga.ga_data; } +static void stacktrace_push_item(list_T *const l, ufunc_T *const fp, const char *const event, + const linenr_T lnum, char *const filepath, + const bool filepath_alloced) +{ + dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); + typval_T tv = { + .v_type = VAR_DICT, + .v_lock = VAR_LOCKED, + .vval.v_dict = d, + }; + + if (fp != NULL) { + tv_dict_add_func(d, S_LEN("funcref"), fp); + } + if (event != NULL) { + tv_dict_add_str(d, S_LEN("event"), event); + } + tv_dict_add_nr(d, S_LEN("lnum"), lnum); + if (filepath_alloced) { + tv_dict_add_allocated_str(d, S_LEN("filepath"), filepath); + } else { + tv_dict_add_str(d, S_LEN("filepath"), filepath); + } + + tv_list_append_tv(l, &tv); +} + +/// Create the stacktrace from exestack. +list_T *stacktrace_create(void) +{ + list_T *const l = tv_list_alloc(exestack.ga_len); + + for (int i = 0; i < exestack.ga_len; i++) { + estack_T *const entry = &((estack_T *)exestack.ga_data)[i]; + linenr_T lnum = entry->es_lnum; + + if (entry->es_type == ETYPE_SCRIPT) { + stacktrace_push_item(l, NULL, NULL, lnum, entry->es_name, false); + } else if (entry->es_type == ETYPE_UFUNC) { + ufunc_T *const fp = entry->es_info.ufunc; + const sctx_T sctx = fp->uf_script_ctx; + bool filepath_alloced = false; + char *filepath = sctx.sc_sid > 0 + ? get_scriptname((LastSet){ .script_ctx = sctx }, + &filepath_alloced) : ""; + lnum += sctx.sc_lnum; + stacktrace_push_item(l, fp, NULL, lnum, filepath, filepath_alloced); + } else if (entry->es_type == ETYPE_AUCMD) { + const sctx_T sctx = entry->es_info.aucmd->script_ctx; + bool filepath_alloced = false; + char *filepath = sctx.sc_sid > 0 + ? get_scriptname((LastSet){ .script_ctx = sctx }, + &filepath_alloced) : ""; + lnum += sctx.sc_lnum; + stacktrace_push_item(l, NULL, entry->es_name, lnum, filepath, filepath_alloced); + } + } + return l; +} + +/// getstacktrace() function +void f_getstacktrace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_set_ret(rettv, stacktrace_create()); +} + static bool runtime_search_path_valid = false; static int *runtime_search_path_ref = NULL; static RuntimeSearchPath runtime_search_path; diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index e705c02e83..056e281c0b 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -220,7 +220,8 @@ M.vars = { type = 'string', desc = [=[ The value of the exception most recently caught and not - finished. See also |v:throwpoint| and |throw-variables|. + finished. See also |v:stacktrace|, |v:throwpoint|, and + |throw-variables|. Example: >vim try throw "oops" @@ -701,6 +702,15 @@ M.vars = { < ]=], }, + stacktrace = { + type = 'table[]', + desc = [=[ + The stack trace of the exception most recently caught and + not finished. Refer to |getstacktrace()| for the structure of + stack trace. See also |v:exception|, |v:throwpoint|, and + |throw-variables|. + ]=], + }, statusmsg = { type = 'string', desc = [=[ @@ -823,7 +833,7 @@ M.vars = { desc = [=[ The point where the exception most recently caught and not finished was thrown. Not set when commands are typed. See - also |v:exception| and |throw-variables|. + also |v:exception|, |v:stacktrace|, and |throw-variables|. Example: >vim try throw "oops" diff --git a/test/old/testdir/test_stacktrace.vim b/test/old/testdir/test_stacktrace.vim new file mode 100644 index 0000000000..2ff5801ce6 --- /dev/null +++ b/test/old/testdir/test_stacktrace.vim @@ -0,0 +1,107 @@ +" Test for getstacktrace() and v:stacktrace + +let s:thisfile = expand('%:p') +let s:testdir = s:thisfile->fnamemodify(':h') + +func Filepath(name) + return s:testdir .. '/' .. a:name +endfunc + +func AssertStacktrace(expect, actual) + call assert_equal(#{lnum: 581, filepath: Filepath('runtest.vim')}, a:actual[0]) + call assert_equal(a:expect, a:actual[-len(a:expect):]) +endfunc + +func Test_getstacktrace() + let g:stacktrace = [] + let lines1 =<< trim [SCRIPT] + " Xscript1 + source Xscript2 + func Xfunc1() + " Xfunc1 + call Xfunc2() + endfunc + [SCRIPT] + let lines2 =<< trim [SCRIPT] + " Xscript2 + func Xfunc2() + " Xfunc2 + let g:stacktrace = getstacktrace() + endfunc + [SCRIPT] + call writefile(lines1, 'Xscript1', 'D') + call writefile(lines2, 'Xscript2', 'D') + source Xscript1 + call Xfunc1() + call AssertStacktrace([ + \ #{funcref: funcref('Test_getstacktrace'), lnum: 35, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], g:stacktrace) + unlet g:stacktrace +endfunc + +func Test_getstacktrace_event() + let g:stacktrace = [] + let lines1 =<< trim [SCRIPT] + " Xscript1 + func Xfunc() + " Xfunc + let g:stacktrace = getstacktrace() + endfunc + augroup test_stacktrace + autocmd SourcePre * call Xfunc() + augroup END + [SCRIPT] + let lines2 =<< trim [SCRIPT] + " Xscript2 + [SCRIPT] + call writefile(lines1, 'Xscript1', 'D') + call writefile(lines2, 'Xscript2', 'D') + source Xscript1 + source Xscript2 + call AssertStacktrace([ + \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 62, filepath: s:thisfile}, + \ #{event: 'SourcePre Autocommands for "*"', lnum: 7, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc'), lnum: 4, filepath: Filepath('Xscript1')}, + \ ], g:stacktrace) + augroup test_stacktrace + autocmd! + augroup END + unlet g:stacktrace +endfunc + +func Test_vstacktrace() + let lines1 =<< trim [SCRIPT] + " Xscript1 + source Xscript2 + func Xfunc1() + " Xfunc1 + call Xfunc2() + endfunc + [SCRIPT] + let lines2 =<< trim [SCRIPT] + " Xscript2 + func Xfunc2() + " Xfunc2 + throw 'Exception from Xfunc2' + endfunc + [SCRIPT] + call writefile(lines1, 'Xscript1', 'D') + call writefile(lines2, 'Xscript2', 'D') + source Xscript1 + call assert_equal([], v:stacktrace) + try + call Xfunc1() + catch + let stacktrace = v:stacktrace + endtry + call assert_equal([], v:stacktrace) + call AssertStacktrace([ + \ #{funcref: funcref('Test_vstacktrace'), lnum: 95, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], stacktrace) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From 8fa4306eb910028ee8df8685ae9b1649608c2608 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 7 Jan 2025 09:05:52 +0800 Subject: vim-patch:9.1.0991: v:stacktrace has wrong type in Vim9 script Problem: v:stacktrace has wrong type in Vim9 script. Solution: Change the type to t_list_dict_any. Fix grammar in docs. (zeertzjq) closes: vim/vim#16390 https://github.com/vim/vim/commit/6655bef33047b826e0ccb8c686f3f57e47161b1c --- runtime/doc/builtin.txt | 10 +++++----- runtime/lua/vim/_meta/vimfn.lua | 10 +++++----- src/nvim/eval.lua | 10 +++++----- test/old/testdir/test_stacktrace.vim | 31 ++++++++++++++++++++++++++++--- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 70c7a7b802..f466dde861 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4186,12 +4186,12 @@ getstacktrace() *getstacktrace()* Returns the current stack trace of Vim scripts. Stack trace is a |List|, of which each item is a |Dictionary| with the following items: - funcref The funcref if the stack is at the function, - otherwise this item is not exist. + funcref The funcref if the stack is at a function, + otherwise this item is omitted. event The string of the event description if the - stack is at autocmd event, otherwise this item - is not exist. - lnum The line number of the script on the stack. + stack is at an autocmd event, otherwise this + item is omitted. + lnum The line number in the script on the stack. filepath The file path of the script on the stack. Return: ~ diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 3de8b9951c..031b109b38 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3773,12 +3773,12 @@ function vim.fn.getscriptinfo(opts) end --- Returns the current stack trace of Vim scripts. --- Stack trace is a |List|, of which each item is a |Dictionary| --- with the following items: ---- funcref The funcref if the stack is at the function, ---- otherwise this item is not exist. +--- funcref The funcref if the stack is at a function, +--- otherwise this item is omitted. --- event The string of the event description if the ---- stack is at autocmd event, otherwise this item ---- is not exist. ---- lnum The line number of the script on the stack. +--- stack is at an autocmd event, otherwise this +--- item is omitted. +--- lnum The line number in the script on the stack. --- filepath The file path of the script on the stack. --- --- @return table[] diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 5901ed5766..4ce1960dcf 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4676,12 +4676,12 @@ M.funcs = { Returns the current stack trace of Vim scripts. Stack trace is a |List|, of which each item is a |Dictionary| with the following items: - funcref The funcref if the stack is at the function, - otherwise this item is not exist. + funcref The funcref if the stack is at a function, + otherwise this item is omitted. event The string of the event description if the - stack is at autocmd event, otherwise this item - is not exist. - lnum The line number of the script on the stack. + stack is at an autocmd event, otherwise this + item is omitted. + lnum The line number in the script on the stack. filepath The file path of the script on the stack. ]=], name = 'getstacktrace', diff --git a/test/old/testdir/test_stacktrace.vim b/test/old/testdir/test_stacktrace.vim index 2ff5801ce6..1e47deefdd 100644 --- a/test/old/testdir/test_stacktrace.vim +++ b/test/old/testdir/test_stacktrace.vim @@ -1,5 +1,7 @@ " Test for getstacktrace() and v:stacktrace +source vim9.vim + let s:thisfile = expand('%:p') let s:testdir = s:thisfile->fnamemodify(':h') @@ -34,7 +36,7 @@ func Test_getstacktrace() source Xscript1 call Xfunc1() call AssertStacktrace([ - \ #{funcref: funcref('Test_getstacktrace'), lnum: 35, filepath: s:thisfile}, + \ #{funcref: funcref('Test_getstacktrace'), lnum: 37, filepath: s:thisfile}, \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], g:stacktrace) @@ -61,7 +63,7 @@ func Test_getstacktrace_event() source Xscript1 source Xscript2 call AssertStacktrace([ - \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 62, filepath: s:thisfile}, + \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 64, filepath: s:thisfile}, \ #{event: 'SourcePre Autocommands for "*"', lnum: 7, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc'), lnum: 4, filepath: Filepath('Xscript1')}, \ ], g:stacktrace) @@ -98,10 +100,33 @@ func Test_vstacktrace() endtry call assert_equal([], v:stacktrace) call AssertStacktrace([ - \ #{funcref: funcref('Test_vstacktrace'), lnum: 95, filepath: s:thisfile}, + \ #{funcref: funcref('Test_vstacktrace'), lnum: 97, filepath: s:thisfile}, \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], stacktrace) endfunc +func Test_zzz_stacktrace_vim9() + let lines =<< trim [SCRIPT] + var stacktrace = getstacktrace() + assert_notequal([], stacktrace) + for d in stacktrace + assert_true(has_key(d, 'lnum')) + endfor + try + throw 'Exception from s:Func' + catch + assert_notequal([], v:stacktrace) + assert_equal(len(stacktrace), len(v:stacktrace)) + for d in v:stacktrace + assert_true(has_key(d, 'lnum')) + endfor + endtry + [SCRIPT] + call CheckDefSuccess(lines) + " FIXME: v:stacktrace is not cleared after the exception handling, and this + " test has to be run as the last one because of this. + " call assert_equal([], v:stacktrace) +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From a942dea157aa6a89f1d2180d3386a65b3d320be2 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 6 Jan 2025 18:16:06 +0100 Subject: vim-patch:7ceaa8f: runtime(vim): Remove trailing comma from match_words fixes: vim/vim#16377 (`filetype plugin indent on` breaks matchit). closes: vim/vim#16389 https://github.com/vim/vim/commit/7ceaa8f3ddbaad75fa02f91c0b354661b38253cb Co-authored-by: Doug Kearns --- runtime/ftplugin/vim.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim index 3eaf748996..6ba057fc03 100644 --- a/runtime/ftplugin/vim.vim +++ b/runtime/ftplugin/vim.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Vim " Maintainer: Doug Kearns -" Last Change: 2025 Jan 3 +" Last Change: 2025 Jan 06 " Former Maintainer: Bram Moolenaar " Contributors: Riley Bruins ('commentstring') @@ -104,7 +104,7 @@ if exists("loaded_matchit") \ '\\)\@!\S:\,' .. \ '\:\,' .. \ '\:\,' .. - \ '\:\,' + \ '\:\' " Ignore syntax region commands and settings, any 'en*' would clobber " if-endif. -- cgit From b44a8ba1e71041f85ba91981d50a89b7fc797817 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 6 Jan 2025 18:28:56 +0100 Subject: vim-patch:cf1f555: runtime(sh): add PS0 to bashSpecialVariables in syntax script PS0 is also a special prompt variable. (It is expanded and displayed after it reads a command but before executing it.) References: https://www.gnu.org/software/bash/manual/html_node/Interactive-Shell-Behavior.html closes: vim/vim#16394 https://github.com/vim/vim/commit/cf1f55548d1c8782c5bd11f82354d98fb30cde42 Co-authored-by: Jon Parise --- runtime/syntax/sh.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim index 0a8fb47b7d..290d4b68c7 100644 --- a/runtime/syntax/sh.vim +++ b/runtime/syntax/sh.vim @@ -5,6 +5,7 @@ " Lennart Schultz " Last Change: 2024 Mar 04 by Vim Project " 2024 Nov 03 by Aliaksei Budavei <0x000c70 AT gmail DOT com> (improved bracket expressions, #15941) +" 2025 Jan 06 add $0 to bashSpecialVariables (#16394) " Version: 208 " Former URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH " For options and settings, please use: :help ft-sh-syntax @@ -404,7 +405,7 @@ syn region shCmdParenRegion matchgroup=shCmdSubRegion start="((\@!" skip='\\\\\| if exists("b:is_bash") syn cluster shCommandSubList add=bashSpecialVariables,bashStatement syn cluster shCaseList add=bashAdminStatement,bashStatement - syn keyword bashSpecialVariables contained auto_resume BASH BASH_ALIASES BASH_ALIASES BASH_ARGC BASH_ARGC BASH_ARGV BASH_ARGV BASH_CMDS BASH_CMDS BASH_COMMAND BASH_COMMAND BASH_ENV BASH_EXECUTION_STRING BASH_EXECUTION_STRING BASH_LINENO BASH_LINENO BASHOPTS BASHOPTS BASHPID BASHPID BASH_REMATCH BASH_REMATCH BASH_SOURCE BASH_SOURCE BASH_SUBSHELL BASH_SUBSHELL BASH_VERSINFO BASH_VERSION BASH_XTRACEFD BASH_XTRACEFD CDPATH COLUMNS COLUMNS COMP_CWORD COMP_CWORD COMP_KEY COMP_KEY COMP_LINE COMP_LINE COMP_POINT COMP_POINT COMPREPLY COMPREPLY COMP_TYPE COMP_TYPE COMP_WORDBREAKS COMP_WORDBREAKS COMP_WORDS COMP_WORDS COPROC COPROC DIRSTACK EMACS EMACS ENV ENV EUID FCEDIT FIGNORE FUNCNAME FUNCNAME FUNCNEST FUNCNEST GLOBIGNORE GROUPS histchars HISTCMD HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE HISTSIZE HISTTIMEFORMAT HISTTIMEFORMAT HOME HOSTFILE HOSTNAME HOSTTYPE IFS IGNOREEOF INPUTRC LANG LC_ALL LC_COLLATE LC_CTYPE LC_CTYPE LC_MESSAGES LC_NUMERIC LC_NUMERIC LINENO LINES LINES MACHTYPE MAIL MAILCHECK MAILPATH MAPFILE MAPFILE OLDPWD OPTARG OPTERR OPTIND OSTYPE PATH PIPESTATUS POSIXLY_CORRECT POSIXLY_CORRECT PPID PROMPT_COMMAND PS1 PS2 PS3 PS4 PWD RANDOM READLINE_LINE READLINE_LINE READLINE_POINT READLINE_POINT REPLY SECONDS SHELL SHELL SHELLOPTS SHLVL TIMEFORMAT TIMEOUT TMPDIR TMPDIR UID + syn keyword bashSpecialVariables contained auto_resume BASH BASH_ALIASES BASH_ALIASES BASH_ARGC BASH_ARGC BASH_ARGV BASH_ARGV BASH_CMDS BASH_CMDS BASH_COMMAND BASH_COMMAND BASH_ENV BASH_EXECUTION_STRING BASH_EXECUTION_STRING BASH_LINENO BASH_LINENO BASHOPTS BASHOPTS BASHPID BASHPID BASH_REMATCH BASH_REMATCH BASH_SOURCE BASH_SOURCE BASH_SUBSHELL BASH_SUBSHELL BASH_VERSINFO BASH_VERSION BASH_XTRACEFD BASH_XTRACEFD CDPATH COLUMNS COLUMNS COMP_CWORD COMP_CWORD COMP_KEY COMP_KEY COMP_LINE COMP_LINE COMP_POINT COMP_POINT COMPREPLY COMPREPLY COMP_TYPE COMP_TYPE COMP_WORDBREAKS COMP_WORDBREAKS COMP_WORDS COMP_WORDS COPROC COPROC DIRSTACK EMACS EMACS ENV ENV EUID FCEDIT FIGNORE FUNCNAME FUNCNAME FUNCNEST FUNCNEST GLOBIGNORE GROUPS histchars HISTCMD HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE HISTSIZE HISTTIMEFORMAT HISTTIMEFORMAT HOME HOSTFILE HOSTNAME HOSTTYPE IFS IGNOREEOF INPUTRC LANG LC_ALL LC_COLLATE LC_CTYPE LC_CTYPE LC_MESSAGES LC_NUMERIC LC_NUMERIC LINENO LINES LINES MACHTYPE MAIL MAILCHECK MAILPATH MAPFILE MAPFILE OLDPWD OPTARG OPTERR OPTIND OSTYPE PATH PIPESTATUS POSIXLY_CORRECT POSIXLY_CORRECT PPID PROMPT_COMMAND PS0 PS1 PS2 PS3 PS4 PWD RANDOM READLINE_LINE READLINE_LINE READLINE_POINT READLINE_POINT REPLY SECONDS SHELL SHELL SHELLOPTS SHLVL TIMEFORMAT TIMEOUT TMPDIR TMPDIR UID syn keyword bashStatement chmod clear complete du egrep expr fgrep find gnufind gnugrep grep head less ls mkdir mv rm rmdir rpm sed sleep sort strip tail syn keyword bashAdminStatement daemon killall killproc nice reload restart start status stop syn keyword bashStatement command compgen -- cgit From 4d9405991963a53ed31089ed456d9f4cfebc325d Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 6 Jan 2025 19:02:03 +0100 Subject: vim-patch:3159b64: runtime(sh): fix typo in Last Change header related: vim/vim#16394 https://github.com/vim/vim/commit/3159b6494ec08fbe780d14e54ad4e89e7b55bb16 Co-authored-by: Christian Brabandt --- runtime/syntax/sh.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim index 290d4b68c7..d71a966553 100644 --- a/runtime/syntax/sh.vim +++ b/runtime/syntax/sh.vim @@ -5,7 +5,7 @@ " Lennart Schultz " Last Change: 2024 Mar 04 by Vim Project " 2024 Nov 03 by Aliaksei Budavei <0x000c70 AT gmail DOT com> (improved bracket expressions, #15941) -" 2025 Jan 06 add $0 to bashSpecialVariables (#16394) +" 2025 Jan 06 add $PS0 to bashSpecialVariables (#16394) " Version: 208 " Former URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH " For options and settings, please use: :help ft-sh-syntax -- cgit From d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 3 Jan 2025 15:40:46 +0100 Subject: refactor: adopt vterm We have changed too much to consider it a mere bundled dependency (such as unicode handling in e3bfcf2fd4a4ebf00b104b082cfe83c8144a842d), and can consider it our own at this point. --- MAINTAIN.md | 2 - src/nvim/CMakeLists.txt | 5 +- src/nvim/mbyte.h | 1 - src/nvim/mbyte_defs.h | 3 + src/nvim/terminal.c | 10 +- src/nvim/vterm/LICENSE | 23 + src/nvim/vterm/README.md | 1 + src/nvim/vterm/encoding.c | 278 ++++ src/nvim/vterm/encoding.h | 10 + src/nvim/vterm/keyboard.c | 252 ++++ src/nvim/vterm/keyboard.h | 10 + src/nvim/vterm/mouse.c | 113 ++ src/nvim/vterm/mouse.h | 10 + src/nvim/vterm/parser.c | 411 ++++++ src/nvim/vterm/parser.h | 9 + src/nvim/vterm/pen.c | 644 ++++++++++ src/nvim/vterm/pen.h | 9 + src/nvim/vterm/screen.c | 1103 ++++++++++++++++ src/nvim/vterm/screen.h | 9 + src/nvim/vterm/state.c | 2310 ++++++++++++++++++++++++++++++++++ src/nvim/vterm/state.h | 7 + src/nvim/vterm/vterm.c | 335 +++++ src/nvim/vterm/vterm.h | 161 +++ src/nvim/vterm/vterm_defs.h | 319 +++++ src/nvim/vterm/vterm_internal_defs.h | 266 ++++ src/nvim/vterm/vterm_keycodes_defs.h | 58 + src/vterm/LICENSE | 23 - src/vterm/encoding.c | 230 ---- src/vterm/encoding/DECdrawing.inc | 36 - src/vterm/encoding/uk.inc | 6 - src/vterm/keyboard.c | 225 ---- src/vterm/mouse.c | 99 -- src/vterm/parser.c | 408 ------ src/vterm/pen.c | 678 ---------- src/vterm/rect.h | 56 - src/vterm/screen.c | 1174 ----------------- src/vterm/state.c | 2281 --------------------------------- src/vterm/vterm.c | 432 ------- src/vterm/vterm.h | 638 ---------- src/vterm/vterm_internal.h | 298 ----- src/vterm/vterm_keycodes.h | 61 - test/unit/fixtures/vterm_test.c | 271 +++- test/unit/fixtures/vterm_test.h | 27 +- test/unit/vterm_spec.lua | 14 +- 44 files changed, 6645 insertions(+), 6671 deletions(-) create mode 100644 src/nvim/vterm/LICENSE create mode 100644 src/nvim/vterm/README.md create mode 100644 src/nvim/vterm/encoding.c create mode 100644 src/nvim/vterm/encoding.h create mode 100644 src/nvim/vterm/keyboard.c create mode 100644 src/nvim/vterm/keyboard.h create mode 100644 src/nvim/vterm/mouse.c create mode 100644 src/nvim/vterm/mouse.h create mode 100644 src/nvim/vterm/parser.c create mode 100644 src/nvim/vterm/parser.h create mode 100644 src/nvim/vterm/pen.c create mode 100644 src/nvim/vterm/pen.h create mode 100644 src/nvim/vterm/screen.c create mode 100644 src/nvim/vterm/screen.h create mode 100644 src/nvim/vterm/state.c create mode 100644 src/nvim/vterm/state.h create mode 100644 src/nvim/vterm/vterm.c create mode 100644 src/nvim/vterm/vterm.h create mode 100644 src/nvim/vterm/vterm_defs.h create mode 100644 src/nvim/vterm/vterm_internal_defs.h create mode 100644 src/nvim/vterm/vterm_keycodes_defs.h delete mode 100644 src/vterm/LICENSE delete mode 100644 src/vterm/encoding.c delete mode 100644 src/vterm/encoding/DECdrawing.inc delete mode 100644 src/vterm/encoding/uk.inc delete mode 100644 src/vterm/keyboard.c delete mode 100644 src/vterm/mouse.c delete mode 100644 src/vterm/parser.c delete mode 100644 src/vterm/pen.c delete mode 100644 src/vterm/rect.h delete mode 100644 src/vterm/screen.c delete mode 100644 src/vterm/state.c delete mode 100644 src/vterm/vterm.c delete mode 100644 src/vterm/vterm.h delete mode 100644 src/vterm/vterm_internal.h delete mode 100644 src/vterm/vterm_keycodes.h diff --git a/MAINTAIN.md b/MAINTAIN.md index 1442faeff8..eb58664ed2 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -148,8 +148,6 @@ These dependencies are "vendored" (inlined), we must update the sources manually * `src/xdiff/`: [xdiff](https://github.com/git/git/tree/master/xdiff) * `src/cjson/`: [lua-cjson](https://github.com/openresty/lua-cjson) * `src/klib/`: [Klib](https://github.com/attractivechaos/klib) -* `src/vterm/`: [libvterm](https://www.leonerd.org.uk/code/libvterm/), - [mirror](https://github.com/neovim/libvterm) * `runtime/lua/vim/inspect.lua`: [inspect.lua](https://github.com/kikito/inspect.lua) * `src/nvim/tui/terminfo_defs.h`: terminfo definitions * Run `scripts/update_terminfo.sh` to update these definitions. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index cce8ab5b25..767f5087c1 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -361,8 +361,8 @@ file(MAKE_DIRECTORY ${TOUCHES_DIR} ${GENERATED_DIR} ${GENERATED_INCLUDES_DIR}) file(GLOB NVIM_SOURCES CONFIGURE_DEPENDS *.c) file(GLOB NVIM_HEADERS CONFIGURE_DEPENDS *.h) -file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c ../vterm/*.c) -file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../vterm/*.h) +file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c) +file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h) file(GLOB NLUA0_SOURCES CONFIGURE_DEPENDS ../mpack/*.c) @@ -391,6 +391,7 @@ foreach(subdir msgpack_rpc tui tui/termkey + vterm event eval lua diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index 2da051fca2..674e0a2638 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -12,7 +12,6 @@ #include "nvim/mbyte_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep -typedef utf8proc_int32_t GraphemeState; #define GRAPHEME_STATE_INIT 0 #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h index 7f2d1ba6ce..e308a81a5d 100644 --- a/src/nvim/mbyte_defs.h +++ b/src/nvim/mbyte_defs.h @@ -2,6 +2,7 @@ #include #include +#include #include "nvim/iconv_defs.h" @@ -71,3 +72,5 @@ typedef struct { int8_t begin_off; ///< Offset to the first byte of the codepoint. int8_t end_off; ///< Offset to one past the end byte of the codepoint. } CharBoundsOff; + +typedef utf8proc_int32_t GraphemeState; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 0743eda374..d7ed709906 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -93,9 +93,15 @@ #include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/vim_defs.h" +#include "nvim/vterm/keyboard.h" +#include "nvim/vterm/mouse.h" +#include "nvim/vterm/parser.h" +#include "nvim/vterm/pen.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/state.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_keycodes_defs.h" #include "nvim/window.h" -#include "vterm/vterm.h" -#include "vterm/vterm_keycodes.h" typedef struct { VimState state; diff --git a/src/nvim/vterm/LICENSE b/src/nvim/vterm/LICENSE new file mode 100644 index 0000000000..0d051634b2 --- /dev/null +++ b/src/nvim/vterm/LICENSE @@ -0,0 +1,23 @@ + + +The MIT License + +Copyright (c) 2008 Paul Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/nvim/vterm/README.md b/src/nvim/vterm/README.md new file mode 100644 index 0000000000..4cc43bfb94 --- /dev/null +++ b/src/nvim/vterm/README.md @@ -0,0 +1 @@ +Adopted from [libvterm](https://www.leonerd.org.uk/code/libvterm/) diff --git a/src/nvim/vterm/encoding.c b/src/nvim/vterm/encoding.c new file mode 100644 index 0000000000..cc3208cfa2 --- /dev/null +++ b/src/nvim/vterm/encoding.c @@ -0,0 +1,278 @@ +#include "nvim/vterm/encoding.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/encoding.c.generated.h" +#endif + +#define UNICODE_INVALID 0xFFFD + +#if defined(DEBUG) && DEBUG > 1 +# define DEBUG_PRINT_UTF8 +#endif + +struct UTF8DecoderData { + // number of bytes remaining in this codepoint + int bytes_remaining; + + // number of bytes total in this codepoint once it's finished + // (for detecting overlongs) + int bytes_total; + + int this_cp; +}; + +static void init_utf8(VTermEncoding *enc, void *data_) +{ + struct UTF8DecoderData *data = data_; + + data->bytes_remaining = 0; + data->bytes_total = 0; +} + +static void decode_utf8(VTermEncoding *enc, void *data_, uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct UTF8DecoderData *data = data_; + +#ifdef DEBUG_PRINT_UTF8 + printf("BEGIN UTF-8\n"); +#endif + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + uint8_t c = (uint8_t)bytes[*pos]; + +#ifdef DEBUG_PRINT_UTF8 + printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); +#endif + + if (c < 0x20) { // C0 + return; + } else if (c >= 0x20 && c < 0x7f) { + if (data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + } + + cp[(*cpi)++] = c; +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 char: U+%04x\n", c); +#endif + data->bytes_remaining = 0; + } else if (c == 0x7f) { // DEL + return; + } else if (c >= 0x80 && c < 0xc0) { + if (!data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + continue; + } + + data->this_cp <<= 6; + data->this_cp |= c & 0x3f; + data->bytes_remaining--; + + if (!data->bytes_remaining) { +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); +#endif + // Check for overlong sequences + switch (data->bytes_total) { + case 2: + if (data->this_cp < 0x0080) { + data->this_cp = UNICODE_INVALID; + } + break; + case 3: + if (data->this_cp < 0x0800) { + data->this_cp = UNICODE_INVALID; + } + break; + case 4: + if (data->this_cp < 0x10000) { + data->this_cp = UNICODE_INVALID; + } + break; + case 5: + if (data->this_cp < 0x200000) { + data->this_cp = UNICODE_INVALID; + } + break; + case 6: + if (data->this_cp < 0x4000000) { + data->this_cp = UNICODE_INVALID; + } + break; + } + // Now look for plain invalid ones + if ((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) + || data->this_cp == 0xFFFE + || data->this_cp == 0xFFFF) { + data->this_cp = UNICODE_INVALID; + } +#ifdef DEBUG_PRINT_UTF8 + printf(" char: U+%04x\n", data->this_cp); +#endif + cp[(*cpi)++] = (uint32_t)data->this_cp; + } + } else if (c >= 0xc0 && c < 0xe0) { + if (data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + } + + data->this_cp = c & 0x1f; + data->bytes_total = 2; + data->bytes_remaining = 1; + } else if (c >= 0xe0 && c < 0xf0) { + if (data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + } + + data->this_cp = c & 0x0f; + data->bytes_total = 3; + data->bytes_remaining = 2; + } else if (c >= 0xf0 && c < 0xf8) { + if (data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + } + + data->this_cp = c & 0x07; + data->bytes_total = 4; + data->bytes_remaining = 3; + } else if (c >= 0xf8 && c < 0xfc) { + if (data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + } + + data->this_cp = c & 0x03; + data->bytes_total = 5; + data->bytes_remaining = 4; + } else if (c >= 0xfc && c < 0xfe) { + if (data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + } + + data->this_cp = c & 0x01; + data->bytes_total = 6; + data->bytes_remaining = 5; + } else { + cp[(*cpi)++] = UNICODE_INVALID; + } + } +} + +static VTermEncoding encoding_utf8 = { + .init = &init_utf8, + .decode = &decode_utf8, +}; + +static void decode_usascii(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + int is_gr = bytes[*pos] & 0x80; + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + uint8_t c = (uint8_t)(bytes[*pos] ^ is_gr); + + if (c < 0x20 || c == 0x7f || c >= 0x80) { + return; + } + + cp[(*cpi)++] = c; + } +} + +static VTermEncoding encoding_usascii = { + .decode = &decode_usascii, +}; + +struct StaticTableEncoding { + const VTermEncoding enc; + const uint32_t chars[128]; +}; + +static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; + int is_gr = bytes[*pos] & 0x80; + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + uint8_t c = (uint8_t)(bytes[*pos] ^ is_gr); + + if (c < 0x20 || c == 0x7f || c >= 0x80) { + return; + } + + if (table->chars[c]) { + cp[(*cpi)++] = table->chars[c]; + } else { + cp[(*cpi)++] = c; + } + } +} + +static const struct StaticTableEncoding encoding_DECdrawing = { + { .decode = &decode_table }, + { + [0x60] = 0x25C6, // BLACK DIAMOND + [0x61] = 0x2592, // MEDIUM SHADE (checkerboard) + [0x62] = 0x2409, // SYMBOL FOR HORIZONTAL TAB + [0x63] = 0x240C, // SYMBOL FOR FORM FEED + [0x64] = 0x240D, // SYMBOL FOR CARRIAGE RETURN + [0x65] = 0x240A, // SYMBOL FOR LINE FEED + [0x66] = 0x00B0, // DEGREE SIGN + [0x67] = 0x00B1, // PLUS-MINUS SIGN (plus or minus) + [0x68] = 0x2424, // SYMBOL FOR NEW LINE + [0x69] = 0x240B, // SYMBOL FOR VERTICAL TAB + [0x6a] = 0x2518, // BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner) + [0x6b] = 0x2510, // BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner) + [0x6c] = 0x250C, // BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner) + [0x6d] = 0x2514, // BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner) + [0x6e] = 0x253C, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines) + [0x6f] = 0x23BA, // HORIZONTAL SCAN LINE-1 + [0x70] = 0x23BB, // HORIZONTAL SCAN LINE-3 + [0x71] = 0x2500, // BOX DRAWINGS LIGHT HORIZONTAL + [0x72] = 0x23BC, // HORIZONTAL SCAN LINE-7 + [0x73] = 0x23BD, // HORIZONTAL SCAN LINE-9 + [0x74] = 0x251C, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT + [0x75] = 0x2524, // BOX DRAWINGS LIGHT VERTICAL AND LEFT + [0x76] = 0x2534, // BOX DRAWINGS LIGHT UP AND HORIZONTAL + [0x77] = 0x252C, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + [0x78] = 0x2502, // BOX DRAWINGS LIGHT VERTICAL + [0x79] = 0x2A7D, // LESS-THAN OR SLANTED EQUAL-TO + [0x7a] = 0x2A7E, // GREATER-THAN OR SLANTED EQUAL-TO + [0x7b] = 0x03C0, // GREEK SMALL LETTER PI + [0x7c] = 0x2260, // NOT EQUAL TO + [0x7d] = 0x00A3, // POUND SIGN + [0x7e] = 0x00B7, // MIDDLE DOT + } +}; + +static const struct StaticTableEncoding encoding_uk = { + { .decode = &decode_table }, + { + [0x23] = 0x00a3, // £ + } +}; + +static struct { + VTermEncodingType type; + char designation; + VTermEncoding *enc; +} +encodings[] = { + { ENC_UTF8, 'u', &encoding_utf8 }, + { ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing }, + { ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk }, + { ENC_SINGLE_94, 'B', &encoding_usascii }, + { 0 }, +}; + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) +{ + for (int i = 0; encodings[i].designation; i++) { + if (encodings[i].type == type && encodings[i].designation == designation) { + return encodings[i].enc; + } + } + return NULL; +} diff --git a/src/nvim/vterm/encoding.h b/src/nvim/vterm/encoding.h new file mode 100644 index 0000000000..204b6d90c9 --- /dev/null +++ b/src/nvim/vterm/encoding.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "nvim/vterm/vterm_defs.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/encoding.h.generated.h" +#endif diff --git a/src/nvim/vterm/keyboard.c b/src/nvim/vterm/keyboard.c new file mode 100644 index 0000000000..696b09157e --- /dev/null +++ b/src/nvim/vterm/keyboard.c @@ -0,0 +1,252 @@ +#include + +#include "nvim/ascii_defs.h" +#include "nvim/tui/termkey/termkey.h" +#include "nvim/vterm/keyboard.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/keyboard.c.generated.h" +#endif + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) +{ + // The shift modifier is never important for Unicode characters apart from Space + if (c != ' ') { + mod &= (unsigned)~VTERM_MOD_SHIFT; + } + + if (mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8((int)c, str); + vterm_push_output_bytes(vt, str, (size_t)seqlen); + return; + } + + int needs_CSIu; + switch (c) { + // Special Ctrl- letters that can't be represented elsewise + case 'i': + case 'j': + case 'm': + case '[': + needs_CSIu = 1; + break; + // Ctrl-\ ] ^ _ don't need CSUu + case '\\': + case ']': + case '^': + case '_': + needs_CSIu = 0; + break; + // Shift-space needs CSIu + case ' ': + needs_CSIu = !!(mod & VTERM_MOD_SHIFT); + break; + // All other characters needs CSIu except for letters a-z + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + // ALT we can just prefix with ESC; anything else requires CSI u + if (needs_CSIu && (mod & (unsigned)~VTERM_MOD_ALT)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1); + return; + } + + if (mod & VTERM_MOD_CTRL) { + c &= 0x1f; + } + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + char literal; + int csinum; +} keycodes_s; + +static keycodes_s keycodes[] = { + { KEYCODE_NONE, NUL, 0 }, // NONE + + { KEYCODE_ENTER, '\r', 0 }, // ENTER + { KEYCODE_TAB, '\t', 0 }, // TAB + { KEYCODE_LITERAL, '\x7f', 0 }, // BACKSPACE == ASCII DEL + { KEYCODE_LITERAL, '\x1b', 0 }, // ESCAPE + + { KEYCODE_CSI_CURSOR, 'A', 0 }, // UP + { KEYCODE_CSI_CURSOR, 'B', 0 }, // DOWN + { KEYCODE_CSI_CURSOR, 'D', 0 }, // LEFT + { KEYCODE_CSI_CURSOR, 'C', 0 }, // RIGHT + + { KEYCODE_CSINUM, '~', 2 }, // INS + { KEYCODE_CSINUM, '~', 3 }, // DEL + { KEYCODE_CSI_CURSOR, 'H', 0 }, // HOME + { KEYCODE_CSI_CURSOR, 'F', 0 }, // END + { KEYCODE_CSINUM, '~', 5 }, // PAGEUP + { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN +}; + +static keycodes_s keycodes_fn[] = { + { KEYCODE_NONE, NUL, 0 }, // F0 - shouldn't happen + { KEYCODE_SS3, 'P', 0 }, // F1 + { KEYCODE_SS3, 'Q', 0 }, // F2 + { KEYCODE_SS3, 'R', 0 }, // F3 + { KEYCODE_SS3, 'S', 0 }, // F4 + { KEYCODE_CSINUM, '~', 15 }, // F5 + { KEYCODE_CSINUM, '~', 17 }, // F6 + { KEYCODE_CSINUM, '~', 18 }, // F7 + { KEYCODE_CSINUM, '~', 19 }, // F8 + { KEYCODE_CSINUM, '~', 20 }, // F9 + { KEYCODE_CSINUM, '~', 21 }, // F10 + { KEYCODE_CSINUM, '~', 23 }, // F11 + { KEYCODE_CSINUM, '~', 24 }, // F12 +}; + +static keycodes_s keycodes_kp[] = { + { KEYCODE_KEYPAD, '0', 'p' }, // KP_0 + { KEYCODE_KEYPAD, '1', 'q' }, // KP_1 + { KEYCODE_KEYPAD, '2', 'r' }, // KP_2 + { KEYCODE_KEYPAD, '3', 's' }, // KP_3 + { KEYCODE_KEYPAD, '4', 't' }, // KP_4 + { KEYCODE_KEYPAD, '5', 'u' }, // KP_5 + { KEYCODE_KEYPAD, '6', 'v' }, // KP_6 + { KEYCODE_KEYPAD, '7', 'w' }, // KP_7 + { KEYCODE_KEYPAD, '8', 'x' }, // KP_8 + { KEYCODE_KEYPAD, '9', 'y' }, // KP_9 + { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT + { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) +{ + if (key == VTERM_KEY_NONE) { + return; + } + + keycodes_s k; + if (key < VTERM_KEY_FUNCTION_0) { + if (key >= sizeof(keycodes)/sizeof(keycodes[0])) { + return; + } + k = keycodes[key]; + } else if (key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { + if ((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) { + return; + } + k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; + } else if (key >= VTERM_KEY_KP_0) { + if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) { + return; + } + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } + + switch (k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + // Shift-Tab is CSI Z but plain Tab is 0x09 + if (mod == VTERM_MOD_SHIFT) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + } else if (mod & VTERM_MOD_SHIFT) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1); + } else { + goto case_LITERAL; + } + break; + + case KEYCODE_ENTER: + // Enter is CRLF in newline mode, but just LF in linefeed + if (vt->state->mode.newline) { + vterm_push_output_sprintf(vt, "\r\n"); + } else { + goto case_LITERAL; + } + break; + + case KEYCODE_LITERAL: + case_LITERAL: + if (mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1); + } else { + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + } + break; + + case KEYCODE_SS3: + case_SS3: + if (mod == 0) { + vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); + } else { + goto case_CSI; + } + break; + + case KEYCODE_CSI: + case_CSI: + if (mod == 0) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); + } else { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); + } + break; + + case KEYCODE_CSINUM: + if (mod == 0) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); + } else { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); + } + break; + + case KEYCODE_CSI_CURSOR: + if (vt->state->mode.cursor) { + goto case_SS3; + } else { + goto case_CSI; + } + + case KEYCODE_KEYPAD: + if (vt->state->mode.keypad) { + k.literal = (char)k.csinum; + goto case_SS3; + } else { + goto case_LITERAL; + } + } +} + +void vterm_keyboard_start_paste(VTerm *vt) +{ + if (vt->state->mode.bracketpaste) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); + } +} + +void vterm_keyboard_end_paste(VTerm *vt) +{ + if (vt->state->mode.bracketpaste) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); + } +} diff --git a/src/nvim/vterm/keyboard.h b/src/nvim/vterm/keyboard.h new file mode 100644 index 0000000000..af5f4a3ed1 --- /dev/null +++ b/src/nvim/vterm/keyboard.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "nvim/vterm/vterm_defs.h" +#include "nvim/vterm/vterm_keycodes_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/keyboard.h.generated.h" +#endif diff --git a/src/nvim/vterm/mouse.c b/src/nvim/vterm/mouse.c new file mode 100644 index 0000000000..9b8be4b60a --- /dev/null +++ b/src/nvim/vterm/mouse.c @@ -0,0 +1,113 @@ +#include "nvim/tui/termkey/termkey.h" +#include "nvim/vterm/mouse.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/mouse.c.generated.h" +#endif + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) +{ + modifiers <<= 2; + + switch (state->mouse_protocol) { + case MOUSE_X10: + if (col + 0x21 > 0xff) { + col = 0xff - 0x21; + } + if (row + 0x21 > 0xff) { + row = 0xff - 0x21; + } + + if (!pressed) { + code = 3; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", + (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: { + char utf8[18]; + size_t len = 0; + + if (!pressed) { + code = 3; + } + + len += (size_t)fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += (size_t)fill_utf8(col + 0x21, utf8 + len); + len += (size_t)fill_utf8(row + 0x21, utf8 + len); + utf8[len] = 0; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); + } + break; + + case MOUSE_SGR: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", + code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if (!pressed) { + code = 3; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", + code | modifiers, col + 1, row + 1); + break; + } +} + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) +{ + VTermState *state = vt->state; + + if (col == state->mouse_col && row == state->mouse_row) { + return; + } + + state->mouse_col = col; + state->mouse_row = row; + + if ((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) + || (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 + : state->mouse_buttons & 0x02 ? 2 + : state->mouse_buttons & + 0x04 ? 3 : 4; + output_mouse(state, button - 1 + 0x20, 1, (int)mod, col, row); + } +} + +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) +{ + VTermState *state = vt->state; + + int old_buttons = state->mouse_buttons; + + if (button > 0 && button <= 3) { + if (pressed) { + state->mouse_buttons |= (1 << (button - 1)); + } else { + state->mouse_buttons &= ~(1 << (button - 1)); + } + } + + // Most of the time we don't get button releases from 4/5 + if (state->mouse_buttons == old_buttons && button < 4) { + return; + } + + if (!state->mouse_flags) { + return; + } + + if (button < 4) { + output_mouse(state, button - 1, pressed, (int)mod, state->mouse_col, state->mouse_row); + } else if (button < 8) { + output_mouse(state, button - 4 + 0x40, pressed, (int)mod, state->mouse_col, state->mouse_row); + } +} diff --git a/src/nvim/vterm/mouse.h b/src/nvim/vterm/mouse.h new file mode 100644 index 0000000000..477f4028a2 --- /dev/null +++ b/src/nvim/vterm/mouse.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "nvim/vterm/vterm_defs.h" +#include "nvim/vterm/vterm_keycodes_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/mouse.h.generated.h" +#endif diff --git a/src/nvim/vterm/parser.c b/src/nvim/vterm/parser.c new file mode 100644 index 0000000000..79d348f2c1 --- /dev/null +++ b/src/nvim/vterm/parser.c @@ -0,0 +1,411 @@ +#include +#include +#include + +#include "nvim/vterm/parser.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/parser.c.generated.h" +#endif + +#undef DEBUG_PARSER + +static bool is_intermed(uint8_t c) +{ + return c >= 0x20 && c <= 0x2f; +} + +static void do_control(VTerm *vt, uint8_t control) +{ + if (vt->parser.callbacks && vt->parser.callbacks->control) { + if ((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_csi(VTerm *vt, char command) +{ +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.v.csi.leader); + for (int argi = 0; argi < vt->parser.v.csi.argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi])); + if (!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi])) { + printf("\n"); + } + printf(" intermed: %s\n", vt->parser.intermed); + } +#endif + + if (vt->parser.callbacks && vt->parser.callbacks->csi) { + if ((*vt->parser.callbacks->csi)(vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, + vt->parser.v.csi.args, + vt->parser.v.csi.argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} + +static void do_escape(VTerm *vt, char command) +{ + char seq[INTERMED_MAX + 1]; + + size_t len = (size_t)vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); // NOLINT(runtime/printf) + seq[len++] = command; + seq[len] = 0; + + if (vt->parser.callbacks && vt->parser.callbacks->escape) { + if ((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); +} + +static void string_fragment(VTerm *vt, const char *str, size_t len, bool final) +{ + VTermStringFragment frag = { + .str = str, + .len = len, + .initial = vt->parser.string_initial, + .final = final, + }; + + switch (vt->parser.state) { + case OSC: + if (vt->parser.callbacks && vt->parser.callbacks->osc) { + (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata); + } + break; + + case DCS_VTERM: + if (vt->parser.callbacks && vt->parser.callbacks->dcs) { + (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, (size_t)vt->parser.v.dcs.commandlen, + frag, + vt->parser.cbdata); + } + break; + + case APC: + if (vt->parser.callbacks && vt->parser.callbacks->apc) { + (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata); + } + break; + + case PM: + if (vt->parser.callbacks && vt->parser.callbacks->pm) { + (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata); + } + break; + + case SOS: + if (vt->parser.callbacks && vt->parser.callbacks->sos) { + (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata); + } + break; + + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + break; + } + + vt->parser.string_initial = false; +} + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) +{ + size_t pos = 0; + const char *string_start; + + switch (vt->parser.state) { + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + string_start = NULL; + break; + case OSC: + case DCS_VTERM: + case APC: + case PM: + case SOS: + string_start = bytes; + break; + } + +#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while (0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) + +#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND) + + for (; pos < len; pos++) { + uint8_t c = (uint8_t)bytes[pos]; + bool c1_allowed = !vt->mode.utf8; + + if (c == 0x00 || c == 0x7f) { // NUL, DEL + if (IS_STRING_STATE()) { + string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false); + string_start = bytes + pos + 1; + } + if (vt->parser.emit_nul) { + do_control(vt, c); + } + continue; + } + if (c == 0x18 || c == 0x1a) { // CAN, SUB + vt->parser.in_esc = false; + ENTER_NORMAL_STATE(); + if (vt->parser.emit_nul) { + do_control(vt, c); + } + continue; + } else if (c == 0x1b) { // ESC + vt->parser.intermedlen = 0; + if (!IS_STRING_STATE()) { + vt->parser.state = NORMAL; + } + vt->parser.in_esc = true; + continue; + } else if (c == 0x07 // BEL, can stand for ST in OSC or DCS state + && IS_STRING_STATE()) {} else if (c < 0x20) { // other C0 + if (vt->parser.state == SOS) { + continue; // All other C0s permitted in SOS + } + if (IS_STRING_STATE()) { + string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false); + } + do_control(vt, c); + if (IS_STRING_STATE()) { + string_start = bytes + pos + 1; + } + continue; + } + + size_t string_len = (size_t)(bytes + pos - string_start); + + if (vt->parser.in_esc) { + // Hoist an ESC letter into a C1 if we're not in a string mode + // Always accept ESC \ == ST even in string mode + if (!vt->parser.intermedlen + && c >= 0x40 && c < 0x60 + && ((!IS_STRING_STATE() || c == 0x5c))) { + c += 0x40; + c1_allowed = true; + if (string_len) { + assert(string_len > 0); + string_len -= 1; + } + vt->parser.in_esc = false; + } else { + string_start = NULL; + vt->parser.state = NORMAL; + } + } + + switch (vt->parser.state) { + case CSI_LEADER: + // Extract leader bytes 0x3c to 0x3f + if (c >= 0x3c && c <= 0x3f) { + if (vt->parser.v.csi.leaderlen < CSI_LEADER_MAX - 1) { + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = (char)c; + } + break; + } + + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; + + vt->parser.v.csi.argi = 0; + vt->parser.v.csi.args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + FALLTHROUGH; + case CSI_ARGS: + // Numerical value of argument + if (c >= '0' && c <= '9') { + if (vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) { + vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; + } + vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; + vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; + break; + } + if (c == ':') { + vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; + c = ';'; + } + if (c == ';') { + vt->parser.v.csi.argi++; + vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; + break; + } + + vt->parser.v.csi.argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + FALLTHROUGH; + case CSI_INTERMED: + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) { + vt->parser.intermed[vt->parser.intermedlen++] = (char)c; + } + break; + } else if (c == 0x1b) { + // ESC in CSI cancels + } else if (c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, (char)c); + } + // else was invalid CSI + + ENTER_NORMAL_STATE(); + break; + + case OSC_COMMAND: + // Numerical value of command + if (c >= '0' && c <= '9') { + if (vt->parser.v.osc.command == -1) { + vt->parser.v.osc.command = 0; + } else { + vt->parser.v.osc.command *= 10; + } + vt->parser.v.osc.command += c - '0'; + break; + } + if (c == ';') { + vt->parser.state = OSC; + string_start = bytes + pos + 1; + break; + } + + string_start = bytes + pos; + string_len = 0; + vt->parser.state = OSC; + goto string_state; + + case DCS_COMMAND: + if (vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) { + vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = (char)c; + } + + if (c >= 0x40 && c <= 0x7e) { + string_start = bytes + pos + 1; + vt->parser.state = DCS_VTERM; + } + break; + +string_state: + case OSC: + case DCS_VTERM: + case APC: + case PM: + case SOS: + if (c == 0x07 || (c1_allowed && c == 0x9c)) { + string_fragment(vt, string_start, string_len, true); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if (vt->parser.in_esc) { + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) { + vt->parser.intermed[vt->parser.intermedlen++] = (char)c; + } + } else if (c >= 0x30 && c < 0x7f) { + do_escape(vt, (char)c); + vt->parser.in_esc = 0; + ENTER_NORMAL_STATE(); + } else { + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); + } + break; + } + if (c1_allowed && c >= 0x80 && c < 0xa0) { + switch (c) { + case 0x90: // DCS + vt->parser.string_initial = true; + vt->parser.v.dcs.commandlen = 0; + ENTER_STATE(DCS_COMMAND); + break; + case 0x98: // SOS + vt->parser.string_initial = true; + ENTER_STATE(SOS); + string_start = bytes + pos + 1; + break; + case 0x9b: // CSI + vt->parser.v.csi.leaderlen = 0; + ENTER_STATE(CSI_LEADER); + break; + case 0x9d: // OSC + vt->parser.v.osc.command = -1; + vt->parser.string_initial = true; + ENTER_STATE(OSC_COMMAND); + break; + case 0x9e: // PM + vt->parser.string_initial = true; + ENTER_STATE(PM); + string_start = bytes + pos + 1; + break; + case 0x9f: // APC + vt->parser.string_initial = true; + ENTER_STATE(APC); + string_start = bytes + pos + 1; + break; + default: + do_control(vt, c); + break; + } + } else { + size_t eaten = 0; + if (vt->parser.callbacks && vt->parser.callbacks->text) { + eaten = (size_t)(*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + } + + if (!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + // force it to make progress + eaten = 1; + } + + pos += (eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + + if (string_start) { + size_t string_len = (size_t)(bytes + pos - string_start); + if (string_len > 0) { + if (vt->parser.in_esc) { + string_len -= 1; + } + string_fragment(vt, string_start, string_len, false); + } + } + + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} diff --git a/src/nvim/vterm/parser.h b/src/nvim/vterm/parser.h new file mode 100644 index 0000000000..168be830c0 --- /dev/null +++ b/src/nvim/vterm/parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/parser.h.generated.h" +#endif diff --git a/src/nvim/vterm/pen.c b/src/nvim/vterm/pen.c new file mode 100644 index 0000000000..e7f50078ae --- /dev/null +++ b/src/nvim/vterm/pen.c @@ -0,0 +1,644 @@ +#include + +#include "nvim/vterm/pen.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/pen.c.generated.h" +#endif + +// Structure used to store RGB triples without the additional metadata stored in VTermColor. +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { + // R G B + { 0, 0, 0 }, // black + { 224, 0, 0 }, // red + { 0, 224, 0 }, // green + { 224, 224, 0 }, // yellow + { 0, 0, 224 }, // blue + { 224, 0, 224 }, // magenta + { 0, 224, 224 }, // cyan + { 224, 224, 224 }, // white == light grey + + // high intensity + { 128, 128, 128 }, // black + { 255, 64, 64 }, // red + { 64, 255, 64 }, // green + { 255, 255, 64 }, // yellow + { 64, 64, 255 }, // blue + { 255, 64, 255 }, // magenta + { 64, 255, 255 }, // cyan + { 255, 255, 255 }, // white for real +}; + +static uint8_t ramp6[] = { + 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, +}; + +static uint8_t ramp24[] = { + 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, + 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, +}; + +static void lookup_default_colour_ansi(long idx, VTermColor *col) +{ + if (idx >= 0 && idx < 16) { + vterm_color_rgb(col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) +{ + if (index >= 0 && index < 16) { + *col = state->colors[index]; + return true; + } + + return false; +} + +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) +{ + if (index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + return lookup_colour_ansi(state, index, col); + } else if (index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + vterm_color_rgb(col, ramp6[index/6/6 % 6], + ramp6[index/6 % 6], + ramp6[index % 6]); + + return true; + } else if (index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; + } + + return false; +} + +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, + VTermColor *col) +{ + switch (palette) { + case 2: // RGB mode - 3 args contain colour values directly + if (argcount < 3) { + return argcount; + } + + vterm_color_rgb(col, (uint8_t)CSI_ARG(args[0]), (uint8_t)CSI_ARG(args[1]), + (uint8_t)CSI_ARG(args[2])); + + return 3; + + case 5: // XTerm 256-colour mode + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } + + vterm_color_indexed(col, (uint8_t)args[0]); + + return argcount ? 1 : 0; + + default: + DEBUG_LOG("Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ +#ifdef DEBUG + if (type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if (state->callbacks && state->callbacks->setpenattr) { + (*state->callbacks->setpenattr)(attr, val, state->cbdata); + } +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) +{ + VTermValue val = { .boolean = boolean }; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) +{ + VTermValue val = { .number = number }; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) +{ + VTermValue val = { .color = color }; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) +{ + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + vterm_color_indexed(colp, (uint8_t)col); + + setpenattr_col(state, attr, *colp); +} + +void vterm_state_newpen(VTermState *state) +{ + // 90% grey so that pure white is brighter + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); + + for (int col = 0; col < 16; col++) { + lookup_default_colour_ansi(col, &state->colors[col]); + } +} + +void vterm_state_resetpen(VTermState *state) +{ + state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; setpenattr_int(state, VTERM_ATTR_FONT, 0); + state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); + state->pen.baseline = 0; setpenattr_int(state, VTERM_ATTR_BASELINE, 0); + + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); + + state->pen.uri = 0; setpenattr_int(state, VTERM_ATTR_URI, 0); +} + +void vterm_state_savepen(VTermState *state, int save) +{ + if (save) { + state->saved.pen = state->pen; + } else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline); + + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + + setpenattr_int(state, VTERM_ATTR_URI, state->pen.uri); + } +} + +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, + const VTermColor *default_bg) +{ + if (default_fg) { + state->default_fg = *default_fg; + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + } + + if (default_bg) { + state->default_bg = *default_bg; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; + } +} + +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) +{ + if (index >= 0 && index < 16) { + state->colors[index] = *col; + } +} + +/// Makes sure that the given color `col` is indeed an RGB colour. After this +/// function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other +/// flags stored in `col->type` will have been reset. +/// +/// @param state is the VTermState instance from which the colour palette should +/// be extracted. +/// @param col is a pointer at the VTermColor instance that should be converted +/// to an RGB colour. +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) +{ + if (VTERM_COLOR_IS_INDEXED(col)) { // Convert indexed colors to RGB + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; // Reset any metadata but the type +} + +void vterm_state_setpen(VTermState *state, const long args[], int argcount) +{ + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while (argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch (arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if (!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 + && state->bold_is_highbright) { + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); + } + break; + } + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline + state->pen.underline = VTERM_UNDERLINE_SINGLE; + if (CSI_ARG_HAS_MORE(args[argi])) { + argi++; + switch (CSI_ARG(args[argi])) { + case 0: + state->pen.underline = 0; + break; + case 1: + state->pen.underline = VTERM_UNDERLINE_SINGLE; + break; + case 2: + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + break; + case 3: + state->pen.underline = VTERM_UNDERLINE_CURLY; + break; + } + } + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 8: // Conceal on + state->pen.conceal = 1; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 28: // Conceal off (Reveal) + state->pen.conceal = 0; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + if (state->pen.bold && state->bold_is_highbright) { + value += 8; + } + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + if (argcount - argi < 1) { + return; + } + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, + argcount - argi - 2, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: // Background colour palette + value = CSI_ARG(args[argi]) - 40; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + case 48: // Background colour alternative palette + if (argcount - argi < 1) { + return; + } + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, + argcount - argi - 2, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 73: // Superscript + case 74: // Subscript + case 75: // Superscript/subscript off + state->pen.small = (arg != 75); + state->pen.baseline = + (arg == 73) ? VTERM_BASELINE_RAISE + : (arg == 74) ? VTERM_BASELINE_LOWER + : VTERM_BASELINE_NORMAL; + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline); + break; + + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: // Foreground colour high-intensity palette + value = CSI_ARG(args[argi]) - 90 + 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: // Background colour high-intensity palette + value = CSI_ARG(args[argi]) - 100 + 8; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + default: + done = 0; + break; + } + + if (!done) { + DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); + } + + while (CSI_ARG_HAS_MORE(args[argi++])) {} + } +} + +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) +{ + // Do nothing if the given color is the default color + if ((fg && VTERM_COLOR_IS_DEFAULT_FG(col)) + || (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + // Decide whether to send an indexed color or an RGB color + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + +int vterm_state_getpen(VTermState *state, long args[], int argcount) +{ + int argi = 0; + + if (state->pen.bold) { + args[argi++] = 1; + } + + if (state->pen.italic) { + args[argi++] = 3; + } + + if (state->pen.underline == VTERM_UNDERLINE_SINGLE) { + args[argi++] = 4; + } + if (state->pen.underline == VTERM_UNDERLINE_CURLY) { + args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; + } + + if (state->pen.blink) { + args[argi++] = 5; + } + + if (state->pen.reverse) { + args[argi++] = 7; + } + + if (state->pen.conceal) { + args[argi++] = 8; + } + + if (state->pen.strike) { + args[argi++] = 9; + } + + if (state->pen.font) { + args[argi++] = 10 + state->pen.font; + } + + if (state->pen.underline == VTERM_UNDERLINE_DOUBLE) { + args[argi++] = 21; + } + + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); + + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); + + if (state->pen.small) { + if (state->pen.baseline == VTERM_BASELINE_RAISE) { + args[argi++] = 73; + } else if (state->pen.baseline == VTERM_BASELINE_LOWER) { + args[argi++] = 74; + } + } + + return argi; +} + +int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ + if (!val) { + return 0; + } + + if (type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return 0; + } + + switch (attr) { + case VTERM_ATTR_BOLD: + state->pen.bold = (unsigned)val->boolean; + break; + case VTERM_ATTR_UNDERLINE: + state->pen.underline = (unsigned)val->number; + break; + case VTERM_ATTR_ITALIC: + state->pen.italic = (unsigned)val->boolean; + break; + case VTERM_ATTR_BLINK: + state->pen.blink = (unsigned)val->boolean; + break; + case VTERM_ATTR_REVERSE: + state->pen.reverse = (unsigned)val->boolean; + break; + case VTERM_ATTR_CONCEAL: + state->pen.conceal = (unsigned)val->boolean; + break; + case VTERM_ATTR_STRIKE: + state->pen.strike = (unsigned)val->boolean; + break; + case VTERM_ATTR_FONT: + state->pen.font = (unsigned)val->number; + break; + case VTERM_ATTR_FOREGROUND: + state->pen.fg = val->color; + break; + case VTERM_ATTR_BACKGROUND: + state->pen.bg = val->color; + break; + case VTERM_ATTR_SMALL: + state->pen.small = (unsigned)val->boolean; + break; + case VTERM_ATTR_BASELINE: + state->pen.baseline = (unsigned)val->number; + break; + case VTERM_ATTR_URI: + state->pen.uri = val->number; + break; + default: + return 0; + } + + if (state->callbacks && state->callbacks->setpenattr) { + (*state->callbacks->setpenattr)(attr, val, state->cbdata); + } + + return 1; +} diff --git a/src/nvim/vterm/pen.h b/src/nvim/vterm/pen.h new file mode 100644 index 0000000000..c5f5217420 --- /dev/null +++ b/src/nvim/vterm/pen.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/pen.h.generated.h" +#endif diff --git a/src/nvim/vterm/screen.c b/src/nvim/vterm/screen.c new file mode 100644 index 0000000000..f24e47e543 --- /dev/null +++ b/src/nvim/vterm/screen.c @@ -0,0 +1,1103 @@ +#include +#include + +#include "nvim/grid.h" +#include "nvim/mbyte.h" +#include "nvim/tui/termkey/termkey.h" +#include "nvim/vterm/pen.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/state.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_defs.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/screen.c.generated.h" +#endif + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +#undef DEBUG_REFLOW + +static inline void clearcell(const VTermScreen *screen, ScreenCell *cell) +{ + cell->schar = 0; + cell->pen = screen->pen; +} + +ScreenCell *getcell(const VTermScreen *screen, int row, int col) +{ + if (row < 0 || row >= screen->rows) { + return NULL; + } + if (col < 0 || col >= screen->cols) { + return NULL; + } + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols) +{ + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, + sizeof(ScreenCell) * (size_t)rows * (size_t)cols); + + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + clearcell(screen, &new_buffer[row * cols + col]); + } + } + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) +{ + VTermRect emit; + + switch (screen->damage_merge) { + case VTERM_DAMAGE_CELL: + // Always emit damage event + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + // Emit damage longer than one row. Try to merge with existing damage in the same row + if (rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } else if (screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } else if (rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if (screen->damaged.start_col > rect.start_col) { + screen->damaged.start_col = rect.start_col; + } + if (screen->damaged.end_col < rect.end_col) { + screen->damaged.end_col = rect.end_col; + } + return; + } else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + // Never emit damage event + if (screen->damaged.start_row == -1) { + screen->damaged = rect; + } else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if (screen->callbacks && screen->callbacks->damage) { + (*screen->callbacks->damage)(emit, screen->cbdata); + } +} + +static void damagescreen(VTermScreen *screen) +{ + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) +{ + VTermScreen *screen = user; + ScreenCell *cell = getcell(screen, pos.row, pos.col); + + if (!cell) { + return 0; + } + + cell->schar = info->schar; + if (info->schar != 0) { + cell->pen = screen->pen; + } + + for (int col = 1; col < info->width; col++) { + getcell(screen, pos.row, pos.col + col)->schar = (uint32_t)-1; + } + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row + 1, + .start_col = pos.col, + .end_col = pos.col + info->width, + }; + + cell->pen.protected_cell = info->protected_cell; + cell->pen.dwl = info->dwl; + cell->pen.dhl = info->dhl; + + damagerect(screen, rect); + + return 1; +} + +static void sb_pushline_from_row(VTermScreen *screen, int row) +{ + VTermPos pos = { .row = row }; + for (pos.col = 0; pos.col < screen->cols; pos.col++) { + vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); + } + + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->sb_pushline + && dest.start_row == 0 && dest.start_col == 0 // starts top-left corner + && dest.end_col == screen->cols // full width + && screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen + for (int row = 0; row < src.start_row; row++) { + sb_pushline_from_row(screen, row); + } + } + + int cols = src.end_col - src.start_col; + int downward = src.start_row - dest.start_row; + + int init_row, test_row, inc_row; + if (downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } else { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + for (int row = init_row; row != test_row; row += inc_row) { + memmove(getcell(screen, row, dest.start_col), + getcell(screen, row + downward, src.start_col), + (size_t)cols * sizeof(ScreenCell)); + } + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->moverect) { + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) { + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + } + + if ((*screen->callbacks->moverect)(dest, src, screen->cbdata)) { + return 1; + } + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + for (int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { + const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); + + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (selective && cell->pen.protected_cell) { + continue; + } + + cell->schar = 0; + cell->pen = (ScreenPen){ + // Only copy .fg and .bg; leave things like rv in reset state + .fg = screen->pen.fg, + .bg = screen->pen.bg, + }; + cell->pen.dwl = info->doublewidth; + cell->pen.dhl = info->doubleheight; + } + } + + return 1; +} + +static int erase_user(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, int selective, void *user) +{ + erase_internal(rect, selective, user); + return erase_user(rect, 0, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) +{ + VTermScreen *screen = user; + + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, + moverect_user, erase_user, screen); + + return 1; + } + + if (screen->damaged.start_row != -1 + && !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if (screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } else if (rect_equal(&screen->pending_scrollrect, &rect) + && ((screen->pending_scroll_downward == 0 && downward == 0) + || (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + if (screen->damaged.start_row == -1) { + return 1; + } + + if (rect_contains(&rect, &screen->damaged)) { + // Scroll region entirely contains the damage; just move it + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + // There are a number of possible cases here, but lets restrict this to only the common case where + // we might actually gain some performance by optimising it. Namely, a vertical scroll that neatly + // cuts the damage region in half. + else if (rect.start_col <= screen->damaged.start_col + && rect.end_col >= screen->damaged.end_col + && rightward == 0) { + if (screen->damaged.start_row >= rect.start_row + && screen->damaged.start_row < rect.end_row) { + screen->damaged.start_row -= downward; + if (screen->damaged.start_row < rect.start_row) { + screen->damaged.start_row = rect.start_row; + } + if (screen->damaged.start_row > rect.end_row) { + screen->damaged.start_row = rect.end_row; + } + } + if (screen->damaged.end_row >= rect.start_row + && screen->damaged.end_row < rect.end_row) { + screen->damaged.end_row -= downward; + if (screen->damaged.end_row < rect.start_row) { + screen->damaged.end_row = rect.start_row; + } + if (screen->damaged.end_row > rect.end_row) { + screen->damaged.end_row = rect.end_row; + } + } + } else { + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", + ARGSrect(screen->damaged), ARGSrect(rect)); + } + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->movecursor) { + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + } + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch (attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = (unsigned)val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_CONCEAL: + screen->pen.conceal = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = (unsigned)val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + case VTERM_ATTR_SMALL: + screen->pen.small = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_BASELINE: + screen->pen.baseline = (unsigned)val->number; + return 1; + case VTERM_ATTR_URI: + screen->pen.uri = val->number; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch (prop) { + case VTERM_PROP_ALTSCREEN: + if (val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) { + return 0; + } + + screen->buffer = + val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + // only send a damage event on disable; because during enable there's an erase that sends a + // damage anyway + if (!val->boolean) { + damagescreen(screen); + } + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = (unsigned)val->boolean; + damagescreen(screen); + break; + default: + ; // ignore + } + + if (screen->callbacks && screen->callbacks->settermprop) { + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + } + + return 1; +} + +static int bell(void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->bell) { + return (*screen->callbacks->bell)(screen->cbdata); + } + + return 0; +} + +/// How many cells are non-blank Returns the position of the first blank cell in the trailing blank +/// end +static int line_popcount(ScreenCell *buffer, int row, int rows, int cols) +{ + int col = cols - 1; + while (col >= 0 && buffer[row * cols + col].schar == 0) { + col--; + } + return col + 1; +} + +static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, + VTermStateFields *statefields) +{ + int old_rows = screen->rows; + int old_cols = screen->cols; + + ScreenCell *old_buffer = screen->buffers[bufidx]; + VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx]; + + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, + sizeof(ScreenCell) * (size_t)new_rows * + (size_t)new_cols); + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, + sizeof(new_lineinfo[0]) * (size_t)new_rows); + + int old_row = old_rows - 1; + int new_row = new_rows - 1; + + VTermPos old_cursor = statefields->pos; + VTermPos new_cursor = { -1, -1 }; + +#ifdef DEBUG_REFLOW + fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n", + old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row); +#endif + + // Keep track of the final row that is knonw to be blank, so we know what spare space we have for + // scrolling into + int final_blank_row = new_rows; + + while (old_row >= 0) { + int old_row_end = old_row; + // TODO(vterm): Stop if dwl or dhl + while (screen->reflow && old_lineinfo && old_row > 0 && old_lineinfo[old_row].continuation) { + old_row--; + } + int old_row_start = old_row; + + int width = 0; + for (int row = old_row_start; row <= old_row_end; row++) { + if (screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) { + width += old_cols; + } else { + width += line_popcount(old_buffer, row, old_rows, old_cols); + } + } + + if (final_blank_row == (new_row + 1) && width == 0) { + final_blank_row = new_row; + } + + int new_height = screen->reflow + ? width ? (width + new_cols - 1) / new_cols : 1 + : 1; + + int new_row_end = new_row; + int new_row_start = new_row - new_height + 1; + + old_row = old_row_start; + int old_col = 0; + + int spare_rows = new_rows - final_blank_row; + + if (new_row_start < 0 // we'd fall off the top + && spare_rows >= 0 // we actually have spare rows + && (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) { + // Attempt to scroll content down into the blank rows at the bottom to make it fit + int downwards = -new_row_start; + if (downwards > spare_rows) { + downwards = spare_rows; + } + int rowcount = new_rows - downwards; + +#ifdef DEBUG_REFLOW + fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards); +#endif + + memmove(&new_buffer[downwards * new_cols], &new_buffer[0], + (size_t)rowcount * (size_t)new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[downwards], &new_lineinfo[0], + (size_t)rowcount * sizeof(new_lineinfo[0])); + + new_row += downwards; + new_row_start += downwards; + new_row_end += downwards; + + if (new_cursor.row >= 0) { + new_cursor.row += downwards; + } + + final_blank_row += downwards; + } + +#ifdef DEBUG_REFLOW + fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n", + new_row_start, new_row_end, old_row_start, old_row_end, width); +#endif + + if (new_row_start < 0) { + if (old_row_start <= old_cursor.row && old_cursor.row <= old_row_end) { + new_cursor.row = 0; + new_cursor.col = old_cursor.col; + if (new_cursor.col >= new_cols) { + new_cursor.col = new_cols - 1; + } + } + break; + } + + for (new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) { + int count = width >= new_cols ? new_cols : width; + width -= count; + + int new_col = 0; + + while (count) { + // TODO(vterm): This could surely be done a lot faster by memcpy()'ing the entire range + new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col]; + + if (old_cursor.row == old_row && old_cursor.col == old_col) { + new_cursor.row = new_row, new_cursor.col = new_col; + } + + old_col++; + if (old_col == old_cols) { + old_row++; + + if (!screen->reflow) { + new_col++; + break; + } + old_col = 0; + } + + new_col++; + count--; + } + + if (old_cursor.row == old_row && old_cursor.col >= old_col) { + new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col); + if (new_cursor.col >= new_cols) { + new_cursor.col = new_cols - 1; + } + } + + while (new_col < new_cols) { + clearcell(screen, &new_buffer[new_row * new_cols + new_col]); + new_col++; + } + + new_lineinfo[new_row].continuation = (new_row > new_row_start); + } + + old_row = old_row_start - 1; + new_row = new_row_start - 1; + } + + if (old_cursor.row <= old_row) { + // cursor would have moved entirely off the top of the screen; lets just bring it within range + new_cursor.row = 0, new_cursor.col = old_cursor.col; + if (new_cursor.col >= new_cols) { + new_cursor.col = new_cols - 1; + } + } + + // We really expect the cursor position to be set by now + if (active && (new_cursor.row == -1 || new_cursor.col == -1)) { + fprintf(stderr, "screen_resize failed to update cursor position\n"); + abort(); + } + + if (old_row >= 0 && bufidx == BUFIDX_PRIMARY) { + // Push spare lines to scrollback buffer + if (screen->callbacks && screen->callbacks->sb_pushline) { + for (int row = 0; row <= old_row; row++) { + sb_pushline_from_row(screen, row); + } + } + if (active) { + statefields->pos.row -= (old_row + 1); + } + } + if (new_row >= 0 && bufidx == BUFIDX_PRIMARY + && screen->callbacks && screen->callbacks->sb_popline) { + // Try to backfill rows by popping scrollback buffer + while (new_row >= 0) { + if (!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) { + break; + } + + VTermPos pos = { .row = new_row }; + for (pos.col = 0; pos.col < old_cols && pos.col < new_cols; + pos.col += screen->sb_buffer[pos.col].width) { + VTermScreenCell *src = &screen->sb_buffer[pos.col]; + ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; + + dst->schar = src->schar; + + dst->pen.bold = src->attrs.bold; + dst->pen.underline = src->attrs.underline; + dst->pen.italic = src->attrs.italic; + dst->pen.blink = src->attrs.blink; + dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse; + dst->pen.conceal = src->attrs.conceal; + dst->pen.strike = src->attrs.strike; + dst->pen.font = src->attrs.font; + dst->pen.small = src->attrs.small; + dst->pen.baseline = src->attrs.baseline; + + dst->pen.fg = src->fg; + dst->pen.bg = src->bg; + + dst->pen.uri = src->uri; + + if (src->width == 2 && pos.col < (new_cols - 1)) { + (dst + 1)->schar = (uint32_t)-1; + } + } + for (; pos.col < new_cols; pos.col++) { + clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]); + } + new_row--; + + if (active) { + statefields->pos.row++; + } + } + } + if (new_row >= 0) { + // Scroll new rows back up to the top and fill in blanks at the bottom + int moverows = new_rows - new_row - 1; + memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], + (size_t)moverows * (size_t)new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], + (size_t)moverows * sizeof(new_lineinfo[0])); + + new_cursor.row -= (new_row + 1); + + for (new_row = moverows; new_row < new_rows; new_row++) { + for (int col = 0; col < new_cols; col++) { + clearcell(screen, &new_buffer[new_row * new_cols + col]); + } + new_lineinfo[new_row] = (VTermLineInfo){ 0 }; + } + } + + vterm_allocator_free(screen->vt, old_buffer); + screen->buffers[bufidx] = new_buffer; + + vterm_allocator_free(screen->vt, old_lineinfo); + statefields->lineinfos[bufidx] = new_lineinfo; + + if (active) { + statefields->pos = new_cursor; + } +} + +static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user) +{ + VTermScreen *screen = user; + + int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] + && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]); + + int old_rows = screen->rows; + int old_cols = screen->cols; + + if (new_cols > old_cols) { + // Ensure that ->sb_buffer is large enough for a new or and old row + if (screen->sb_buffer) { + vterm_allocator_free(screen->vt, screen->sb_buffer); + } + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, + sizeof(VTermScreenCell) * (size_t)new_cols); + } + + resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields); + if (screen->buffers[BUFIDX_ALTSCREEN]) { + resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields); + } else if (new_rows != old_rows) { + // We don't need a full resize of the altscreen because it isn't enabled but we should at least + // keep the lineinfo the right size + vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]); + + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, + sizeof(new_lineinfo[0]) * + (size_t)new_rows); + for (int row = 0; row < new_rows; row++) { + new_lineinfo[row] = (VTermLineInfo){ 0 }; + } + + fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo; + } + + screen->buffer = + altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + + screen->rows = new_rows; + screen->cols = new_cols; + + if (new_cols <= old_cols) { + if (screen->sb_buffer) { + vterm_allocator_free(screen->vt, screen->sb_buffer); + } + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, + sizeof(VTermScreenCell) * (size_t)new_cols); + } + + // TODO(vterm): Maaaaybe we can optimise this if there's no reflow happening + damagescreen(screen); + + if (screen->callbacks && screen->callbacks->resize) { + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + } + + return 1; +} + +static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, + void *user) +{ + VTermScreen *screen = user; + + if (newinfo->doublewidth != oldinfo->doublewidth + || newinfo->doubleheight != oldinfo->doubleheight) { + for (int col = 0; col < screen->cols; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->pen.dwl = newinfo->doublewidth; + cell->pen.dhl = newinfo->doubleheight; + } + + VTermRect rect = { + .start_row = row, + .end_row = row + 1, + .start_col = 0, + .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, + }; + damagerect(screen, rect); + + if (newinfo->doublewidth) { + rect.start_col = screen->cols / 2; + rect.end_col = screen->cols; + + erase_internal(rect, 0, user); + } + } + + return 1; +} + +static int sb_clear(void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->sb_clear) { + if ((*screen->callbacks->sb_clear)(screen->cbdata)) { + return 1; + } + } + + return 0; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, + .sb_clear = &sb_clear, +}; + +static VTermScreen *screen_new(VTerm *vt) +{ + VTermState *state = vterm_obtain_state(vt); + if (!state) { + return NULL; + } + + VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + int rows, cols; + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->global_reverse = false; + screen->reflow = false; + + screen->callbacks = NULL; + screen->cbdata = NULL; + + screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); + + screen->buffer = screen->buffers[BUFIDX_PRIMARY]; + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * (size_t)cols); + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +void vterm_screen_free(VTermScreen *screen) +{ + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]); + if (screen->buffers[BUFIDX_ALTSCREEN]) { + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]); + } + + vterm_allocator_free(screen->vt, screen->sb_buffer); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) +{ + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +// Copy internal to external representation of a screen cell +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) +{ + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if (!intcell) { + return 0; + } + + cell->schar = intcell->schar; + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.conceal = intcell->pen.conceal; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + cell->attrs.small = intcell->pen.small; + cell->attrs.baseline = intcell->pen.baseline; + + cell->attrs.dwl = intcell->pen.dwl; + cell->attrs.dhl = intcell->pen.dhl; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + cell->uri = intcell->pen.uri; + + if (pos.col < (screen->cols - 1) + && getcell(screen, pos.row, pos.col + 1)->schar == (uint32_t)-1) { + cell->width = 2; + } else { + cell->width = 1; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) +{ + if (vt->screen) { + return vt->screen; + } + + VTermScreen *screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow) +{ + screen->reflow = reflow; +} + +#undef vterm_screen_set_reflow +void vterm_screen_set_reflow(VTermScreen *screen, bool reflow) +{ + vterm_screen_enable_reflow(screen, reflow); +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) +{ + if (!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, + void *user) +{ + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, + const VTermStateFallbacks *fallbacks, void *user) +{ + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void vterm_screen_flush_damage(VTermScreen *screen) +{ + if (screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, + screen->pending_scroll_rightward, + moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if (screen->damaged.start_row != -1) { + if (screen->callbacks && screen->callbacks->damage) { + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + } + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) +{ + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} + +/// Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` instance. +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) +{ + vterm_state_convert_color_to_rgb(screen->state, col); +} + +// Some utility functions on VTermRect structures + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +// Expand dst to contain src as well +void rect_expand(VTermRect *dst, VTermRect *src) +{ + if (dst->start_row > src->start_row) { + dst->start_row = src->start_row; + } + if (dst->start_col > src->start_col) { + dst->start_col = src->start_col; + } + if (dst->end_row < src->end_row) { + dst->end_row = src->end_row; + } + if (dst->end_col < src->end_col) { + dst->end_col = src->end_col; + } +} + +// Clip the dst to ensure it does not step outside of bounds +void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if (dst->start_row < bounds->start_row) { + dst->start_row = bounds->start_row; + } + if (dst->start_col < bounds->start_col) { + dst->start_col = bounds->start_col; + } + if (dst->end_row > bounds->end_row) { + dst->end_row = bounds->end_row; + } + if (dst->end_col > bounds->end_col) { + dst->end_col = bounds->end_col; + } + // Ensure it doesn't end up negatively-sized + if (dst->end_row < dst->start_row) { + dst->end_row = dst->start_row; + } + if (dst->end_col < dst->start_col) { + dst->end_col = dst->start_col; + } +} + +// True if the two rectangles are equal +int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) + && (a->start_col == b->start_col) + && (a->end_row == b->end_row) + && (a->end_col == b->end_col); +} + +// True if small is contained entirely within big +int rect_contains(VTermRect *big, VTermRect *small) +{ + if (small->start_row < big->start_row) { + return 0; + } + if (small->start_col < big->start_col) { + return 0; + } + if (small->end_row > big->end_row) { + return 0; + } + if (small->end_col > big->end_col) { + return 0; + } + return 1; +} + +// True if the rectangles overlap at all +int rect_intersects(VTermRect *a, VTermRect *b) +{ + if (a->start_row > b->end_row || b->start_row > a->end_row) { + return 0; + } + if (a->start_col > b->end_col || b->start_col > a->end_col) { + return 0; + } + return 1; +} diff --git a/src/nvim/vterm/screen.h b/src/nvim/vterm/screen.h new file mode 100644 index 0000000000..fa7520d75a --- /dev/null +++ b/src/nvim/vterm/screen.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/screen.h.generated.h" +#endif diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c new file mode 100644 index 0000000000..9e787acd9b --- /dev/null +++ b/src/nvim/vterm/state.c @@ -0,0 +1,2310 @@ +#include +#include + +#include "nvim/grid.h" +#include "nvim/mbyte.h" +#include "nvim/vterm/encoding.h" +#include "nvim/vterm/parser.h" +#include "nvim/vterm/pen.h" +#include "nvim/vterm/state.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/state.c.generated.h" +#endif + +#define strneq(a, b, n) (strncmp(a, b, n) == 0) + +// Some convenient wrappers to make callback functions easier + +static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos) +{ + VTermGlyphInfo info = { + .schar = schar, + .width = width, + .protected_cell = state->protected_cell, + .dwl = state->lineinfo[pos.row].doublewidth, + .dhl = state->lineinfo[pos.row].doubleheight, + }; + + if (state->callbacks && state->callbacks->putglyph) { + if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) +{ + if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) { + return; + } + + if (cancel_phantom) { + state->at_phantom = 0; + } + + if (state->callbacks && state->callbacks->movecursor) { + if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, + state->cbdata)) { + return; + } + } +} + +static void erase(VTermState *state, VTermRect rect, int selective) +{ + if (rect.end_col == state->cols) { + // If we're erasing the final cells of any lines, cancel the continuation marker on the + // subsequent line + for (int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) { + state->lineinfo[row].continuation = 0; + } + } + + if (state->callbacks && state->callbacks->erase) { + if ((*state->callbacks->erase)(rect, selective, state->cbdata)) { + return; + } + } +} + +static VTermState *vterm_state_new(VTerm *vt) +{ + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + + state->selection.callbacks = NULL; + state->selection.user = NULL; + state->selection.buffer = NULL; + + vterm_state_newpen(state); + + state->bold_is_highbright = 0; + + state->combine_pos.row = -1; + + state->tabstops = vterm_allocator_malloc(state->vt, ((size_t)state->cols + 7) / 8); + + state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, + (size_t)state->rows * + sizeof(VTermLineInfo)); + // TODO(vterm): Make an 'enable' function + state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, + (size_t)state->rows * + sizeof(VTermLineInfo)); + state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if (*state->encoding_utf8.enc->init) { + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + } + + return state; +} + +void vterm_state_free(VTermState *state) +{ + vterm_allocator_free(state->vt, state->tabstops); + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); + if (state->lineinfos[BUFIDX_ALTSCREEN]) { + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); + } + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) +{ + if (!downward && !rightward) { + return; + } + + int rows = rect.end_row - rect.start_row; + if (downward > rows) { + downward = rows; + } else if (downward < -rows) { + downward = -rows; + } + + int cols = rect.end_col - rect.start_col; + if (rightward > cols) { + rightward = cols; + } else if (rightward < -cols) { + rightward = -cols; + } + + // Update lineinfo if full line + if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { + int height = rect.end_row - rect.start_row - abs(downward); + + if (downward > 0) { + memmove(state->lineinfo + rect.start_row, + state->lineinfo + rect.start_row + downward, + (size_t)height * sizeof(state->lineinfo[0])); + for (int row = rect.end_row - downward; row < rect.end_row; row++) { + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + } else { + memmove(state->lineinfo + rect.start_row - downward, + state->lineinfo + rect.start_row, + (size_t)height * sizeof(state->lineinfo[0])); + for (int row = rect.start_row; row < rect.start_row - downward; row++) { + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + } + } + + if (state->callbacks && state->callbacks->scrollrect) { + if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) { + return; + } + } + + if (state->callbacks) { + vterm_scroll_rect(rect, downward, rightward, + state->callbacks->moverect, state->callbacks->erase, state->cbdata); + } +} + +static void linefeed(VTermState *state) +{ + if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, 1, 0); + } else if (state->pos.row < state->rows - 1) { + state->pos.row++; + } +} + +static void set_col_tabstop(VTermState *state, int col) +{ + uint8_t mask = (uint8_t)(1 << (col & 7)); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) +{ + uint8_t mask = (uint8_t)(1 << (col & 7)); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) +{ + uint8_t mask = (uint8_t)(1 << (col & 7)); + return state->tabstops[col >> 3] & mask; +} + +static int is_cursor_in_scrollregion(const VTermState *state) +{ + if (state->pos.row < state->scrollregion_top + || state->pos.row >= SCROLLREGION_BOTTOM(state)) { + return 0; + } + if (state->pos.col < SCROLLREGION_LEFT(state) + || state->pos.col >= SCROLLREGION_RIGHT(state)) { + return 0; + } + + return 1; +} + +static void tab(VTermState *state, int count, int direction) +{ + while (count > 0) { + if (direction > 0) { + if (state->pos.col >= THISROWWIDTH(state) - 1) { + return; + } + + state->pos.col++; + } else if (direction < 0) { + if (state->pos.col < 1) { + return; + } + + state->pos.col--; + } + + if (is_col_tabstop(state, state->pos.col)) { + count--; + } + } +} + +#define NO_FORCE 0 +#define FORCE 1 + +#define DWL_OFF 0 +#define DWL_ON 1 + +#define DHL_OFF 0 +#define DHL_TOP 1 +#define DHL_BOTTOM 2 + +static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) +{ + VTermLineInfo info = state->lineinfo[row]; + + if (dwl == DWL_OFF) { + info.doublewidth = DWL_OFF; + } else if (dwl == DWL_ON) { + info.doublewidth = DWL_ON; + } + // else -1 to ignore + + if (dhl == DHL_OFF) { + info.doubleheight = DHL_OFF; + } else if (dhl == DHL_TOP) { + info.doubleheight = DHL_TOP; + } else if (dhl == DHL_BOTTOM) { + info.doubleheight = DHL_BOTTOM; + } + + if ((state->callbacks + && state->callbacks->setlineinfo + && (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) + || force) { + state->lineinfo[row] = info; + } +} + +static int on_text(const char bytes[], size_t len, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); + size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); + + int npoints = 0; + size_t eaten = 0; + + VTermEncodingInstance *encoding = + state->gsingle_set ? &state->encoding[state->gsingle_set] + : !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] + : state->vt->mode.utf8 ? &state->encoding_utf8 + : &state->encoding[state-> + gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, + codepoints, &npoints, state->gsingle_set ? 1 : (int)maxpoints, + bytes, &eaten, len); + + // There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet for even a single codepoint + if (!npoints) { + return (int)eaten; + } + + if (state->gsingle_set && npoints) { + state->gsingle_set = 0; + } + + int i = 0; + GraphemeState grapheme_state = GRAPHEME_STATE_INIT; + size_t grapheme_len = 0; + bool recombine = false; + + // See if the cursor has moved since + if (state->pos.row == state->combine_pos.row + && state->pos.col == state->combine_pos.col + state->combine_width) { + // This is a combining char. that needs to be merged with the previous glyph output + if (utf_iscomposing((int)state->grapheme_last, (int)codepoints[i], &state->grapheme_state)) { + // Find where we need to append these combining chars + grapheme_len = state->grapheme_len; + grapheme_state = state->grapheme_state; + state->pos.col = state->combine_pos.col; + recombine = true; + } else { + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); + } + } + + while (i < npoints) { + // Try to find combining characters following this + do { + if (grapheme_len < sizeof(state->grapheme_buf) - 4) { + grapheme_len += (size_t)utf_char2bytes((int)codepoints[i], + state->grapheme_buf + grapheme_len); + } + i++; + } while (i < npoints && utf_iscomposing((int)codepoints[i - 1], (int)codepoints[i], + &grapheme_state)); + + int width = utf_ptr2cells_len(state->grapheme_buf, (int)grapheme_len); + + if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + state->lineinfo[state->pos.row].continuation = 1; + } + + if (state->mode.insert && !recombine) { + // TODO(vterm): This will be a little inefficient for large bodies of text, as it'll have to + // 'ICH' effectively before every glyph. We should scan ahead and ICH as many times as + // required + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = THISROWWIDTH(state), + }; + scroll(state, rect, 0, -1); + } + + schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len); + putglyph(state, sc, width, state->pos); + + if (i == npoints) { + // End of the buffer. Save the chars in case we have to combine with more on the next call + state->grapheme_len = grapheme_len; + state->grapheme_last = codepoints[i - 1]; + state->grapheme_state = grapheme_state; + state->combine_width = width; + state->combine_pos = state->pos; + } else { + grapheme_len = 0; + recombine = false; + } + + if (state->pos.col + width >= THISROWWIDTH(state)) { + if (state->mode.autowrap) { + state->at_phantom = 1; + } + } else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + +#ifdef DEBUG + if (state->pos.row < 0 || state->pos.row >= state->rows + || state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", + state->pos.row, state->pos.col); + abort(); + } +#endif + + return (int)eaten; +} + +static int on_control(uint8_t control, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch (control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if (state->callbacks && state->callbacks->bell) { + (*state->callbacks->bell)(state->cbdata); + } + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if (state->pos.col > 0) { + state->pos.col--; + } + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if (state->mode.newline) { + state->pos.col = 0; + } + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if (state->pos.row == state->scrollregion_top) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, -1, 0); + } else if (state->pos.row > 0) { + state->pos.row--; + } + break; + + case 0x8e: // SS2 - ECMA-48 8.3.141 + state->gsingle_set = 2; + break; + + case 0x8f: // SS3 - ECMA-48 8.3.142 + state->gsingle_set = 3; + break; + + default: + if (state->fallbacks && state->fallbacks->control) { + if ((*state->fallbacks->control)(control, state->fbdata)) { + return 1; + } + } + + return 0; + } + + updatecursor(state, &oldpos, 1); + +#ifdef DEBUG + if (state->pos.row < 0 || state->pos.row >= state->rows + || state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", + control, state->pos.row, state->pos.col); + abort(); + } +#endif + + return 1; +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .boolean = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .number = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) +{ + VTermValue val = { .string = frag }; + return vterm_state_set_termprop(state, prop, &val); +} + +static void savecursor(VTermState *state, int save) +{ + if (save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) +{ + VTermState *state = user; + + // Easier to decode this from the first byte, even though the final byte terminates it + switch (bytes[0]) { + case ' ': + if (len != 2) { + return 0; + } + + switch (bytes[1]) { + case 'F': // S7C1T + state->vt->mode.ctrl8bit = 0; + break; + + case 'G': // S8C1T + state->vt->mode.ctrl8bit = 1; + break; + + default: + return 0; + } + return 2; + + case '#': + if (len != 2) { + return 0; + } + + switch (bytes[1]) { + case '3': // DECDHL top + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); + break; + + case '4': // DECDHL bottom + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); + break; + + case '5': // DECSWL + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); + break; + + case '6': // DECDWL + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); + break; + + case '8': // DECALN + { + VTermPos pos; + schar_T E = schar_from_ascii('E'); // E + for (pos.row = 0; pos.row < state->rows; pos.row++) { + for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) { + putglyph(state, E, 1, pos); + } + } + break; + } + + default: + return 0; + } + return 2; + + case '(': + case ')': + case '*': + case '+': // SCS + if (len != 2) { + return 0; + } + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if (newenc) { + state->encoding[setnum].enc = newenc; + + if (newenc->init) { + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if (state->callbacks && state->callbacks->movecursor) { + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, + state->cbdata); + } + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + case '~': // LS1R - ECMA-48 8.3.77 + state->gr_set = 1; + return 1; + + case '}': // LS2R - ECMA-48 8.3.79 + state->gr_set = 2; + return 1; + + case '|': // LS3R - ECMA-48 8.3.81 + state->gr_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) +{ + switch (num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = (unsigned)val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = (unsigned)val; + break; + + default: + DEBUG_LOG("libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) +{ + switch (num) { + case 1: + state->mode.cursor = (unsigned)val; + break; + + case 5: // DECSCNM - screen mode + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = (unsigned)val; + state->pos.row = state->mode.origin ? state->scrollregion_top : 0; + state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; + updatecursor(state, &oldpos, 1); + } + break; + + case 7: + state->mode.autowrap = (unsigned)val; + break; + + case 12: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 69: // DECVSSM - vertical split screen mode + // DECLRMM - left/right margin mode + state->mode.leftrightmargin = (unsigned)val; + if (val) { + // Setting DECVSSM must clear doublewidth/doubleheight state of every line + for (int row = 0; row < state->rows; row++) { + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + } + + break; + + case 1000: + case 1002: + case 1003: + settermprop_int(state, VTERM_PROP_MOUSE, + !val ? VTERM_PROP_MOUSE_NONE + : (num == 1000) ? VTERM_PROP_MOUSE_CLICK + : (num == 1002) ? VTERM_PROP_MOUSE_DRAG + : VTERM_PROP_MOUSE_MOVE); + break; + + case 1004: + settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val); + state->mode.report_focus = (unsigned)val; + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + savecursor(state, val); + break; + + case 2004: + state->mode.bracketpaste = (unsigned)val; + break; + + default: + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static void request_dec_mode(VTermState *state, int num) +{ + int reply; + + switch (num) { + case 1: + reply = state->mode.cursor; + break; + + case 5: + reply = state->mode.screen; + break; + + case 6: + reply = state->mode.origin; + break; + + case 7: + reply = state->mode.autowrap; + break; + + case 12: + reply = state->mode.cursor_blink; + break; + + case 25: + reply = state->mode.cursor_visible; + break; + + case 69: + reply = state->mode.leftrightmargin; + break; + + case 1000: + reply = state->mouse_flags == MOUSE_WANT_CLICK; + break; + + case 1002: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); + break; + + case 1003: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); + break; + + case 1004: + reply = state->mode.report_focus; + break; + + case 1005: + reply = state->mouse_protocol == MOUSE_UTF8; + break; + + case 1006: + reply = state->mouse_protocol == MOUSE_SGR; + break; + + case 1015: + reply = state->mouse_protocol == MOUSE_RXVT; + break; + + case 1047: + reply = state->mode.alt_screen; + break; + + case 2004: + reply = state->mode.bracketpaste; + break; + + default: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); + return; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); +} + +static void request_version_string(VTermState *state) +{ + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", + VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user) +{ + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + int cancel_phantom = 1; + + if (leader && leader[0]) { + if (leader[1]) { // longer than 1 char + return 0; + } + + switch (leader[0]) { + case '?': + case '>': + leader_byte = (int)leader[0]; + break; + default: + return 0; + } + } + + if (intermed && intermed[0]) { + if (intermed[1]) { // longer than 1 char + return 0; + } + + switch (intermed[0]) { + case ' ': + case '!': + case '"': + case '$': + case '\'': + intermed_byte = (int)intermed[0]; + break; + default: + return 0; + } + } + + VTermPos oldpos = state->pos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + int selective; + +#define LBOUND(v, min) if ((v) < (min))(v) = (min) +#define UBOUND(v, max) if ((v) > (max))(v) = (max) + +#define LEADER(l, b) ((l << 8) | b) +#define INTERMED(i, b) ((i << 16) | b) + + switch (intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) { + rect.end_col = SCROLLREGION_RIGHT(state); + } else { + rect.end_col = THISROWWIDTH(state); + } + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val - 1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + case LEADER('?', 0x4a): // DECSED - Selective Erase in Display + selective = (leader_byte == '?'); + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; rect.end_col = state->cols; + if (rect.end_col > rect.start_col) { + erase(state, rect, selective); + } + + rect.start_row = state->pos.row + 1; rect.end_row = state->rows; + rect.start_col = 0; + for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { + set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); + } + if (rect.end_row > rect.start_row) { + erase(state, rect, selective); + } + break; + + case 1: + rect.start_row = 0; rect.end_row = state->pos.row; + rect.start_col = 0; rect.end_col = state->cols; + for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { + set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); + } + if (rect.end_col > rect.start_col) { + erase(state, rect, selective); + } + + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if (rect.end_row > rect.start_row) { + erase(state, rect, selective); + } + break; + + case 2: + rect.start_row = 0; rect.end_row = state->rows; + rect.start_col = 0; rect.end_col = state->cols; + for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { + set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); + } + erase(state, rect, selective); + break; + + case 3: + if (state->callbacks && state->callbacks->sb_clear) { + if ((*state->callbacks->sb_clear)(state->cbdata)) { + return 1; + } + } + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line + selective = (leader_byte == '?'); + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; + case 1: + rect.start_col = 0; rect.end_col = state->pos.col + 1; break; + case 2: + rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; + default: + return 0; + } + + if (rect.end_col > rect.start_col) { + erase(state, rect, selective); + } + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) { + rect.end_col = SCROLLREGION_RIGHT(state); + } else { + rect.end_col = THISROWWIDTH(state); + } + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + UBOUND(rect.end_col, THISROWWIDTH(state)); + + erase(state, rect, 0); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col - 1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len); + while (state->pos.col < col) { + putglyph(state, sc, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if (val == 0) { + // DEC VT100 response + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + } + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + } + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for (col = 0; col < state->cols; col++) { + clear_col_tabstop(state, col); + } + break; + case 1: + case 2: + case 4: + break; + // TODO(vterm): 1, 2 and 4 aren't meaningful yet without line tab stops + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if (!CSI_ARG_IS_MISSING(args[0])) { + set_mode(state, CSI_ARG(args[0]), 1); + } + break; + + case LEADER('?', 0x68): // DEC private mode set + for (int i = 0; i < argcount; i++) { + if (!CSI_ARG_IS_MISSING(args[i])) { + set_dec_mode(state, CSI_ARG(args[i]), 1); + } + } + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if (!CSI_ARG_IS_MISSING(args[0])) { + set_mode(state, CSI_ARG(args[0]), 0); + } + break; + + case LEADER('?', 0x6c): // DEC private mode reset + for (int i = 0; i < argcount; i++) { + if (!CSI_ARG_IS_MISSING(args[i])) { + set_dec_mode(state, CSI_ARG(args[i]), 0); + } + } + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case LEADER('?', 0x6d): // DECSGR + // No actual DEC terminal recognised these, but some printers did. These are alternative ways to + // request subscript/superscript/off + for (int argi = 0; argi < argcount; argi++) { + long arg; + switch (arg = CSI_ARG(args[argi])) { + case 4: // Superscript on + arg = 73; + vterm_state_setpen(state, &arg, 1); + break; + case 5: // Subscript on + arg = 74; + vterm_state_setpen(state, &arg, 1); + break; + case 24: // Super+subscript off + arg = 75; + vterm_state_setpen(state, &arg, 1); + break; + } + } + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + case LEADER('?', 0x6e): // DECDSR + val = CSI_ARG_OR(args[0], 0); + + { + char *qmark = (leader_byte == '?') ? "?" : ""; + + switch (val) { + case 0: + case 1: + case 2: + case 3: + case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); + break; + case 6: // CPR - cursor position report + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, + state->pos.col + 1); + break; + } + } + break; + + case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case LEADER('?', INTERMED('$', 0x70)): + request_dec_mode(state, CSI_ARG(args[0])); + break; + + case LEADER('>', 0x71): // XTVERSION - xterm query version string + request_version_string(state); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch (val) { + case 0: + case 1: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 2: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 3: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 4: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 5: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + case 6: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + } + + break; + + case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + case 2: + state->protected_cell = 0; + break; + case 1: + state->protected_cell = 1; + break; + } + + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_bottom = argcount < 2 + || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_top, 0); + UBOUND(state->scrollregion_top, state->rows); + LBOUND(state->scrollregion_bottom, -1); + if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) { + state->scrollregion_bottom = -1; + } else { + UBOUND(state->scrollregion_bottom, state->rows); + } + + if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case 0x73: // DECSLRM - DEC custom + // Always allow setting these margins, just they won't take effect without DECVSSM + state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_left, 0); + UBOUND(state->scrollregion_left, state->cols); + LBOUND(state->scrollregion_right, -1); + if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) { + state->scrollregion_right = -1; + } else { + UBOUND(state->scrollregion_right, state->cols); + } + + if (state->scrollregion_right > -1 + && state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case INTERMED('\'', 0x7D): // DECIC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, -count); + + break; + + case INTERMED('\'', 0x7E): // DECDC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, count); + + break; + + default: + if (state->fallbacks && state->fallbacks->csi) { + if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) { + return 1; + } + } + + return 0; + } + + if (state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_top); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1); + LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); + UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1); + } else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows - 1); + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, THISROWWIDTH(state) - 1); + } + + updatecursor(state, &oldpos, cancel_phantom); + +#ifdef DEBUG + if (state->pos.row < 0 || state->pos.row >= state->rows + || state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", + command, state->pos.row, state->pos.col); + abort(); + } + + if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); + abort(); + } + + if (SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { + fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); + abort(); + } +#endif + + return 1; +} + +static uint8_t unbase64one(char c) +{ + if (c >= 'A' && c <= 'Z') { + return (uint8_t)c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return (uint8_t)c - 'a' + 26; + } else if (c >= '0' && c <= '9') { + return (uint8_t)c - '0' + 52; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } + + return 0xFF; +} + +static void osc_selection(VTermState *state, VTermStringFragment frag) +{ + if (frag.initial) { + state->tmp.selection.mask = 0; + state->tmp.selection.state = SELECTION_INITIAL; + } + + while (!state->tmp.selection.state && frag.len) { + // Parse selection parameter + switch (frag.str[0]) { + case 'c': + state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; + break; + case 'p': + state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; + break; + case 'q': + state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; + break; + case 's': + state->tmp.selection.mask |= VTERM_SELECTION_SELECT; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); + break; + + case ';': + state->tmp.selection.state = SELECTION_SELECTED; + if (!state->tmp.selection.mask) { + state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; + } + break; + } + + frag.str++; + frag.len--; + } + + if (!frag.len) { + // Clear selection if we're already finished but didn't do anything + if (frag.final && state->selection.callbacks->set) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = NULL, + .len = 0, + .initial = state->tmp.selection.state != SELECTION_SET, + .final = true, + }, state->selection.user); + } + return; + } + + if (state->tmp.selection.state == SELECTION_SELECTED) { + if (frag.str[0] == '?') { + state->tmp.selection.state = SELECTION_QUERY; + } else { + state->tmp.selection.state = SELECTION_SET_INITIAL; + state->tmp.selection.recvpartial = 0; + } + } + + if (state->tmp.selection.state == SELECTION_QUERY) { + if (state->selection.callbacks->query) { + (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); + } + return; + } + + if (state->tmp.selection.state == SELECTION_INVALID) { + return; + } + + if (state->selection.callbacks->set) { + size_t bufcur = 0; + char *buffer = state->selection.buffer; + + uint32_t x = 0; // Current decoding value + int n = 0; // Number of sextets consumed + + if (state->tmp.selection.recvpartial) { + n = state->tmp.selection.recvpartial >> 24; + x = state->tmp.selection.recvpartial & 0x03FFFF; // could be up to 18 bits of state in here + + state->tmp.selection.recvpartial = 0; + } + + while ((state->selection.buflen - bufcur) >= 3 && frag.len) { + if (frag.str[0] == '=') { + if (n == 2) { + buffer[0] = (char)(x >> 4 & 0xFF); + buffer += 1, bufcur += 1; + } + if (n == 3) { + buffer[0] = (char)(x >> 10 & 0xFF); + buffer[1] = (char)(x >> 2 & 0xFF); + buffer += 2, bufcur += 2; + } + + while (frag.len && frag.str[0] == '=') { + frag.str++, frag.len--; + } + + n = 0; + } else { + uint8_t b = unbase64one(frag.str[0]); + if (b == 0xFF) { + DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); + + state->tmp.selection.state = SELECTION_INVALID; + if (state->selection.callbacks->set) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = NULL, + .len = 0, + .initial = true, + .final = true, + }, state->selection.user); + } + break; + } + + x = (x << 6) | b; + n++; + frag.str++, frag.len--; + + if (n == 4) { + buffer[0] = (char)(x >> 16 & 0xFF); + buffer[1] = (char)(x >> 8 & 0xFF); + buffer[2] = (char)(x >> 0 & 0xFF); + + buffer += 3, bufcur += 3; + x = 0; + n = 0; + } + } + + if (!frag.len || (state->selection.buflen - bufcur) < 3) { + if (bufcur) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = state->selection.buffer, + .len = bufcur, + .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, + .final = frag.final && !frag.len, + }, state->selection.user); + state->tmp.selection.state = SELECTION_SET; + } + + buffer = state->selection.buffer; + bufcur = 0; + } + } + + if (n) { + state->tmp.selection.recvpartial = (uint32_t)(n << 24) | x; + } + } +} + +static int on_osc(int command, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + switch (command) { + case 0: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 1: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + return 1; + + case 2: + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 52: + if (state->selection.callbacks) { + osc_selection(state, frag); + } + + return 1; + + default: + if (state->fallbacks && state->fallbacks->osc) { + if ((*state->fallbacks->osc)(command, frag, state->fbdata)) { + return 1; + } + } + } + + return 0; +} + +static void request_status_string(VTermState *state, VTermStringFragment frag) +{ + VTerm *vt = state->vt; + + char *tmp = state->tmp.decrqss; + + if (frag.initial) { + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; + } + + size_t i = 0; + while (i < sizeof(state->tmp.decrqss) - 1 && tmp[i]) { + i++; + } + while (i < sizeof(state->tmp.decrqss) - 1 && frag.len--) { + tmp[i++] = (frag.str++)[0]; + } + tmp[i] = 0; + + if (!frag.final) { + return; + } + + switch (tmp[0] | tmp[1] << 8 | tmp[2] << 16) { + case 'm': { + // Query SGR + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); + size_t cur = 0; + + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... + if (cur >= vt->tmpbuffer_len) { + return; + } + + for (int argi = 0; argi < argc; argi++) { + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + argi == argc - 1 ? "%ld" + : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" + : "%ld;", + CSI_ARG(args[argi])); + if (cur >= vt->tmpbuffer_len) { + return; + } + } + + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST + if (cur >= vt->tmpbuffer_len) { + return; + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + return; + } + + case 'r': + // Query DECSTBM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%dr", state->scrollregion_top + 1, + SCROLLREGION_BOTTOM(state)); + return; + + case 's': + // Query DECSLRM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%ds", SCROLLREGION_LEFT(state) + 1, + SCROLLREGION_RIGHT(state)); + return; + + case ' '|('q' << 8): { + // Query DECSCUSR + int reply = 0; + switch (state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: + reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: + reply = 4; break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: + reply = 6; break; + } + if (state->mode.cursor_blink) { + reply--; + } + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d q", reply); + return; + } + + case '\"'|('q' << 8): + // Query DECSCA + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d\"q", state->protected_cell ? 1 : 2); + return; + } + + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); +} + +static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (commandlen == 2 && strneq(command, "$q", 2)) { + request_status_string(state, frag); + return 1; + } else if (state->fallbacks && state->fallbacks->dcs) { + if ((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) { + return 1; + } + } + + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); + return 0; +} + +static int on_apc(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (state->fallbacks && state->fallbacks->apc) { + if ((*state->fallbacks->apc)(frag, state->fbdata)) { + return 1; + } + } + + // No DEBUG_LOG because all APCs are unhandled + return 0; +} + +static int on_pm(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (state->fallbacks && state->fallbacks->pm) { + if ((*state->fallbacks->pm)(frag, state->fbdata)) { + return 1; + } + } + + // No DEBUG_LOG because all PMs are unhandled + return 0; +} + +static int on_sos(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (state->fallbacks && state->fallbacks->sos) { + if ((*state->fallbacks->sos)(frag, state->fbdata)) { + return 1; + } + } + + // No DEBUG_LOG because all SOSs are unhandled + return 0; +} + +static int on_resize(int rows, int cols, void *user) +{ + VTermState *state = user; + VTermPos oldpos = state->pos; + + if (cols != state->cols) { + uint8_t *newtabstops = vterm_allocator_malloc(state->vt, ((size_t)cols + 7) / 8); + + // TODO(vterm): This can all be done much more efficiently bytewise + int col; + for (col = 0; col < state->cols && col < cols; col++) { + uint8_t mask = (uint8_t)(1 << (col & 7)); + if (state->tabstops[col >> 3] & mask) { + newtabstops[col >> 3] |= mask; + } else { + newtabstops[col >> 3] &= ~mask; + } + } + + for (; col < cols; col++) { + uint8_t mask = (uint8_t)(1 << (col & 7)); + if (col % 8 == 0) { + newtabstops[col >> 3] |= mask; + } else { + newtabstops[col >> 3] &= ~mask; + } + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + state->rows = rows; + state->cols = cols; + + if (state->scrollregion_bottom > -1) { + UBOUND(state->scrollregion_bottom, state->rows); + } + if (state->scrollregion_right > -1) { + UBOUND(state->scrollregion_right, state->cols); + } + + VTermStateFields fields = { + .pos = state->pos, + .lineinfos = {[0] = state->lineinfos[0], [1] = state->lineinfos[1] }, + }; + + if (state->callbacks && state->callbacks->resize) { + (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); + state->pos = fields.pos; + + state->lineinfos[0] = fields.lineinfos[0]; + state->lineinfos[1] = fields.lineinfos[1]; + } else { + if (rows != state->rows) { + for (int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { + VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; + if (!oldlineinfo) { + continue; + } + + VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, + (size_t)rows * sizeof(VTermLineInfo)); + + int row; + for (row = 0; row < state->rows && row < rows; row++) { + newlineinfo[row] = oldlineinfo[row]; + } + + for (; row < rows; row++) { + newlineinfo[row] = (VTermLineInfo){ + .doublewidth = 0, + }; + } + + vterm_allocator_free(state->vt, state->lineinfos[bufidx]); + state->lineinfos[bufidx] = newlineinfo; + } + } + } + + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + + if (state->at_phantom && state->pos.col < cols - 1) { + state->at_phantom = 0; + state->pos.col++; + } + + if (state->pos.row < 0) { + state->pos.row = 0; + } + if (state->pos.row >= rows) { + state->pos.row = rows - 1; + } + if (state->pos.col < 0) { + state->pos.col = 0; + } + if (state->pos.col >= cols) { + state->pos.col = cols - 1; + } + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .apc = on_apc, + .pm = on_pm, + .sos = on_sos, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) +{ + if (vt->state) { + return vt->state; + } + + VTermState *state = vterm_state_new(vt); + vt->state = state; + + vterm_parser_set_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) +{ + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + state->scrollregion_left = 0; + state->scrollregion_right = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.alt_screen = 0; + state->mode.origin = 0; + state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; + + state->mouse_flags = 0; + + state->vt->mode.ctrl8bit = 0; + + for (int col = 0; col < state->cols; col++) { + if (col % 8 == 0) { + set_col_tabstop(state, col); + } else { + clear_col_tabstop(state, col); + } + } + + for (int row = 0; row < state->rows; row++) { + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + + if (state->callbacks && state->callbacks->initpen) { + (*state->callbacks->initpen)(state->cbdata); + } + + vterm_state_resetpen(state); + + VTermEncoding *default_enc = state->vt->mode.utf8 + ? vterm_lookup_encoding(ENC_UTF8, 'u') + : vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for (int i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if (default_enc->init) { + (*default_enc->init)(default_enc, state->encoding[i].data); + } + } + + state->gl_set = 0; + state->gr_set = 1; + state->gsingle_set = 0; + + state->protected_cell = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + + if (hard) { + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + VTermRect rect = { 0, state->rows, 0, state->cols }; + erase(state, rect, 0); + } +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) +{ + if (callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if (state->callbacks && state->callbacks->initpen) { + (*state->callbacks->initpen)(state->cbdata); + } + } else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, + void *user) +{ + if (fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) +{ + // Only store the new value of the property if usercode said it was happy. This is especially + // important for altscreen switching + if (state->callbacks && state->callbacks->settermprop) { + if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) { + return 0; + } + } + + switch (prop) { + case VTERM_PROP_TITLE: + case VTERM_PROP_ICONNAME: + // we don't store these, just transparently pass through + return 1; + case VTERM_PROP_CURSORVISIBLE: + state->mode.cursor_visible = (unsigned)val->boolean; + return 1; + case VTERM_PROP_CURSORBLINK: + state->mode.cursor_blink = (unsigned)val->boolean; + return 1; + case VTERM_PROP_CURSORSHAPE: + state->mode.cursor_shape = (unsigned)val->number; + return 1; + case VTERM_PROP_REVERSE: + state->mode.screen = (unsigned)val->boolean; + return 1; + case VTERM_PROP_ALTSCREEN: + state->mode.alt_screen = (unsigned)val->boolean; + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + if (state->mode.alt_screen) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect, 0); + } + return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if (val->number) { + state->mouse_flags |= MOUSE_WANT_CLICK; + } + if (val->number == VTERM_PROP_MOUSE_DRAG) { + state->mouse_flags |= MOUSE_WANT_DRAG; + } + if (val->number == VTERM_PROP_MOUSE_MOVE) { + state->mouse_flags |= MOUSE_WANT_MOVE; + } + return 1; + case VTERM_PROP_FOCUSREPORT: + state->mode.report_focus = (unsigned)val->boolean; + return 1; + + case VTERM_N_PROPS: + return 0; + } + + return 0; +} + +void vterm_state_focus_in(VTermState *state) +{ + if (state->mode.report_focus) { + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); + } +} + +void vterm_state_focus_out(VTermState *state) +{ + if (state->mode.report_focus) { + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); + } +} + +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) +{ + return state->lineinfo + row; +} + +void vterm_state_set_selection_callbacks(VTermState *state, + const VTermSelectionCallbacks *callbacks, void *user, + char *buffer, size_t buflen) +{ + if (buflen && !buffer) { + buffer = vterm_allocator_malloc(state->vt, buflen); + } + + state->selection.callbacks = callbacks; + state->selection.user = user; + state->selection.buffer = buffer; + state->selection.buflen = buflen; +} diff --git a/src/nvim/vterm/state.h b/src/nvim/vterm/state.h new file mode 100644 index 0000000000..2f59cf7eec --- /dev/null +++ b/src/nvim/vterm/state.h @@ -0,0 +1,7 @@ +#pragma once + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/state.h.generated.h" +#endif diff --git a/src/nvim/vterm/vterm.c b/src/nvim/vterm/vterm.c new file mode 100644 index 0000000000..76d5dc3808 --- /dev/null +++ b/src/nvim/vterm/vterm.c @@ -0,0 +1,335 @@ +#include +#include +#include +#include + +#include "auto/config.h" +#include "nvim/memory.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/state.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/vterm.c.generated.h" +#endif + +// ***************** +// * API functions * +// ***************** + +static void *default_malloc(size_t size, void *allocdata) +{ + void *ptr = xmalloc(size); + if (ptr) { + memset(ptr, 0, size); + } + return ptr; +} + +static void default_free(void *ptr, void *allocdata) +{ + xfree(ptr); +} + +static VTermAllocatorFunctions default_allocator = { + .malloc = &default_malloc, + .free = &default_free, +}; + +/// Convenient shortcut for default cases +VTerm *vterm_new(int rows, int cols) +{ + return vterm_build(&(const struct VTermBuilder){ + .rows = rows, + .cols = cols, + }); +} + +// A handy macro for defaulting values out of builder fields +#define DEFAULT(v, def) ((v) ? (v) : (def)) + +VTerm *vterm_build(const struct VTermBuilder *builder) +{ + const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator); + + // Need to bootstrap using the allocator function directly + VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata); + + vt->allocator = allocator; + vt->allocdata = builder->allocdata; + + vt->rows = builder->rows; + vt->cols = builder->cols; + + vt->parser.state = NORMAL; + + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.emit_nul = false; + + vt->outfunc = NULL; + vt->outdata = NULL; + + vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096); + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096); + vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) +{ + if (vt->screen) { + vterm_screen_free(vt->screen); + } + + if (vt->state) { + vterm_state_free(vt->state); + } + + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); + + vterm_allocator_free(vt, vt); +} + +void *vterm_allocator_malloc(VTerm *vt, size_t size) +{ + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +void vterm_allocator_free(VTerm *vt, void *ptr) +{ + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) +{ + if (rowsp) { + *rowsp = vt->rows; + } + if (colsp) { + *colsp = vt->cols; + } +} + +void vterm_set_size(VTerm *vt, int rows, int cols) +{ + if (rows < 1 || cols < 1) { + return; + } + + vt->rows = rows; + vt->cols = cols; + + if (vt->parser.callbacks && vt->parser.callbacks->resize) { + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); + } +} + +void vterm_set_utf8(VTerm *vt, int is_utf8) +{ + vt->mode.utf8 = (unsigned)is_utf8; +} + +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) +{ + vt->outfunc = func; + vt->outdata = user; +} + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) +{ + if (vt->outfunc) { + (vt->outfunc)(bytes, len, vt->outdata); + return; + } + + if (len > vt->outbuffer_len - vt->outbuffer_cur) { + return; + } + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) + FUNC_ATTR_PRINTF(2, 3) +{ + va_list args; + va_start(args, format); + size_t len = (size_t)vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, format, args); + vterm_push_output_bytes(vt, vt->tmpbuffer, len); + va_end(args); +} + +void vterm_push_output_sprintf_ctrl(VTerm *vt, uint8_t ctrl, const char *fmt, ...) + FUNC_ATTR_PRINTF(3, 4) +{ + size_t cur; + + if (ctrl >= 0x80 && !vt->mode.ctrl8bit) { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); + } else { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); + } + + if (cur >= vt->tmpbuffer_len) { + return; + } + + va_list args; + va_start(args, fmt); + cur += (size_t)vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) { + return; + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +void vterm_push_output_sprintf_str(VTerm *vt, uint8_t ctrl, bool term, const char *fmt, ...) + FUNC_ATTR_PRINTF(4, 5) +{ + size_t cur = 0; + + if (ctrl) { + if (ctrl >= 0x80 && !vt->mode.ctrl8bit) { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); + } else { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); + } + + if (cur >= vt->tmpbuffer_len) { + return; + } + } + + va_list args; + va_start(args, fmt); + cur += (size_t)vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) { + return; + } + + if (term) { + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST + + if (cur >= vt->tmpbuffer_len) { + return; + } + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) +{ + switch (attr) { + case VTERM_ATTR_BOLD: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: + return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_CONCEAL: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: + return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: + return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: + return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_SMALL: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BASELINE: + return VTERM_VALUETYPE_INT; + case VTERM_ATTR_URI: + return VTERM_VALUETYPE_INT; + + case VTERM_N_ATTRS: + return 0; + } + return 0; // UNREACHABLE +} + +void vterm_scroll_rect(VTermRect rect, int downward, int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), void *user) +{ + VTermRect src; + VTermRect dest; + + if (abs(downward) >= rect.end_row - rect.start_row + || abs(rightward) >= rect.end_col - rect.start_col) { + // Scroll more than area; just erase the lot + (*eraserect)(rect, 0, user); + return; + } + + if (rightward >= 0) { + // rect: [XXX................] + // src: [----------------] + // dest: [----------------] + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } else { + // rect: [................XXX] + // src: [----------------] + // dest: [----------------] + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if (downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if (moverect) { + (*moverect)(dest, src, user); + } + + if (downward > 0) { + rect.start_row = rect.end_row - downward; + } else if (downward < 0) { + rect.end_row = rect.start_row - downward; + } + + if (rightward > 0) { + rect.start_col = rect.end_col - rightward; + } else if (rightward < 0) { + rect.end_col = rect.start_col - rightward; + } + + (*eraserect)(rect, 0, user); +} diff --git a/src/nvim/vterm/vterm.h b/src/nvim/vterm/vterm.h new file mode 100644 index 0000000000..e66f40425a --- /dev/null +++ b/src/nvim/vterm/vterm.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include + +#include "nvim/macros_defs.h" +#include "nvim/types_defs.h" +#include "nvim/vterm/vterm_defs.h" +#include "nvim/vterm/vterm_keycodes_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/vterm.h.generated.h" +#endif + +#define VTERM_VERSION_MAJOR 0 +#define VTERM_VERSION_MINOR 3 + +// move a rect +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +// Bit-field describing the content of the tagged union `VTermColor`. +typedef enum { + // If the lower bit of `type` is not set, the colour is 24-bit RGB. + VTERM_COLOR_RGB = 0x00, + + // The colour is an index into a palette of 256 colours. + VTERM_COLOR_INDEXED = 0x01, + + // Mask that can be used to extract the RGB/Indexed bit. + VTERM_COLOR_TYPE_MASK = 0x01, + + // If set, indicates that this colour should be the default foreground color, i.e. there was no + // SGR request for another colour. When rendering this colour it is possible to ignore "idx" and + // just use a colour that is not in the palette. + VTERM_COLOR_DEFAULT_FG = 0x02, + + // If set, indicates that this colour should be the default background color, i.e. there was no + // SGR request for another colour. A common option when rendering this colour is to not render a + // background at all, for example by rendering the window transparently at this spot. + VTERM_COLOR_DEFAULT_BG = 0x04, + + // Mask that can be used to extract the default foreground/background bit. + VTERM_COLOR_DEFAULT_MASK = 0x06, +} VTermColorType; + +// Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the given VTermColor +// instance is an indexed colour. +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +// Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that the given VTermColor +// instance is an rgb colour. +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +// Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating that the given +// VTermColor instance corresponds to the default foreground color. +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +// Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating that the given +// VTermColor instance corresponds to the default background color. +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +// Constructs a new VTermColor instance representing the given RGB values. +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +// Construct a new VTermColor instance representing an indexed color with the given index. +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +// ------------ +// Parser layer +// ------------ + +/// Flag to indicate non-final subparameters in a single CSI parameter. +/// Consider +/// CSI 1;2:3:4;5a +/// 1 4 and 5 are final. +/// 2 and 3 are non-final and will have this bit set +/// +/// Don't confuse this with the final byte of the CSI escape; 'a' in this case. +#define CSI_ARG_FLAG_MORE (1U << 31) +#define CSI_ARG_MASK (~(1U << 31)) + +#define CSI_ARG_HAS_MORE(a) ((a)& CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a)& CSI_ARG_MASK) + +// Can't use -1 to indicate a missing argument; use this instead +#define CSI_ARG_MISSING ((1UL<<31) - 1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a, def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +enum { + VTERM_UNDERLINE_OFF, + VTERM_UNDERLINE_SINGLE, + VTERM_UNDERLINE_DOUBLE, + VTERM_UNDERLINE_CURLY, +}; + +enum { + VTERM_BASELINE_NORMAL, + VTERM_BASELINE_RAISE, + VTERM_BASELINE_LOWER, +}; + +// Back-compat alias for the brief time it was in 0.3-RC1 +#define vterm_screen_set_reflow vterm_screen_enable_reflow + +void vterm_scroll_rect(VTermRect rect, int downward, int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), void *user); + +struct VTermScreen { + VTerm *vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void *cbdata; + + VTermDamageSize damage_merge; + // start_row == -1 => no damage + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + + unsigned global_reverse : 1; + unsigned reflow : 1; + + // Primary and Altscreen. buffers[1] is lazily allocated as needed + ScreenCell *buffers[2]; + + // buffer will == buffers[0] or buffers[1], depending on altscreen + ScreenCell *buffer; + + // buffer for a single screen row used in scrollback storage callbacks + VTermScreenCell *sb_buffer; + + ScreenPen pen; +}; diff --git a/src/nvim/vterm/vterm_defs.h b/src/nvim/vterm/vterm_defs.h new file mode 100644 index 0000000000..d0a8ba8814 --- /dev/null +++ b/src/nvim/vterm/vterm_defs.h @@ -0,0 +1,319 @@ +#pragma once +#include +#include +#include + +#include "nvim/types_defs.h" + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +// some small utility functions; we can just keep these static here + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +// Tagged union storing either an RGB color or an index into a colour palette. In order to convert +// indexed colours to RGB, you may use the vterm_state_convert_color_to_rgb() or +// vterm_screen_convert_color_to_rgb() functions which lookup the RGB colour from the palette +// maintained by a VTermState or VTermScreen instance. +typedef union { + // Tag indicating which union member is actually valid. This variable coincides with the `type` + // member of the `rgb` and the `indexed` struct in memory. Please use the `VTERM_COLOR_IS_*` test + // macros to check whether a particular type flag is set. + uint8_t type; + + // Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + struct { + // Same as the top-level `type` member stored in VTermColor. + uint8_t type; + + // The actual 8-bit red, green, blue colour values. + uint8_t red, green, blue; + } rgb; + + // If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into the colour palette. + struct { + // Same as the top-level `type` member stored in VTermColor. + uint8_t type; + + // Index into the colour map. + uint8_t idx; + } indexed; +} VTermColor; + +typedef struct { + unsigned bold : 1; + unsigned underline : 2; + unsigned italic : 1; + unsigned blink : 1; + unsigned reverse : 1; + unsigned conceal : 1; + unsigned strike : 1; + unsigned font : 4; // 0 to 9 + unsigned dwl : 1; // On a DECDWL or DECDHL line + unsigned dhl : 2; // On a DECDHL line (1=top 2=bottom) + unsigned small : 1; + unsigned baseline : 2; +} VTermScreenCellAttrs; + +typedef struct { + schar_T schar; + char width; + VTermScreenCellAttrs attrs; + VTermColor fg, bg; + int uri; +} VTermScreenCell; + +typedef enum { + // VTERM_PROP_NONE = 0 + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + VTERM_PROP_FOCUSREPORT, // bool + + VTERM_N_PROPS, +} VTermProp; + +typedef struct { + const char *str; + size_t len : 30; + bool initial : 1; + bool final : 1; +} VTermStringFragment; + +typedef union { + int boolean; + int number; + VTermStringFragment string; + VTermColor color; +} VTermValue; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); + int (*sb_clear)(void *user); +} VTermScreenCallbacks; + +typedef struct { + int (*control)(uint8_t control, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); +} VTermStateFallbacks; + +typedef enum { + VTERM_DAMAGE_CELL, // every cell + VTERM_DAMAGE_ROW, // entire rows + VTERM_DAMAGE_SCREEN, // entire screen + VTERM_DAMAGE_SCROLL, // entire screen + scrollrect + + VTERM_N_DAMAGES, +} VTermDamageSize; + +typedef enum { + VTERM_ATTR_BOLD_MASK = 1 << 0, + VTERM_ATTR_UNDERLINE_MASK = 1 << 1, + VTERM_ATTR_ITALIC_MASK = 1 << 2, + VTERM_ATTR_BLINK_MASK = 1 << 3, + VTERM_ATTR_REVERSE_MASK = 1 << 4, + VTERM_ATTR_STRIKE_MASK = 1 << 5, + VTERM_ATTR_FONT_MASK = 1 << 6, + VTERM_ATTR_FOREGROUND_MASK = 1 << 7, + VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + VTERM_ATTR_CONCEAL_MASK = 1 << 9, + VTERM_ATTR_SMALL_MASK = 1 << 10, + VTERM_ATTR_BASELINE_MASK = 1 << 11, + VTERM_ATTR_URI_MASK = 1 << 12, + + VTERM_ALL_ATTRS_MASK = (1 << 13) - 1, +} VTermAttrMask; + +typedef enum { + // VTERM_VALUETYPE_NONE = 0 + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES, +} VTermValueType; + +typedef enum { + // VTERM_ATTR_NONE = 0 + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_CONCEAL, // bool: 8, 28 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + VTERM_ATTR_SMALL, // bool: 73, 74, 75 + VTERM_ATTR_BASELINE, // number: 73, 74, 75 + VTERM_ATTR_URI, // number + + VTERM_N_ATTRS, +} VTermAttr; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, + VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES, +}; + +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES, +}; + +typedef enum { + VTERM_SELECTION_CLIPBOARD = (1<<0), + VTERM_SELECTION_PRIMARY = (1<<1), + VTERM_SELECTION_SECONDARY = (1<<2), + VTERM_SELECTION_SELECT = (1<<3), + VTERM_SELECTION_CUT0 = (1<<4), // also CUT1 .. CUT7 by bitshifting +} VTermSelectionMask; + +typedef struct { + schar_T schar; + int width; + unsigned protected_cell:1; // DECSCA-protected against DECSEL/DECSED + unsigned dwl:1; // DECDWL or DECDHL double-width line + unsigned dhl:2; // DECDHL double-height line (1=top 2=bottom) +} VTermGlyphInfo; + +typedef struct { + unsigned doublewidth:1; // DECDWL or DECDHL line + unsigned doubleheight:2; // DECDHL line (1=top 2=bottom) + unsigned continuation:1; // Line is a flow continuation of the previous +} VTermLineInfo; + +// Copies of VTermState fields that the 'resize' callback might have reason to edit. 'resize' +// callback gets total control of these fields and may free-and-reallocate them if required. They +// will be copied back from the struct after the callback has returned. +typedef struct { + VTermPos pos; // current cursor position + VTermLineInfo *lineinfos[2]; // [1] may be NULL +} VTermStateFields; + +typedef struct { + // libvterm relies on this memory to be zeroed out before it is returned by the allocator. + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +// Setting output callback will override the buffer logic +typedef void VTermOutputCallback(const char *s, size_t len, void *user); + +struct VTermBuilder { + int ver; // currently unused but reserved for some sort of ABI version flag + + int rows, cols; + + const VTermAllocatorFunctions *allocator; + void *allocdata; + + // Override default sizes for various structures + size_t outbuffer_len; // default: 4096 + size_t tmpbuffer_len; // default: 4096 +}; + +typedef struct { + int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, int selective, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); + int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, + void *user); + int (*sb_clear)(void *user); +} VTermStateCallbacks; + +typedef struct { + int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user); + int (*query)(VTermSelectionMask mask, void *user); +} VTermSelectionCallbacks; + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(uint8_t control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +// State of the pen at some moment in time, also used in a cell +typedef struct { + // After the bitfield + VTermColor fg, bg; + + // Opaque ID that maps to a URI in a set + int uri; + + unsigned bold : 1; + unsigned underline : 2; + unsigned italic : 1; + unsigned blink : 1; + unsigned reverse : 1; + unsigned conceal : 1; + unsigned strike : 1; + unsigned font : 4; // 0 to 9 + unsigned small : 1; + unsigned baseline : 2; + + // Extra state storage that isn't strictly pen-related + unsigned protected_cell : 1; + unsigned dwl : 1; // on a DECDWL or DECDHL line + unsigned dhl : 2; // on a DECDHL line (1=top 2=bottom) +} ScreenPen; + +// Internal representation of a screen cell +typedef struct { + schar_T schar; + ScreenPen pen; +} ScreenCell; diff --git a/src/nvim/vterm/vterm_internal_defs.h b/src/nvim/vterm/vterm_internal_defs.h new file mode 100644 index 0000000000..770f862ce3 --- /dev/null +++ b/src/nvim/vterm/vterm_internal_defs.h @@ -0,0 +1,266 @@ +#pragma once + +#include + +#include "nvim/mbyte_defs.h" +#include "nvim/vterm/vterm_defs.h" + +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + +#define BUFIDX_PRIMARY 0 +#define BUFIDX_ALTSCREEN 1 + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4 * sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen { + VTermColor fg; + VTermColor bg; + int uri; + unsigned bold:1; + unsigned underline:2; + unsigned italic:1; + unsigned blink:1; + unsigned reverse:1; + unsigned conceal:1; + unsigned strike:1; + unsigned font:4; // To store 0-9 + unsigned small:1; + unsigned baseline:2; +}; + +struct VTermState { + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + const VTermStateFallbacks *fallbacks; + void *fbdata; + + int rows; + int cols; + + // Current cursor position + VTermPos pos; + + int at_phantom; // True if we're on the "81st" phantom column to defer a wraparound + + int scrollregion_top; + int scrollregion_bottom; // -1 means unbounded +#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > \ + -1 ? (state)->scrollregion_bottom : (state)->rows) + int scrollregion_left; +#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) + int scrollregion_right; // -1 means unbounded +#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin \ + && (state)->scrollregion_right > \ + -1 ? (state)->scrollregion_right : (state)->cols) + + // Bitvector of tab stops + uint8_t *tabstops; + + // Primary and Altscreen; lineinfos[1] is lazily allocated as needed + VTermLineInfo *lineinfos[2]; + + // lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen + VTermLineInfo *lineinfo; +#define ROWWIDTH(state, \ + row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) +#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) + + // Mouse state + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT, } mouse_protocol; + +// Last glyph output, for Unicode recombining purposes + char grapheme_buf[MAX_SCHAR_SIZE]; + size_t grapheme_len; + uint32_t grapheme_last; // last added UTF-32 char + GraphemeState grapheme_state; + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + unsigned keypad:1; + unsigned cursor:1; + unsigned autowrap:1; + unsigned insert:1; + unsigned newline:1; + unsigned cursor_visible:1; + unsigned cursor_blink:1; + unsigned cursor_shape:2; + unsigned alt_screen:1; + unsigned origin:1; + unsigned screen:1; + unsigned leftrightmargin:1; + unsigned bracketpaste:1; + unsigned report_focus:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set, gsingle_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only + + int bold_is_highbright; + + unsigned protected_cell : 1; + +// Saved state under DEC mode 1048/1049 + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + unsigned cursor_visible:1; + unsigned cursor_blink:1; + unsigned cursor_shape:2; + } mode; + } saved; + +// Temporary state for DECRQSS parsing + union { + char decrqss[4]; + struct { + uint16_t mask; + enum { + SELECTION_INITIAL, + SELECTION_SELECTED, + SELECTION_QUERY, + SELECTION_SET_INITIAL, + SELECTION_SET, + SELECTION_INVALID, + } state : 8; + uint32_t recvpartial; + uint32_t sendpartial; + } selection; + } tmp; + + struct { + const VTermSelectionCallbacks *callbacks; + void *user; + char *buffer; + size_t buflen; + } selection; +}; + +struct VTerm { + const VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + struct { + unsigned utf8:1; + unsigned ctrl8bit:1; + } mode; + + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + DCS_COMMAND, + // below here are the "string states" + OSC_COMMAND, + OSC, + DCS_VTERM, + APC, + PM, + SOS, + } state; + + bool in_esc : 1; + + int intermedlen; + char intermed[INTERMED_MAX]; + + union { + struct { + int leaderlen; + char leader[CSI_LEADER_MAX]; + + int argi; + long args[CSI_ARGS_MAX]; + } csi; + struct { + int command; + } osc; + struct { + int commandlen; + char command[CSI_LEADER_MAX]; + } dcs; + } v; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + bool string_initial; + + bool emit_nul; + } parser; + + // len == malloc()ed size; cur == number of valid bytes + + VTermOutputCallback *outfunc; + void *outdata; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + char *tmpbuffer; + size_t tmpbuffer_len; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init)(VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94, +} VTermEncodingType; + +enum { + C1_SS3 = 0x8f, + C1_DCS = 0x90, + C1_CSI = 0x9b, + C1_ST = 0x9c, + C1_OSC = 0x9d, +}; diff --git a/src/nvim/vterm/vterm_keycodes_defs.h b/src/nvim/vterm/vterm_keycodes_defs.h new file mode 100644 index 0000000000..70db05af54 --- /dev/null +++ b/src/nvim/vterm/vterm_keycodes_defs.h @@ -0,0 +1,58 @@ +#pragma once + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, + + VTERM_ALL_MODS_MASK = 0x07, +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0 = 256, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_KP_0, + VTERM_KEY_KP_1, + VTERM_KEY_KP_2, + VTERM_KEY_KP_3, + VTERM_KEY_KP_4, + VTERM_KEY_KP_5, + VTERM_KEY_KP_6, + VTERM_KEY_KP_7, + VTERM_KEY_KP_8, + VTERM_KEY_KP_9, + VTERM_KEY_KP_MULT, + VTERM_KEY_KP_PLUS, + VTERM_KEY_KP_COMMA, + VTERM_KEY_KP_MINUS, + VTERM_KEY_KP_PERIOD, + VTERM_KEY_KP_DIVIDE, + VTERM_KEY_KP_ENTER, + VTERM_KEY_KP_EQUAL, + + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX, +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0 + (n)) diff --git a/src/vterm/LICENSE b/src/vterm/LICENSE deleted file mode 100644 index 0d051634b2..0000000000 --- a/src/vterm/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ - - -The MIT License - -Copyright (c) 2008 Paul Evans - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/src/vterm/encoding.c b/src/vterm/encoding.c deleted file mode 100644 index 434ac3f99b..0000000000 --- a/src/vterm/encoding.c +++ /dev/null @@ -1,230 +0,0 @@ -#include "vterm_internal.h" - -#define UNICODE_INVALID 0xFFFD - -#if defined(DEBUG) && DEBUG > 1 -# define DEBUG_PRINT_UTF8 -#endif - -struct UTF8DecoderData { - // number of bytes remaining in this codepoint - int bytes_remaining; - - // number of bytes total in this codepoint once it's finished - // (for detecting overlongs) - int bytes_total; - - int this_cp; -}; - -static void init_utf8(VTermEncoding *enc, void *data_) -{ - struct UTF8DecoderData *data = data_; - - data->bytes_remaining = 0; - data->bytes_total = 0; -} - -static void decode_utf8(VTermEncoding *enc, void *data_, - uint32_t cp[], int *cpi, int cplen, - const char bytes[], size_t *pos, size_t bytelen) -{ - struct UTF8DecoderData *data = data_; - -#ifdef DEBUG_PRINT_UTF8 - printf("BEGIN UTF-8\n"); -#endif - - for(; *pos < bytelen && *cpi < cplen; (*pos)++) { - unsigned char c = bytes[*pos]; - -#ifdef DEBUG_PRINT_UTF8 - printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); -#endif - - if(c < 0x20) // C0 - return; - - else if(c >= 0x20 && c < 0x7f) { - if(data->bytes_remaining) - cp[(*cpi)++] = UNICODE_INVALID; - - cp[(*cpi)++] = c; -#ifdef DEBUG_PRINT_UTF8 - printf(" UTF-8 char: U+%04x\n", c); -#endif - data->bytes_remaining = 0; - } - - else if(c == 0x7f) // DEL - return; - - else if(c >= 0x80 && c < 0xc0) { - if(!data->bytes_remaining) { - cp[(*cpi)++] = UNICODE_INVALID; - continue; - } - - data->this_cp <<= 6; - data->this_cp |= c & 0x3f; - data->bytes_remaining--; - - if(!data->bytes_remaining) { -#ifdef DEBUG_PRINT_UTF8 - printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); -#endif - // Check for overlong sequences - switch(data->bytes_total) { - case 2: - if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; - break; - case 3: - if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; - break; - case 4: - if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; - break; - case 5: - if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; - break; - case 6: - if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; - break; - } - // Now look for plain invalid ones - if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || - data->this_cp == 0xFFFE || - data->this_cp == 0xFFFF) - data->this_cp = UNICODE_INVALID; -#ifdef DEBUG_PRINT_UTF8 - printf(" char: U+%04x\n", data->this_cp); -#endif - cp[(*cpi)++] = data->this_cp; - } - } - - else if(c >= 0xc0 && c < 0xe0) { - if(data->bytes_remaining) - cp[(*cpi)++] = UNICODE_INVALID; - - data->this_cp = c & 0x1f; - data->bytes_total = 2; - data->bytes_remaining = 1; - } - - else if(c >= 0xe0 && c < 0xf0) { - if(data->bytes_remaining) - cp[(*cpi)++] = UNICODE_INVALID; - - data->this_cp = c & 0x0f; - data->bytes_total = 3; - data->bytes_remaining = 2; - } - - else if(c >= 0xf0 && c < 0xf8) { - if(data->bytes_remaining) - cp[(*cpi)++] = UNICODE_INVALID; - - data->this_cp = c & 0x07; - data->bytes_total = 4; - data->bytes_remaining = 3; - } - - else if(c >= 0xf8 && c < 0xfc) { - if(data->bytes_remaining) - cp[(*cpi)++] = UNICODE_INVALID; - - data->this_cp = c & 0x03; - data->bytes_total = 5; - data->bytes_remaining = 4; - } - - else if(c >= 0xfc && c < 0xfe) { - if(data->bytes_remaining) - cp[(*cpi)++] = UNICODE_INVALID; - - data->this_cp = c & 0x01; - data->bytes_total = 6; - data->bytes_remaining = 5; - } - - else { - cp[(*cpi)++] = UNICODE_INVALID; - } - } -} - -static VTermEncoding encoding_utf8 = { - .init = &init_utf8, - .decode = &decode_utf8, -}; - -static void decode_usascii(VTermEncoding *enc, void *data, - uint32_t cp[], int *cpi, int cplen, - const char bytes[], size_t *pos, size_t bytelen) -{ - int is_gr = bytes[*pos] & 0x80; - - for(; *pos < bytelen && *cpi < cplen; (*pos)++) { - unsigned char c = bytes[*pos] ^ is_gr; - - if(c < 0x20 || c == 0x7f || c >= 0x80) - return; - - cp[(*cpi)++] = c; - } -} - -static VTermEncoding encoding_usascii = { - .decode = &decode_usascii, -}; - -struct StaticTableEncoding { - const VTermEncoding enc; - const uint32_t chars[128]; -}; - -static void decode_table(VTermEncoding *enc, void *data, - uint32_t cp[], int *cpi, int cplen, - const char bytes[], size_t *pos, size_t bytelen) -{ - struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; - int is_gr = bytes[*pos] & 0x80; - - for(; *pos < bytelen && *cpi < cplen; (*pos)++) { - unsigned char c = bytes[*pos] ^ is_gr; - - if(c < 0x20 || c == 0x7f || c >= 0x80) - return; - - if(table->chars[c]) - cp[(*cpi)++] = table->chars[c]; - else - cp[(*cpi)++] = c; - } -} - -#include "encoding/DECdrawing.inc" -#include "encoding/uk.inc" - -static struct { - VTermEncodingType type; - char designation; - VTermEncoding *enc; -} -encodings[] = { - { ENC_UTF8, 'u', &encoding_utf8 }, - { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing }, - { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk }, - { ENC_SINGLE_94, 'B', &encoding_usascii }, - { 0 }, -}; - -/* This ought to be INTERNAL but isn't because it's used by unit testing */ -VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) -{ - for(int i = 0; encodings[i].designation; i++) - if(encodings[i].type == type && encodings[i].designation == designation) - return encodings[i].enc; - return NULL; -} diff --git a/src/vterm/encoding/DECdrawing.inc b/src/vterm/encoding/DECdrawing.inc deleted file mode 100644 index 627397bcc2..0000000000 --- a/src/vterm/encoding/DECdrawing.inc +++ /dev/null @@ -1,36 +0,0 @@ -static const struct StaticTableEncoding encoding_DECdrawing = { - { .decode = &decode_table }, - { - [0x60] = 0x25C6, // BLACK DIAMOND - [0x61] = 0x2592, // MEDIUM SHADE (checkerboard) - [0x62] = 0x2409, // SYMBOL FOR HORIZONTAL TAB - [0x63] = 0x240C, // SYMBOL FOR FORM FEED - [0x64] = 0x240D, // SYMBOL FOR CARRIAGE RETURN - [0x65] = 0x240A, // SYMBOL FOR LINE FEED - [0x66] = 0x00B0, // DEGREE SIGN - [0x67] = 0x00B1, // PLUS-MINUS SIGN (plus or minus) - [0x68] = 0x2424, // SYMBOL FOR NEW LINE - [0x69] = 0x240B, // SYMBOL FOR VERTICAL TAB - [0x6a] = 0x2518, // BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner) - [0x6b] = 0x2510, // BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner) - [0x6c] = 0x250C, // BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner) - [0x6d] = 0x2514, // BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner) - [0x6e] = 0x253C, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines) - [0x6f] = 0x23BA, // HORIZONTAL SCAN LINE-1 - [0x70] = 0x23BB, // HORIZONTAL SCAN LINE-3 - [0x71] = 0x2500, // BOX DRAWINGS LIGHT HORIZONTAL - [0x72] = 0x23BC, // HORIZONTAL SCAN LINE-7 - [0x73] = 0x23BD, // HORIZONTAL SCAN LINE-9 - [0x74] = 0x251C, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT - [0x75] = 0x2524, // BOX DRAWINGS LIGHT VERTICAL AND LEFT - [0x76] = 0x2534, // BOX DRAWINGS LIGHT UP AND HORIZONTAL - [0x77] = 0x252C, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL - [0x78] = 0x2502, // BOX DRAWINGS LIGHT VERTICAL - [0x79] = 0x2A7D, // LESS-THAN OR SLANTED EQUAL-TO - [0x7a] = 0x2A7E, // GREATER-THAN OR SLANTED EQUAL-TO - [0x7b] = 0x03C0, // GREEK SMALL LETTER PI - [0x7c] = 0x2260, // NOT EQUAL TO - [0x7d] = 0x00A3, // POUND SIGN - [0x7e] = 0x00B7, // MIDDLE DOT - } -}; diff --git a/src/vterm/encoding/uk.inc b/src/vterm/encoding/uk.inc deleted file mode 100644 index 5c7700226b..0000000000 --- a/src/vterm/encoding/uk.inc +++ /dev/null @@ -1,6 +0,0 @@ -static const struct StaticTableEncoding encoding_uk = { - { .decode = &decode_table }, - { - [0x23] = 0x00a3, // £ - } -}; diff --git a/src/vterm/keyboard.c b/src/vterm/keyboard.c deleted file mode 100644 index 7e062c8c02..0000000000 --- a/src/vterm/keyboard.c +++ /dev/null @@ -1,225 +0,0 @@ -#include "vterm_internal.h" -#include - -#include "nvim/tui/termkey/termkey.h" - -void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) -{ - /* The shift modifier is never important for Unicode characters - * apart from Space - */ - if(c != ' ') - mod &= ~VTERM_MOD_SHIFT; - - if(mod == 0) { - // Normal text - ignore just shift - char str[6]; - int seqlen = fill_utf8(c, str); - vterm_push_output_bytes(vt, str, seqlen); - return; - } - - int needs_CSIu; - switch(c) { - /* Special Ctrl- letters that can't be represented elsewise */ - case 'i': case 'j': case 'm': case '[': - needs_CSIu = 1; - break; - /* Ctrl-\ ] ^ _ don't need CSUu */ - case '\\': case ']': case '^': case '_': - needs_CSIu = 0; - break; - /* Shift-space needs CSIu */ - case ' ': - needs_CSIu = !!(mod & VTERM_MOD_SHIFT); - break; - /* All other characters needs CSIu except for letters a-z */ - default: - needs_CSIu = (c < 'a' || c > 'z'); - } - - /* ALT we can just prefix with ESC; anything else requires CSI u */ - if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1); - return; - } - - if(mod & VTERM_MOD_CTRL) - c &= 0x1f; - - vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); -} - -typedef struct { - enum { - KEYCODE_NONE, - KEYCODE_LITERAL, - KEYCODE_TAB, - KEYCODE_ENTER, - KEYCODE_SS3, - KEYCODE_CSI, - KEYCODE_CSI_CURSOR, - KEYCODE_CSINUM, - KEYCODE_KEYPAD, - } type; - char literal; - int csinum; -} keycodes_s; - -static keycodes_s keycodes[] = { - { KEYCODE_NONE }, // NONE - - { KEYCODE_ENTER, '\r' }, // ENTER - { KEYCODE_TAB, '\t' }, // TAB - { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL - { KEYCODE_LITERAL, '\x1b' }, // ESCAPE - - { KEYCODE_CSI_CURSOR, 'A' }, // UP - { KEYCODE_CSI_CURSOR, 'B' }, // DOWN - { KEYCODE_CSI_CURSOR, 'D' }, // LEFT - { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT - - { KEYCODE_CSINUM, '~', 2 }, // INS - { KEYCODE_CSINUM, '~', 3 }, // DEL - { KEYCODE_CSI_CURSOR, 'H' }, // HOME - { KEYCODE_CSI_CURSOR, 'F' }, // END - { KEYCODE_CSINUM, '~', 5 }, // PAGEUP - { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN -}; - -static keycodes_s keycodes_fn[] = { - { KEYCODE_NONE }, // F0 - shouldn't happen - { KEYCODE_SS3, 'P' }, // F1 - { KEYCODE_SS3, 'Q' }, // F2 - { KEYCODE_SS3, 'R' }, // F3 - { KEYCODE_SS3, 'S' }, // F4 - { KEYCODE_CSINUM, '~', 15 }, // F5 - { KEYCODE_CSINUM, '~', 17 }, // F6 - { KEYCODE_CSINUM, '~', 18 }, // F7 - { KEYCODE_CSINUM, '~', 19 }, // F8 - { KEYCODE_CSINUM, '~', 20 }, // F9 - { KEYCODE_CSINUM, '~', 21 }, // F10 - { KEYCODE_CSINUM, '~', 23 }, // F11 - { KEYCODE_CSINUM, '~', 24 }, // F12 -}; - -static keycodes_s keycodes_kp[] = { - { KEYCODE_KEYPAD, '0', 'p' }, // KP_0 - { KEYCODE_KEYPAD, '1', 'q' }, // KP_1 - { KEYCODE_KEYPAD, '2', 'r' }, // KP_2 - { KEYCODE_KEYPAD, '3', 's' }, // KP_3 - { KEYCODE_KEYPAD, '4', 't' }, // KP_4 - { KEYCODE_KEYPAD, '5', 'u' }, // KP_5 - { KEYCODE_KEYPAD, '6', 'v' }, // KP_6 - { KEYCODE_KEYPAD, '7', 'w' }, // KP_7 - { KEYCODE_KEYPAD, '8', 'x' }, // KP_8 - { KEYCODE_KEYPAD, '9', 'y' }, // KP_9 - { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT - { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS - { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA - { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS - { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD - { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE - { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER - { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL -}; - -void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) -{ - if(key == VTERM_KEY_NONE) - return; - - keycodes_s k; - if(key < VTERM_KEY_FUNCTION_0) { - if(key >= sizeof(keycodes)/sizeof(keycodes[0])) - return; - k = keycodes[key]; - } - else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { - if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) - return; - k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; - } - else if(key >= VTERM_KEY_KP_0) { - if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) - return; - k = keycodes_kp[key - VTERM_KEY_KP_0]; - } - - switch(k.type) { - case KEYCODE_NONE: - break; - - case KEYCODE_TAB: - /* Shift-Tab is CSI Z but plain Tab is 0x09 */ - if(mod == VTERM_MOD_SHIFT) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); - else if(mod & VTERM_MOD_SHIFT) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1); - else - goto case_LITERAL; - break; - - case KEYCODE_ENTER: - /* Enter is CRLF in newline mode, but just LF in linefeed */ - if(vt->state->mode.newline) - vterm_push_output_sprintf(vt, "\r\n"); - else - goto case_LITERAL; - break; - - case KEYCODE_LITERAL: case_LITERAL: - if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1); - else - vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); - break; - - case KEYCODE_SS3: case_SS3: - if(mod == 0) - vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); - else - goto case_CSI; - break; - - case KEYCODE_CSI: case_CSI: - if(mod == 0) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); - else - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); - break; - - case KEYCODE_CSINUM: - if(mod == 0) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); - else - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); - break; - - case KEYCODE_CSI_CURSOR: - if(vt->state->mode.cursor) - goto case_SS3; - else - goto case_CSI; - - case KEYCODE_KEYPAD: - if(vt->state->mode.keypad) { - k.literal = k.csinum; - goto case_SS3; - } - else - goto case_LITERAL; - } -} - -void vterm_keyboard_start_paste(VTerm *vt) -{ - if(vt->state->mode.bracketpaste) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); -} - -void vterm_keyboard_end_paste(VTerm *vt) -{ - if(vt->state->mode.bracketpaste) - vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); -} diff --git a/src/vterm/mouse.c b/src/vterm/mouse.c deleted file mode 100644 index a9d3fe4ca9..0000000000 --- a/src/vterm/mouse.c +++ /dev/null @@ -1,99 +0,0 @@ -#include "vterm_internal.h" - -#include "nvim/tui/termkey/termkey.h" - -static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) -{ - modifiers <<= 2; - - switch(state->mouse_protocol) { - case MOUSE_X10: - if(col + 0x21 > 0xff) - col = 0xff - 0x21; - if(row + 0x21 > 0xff) - row = 0xff - 0x21; - - if(!pressed) - code = 3; - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", - (code | modifiers) + 0x20, col + 0x21, row + 0x21); - break; - - case MOUSE_UTF8: - { - char utf8[18]; size_t len = 0; - - if(!pressed) - code = 3; - - len += fill_utf8((code | modifiers) + 0x20, utf8 + len); - len += fill_utf8(col + 0x21, utf8 + len); - len += fill_utf8(row + 0x21, utf8 + len); - utf8[len] = 0; - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); - } - break; - - case MOUSE_SGR: - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", - code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); - break; - - case MOUSE_RXVT: - if(!pressed) - code = 3; - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", - code | modifiers, col + 1, row + 1); - break; - } -} - -void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) -{ - VTermState *state = vt->state; - - if(col == state->mouse_col && row == state->mouse_row) - return; - - state->mouse_col = col; - state->mouse_row = row; - - if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || - (state->mouse_flags & MOUSE_WANT_MOVE)) { - int button = state->mouse_buttons & 0x01 ? 1 : - state->mouse_buttons & 0x02 ? 2 : - state->mouse_buttons & 0x04 ? 3 : 4; - output_mouse(state, button-1 + 0x20, 1, mod, col, row); - } -} - -void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) -{ - VTermState *state = vt->state; - - int old_buttons = state->mouse_buttons; - - if(button > 0 && button <= 3) { - if(pressed) - state->mouse_buttons |= (1 << (button-1)); - else - state->mouse_buttons &= ~(1 << (button-1)); - } - - /* Most of the time we don't get button releases from 4/5 */ - if(state->mouse_buttons == old_buttons && button < 4) - return; - - if(!state->mouse_flags) - return; - - if(button < 4) { - output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row); - } - else if(button < 8) { - output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); - } -} diff --git a/src/vterm/parser.c b/src/vterm/parser.c deleted file mode 100644 index 84d017a791..0000000000 --- a/src/vterm/parser.c +++ /dev/null @@ -1,408 +0,0 @@ -#include "vterm_internal.h" - -#include -#include -#include - -#undef DEBUG_PARSER - -static bool is_intermed(unsigned char c) -{ - return c >= 0x20 && c <= 0x2f; -} - -static void do_control(VTerm *vt, unsigned char control) -{ - if(vt->parser.callbacks && vt->parser.callbacks->control) - if((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) - return; - - DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); -} - -static void do_csi(VTerm *vt, char command) -{ -#ifdef DEBUG_PARSER - printf("Parsed CSI args as:\n", arglen, args); - printf(" leader: %s\n", vt->parser.v.csi.leader); - for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) { - printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi])); - if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi])) - printf("\n"); - printf(" intermed: %s\n", vt->parser.intermed); - } -#endif - - if(vt->parser.callbacks && vt->parser.callbacks->csi) - if((*vt->parser.callbacks->csi)( - vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, - vt->parser.v.csi.args, - vt->parser.v.csi.argi, - vt->parser.intermedlen ? vt->parser.intermed : NULL, - command, - vt->parser.cbdata)) - return; - - DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); -} - -static void do_escape(VTerm *vt, char command) -{ - char seq[INTERMED_MAX+1]; - - size_t len = vt->parser.intermedlen; - strncpy(seq, vt->parser.intermed, len); - seq[len++] = command; - seq[len] = 0; - - if(vt->parser.callbacks && vt->parser.callbacks->escape) - if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) - return; - - DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); -} - -static void string_fragment(VTerm *vt, const char *str, size_t len, bool final) -{ - VTermStringFragment frag = { - .str = str, - .len = len, - .initial = vt->parser.string_initial, - .final = final, - }; - - switch(vt->parser.state) { - case OSC: - if(vt->parser.callbacks && vt->parser.callbacks->osc) - (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata); - break; - - case DCS: - if(vt->parser.callbacks && vt->parser.callbacks->dcs) - (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata); - break; - - case APC: - if(vt->parser.callbacks && vt->parser.callbacks->apc) - (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata); - break; - - case PM: - if(vt->parser.callbacks && vt->parser.callbacks->pm) - (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata); - break; - - case SOS: - if(vt->parser.callbacks && vt->parser.callbacks->sos) - (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata); - break; - - case NORMAL: - case CSI_LEADER: - case CSI_ARGS: - case CSI_INTERMED: - case OSC_COMMAND: - case DCS_COMMAND: - break; - } - - vt->parser.string_initial = false; -} - -size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) -{ - size_t pos = 0; - const char *string_start; - - switch(vt->parser.state) { - case NORMAL: - case CSI_LEADER: - case CSI_ARGS: - case CSI_INTERMED: - case OSC_COMMAND: - case DCS_COMMAND: - string_start = NULL; - break; - case OSC: - case DCS: - case APC: - case PM: - case SOS: - string_start = bytes; - break; - } - -#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) -#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) - -#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND) - - for( ; pos < len; pos++) { - unsigned char c = bytes[pos]; - bool c1_allowed = !vt->mode.utf8; - - if(c == 0x00 || c == 0x7f) { // NUL, DEL - if(IS_STRING_STATE()) { - string_fragment(vt, string_start, bytes + pos - string_start, false); - string_start = bytes + pos + 1; - } - if(vt->parser.emit_nul) - do_control(vt, c); - continue; - } - if(c == 0x18 || c == 0x1a) { // CAN, SUB - vt->parser.in_esc = false; - ENTER_NORMAL_STATE(); - if(vt->parser.emit_nul) - do_control(vt, c); - continue; - } - else if(c == 0x1b) { // ESC - vt->parser.intermedlen = 0; - if(!IS_STRING_STATE()) - vt->parser.state = NORMAL; - vt->parser.in_esc = true; - continue; - } - else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state - IS_STRING_STATE()) { - // fallthrough - } - else if(c < 0x20) { // other C0 - if(vt->parser.state == SOS) - continue; // All other C0s permitted in SOS - - if(IS_STRING_STATE()) - string_fragment(vt, string_start, bytes + pos - string_start, false); - do_control(vt, c); - if(IS_STRING_STATE()) - string_start = bytes + pos + 1; - continue; - } - // else fallthrough - - size_t string_len = bytes + pos - string_start; - - if(vt->parser.in_esc) { - // Hoist an ESC letter into a C1 if we're not in a string mode - // Always accept ESC \ == ST even in string mode - if(!vt->parser.intermedlen && - c >= 0x40 && c < 0x60 && - ((!IS_STRING_STATE() || c == 0x5c))) { - c += 0x40; - c1_allowed = true; - if(string_len) { - assert(string_len > 0); - string_len -= 1; - } - vt->parser.in_esc = false; - } - else { - string_start = NULL; - vt->parser.state = NORMAL; - } - } - - switch(vt->parser.state) { - case CSI_LEADER: - /* Extract leader bytes 0x3c to 0x3f */ - if(c >= 0x3c && c <= 0x3f) { - if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1) - vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c; - break; - } - - /* else fallthrough */ - vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; - - vt->parser.v.csi.argi = 0; - vt->parser.v.csi.args[0] = CSI_ARG_MISSING; - vt->parser.state = CSI_ARGS; - - /* fallthrough */ - case CSI_ARGS: - /* Numerical value of argument */ - if(c >= '0' && c <= '9') { - if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) - vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; - vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; - vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; - break; - } - if(c == ':') { - vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; - c = ';'; - } - if(c == ';') { - vt->parser.v.csi.argi++; - vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; - break; - } - - /* else fallthrough */ - vt->parser.v.csi.argi++; - vt->parser.intermedlen = 0; - vt->parser.state = CSI_INTERMED; - case CSI_INTERMED: - if(is_intermed(c)) { - if(vt->parser.intermedlen < INTERMED_MAX-1) - vt->parser.intermed[vt->parser.intermedlen++] = c; - break; - } - else if(c == 0x1b) { - /* ESC in CSI cancels */ - } - else if(c >= 0x40 && c <= 0x7e) { - vt->parser.intermed[vt->parser.intermedlen] = 0; - do_csi(vt, c); - } - /* else was invalid CSI */ - - ENTER_NORMAL_STATE(); - break; - - case OSC_COMMAND: - /* Numerical value of command */ - if(c >= '0' && c <= '9') { - if(vt->parser.v.osc.command == -1) - vt->parser.v.osc.command = 0; - else - vt->parser.v.osc.command *= 10; - vt->parser.v.osc.command += c - '0'; - break; - } - if(c == ';') { - vt->parser.state = OSC; - string_start = bytes + pos + 1; - break; - } - - /* else fallthrough */ - string_start = bytes + pos; - string_len = 0; - vt->parser.state = OSC; - goto string_state; - - case DCS_COMMAND: - if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) - vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c; - - if(c >= 0x40 && c<= 0x7e) { - string_start = bytes + pos + 1; - vt->parser.state = DCS; - } - break; - -string_state: - case OSC: - case DCS: - case APC: - case PM: - case SOS: - if(c == 0x07 || (c1_allowed && c == 0x9c)) { - string_fragment(vt, string_start, string_len, true); - ENTER_NORMAL_STATE(); - } - break; - - case NORMAL: - if(vt->parser.in_esc) { - if(is_intermed(c)) { - if(vt->parser.intermedlen < INTERMED_MAX-1) - vt->parser.intermed[vt->parser.intermedlen++] = c; - } - else if(c >= 0x30 && c < 0x7f) { - do_escape(vt, c); - vt->parser.in_esc = 0; - ENTER_NORMAL_STATE(); - } - else { - DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); - } - break; - } - if(c1_allowed && c >= 0x80 && c < 0xa0) { - switch(c) { - case 0x90: // DCS - vt->parser.string_initial = true; - vt->parser.v.dcs.commandlen = 0; - ENTER_STATE(DCS_COMMAND); - break; - case 0x98: // SOS - vt->parser.string_initial = true; - ENTER_STATE(SOS); - string_start = bytes + pos + 1; - string_len = 0; - break; - case 0x9b: // CSI - vt->parser.v.csi.leaderlen = 0; - ENTER_STATE(CSI_LEADER); - break; - case 0x9d: // OSC - vt->parser.v.osc.command = -1; - vt->parser.string_initial = true; - string_start = bytes + pos + 1; - ENTER_STATE(OSC_COMMAND); - break; - case 0x9e: // PM - vt->parser.string_initial = true; - ENTER_STATE(PM); - string_start = bytes + pos + 1; - string_len = 0; - break; - case 0x9f: // APC - vt->parser.string_initial = true; - ENTER_STATE(APC); - string_start = bytes + pos + 1; - string_len = 0; - break; - default: - do_control(vt, c); - break; - } - } - else { - size_t eaten = 0; - if(vt->parser.callbacks && vt->parser.callbacks->text) - eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); - - if(!eaten) { - DEBUG_LOG("libvterm: Text callback did not consume any input\n"); - /* force it to make progress */ - eaten = 1; - } - - pos += (eaten - 1); // we'll ++ it again in a moment - } - break; - } - } - - if(string_start) { - size_t string_len = bytes + pos - string_start; - if (string_len > 0) { - if(vt->parser.in_esc) { - string_len -= 1; - } - string_fragment(vt, string_start, string_len, false); - } - } - - return len; -} - -void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) -{ - vt->parser.callbacks = callbacks; - vt->parser.cbdata = user; -} - -void *vterm_parser_get_cbdata(VTerm *vt) -{ - return vt->parser.cbdata; -} - -void vterm_parser_set_emit_nul(VTerm *vt, bool emit) -{ - vt->parser.emit_nul = emit; -} diff --git a/src/vterm/pen.c b/src/vterm/pen.c deleted file mode 100644 index 1876eb9881..0000000000 --- a/src/vterm/pen.c +++ /dev/null @@ -1,678 +0,0 @@ -#include "vterm_internal.h" - -#include - -/** - * Structure used to store RGB triples without the additional metadata stored in - * VTermColor. - */ -typedef struct { - uint8_t red, green, blue; -} VTermRGB; - -static const VTermRGB ansi_colors[] = { - /* R G B */ - { 0, 0, 0 }, // black - { 224, 0, 0 }, // red - { 0, 224, 0 }, // green - { 224, 224, 0 }, // yellow - { 0, 0, 224 }, // blue - { 224, 0, 224 }, // magenta - { 0, 224, 224 }, // cyan - { 224, 224, 224 }, // white == light grey - - // high intensity - { 128, 128, 128 }, // black - { 255, 64, 64 }, // red - { 64, 255, 64 }, // green - { 255, 255, 64 }, // yellow - { 64, 64, 255 }, // blue - { 255, 64, 255 }, // magenta - { 64, 255, 255 }, // cyan - { 255, 255, 255 }, // white for real -}; - -static int ramp6[] = { - 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, -}; - -static int ramp24[] = { - 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, - 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, -}; - -static void lookup_default_colour_ansi(long idx, VTermColor *col) -{ - if (idx >= 0 && idx < 16) { - vterm_color_rgb( - col, - ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); - } -} - -static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) -{ - if(index >= 0 && index < 16) { - *col = state->colors[index]; - return true; - } - - return false; -} - -static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) -{ - if(index >= 0 && index < 16) { - // Normal 8 colours or high intensity - parse as palette 0 - return lookup_colour_ansi(state, index, col); - } - else if(index >= 16 && index < 232) { - // 216-colour cube - index -= 16; - - vterm_color_rgb(col, ramp6[index/6/6 % 6], - ramp6[index/6 % 6], - ramp6[index % 6]); - - return true; - } - else if(index >= 232 && index < 256) { - // 24 greyscales - index -= 232; - - vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); - - return true; - } - - return false; -} - -static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) -{ - switch(palette) { - case 2: // RGB mode - 3 args contain colour values directly - if(argcount < 3) - return argcount; - - vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); - - return 3; - - case 5: // XTerm 256-colour mode - if (!argcount || CSI_ARG_IS_MISSING(args[0])) { - return argcount ? 1 : 0; - } - - vterm_color_indexed(col, args[0]); - - return argcount ? 1 : 0; - - default: - DEBUG_LOG("Unrecognised colour palette %d\n", palette); - return 0; - } -} - -// Some conveniences - -static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) -{ -#ifdef DEBUG - if(type != vterm_get_attr_type(attr)) { - DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", - attr, vterm_get_attr_type(attr), type); - return; - } -#endif - if(state->callbacks && state->callbacks->setpenattr) - (*state->callbacks->setpenattr)(attr, val, state->cbdata); -} - -static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) -{ - VTermValue val = { .boolean = boolean }; - setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); -} - -static void setpenattr_int(VTermState *state, VTermAttr attr, int number) -{ - VTermValue val = { .number = number }; - setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); -} - -static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) -{ - VTermValue val = { .color = color }; - setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); -} - -static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) -{ - VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; - - vterm_color_indexed(colp, col); - - setpenattr_col(state, attr, *colp); -} - -INTERNAL void vterm_state_newpen(VTermState *state) -{ - // 90% grey so that pure white is brighter - vterm_color_rgb(&state->default_fg, 240, 240, 240); - vterm_color_rgb(&state->default_bg, 0, 0, 0); - vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); - - for(int col = 0; col < 16; col++) - lookup_default_colour_ansi(col, &state->colors[col]); -} - -INTERNAL void vterm_state_resetpen(VTermState *state) -{ - state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); - state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0); - state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); - state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); - state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); - state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); - state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); - state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0); - state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); - state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0); - - state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); - state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); - - state->pen.uri = 0; setpenattr_int(state, VTERM_ATTR_URI, 0); -} - -INTERNAL void vterm_state_savepen(VTermState *state, int save) -{ - if(save) { - state->saved.pen = state->pen; - } - else { - state->pen = state->saved.pen; - - setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); - setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline); - setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); - setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); - setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); - setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); - setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); - setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font); - setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); - setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); - - setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg); - setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg); - - setpenattr_int( state, VTERM_ATTR_URI, state->pen.uri); - } -} - -int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) -{ - /* First make sure that the two colours are of the same type (RGB/Indexed) */ - if (a->type != b->type) { - return false; - } - - /* Depending on the type inspect the corresponding members */ - if (VTERM_COLOR_IS_INDEXED(a)) { - return a->indexed.idx == b->indexed.idx; - } - else if (VTERM_COLOR_IS_RGB(a)) { - return (a->rgb.red == b->rgb.red) - && (a->rgb.green == b->rgb.green) - && (a->rgb.blue == b->rgb.blue); - } - - return 0; -} - -void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) -{ - *default_fg = state->default_fg; - *default_bg = state->default_bg; -} - -void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) -{ - lookup_colour_palette(state, index, col); -} - -void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) -{ - if(default_fg) { - state->default_fg = *default_fg; - state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_FG; - } - - if(default_bg) { - state->default_bg = *default_bg; - state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_BG; - } -} - -void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) -{ - if(index >= 0 && index < 16) - state->colors[index] = *col; -} - -void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) -{ - if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ - lookup_colour_palette(state, col->indexed.idx, col); - } - col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ -} - -void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) -{ - state->bold_is_highbright = bold_is_highbright; -} - -INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) -{ - // SGR - ECMA-48 8.3.117 - - int argi = 0; - int value; - - while(argi < argcount) { - // This logic is easier to do 'done' backwards; set it true, and make it - // false again in the 'default' case - int done = 1; - - long arg; - switch(arg = CSI_ARG(args[argi])) { - case CSI_ARG_MISSING: - case 0: // Reset - vterm_state_resetpen(state); - break; - - case 1: { // Bold on - const VTermColor *fg = &state->pen.fg; - state->pen.bold = 1; - setpenattr_bool(state, VTERM_ATTR_BOLD, 1); - if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); - break; - } - - case 3: // Italic on - state->pen.italic = 1; - setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); - break; - - case 4: // Underline - state->pen.underline = VTERM_UNDERLINE_SINGLE; - if(CSI_ARG_HAS_MORE(args[argi])) { - argi++; - switch(CSI_ARG(args[argi])) { - case 0: - state->pen.underline = 0; - break; - case 1: - state->pen.underline = VTERM_UNDERLINE_SINGLE; - break; - case 2: - state->pen.underline = VTERM_UNDERLINE_DOUBLE; - break; - case 3: - state->pen.underline = VTERM_UNDERLINE_CURLY; - break; - } - } - setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); - break; - - case 5: // Blink - state->pen.blink = 1; - setpenattr_bool(state, VTERM_ATTR_BLINK, 1); - break; - - case 7: // Reverse on - state->pen.reverse = 1; - setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); - break; - - case 8: // Conceal on - state->pen.conceal = 1; - setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); - break; - - case 9: // Strikethrough on - state->pen.strike = 1; - setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); - break; - - case 10: case 11: case 12: case 13: case 14: - case 15: case 16: case 17: case 18: case 19: // Select font - state->pen.font = CSI_ARG(args[argi]) - 10; - setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); - break; - - case 21: // Underline double - state->pen.underline = VTERM_UNDERLINE_DOUBLE; - setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); - break; - - case 22: // Bold off - state->pen.bold = 0; - setpenattr_bool(state, VTERM_ATTR_BOLD, 0); - break; - - case 23: // Italic and Gothic (currently unsupported) off - state->pen.italic = 0; - setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); - break; - - case 24: // Underline off - state->pen.underline = 0; - setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); - break; - - case 25: // Blink off - state->pen.blink = 0; - setpenattr_bool(state, VTERM_ATTR_BLINK, 0); - break; - - case 27: // Reverse off - state->pen.reverse = 0; - setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); - break; - - case 28: // Conceal off (Reveal) - state->pen.conceal = 0; - setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); - break; - - case 29: // Strikethrough off - state->pen.strike = 0; - setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); - break; - - case 30: case 31: case 32: case 33: - case 34: case 35: case 36: case 37: // Foreground colour palette - value = CSI_ARG(args[argi]) - 30; - if(state->pen.bold && state->bold_is_highbright) - value += 8; - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); - break; - - case 38: // Foreground colour alternative palette - if(argcount - argi < 1) - return; - argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg); - setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); - break; - - case 39: // Foreground colour default - state->pen.fg = state->default_fg; - setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); - break; - - case 40: case 41: case 42: case 43: - case 44: case 45: case 46: case 47: // Background colour palette - value = CSI_ARG(args[argi]) - 40; - set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); - break; - - case 48: // Background colour alternative palette - if(argcount - argi < 1) - return; - argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg); - setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); - break; - - case 49: // Default background - state->pen.bg = state->default_bg; - setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); - break; - - case 73: // Superscript - case 74: // Subscript - case 75: // Superscript/subscript off - state->pen.small = (arg != 75); - state->pen.baseline = - (arg == 73) ? VTERM_BASELINE_RAISE : - (arg == 74) ? VTERM_BASELINE_LOWER : - VTERM_BASELINE_NORMAL; - setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); - setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); - break; - - case 90: case 91: case 92: case 93: - case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette - value = CSI_ARG(args[argi]) - 90 + 8; - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); - break; - - case 100: case 101: case 102: case 103: - case 104: case 105: case 106: case 107: // Background colour high-intensity palette - value = CSI_ARG(args[argi]) - 100 + 8; - set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); - break; - - default: - done = 0; - break; - } - - if(!done) { - DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); - } - - while(CSI_ARG_HAS_MORE(args[argi++])); - } -} - -static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) -{ - /* Do nothing if the given color is the default color */ - if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || - (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { - return argi; - } - - /* Decide whether to send an indexed color or an RGB color */ - if (VTERM_COLOR_IS_INDEXED(col)) { - const uint8_t idx = col->indexed.idx; - if (idx < 8) { - args[argi++] = (idx + (fg ? 30 : 40)); - } - else if (idx < 16) { - args[argi++] = (idx - 8 + (fg ? 90 : 100)); - } - else { - args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); - args[argi++] = CSI_ARG_FLAG_MORE | 5; - args[argi++] = idx; - } - } - else if (VTERM_COLOR_IS_RGB(col)) { - args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); - args[argi++] = CSI_ARG_FLAG_MORE | 2; - args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; - args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; - args[argi++] = col->rgb.blue; - } - return argi; -} - -INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) -{ - int argi = 0; - - if(state->pen.bold) - args[argi++] = 1; - - if(state->pen.italic) - args[argi++] = 3; - - if(state->pen.underline == VTERM_UNDERLINE_SINGLE) - args[argi++] = 4; - if(state->pen.underline == VTERM_UNDERLINE_CURLY) - args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; - - if(state->pen.blink) - args[argi++] = 5; - - if(state->pen.reverse) - args[argi++] = 7; - - if(state->pen.conceal) - args[argi++] = 8; - - if(state->pen.strike) - args[argi++] = 9; - - if(state->pen.font) - args[argi++] = 10 + state->pen.font; - - if(state->pen.underline == VTERM_UNDERLINE_DOUBLE) - args[argi++] = 21; - - argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); - - argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); - - if(state->pen.small) { - if(state->pen.baseline == VTERM_BASELINE_RAISE) - args[argi++] = 73; - else if(state->pen.baseline == VTERM_BASELINE_LOWER) - args[argi++] = 74; - } - - return argi; -} - -int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) -{ - switch(attr) { - case VTERM_ATTR_BOLD: - val->boolean = state->pen.bold; - return 1; - - case VTERM_ATTR_UNDERLINE: - val->number = state->pen.underline; - return 1; - - case VTERM_ATTR_ITALIC: - val->boolean = state->pen.italic; - return 1; - - case VTERM_ATTR_BLINK: - val->boolean = state->pen.blink; - return 1; - - case VTERM_ATTR_REVERSE: - val->boolean = state->pen.reverse; - return 1; - - case VTERM_ATTR_CONCEAL: - val->boolean = state->pen.conceal; - return 1; - - case VTERM_ATTR_STRIKE: - val->boolean = state->pen.strike; - return 1; - - case VTERM_ATTR_FONT: - val->number = state->pen.font; - return 1; - - case VTERM_ATTR_FOREGROUND: - val->color = state->pen.fg; - return 1; - - case VTERM_ATTR_BACKGROUND: - val->color = state->pen.bg; - return 1; - - case VTERM_ATTR_SMALL: - val->boolean = state->pen.small; - return 1; - - case VTERM_ATTR_BASELINE: - val->number = state->pen.baseline; - return 1; - - case VTERM_ATTR_URI: - val->number = state->pen.uri; - return 1; - - case VTERM_N_ATTRS: - return 0; - } - - return 0; -} - -int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) -{ - if (!val) { - return 0; - } - - if(type != vterm_get_attr_type(attr)) { - DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", - attr, vterm_get_attr_type(attr), type); - return 0; - } - - switch (attr) { - case VTERM_ATTR_BOLD: - state->pen.bold = val->boolean; - break; - case VTERM_ATTR_UNDERLINE: - state->pen.underline = val->number; - break; - case VTERM_ATTR_ITALIC: - state->pen.italic = val->boolean; - break; - case VTERM_ATTR_BLINK: - state->pen.blink = val->boolean; - break; - case VTERM_ATTR_REVERSE: - state->pen.reverse = val->boolean; - break; - case VTERM_ATTR_CONCEAL: - state->pen.conceal = val->boolean; - break; - case VTERM_ATTR_STRIKE: - state->pen.strike = val->boolean; - break; - case VTERM_ATTR_FONT: - state->pen.font = val->number; - break; - case VTERM_ATTR_FOREGROUND: - state->pen.fg = val->color; - break; - case VTERM_ATTR_BACKGROUND: - state->pen.bg = val->color; - break; - case VTERM_ATTR_SMALL: - state->pen.small = val->boolean; - break; - case VTERM_ATTR_BASELINE: - state->pen.baseline = val->number; - break; - case VTERM_ATTR_URI: - state->pen.uri = val->number; - break; - default: - return 0; - } - - if(state->callbacks && state->callbacks->setpenattr) - (*state->callbacks->setpenattr)(attr, val, state->cbdata); - - return 1; -} diff --git a/src/vterm/rect.h b/src/vterm/rect.h deleted file mode 100644 index 2114f24c1b..0000000000 --- a/src/vterm/rect.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Some utility functions on VTermRect structures - */ - -#define STRFrect "(%d,%d-%d,%d)" -#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col - -/* Expand dst to contain src as well */ -static void rect_expand(VTermRect *dst, VTermRect *src) -{ - if(dst->start_row > src->start_row) dst->start_row = src->start_row; - if(dst->start_col > src->start_col) dst->start_col = src->start_col; - if(dst->end_row < src->end_row) dst->end_row = src->end_row; - if(dst->end_col < src->end_col) dst->end_col = src->end_col; -} - -/* Clip the dst to ensure it does not step outside of bounds */ -static void rect_clip(VTermRect *dst, VTermRect *bounds) -{ - if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; - if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; - if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; - if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; - /* Ensure it doesn't end up negatively-sized */ - if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; - if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; -} - -/* True if the two rectangles are equal */ -static int rect_equal(VTermRect *a, VTermRect *b) -{ - return (a->start_row == b->start_row) && - (a->start_col == b->start_col) && - (a->end_row == b->end_row) && - (a->end_col == b->end_col); -} - -/* True if small is contained entirely within big */ -static int rect_contains(VTermRect *big, VTermRect *small) -{ - if(small->start_row < big->start_row) return 0; - if(small->start_col < big->start_col) return 0; - if(small->end_row > big->end_row) return 0; - if(small->end_col > big->end_col) return 0; - return 1; -} - -/* True if the rectangles overlap at all */ -static int rect_intersects(VTermRect *a, VTermRect *b) -{ - if(a->start_row > b->end_row || b->start_row > a->end_row) - return 0; - if(a->start_col > b->end_col || b->start_col > a->end_col) - return 0; - return 1; -} diff --git a/src/vterm/screen.c b/src/vterm/screen.c deleted file mode 100644 index 7de345ca39..0000000000 --- a/src/vterm/screen.c +++ /dev/null @@ -1,1174 +0,0 @@ -#include "vterm_internal.h" - -#include -#include -#include "nvim/grid.h" -#include "nvim/mbyte.h" -#include "nvim/tui/termkey/termkey.h" - -#include "rect.h" - -#define UNICODE_SPACE 0x20 -#define UNICODE_LINEFEED 0x0a - -#undef DEBUG_REFLOW - -/* State of the pen at some moment in time, also used in a cell */ -typedef struct -{ - /* After the bitfield */ - VTermColor fg, bg; - - /* Opaque ID that maps to a URI in a set */ - int uri; - - unsigned int bold : 1; - unsigned int underline : 2; - unsigned int italic : 1; - unsigned int blink : 1; - unsigned int reverse : 1; - unsigned int conceal : 1; - unsigned int strike : 1; - unsigned int font : 4; /* 0 to 9 */ - unsigned int small : 1; - unsigned int baseline : 2; - - /* Extra state storage that isn't strictly pen-related */ - unsigned int protected_cell : 1; - unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ - unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ -} ScreenPen; - -/* Internal representation of a screen cell */ -typedef struct -{ - schar_T schar; - ScreenPen pen; -} ScreenCell; - -struct VTermScreen -{ - VTerm *vt; - VTermState *state; - - const VTermScreenCallbacks *callbacks; - void *cbdata; - - VTermDamageSize damage_merge; - /* start_row == -1 => no damage */ - VTermRect damaged; - VTermRect pending_scrollrect; - int pending_scroll_downward, pending_scroll_rightward; - - int rows; - int cols; - - unsigned int global_reverse : 1; - unsigned int reflow : 1; - - /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ - ScreenCell *buffers[2]; - - /* buffer will == buffers[0] or buffers[1], depending on altscreen */ - ScreenCell *buffer; - - /* buffer for a single screen row used in scrollback storage callbacks */ - VTermScreenCell *sb_buffer; - - ScreenPen pen; -}; - -static inline void clearcell(const VTermScreen *screen, ScreenCell *cell) -{ - cell->schar = 0; - cell->pen = screen->pen; -} - -static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) -{ - if(row < 0 || row >= screen->rows) - return NULL; - if(col < 0 || col >= screen->cols) - return NULL; - return screen->buffer + (screen->cols * row) + col; -} - -static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols) -{ - ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols); - - for(int row = 0; row < rows; row++) { - for(int col = 0; col < cols; col++) { - clearcell(screen, &new_buffer[row * cols + col]); - } - } - - return new_buffer; -} - -static void damagerect(VTermScreen *screen, VTermRect rect) -{ - VTermRect emit; - - switch(screen->damage_merge) { - case VTERM_DAMAGE_CELL: - /* Always emit damage event */ - emit = rect; - break; - - case VTERM_DAMAGE_ROW: - /* Emit damage longer than one row. Try to merge with existing damage in - * the same row */ - if(rect.end_row > rect.start_row + 1) { - // Bigger than 1 line - flush existing, emit this - vterm_screen_flush_damage(screen); - emit = rect; - } - else if(screen->damaged.start_row == -1) { - // None stored yet - screen->damaged = rect; - return; - } - else if(rect.start_row == screen->damaged.start_row) { - // Merge with the stored line - if(screen->damaged.start_col > rect.start_col) - screen->damaged.start_col = rect.start_col; - if(screen->damaged.end_col < rect.end_col) - screen->damaged.end_col = rect.end_col; - return; - } - else { - // Emit the currently stored line, store a new one - emit = screen->damaged; - screen->damaged = rect; - } - break; - - case VTERM_DAMAGE_SCREEN: - case VTERM_DAMAGE_SCROLL: - /* Never emit damage event */ - if(screen->damaged.start_row == -1) - screen->damaged = rect; - else { - rect_expand(&screen->damaged, &rect); - } - return; - - default: - DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); - return; - } - - if(screen->callbacks && screen->callbacks->damage) - (*screen->callbacks->damage)(emit, screen->cbdata); -} - -static void damagescreen(VTermScreen *screen) -{ - VTermRect rect = { - .start_row = 0, - .end_row = screen->rows, - .start_col = 0, - .end_col = screen->cols, - }; - - damagerect(screen, rect); -} - -static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) -{ - VTermScreen *screen = user; - ScreenCell *cell = getcell(screen, pos.row, pos.col); - - if(!cell) - return 0; - - cell->schar = info->schar; - if (info->schar != 0) { - cell->pen = screen->pen; - } - - for(int col = 1; col < info->width; col++) - getcell(screen, pos.row, pos.col + col)->schar = (uint32_t)-1; - - VTermRect rect = { - .start_row = pos.row, - .end_row = pos.row+1, - .start_col = pos.col, - .end_col = pos.col+info->width, - }; - - cell->pen.protected_cell = info->protected_cell; - cell->pen.dwl = info->dwl; - cell->pen.dhl = info->dhl; - - damagerect(screen, rect); - - return 1; -} - -static void sb_pushline_from_row(VTermScreen *screen, int row) -{ - VTermPos pos = { .row = row }; - for(pos.col = 0; pos.col < screen->cols; pos.col++) - vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); - - (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); -} - -static int moverect_internal(VTermRect dest, VTermRect src, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->sb_pushline && - dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner - dest.end_col == screen->cols && // full width - screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen - for(int row = 0; row < src.start_row; row++) - sb_pushline_from_row(screen, row); - } - - int cols = src.end_col - src.start_col; - int downward = src.start_row - dest.start_row; - - int init_row, test_row, inc_row; - if(downward < 0) { - init_row = dest.end_row - 1; - test_row = dest.start_row - 1; - inc_row = -1; - } - else { - init_row = dest.start_row; - test_row = dest.end_row; - inc_row = +1; - } - - for(int row = init_row; row != test_row; row += inc_row) - memmove(getcell(screen, row, dest.start_col), - getcell(screen, row + downward, src.start_col), - cols * sizeof(ScreenCell)); - - return 1; -} - -static int moverect_user(VTermRect dest, VTermRect src, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->moverect) { - if(screen->damage_merge != VTERM_DAMAGE_SCROLL) - // Avoid an infinite loop - vterm_screen_flush_damage(screen); - - if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) - return 1; - } - - damagerect(screen, dest); - - return 1; -} - -static int erase_internal(VTermRect rect, int selective, void *user) -{ - VTermScreen *screen = user; - - for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { - const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); - - for(int col = rect.start_col; col < rect.end_col; col++) { - ScreenCell *cell = getcell(screen, row, col); - - if(selective && cell->pen.protected_cell) - continue; - - cell->schar = 0; - cell->pen = (ScreenPen){ - /* Only copy .fg and .bg; leave things like rv in reset state */ - .fg = screen->pen.fg, - .bg = screen->pen.bg, - }; - cell->pen.dwl = info->doublewidth; - cell->pen.dhl = info->doubleheight; - } - } - - return 1; -} - -static int erase_user(VTermRect rect, int selective, void *user) -{ - VTermScreen *screen = user; - - damagerect(screen, rect); - - return 1; -} - -static int erase(VTermRect rect, int selective, void *user) -{ - erase_internal(rect, selective, user); - return erase_user(rect, 0, user); -} - -static int scrollrect(VTermRect rect, int downward, int rightward, void *user) -{ - VTermScreen *screen = user; - - if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { - vterm_scroll_rect(rect, downward, rightward, - moverect_internal, erase_internal, screen); - - vterm_screen_flush_damage(screen); - - vterm_scroll_rect(rect, downward, rightward, - moverect_user, erase_user, screen); - - return 1; - } - - if(screen->damaged.start_row != -1 && - !rect_intersects(&rect, &screen->damaged)) { - vterm_screen_flush_damage(screen); - } - - if(screen->pending_scrollrect.start_row == -1) { - screen->pending_scrollrect = rect; - screen->pending_scroll_downward = downward; - screen->pending_scroll_rightward = rightward; - } - else if(rect_equal(&screen->pending_scrollrect, &rect) && - ((screen->pending_scroll_downward == 0 && downward == 0) || - (screen->pending_scroll_rightward == 0 && rightward == 0))) { - screen->pending_scroll_downward += downward; - screen->pending_scroll_rightward += rightward; - } - else { - vterm_screen_flush_damage(screen); - - screen->pending_scrollrect = rect; - screen->pending_scroll_downward = downward; - screen->pending_scroll_rightward = rightward; - } - - vterm_scroll_rect(rect, downward, rightward, - moverect_internal, erase_internal, screen); - - if(screen->damaged.start_row == -1) - return 1; - - if(rect_contains(&rect, &screen->damaged)) { - /* Scroll region entirely contains the damage; just move it */ - vterm_rect_move(&screen->damaged, -downward, -rightward); - rect_clip(&screen->damaged, &rect); - } - /* There are a number of possible cases here, but lets restrict this to only - * the common case where we might actually gain some performance by - * optimising it. Namely, a vertical scroll that neatly cuts the damage - * region in half. - */ - else if(rect.start_col <= screen->damaged.start_col && - rect.end_col >= screen->damaged.end_col && - rightward == 0) { - if(screen->damaged.start_row >= rect.start_row && - screen->damaged.start_row < rect.end_row) { - screen->damaged.start_row -= downward; - if(screen->damaged.start_row < rect.start_row) - screen->damaged.start_row = rect.start_row; - if(screen->damaged.start_row > rect.end_row) - screen->damaged.start_row = rect.end_row; - } - if(screen->damaged.end_row >= rect.start_row && - screen->damaged.end_row < rect.end_row) { - screen->damaged.end_row -= downward; - if(screen->damaged.end_row < rect.start_row) - screen->damaged.end_row = rect.start_row; - if(screen->damaged.end_row > rect.end_row) - screen->damaged.end_row = rect.end_row; - } - } - else { - DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", - ARGSrect(screen->damaged), ARGSrect(rect)); - } - - return 1; -} - -static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->movecursor) - return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); - - return 0; -} - -static int setpenattr(VTermAttr attr, VTermValue *val, void *user) -{ - VTermScreen *screen = user; - - switch(attr) { - case VTERM_ATTR_BOLD: - screen->pen.bold = val->boolean; - return 1; - case VTERM_ATTR_UNDERLINE: - screen->pen.underline = val->number; - return 1; - case VTERM_ATTR_ITALIC: - screen->pen.italic = val->boolean; - return 1; - case VTERM_ATTR_BLINK: - screen->pen.blink = val->boolean; - return 1; - case VTERM_ATTR_REVERSE: - screen->pen.reverse = val->boolean; - return 1; - case VTERM_ATTR_CONCEAL: - screen->pen.conceal = val->boolean; - return 1; - case VTERM_ATTR_STRIKE: - screen->pen.strike = val->boolean; - return 1; - case VTERM_ATTR_FONT: - screen->pen.font = val->number; - return 1; - case VTERM_ATTR_FOREGROUND: - screen->pen.fg = val->color; - return 1; - case VTERM_ATTR_BACKGROUND: - screen->pen.bg = val->color; - return 1; - case VTERM_ATTR_SMALL: - screen->pen.small = val->boolean; - return 1; - case VTERM_ATTR_BASELINE: - screen->pen.baseline = val->number; - return 1; - case VTERM_ATTR_URI: - screen->pen.uri = val->number; - return 1; - - case VTERM_N_ATTRS: - return 0; - } - - return 0; -} - -static int settermprop(VTermProp prop, VTermValue *val, void *user) -{ - VTermScreen *screen = user; - - switch(prop) { - case VTERM_PROP_ALTSCREEN: - if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) - return 0; - - screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; - /* only send a damage event on disable; because during enable there's an - * erase that sends a damage anyway - */ - if(!val->boolean) - damagescreen(screen); - break; - case VTERM_PROP_REVERSE: - screen->global_reverse = val->boolean; - damagescreen(screen); - break; - default: - ; /* ignore */ - } - - if(screen->callbacks && screen->callbacks->settermprop) - return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); - - return 1; -} - -static int bell(void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->bell) - return (*screen->callbacks->bell)(screen->cbdata); - - return 0; -} - -/* How many cells are non-blank - * Returns the position of the first blank cell in the trailing blank end */ -static int line_popcount(ScreenCell *buffer, int row, int rows, int cols) -{ - int col = cols - 1; - while(col >= 0 && buffer[row * cols + col].schar == 0) - col--; - return col + 1; -} - -static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields) -{ - int old_rows = screen->rows; - int old_cols = screen->cols; - - ScreenCell *old_buffer = screen->buffers[bufidx]; - VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx]; - - ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); - VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); - - int old_row = old_rows - 1; - int new_row = new_rows - 1; - - VTermPos old_cursor = statefields->pos; - VTermPos new_cursor = { -1, -1 }; - -#ifdef DEBUG_REFLOW - fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n", - old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row); -#endif - - /* Keep track of the final row that is knonw to be blank, so we know what - * spare space we have for scrolling into - */ - int final_blank_row = new_rows; - - while(old_row >= 0) { - int old_row_end = old_row; - /* TODO: Stop if dwl or dhl */ - while(screen->reflow && old_lineinfo && old_row > 0 && old_lineinfo[old_row].continuation) - old_row--; - int old_row_start = old_row; - - int width = 0; - for(int row = old_row_start; row <= old_row_end; row++) { - if(screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) - width += old_cols; - else - width += line_popcount(old_buffer, row, old_rows, old_cols); - } - - if(final_blank_row == (new_row + 1) && width == 0) - final_blank_row = new_row; - - int new_height = screen->reflow - ? width ? (width + new_cols - 1) / new_cols : 1 - : 1; - - int new_row_end = new_row; - int new_row_start = new_row - new_height + 1; - - old_row = old_row_start; - int old_col = 0; - - int spare_rows = new_rows - final_blank_row; - - if(new_row_start < 0 && /* we'd fall off the top */ - spare_rows >= 0 && /* we actually have spare rows */ - (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) - { - /* Attempt to scroll content down into the blank rows at the bottom to - * make it fit - */ - int downwards = -new_row_start; - if(downwards > spare_rows) - downwards = spare_rows; - int rowcount = new_rows - downwards; - -#ifdef DEBUG_REFLOW - fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards); -#endif - - memmove(&new_buffer[downwards * new_cols], &new_buffer[0], (unsigned long) rowcount * new_cols * sizeof(ScreenCell)); - memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0])); - - new_row += downwards; - new_row_start += downwards; - new_row_end += downwards; - - if(new_cursor.row >= 0) - new_cursor.row += downwards; - - final_blank_row += downwards; - } - -#ifdef DEBUG_REFLOW - fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n", - new_row_start, new_row_end, old_row_start, old_row_end, width); -#endif - - if(new_row_start < 0) { - if(old_row_start <= old_cursor.row && old_cursor.row <= old_row_end) { - new_cursor.row = 0; - new_cursor.col = old_cursor.col; - if(new_cursor.col >= new_cols) - new_cursor.col = new_cols-1; - } - break; - } - - for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) { - int count = width >= new_cols ? new_cols : width; - width -= count; - - int new_col = 0; - - while(count) { - /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */ - new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col]; - - if(old_cursor.row == old_row && old_cursor.col == old_col) - new_cursor.row = new_row, new_cursor.col = new_col; - - old_col++; - if(old_col == old_cols) { - old_row++; - - if(!screen->reflow) { - new_col++; - break; - } - old_col = 0; - } - - new_col++; - count--; - } - - if(old_cursor.row == old_row && old_cursor.col >= old_col) { - new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col); - if(new_cursor.col >= new_cols) - new_cursor.col = new_cols-1; - } - - while(new_col < new_cols) { - clearcell(screen, &new_buffer[new_row * new_cols + new_col]); - new_col++; - } - - new_lineinfo[new_row].continuation = (new_row > new_row_start); - } - - old_row = old_row_start - 1; - new_row = new_row_start - 1; - } - - if(old_cursor.row <= old_row) { - /* cursor would have moved entirely off the top of the screen; lets just - * bring it within range */ - new_cursor.row = 0, new_cursor.col = old_cursor.col; - if(new_cursor.col >= new_cols) - new_cursor.col = new_cols-1; - } - - /* We really expect the cursor position to be set by now */ - if(active && (new_cursor.row == -1 || new_cursor.col == -1)) { - fprintf(stderr, "screen_resize failed to update cursor position\n"); - abort(); - } - - if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) { - /* Push spare lines to scrollback buffer */ - if(screen->callbacks && screen->callbacks->sb_pushline) - for(int row = 0; row <= old_row; row++) - sb_pushline_from_row(screen, row); - if(active) - statefields->pos.row -= (old_row + 1); - } - if(new_row >= 0 && bufidx == BUFIDX_PRIMARY && - screen->callbacks && screen->callbacks->sb_popline) { - /* Try to backfill rows by popping scrollback buffer */ - while(new_row >= 0) { - if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) - break; - - VTermPos pos = { .row = new_row }; - for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) { - VTermScreenCell *src = &screen->sb_buffer[pos.col]; - ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; - - dst->schar = src->schar; - - dst->pen.bold = src->attrs.bold; - dst->pen.underline = src->attrs.underline; - dst->pen.italic = src->attrs.italic; - dst->pen.blink = src->attrs.blink; - dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse; - dst->pen.conceal = src->attrs.conceal; - dst->pen.strike = src->attrs.strike; - dst->pen.font = src->attrs.font; - dst->pen.small = src->attrs.small; - dst->pen.baseline = src->attrs.baseline; - - dst->pen.fg = src->fg; - dst->pen.bg = src->bg; - - dst->pen.uri = src->uri; - - if(src->width == 2 && pos.col < (new_cols-1)) - (dst + 1)->schar = (uint32_t) -1; - } - for( ; pos.col < new_cols; pos.col++) - clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]); - new_row--; - - if(active) - statefields->pos.row++; - } - } - if(new_row >= 0) { - /* Scroll new rows back up to the top and fill in blanks at the bottom */ - int moverows = new_rows - new_row - 1; - memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], (unsigned long) moverows * new_cols * sizeof(ScreenCell)); - memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0])); - - new_cursor.row -= (new_row + 1); - - for(new_row = moverows; new_row < new_rows; new_row++) { - for(int col = 0; col < new_cols; col++) - clearcell(screen, &new_buffer[new_row * new_cols + col]); - new_lineinfo[new_row] = (VTermLineInfo){ 0 }; - } - } - - vterm_allocator_free(screen->vt, old_buffer); - screen->buffers[bufidx] = new_buffer; - - vterm_allocator_free(screen->vt, old_lineinfo); - statefields->lineinfos[bufidx] = new_lineinfo; - - if(active) - statefields->pos = new_cursor; - - return; -} - -static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user) -{ - VTermScreen *screen = user; - - int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]); - - int old_rows = screen->rows; - int old_cols = screen->cols; - - if(new_cols > old_cols) { - /* Ensure that ->sb_buffer is large enough for a new or and old row */ - if(screen->sb_buffer) - vterm_allocator_free(screen->vt, screen->sb_buffer); - - screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); - } - - resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields); - if(screen->buffers[BUFIDX_ALTSCREEN]) - resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields); - else if(new_rows != old_rows) { - /* We don't need a full resize of the altscreen because it isn't enabled - * but we should at least keep the lineinfo the right size */ - vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]); - - VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); - for(int row = 0; row < new_rows; row++) - new_lineinfo[row] = (VTermLineInfo){ 0 }; - - fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo; - } - - screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; - - screen->rows = new_rows; - screen->cols = new_cols; - - if(new_cols <= old_cols) { - if(screen->sb_buffer) - vterm_allocator_free(screen->vt, screen->sb_buffer); - - screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); - } - - /* TODO: Maaaaybe we can optimise this if there's no reflow happening */ - damagescreen(screen); - - if(screen->callbacks && screen->callbacks->resize) - return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); - - return 1; -} - -static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) -{ - VTermScreen *screen = user; - - if(newinfo->doublewidth != oldinfo->doublewidth || - newinfo->doubleheight != oldinfo->doubleheight) { - for(int col = 0; col < screen->cols; col++) { - ScreenCell *cell = getcell(screen, row, col); - cell->pen.dwl = newinfo->doublewidth; - cell->pen.dhl = newinfo->doubleheight; - } - - VTermRect rect = { - .start_row = row, - .end_row = row + 1, - .start_col = 0, - .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, - }; - damagerect(screen, rect); - - if(newinfo->doublewidth) { - rect.start_col = screen->cols / 2; - rect.end_col = screen->cols; - - erase_internal(rect, 0, user); - } - } - - return 1; -} - -static int sb_clear(void *user) { - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->sb_clear) - if((*screen->callbacks->sb_clear)(screen->cbdata)) - return 1; - - return 0; -} - -static VTermStateCallbacks state_cbs = { - .putglyph = &putglyph, - .movecursor = &movecursor, - .scrollrect = &scrollrect, - .erase = &erase, - .setpenattr = &setpenattr, - .settermprop = &settermprop, - .bell = &bell, - .resize = &resize, - .setlineinfo = &setlineinfo, - .sb_clear = &sb_clear, -}; - -static VTermScreen *screen_new(VTerm *vt) -{ - VTermState *state = vterm_obtain_state(vt); - if(!state) - return NULL; - - VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); - int rows, cols; - - vterm_get_size(vt, &rows, &cols); - - screen->vt = vt; - screen->state = state; - - screen->damage_merge = VTERM_DAMAGE_CELL; - screen->damaged.start_row = -1; - screen->pending_scrollrect.start_row = -1; - - screen->rows = rows; - screen->cols = cols; - - screen->global_reverse = false; - screen->reflow = false; - - screen->callbacks = NULL; - screen->cbdata = NULL; - - screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); - - screen->buffer = screen->buffers[BUFIDX_PRIMARY]; - - screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); - - vterm_state_set_callbacks(screen->state, &state_cbs, screen); - - return screen; -} - -INTERNAL void vterm_screen_free(VTermScreen *screen) -{ - vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]); - if(screen->buffers[BUFIDX_ALTSCREEN]) - vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]); - - vterm_allocator_free(screen->vt, screen->sb_buffer); - - vterm_allocator_free(screen->vt, screen); -} - -void vterm_screen_reset(VTermScreen *screen, int hard) -{ - screen->damaged.start_row = -1; - screen->pending_scrollrect.start_row = -1; - vterm_state_reset(screen->state, hard); - vterm_screen_flush_damage(screen); -} - -size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, const VTermRect rect) -{ - size_t outpos = 0; - int padding = 0; - -#define PUT(bytes, thislen) \ - if(true) { \ - if(buffer && outpos + thislen <= len) \ - memcpy((char *)buffer + outpos, bytes, thislen); \ - outpos += thislen; \ - } \ - - for(int row = rect.start_row; row < rect.end_row; row++) { - for(int col = rect.start_col; col < rect.end_col; col++) { - ScreenCell *cell = getcell(screen, row, col); - - if(cell->schar == 0) - // Erased cell, might need a space - padding++; - else if(cell->schar == (uint32_t)-1) - // Gap behind a double-width char, do nothing - ; - else { - while(padding) { - PUT(" ", 1); - padding--; - } - char buf[MAX_SCHAR_SIZE + 1]; - size_t thislen = schar_get(buf, cell->schar); - PUT(buf, thislen); - } - } - - if(row < rect.end_row - 1) { - PUT("\n", 1); - padding = 0; - } - } - - return outpos; -} - -/* Copy internal to external representation of a screen cell */ -int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) -{ - ScreenCell *intcell = getcell(screen, pos.row, pos.col); - if(!intcell) - return 0; - - cell->schar = intcell->schar; - - cell->attrs.bold = intcell->pen.bold; - cell->attrs.underline = intcell->pen.underline; - cell->attrs.italic = intcell->pen.italic; - cell->attrs.blink = intcell->pen.blink; - cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; - cell->attrs.conceal = intcell->pen.conceal; - cell->attrs.strike = intcell->pen.strike; - cell->attrs.font = intcell->pen.font; - cell->attrs.small = intcell->pen.small; - cell->attrs.baseline = intcell->pen.baseline; - - cell->attrs.dwl = intcell->pen.dwl; - cell->attrs.dhl = intcell->pen.dhl; - - cell->fg = intcell->pen.fg; - cell->bg = intcell->pen.bg; - - cell->uri = intcell->pen.uri; - - if(pos.col < (screen->cols - 1) && - getcell(screen, pos.row, pos.col + 1)->schar == (uint32_t)-1) - cell->width = 2; - else - cell->width = 1; - - return 1; -} - -int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) -{ - /* This cell is EOL if this and every cell to the right is black */ - for(; pos.col < screen->cols; pos.col++) { - ScreenCell *cell = getcell(screen, pos.row, pos.col); - if(cell->schar != 0) - return 0; - } - - return 1; -} - -VTermScreen *vterm_obtain_screen(VTerm *vt) -{ - if(vt->screen) - return vt->screen; - - VTermScreen *screen = screen_new(vt); - vt->screen = screen; - - return screen; -} - -void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow) -{ - screen->reflow = reflow; -} - -#undef vterm_screen_set_reflow -void vterm_screen_set_reflow(VTermScreen *screen, bool reflow) -{ - vterm_screen_enable_reflow(screen, reflow); -} - -void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) -{ - if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) { - int rows, cols; - vterm_get_size(screen->vt, &rows, &cols); - - screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols); - } -} - -void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) -{ - screen->callbacks = callbacks; - screen->cbdata = user; -} - -void *vterm_screen_get_cbdata(VTermScreen *screen) -{ - return screen->cbdata; -} - -void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user) -{ - vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); -} - -void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) -{ - return vterm_state_get_unrecognised_fbdata(screen->state); -} - -void vterm_screen_flush_damage(VTermScreen *screen) -{ - if(screen->pending_scrollrect.start_row != -1) { - vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, - moverect_user, erase_user, screen); - - screen->pending_scrollrect.start_row = -1; - } - - if(screen->damaged.start_row != -1) { - if(screen->callbacks && screen->callbacks->damage) - (*screen->callbacks->damage)(screen->damaged, screen->cbdata); - - screen->damaged.start_row = -1; - } -} - -void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) -{ - vterm_screen_flush_damage(screen); - screen->damage_merge = size; -} - -static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) -{ - if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) - return 1; - if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) - return 1; - if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) - return 1; - if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) - return 1; - if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) - return 1; - if((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) - return 1; - if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) - return 1; - if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) - return 1; - if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) - return 1; - if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) - return 1; - if((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) - return 1; - if((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) - return 1; - if((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) - return 1; - - return 0; -} - -int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) -{ - ScreenCell *target = getcell(screen, pos.row, pos.col); - - // TODO: bounds check - extent->start_row = pos.row; - extent->end_row = pos.row + 1; - - if(extent->start_col < 0) - extent->start_col = 0; - if(extent->end_col < 0) - extent->end_col = screen->cols; - - int col; - - for(col = pos.col - 1; col >= extent->start_col; col--) - if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) - break; - extent->start_col = col + 1; - - for(col = pos.col + 1; col < extent->end_col; col++) - if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) - break; - extent->end_col = col - 1; - - return 1; -} - -void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) -{ - vterm_state_convert_color_to_rgb(screen->state, col); -} - -static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer) -{ - for(int row = 0; row <= screen->rows - 1; row++) - for(int col = 0; col <= screen->cols - 1; col++) { - ScreenCell *cell = &buffer[row * screen->cols + col]; - if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg)) - cell->pen.fg = screen->pen.fg; - if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg)) - cell->pen.bg = screen->pen.bg; - } -} - -void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg) -{ - vterm_state_set_default_colors(screen->state, default_fg, default_bg); - - if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) { - screen->pen.fg = *default_fg; - screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_FG; - } - - if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) { - screen->pen.bg = *default_bg; - screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_BG; - } - - reset_default_colours(screen, screen->buffers[0]); - if(screen->buffers[1]) - reset_default_colours(screen, screen->buffers[1]); -} diff --git a/src/vterm/state.c b/src/vterm/state.c deleted file mode 100644 index d546672e67..0000000000 --- a/src/vterm/state.c +++ /dev/null @@ -1,2281 +0,0 @@ -#include "vterm_internal.h" - -#include -#include - -#include "nvim/grid.h" -#include "nvim/mbyte.h" - -#define strneq(a,b,n) (strncmp(a,b,n)==0) - -#if defined(DEBUG) && DEBUG > 1 -# define DEBUG_GLYPH_COMBINE -#endif - -/* Some convenient wrappers to make callback functions easier */ - -static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos) -{ - VTermGlyphInfo info = { - .schar = schar, - .width = width, - .protected_cell = state->protected_cell, - .dwl = state->lineinfo[pos.row].doublewidth, - .dhl = state->lineinfo[pos.row].doubleheight, - }; - - if(state->callbacks && state->callbacks->putglyph) - if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) - return; - - DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); -} - -static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) -{ - if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) - return; - - if(cancel_phantom) - state->at_phantom = 0; - - if(state->callbacks && state->callbacks->movecursor) - if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) - return; -} - -static void erase(VTermState *state, VTermRect rect, int selective) -{ - if(rect.end_col == state->cols) { - /* If we're erasing the final cells of any lines, cancel the continuation - * marker on the subsequent line - */ - for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) - state->lineinfo[row].continuation = 0; - } - - if(state->callbacks && state->callbacks->erase) - if((*state->callbacks->erase)(rect, selective, state->cbdata)) - return; -} - -static VTermState *vterm_state_new(VTerm *vt) -{ - VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); - - state->vt = vt; - - state->rows = vt->rows; - state->cols = vt->cols; - - state->mouse_col = 0; - state->mouse_row = 0; - state->mouse_buttons = 0; - - state->mouse_protocol = MOUSE_X10; - - state->callbacks = NULL; - state->cbdata = NULL; - - state->selection.callbacks = NULL; - state->selection.user = NULL; - state->selection.buffer = NULL; - - vterm_state_newpen(state); - - state->bold_is_highbright = 0; - - state->combine_pos.row = -1; - - state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); - - state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); - /* TODO: Make an 'enable' function */ - state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); - state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; - - state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); - if(*state->encoding_utf8.enc->init) - (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); - - return state; -} - -INTERNAL void vterm_state_free(VTermState *state) -{ - vterm_allocator_free(state->vt, state->tabstops); - vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); - if(state->lineinfos[BUFIDX_ALTSCREEN]) - vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); - vterm_allocator_free(state->vt, state); -} - -static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) -{ - if(!downward && !rightward) - return; - - int rows = rect.end_row - rect.start_row; - if(downward > rows) - downward = rows; - else if(downward < -rows) - downward = -rows; - - int cols = rect.end_col - rect.start_col; - if(rightward > cols) - rightward = cols; - else if(rightward < -cols) - rightward = -cols; - - // Update lineinfo if full line - if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { - int height = rect.end_row - rect.start_row - abs(downward); - - if(downward > 0) { - memmove(state->lineinfo + rect.start_row, - state->lineinfo + rect.start_row + downward, - height * sizeof(state->lineinfo[0])); - for(int row = rect.end_row - downward; row < rect.end_row; row++) - state->lineinfo[row] = (VTermLineInfo){ 0 }; - } - else { - memmove(state->lineinfo + rect.start_row - downward, - state->lineinfo + rect.start_row, - height * sizeof(state->lineinfo[0])); - for(int row = rect.start_row; row < rect.start_row - downward; row++) - state->lineinfo[row] = (VTermLineInfo){ 0 }; - } - } - - if(state->callbacks && state->callbacks->scrollrect) - if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) - return; - - if(state->callbacks) - vterm_scroll_rect(rect, downward, rightward, - state->callbacks->moverect, state->callbacks->erase, state->cbdata); -} - -static void linefeed(VTermState *state) -{ - if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { - VTermRect rect = { - .start_row = state->scrollregion_top, - .end_row = SCROLLREGION_BOTTOM(state), - .start_col = SCROLLREGION_LEFT(state), - .end_col = SCROLLREGION_RIGHT(state), - }; - - scroll(state, rect, 1, 0); - } - else if(state->pos.row < state->rows-1) - state->pos.row++; -} - -static void set_col_tabstop(VTermState *state, int col) -{ - unsigned char mask = 1 << (col & 7); - state->tabstops[col >> 3] |= mask; -} - -static void clear_col_tabstop(VTermState *state, int col) -{ - unsigned char mask = 1 << (col & 7); - state->tabstops[col >> 3] &= ~mask; -} - -static int is_col_tabstop(VTermState *state, int col) -{ - unsigned char mask = 1 << (col & 7); - return state->tabstops[col >> 3] & mask; -} - -static int is_cursor_in_scrollregion(const VTermState *state) -{ - if(state->pos.row < state->scrollregion_top || - state->pos.row >= SCROLLREGION_BOTTOM(state)) - return 0; - if(state->pos.col < SCROLLREGION_LEFT(state) || - state->pos.col >= SCROLLREGION_RIGHT(state)) - return 0; - - return 1; -} - -static void tab(VTermState *state, int count, int direction) -{ - while(count > 0) { - if(direction > 0) { - if(state->pos.col >= THISROWWIDTH(state)-1) - return; - - state->pos.col++; - } - else if(direction < 0) { - if(state->pos.col < 1) - return; - - state->pos.col--; - } - - if(is_col_tabstop(state, state->pos.col)) - count--; - } -} - -#define NO_FORCE 0 -#define FORCE 1 - -#define DWL_OFF 0 -#define DWL_ON 1 - -#define DHL_OFF 0 -#define DHL_TOP 1 -#define DHL_BOTTOM 2 - -static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) -{ - VTermLineInfo info = state->lineinfo[row]; - - if(dwl == DWL_OFF) - info.doublewidth = DWL_OFF; - else if(dwl == DWL_ON) - info.doublewidth = DWL_ON; - // else -1 to ignore - - if(dhl == DHL_OFF) - info.doubleheight = DHL_OFF; - else if(dhl == DHL_TOP) - info.doubleheight = DHL_TOP; - else if(dhl == DHL_BOTTOM) - info.doubleheight = DHL_BOTTOM; - - if((state->callbacks && - state->callbacks->setlineinfo && - (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) - || force) - state->lineinfo[row] = info; -} - -static int on_text(const char bytes[], size_t len, void *user) -{ - VTermState *state = user; - - VTermPos oldpos = state->pos; - - uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); - size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); - - int npoints = 0; - size_t eaten = 0; - - VTermEncodingInstance *encoding = - state->gsingle_set ? &state->encoding[state->gsingle_set] : - !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : - state->vt->mode.utf8 ? &state->encoding_utf8 : - &state->encoding[state->gr_set]; - - (*encoding->enc->decode)(encoding->enc, encoding->data, - codepoints, &npoints, state->gsingle_set ? 1 : maxpoints, - bytes, &eaten, len); - - /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet - * for even a single codepoint - */ - if(!npoints) - return eaten; - - if(state->gsingle_set && npoints) - state->gsingle_set = 0; - - int i = 0; - GraphemeState grapheme_state = GRAPHEME_STATE_INIT; - size_t grapheme_len = 0; - bool recombine = false; - - /* See if the cursor has moved since */ - if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { - /* This is a combining char. that needs to be merged with the previous - * glyph output */ - if(utf_iscomposing(state->grapheme_last, codepoints[i], &state->grapheme_state)) { - /* Find where we need to append these combining chars */ - grapheme_len = state->grapheme_len; - grapheme_state = state->grapheme_state; - state->pos.col = state->combine_pos.col; - recombine = true; - } else { - DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); - } - } - - while(i < npoints) { - // Try to find combining characters following this - do { - if (grapheme_len < sizeof(state->grapheme_buf) - 4) { - grapheme_len += utf_char2bytes(codepoints[i], state->grapheme_buf + grapheme_len); - } - i++; - } while(i < npoints && utf_iscomposing(codepoints[i-1], codepoints[i], &grapheme_state)); - - int width = utf_ptr2cells_len(state->grapheme_buf, grapheme_len); - - if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { - linefeed(state); - state->pos.col = 0; - state->at_phantom = 0; - state->lineinfo[state->pos.row].continuation = 1; - } - - if(state->mode.insert && !recombine) { - /* TODO: This will be a little inefficient for large bodies of text, as - * it'll have to 'ICH' effectively before every glyph. We should scan - * ahead and ICH as many times as required - */ - VTermRect rect = { - .start_row = state->pos.row, - .end_row = state->pos.row + 1, - .start_col = state->pos.col, - .end_col = THISROWWIDTH(state), - }; - scroll(state, rect, 0, -1); - } - - schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len); - putglyph(state, sc, width, state->pos); - - if(i == npoints) { - /* End of the buffer. Save the chars in case we have to combine with - * more on the next call */ - state->grapheme_len = grapheme_len; - state->grapheme_last = codepoints[i-1]; - state->grapheme_state = grapheme_state; - state->combine_width = width; - state->combine_pos = state->pos; - } else { - grapheme_len = 0; - recombine = false; - } - - if(state->pos.col + width >= THISROWWIDTH(state)) { - if(state->mode.autowrap) - state->at_phantom = 1; - } - else { - state->pos.col += width; - } - } - - updatecursor(state, &oldpos, 0); - -#ifdef DEBUG - if(state->pos.row < 0 || state->pos.row >= state->rows || - state->pos.col < 0 || state->pos.col >= state->cols) { - fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", - state->pos.row, state->pos.col); - abort(); - } -#endif - - return eaten; -} - -static int on_control(unsigned char control, void *user) -{ - VTermState *state = user; - - VTermPos oldpos = state->pos; - - switch(control) { - case 0x07: // BEL - ECMA-48 8.3.3 - if(state->callbacks && state->callbacks->bell) - (*state->callbacks->bell)(state->cbdata); - break; - - case 0x08: // BS - ECMA-48 8.3.5 - if(state->pos.col > 0) - state->pos.col--; - break; - - case 0x09: // HT - ECMA-48 8.3.60 - tab(state, 1, +1); - break; - - case 0x0a: // LF - ECMA-48 8.3.74 - case 0x0b: // VT - case 0x0c: // FF - linefeed(state); - if(state->mode.newline) - state->pos.col = 0; - break; - - case 0x0d: // CR - ECMA-48 8.3.15 - state->pos.col = 0; - break; - - case 0x0e: // LS1 - ECMA-48 8.3.76 - state->gl_set = 1; - break; - - case 0x0f: // LS0 - ECMA-48 8.3.75 - state->gl_set = 0; - break; - - case 0x84: // IND - DEPRECATED but implemented for completeness - linefeed(state); - break; - - case 0x85: // NEL - ECMA-48 8.3.86 - linefeed(state); - state->pos.col = 0; - break; - - case 0x88: // HTS - ECMA-48 8.3.62 - set_col_tabstop(state, state->pos.col); - break; - - case 0x8d: // RI - ECMA-48 8.3.104 - if(state->pos.row == state->scrollregion_top) { - VTermRect rect = { - .start_row = state->scrollregion_top, - .end_row = SCROLLREGION_BOTTOM(state), - .start_col = SCROLLREGION_LEFT(state), - .end_col = SCROLLREGION_RIGHT(state), - }; - - scroll(state, rect, -1, 0); - } - else if(state->pos.row > 0) - state->pos.row--; - break; - - case 0x8e: // SS2 - ECMA-48 8.3.141 - state->gsingle_set = 2; - break; - - case 0x8f: // SS3 - ECMA-48 8.3.142 - state->gsingle_set = 3; - break; - - default: - if(state->fallbacks && state->fallbacks->control) - if((*state->fallbacks->control)(control, state->fbdata)) - return 1; - - return 0; - } - - updatecursor(state, &oldpos, 1); - -#ifdef DEBUG - if(state->pos.row < 0 || state->pos.row >= state->rows || - state->pos.col < 0 || state->pos.col >= state->cols) { - fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", - control, state->pos.row, state->pos.col); - abort(); - } -#endif - - return 1; -} - -static int settermprop_bool(VTermState *state, VTermProp prop, int v) -{ - VTermValue val = { .boolean = v }; - return vterm_state_set_termprop(state, prop, &val); -} - -static int settermprop_int(VTermState *state, VTermProp prop, int v) -{ - VTermValue val = { .number = v }; - return vterm_state_set_termprop(state, prop, &val); -} - -static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) -{ - VTermValue val = { .string = frag }; - return vterm_state_set_termprop(state, prop, &val); -} - -static void savecursor(VTermState *state, int save) -{ - if(save) { - state->saved.pos = state->pos; - state->saved.mode.cursor_visible = state->mode.cursor_visible; - state->saved.mode.cursor_blink = state->mode.cursor_blink; - state->saved.mode.cursor_shape = state->mode.cursor_shape; - - vterm_state_savepen(state, 1); - } - else { - VTermPos oldpos = state->pos; - - state->pos = state->saved.pos; - - settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); - settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); - - vterm_state_savepen(state, 0); - - updatecursor(state, &oldpos, 1); - } -} - -static int on_escape(const char *bytes, size_t len, void *user) -{ - VTermState *state = user; - - /* Easier to decode this from the first byte, even though the final - * byte terminates it - */ - switch(bytes[0]) { - case ' ': - if(len != 2) - return 0; - - switch(bytes[1]) { - case 'F': // S7C1T - state->vt->mode.ctrl8bit = 0; - break; - - case 'G': // S8C1T - state->vt->mode.ctrl8bit = 1; - break; - - default: - return 0; - } - return 2; - - case '#': - if(len != 2) - return 0; - - switch(bytes[1]) { - case '3': // DECDHL top - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); - break; - - case '4': // DECDHL bottom - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); - break; - - case '5': // DECSWL - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); - break; - - case '6': // DECDWL - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); - break; - - case '8': // DECALN - { - VTermPos pos; - schar_T E = schar_from_ascii('E'); // E - for(pos.row = 0; pos.row < state->rows; pos.row++) - for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) - putglyph(state, E, 1, pos); - break; - } - - default: - return 0; - } - return 2; - - case '(': case ')': case '*': case '+': // SCS - if(len != 2) - return 0; - - { - int setnum = bytes[0] - 0x28; - VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); - - if(newenc) { - state->encoding[setnum].enc = newenc; - - if(newenc->init) - (*newenc->init)(newenc, state->encoding[setnum].data); - } - } - - return 2; - - case '7': // DECSC - savecursor(state, 1); - return 1; - - case '8': // DECRC - savecursor(state, 0); - return 1; - - case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 - return 1; - - case '=': // DECKPAM - state->mode.keypad = 1; - return 1; - - case '>': // DECKPNM - state->mode.keypad = 0; - return 1; - - case 'c': // RIS - ECMA-48 8.3.105 - { - VTermPos oldpos = state->pos; - vterm_state_reset(state, 1); - if(state->callbacks && state->callbacks->movecursor) - (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); - return 1; - } - - case 'n': // LS2 - ECMA-48 8.3.78 - state->gl_set = 2; - return 1; - - case 'o': // LS3 - ECMA-48 8.3.80 - state->gl_set = 3; - return 1; - - case '~': // LS1R - ECMA-48 8.3.77 - state->gr_set = 1; - return 1; - - case '}': // LS2R - ECMA-48 8.3.79 - state->gr_set = 2; - return 1; - - case '|': // LS3R - ECMA-48 8.3.81 - state->gr_set = 3; - return 1; - - default: - return 0; - } -} - -static void set_mode(VTermState *state, int num, int val) -{ - switch(num) { - case 4: // IRM - ECMA-48 7.2.10 - state->mode.insert = val; - break; - - case 20: // LNM - ANSI X3.4-1977 - state->mode.newline = val; - break; - - default: - DEBUG_LOG("libvterm: Unknown mode %d\n", num); - return; - } -} - -static void set_dec_mode(VTermState *state, int num, int val) -{ - switch(num) { - case 1: - state->mode.cursor = val; - break; - - case 5: // DECSCNM - screen mode - settermprop_bool(state, VTERM_PROP_REVERSE, val); - break; - - case 6: // DECOM - origin mode - { - VTermPos oldpos = state->pos; - state->mode.origin = val; - state->pos.row = state->mode.origin ? state->scrollregion_top : 0; - state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; - updatecursor(state, &oldpos, 1); - } - break; - - case 7: - state->mode.autowrap = val; - break; - - case 12: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); - break; - - case 25: - settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); - break; - - case 69: // DECVSSM - vertical split screen mode - // DECLRMM - left/right margin mode - state->mode.leftrightmargin = val; - if(val) { - // Setting DECVSSM must clear doublewidth/doubleheight state of every line - for(int row = 0; row < state->rows; row++) - set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); - } - - break; - - case 1000: - case 1002: - case 1003: - settermprop_int(state, VTERM_PROP_MOUSE, - !val ? VTERM_PROP_MOUSE_NONE : - (num == 1000) ? VTERM_PROP_MOUSE_CLICK : - (num == 1002) ? VTERM_PROP_MOUSE_DRAG : - VTERM_PROP_MOUSE_MOVE); - break; - - case 1004: - settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val); - state->mode.report_focus = val; - break; - - case 1005: - state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; - break; - - case 1006: - state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; - break; - - case 1015: - state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; - break; - - case 1047: - settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); - break; - - case 1048: - savecursor(state, val); - break; - - case 1049: - settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); - savecursor(state, val); - break; - - case 2004: - state->mode.bracketpaste = val; - break; - - default: - DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); - return; - } -} - -static void request_dec_mode(VTermState *state, int num) -{ - int reply; - - switch(num) { - case 1: - reply = state->mode.cursor; - break; - - case 5: - reply = state->mode.screen; - break; - - case 6: - reply = state->mode.origin; - break; - - case 7: - reply = state->mode.autowrap; - break; - - case 12: - reply = state->mode.cursor_blink; - break; - - case 25: - reply = state->mode.cursor_visible; - break; - - case 69: - reply = state->mode.leftrightmargin; - break; - - case 1000: - reply = state->mouse_flags == MOUSE_WANT_CLICK; - break; - - case 1002: - reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); - break; - - case 1003: - reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); - break; - - case 1004: - reply = state->mode.report_focus; - break; - - case 1005: - reply = state->mouse_protocol == MOUSE_UTF8; - break; - - case 1006: - reply = state->mouse_protocol == MOUSE_SGR; - break; - - case 1015: - reply = state->mouse_protocol == MOUSE_RXVT; - break; - - case 1047: - reply = state->mode.alt_screen; - break; - - case 2004: - reply = state->mode.bracketpaste; - break; - - default: - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); - return; - } - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); -} - -static void request_version_string(VTermState *state) -{ - vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", - VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); -} - -static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) -{ - VTermState *state = user; - int leader_byte = 0; - int intermed_byte = 0; - int cancel_phantom = 1; - - if(leader && leader[0]) { - if(leader[1]) // longer than 1 char - return 0; - - switch(leader[0]) { - case '?': - case '>': - leader_byte = leader[0]; - break; - default: - return 0; - } - } - - if(intermed && intermed[0]) { - if(intermed[1]) // longer than 1 char - return 0; - - switch(intermed[0]) { - case ' ': - case '!': - case '"': - case '$': - case '\'': - intermed_byte = intermed[0]; - break; - default: - return 0; - } - } - - VTermPos oldpos = state->pos; - - // Some temporaries for later code - int count, val; - int row, col; - VTermRect rect; - int selective; - -#define LBOUND(v,min) if((v) < (min)) (v) = (min) -#define UBOUND(v,max) if((v) > (max)) (v) = (max) - -#define LEADER(l,b) ((l << 8) | b) -#define INTERMED(i,b) ((i << 16) | b) - - switch(intermed_byte << 16 | leader_byte << 8 | command) { - case 0x40: // ICH - ECMA-48 8.3.64 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; - if(state->mode.leftrightmargin) - rect.end_col = SCROLLREGION_RIGHT(state); - else - rect.end_col = THISROWWIDTH(state); - - scroll(state, rect, 0, -count); - - break; - - case 0x41: // CUU - ECMA-48 8.3.22 - count = CSI_ARG_COUNT(args[0]); - state->pos.row -= count; - state->at_phantom = 0; - break; - - case 0x42: // CUD - ECMA-48 8.3.19 - count = CSI_ARG_COUNT(args[0]); - state->pos.row += count; - state->at_phantom = 0; - break; - - case 0x43: // CUF - ECMA-48 8.3.20 - count = CSI_ARG_COUNT(args[0]); - state->pos.col += count; - state->at_phantom = 0; - break; - - case 0x44: // CUB - ECMA-48 8.3.18 - count = CSI_ARG_COUNT(args[0]); - state->pos.col -= count; - state->at_phantom = 0; - break; - - case 0x45: // CNL - ECMA-48 8.3.12 - count = CSI_ARG_COUNT(args[0]); - state->pos.col = 0; - state->pos.row += count; - state->at_phantom = 0; - break; - - case 0x46: // CPL - ECMA-48 8.3.13 - count = CSI_ARG_COUNT(args[0]); - state->pos.col = 0; - state->pos.row -= count; - state->at_phantom = 0; - break; - - case 0x47: // CHA - ECMA-48 8.3.9 - val = CSI_ARG_OR(args[0], 1); - state->pos.col = val-1; - state->at_phantom = 0; - break; - - case 0x48: // CUP - ECMA-48 8.3.21 - row = CSI_ARG_OR(args[0], 1); - col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); - // zero-based - state->pos.row = row-1; - state->pos.col = col-1; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - state->at_phantom = 0; - break; - - case 0x49: // CHT - ECMA-48 8.3.10 - count = CSI_ARG_COUNT(args[0]); - tab(state, count, +1); - break; - - case 0x4a: // ED - ECMA-48 8.3.39 - case LEADER('?', 0x4a): // DECSED - Selective Erase in Display - selective = (leader_byte == '?'); - switch(CSI_ARG(args[0])) { - case CSI_ARG_MISSING: - case 0: - rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; rect.end_col = state->cols; - if(rect.end_col > rect.start_col) - erase(state, rect, selective); - - rect.start_row = state->pos.row + 1; rect.end_row = state->rows; - rect.start_col = 0; - for(int row_ = rect.start_row; row_ < rect.end_row; row_++) - set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); - if(rect.end_row > rect.start_row) - erase(state, rect, selective); - break; - - case 1: - rect.start_row = 0; rect.end_row = state->pos.row; - rect.start_col = 0; rect.end_col = state->cols; - for(int row_ = rect.start_row; row_ < rect.end_row; row_++) - set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); - if(rect.end_col > rect.start_col) - erase(state, rect, selective); - - rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; - rect.end_col = state->pos.col + 1; - if(rect.end_row > rect.start_row) - erase(state, rect, selective); - break; - - case 2: - rect.start_row = 0; rect.end_row = state->rows; - rect.start_col = 0; rect.end_col = state->cols; - for(int row_ = rect.start_row; row_ < rect.end_row; row_++) - set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); - erase(state, rect, selective); - break; - - case 3: - if(state->callbacks && state->callbacks->sb_clear) - if((*state->callbacks->sb_clear)(state->cbdata)) - return 1; - break; - } - break; - - case 0x4b: // EL - ECMA-48 8.3.41 - case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line - selective = (leader_byte == '?'); - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - - switch(CSI_ARG(args[0])) { - case CSI_ARG_MISSING: - case 0: - rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; - case 1: - rect.start_col = 0; rect.end_col = state->pos.col + 1; break; - case 2: - rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; - default: - return 0; - } - - if(rect.end_col > rect.start_col) - erase(state, rect, selective); - - break; - - case 0x4c: // IL - ECMA-48 8.3.67 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, -count, 0); - - break; - - case 0x4d: // DL - ECMA-48 8.3.32 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, count, 0); - - break; - - case 0x50: // DCH - ECMA-48 8.3.26 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; - if(state->mode.leftrightmargin) - rect.end_col = SCROLLREGION_RIGHT(state); - else - rect.end_col = THISROWWIDTH(state); - - scroll(state, rect, 0, count); - - break; - - case 0x53: // SU - ECMA-48 8.3.147 - count = CSI_ARG_COUNT(args[0]); - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, count, 0); - - break; - - case 0x54: // SD - ECMA-48 8.3.113 - count = CSI_ARG_COUNT(args[0]); - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, -count, 0); - - break; - - case 0x58: // ECH - ECMA-48 8.3.38 - count = CSI_ARG_COUNT(args[0]); - - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; - rect.end_col = state->pos.col + count; - UBOUND(rect.end_col, THISROWWIDTH(state)); - - erase(state, rect, 0); - break; - - case 0x5a: // CBT - ECMA-48 8.3.7 - count = CSI_ARG_COUNT(args[0]); - tab(state, count, -1); - break; - - case 0x60: // HPA - ECMA-48 8.3.57 - col = CSI_ARG_OR(args[0], 1); - state->pos.col = col-1; - state->at_phantom = 0; - break; - - case 0x61: // HPR - ECMA-48 8.3.59 - count = CSI_ARG_COUNT(args[0]); - state->pos.col += count; - state->at_phantom = 0; - break; - - case 0x62: { // REP - ECMA-48 8.3.103 - const int row_width = THISROWWIDTH(state); - count = CSI_ARG_COUNT(args[0]); - col = state->pos.col + count; - UBOUND(col, row_width); - schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len); - while (state->pos.col < col) { - putglyph(state, sc, state->combine_width, state->pos); - state->pos.col += state->combine_width; - } - if (state->pos.col + state->combine_width >= row_width) { - if (state->mode.autowrap) { - state->at_phantom = 1; - cancel_phantom = 0; - } - } - break; - } - - case 0x63: // DA - ECMA-48 8.3.24 - val = CSI_ARG_OR(args[0], 0); - if(val == 0) - // DEC VT100 response - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); - break; - - case LEADER('>', 0x63): // DEC secondary Device Attributes - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); - break; - - case 0x64: // VPA - ECMA-48 8.3.158 - row = CSI_ARG_OR(args[0], 1); - state->pos.row = row-1; - if(state->mode.origin) - state->pos.row += state->scrollregion_top; - state->at_phantom = 0; - break; - - case 0x65: // VPR - ECMA-48 8.3.160 - count = CSI_ARG_COUNT(args[0]); - state->pos.row += count; - state->at_phantom = 0; - break; - - case 0x66: // HVP - ECMA-48 8.3.63 - row = CSI_ARG_OR(args[0], 1); - col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); - // zero-based - state->pos.row = row-1; - state->pos.col = col-1; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - state->at_phantom = 0; - break; - - case 0x67: // TBC - ECMA-48 8.3.154 - val = CSI_ARG_OR(args[0], 0); - - switch(val) { - case 0: - clear_col_tabstop(state, state->pos.col); - break; - case 3: - case 5: - for(col = 0; col < state->cols; col++) - clear_col_tabstop(state, col); - break; - case 1: - case 2: - case 4: - break; - /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ - default: - return 0; - } - break; - - case 0x68: // SM - ECMA-48 8.3.125 - if(!CSI_ARG_IS_MISSING(args[0])) - set_mode(state, CSI_ARG(args[0]), 1); - break; - - case LEADER('?', 0x68): // DEC private mode set - for(int i = 0; i < argcount; i++) { - if(!CSI_ARG_IS_MISSING(args[i])) - set_dec_mode(state, CSI_ARG(args[i]), 1); - } - break; - - case 0x6a: // HPB - ECMA-48 8.3.58 - count = CSI_ARG_COUNT(args[0]); - state->pos.col -= count; - state->at_phantom = 0; - break; - - case 0x6b: // VPB - ECMA-48 8.3.159 - count = CSI_ARG_COUNT(args[0]); - state->pos.row -= count; - state->at_phantom = 0; - break; - - case 0x6c: // RM - ECMA-48 8.3.106 - if(!CSI_ARG_IS_MISSING(args[0])) - set_mode(state, CSI_ARG(args[0]), 0); - break; - - case LEADER('?', 0x6c): // DEC private mode reset - for(int i = 0; i < argcount; i++) { - if(!CSI_ARG_IS_MISSING(args[i])) - set_dec_mode(state, CSI_ARG(args[i]), 0); - } - break; - - case 0x6d: // SGR - ECMA-48 8.3.117 - vterm_state_setpen(state, args, argcount); - break; - - case LEADER('?', 0x6d): // DECSGR - /* No actual DEC terminal recognised these, but some printers did. These - * are alternative ways to request subscript/superscript/off - */ - for(int argi = 0; argi < argcount; argi++) { - long arg; - switch(arg = CSI_ARG(args[argi])) { - case 4: // Superscript on - arg = 73; - vterm_state_setpen(state, &arg, 1); - break; - case 5: // Subscript on - arg = 74; - vterm_state_setpen(state, &arg, 1); - break; - case 24: // Super+subscript off - arg = 75; - vterm_state_setpen(state, &arg, 1); - break; - } - } - break; - - case 0x6e: // DSR - ECMA-48 8.3.35 - case LEADER('?', 0x6e): // DECDSR - val = CSI_ARG_OR(args[0], 0); - - { - char *qmark = (leader_byte == '?') ? "?" : ""; - - switch(val) { - case 0: case 1: case 2: case 3: case 4: - // ignore - these are replies - break; - case 5: - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); - break; - case 6: // CPR - cursor position report - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); - break; - } - } - break; - - - case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset - vterm_state_reset(state, 0); - break; - - case LEADER('?', INTERMED('$', 0x70)): - request_dec_mode(state, CSI_ARG(args[0])); - break; - - case LEADER('>', 0x71): // XTVERSION - xterm query version string - request_version_string(state); - break; - - case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape - val = CSI_ARG_OR(args[0], 1); - - switch(val) { - case 0: case 1: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); - break; - case 2: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); - break; - case 3: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); - break; - case 4: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); - break; - case 5: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); - break; - case 6: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); - break; - } - - break; - - case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute - val = CSI_ARG_OR(args[0], 0); - - switch(val) { - case 0: case 2: - state->protected_cell = 0; - break; - case 1: - state->protected_cell = 1; - break; - } - - break; - - case 0x72: // DECSTBM - DEC custom - state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; - state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); - LBOUND(state->scrollregion_top, 0); - UBOUND(state->scrollregion_top, state->rows); - LBOUND(state->scrollregion_bottom, -1); - if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) - state->scrollregion_bottom = -1; - else - UBOUND(state->scrollregion_bottom, state->rows); - - if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { - // Invalid - state->scrollregion_top = 0; - state->scrollregion_bottom = -1; - } - - // Setting the scrolling region restores the cursor to the home position - state->pos.row = 0; - state->pos.col = 0; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - - break; - - case 0x73: // DECSLRM - DEC custom - // Always allow setting these margins, just they won't take effect without DECVSSM - state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; - state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); - LBOUND(state->scrollregion_left, 0); - UBOUND(state->scrollregion_left, state->cols); - LBOUND(state->scrollregion_right, -1); - if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) - state->scrollregion_right = -1; - else - UBOUND(state->scrollregion_right, state->cols); - - if(state->scrollregion_right > -1 && - state->scrollregion_right <= state->scrollregion_left) { - // Invalid - state->scrollregion_left = 0; - state->scrollregion_right = -1; - } - - // Setting the scrolling region restores the cursor to the home position - state->pos.row = 0; - state->pos.col = 0; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - - break; - - case INTERMED('\'', 0x7D): // DECIC - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = state->pos.col; - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, 0, -count); - - break; - - case INTERMED('\'', 0x7E): // DECDC - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = state->pos.col; - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, 0, count); - - break; - - default: - if(state->fallbacks && state->fallbacks->csi) - if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) - return 1; - - return 0; - } - - if(state->mode.origin) { - LBOUND(state->pos.row, state->scrollregion_top); - UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); - LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); - UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); - } - else { - LBOUND(state->pos.row, 0); - UBOUND(state->pos.row, state->rows-1); - LBOUND(state->pos.col, 0); - UBOUND(state->pos.col, THISROWWIDTH(state)-1); - } - - updatecursor(state, &oldpos, cancel_phantom); - -#ifdef DEBUG - if(state->pos.row < 0 || state->pos.row >= state->rows || - state->pos.col < 0 || state->pos.col >= state->cols) { - fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", - command, state->pos.row, state->pos.col); - abort(); - } - - if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { - fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", - command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); - abort(); - } - - if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { - fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", - command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); - abort(); - } -#endif - - return 1; -} - -static char base64_one(uint8_t b) -{ - if(b < 26) - return 'A' + b; - else if(b < 52) - return 'a' + b - 26; - else if(b < 62) - return '0' + b - 52; - else if(b == 62) - return '+'; - else if(b == 63) - return '/'; - return 0; -} - -static uint8_t unbase64one(char c) -{ - if(c >= 'A' && c <= 'Z') - return c - 'A'; - else if(c >= 'a' && c <= 'z') - return c - 'a' + 26; - else if(c >= '0' && c <= '9') - return c - '0' + 52; - else if(c == '+') - return 62; - else if(c == '/') - return 63; - - return 0xFF; -} - -static void osc_selection(VTermState *state, VTermStringFragment frag) -{ - if(frag.initial) { - state->tmp.selection.mask = 0; - state->tmp.selection.state = SELECTION_INITIAL; - } - - while(!state->tmp.selection.state && frag.len) { - /* Parse selection parameter */ - switch(frag.str[0]) { - case 'c': - state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; - break; - case 'p': - state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; - break; - case 'q': - state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; - break; - case 's': - state->tmp.selection.mask |= VTERM_SELECTION_SELECT; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); - break; - - case ';': - state->tmp.selection.state = SELECTION_SELECTED; - if(!state->tmp.selection.mask) - state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; - break; - } - - frag.str++; - frag.len--; - } - - if(!frag.len) { - /* Clear selection if we're already finished but didn't do anything */ - if(frag.final && state->selection.callbacks->set) { - (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ - .str = NULL, - .len = 0, - .initial = state->tmp.selection.state != SELECTION_SET, - .final = true, - }, state->selection.user); - } - return; - } - - if(state->tmp.selection.state == SELECTION_SELECTED) { - if(frag.str[0] == '?') { - state->tmp.selection.state = SELECTION_QUERY; - } - else { - state->tmp.selection.state = SELECTION_SET_INITIAL; - state->tmp.selection.recvpartial = 0; - } - } - - if(state->tmp.selection.state == SELECTION_QUERY) { - if(state->selection.callbacks->query) - (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); - return; - } - - if(state->tmp.selection.state == SELECTION_INVALID) - return; - - if(state->selection.callbacks->set) { - size_t bufcur = 0; - char *buffer = state->selection.buffer; - - uint32_t x = 0; /* Current decoding value */ - int n = 0; /* Number of sextets consumed */ - - if(state->tmp.selection.recvpartial) { - n = state->tmp.selection.recvpartial >> 24; - x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */ - - state->tmp.selection.recvpartial = 0; - } - - while((state->selection.buflen - bufcur) >= 3 && frag.len) { - if(frag.str[0] == '=') { - if(n == 2) { - buffer[0] = (x >> 4) & 0xFF; - buffer += 1, bufcur += 1; - } - if(n == 3) { - buffer[0] = (x >> 10) & 0xFF; - buffer[1] = (x >> 2) & 0xFF; - buffer += 2, bufcur += 2; - } - - while(frag.len && frag.str[0] == '=') - frag.str++, frag.len--; - - n = 0; - } - else { - uint8_t b = unbase64one(frag.str[0]); - if(b == 0xFF) { - DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); - - state->tmp.selection.state = SELECTION_INVALID; - if(state->selection.callbacks->set) { - (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ - .str = NULL, - .len = 0, - .initial = true, - .final = true, - }, state->selection.user); - } - break; - } - - x = (x << 6) | b; - n++; - frag.str++, frag.len--; - - if(n == 4) { - buffer[0] = (x >> 16) & 0xFF; - buffer[1] = (x >> 8) & 0xFF; - buffer[2] = (x >> 0) & 0xFF; - - buffer += 3, bufcur += 3; - x = 0; - n = 0; - } - } - - if(!frag.len || (state->selection.buflen - bufcur) < 3) { - if(bufcur) { - (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ - .str = state->selection.buffer, - .len = bufcur, - .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, - .final = frag.final && !frag.len, - }, state->selection.user); - state->tmp.selection.state = SELECTION_SET; - } - - buffer = state->selection.buffer; - bufcur = 0; - } - } - - if(n) - state->tmp.selection.recvpartial = (n << 24) | x; - } -} - -static int on_osc(int command, VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - switch(command) { - case 0: - settermprop_string(state, VTERM_PROP_ICONNAME, frag); - settermprop_string(state, VTERM_PROP_TITLE, frag); - return 1; - - case 1: - settermprop_string(state, VTERM_PROP_ICONNAME, frag); - return 1; - - case 2: - settermprop_string(state, VTERM_PROP_TITLE, frag); - return 1; - - case 52: - if(state->selection.callbacks) - osc_selection(state, frag); - - return 1; - - default: - if(state->fallbacks && state->fallbacks->osc) - if((*state->fallbacks->osc)(command, frag, state->fbdata)) - return 1; - } - - return 0; -} - -static void request_status_string(VTermState *state, VTermStringFragment frag) -{ - VTerm *vt = state->vt; - - char *tmp = state->tmp.decrqss; - - if(frag.initial) - tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; - - int i = 0; - while(i < sizeof(state->tmp.decrqss)-1 && tmp[i]) - i++; - while(i < sizeof(state->tmp.decrqss)-1 && frag.len--) - tmp[i++] = (frag.str++)[0]; - tmp[i] = 0; - - if(!frag.final) - return; - - switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) { - case 'm': { - // Query SGR - long args[20]; - int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); - size_t cur = 0; - - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... - if(cur >= vt->tmpbuffer_len) - return; - - for(int argi = 0; argi < argc; argi++) { - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - argi == argc - 1 ? "%ld" : - CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : - "%ld;", - CSI_ARG(args[argi])); - if(cur >= vt->tmpbuffer_len) - return; - } - - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST - if(cur >= vt->tmpbuffer_len) - return; - - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); - return; - } - - case 'r': - // Query DECSTBM - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); - return; - - case 's': - // Query DECSLRM - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); - return; - - case ' '|('q'<<8): { - // Query DECSCUSR - int reply; - switch(state->mode.cursor_shape) { - case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; - case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; - case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; - } - if(state->mode.cursor_blink) - reply--; - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d q", reply); - return; - } - - case '\"'|('q'<<8): - // Query DECSCA - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d\"q", state->protected_cell ? 1 : 2); - return; - } - - vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); -} - -static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(commandlen == 2 && strneq(command, "$q", 2)) { - request_status_string(state, frag); - return 1; - } - else if(state->fallbacks && state->fallbacks->dcs) - if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) - return 1; - - DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); - return 0; -} - -static int on_apc(VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(state->fallbacks && state->fallbacks->apc) - if((*state->fallbacks->apc)(frag, state->fbdata)) - return 1; - - /* No DEBUG_LOG because all APCs are unhandled */ - return 0; -} - -static int on_pm(VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(state->fallbacks && state->fallbacks->pm) - if((*state->fallbacks->pm)(frag, state->fbdata)) - return 1; - - /* No DEBUG_LOG because all PMs are unhandled */ - return 0; -} - -static int on_sos(VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(state->fallbacks && state->fallbacks->sos) - if((*state->fallbacks->sos)(frag, state->fbdata)) - return 1; - - /* No DEBUG_LOG because all SOSs are unhandled */ - return 0; -} - -static int on_resize(int rows, int cols, void *user) -{ - VTermState *state = user; - VTermPos oldpos = state->pos; - - if(cols != state->cols) { - unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); - - /* TODO: This can all be done much more efficiently bytewise */ - int col; - for(col = 0; col < state->cols && col < cols; col++) { - unsigned char mask = 1 << (col & 7); - if(state->tabstops[col >> 3] & mask) - newtabstops[col >> 3] |= mask; - else - newtabstops[col >> 3] &= ~mask; - } - - for( ; col < cols; col++) { - unsigned char mask = 1 << (col & 7); - if(col % 8 == 0) - newtabstops[col >> 3] |= mask; - else - newtabstops[col >> 3] &= ~mask; - } - - vterm_allocator_free(state->vt, state->tabstops); - state->tabstops = newtabstops; - } - - state->rows = rows; - state->cols = cols; - - if(state->scrollregion_bottom > -1) - UBOUND(state->scrollregion_bottom, state->rows); - if(state->scrollregion_right > -1) - UBOUND(state->scrollregion_right, state->cols); - - VTermStateFields fields = { - .pos = state->pos, - .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] }, - }; - - if(state->callbacks && state->callbacks->resize) { - (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); - state->pos = fields.pos; - - state->lineinfos[0] = fields.lineinfos[0]; - state->lineinfos[1] = fields.lineinfos[1]; - } - else { - if(rows != state->rows) { - for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { - VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; - if(!oldlineinfo) - continue; - - VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); - - int row; - for(row = 0; row < state->rows && row < rows; row++) { - newlineinfo[row] = oldlineinfo[row]; - } - - for( ; row < rows; row++) { - newlineinfo[row] = (VTermLineInfo){ - .doublewidth = 0, - }; - } - - vterm_allocator_free(state->vt, state->lineinfos[bufidx]); - state->lineinfos[bufidx] = newlineinfo; - } - } - } - - state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; - - if(state->at_phantom && state->pos.col < cols-1) { - state->at_phantom = 0; - state->pos.col++; - } - - if(state->pos.row < 0) - state->pos.row = 0; - if(state->pos.row >= rows) - state->pos.row = rows - 1; - if(state->pos.col < 0) - state->pos.col = 0; - if(state->pos.col >= cols) - state->pos.col = cols - 1; - - updatecursor(state, &oldpos, 1); - - return 1; -} - -static const VTermParserCallbacks parser_callbacks = { - .text = on_text, - .control = on_control, - .escape = on_escape, - .csi = on_csi, - .osc = on_osc, - .dcs = on_dcs, - .apc = on_apc, - .pm = on_pm, - .sos = on_sos, - .resize = on_resize, -}; - -VTermState *vterm_obtain_state(VTerm *vt) -{ - if(vt->state) - return vt->state; - - VTermState *state = vterm_state_new(vt); - vt->state = state; - - vterm_parser_set_callbacks(vt, &parser_callbacks, state); - - return state; -} - -void vterm_state_reset(VTermState *state, int hard) -{ - state->scrollregion_top = 0; - state->scrollregion_bottom = -1; - state->scrollregion_left = 0; - state->scrollregion_right = -1; - - state->mode.keypad = 0; - state->mode.cursor = 0; - state->mode.autowrap = 1; - state->mode.insert = 0; - state->mode.newline = 0; - state->mode.alt_screen = 0; - state->mode.origin = 0; - state->mode.leftrightmargin = 0; - state->mode.bracketpaste = 0; - state->mode.report_focus = 0; - - state->mouse_flags = 0; - - state->vt->mode.ctrl8bit = 0; - - for(int col = 0; col < state->cols; col++) - if(col % 8 == 0) - set_col_tabstop(state, col); - else - clear_col_tabstop(state, col); - - for(int row = 0; row < state->rows; row++) - set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); - - if(state->callbacks && state->callbacks->initpen) - (*state->callbacks->initpen)(state->cbdata); - - vterm_state_resetpen(state); - - VTermEncoding *default_enc = state->vt->mode.utf8 ? - vterm_lookup_encoding(ENC_UTF8, 'u') : - vterm_lookup_encoding(ENC_SINGLE_94, 'B'); - - for(int i = 0; i < 4; i++) { - state->encoding[i].enc = default_enc; - if(default_enc->init) - (*default_enc->init)(default_enc, state->encoding[i].data); - } - - state->gl_set = 0; - state->gr_set = 1; - state->gsingle_set = 0; - - state->protected_cell = 0; - - // Initialise the props - settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); - - if(hard) { - state->pos.row = 0; - state->pos.col = 0; - state->at_phantom = 0; - - VTermRect rect = { 0, state->rows, 0, state->cols }; - erase(state, rect, 0); - } -} - -void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) -{ - *cursorpos = state->pos; -} - -void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) -{ - if(callbacks) { - state->callbacks = callbacks; - state->cbdata = user; - - if(state->callbacks && state->callbacks->initpen) - (*state->callbacks->initpen)(state->cbdata); - } - else { - state->callbacks = NULL; - state->cbdata = NULL; - } -} - -void *vterm_state_get_cbdata(VTermState *state) -{ - return state->cbdata; -} - -void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user) -{ - if(fallbacks) { - state->fallbacks = fallbacks; - state->fbdata = user; - } - else { - state->fallbacks = NULL; - state->fbdata = NULL; - } -} - -void *vterm_state_get_unrecognised_fbdata(VTermState *state) -{ - return state->fbdata; -} - -int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) -{ - /* Only store the new value of the property if usercode said it was happy. - * This is especially important for altscreen switching */ - if(state->callbacks && state->callbacks->settermprop) - if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) - return 0; - - switch(prop) { - case VTERM_PROP_TITLE: - case VTERM_PROP_ICONNAME: - // we don't store these, just transparently pass through - return 1; - case VTERM_PROP_CURSORVISIBLE: - state->mode.cursor_visible = val->boolean; - return 1; - case VTERM_PROP_CURSORBLINK: - state->mode.cursor_blink = val->boolean; - return 1; - case VTERM_PROP_CURSORSHAPE: - state->mode.cursor_shape = val->number; - return 1; - case VTERM_PROP_REVERSE: - state->mode.screen = val->boolean; - return 1; - case VTERM_PROP_ALTSCREEN: - state->mode.alt_screen = val->boolean; - state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; - if(state->mode.alt_screen) { - VTermRect rect = { - .start_row = 0, - .start_col = 0, - .end_row = state->rows, - .end_col = state->cols, - }; - erase(state, rect, 0); - } - return 1; - case VTERM_PROP_MOUSE: - state->mouse_flags = 0; - if(val->number) - state->mouse_flags |= MOUSE_WANT_CLICK; - if(val->number == VTERM_PROP_MOUSE_DRAG) - state->mouse_flags |= MOUSE_WANT_DRAG; - if(val->number == VTERM_PROP_MOUSE_MOVE) - state->mouse_flags |= MOUSE_WANT_MOVE; - return 1; - case VTERM_PROP_FOCUSREPORT: - state->mode.report_focus = val->boolean; - return 1; - - case VTERM_N_PROPS: - return 0; - } - - return 0; -} - -void vterm_state_focus_in(VTermState *state) -{ - if(state->mode.report_focus) - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); -} - -void vterm_state_focus_out(VTermState *state) -{ - if(state->mode.report_focus) - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); -} - -const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) -{ - return state->lineinfo + row; -} - -void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, - char *buffer, size_t buflen) -{ - if(buflen && !buffer) - buffer = vterm_allocator_malloc(state->vt, buflen); - - state->selection.callbacks = callbacks; - state->selection.user = user; - state->selection.buffer = buffer; - state->selection.buflen = buflen; -} - -void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag) -{ - VTerm *vt = state->vt; - - if(frag.initial) { - /* TODO: support sending more than one mask bit */ - static const char selection_chars[] = "cpqs"; - int idx; - for(idx = 0; idx < 4; idx++) - if(mask & (1 << idx)) - break; - - vterm_push_output_sprintf_str(vt, C1_OSC, false, "52;%c;", selection_chars[idx]); - - state->tmp.selection.sendpartial = 0; - } - - if(frag.len) { - size_t bufcur = 0; - char *buffer = state->selection.buffer; - - uint32_t x = 0; - int n = 0; - - if(state->tmp.selection.sendpartial) { - n = state->tmp.selection.sendpartial >> 24; - x = state->tmp.selection.sendpartial & 0xFFFFFF; - - state->tmp.selection.sendpartial = 0; - } - - while((state->selection.buflen - bufcur) >= 4 && frag.len) { - x = (x << 8) | frag.str[0]; - n++; - frag.str++, frag.len--; - - if(n == 3) { - buffer[0] = base64_one((x >> 18) & 0x3F); - buffer[1] = base64_one((x >> 12) & 0x3F); - buffer[2] = base64_one((x >> 6) & 0x3F); - buffer[3] = base64_one((x >> 0) & 0x3F); - - buffer += 4, bufcur += 4; - x = 0; - n = 0; - } - - if(!frag.len || (state->selection.buflen - bufcur) < 4) { - if(bufcur) - vterm_push_output_bytes(vt, state->selection.buffer, bufcur); - - buffer = state->selection.buffer; - bufcur = 0; - } - } - - if(n) - state->tmp.selection.sendpartial = (n << 24) | x; - } - - if(frag.final) { - if(state->tmp.selection.sendpartial) { - int n = state->tmp.selection.sendpartial >> 24; - uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF; - char *buffer = state->selection.buffer; - - /* n is either 1 or 2 now */ - x <<= (n == 1) ? 16 : 8; - - buffer[0] = base64_one((x >> 18) & 0x3F); - buffer[1] = base64_one((x >> 12) & 0x3F); - buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F); - buffer[3] = '='; - - vterm_push_output_sprintf_str(vt, 0, true, "%.*s", 4, buffer); - } - else - vterm_push_output_sprintf_str(vt, 0, true, ""); - } -} diff --git a/src/vterm/vterm.c b/src/vterm/vterm.c deleted file mode 100644 index 530c513755..0000000000 --- a/src/vterm/vterm.c +++ /dev/null @@ -1,432 +0,0 @@ -#include "vterm_internal.h" - -#include "auto/config.h" -#include -#include -#include -#include - -/***************** - * API functions * - *****************/ - -static void *default_malloc(size_t size, void *allocdata) -{ - void *ptr = malloc(size); - if(ptr) - memset(ptr, 0, size); - return ptr; -} - -static void default_free(void *ptr, void *allocdata) -{ - free(ptr); -} - -static VTermAllocatorFunctions default_allocator = { - .malloc = &default_malloc, - .free = &default_free, -}; - -VTerm *vterm_new(int rows, int cols) -{ - return vterm_build(&(const struct VTermBuilder){ - .rows = rows, - .cols = cols, - }); -} - -VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) -{ - return vterm_build(&(const struct VTermBuilder){ - .rows = rows, - .cols = cols, - .allocator = funcs, - .allocdata = allocdata, - }); -} - -/* A handy macro for defaulting values out of builder fields */ -#define DEFAULT(v, def) ((v) ? (v) : (def)) - -VTerm *vterm_build(const struct VTermBuilder *builder) -{ - const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator); - - /* Need to bootstrap using the allocator function directly */ - VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata); - - vt->allocator = allocator; - vt->allocdata = builder->allocdata; - - vt->rows = builder->rows; - vt->cols = builder->cols; - - vt->parser.state = NORMAL; - - vt->parser.callbacks = NULL; - vt->parser.cbdata = NULL; - - vt->parser.emit_nul = false; - - vt->outfunc = NULL; - vt->outdata = NULL; - - vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096); - vt->outbuffer_cur = 0; - vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); - - vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096); - vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); - - return vt; -} - -void vterm_free(VTerm *vt) -{ - if(vt->screen) - vterm_screen_free(vt->screen); - - if(vt->state) - vterm_state_free(vt->state); - - vterm_allocator_free(vt, vt->outbuffer); - vterm_allocator_free(vt, vt->tmpbuffer); - - vterm_allocator_free(vt, vt); -} - -INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) -{ - return (*vt->allocator->malloc)(size, vt->allocdata); -} - -INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) -{ - (*vt->allocator->free)(ptr, vt->allocdata); -} - -void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) -{ - if(rowsp) - *rowsp = vt->rows; - if(colsp) - *colsp = vt->cols; -} - -void vterm_set_size(VTerm *vt, int rows, int cols) -{ - if(rows < 1 || cols < 1) - return; - - vt->rows = rows; - vt->cols = cols; - - if(vt->parser.callbacks && vt->parser.callbacks->resize) - (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); -} - -int vterm_get_utf8(const VTerm *vt) -{ - return vt->mode.utf8; -} - -void vterm_set_utf8(VTerm *vt, int is_utf8) -{ - vt->mode.utf8 = is_utf8; -} - -void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) -{ - vt->outfunc = func; - vt->outdata = user; -} - -INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) -{ - if(vt->outfunc) { - (vt->outfunc)(bytes, len, vt->outdata); - return; - } - - if(len > vt->outbuffer_len - vt->outbuffer_cur) - return; - - memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); - vt->outbuffer_cur += len; -} - -INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) -{ - size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, - format, args); - - vterm_push_output_bytes(vt, vt->tmpbuffer, len); -} - -INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) -{ - va_list args; - va_start(args, format); - vterm_push_output_vsprintf(vt, format, args); - va_end(args); -} - -INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) -{ - size_t cur; - - if(ctrl >= 0x80 && !vt->mode.ctrl8bit) - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - ESC_S "%c", ctrl - 0x40); - else - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - "%c", ctrl); - - if(cur >= vt->tmpbuffer_len) - return; - - va_list args; - va_start(args, fmt); - cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - fmt, args); - va_end(args); - - if(cur >= vt->tmpbuffer_len) - return; - - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); -} - -INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...) -{ - size_t cur = 0; - - if(ctrl) { - if(ctrl >= 0x80 && !vt->mode.ctrl8bit) - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - ESC_S "%c", ctrl - 0x40); - else - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - "%c", ctrl); - - if(cur >= vt->tmpbuffer_len) - return; - } - - va_list args; - va_start(args, fmt); - cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - fmt, args); - va_end(args); - - if(cur >= vt->tmpbuffer_len) - return; - - if(term) { - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST - - if(cur >= vt->tmpbuffer_len) - return; - } - - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); -} - -size_t vterm_output_get_buffer_size(const VTerm *vt) -{ - return vt->outbuffer_len; -} - -size_t vterm_output_get_buffer_current(const VTerm *vt) -{ - return vt->outbuffer_cur; -} - -size_t vterm_output_get_buffer_remaining(const VTerm *vt) -{ - return vt->outbuffer_len - vt->outbuffer_cur; -} - -size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) -{ - if(len > vt->outbuffer_cur) - len = vt->outbuffer_cur; - - memcpy(buffer, vt->outbuffer, len); - - if(len < vt->outbuffer_cur) - memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); - - vt->outbuffer_cur -= len; - - return len; -} - -VTermValueType vterm_get_attr_type(VTermAttr attr) -{ - switch(attr) { - case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; - case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_CONCEAL: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; - case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; - case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; - case VTERM_ATTR_SMALL: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT; - case VTERM_ATTR_URI: return VTERM_VALUETYPE_INT; - - case VTERM_N_ATTRS: return 0; - } - return 0; /* UNREACHABLE */ -} - -VTermValueType vterm_get_prop_type(VTermProp prop) -{ - switch(prop) { - case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; - case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; - case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; - case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; - case VTERM_PROP_FOCUSREPORT: return VTERM_VALUETYPE_BOOL; - - case VTERM_N_PROPS: return 0; - } - return 0; /* UNREACHABLE */ -} - -void vterm_scroll_rect(VTermRect rect, - int downward, - int rightward, - int (*moverect)(VTermRect src, VTermRect dest, void *user), - int (*eraserect)(VTermRect rect, int selective, void *user), - void *user) -{ - VTermRect src; - VTermRect dest; - - if(abs(downward) >= rect.end_row - rect.start_row || - abs(rightward) >= rect.end_col - rect.start_col) { - /* Scroll more than area; just erase the lot */ - (*eraserect)(rect, 0, user); - return; - } - - if(rightward >= 0) { - /* rect: [XXX................] - * src: [----------------] - * dest: [----------------] - */ - dest.start_col = rect.start_col; - dest.end_col = rect.end_col - rightward; - src.start_col = rect.start_col + rightward; - src.end_col = rect.end_col; - } - else { - /* rect: [................XXX] - * src: [----------------] - * dest: [----------------] - */ - int leftward = -rightward; - dest.start_col = rect.start_col + leftward; - dest.end_col = rect.end_col; - src.start_col = rect.start_col; - src.end_col = rect.end_col - leftward; - } - - if(downward >= 0) { - dest.start_row = rect.start_row; - dest.end_row = rect.end_row - downward; - src.start_row = rect.start_row + downward; - src.end_row = rect.end_row; - } - else { - int upward = -downward; - dest.start_row = rect.start_row + upward; - dest.end_row = rect.end_row; - src.start_row = rect.start_row; - src.end_row = rect.end_row - upward; - } - - if(moverect) - (*moverect)(dest, src, user); - - if(downward > 0) - rect.start_row = rect.end_row - downward; - else if(downward < 0) - rect.end_row = rect.start_row - downward; - - if(rightward > 0) - rect.start_col = rect.end_col - rightward; - else if(rightward < 0) - rect.end_col = rect.start_col - rightward; - - (*eraserect)(rect, 0, user); -} - -void vterm_copy_cells(VTermRect dest, - VTermRect src, - void (*copycell)(VTermPos dest, VTermPos src, void *user), - void *user) -{ - int downward = src.start_row - dest.start_row; - int rightward = src.start_col - dest.start_col; - - int init_row, test_row, init_col, test_col; - int inc_row, inc_col; - - if(downward < 0) { - init_row = dest.end_row - 1; - test_row = dest.start_row - 1; - inc_row = -1; - } - else /* downward >= 0 */ { - init_row = dest.start_row; - test_row = dest.end_row; - inc_row = +1; - } - - if(rightward < 0) { - init_col = dest.end_col - 1; - test_col = dest.start_col - 1; - inc_col = -1; - } - else /* rightward >= 0 */ { - init_col = dest.start_col; - test_col = dest.end_col; - inc_col = +1; - } - - VTermPos pos; - for(pos.row = init_row; pos.row != test_row; pos.row += inc_row) - for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) { - VTermPos srcpos = { pos.row + downward, pos.col + rightward }; - (*copycell)(pos, srcpos, user); - } -} - -void vterm_check_version(int major, int minor) -{ - if(major != VTERM_VERSION_MAJOR) { - fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", - major, VTERM_VERSION_MAJOR); - exit(1); - } - - if(minor > VTERM_VERSION_MINOR) { - fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", - minor, VTERM_VERSION_MINOR); - exit(1); - } - - // Happy -} diff --git a/src/vterm/vterm.h b/src/vterm/vterm.h deleted file mode 100644 index 4de1d885b8..0000000000 --- a/src/vterm/vterm.h +++ /dev/null @@ -1,638 +0,0 @@ -#ifndef __VTERM_H__ -#define __VTERM_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "nvim/macros_defs.h" -#include "nvim/types_defs.h" -#include "vterm_keycodes.h" - -#define VTERM_VERSION_MAJOR 0 -#define VTERM_VERSION_MINOR 3 -#define VTERM_VERSION_PATCH 3 - -#define VTERM_CHECK_VERSION \ - vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) - -typedef struct VTerm VTerm; -typedef struct VTermState VTermState; -typedef struct VTermScreen VTermScreen; - -typedef struct { - int row; - int col; -} VTermPos; - -/* some small utility functions; we can just keep these static here */ - -/* order points by on-screen flow order */ -static inline int vterm_pos_cmp(VTermPos a, VTermPos b) -{ - return (a.row == b.row) ? a.col - b.col : a.row - b.row; -} - -typedef struct { - int start_row; - int end_row; - int start_col; - int end_col; -} VTermRect; - -/* true if the rect contains the point */ -static inline int vterm_rect_contains(VTermRect r, VTermPos p) -{ - return p.row >= r.start_row && p.row < r.end_row && - p.col >= r.start_col && p.col < r.end_col; -} - -/* move a rect */ -static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) -{ - rect->start_row += row_delta; rect->end_row += row_delta; - rect->start_col += col_delta; rect->end_col += col_delta; -} - -/** - * Bit-field describing the content of the tagged union `VTermColor`. - */ -typedef enum { - /** - * If the lower bit of `type` is not set, the colour is 24-bit RGB. - */ - VTERM_COLOR_RGB = 0x00, - - /** - * The colour is an index into a palette of 256 colours. - */ - VTERM_COLOR_INDEXED = 0x01, - - /** - * Mask that can be used to extract the RGB/Indexed bit. - */ - VTERM_COLOR_TYPE_MASK = 0x01, - - /** - * If set, indicates that this colour should be the default foreground - * color, i.e. there was no SGR request for another colour. When - * rendering this colour it is possible to ignore "idx" and just use a - * colour that is not in the palette. - */ - VTERM_COLOR_DEFAULT_FG = 0x02, - - /** - * If set, indicates that this colour should be the default background - * color, i.e. there was no SGR request for another colour. A common - * option when rendering this colour is to not render a background at - * all, for example by rendering the window transparently at this spot. - */ - VTERM_COLOR_DEFAULT_BG = 0x04, - - /** - * Mask that can be used to extract the default foreground/background bit. - */ - VTERM_COLOR_DEFAULT_MASK = 0x06 -} VTermColorType; - -/** - * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the - * given VTermColor instance is an indexed colour. - */ -#define VTERM_COLOR_IS_INDEXED(col) \ - (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) - -/** - * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that - * the given VTermColor instance is an rgb colour. - */ -#define VTERM_COLOR_IS_RGB(col) \ - (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) - -/** - * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating - * that the given VTermColor instance corresponds to the default foreground - * color. - */ -#define VTERM_COLOR_IS_DEFAULT_FG(col) \ - (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) - -/** - * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating - * that the given VTermColor instance corresponds to the default background - * color. - */ -#define VTERM_COLOR_IS_DEFAULT_BG(col) \ - (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) - -/** - * Tagged union storing either an RGB color or an index into a colour palette. - * In order to convert indexed colours to RGB, you may use the - * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() - * functions which lookup the RGB colour from the palette maintained by a - * VTermState or VTermScreen instance. - */ -typedef union { - /** - * Tag indicating which union member is actually valid. This variable - * coincides with the `type` member of the `rgb` and the `indexed` struct - * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether - * a particular type flag is set. - */ - uint8_t type; - - /** - * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. - */ - struct { - /** - * Same as the top-level `type` member stored in VTermColor. - */ - uint8_t type; - - /** - * The actual 8-bit red, green, blue colour values. - */ - uint8_t red, green, blue; - } rgb; - - /** - * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into - * the colour palette. - */ - struct { - /** - * Same as the top-level `type` member stored in VTermColor. - */ - uint8_t type; - - /** - * Index into the colour map. - */ - uint8_t idx; - } indexed; -} VTermColor; - -/** - * Constructs a new VTermColor instance representing the given RGB values. - */ -static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, - uint8_t blue) -{ - col->type = VTERM_COLOR_RGB; - col->rgb.red = red; - col->rgb.green = green; - col->rgb.blue = blue; -} - -/** - * Construct a new VTermColor instance representing an indexed color with the - * given index. - */ -static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) -{ - col->type = VTERM_COLOR_INDEXED; - col->indexed.idx = idx; -} - -/** - * Compares two colours. Returns true if the colors are equal, false otherwise. - */ -int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); - -typedef enum { - /* VTERM_VALUETYPE_NONE = 0 */ - VTERM_VALUETYPE_BOOL = 1, - VTERM_VALUETYPE_INT, - VTERM_VALUETYPE_STRING, - VTERM_VALUETYPE_COLOR, - - VTERM_N_VALUETYPES -} VTermValueType; - -typedef struct { - const char *str; - size_t len : 30; - bool initial : 1; - bool final : 1; -} VTermStringFragment; - -typedef union { - int boolean; - int number; - VTermStringFragment string; - VTermColor color; -} VTermValue; - -typedef enum { - /* VTERM_ATTR_NONE = 0 */ - VTERM_ATTR_BOLD = 1, // bool: 1, 22 - VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 - VTERM_ATTR_ITALIC, // bool: 3, 23 - VTERM_ATTR_BLINK, // bool: 5, 25 - VTERM_ATTR_REVERSE, // bool: 7, 27 - VTERM_ATTR_CONCEAL, // bool: 8, 28 - VTERM_ATTR_STRIKE, // bool: 9, 29 - VTERM_ATTR_FONT, // number: 10-19 - VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 - VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 - VTERM_ATTR_SMALL, // bool: 73, 74, 75 - VTERM_ATTR_BASELINE, // number: 73, 74, 75 - VTERM_ATTR_URI, // number - - VTERM_N_ATTRS -} VTermAttr; - -typedef enum { - /* VTERM_PROP_NONE = 0 */ - VTERM_PROP_CURSORVISIBLE = 1, // bool - VTERM_PROP_CURSORBLINK, // bool - VTERM_PROP_ALTSCREEN, // bool - VTERM_PROP_TITLE, // string - VTERM_PROP_ICONNAME, // string - VTERM_PROP_REVERSE, // bool - VTERM_PROP_CURSORSHAPE, // number - VTERM_PROP_MOUSE, // number - VTERM_PROP_FOCUSREPORT, // bool - - VTERM_N_PROPS -} VTermProp; - -enum { - VTERM_PROP_CURSORSHAPE_BLOCK = 1, - VTERM_PROP_CURSORSHAPE_UNDERLINE, - VTERM_PROP_CURSORSHAPE_BAR_LEFT, - - VTERM_N_PROP_CURSORSHAPES -}; - -enum { - VTERM_PROP_MOUSE_NONE = 0, - VTERM_PROP_MOUSE_CLICK, - VTERM_PROP_MOUSE_DRAG, - VTERM_PROP_MOUSE_MOVE, - - VTERM_N_PROP_MOUSES -}; - -typedef enum { - VTERM_SELECTION_CLIPBOARD = (1<<0), - VTERM_SELECTION_PRIMARY = (1<<1), - VTERM_SELECTION_SECONDARY = (1<<2), - VTERM_SELECTION_SELECT = (1<<3), - VTERM_SELECTION_CUT0 = (1<<4), /* also CUT1 .. CUT7 by bitshifting */ -} VTermSelectionMask; - -typedef struct { - schar_T schar; - int width; - unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ - unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ - unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ -} VTermGlyphInfo; - -typedef struct { - unsigned int doublewidth:1; /* DECDWL or DECDHL line */ - unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ - unsigned int continuation:1; /* Line is a flow continuation of the previous */ -} VTermLineInfo; - -/* Copies of VTermState fields that the 'resize' callback might have reason to - * edit. 'resize' callback gets total control of these fields and may - * free-and-reallocate them if required. They will be copied back from the - * struct after the callback has returned. - */ -typedef struct { - VTermPos pos; /* current cursor position */ - VTermLineInfo *lineinfos[2]; /* [1] may be NULL */ -} VTermStateFields; - -typedef struct { - /* libvterm relies on this memory to be zeroed out before it is returned - * by the allocator. */ - void *(*malloc)(size_t size, void *allocdata); - void (*free)(void *ptr, void *allocdata); -} VTermAllocatorFunctions; - -void vterm_check_version(int major, int minor); - -struct VTermBuilder { - int ver; /* currently unused but reserved for some sort of ABI version flag */ - - int rows, cols; - - const VTermAllocatorFunctions *allocator; - void *allocdata; - - /* Override default sizes for various structures */ - size_t outbuffer_len; /* default: 4096 */ - size_t tmpbuffer_len; /* default: 4096 */ -}; - -VTerm *vterm_build(const struct VTermBuilder *builder); - -/* A convenient shortcut for default cases */ -VTerm *vterm_new(int rows, int cols); -/* This shortcuts are generally discouraged in favour of just using vterm_build() */ -VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); - -void vterm_free(VTerm* vt); - -void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); -void vterm_set_size(VTerm *vt, int rows, int cols); - -int vterm_get_utf8(const VTerm *vt); -void vterm_set_utf8(VTerm *vt, int is_utf8); - -size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); - -/* Setting output callback will override the buffer logic */ -typedef void VTermOutputCallback(const char *s, size_t len, void *user); -void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); - -/* These buffer functions only work if output callback is NOT set - * These are deprecated and will be removed in a later version */ -size_t vterm_output_get_buffer_size(const VTerm *vt); -size_t vterm_output_get_buffer_current(const VTerm *vt); -size_t vterm_output_get_buffer_remaining(const VTerm *vt); - -/* This too */ -size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); - -void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); -void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); - -void vterm_keyboard_start_paste(VTerm *vt); -void vterm_keyboard_end_paste(VTerm *vt); - -void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); -void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); - -// ------------ -// Parser layer -// ------------ - -/* Flag to indicate non-final subparameters in a single CSI parameter. - * Consider - * CSI 1;2:3:4;5a - * 1 4 and 5 are final. - * 2 and 3 are non-final and will have this bit set - * - * Don't confuse this with the final byte of the CSI escape; 'a' in this case. - */ -#define CSI_ARG_FLAG_MORE (1U<<31) -#define CSI_ARG_MASK (~(1U<<31)) - -#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) -#define CSI_ARG(a) ((a) & CSI_ARG_MASK) - -/* Can't use -1 to indicate a missing argument; use this instead */ -#define CSI_ARG_MISSING ((1UL<<31)-1) - -#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) -#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) -#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) - -typedef struct { - int (*text)(const char *bytes, size_t len, void *user); - int (*control)(unsigned char control, void *user); - int (*escape)(const char *bytes, size_t len, void *user); - int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); - int (*osc)(int command, VTermStringFragment frag, void *user); - int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); - int (*apc)(VTermStringFragment frag, void *user); - int (*pm)(VTermStringFragment frag, void *user); - int (*sos)(VTermStringFragment frag, void *user); - int (*resize)(int rows, int cols, void *user); -} VTermParserCallbacks; - -void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); -void *vterm_parser_get_cbdata(VTerm *vt); - -/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them - * to be emitted by the 'control' callback - */ -void vterm_parser_set_emit_nul(VTerm *vt, bool emit); - -// ----------- -// State layer -// ----------- - -typedef struct { - int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); - int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); - int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); - int (*moverect)(VTermRect dest, VTermRect src, void *user); - int (*erase)(VTermRect rect, int selective, void *user); - int (*initpen)(void *user); - int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); - int (*settermprop)(VTermProp prop, VTermValue *val, void *user); - int (*bell)(void *user); - int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); - int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); - int (*sb_clear)(void *user); -} VTermStateCallbacks; - -typedef struct { - int (*control)(unsigned char control, void *user); - int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); - int (*osc)(int command, VTermStringFragment frag, void *user); - int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); - int (*apc)(VTermStringFragment frag, void *user); - int (*pm)(VTermStringFragment frag, void *user); - int (*sos)(VTermStringFragment frag, void *user); -} VTermStateFallbacks; - -typedef struct { - int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user); - int (*query)(VTermSelectionMask mask, void *user); -} VTermSelectionCallbacks; - -VTermState *vterm_obtain_state(VTerm *vt); - -void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); -void *vterm_state_get_cbdata(VTermState *state); - -void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user); -void *vterm_state_get_unrecognised_fbdata(VTermState *state); - -void vterm_state_reset(VTermState *state, int hard); -void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); -void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); -void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); -void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); -void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); -void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); -int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); -int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val); -int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); -void vterm_state_focus_in(VTermState *state); -void vterm_state_focus_out(VTermState *state); -const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); - -/** - * Makes sure that the given color `col` is indeed an RGB colour. After this - * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other - * flags stored in `col->type` will have been reset. - * - * @param state is the VTermState instance from which the colour palette should - * be extracted. - * @param col is a pointer at the VTermColor instance that should be converted - * to an RGB colour. - */ -void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); - -void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, - char *buffer, size_t buflen); - -void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag); - -// ------------ -// Screen layer -// ------------ - -typedef struct { - unsigned int bold : 1; - unsigned int underline : 2; - unsigned int italic : 1; - unsigned int blink : 1; - unsigned int reverse : 1; - unsigned int conceal : 1; - unsigned int strike : 1; - unsigned int font : 4; /* 0 to 9 */ - unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ - unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ - unsigned int small : 1; - unsigned int baseline : 2; -} VTermScreenCellAttrs; - -enum { - VTERM_UNDERLINE_OFF, - VTERM_UNDERLINE_SINGLE, - VTERM_UNDERLINE_DOUBLE, - VTERM_UNDERLINE_CURLY, -}; - -enum { - VTERM_BASELINE_NORMAL, - VTERM_BASELINE_RAISE, - VTERM_BASELINE_LOWER, -}; - -typedef struct { - schar_T schar; - char width; - VTermScreenCellAttrs attrs; - VTermColor fg, bg; - int uri; -} VTermScreenCell; - -typedef struct { - int (*damage)(VTermRect rect, void *user); - int (*moverect)(VTermRect dest, VTermRect src, void *user); - int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); - int (*settermprop)(VTermProp prop, VTermValue *val, void *user); - int (*bell)(void *user); - int (*resize)(int rows, int cols, void *user); - int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); - int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); - int (*sb_clear)(void* user); -} VTermScreenCallbacks; - -VTermScreen *vterm_obtain_screen(VTerm *vt); - -void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); -void *vterm_screen_get_cbdata(VTermScreen *screen); - -void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user); -void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); - -void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow); - -// Back-compat alias for the brief time it was in 0.3-RC1 -#define vterm_screen_set_reflow vterm_screen_enable_reflow - -void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); - -typedef enum { - VTERM_DAMAGE_CELL, /* every cell */ - VTERM_DAMAGE_ROW, /* entire rows */ - VTERM_DAMAGE_SCREEN, /* entire screen */ - VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ - - VTERM_N_DAMAGES -} VTermDamageSize; - -void vterm_screen_flush_damage(VTermScreen *screen); -void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); - -void vterm_screen_reset(VTermScreen *screen, int hard); - -/* Neither of these functions NUL-terminate the buffer */ -size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); -size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); - -typedef enum { - VTERM_ATTR_BOLD_MASK = 1 << 0, - VTERM_ATTR_UNDERLINE_MASK = 1 << 1, - VTERM_ATTR_ITALIC_MASK = 1 << 2, - VTERM_ATTR_BLINK_MASK = 1 << 3, - VTERM_ATTR_REVERSE_MASK = 1 << 4, - VTERM_ATTR_STRIKE_MASK = 1 << 5, - VTERM_ATTR_FONT_MASK = 1 << 6, - VTERM_ATTR_FOREGROUND_MASK = 1 << 7, - VTERM_ATTR_BACKGROUND_MASK = 1 << 8, - VTERM_ATTR_CONCEAL_MASK = 1 << 9, - VTERM_ATTR_SMALL_MASK = 1 << 10, - VTERM_ATTR_BASELINE_MASK = 1 << 11, - VTERM_ATTR_URI_MASK = 1 << 12, - - VTERM_ALL_ATTRS_MASK = (1 << 13) - 1 -} VTermAttrMask; - -int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); - -int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); - -int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); - -/** - * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` - * instance. - */ -void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); - -/** - * Similar to vterm_state_set_default_colors(), but also resets colours in the - * screen buffer(s) - */ -void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg); - -// --------- -// Utilities -// --------- - -VTermValueType vterm_get_attr_type(VTermAttr attr); -VTermValueType vterm_get_prop_type(VTermProp prop); - -void vterm_scroll_rect(VTermRect rect, - int downward, - int rightward, - int (*moverect)(VTermRect src, VTermRect dest, void *user), - int (*eraserect)(VTermRect rect, int selective, void *user), - void *user); - -void vterm_copy_cells(VTermRect dest, - VTermRect src, - void (*copycell)(VTermPos dest, VTermPos src, void *user), - void *user); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/vterm/vterm_internal.h b/src/vterm/vterm_internal.h deleted file mode 100644 index 8f1722dd93..0000000000 --- a/src/vterm/vterm_internal.h +++ /dev/null @@ -1,298 +0,0 @@ -#ifndef __VTERM_INTERNAL_H__ -#define __VTERM_INTERNAL_H__ - -#include "vterm.h" - -#include -#include "nvim/mbyte.h" - -#if defined(__GNUC__) -# define INTERNAL __attribute__((visibility("internal"))) -#else -# define INTERNAL -#endif - -#ifdef DEBUG -# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) -#else -# define DEBUG_LOG(...) -#endif - -#define ESC_S "\x1b" - -#define INTERMED_MAX 16 - -#define CSI_ARGS_MAX 16 -#define CSI_LEADER_MAX 16 - -#define BUFIDX_PRIMARY 0 -#define BUFIDX_ALTSCREEN 1 - -typedef struct VTermEncoding VTermEncoding; - -typedef struct { - VTermEncoding *enc; - - // This size should be increased if required by other stateful encodings - char data[4*sizeof(uint32_t)]; -} VTermEncodingInstance; - -struct VTermPen -{ - VTermColor fg; - VTermColor bg; - int uri; - unsigned int bold:1; - unsigned int underline:2; - unsigned int italic:1; - unsigned int blink:1; - unsigned int reverse:1; - unsigned int conceal:1; - unsigned int strike:1; - unsigned int font:4; /* To store 0-9 */ - unsigned int small:1; - unsigned int baseline:2; -}; - -struct VTermState -{ - VTerm *vt; - - const VTermStateCallbacks *callbacks; - void *cbdata; - - const VTermStateFallbacks *fallbacks; - void *fbdata; - - int rows; - int cols; - - /* Current cursor position */ - VTermPos pos; - - int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ - - int scrollregion_top; - int scrollregion_bottom; /* -1 means unbounded */ -#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) - int scrollregion_left; -#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) - int scrollregion_right; /* -1 means unbounded */ -#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) - - /* Bitvector of tab stops */ - unsigned char *tabstops; - - /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */ - VTermLineInfo *lineinfos[2]; - - /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */ - VTermLineInfo *lineinfo; -#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) -#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) - - /* Mouse state */ - int mouse_col, mouse_row; - int mouse_buttons; - int mouse_flags; -#define MOUSE_WANT_CLICK 0x01 -#define MOUSE_WANT_DRAG 0x02 -#define MOUSE_WANT_MOVE 0x04 - - enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; - - /* Last glyph output, for Unicode recombining purposes */ - char grapheme_buf[MAX_SCHAR_SIZE]; - size_t grapheme_len; - uint32_t grapheme_last; // last added UTF-32 char - GraphemeState grapheme_state; - int combine_width; // The width of the glyph above - VTermPos combine_pos; // Position before movement - - struct { - unsigned int keypad:1; - unsigned int cursor:1; - unsigned int autowrap:1; - unsigned int insert:1; - unsigned int newline:1; - unsigned int cursor_visible:1; - unsigned int cursor_blink:1; - unsigned int cursor_shape:2; - unsigned int alt_screen:1; - unsigned int origin:1; - unsigned int screen:1; - unsigned int leftrightmargin:1; - unsigned int bracketpaste:1; - unsigned int report_focus:1; - } mode; - - VTermEncodingInstance encoding[4], encoding_utf8; - int gl_set, gr_set, gsingle_set; - - struct VTermPen pen; - - VTermColor default_fg; - VTermColor default_bg; - VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only - - int bold_is_highbright; - - unsigned int protected_cell : 1; - - /* Saved state under DEC mode 1048/1049 */ - struct { - VTermPos pos; - struct VTermPen pen; - - struct { - unsigned int cursor_visible:1; - unsigned int cursor_blink:1; - unsigned int cursor_shape:2; - } mode; - } saved; - - /* Temporary state for DECRQSS parsing */ - union { - char decrqss[4]; - struct { - uint16_t mask; - enum { - SELECTION_INITIAL, - SELECTION_SELECTED, - SELECTION_QUERY, - SELECTION_SET_INITIAL, - SELECTION_SET, - SELECTION_INVALID, - } state : 8; - uint32_t recvpartial; - uint32_t sendpartial; - } selection; - } tmp; - - struct { - const VTermSelectionCallbacks *callbacks; - void *user; - char *buffer; - size_t buflen; - } selection; -}; - -struct VTerm -{ - const VTermAllocatorFunctions *allocator; - void *allocdata; - - int rows; - int cols; - - struct { - unsigned int utf8:1; - unsigned int ctrl8bit:1; - } mode; - - struct { - enum VTermParserState { - NORMAL, - CSI_LEADER, - CSI_ARGS, - CSI_INTERMED, - DCS_COMMAND, - /* below here are the "string states" */ - OSC_COMMAND, - OSC, - DCS, - APC, - PM, - SOS, - } state; - - bool in_esc : 1; - - int intermedlen; - char intermed[INTERMED_MAX]; - - union { - struct { - int leaderlen; - char leader[CSI_LEADER_MAX]; - - int argi; - long args[CSI_ARGS_MAX]; - } csi; - struct { - int command; - } osc; - struct { - int commandlen; - char command[CSI_LEADER_MAX]; - } dcs; - } v; - - const VTermParserCallbacks *callbacks; - void *cbdata; - - bool string_initial; - - bool emit_nul; - } parser; - - /* len == malloc()ed size; cur == number of valid bytes */ - - VTermOutputCallback *outfunc; - void *outdata; - - char *outbuffer; - size_t outbuffer_len; - size_t outbuffer_cur; - - char *tmpbuffer; - size_t tmpbuffer_len; - - VTermState *state; - VTermScreen *screen; -}; - -struct VTermEncoding { - void (*init) (VTermEncoding *enc, void *data); - void (*decode)(VTermEncoding *enc, void *data, - uint32_t cp[], int *cpi, int cplen, - const char bytes[], size_t *pos, size_t len); -}; - -typedef enum { - ENC_UTF8, - ENC_SINGLE_94 -} VTermEncodingType; - -void *vterm_allocator_malloc(VTerm *vt, size_t size); -void vterm_allocator_free(VTerm *vt, void *ptr); - -void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); -void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); -void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); -void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); -void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...); - -void vterm_state_free(VTermState *state); - -void vterm_state_newpen(VTermState *state); -void vterm_state_resetpen(VTermState *state); -void vterm_state_setpen(VTermState *state, const long args[], int argcount); -int vterm_state_getpen(VTermState *state, long args[], int argcount); -void vterm_state_savepen(VTermState *state, int save); - -enum { - C1_SS3 = 0x8f, - C1_DCS = 0x90, - C1_CSI = 0x9b, - C1_ST = 0x9c, - C1_OSC = 0x9d, -}; - -void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); - -void vterm_screen_free(VTermScreen *screen); - -VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); - -#endif diff --git a/src/vterm/vterm_keycodes.h b/src/vterm/vterm_keycodes.h deleted file mode 100644 index 661759febd..0000000000 --- a/src/vterm/vterm_keycodes.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef __VTERM_INPUT_H__ -#define __VTERM_INPUT_H__ - -typedef enum { - VTERM_MOD_NONE = 0x00, - VTERM_MOD_SHIFT = 0x01, - VTERM_MOD_ALT = 0x02, - VTERM_MOD_CTRL = 0x04, - - VTERM_ALL_MODS_MASK = 0x07 -} VTermModifier; - -typedef enum { - VTERM_KEY_NONE, - - VTERM_KEY_ENTER, - VTERM_KEY_TAB, - VTERM_KEY_BACKSPACE, - VTERM_KEY_ESCAPE, - - VTERM_KEY_UP, - VTERM_KEY_DOWN, - VTERM_KEY_LEFT, - VTERM_KEY_RIGHT, - - VTERM_KEY_INS, - VTERM_KEY_DEL, - VTERM_KEY_HOME, - VTERM_KEY_END, - VTERM_KEY_PAGEUP, - VTERM_KEY_PAGEDOWN, - - VTERM_KEY_FUNCTION_0 = 256, - VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, - - VTERM_KEY_KP_0, - VTERM_KEY_KP_1, - VTERM_KEY_KP_2, - VTERM_KEY_KP_3, - VTERM_KEY_KP_4, - VTERM_KEY_KP_5, - VTERM_KEY_KP_6, - VTERM_KEY_KP_7, - VTERM_KEY_KP_8, - VTERM_KEY_KP_9, - VTERM_KEY_KP_MULT, - VTERM_KEY_KP_PLUS, - VTERM_KEY_KP_COMMA, - VTERM_KEY_KP_MINUS, - VTERM_KEY_KP_PERIOD, - VTERM_KEY_KP_DIVIDE, - VTERM_KEY_KP_ENTER, - VTERM_KEY_KP_EQUAL, - - VTERM_KEY_MAX, // Must be last - VTERM_N_KEYS = VTERM_KEY_MAX -} VTermKey; - -#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) - -#endif diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c index 8755e32e7a..7522962a05 100644 --- a/test/unit/fixtures/vterm_test.c +++ b/test/unit/fixtures/vterm_test.c @@ -1,7 +1,11 @@ #include +#include + #include "nvim/grid.h" #include "nvim/mbyte.h" - +#include "nvim/vterm/pen.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/vterm_internal_defs.h" #include "vterm_test.h" int parser_text(const char bytes[], size_t len, void *user) @@ -204,7 +208,8 @@ int selection_query(VTermSelectionMask mask, void *user) return 1; } -static void print_schar(FILE *f, schar_T schar) { +static void print_schar(FILE *f, schar_T schar) +{ char buf[MAX_SCHAR_SIZE]; schar_get(buf, schar); StrCharInfo ci = utf_ptr2StrCharInfo(buf); @@ -319,6 +324,34 @@ void print_color(const VTermColor *col) fclose(f); } +static VTermValueType vterm_get_prop_type(VTermProp prop) +{ + switch (prop) { + case VTERM_PROP_CURSORVISIBLE: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: + return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: + return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: + return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: + return VTERM_VALUETYPE_INT; + case VTERM_PROP_FOCUSREPORT: + return VTERM_VALUETYPE_BOOL; + + case VTERM_N_PROPS: + return 0; + } + return 0; // UNREACHABLE +} + bool want_state_settermprop; int state_settermprop(VTermProp prop, VTermValue *val, void *user) { @@ -463,14 +496,14 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) } int eol = cols; - while (eol && !cells[eol-1].schar) { + while (eol && !cells[eol - 1].schar) { eol--; } FILE *f = fopen(VTERM_TEST_FILE, "a"); fprintf(f, "sb_pushline %d =", cols); for (int c = 0; c < eol; c++) { - fprintf(f, " "); + fprintf(f, " "); print_schar(f, cells[c].schar); } fprintf(f, "\n"); @@ -488,7 +521,7 @@ int screen_sb_popline(int cols, VTermScreenCell *cells, void *user) // All lines of scrollback contain "ABCDE" for (int col = 0; col < cols; col++) { - if(col < 5) { + if (col < 5) { cells[col].schar = schar_from_ascii((uint32_t)('A' + col)); } else { cells[col].schar = 0; @@ -524,3 +557,231 @@ void term_output(const char *s, size_t len, void *user) } fclose(f); } + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) +{ + switch (attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_CONCEAL: + val->boolean = state->pen.conceal; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_ATTR_SMALL: + val->boolean = state->pen.small; + return 1; + + case VTERM_ATTR_BASELINE: + val->number = state->pen.baseline; + return 1; + + case VTERM_ATTR_URI: + val->number = state->pen.uri; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) +{ + if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) { + return 1; + } + if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) { + return 1; + } + if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) { + return 1; + } + if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) { + return 1; + } + if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) { + return 1; + } + if ((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) { + return 1; + } + if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) { + return 1; + } + if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) { + return 1; + } + if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) { + return 1; + } + if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) { + return 1; + } + if ((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) { + return 1; + } + if ((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) { + return 1; + } + if ((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) { + return 1; + } + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, + VTermAttrMask attrs) +{ + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO(vterm): bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if (extent->start_col < 0) { + extent->start_col = 0; + } + if (extent->end_col < 0) { + extent->end_col = screen->cols; + } + + int col; + + for (col = pos.col - 1; col >= extent->start_col; col--) { + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { + break; + } + } + extent->start_col = col + 1; + + for (col = pos.col + 1; col < extent->end_col; col++) { + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { + break; + } + } + extent->end_col = col - 1; + + return 1; +} + +/// Does not NUL-terminate the buffer +size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, + const VTermRect rect) +{ + size_t outpos = 0; + int padding = 0; + +#define PUT(bytes, thislen) \ + if (true) { \ + if (buffer && outpos + thislen <= len) \ + memcpy((char *)buffer + outpos, bytes, thislen); \ + outpos += thislen; \ + } \ + + for (int row = rect.start_row; row < rect.end_row; row++) { + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (cell->schar == 0) { + // Erased cell, might need a space + padding++; + } else if (cell->schar == (uint32_t)-1) { + // Gap behind a double-width char, do nothing + } else { + while (padding) { + PUT(" ", 1); + padding--; + } + char buf[MAX_SCHAR_SIZE + 1]; + size_t thislen = schar_get(buf, cell->schar); + PUT(buf, thislen); + } + } + + if (row < rect.end_row - 1) { + PUT("\n", 1); + padding = 0; + } + } + + return outpos; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) +{ + // This cell is EOL if this and every cell to the right is black + for (; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if (cell->schar != 0) { + return 0; + } + } + + return 1; +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) +{ + *cursorpos = state->pos; +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) +{ + state->bold_is_highbright = bold_is_highbright; +} + +/// Compares two colours. Returns true if the colors are equal, false otherwise. +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) +{ + // First make sure that the two colours are of the same type (RGB/Indexed) + if (a->type != b->type) { + return false; + } + + // Depending on the type inspect the corresponding members + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) + && (a->rgb.green == b->rgb.green) + && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} diff --git a/test/unit/fixtures/vterm_test.h b/test/unit/fixtures/vterm_test.h index a05e7d499e..ef6463af6d 100644 --- a/test/unit/fixtures/vterm_test.h +++ b/test/unit/fixtures/vterm_test.h @@ -2,8 +2,17 @@ #include #include "nvim/macros_defs.h" -#include "vterm/vterm.h" +#include "nvim/vterm/vterm.h" +EXTERN VTermPos state_pos; +EXTERN bool want_state_putglyph INIT (=false); +EXTERN bool want_state_movecursor INIT(= false); +EXTERN bool want_state_erase INIT(= false); +EXTERN bool want_state_scrollrect INIT(= false); +EXTERN bool want_state_moverect INIT(= false); +EXTERN bool want_state_settermprop INIT(= false); +EXTERN bool want_state_scrollback INIT(= false); +EXTERN bool want_screen_scrollback INIT(= false); int parser_text(const char bytes[], size_t len, void *user); int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); @@ -27,12 +36,10 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user); int screen_sb_popline(int cols, VTermScreenCell *cells, void *user); int screen_sb_clear(void *user); void term_output(const char *s, size_t len, void *user); -EXTERN VTermPos state_pos; -EXTERN bool want_state_putglyph INIT (=false); -EXTERN bool want_state_movecursor INIT(= false); -EXTERN bool want_state_erase INIT(= false); -EXTERN bool want_state_scrollrect INIT(= false); -EXTERN bool want_state_moverect INIT(= false); -EXTERN bool want_state_settermprop INIT(= false); -EXTERN bool want_state_scrollback INIT(= false); -EXTERN bool want_screen_scrollback INIT(= false); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); +size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, VTermRect rect); +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 2457525fb7..db0aa3c575 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -61,7 +61,6 @@ local bit = require('bit') --- @field vterm_screen_enable_reflow function --- @field vterm_screen_get_attrs_extent function --- @field vterm_screen_get_cell function ---- @field vterm_screen_get_chars fun(any, any, any, any):any --- @field vterm_screen_get_text fun(any, any, any, any):any --- @field vterm_screen_is_eol fun(any, any):any --- @field vterm_screen_reset function @@ -79,10 +78,17 @@ local bit = require('bit') --- @field vterm_state_set_selection_callbacks function --- @field vterm_state_set_unrecognised_fallbacks function local vterm = t.cimport( - './src/nvim/mbyte.h', './src/nvim/grid.h', - './src/vterm/vterm.h', - './src/vterm/vterm_internal.h', + './src/nvim/mbyte.h', + './src/nvim/vterm/encoding.h', + './src/nvim/vterm/keyboard.h', + './src/nvim/vterm/mouse.h', + './src/nvim/vterm/parser.h', + './src/nvim/vterm/pen.h', + './src/nvim/vterm/screen.h', + './src/nvim/vterm/state.h', + './src/nvim/vterm/vterm.h', + './src/nvim/vterm/vterm_internal.h', './test/unit/fixtures/vterm_test.h' ) -- cgit From b67fcd0488746b079a3b721ae4800af94cd126e1 Mon Sep 17 00:00:00 2001 From: Jared Baur <45740526+jmbaur@users.noreply.github.com> Date: Tue, 7 Jan 2025 04:21:15 -0800 Subject: fix(highlight): make `TablineSel` more noticeable #31896 The default `TablineSel` highlighting makes it subjectively difficult to differentiate the selected tab from unselected ones. --- src/nvim/highlight_group.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 6fd0b858a8..6575113e2f 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -150,7 +150,6 @@ static const char *highlight_init_both[] = { "PmenuMatchSel gui=bold cterm=bold", "PmenuSel gui=reverse cterm=reverse,underline blend=0", "RedrawDebugNormal gui=reverse cterm=reverse", - "TabLineSel gui=bold cterm=bold", "TermCursor gui=reverse cterm=reverse", "Underlined gui=underline cterm=underline", "lCursor guifg=bg guibg=fg", @@ -181,6 +180,7 @@ static const char *highlight_init_both[] = { "default link StatusLineTermNC StatusLineNC", "default link TabLine StatusLineNC", "default link TabLineFill TabLine", + "default link TabLineSel Normal", "default link VertSplit WinSeparator", "default link VisualNOS Visual", "default link Whitespace NonText", -- cgit From d9ee0d2984e5fc30cb032785d32f42c72c7e64e1 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Wed, 1 Jan 2025 11:33:45 -0800 Subject: perf(treesitter): don't fetch parser for each fold line **Problem:** The treesitter `foldexpr` calls `get_parser()` for each line in the buffer when calculating folds. This can be incredibly slow for buffers where a parser cannot be found (because the result is not cached), and exponentially more so when the user has many `runtimepath`s. **Solution:** Only fetch the parser when it is needed; that is, only when initializing fold data for a buffer. Co-authored-by: Jongwook Choi Co-authored-by: Justin M. Keyes --- runtime/doc/news.txt | 2 + runtime/lua/vim/treesitter/_fold.lua | 34 +++++++++++---- test/functional/treesitter/fold_spec.lua | 75 ++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b7606e65f5..17f85154c2 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -287,6 +287,8 @@ PERFORMANCE highlighting. • LSP diagnostics and inlay hints are de-duplicated (new requests cancel inflight requests). This greatly improves performance with slow LSP servers. +• 10x speedup for |vim.treesitter.foldexpr()| (when no parser exists for the + buffer). PLUGINS diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 207ac1ab67..d16013eca2 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -19,14 +19,19 @@ local api = vim.api ---The range on which to evaluate foldexpr. ---When in insert mode, the evaluation is deferred to InsertLeave. ---@field foldupdate_range? Range2 +--- +---The treesitter parser associated with this buffer. +---@field parser? vim.treesitter.LanguageTree local FoldInfo = {} FoldInfo.__index = FoldInfo ---@private -function FoldInfo.new() +---@param bufnr integer +function FoldInfo.new(bufnr) return setmetatable({ levels0 = {}, levels = {}, + parser = ts.get_parser(bufnr, nil, { error = false }), }, FoldInfo) end @@ -69,7 +74,10 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) - local parser = assert(ts.get_parser(bufnr, nil, { error = false })) + local parser = info.parser + if not parser then + return + end parser:parse(parse_injections and { srow, erow } or nil) @@ -347,13 +355,21 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - local parser = ts.get_parser(bufnr, nil, { error = false }) - if not parser then - return '0' - end - if not foldinfos[bufnr] then - foldinfos[bufnr] = FoldInfo.new() + foldinfos[bufnr] = FoldInfo.new(bufnr) + api.nvim_create_autocmd('BufUnload', { + buffer = bufnr, + once = true, + callback = function() + foldinfos[bufnr] = nil + end, + }) + + local parser = foldinfos[bufnr].parser + if not parser then + return '0' + end + compute_folds_levels(bufnr, foldinfos[bufnr]) parser:register_cbs({ @@ -383,7 +399,7 @@ api.nvim_create_autocmd('OptionSet', { or foldinfos[buf] and { buf } or {} for _, bufnr in ipairs(bufs) do - foldinfos[bufnr] = FoldInfo.new() + foldinfos[bufnr] = FoldInfo.new(bufnr) api.nvim_buf_call(bufnr, function() compute_folds_levels(bufnr, foldinfos[bufnr]) end) diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index e38e58ff92..9f7fdf529f 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -5,6 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = n.clear local eq = t.eq local insert = n.insert +local write_file = t.write_file local exec_lua = n.exec_lua local command = n.command local feed = n.feed @@ -767,4 +768,78 @@ t2]]) ]], } end) + + it("doesn't call get_parser too often when parser is not available", function() + -- spy on vim.treesitter.get_parser() to keep track of how many times it is called + exec_lua(function() + _G.count = 0 + vim.treesitter.get_parser = (function(wrapped) + return function(...) + _G.count = _G.count + 1 + return wrapped(...) + end + end)(vim.treesitter.get_parser) + end) + + insert(test_text) + command [[ + set filetype=some_filetype_without_treesitter_parser + set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 + ]] + + -- foldexpr will return '0' for all lines + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end + + eq( + 1, + exec_lua [[ return _G.count ]], + 'count should not be as high as the # of lines; actually only once for the buffer.' + ) + end) + + it('can detect a new parser and refresh folds accordingly', function() + write_file('test_fold_file.txt', test_text) + command [[ + e test_fold_file.txt + set filetype=some_filetype_without_treesitter_parser + set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 + ]] + + -- foldexpr will return '0' for all lines + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end + + -- reload buffer as c filetype to simulate new parser being found + feed('GA// vim: ft=c') + command([[w | e]]) + + eq({ + [1] = '>1', + [2] = '1', + [3] = '1', + [4] = '1', + [5] = '>2', + [6] = '2', + [7] = '2', + [8] = '1', + [9] = '1', + [10] = '>2', + [11] = '2', + [12] = '2', + [13] = '2', + [14] = '2', + [15] = '>3', + [16] = '3', + [17] = '3', + [18] = '2', + [19] = '1', + }, get_fold_levels()) + end) end) -- cgit From b12b91c2743954dbe8599caa60e58e5d74aa4e76 Mon Sep 17 00:00:00 2001 From: glepnir Date: Wed, 8 Jan 2025 00:09:01 +0800 Subject: feat(health): show :checkhealth in floating window #31086 Problem: health can not shown in a floating window Solution: add g:health variable --- runtime/doc/health.txt | 11 +++++++++ runtime/doc/lsp.txt | 2 +- runtime/doc/news.txt | 2 ++ runtime/lua/vim/health.lua | 41 +++++++++++++++++++++++++++++----- runtime/lua/vim/lsp/util.lua | 8 ++++--- test/functional/plugin/health_spec.lua | 12 ++++++++++ 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/runtime/doc/health.txt b/runtime/doc/health.txt index cb70961b55..bca145bd8e 100644 --- a/runtime/doc/health.txt +++ b/runtime/doc/health.txt @@ -21,6 +21,17 @@ To run all healthchecks, use: >vim < Plugin authors are encouraged to write new healthchecks. |health-dev| + *g:health* +g:health This global variable controls the behavior and appearance of the + `health` floating window. It should be a dictionary containing the + following optional keys: + - `style`: string? Determines the display style of the `health` window. + Set to `'float'` to enable a floating window. Other + styles are not currently supported. + + Example: >lua + vim.g.health = { style = 'float' } + Commands *health-commands* *:che* *:checkhealth* diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 71e8a12ca3..16e6abe294 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2024,7 +2024,7 @@ Lua module: vim.lsp.util *lsp-util* • {zindex}? (`integer`) override `zindex`, defaults to 50 • {title}? (`string`) • {title_pos}? (`'left'|'center'|'right'`) - • {relative}? (`'mouse'|'cursor'`) (default: `'cursor'`) + • {relative}? (`'mouse'|'cursor'|'editor'`) (default: `'cursor'`) • {anchor_bias}? (`'auto'|'above'|'below'`, default: `'auto'`) - "auto": place window based on which side of the cursor has more lines diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 17f85154c2..931f5e117c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -359,6 +359,8 @@ UI • |vim.diagnostic.setqflist()| updates an existing quickfix list with the given title if found • |ui-messages| content chunks now also contain the highlight group ID. +• |:checkhealth| can be display in a floating window and controlled by + the |g:health| variable. ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 01166628b1..6dc902489f 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -11,6 +11,17 @@ --- < --- Plugin authors are encouraged to write new healthchecks. |health-dev| --- +--- *g:health* +--- g:health This global variable controls the behavior and appearance of the +--- `health` floating window. It should be a dictionary containing the +--- following optional keys: +--- - `style`: string? Determines the display style of the `health` window. +--- Set to `'float'` to enable a floating window. Other +--- styles are not currently supported. +--- +--- Example: >lua +--- vim.g.health = { style = 'float' } +--- --- Commands *health-commands* --- --- *:che* *:checkhealth* @@ -331,13 +342,31 @@ function M._check(mods, plugin_names) local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$') - -- When no command modifiers are used: - -- - If the current buffer is empty, open healthcheck directly. - -- - If not specified otherwise open healthcheck in a tab. - local buf_cmd = #mods > 0 and (mods .. ' sbuffer') or emptybuf and 'buffer' or 'tab sbuffer' - local bufnr = vim.api.nvim_create_buf(true, true) - vim.cmd(buf_cmd .. ' ' .. bufnr) + if + vim.g.health + and type(vim.g.health) == 'table' + and vim.tbl_get(vim.g.health, 'style') == 'float' + then + local max_height = math.floor(vim.o.lines * 0.8) + local max_width = 80 + local float_bufnr, float_winid = vim.lsp.util.open_floating_preview({}, '', { + height = max_height, + width = max_width, + offset_x = math.floor((vim.o.columns - max_width) / 2), + offset_y = math.floor((vim.o.lines - max_height) / 2) - 1, + relative = 'editor', + }) + vim.api.nvim_set_current_win(float_winid) + vim.bo[float_bufnr].modifiable = true + vim.wo[float_winid].list = false + else + -- When no command modifiers are used: + -- - If the current buffer is empty, open healthcheck directly. + -- - If not specified otherwise open healthcheck in a tab. + local buf_cmd = #mods > 0 and (mods .. ' sbuffer') or emptybuf and 'buffer' or 'tab sbuffer' + vim.cmd(buf_cmd .. ' ' .. bufnr) + end if vim.fn.bufexists('health://') == 1 then vim.cmd.bwipe('health://') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 89b774816b..5cccb3321f 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -875,11 +875,13 @@ function M.make_floating_popup_options(width, height, opts) return { anchor = anchor, + row = row + (opts.offset_y or 0), col = col + (opts.offset_x or 0), height = height, focusable = opts.focusable, - relative = opts.relative == 'mouse' and 'mouse' or 'cursor', - row = row + (opts.offset_y or 0), + relative = opts.relative == 'mouse' and 'mouse' + or opts.relative == 'editor' and 'editor' + or 'cursor', style = 'minimal', width = width, border = opts.border or default_border, @@ -1494,7 +1496,7 @@ end --- @field title_pos? 'left'|'center'|'right' --- --- (default: `'cursor'`) ---- @field relative? 'mouse'|'cursor' +--- @field relative? 'mouse'|'cursor'|'editor' --- --- - "auto": place window based on which side of the cursor has more lines --- - "above": place the window above the cursor unless there are not enough lines diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 753da64522..406b5c3c16 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -66,6 +66,18 @@ describe(':checkhealth', function() eq({}, getcompletion('', 'checkhealth')) assert_alive() end) + + it('vim.g.health', function() + clear() + command("let g:health = {'style':'float'}") + command('checkhealth lsp') + eq( + 'editor', + exec_lua([[ + return vim.api.nvim_win_get_config(0).relative + ]]) + ) + end) end) describe('vim.health', function() -- cgit From 67192760409be55b9522dfa34d3c24aa22883a0d Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Wed, 8 Jan 2025 00:02:35 +0200 Subject: fix(highlight): make TablineSel more noticeable with 'notermguicolors' #31905 Problem: Linking `TablineSel` to `Normal` makes it more noticeable with `notermguicolors` but less so with `termguicolors` (compared to using bold text in both cases). Solution: use bold text with `termguicolors` and regular with `notermguicolors`. --- src/nvim/highlight_group.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 6575113e2f..ad4b2732f6 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -150,6 +150,7 @@ static const char *highlight_init_both[] = { "PmenuMatchSel gui=bold cterm=bold", "PmenuSel gui=reverse cterm=reverse,underline blend=0", "RedrawDebugNormal gui=reverse cterm=reverse", + "TabLineSel gui=bold cterm=NONE", "TermCursor gui=reverse cterm=reverse", "Underlined gui=underline cterm=underline", "lCursor guifg=bg guibg=fg", @@ -180,7 +181,6 @@ static const char *highlight_init_both[] = { "default link StatusLineTermNC StatusLineNC", "default link TabLine StatusLineNC", "default link TabLineFill TabLine", - "default link TabLineSel Normal", "default link VertSplit WinSeparator", "default link VisualNOS Visual", "default link Whitespace NonText", -- cgit From 141114c170d6dd9a7457b7822184970fd6ca0a43 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 8 Jan 2025 13:16:41 +0800 Subject: fix(api): crash on invalid buffer to nvim_buf_del_user_command (#31908) --- src/nvim/api/command.c | 3 +++ test/functional/api/command_spec.lua | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 7e79133ed4..23e08bd3fe 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -931,6 +931,9 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) gap = &ucmds; } else { buf_T *buf = find_buffer_by_handle(buffer, err); + if (ERROR_SET(err)) { + return; + } gap = &buf->b_ucmds; } diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index a16c6a88e3..fabd9be6d6 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -651,6 +651,11 @@ describe('nvim_create_user_command', function() api.nvim_set_current_buf(bufnr) command('Hello') assert_alive() + eq( + 'Invalid buffer id: 1234', + pcall_err(api.nvim_buf_create_user_command, 1234, 'Hello', '', {}) + ) + assert_alive() end) it('can use a Lua complete function', function() @@ -771,5 +776,9 @@ describe('nvim_del_user_command', function() command('Hello') api.nvim_buf_del_user_command(0, 'Hello') matches('Not an editor command: Hello', pcall_err(command, 'Hello')) + eq('Invalid command (not found): Hello', pcall_err(api.nvim_buf_del_user_command, 0, 'Hello')) + eq('Invalid command (not found): Bye', pcall_err(api.nvim_buf_del_user_command, 0, 'Bye')) + eq('Invalid buffer id: 1234', pcall_err(api.nvim_buf_del_user_command, 1234, 'Hello')) + assert_alive() end) end) -- cgit From 561580aba5307cbd4d4a6c00cb39048831109bc7 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 7 Jan 2025 21:32:55 +0100 Subject: vim-patch:9.1.0995: filetype: shaderslang files are not detected Problem: filetype: shaderslang files are not detected Solution: detect '*.slang' files as shaderslang filetype, include a filetype and syntax script (mtvare6) Reference: https://shader-slang.com/ closes: vim/vim#16387 https://github.com/vim/vim/commit/616219f684744bcfad61a53c13166cda9b141dea Co-authored-by: mtvare6 --- runtime/ftplugin/shaderslang.vim | 54 ++++++ runtime/lua/vim/filetype.lua | 1 + runtime/syntax/shaderslang.vim | 360 +++++++++++++++++++++++++++++++++++++ test/old/testdir/test_filetype.vim | 1 + 4 files changed, 416 insertions(+) create mode 100644 runtime/ftplugin/shaderslang.vim create mode 100644 runtime/syntax/shaderslang.vim diff --git a/runtime/ftplugin/shaderslang.vim b/runtime/ftplugin/shaderslang.vim new file mode 100644 index 0000000000..f3d1ab8c1c --- /dev/null +++ b/runtime/ftplugin/shaderslang.vim @@ -0,0 +1,54 @@ +" Vim filetype plugin file +" Language: Slang +" Maintainer: Austin Shijo +" Last Change: 2025 Jan 05 + +" Only do this when not done yet for this buffer +if exists("b:did_ftplugin") + finish +endif + +" Don't load another plugin for this buffer +let b:did_ftplugin = 1 + +" Using line continuation here. +let s:cpo_save = &cpo +set cpo-=C + +let b:undo_ftplugin = "setl fo< com< cms< inc<" + +" Set 'formatoptions' to break comment lines but not other lines, +" and insert the comment leader when hitting or using "o". +setlocal fo-=t fo+=croql + +" Set comment string (Slang uses C-style comments) +setlocal commentstring=//\ %s + +" Set 'comments' to format dashed lists in comments +setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,:// + +" When the matchit plugin is loaded, this makes the % command skip parens and +" braces in comments properly, and adds support for shader-specific keywords +if exists("loaded_matchit") + " Add common shader control structures + let b:match_words = '{\|^\s*\<\(if\|for\|while\|switch\|struct\|class\)\>:}\|^\s*\,' .. + \ '^\s*#\s*if\(\|def\|ndef\)\>:^\s*#\s*elif\>:^\s*#\s*else\>:^\s*#\s*endif\>,' .. + \ '\[:\]' + let b:match_skip = 's:comment\|string\|character\|special' + let b:match_ignorecase = 0 + let b:undo_ftplugin ..= " | unlet! b:match_skip b:match_words b:match_ignorecase" +endif + +" Win32 and GTK can filter files in the browse dialog +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") + let b:browsefilter = "Slang Source Files (*.slang)\t*.slang\n" + if has("win32") + let b:browsefilter ..= "All Files (*.*)\t*\n" + else + let b:browsefilter ..= "All Files (*)\t*\n" + endif + let b:undo_ftplugin ..= " | unlet! b:browsefilter" +endif + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index b6d6906589..033a04cdc7 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1097,6 +1097,7 @@ local extension = { la = 'sh', lai = 'sh', mdd = 'sh', + slang = 'shaderslang', sieve = 'sieve', siv = 'sieve', sig = detect.sig, diff --git a/runtime/syntax/shaderslang.vim b/runtime/syntax/shaderslang.vim new file mode 100644 index 0000000000..1cae202b04 --- /dev/null +++ b/runtime/syntax/shaderslang.vim @@ -0,0 +1,360 @@ +" Vim syntax file +" Language: Slang +" Maintainer: Austin Shijo +" Last Change: 2024 Jan 05 + +if exists("b:current_syntax") + finish +endif + +" Read the C syntax to start with +runtime! syntax/c.vim +unlet b:current_syntax + +" Annotations +syn match shaderslangAnnotation /<.*;>/ + +" Attributes +syn match shaderslangAttribute /^\s*\[maxvertexcount(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[domain(\s*"\(tri\|quad\|isoline\)"\s*)\]/ +syn match shaderslangAttribute /^\s*\[earlydepthstencil\]/ +syn match shaderslangAttribute /^\s*\[instance(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[maxtessfactor(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[numthreads(\s*\w\+\s*,\s*\w\+\s*,\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[outputcontrolpoints(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[outputtopology(\s*"\(point\|line\|triangle_cw\|triangle_ccw\|triangle\)"\s*)\]/ +syn match shaderslangAttribute /^\s*\[partitioning(\s*"\(integer\|fractional_even\|fractional_odd\|pow2\)"\s*)\]/ +syn match shaderslangAttribute /^\s*\[patchconstantfunc(\s*"\(\d\|\w\|_\)\+"\s*)\]/ +syn match shaderslangAttribute /^\s*\[WaveSize(\s*\w\+\(\s*,\s*\w\+\(\s*,\s*\w\+\)\?\)\?\s*)\]/ +syn match shaderslangAttribute /^\s*\[shader(\s*"\(anyhit\|callable\|closesthit\|intersection\|miss\|raygeneration\)"\s*)\]/ + +syn match shaderslangAttribute /^\s*\[fastopt\]/ +syn match shaderslangAttribute /^\s*\[loop\]/ +syn match shaderslangAttribute /^\s*\[unroll\]/ +syn match shaderslangAttribute /^\s*\[allow_uav_condition\]/ +syn match shaderslangAttribute /^\s*\[branch\]/ +syn match shaderslangAttribute /^\s*\[flatten\]/ +syn match shaderslangAttribute /^\s*\[forcecase\]/ +syn match shaderslangAttribute /^\s*\[call\]/ +syn match shaderslangAttribute /^\s*\[WaveOpsIncludeHelperLanes\]/ + +syn match shaderslangAttribute /\[raypayload\]/ + +" Work graph shader target attributes +syn match shaderslangAttribute /^\s*\[Shader(\s*"\(\d\|\w\|_\)\+"\s*)\]/ + +" Work graph shader function attributes +syn match shaderslangAttribute /^\s*\[NodeLaunch(\s*"\(broadcasting\|coalescing\|thread\)"\s*)\]/ +syn match shaderslangAttribute /^\s*\[NodeIsProgramEntry\]/ +syn match shaderslangAttribute /^\s*\[NodeLocalRootArgumentsTableIndex(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[NumThreads(\s*\w\+\s*,\s*\w\+\s*,\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[NodeShareInputOf(\s*"\w\+"\(\s*,\s*\w\+\)\?\s*)\]/ +syn match shaderslangAttribute /^\s*\[NodeDispatchGrid(\s*\w\+\s*,\s*\w\+\s*,\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[NodeMaxDispatchGrid(\s*\w\+\s*,\s*\w\+\s*,\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[NodeMaxRecursionDepth(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /^\s*\[NodeMaxInputRecordsPerGraphEntryRecord(\s*\w\+\s*,\s*\(true\|false\)\s*)\]/ + +" Work graph record attributes +syn match shaderslangAttribute /\[NodeTrackRWInputSharing\]/ +syn match shaderslangAttribute /\[MaxRecords(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /\[NodeID(\s*"\w\+"\(\s*,\s*\w\+\)\?\s*)\]/ +syn match shaderslangAttribute /\[MaxRecordsSharedWith(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /\[AllowSparseNodes\]/ +syn match shaderslangAttribute /\[NodeArraySize(\s*\w\+\s*)\]/ +syn match shaderslangAttribute /\[UnboundedSparseNodes\]/ + +" Intrinsic functions +syn keyword shaderslangFunc abs acos acosh asin asinh atan atanh cos cosh exp exp2 floor log log10 log2 round rsqrt sin sincos sinh sqrt tan tanh trunc +syn keyword shaderslangFunc AllMemoryBarrier AllMemoryBarrierWithGroupSync DeviceMemoryBarrier DeviceMemoryBarrierWithGroupSync GroupMemoryBarrier GroupMemoryBarrierWithGroupSync +syn keyword shaderslangFunc abort clip errorf printf +syn keyword shaderslangFunc all any countbits faceforward firstbithigh firstbitlow isfinite isinf isnan max min noise pow reversebits sign +syn keyword shaderslangFunc asdouble asfloat asint asuint D3DCOLORtoUBYTE4 f16tof32 f32tof16 +syn keyword shaderslangFunc ceil clamp degrees fma fmod frac frexp ldexp lerp mad modf radiants saturate smoothstep step +syn keyword shaderslangFunc cross determinant distance dot dst length lit msad4 mul normalize rcp reflect refract transpose +syn keyword shaderslangFunc ddx ddx_coarse ddx_fine ddy ddy_coarse ddy_fine fwidth +syn keyword shaderslangFunc EvaluateAttributeAtCentroid EvaluateAttributeAtSample EvaluateAttributeSnapped +syn keyword shaderslangFunc GetRenderTargetSampleCount GetRenderTargetSamplePosition +syn keyword shaderslangFunc InterlockedAdd InterlockedAnd InterlockedCompareExchange InterlockedCompareStore InterlockedExchange InterlockedMax InterlockedMin InterlockedOr InterlockedXor +syn keyword shaderslangFunc InterlockedCompareStoreFloatBitwise InterlockedCompareExchangeFloatBitwise +syn keyword shaderslangFunc Process2DQuadTessFactorsAvg Process2DQuadTessFactorsMax Process2DQuadTessFactorsMin ProcessIsolineTessFactors +syn keyword shaderslangFunc ProcessQuadTessFactorsAvg ProcessQuadTessFactorsMax ProcessQuadTessFactorsMin ProcessTriTessFactorsAvg ProcessTriTessFactorsMax ProcessTriTessFactorsMin +syn keyword shaderslangFunc tex1D tex1Dbias tex1Dgrad tex1Dlod tex1Dproj +syn keyword shaderslangFunc tex2D tex2Dbias tex2Dgrad tex2Dlod tex2Dproj +syn keyword shaderslangFunc tex3D tex3Dbias tex3Dgrad tex3Dlod tex3Dproj +syn keyword shaderslangFunc texCUBE texCUBEbias texCUBEgrad texCUBElod texCUBEproj +syn keyword shaderslangFunc WaveIsFirstLane WaveGetLaneCount WaveGetLaneIndex +syn keyword shaderslangFunc IsHelperLane +syn keyword shaderslangFunc WaveActiveAnyTrue WaveActiveAllTrue WaveActiveBallot +syn keyword shaderslangFunc WaveReadLaneFirst WaveReadLaneAt +syn keyword shaderslangFunc WaveActiveAllEqual WaveActiveAllEqualBool WaveActiveCountBits +syn keyword shaderslangFunc WaveActiveSum WaveActiveProduct WaveActiveBitAnd WaveActiveBitOr WaveActiveBitXor WaveActiveMin WaveActiveMax +syn keyword shaderslangFunc WavePrefixCountBits WavePrefixProduct WavePrefixSum +syn keyword shaderslangFunc QuadReadAcrossX QuadReadAcrossY QuadReadAcrossDiagonal QuadReadLaneAt +syn keyword shaderslangFunc QuadAny QuadAll +syn keyword shaderslangFunc WaveMatch WaveMultiPrefixSum WaveMultiPrefixProduct WaveMultiPrefixCountBits WaveMultiPrefixAnd WaveMultiPrefixOr WaveMultiPrefixXor +syn keyword shaderslangFunc NonUniformResourceIndex +syn keyword shaderslangFunc DispatchMesh SetMeshOutputCounts +syn keyword shaderslangFunc dot4add_u8packed dot4add_i8packed dot2add + +syn keyword shaderslangFunc RestartStrip +syn keyword shaderslangFunc CalculateLevelOfDetail CalculateLevelOfDetailUnclamped Gather GetDimensions GetSamplePosition Load Sample SampleBias SampleCmp SampleCmpLevelZero SampleGrad SampleLevel GatherRaw SampleCmpLevel +syn keyword shaderslangFunc SampleCmpBias SampleCmpGrad +syn keyword shaderslangFunc WriteSamplerFeedback WriteSamplerFeedbackBias WriteSamplerFeedbackGrad WriteSamplerFeedbackLevel +syn keyword shaderslangFunc Append Consume DecrementCounter IncrementCounter +syn keyword shaderslangFunc Load2 Load3 Load4 Store Store2 Store3 Store4 +syn keyword shaderslangFunc GatherRed GatherGreen GatherBlue GatherAlpha GatherCmp GatherCmpRed GatherCmpGreen GatherCmpBlue GatherCmpAlpha +syn match shaderslangFunc /\.mips\[\d\+\]\[\d\+\]/ +syn match shaderslangFunc /\.sample\[\d\+\]\[\d\+\]/ + +" Ray intrinsics +syn keyword shaderslangFunc AcceptHitAndEndSearch CallShader IgnoreHit ReportHit TraceRay +syn keyword shaderslangFunc DispatchRaysIndex DispatchRaysDimensions +syn keyword shaderslangFunc WorldRayOrigin WorldRayDirection RayTMin RayTCurrent RayFlags +syn keyword shaderslangFunc InstanceIndex InstanceID GeometryIndex PrimitiveIndex ObjectRayOrigin ObjectRayDirection ObjectToWorld3x4 ObjectToWorld4x3 WorldToObject3x4 WorldToObject4x3 +syn keyword shaderslangFunc HitKind + +" RayQuery intrinsics +syn keyword shaderslangFunc TraceRayInline Proceed Abort CommittedStatus +syn keyword shaderslangFunc CandidateType CandidateProceduralPrimitiveNonOpaque CandidateTriangleRayT CandidateInstanceIndex CandidateInstanceID CandidateInstanceContributionToHitGroupIndex CandidateGeometryIndex +syn keyword shaderslangFunc CandidatePrimitiveIndex CandidateObjectRayOrigin CandidateObjectRayDirection CandidateObjectToWorld3x4 CandidateObjectToWorld4x3 CandidateWorldToObject3x4 CandidateWorldToObject4x3 +syn keyword shaderslangFunc CommitNonOpaqueTriangleHit CommitProceduralPrimitiveHit CommittedStatus CommittedRayT CommittedInstanceIndex CommittedInstanceID CommittedInstanceContributionToHitGroupIndex +syn keyword shaderslangFunc CommittedGeometryIndex CommittedPrimitiveIndex CommittedObjectRayOrigin CommittedObjectRayDirection CommittedObjectToWorld3x4 CommittedObjectToWorld4x3 CommittedWorldToObject3x4 +syn keyword shaderslangFunc CommittedWorldToObject4x3 CandidateTriangleBarycentrics CandidateTriangleFrontFace CommittedTriangleBarycentrics CommittedTriangleFrontFace + +" Pack/unpack math intrinsics +syn keyword shaderslangFunc unpack_s8s16 unpack_u8u16 unpack_s8s32 unpack_u8u32 +syn keyword shaderslangFunc pack_u8 pack_s8 pack_clamp_u8 pack_clamp_s8 + +" Work graph object methods +syn keyword shaderslangFunc Get FinishedCrossGroupSharing Count GetThreadNodeOutputRecords GetGroupNodeOutputRecords IsValid GroupIncrementOutputCount ThreadIncrementOutputCount OutputComplete + +" Work graph free intrinsics +syn keyword shaderslangFunc GetRemainingRecursionLevels Barrier + +" Layout Qualifiers +syn keyword shaderslangLayoutQual const row_major column_major +syn keyword shaderslangLayoutQual point line triangle lineadj triangleadj +syn keyword shaderslangLayoutQual InputPatch OutputPatch +syn match shaderslangLayoutQual /PointStream<\s*\w\+\s*>/ +syn match shaderslangLayoutQual /LineStream<\s*\w\+\s*>/ +syn match shaderslangLayoutQual /TriangleStream<\s*\w\+\s*>/ + +" User defined Semantics +syn match shaderslangSemantic /:\s*[A-Z]\w*/ +syn match shaderslangSemantic /:\s*packoffset(\s*c\d\+\(\.[xyzw]\)\?\s*)/ " packoffset +syn match shaderslangSemantic /:\s*register(\s*\(r\|x\|v\|t\|s\|cb\|icb\|b\|c\|u\)\d\+\s*)/ " register +syn match shaderslangSemantic /:\s*read(\s*\(\(anyhit\|closesthit\|miss\|caller\)\s*,\s*\)*\(anyhit\|closesthit\|miss\|caller\)\?\s*)/ " read +syn match shaderslangSemantic /:\s*write(\s*\(\(anyhit\|closesthit\|miss\|caller\)\s*,\s*\)*\(anyhit\|closesthit\|miss\|caller\)\?\s*)/ " write + +" System-Value Semantics +" Vertex Shader +syn match shaderslangSemantic /SV_ClipDistance\d\+/ +syn match shaderslangSemantic /SV_CullDistance\d\+/ +syn keyword shaderslangSemantic SV_Position SV_InstanceID SV_PrimitiveID SV_VertexID +syn keyword shaderslangSemantic SV_StartVertexLocation SV_StartInstanceLocation +" Tessellation pipeline +syn keyword shaderslangSemantic SV_DomainLocation SV_InsideTessFactor SV_OutputControlPointID SV_TessFactor +" Geometry Shader +syn keyword shaderslangSemantic SV_GSInstanceID SV_RenderTargetArrayIndex +" Pixel Shader - MSAA +syn keyword shaderslangSemantic SV_Coverage SV_Depth SV_IsFrontFace SV_SampleIndex +syn match shaderslangSemantic /SV_Target[0-7]/ +syn keyword shaderslangSemantic SV_ShadingRate SV_ViewID +syn match shaderslangSemantic /SV_Barycentrics[0-1]/ +" Compute Shader +syn keyword shaderslangSemantic SV_DispatchThreadID SV_GroupID SV_GroupIndex SV_GroupThreadID +" Mesh shading pipeline +syn keyword shaderslangSemantic SV_CullPrimitive +" Work graph record system values +syn keyword shaderslangSemantic SV_DispatchGrid + +" slang structures +syn keyword shaderslangStructure cbuffer + +" Shader profiles +" Cg profiles +syn keyword shaderslangProfile arbfp1 arbvp1 fp20 vp20 fp30 vp30 ps_1_1 ps_1_2 ps_1_3 +" Shader Model 1 +syn keyword shaderslangProfile vs_1_1 +" Shader Model 2 +syn keyword shaderslangProfile ps_2_0 ps_2_x vs_2_0 vs_2_x +" Shader Model 3 +syn keyword shaderslangProfile ps_3_0 vs_3_0 +" Shader Model 4 +syn keyword shaderslangProfile gs_4_0 ps_4_0 vs_4_0 gs_4_1 ps_4_1 vs_4_1 +" Shader Model 5 +syn keyword shaderslangProfile cs_4_0 cs_4_1 cs_5_0 ds_5_0 gs_5_0 hs_5_0 ps_5_0 vs_5_0 +" Shader Model 6 +syn keyword shaderslangProfile cs_6_0 ds_6_0 gs_6_0 hs_6_0 ps_6_0 vs_6_0 lib_6_0 + +" Swizzling +syn match shaderslangSwizzle /\.[xyzw]\{1,4\}\>/ +syn match shaderslangSwizzle /\.[rgba]\{1,4\}\>/ +syn match shaderslangSwizzle /\.\(_m[0-3]\{2}\)\{1,4\}/ +syn match shaderslangSwizzle /\.\(_[1-4]\{2}\)\{1,4\}/ + +" Other Statements +syn keyword shaderslangStatement discard + +" Storage class +syn match shaderslangStorageClass /\/ +syn match shaderslangStorageClass /\/ +syn keyword shaderslangStorageClass inout +syn keyword shaderslangStorageClass extern nointerpolation precise shared groupshared static uniform volatile +syn keyword shaderslangStorageClass snorm unorm +syn keyword shaderslangStorageClass linear centroid nointerpolation noperspective sample +syn keyword shaderslangStorageClass globallycoherent + +" Types +" Buffer types +syn keyword shaderslangType ConstantBuffer Buffer ByteAddressBuffer ConsumeStructuredBuffer StructuredBuffer +syn keyword shaderslangType AppendStructuredBuffer RWBuffer RWByteAddressBuffer RWStructuredBuffer +syn keyword shaderslangType RasterizerOrderedBuffer RasterizerOrderedByteAddressBuffer RasterizerOrderedStructuredBuffer + +" Scalar types +syn keyword shaderslangType bool int uint dword half float double +syn keyword shaderslangType min16float min10float min16int min12int min16uint +syn keyword shaderslangType float16_t float32_t float64_t + +" Vector types +syn match shaderslangType /vector<\s*\w\+,\s*[1-4]\s*>/ +syn keyword shaderslangType bool1 bool2 bool3 bool4 +syn keyword shaderslangType int1 int2 int3 int4 +syn keyword shaderslangType uint1 uint2 uint3 uint4 +syn keyword shaderslangType dword1 dword2 dword3 dword4 +syn keyword shaderslangType half1 half2 half3 half4 +syn keyword shaderslangType float1 float2 float3 float4 +syn keyword shaderslangType double1 double2 double3 double4 +syn keyword shaderslangType min16float1 min16float2 min16float3 min16float4 +syn keyword shaderslangType min10float1 min10float2 min10float3 min10float4 +syn keyword shaderslangType min16int1 min16int2 min16int3 min16int4 +syn keyword shaderslangType min12int1 min12int2 min12int3 min12int4 +syn keyword shaderslangType min16uint1 min16uint2 min16uint3 min16uint4 +syn keyword shaderslangType float16_t1 float16_t2 float16_t3 float16_t4 +syn keyword shaderslangType float32_t1 float32_t2 float32_t3 float32_t4 +syn keyword shaderslangType float64_t1 float64_t2 float64_t3 float64_t4 +syn keyword shaderslangType int16_t1 int16_t2 int16_t3 int16_t4 +syn keyword shaderslangType int32_t1 int32_t2 int32_t3 int32_t4 +syn keyword shaderslangType int64_t1 int64_t2 int64_t3 int64_t4 +syn keyword shaderslangType uint16_t1 uint16_t2 uint16_t3 uint16_t4 +syn keyword shaderslangType uint32_t1 uint32_t2 uint32_t3 uint32_t4 +syn keyword shaderslangType uint64_t1 uint64_t2 uint64_t3 uint64_t4 + +" Packed types +syn keyword shaderslangType uint8_t4_packed int8_t4_packed + +" Matrix types +syn match shaderslangType /matrix<\s*\w\+\s*,\s*[1-4]\s*,\s*[1-4]\s*>/ +syn keyword shaderslangType bool1x1 bool2x1 bool3x1 bool4x1 bool1x2 bool2x2 bool3x2 bool4x2 bool1x3 bool2x3 bool3x3 bool4x3 bool1x4 bool2x4 bool3x4 bool4x4 +syn keyword shaderslangType int1x1 int2x1 int3x1 int4x1 int1x2 int2x2 int3x2 int4x2 int1x3 int2x3 int3x3 int4x3 int1x4 int2x4 int3x4 int4x4 +syn keyword shaderslangType uint1x1 uint2x1 uint3x1 uint4x1 uint1x2 uint2x2 uint3x2 uint4x2 uint1x3 uint2x3 uint3x3 uint4x3 uint1x4 uint2x4 uint3x4 uint4x4 +syn keyword shaderslangType dword1x1 dword2x1 dword3x1 dword4x1 dword1x2 dword2x2 dword3x2 dword4x2 dword1x3 dword2x3 dword3x3 dword4x3 dword1x4 dword2x4 dword3x4 dword4x4 +syn keyword shaderslangType half1x1 half2x1 half3x1 half4x1 half1x2 half2x2 half3x2 half4x2 half1x3 half2x3 half3x3 half4x3 half1x4 half2x4 half3x4 half4x4 +syn keyword shaderslangType float1x1 float2x1 float3x1 float4x1 float1x2 float2x2 float3x2 float4x2 float1x3 float2x3 float3x3 float4x3 float1x4 float2x4 float3x4 float4x4 +syn keyword shaderslangType double1x1 double2x1 double3x1 double4x1 double1x2 double2x2 double3x2 double4x2 double1x3 double2x3 double3x3 double4x3 double1x4 double2x4 double3x4 double4x4 +syn keyword shaderslangType min16float1x1 min16float2x1 min16float3x1 min16float4x1 min16float1x2 min16float2x2 min16float3x2 min16float4x2 min16float1x3 min16float2x3 min16float3x3 min16float4x3 min16float1x4 min16float2x4 min16float3x4 min16float4x4 +syn keyword shaderslangType min10float1x1 min10float2x1 min10float3x1 min10float4x1 min10float1x2 min10float2x2 min10float3x2 min10float4x2 min10float1x3 min10float2x3 min10float3x3 min10float4x3 min10float1x4 min10float2x4 min10float3x4 min10float4x4 +syn keyword shaderslangType min16int1x1 min16int2x1 min16int3x1 min16int4x1 min16int1x2 min16int2x2 min16int3x2 min16int4x2 min16int1x3 min16int2x3 min16int3x3 min16int4x3 min16int1x4 min16int2x4 min16int3x4 min16int4x4 +syn keyword shaderslangType min12int1x1 min12int2x1 min12int3x1 min12int4x1 min12int1x2 min12int2x2 min12int3x2 min12int4x2 min12int1x3 min12int2x3 min12int3x3 min12int4x3 min12int1x4 min12int2x4 min12int3x4 min12int4x4 +syn keyword shaderslangType min16uint1x1 min16uint2x1 min16uint3x1 min16uint4x1 min16uint1x2 min16uint2x2 min16uint3x2 min16uint4x2 min16uint1x3 min16uint2x3 min16uint3x3 min16uint4x3 min16uint1x4 min16uint2x4 min16uint3x4 min16uint4x4 +syn keyword shaderslangType float16_t1x1 float16_t2x1 float16_t3x1 float16_t4x1 float16_t1x2 float16_t2x2 float16_t3x2 float16_t4x2 float16_t1x3 float16_t2x3 float16_t3x3 float16_t4x3 float16_t1x4 float16_t2x4 float16_t3x4 float16_t4x4 +syn keyword shaderslangType float32_t1x1 float32_t2x1 float32_t3x1 float32_t4x1 float32_t1x2 float32_t2x2 float32_t3x2 float32_t4x2 float32_t1x3 float32_t2x3 float32_t3x3 float32_t4x3 float32_t1x4 float32_t2x4 float32_t3x4 float32_t4x4 +syn keyword shaderslangType float64_t1x1 float64_t2x1 float64_t3x1 float64_t4x1 float64_t1x2 float64_t2x2 float64_t3x2 float64_t4x2 float64_t1x3 float64_t2x3 float64_t3x3 float64_t4x3 float64_t1x4 float64_t2x4 float64_t3x4 float64_t4x4 +syn keyword shaderslangType int16_t1x1 int16_t2x1 int16_t3x1 int16_t4x1 int16_t1x2 int16_t2x2 int16_t3x2 int16_t4x2 int16_t1x3 int16_t2x3 int16_t3x3 int16_t4x3 int16_t1x4 int16_t2x4 int16_t3x4 int16_t4x4 +syn keyword shaderslangType int32_t1x1 int32_t2x1 int32_t3x1 int32_t4x1 int32_t1x2 int32_t2x2 int32_t3x2 int32_t4x2 int32_t1x3 int32_t2x3 int32_t3x3 int32_t4x3 int32_t1x4 int32_t2x4 int32_t3x4 int32_t4x4 +syn keyword shaderslangType int64_t1x1 int64_t2x1 int64_t3x1 int64_t4x1 int64_t1x2 int64_t2x2 int64_t3x2 int64_t4x2 int64_t1x3 int64_t2x3 int64_t3x3 int64_t4x3 int64_t1x4 int64_t2x4 int64_t3x4 int64_t4x4 +syn keyword shaderslangType uint16_t1x1 uint16_t2x1 uint16_t3x1 uint16_t4x1 uint16_t1x2 uint16_t2x2 uint16_t3x2 uint16_t4x2 uint16_t1x3 uint16_t2x3 uint16_t3x3 uint16_t4x3 uint16_t1x4 uint16_t2x4 uint16_t3x4 uint16_t4x4 +syn keyword shaderslangType uint32_t1x1 uint32_t2x1 uint32_t3x1 uint32_t4x1 uint32_t1x2 uint32_t2x2 uint32_t3x2 uint32_t4x2 uint32_t1x3 uint32_t2x3 uint32_t3x3 uint32_t4x3 uint32_t1x4 uint32_t2x4 uint32_t3x4 uint32_t4x4 +syn keyword shaderslangType uint64_t1x1 uint64_t2x1 uint64_t3x1 uint64_t4x1 uint64_t1x2 uint64_t2x2 uint64_t3x2 uint64_t4x2 uint64_t1x3 uint64_t2x3 uint64_t3x3 uint64_t4x3 uint64_t1x4 uint64_t2x4 uint64_t3x4 uint64_t4x4 + +" Sampler types +syn keyword shaderslangType SamplerState SamplerComparisonState +syn keyword shaderslangType sampler sampler1D sampler2D sampler3D samplerCUBE sampler_state + +" Texture types +syn keyword shaderslangType Texture1D Texture1DArray Texture2D Texture2DArray Texture2DMS Texture2DMSArray Texture3D TextureCube TextureCubeArray +syn keyword shaderslangType RWTexture1D RWTexture2D RWTexture2DArray RWTexture3D RWTextureCubeArray RWTexture2DMS RWTexture2DMSArray +syn keyword shaderslangType FeedbackTexture2D FeedbackTexture2DArray +syn keyword shaderslangType RasterizerOrderedTexture1D RasterizerOrderedTexture1DArray RasterizerOrderedTexture2D RasterizerOrderedTexture2DArray RasterizerOrderedTexture3D +syn keyword shaderslangTypeDeprec texture texture1D texture2D texture3D + +" Raytracing types +syn keyword shaderslangType RaytracingAccelerationStructure RayDesc RayQuery BuiltInTriangleIntersectionAttributes + +" Work graph input record objects +syn keyword shaderslangType DispatchNodeInputRecord RWDispatchNodeInputRecord GroupNodeInputRecords RWGroupNodeInputRecords ThreadNodeInputRecord RWThreadNodeInputRecord EmptyNodeInput + +" Work graph output node objects +syn keyword shaderslangType NodeOutput NodeOutputArray EmptyNodeOutput EmptyNodeOutputArray + +" Work graph output record objects +syn keyword shaderslangType ThreadNodeOutputRecords GroupNodeOutputRecords + +" State Groups args +syn case ignore " This section case insensitive + +" Blend state group +syn keyword shaderslangStateGroupArg AlphaToCoverageEnable BlendEnable SrcBlend DestBlend BlendOp SrcBlendAlpha DestBlendAlpha BlendOpAlpha RenderTargetWriteMask +syn keyword shaderslangStateGroupVal ZERO ONE SRC_COLOR INV_SRC_COLOR SRC_ALPHA INV_SRC_ALPHA DEST_ALPHA INV_DEST_ALPHA DEST_COLOR INV_DEST_COLOR SRC_ALPHA_SAT BLEND_FACTOR INV_BLEND_FACTOR SRC1_COLOR INV_SRC1_COLOR SRC1_ALPHA INV_SRC1_ALPHA +syn keyword shaderslangStateGroupVal ADD SUBSTRACT REV_SUBSTRACT MIN MAX + +" Rasterizer state group +syn keyword shaderslangStateGroupArg FillMode CullMode FrontCounterClockwise DepthBias DepthBiasClamp SlopeScaledDepthBias ZClipEnable DepthClipEnable ScissorEnable MultisampleEnable AntialiasedLineEnable +syn keyword shaderslangStateGroupVal SOLID WIREFRAME +syn keyword shaderslangStateGroupVal NONE FRONT BACK + +" Sampler state group +syn keyword shaderslangStateGroupArg Filter AddressU AddressV AddressW MipLODBias MaxAnisotropy ComparisonFunc BorderColor MinLOD MaxLOD ComparisonFilter +syn keyword shaderslangStateGroupVal MIN_MAG_MIP_POINT MIN_MAG_POINT_MIP_LINEAR MIN_POINT_MAG_LINEAR_MIP_POINT MIN_POINT_MAG_MIP_LINEAR MIN_LINEAR_MAG_MIP_POINT MIN_LINEAR_MAG_POINT_MIP_LINEAR MIN_MAG_LINEAR_MIP_POINT MIN_MAG_MIP_LINEAR ANISOTROPIC +syn keyword shaderslangStateGroupVal COMPARISON_MIN_MAG_MIP_POINT COMPARISON_MIN_MAG_POINT_MIP_LINEAR COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT COMPARISON_MIN_POINT_MAG_MIP_LINEAR COMPARISON_MIN_LINEAR_MAG_MIP_POINT +syn keyword shaderslangStateGroupVal COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR COMPARISON_MIN_MAG_LINEAR_MIP_POINT COMPARISON_MIN_MAG_MIP_LINEAR COMPARISON_ANISOTROPIC +syn keyword shaderslangStateGroupVal COMPARISON_NEVER COMPARISON_LESS COMPARISON_EQUAL COMPARISON_LESS_EQUAL COMPARISON_GREATER COMPARISON_NOT_EQUAL COMPARISON_GREATER_EQUAL COMPARISON_ALWAYS +syn keyword shaderslangStateGroupVal WRAP MIRROR CLAMP BORDER MIRROR_ONCE +syn keyword shaderslangStateGroupVal SAMPLER_FEEDBACK_MIN_MIP SAMPLER_FEEDBACK_MIP_REGION_USED + +" Ray flags +syn keyword shaderslangStateGroupVal RAY_FLAG_NONE RAY_FLAG_FORCE_OPAQUE RAY_FLAG_FORCE_NON_OPAQUE RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH RAY_FLAG_SKIP_CLOSEST_HIT_SHADER +syn keyword shaderslangStateGroupVal RAY_FLAG_CULL_BACK_FACING_TRIANGLES RAY_FLAG_CULL_FRONT_FACING_TRIANGLES RAY_FLAG_CULL_OPAQUE RAY_FLAG_CULL_NON_OPAQUE +syn keyword shaderslangStateGroupVal RAY_FLAG_SKIP_TRIANGLES RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES + +" HitKind enum +syn keyword shaderslangStateGroupVal HIT_KIND_TRIANGLE_FRONT_FACE HIT_KIND_TRIANGLE_BACK_FACE + +" RayQuery enums +syn keyword shaderslangStateGroupVal COMMITTED_NOTHING COMMITTED_TRIANGLE_HIT COMMITTED_PROCEDURAL_PRIMITIVE_HIT +syn keyword shaderslangStateGroupVal CANDIDATE_NON_OPAQUE_TRIANGLE CANDIDATE_PROCEDURAL_PRIMITIVE + +" Heap objects +syn keyword shaderslangStateGroupVal ResourceDescriptorHeap SamplerDescriptorHeap + +" Work graph constants +syn keyword shaderslangStateGroupVal UAV_MEMORY GROUP_SHARED_MEMORY NODE_INPUT_MEMORY NODE_OUTPUT_MEMORY ALL_MEMORY GROUP_SYNC GROUP_SCOPE DEVICE_SCOPE + +syn case match " Case sensitive from now on + +" Effect files declarations and functions +" Effect groups, techniques passes +syn keyword shaderslangEffectGroup fxgroup technique11 pass +" Effect functions +syn keyword shaderslangEffectFunc SetBlendState SetDepthStencilState SetRasterizerState SetVertexShader SetHullShader SetDomainShader SetGeometryShader SetPixelShader SetComputeShader CompileShader ConstructGSWithSO SetRenderTargets + +" Default highlighting +hi def link shaderslangProfile shaderslangStatement +hi def link shaderslangStateGroupArg shaderslangStatement +hi def link shaderslangStateGroupVal Number +hi def link shaderslangStatement Statement +hi def link shaderslangType Type +hi def link shaderslangTypeDeprec WarningMsg +hi def link shaderslangStorageClass StorageClass +hi def link shaderslangSemantic PreProc +hi def link shaderslangFunc shaderslangStatement +hi def link shaderslangLayoutQual shaderslangFunc +hi def link shaderslangAnnotation PreProc +hi def link shaderslangStructure Structure +hi def link shaderslangSwizzle SpecialChar +hi def link shaderslangAttribute Statement + +hi def link shaderslangEffectGroup Type +hi def link shaderslangEffectFunc Statement + +let b:current_syntax = "shaderslang" diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 685f700130..6c8ab3a270 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -691,6 +691,7 @@ func s:GetFilenameChecks() abort \ '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', 'user-dirs.defaults', 'user-dirs.dirs', \ 'makepkg.conf', '.makepkg.conf', 'file.mdd', '.env', '.envrc', 'devscripts.conf', '.devscripts', 'file.lo', \ 'file.la', 'file.lai'], + \ 'shaderslang': ['file.slang'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'sil': ['file.sil'], \ 'simula': ['file.sim'], -- cgit From 5b9518b43663f9e77e5f041006df921350bf5061 Mon Sep 17 00:00:00 2001 From: notomo <18519692+notomo@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:37:29 +0900 Subject: fix(api): nvim_set_decoration_provider callback return type #31912 Problem: incorrect return type doc causes luals `Annotations specify that at most 0 return value(s) are required, found 1 returned here instead.` diagnosis Solution: correct return type doc --- runtime/lua/vim/_meta/api_keysets.lua | 6 +++--- src/nvim/api/keysets_defs.h | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index e11dddb2d3..c08ab0663b 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -227,10 +227,10 @@ error('Cannot require a meta file') --- @field do_source? boolean --- @class vim.api.keyset.set_decoration_provider ---- @field on_start? fun(_: "start", tick: integer) +--- @field on_start? fun(_: "start", tick: integer): boolean? --- @field on_buf? fun(_: "buf", bufnr: integer, tick: integer) ---- @field on_win? fun(_: "win", winid: integer, bufnr: integer, toprow: integer, botrow: integer) ---- @field on_line? fun(_: "line", winid: integer, bufnr: integer, row: integer) +--- @field on_win? fun(_: "win", winid: integer, bufnr: integer, toprow: integer, botrow: integer): boolean? +--- @field on_line? fun(_: "line", winid: integer, bufnr: integer, row: integer): boolean? --- @field on_end? fun(_: "end", tick: integer) --- @field _on_hl_def? fun(_: "hl_def") --- @field _on_spell_nav? fun(_: "spell_nav") diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 48f5f7246c..40e89d230d 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -13,10 +13,11 @@ typedef struct { typedef struct { OptionalKeys is_set__set_decoration_provider_; - LuaRefOf(("start" _, Integer tick)) on_start; + LuaRefOf(("start" _, Integer tick), *Boolean) on_start; LuaRefOf(("buf" _, Integer bufnr, Integer tick)) on_buf; - LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow)) on_win; - LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row)) on_line; + LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow), + *Boolean) on_win; + LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row), *Boolean) on_line; LuaRefOf(("end" _, Integer tick)) on_end; LuaRefOf(("hl_def" _)) _on_hl_def; LuaRefOf(("spell_nav" _)) _on_spell_nav; -- cgit From 17b46d01e29443452ae8b607017f8f5c585d3f0a Mon Sep 17 00:00:00 2001 From: Guilherme Soares <48023091+guilhas07@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:06:09 +0000 Subject: test(treesitter): inspect_tree #31182 To prevent #30986 and #31198 regression update inspect_tree tests --- test/functional/treesitter/inspect_tree_spec.lua | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua index 1f7d15cc96..47f3421cfe 100644 --- a/test/functional/treesitter/inspect_tree_spec.lua +++ b/test/functional/treesitter/inspect_tree_spec.lua @@ -120,14 +120,17 @@ describe('vim.treesitter.inspect_tree', function() end) it('updates source and tree buffer windows and closes them correctly', function() + local name = t.tmpname() + n.command('edit ' .. name) insert([[ print() ]]) + n.command('set filetype=lua | write') -- setup two windows for the source buffer exec_lua(function() _G.source_win = vim.api.nvim_get_current_win() - vim.api.nvim_open_win(0, false, { + _G.source_win2 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) @@ -135,40 +138,44 @@ describe('vim.treesitter.inspect_tree', function() -- setup three windows for the tree buffer exec_lua(function() - vim.treesitter.start(0, 'lua') vim.treesitter.inspect_tree() _G.tree_win = vim.api.nvim_get_current_win() - _G.tree_win_copy_1 = vim.api.nvim_open_win(0, false, { + _G.tree_win2 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) - _G.tree_win_copy_2 = vim.api.nvim_open_win(0, false, { + _G.tree_win3 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) end) - -- close original source window - exec_lua('vim.api.nvim_win_close(source_win, false)') + -- close original source window without closing tree views + exec_lua('vim.api.nvim_set_current_win(source_win)') + feed(':quit') + eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win)')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win2)')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win3)')) -- navigates correctly to the remaining source buffer window + exec_lua('vim.api.nvim_set_current_win(tree_win)') feed('') eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2')) -- close original tree window exec_lua(function() - vim.api.nvim_set_current_win(_G.tree_win_copy_1) + vim.api.nvim_set_current_win(_G.tree_win2) vim.api.nvim_win_close(_G.tree_win, false) end) -- navigates correctly to the remaining source buffer window feed('') eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2')) -- close source buffer window and all remaining tree windows - t.pcall_err(exec_lua, 'vim.api.nvim_win_close(0, false)') - - eq(false, exec_lua('return vim.api.nvim_win_is_valid(tree_win_copy_1)')) - eq(false, exec_lua('return vim.api.nvim_win_is_valid(tree_win_copy_2)')) + n.expect_exit(n.command, 'quit') end) end) -- cgit From 0d469b697ed48a1cb1b624d65b6cd22a47195707 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 08:40:50 +0800 Subject: vim-patch:1718e7d: runtime(vim): Update base-syntax, improve ex-bang matching (#31922) Always match ex-bang explicitly rather than incidentally as the ! operator. fixes: vim/vim#16221 closes: vim/vim#16410 https://github.com/vim/vim/commit/1718e7d07e391571ac81c507a746b3bc7a7e2024 Co-authored-by: Doug Kearns --- runtime/syntax/vim.vim | 17 ++++++++++------- src/nvim/generators/gen_vimvim.lua | 8 ++++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 5bea65436d..12798201e2 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -198,7 +198,7 @@ syn case match syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimCall,vimCatch,vimConst,vimDef,vimDefFold,vimDelcommand,@vimEcho,vimEnddef,vimEndfunction,vimExecute,vimIsCommand,vimExtCmd,vimFor,vimFunction,vimFuncFold,vimGlobal,vimHighlight,vimLet,vimLoadkeymap,vimMap,vimMark,vimMatch,vimNotFunc,vimNormal,vimSet,vimSleep,vimSyntax,vimThrow,vimUnlet,vimUnmap,vimUserCmd,vimMenu,vimMenutranslate,@vim9CmdList syn cluster vim9CmdList contains=vim9Abstract,vim9Class,vim9Const,vim9Enum,vim9Export,vim9Final,vim9For,vim9Interface,vim9Type,vim9Var syn match vimCmdSep "[:|]\+" skipwhite nextgroup=@vimCmdList,vimSubst1 -syn match vimIsCommand "\<\%(\h\w*\|[23]mat\%[ch]\)\>" contains=vimCommand +syn match vimIsCommand "\<\%(\h\w*\|[23]mat\%[ch]\)\>" nextgroup=vimBang contains=vimCommand syn match vimBang contained "!" syn match vimVar contained "\<\h[a-zA-Z0-9#_]*\>" syn match vimVar "\<[bwglstav]:\h[a-zA-Z0-9#_]*\>" @@ -207,7 +207,6 @@ syn match vimVar "\s\zs&t_\S[a-zA-Z0-9]\>" syn match vimVar "\s\zs&t_k;" syn match vimFBVar contained "\<[bwglstav]:\h[a-zA-Z0-9#_]*\>" syn keyword vimCommand contained in -syn match vimBang contained "!" syn cluster vimExprList contains=vimEnvvar,vimFunc,vimNumber,vimOper,vimOperParen,vimLetRegister,vimString,vimVar,@vim9ExprList syn cluster vim9ExprList contains=vim9Boolean,vim9Null @@ -275,7 +274,8 @@ syn keyword vimAugroupKey contained aug[roup] skipwhite nextgroup=vimAugroupBan " Operators: {{{2 " ========= syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimRegister,@vimContinue,vim9Comment,vimVar,vimBoolean,vimNull -syn match vimOper "||\|&&\|[-+*/%.!]" skipwhite nextgroup=vimString,vimSpecFile +syn match vimOper "\a\@=\|<=\|=\~\|!\~\|>\|<\|=\|!\~#\)[?#]\{0,2}" skipwhite nextgroup=vimString,vimSpecFile syn match vimOper "\(\" skipwhite nextgroup=vimString,vimSpecFile syn region vimOperParen matchgroup=vimParenSep start="(" end=")" contains=@vimOperGroup @@ -553,8 +553,8 @@ syn region vimPatSepZone oneline contained matchgroup=vimPatSepZ start="\\%\ syn region vimPatRegion contained transparent matchgroup=vimPatSepR start="\\[z%]\=(" end="\\)" contains=@vimSubstList oneline syn match vimNotPatSep contained "\\\\" syn cluster vimStringGroup contains=vimEscape,vimEscapeBrace,vimPatSep,vimNotPatSep,vimPatSepErr,vimPatSepZone,@Spell -syn region vimString oneline keepend start=+[^a-zA-Z>!\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ contains=@vimStringGroup extend -syn region vimString oneline keepend start=+[^a-zA-Z>!\\@]'+lc=1 end=+'+ extend +syn region vimString oneline keepend start=+[^a-zA-Z>\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ contains=@vimStringGroup extend +syn region vimString oneline keepend start=+[^a-zA-Z>\\@]'+lc=1 end=+'+ extend "syn region vimString oneline start="\s/\s*\A"lc=1 skip="\\\\\|\\+" end="/" contains=@vimStringGroup " see tst45.vim syn match vimString contained +"[^"]*\\$+ skipnl nextgroup=vimStringCont syn match vimStringCont contained +\(\\\\\|.\)\{-}[^\\]"+ @@ -678,10 +678,12 @@ syn keyword vimAbb abc[lear] cabc[lear] iabc[lear] skipwhite nextgroup=vimMapMod " Autocmd: {{{2 " ======= -syn match vimAutoEventList contained "\(!\s\+\)\=\(\a\+,\)*\a\+" contains=vimAutoEvent,nvimAutoEvent nextgroup=vimAutoCmdSpace +syn match vimAutoCmdBang contained "\a\@1<=!" skipwhite nextgroup=vimAutoEventList +syn match vimAutoEventList contained "\%(\a\+,\)*\a\+" contains=vimAutoEvent,nvimAutoEvent nextgroup=vimAutoCmdSpace syn match vimAutoCmdSpace contained "\s\+" nextgroup=vimAutoCmdSfxList syn match vimAutoCmdSfxList contained "\S*" skipwhite nextgroup=vimAutoCmdMod,vimAutoCmdBlock -syn keyword vimAutoCmd au[tocmd] do[autocmd] doautoa[ll] skipwhite nextgroup=vimAutoEventList +syn keyword vimAutoCmd au[tocmd] skipwhite nextgroup=vimAutoCmdBang,vimAutoEventList +syn keyword vimAutoCmd do[autocmd] doautoa[ll] skipwhite nextgroup=vimAutoEventList syn match vimAutoCmdMod "\(++\)\=\(once\|nested\)" skipwhite nextgroup=vimAutoCmdBlock syn region vimAutoCmdBlock contained matchgroup=vimSep start="{" end="}" contains=@vimDefBodyList @@ -1275,6 +1277,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimAugroupError vimError hi def link vimAugroupKey vimCommand hi def link vimAutoCmd vimCommand + hi def link vimAutoCmdBang vimBang hi def link vimAutoEvent Type hi def link vimAutoCmdMod Special hi def link vimBang vimOper diff --git a/src/nvim/generators/gen_vimvim.lua b/src/nvim/generators/gen_vimvim.lua index 0675f04b73..d8053822bf 100644 --- a/src/nvim/generators/gen_vimvim.lua +++ b/src/nvim/generators/gen_vimvim.lua @@ -52,11 +52,13 @@ local function is_special_cased_cmd(cmd) end local vimcmd_start = 'syn keyword vimCommand contained ' +local vimcmd_end = ' nextgroup=vimBang' w(vimcmd_start) + local prev_cmd = nil for _, cmd_desc in ipairs(ex_cmds.cmds) do if lld.line_length > 850 then - w('\n' .. vimcmd_start) + w(vimcmd_end .. '\n' .. vimcmd_start) end local cmd = cmd_desc.command if cmd:match('%w') and cmd ~= 'z' and not is_special_cased_cmd(cmd) then @@ -79,9 +81,11 @@ for _, cmd_desc in ipairs(ex_cmds.cmds) do prev_cmd = cmd end +w(vimcmd_end .. '\n') + local vimopt_start = 'syn keyword vimOption contained ' local vimopt_end = ' skipwhite nextgroup=vimSetEqual,vimSetMod' -w('\n\n' .. vimopt_start) +w('\n' .. vimopt_start) for _, opt_desc in ipairs(options.options) do if not opt_desc.immutable then -- cgit From 822313e42b5b8d51ea0b3f1f97c47026f2c7e2e2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 08:59:59 +0800 Subject: vim-patch:partial:9.1.0882: too many strlen() calls in insexpand.c (#31490) Problem: too many strlen() calls in insexpand.c Solution: Refactor insexpand.c and reduce number of calls to STRLEN(), fix a warning get_next_filename_completion(), add new function ins_compl_leader_len() (John Marriott) closes: vim/vim#16095 https://github.com/vim/vim/commit/5e6ea92b2c58cbfc642d7e35bd717f99aa2e1e53 Co-authored-by: John Marriott --- src/nvim/insexpand.c | 323 +++++++++++++++++++++++++++------------------------ 1 file changed, 168 insertions(+), 155 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 053d97fd64..98487be523 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -10,6 +10,7 @@ #include #include "klib/kvec.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" @@ -156,7 +157,7 @@ struct compl_S { compl_T *cp_next; compl_T *cp_prev; compl_T *cp_match_next; ///< matched next compl_T - char *cp_str; ///< matched text + String cp_str; ///< matched text char *(cp_text[CPT_COUNT]); ///< text for the menu typval_T cp_user_data; char *cp_fname; ///< file containing the match, allocated when @@ -220,7 +221,7 @@ static bool compl_enter_selects = false; /// When "compl_leader" is not NULL only matches that start with this string /// are used. -static char *compl_leader = NULL; +static String compl_leader = STRING_INIT; static bool compl_get_longest = false; ///< put longest common string in compl_leader @@ -245,8 +246,7 @@ static bool compl_started = false; static int ctrl_x_mode = CTRL_X_NORMAL; static int compl_matches = 0; ///< number of completion matches -static char *compl_pattern = NULL; -static size_t compl_patternlen = 0; +static String compl_pattern = STRING_INIT; static Direction compl_direction = FORWARD; static Direction compl_shows_dir = FORWARD; static int compl_pending = 0; ///< > 1 for postponed CTRL-N @@ -257,8 +257,8 @@ static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed static colnr_T compl_ins_end_col = 0; -static char *compl_orig_text = NULL; ///< text as it was before - ///< completion started +static String compl_orig_text = STRING_INIT; ///< text as it was before + ///< completion started /// Undo information to restore extmarks for original text. static extmark_undo_vec_t compl_orig_extmarks; static int compl_cont_mode = 0; @@ -639,7 +639,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp // Rule 1: Were any chars converted to lower? { - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (mb_islower(c)) { @@ -659,7 +659,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp // upper case. if (!has_lower) { bool was_letter = false; - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { @@ -675,7 +675,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp // Copy the original case of the part we typed. { - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (mb_islower(c)) { @@ -705,7 +705,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp ga_grow(&gap, IOSIZE); *p = NUL; STRCPY(gap.ga_data, IObuff); - gap.ga_len = (int)strlen(IObuff); + gap.ga_len = (int)(p - IObuff); } else { p += utf_char2bytes(wca[i++], p); } @@ -752,7 +752,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir // Find actual length of original text. { - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); @@ -841,8 +841,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons match = compl_first_match; do { if (!match_at_original_text(match) - && strncmp(match->cp_str, str, (size_t)len) == 0 - && ((int)strlen(match->cp_str) <= len || match->cp_str[len] == NUL)) { + && strncmp(match->cp_str.data, str, (size_t)len) == 0 + && ((int)match->cp_str.size <= len || match->cp_str.data[len] == NUL)) { if (cptext_allocated) { free_cptext(cptext); } @@ -862,7 +862,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons if (flags & CP_ORIGINAL_TEXT) { match->cp_number = 0; } - match->cp_str = xstrnsave(str, (size_t)len); + match->cp_str = cbuf_to_string(str, (size_t)len); // match-fname is: // - compl_curr_match->cp_fname if it is a string equal to fname. @@ -944,9 +944,9 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len) return true; } if (match->cp_flags & CP_ICASE) { - return STRNICMP(match->cp_str, str, len) == 0; + return STRNICMP(match->cp_str.data, str, len) == 0; } - return strncmp(match->cp_str, str, len) == 0; + return strncmp(match->cp_str.data, str, len) == 0; } /// when len is -1 mean use whole length of p otherwise part of p @@ -976,13 +976,13 @@ int ins_compl_col_range_attr(int col) /// Reduce the longest common string for match "match". static void ins_compl_longest_match(compl_T *match) { - if (compl_leader == NULL) { + if (compl_leader.data == NULL) { // First match, use it as a whole. - compl_leader = xstrdup(match->cp_str); + compl_leader = copy_string(match->cp_str, NULL); bool had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(false); - ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it @@ -996,8 +996,8 @@ static void ins_compl_longest_match(compl_T *match) } // Reduce the text if this match differs from compl_leader. - char *p = compl_leader; - char *s = match->cp_str; + char *p = compl_leader.data; + char *s = match->cp_str.data; while (*p != NUL) { int c1 = utf_ptr2char(p); int c2 = utf_ptr2char(s); @@ -1014,9 +1014,11 @@ static void ins_compl_longest_match(compl_T *match) if (*p != NUL) { // Leader was shortened, need to change the inserted text. *p = NUL; + compl_leader.size = (size_t)(p - compl_leader.data); + bool had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(false); - ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it @@ -1078,8 +1080,8 @@ bool ins_compl_has_shown_match(void) /// Return whether the shown match is long enough. bool ins_compl_long_shown_match(void) { - return compl_shown_match != NULL && compl_shown_match->cp_str != NULL - && (colnr_T)strlen(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col; + return compl_shown_match != NULL && compl_shown_match->cp_str.data != NULL + && (colnr_T)compl_shown_match->cp_str.size > curwin->w_cursor.col - compl_col; } /// Get the local or global value of 'completeopt' flags. @@ -1134,7 +1136,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc_lock(VAR_FIXED); - tv_dict_add_str(dict, S_LEN("word"), match->cp_str); + tv_dict_add_str(dict, S_LEN("word"), match->cp_str.data); tv_dict_add_str(dict, S_LEN("abbr"), match->cp_text[CPT_ABBR]); tv_dict_add_str(dict, S_LEN("menu"), match->cp_text[CPT_MENU]); tv_dict_add_str(dict, S_LEN("kind"), match->cp_text[CPT_KIND]); @@ -1203,7 +1205,6 @@ static int ins_compl_build_pum(void) XFREE_CLEAR(compl_leader); } - const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0; int max_fuzzy_score = 0; unsigned cur_cot_flags = get_cot_flags(); bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; @@ -1214,7 +1215,7 @@ static int ins_compl_build_pum(void) // match after it, don't highlight anything. bool shown_match_ok = match_at_original_text(compl_shown_match); - if (strequal(compl_leader, compl_orig_text) && !shown_match_ok) { + if (strequal(compl_leader.data, compl_orig_text.data) && !shown_match_ok) { compl_shown_match = compl_no_select ? compl_first_match : compl_first_match->cp_next; } @@ -1227,13 +1228,13 @@ static int ins_compl_build_pum(void) comp->cp_in_match_array = false; // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, // set the cp_score for later comparisons. - if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) { - comp->cp_score = fuzzy_match_str(comp->cp_str, compl_leader); + if (compl_fuzzy_match && compl_leader.data != NULL && compl_leader.size > 0) { + comp->cp_score = fuzzy_match_str(comp->cp_str.data, compl_leader.data); } if (!match_at_original_text(comp) - && (compl_leader == NULL - || ins_compl_equal(comp, compl_leader, (size_t)lead_len) + && (compl_leader.data == NULL + || ins_compl_equal(comp, compl_leader.data, compl_leader.size) || (compl_fuzzy_match && comp->cp_score > 0))) { compl_match_arraysize++; comp->cp_in_match_array = true; @@ -1305,7 +1306,7 @@ static int ins_compl_build_pum(void) comp = match_head; while (comp != NULL) { compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR] != NULL - ? comp->cp_text[CPT_ABBR] : comp->cp_str; + ? comp->cp_text[CPT_ABBR] : comp->cp_str.data; compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND]; compl_match_array[i].pum_info = comp->cp_text[CPT_INFO]; compl_match_array[i].pum_score = comp->cp_score; @@ -1318,7 +1319,7 @@ static int ins_compl_build_pum(void) comp = match_next; } - if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) { + if (compl_fuzzy_match && compl_leader.data != NULL && compl_leader.size > 0) { for (i = 0; i < compl_match_arraysize; i++) { compl_match_array[i].pum_idx = i; } @@ -1356,7 +1357,7 @@ void ins_compl_show_pum(void) } else { // popup menu already exists, only need to find the current item. for (int i = 0; i < compl_match_arraysize; i++) { - if (compl_match_array[i].pum_text == compl_shown_match->cp_str + if (compl_match_array[i].pum_text == compl_shown_match->cp_str.data || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; @@ -1425,7 +1426,13 @@ bool compl_match_curr_select(int selected) /// Get current completion leader char *ins_compl_leader(void) { - return compl_leader != NULL ? compl_leader : compl_orig_text; + return compl_leader.data != NULL ? compl_leader.data : compl_orig_text.data; +} + +/// Get current completion leader length +size_t ins_compl_leader_len(void) +{ + return compl_leader.data != NULL ? compl_leader.size : compl_orig_text.size; } /// Add any identifiers that match the given pattern "pat" in the list of @@ -1671,9 +1678,8 @@ static char *find_line_end(char *ptr) /// Free the list of completions static void ins_compl_free(void) { - XFREE_CLEAR(compl_pattern); - compl_patternlen = 0; - XFREE_CLEAR(compl_leader); + API_CLEAR_STRING(compl_pattern); + API_CLEAR_STRING(compl_leader); if (compl_first_match == NULL) { return; @@ -1686,7 +1692,7 @@ static void ins_compl_free(void) do { compl_T *match = compl_curr_match; compl_curr_match = compl_curr_match->cp_next; - xfree(match->cp_str); + API_CLEAR_STRING(match->cp_str); // several entries may use the same fname, free it just once. if (match->cp_flags & CP_FREE_FNAME) { xfree(match->cp_fname); @@ -1707,12 +1713,11 @@ void ins_compl_clear(void) compl_started = false; compl_matches = 0; compl_ins_end_col = 0; - XFREE_CLEAR(compl_pattern); - compl_patternlen = 0; - XFREE_CLEAR(compl_leader); + API_CLEAR_STRING(compl_pattern); + API_CLEAR_STRING(compl_leader); edit_submode_extra = NULL; kv_destroy(compl_orig_extmarks); - XFREE_CLEAR(compl_orig_text); + API_CLEAR_STRING(compl_orig_text); compl_enter_selects = false; // clear v:completed_item set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); @@ -1802,8 +1807,9 @@ int ins_compl_bs(void) // TODO(bfredl): get rid of random update_screen() calls deep inside completion logic line = get_cursor_line_ptr(); - xfree(compl_leader); - compl_leader = xstrnsave(line + compl_col, (size_t)(p_off - (ptrdiff_t)compl_col)); + API_CLEAR_STRING(compl_leader); + compl_leader = cbuf_to_string(line + compl_col, + (size_t)(p_off - (ptrdiff_t)compl_col)); ins_compl_new_leader(); if (compl_shown_match != NULL) { @@ -1837,11 +1843,11 @@ static void ins_compl_new_leader(void) { ins_compl_del_pum(); ins_compl_delete(true); - ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); compl_used_match = false; if (compl_started) { - ins_compl_set_original_text(compl_leader); + ins_compl_set_original_text(compl_leader.data, compl_leader.size); } else { spell_bad_len = 0; // need to redetect bad word // Matches were cleared, need to search for them now. @@ -1901,9 +1907,9 @@ void ins_compl_addleader(int c) ins_compl_restart(); } - xfree(compl_leader); - compl_leader = xstrnsave(get_cursor_line_ptr() + compl_col, - (size_t)(curwin->w_cursor.col - compl_col)); + API_CLEAR_STRING(compl_leader); + compl_leader = cbuf_to_string(get_cursor_line_ptr() + compl_col, + (size_t)(curwin->w_cursor.col - compl_col)); ins_compl_new_leader(); } @@ -1923,19 +1929,19 @@ static void ins_compl_restart(void) } /// Set the first match, the original text. -static void ins_compl_set_original_text(char *str) +static void ins_compl_set_original_text(char *str, size_t len) FUNC_ATTR_NONNULL_ALL { // Replace the original text entry. // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly // be at the last item for backward completion if (match_at_original_text(compl_first_match)) { // safety check - xfree(compl_first_match->cp_str); - compl_first_match->cp_str = xstrdup(str); + API_CLEAR_STRING(compl_first_match->cp_str); + compl_first_match->cp_str = cbuf_to_string(str, len); } else if (compl_first_match->cp_prev != NULL && match_at_original_text(compl_first_match->cp_prev)) { - xfree(compl_first_match->cp_prev->cp_str); - compl_first_match->cp_prev->cp_str = xstrdup(str); + API_CLEAR_STRING(compl_first_match->cp_prev->cp_str); + compl_first_match->cp_prev->cp_str = cbuf_to_string(str, len); } } @@ -1945,8 +1951,8 @@ void ins_compl_addfrommatch(void) { int len = (int)curwin->w_cursor.col - (int)compl_col; assert(compl_shown_match != NULL); - char *p = compl_shown_match->cp_str; - if ((int)strlen(p) <= len) { // the match is too short + char *p = compl_shown_match->cp_str.data; + if ((int)compl_shown_match->cp_str.size <= len) { // the match is too short // When still at the original match use the first entry that matches // the leader. if (!match_at_original_text(compl_shown_match)) { @@ -1954,15 +1960,17 @@ void ins_compl_addfrommatch(void) } p = NULL; + size_t plen = 0; for (compl_T *cp = compl_shown_match->cp_next; cp != NULL && !is_first_match(cp); cp = cp->cp_next) { - if (compl_leader == NULL - || ins_compl_equal(cp, compl_leader, strlen(compl_leader))) { - p = cp->cp_str; + if (compl_leader.data == NULL + || ins_compl_equal(cp, compl_leader.data, compl_leader.size)) { + p = cp->cp_str.data; + plen = cp->cp_str.size; break; } } - if (p == NULL || (int)strlen(p) <= len) { + if (p == NULL || (int)plen <= len) { return; } } @@ -2102,7 +2110,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // Get here when we have finished typing a sequence of ^N and // ^P or other completion characters in CTRL-X mode. Free up // memory that was used, and make sure we can redo the insert. - if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { + if (compl_curr_match != NULL || compl_leader.data != NULL || c == Ctrl_E) { // If any of the original typed text has been changed, eg when // ignorecase is set, we must add back-spaces to the redo // buffer. We add as few as necessary to delete just the part @@ -2111,7 +2119,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // CTRL-E then don't use the current match. char *ptr; if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { - ptr = compl_curr_match->cp_str; + ptr = compl_curr_match->cp_str.data; } else { ptr = NULL; } @@ -2154,7 +2162,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) if ((c == Ctrl_Y || (compl_enter_selects && (c == CAR || c == K_KENTER || c == NL))) && pum_visible()) { - word = xstrdup(compl_shown_match->cp_str); + word = xstrdup(compl_shown_match->cp_str.data); retval = true; // May need to remove ComplMatchIns highlight. redrawWinline(curwin, curwin->w_cursor.lnum); @@ -2165,16 +2173,18 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) if (c == Ctrl_E) { ins_compl_delete(false); char *p = NULL; - if (compl_leader != NULL) { - p = compl_leader; + size_t plen = 0; + if (compl_leader.data != NULL) { + p = compl_leader.data; + plen = compl_leader.size; } else if (compl_first_match != NULL) { - p = compl_orig_text; + p = compl_orig_text.data; + plen = compl_orig_text.size; } if (p != NULL) { const int compl_len = get_compl_len(); - const int len = (int)strlen(p); - if (len > compl_len) { - ins_compl_insert_bytes(p + compl_len, len - compl_len); + if ((int)plen > compl_len) { + ins_compl_insert_bytes(p + compl_len, (int)plen - compl_len); } } restore_orig_extmarks(); @@ -2324,18 +2334,18 @@ bool ins_compl_prep(int c) /// "ptr" is the known leader text or NUL. static void ins_compl_fixRedoBufForLeader(char *ptr_arg) { - int len; + int len = 0; char *ptr = ptr_arg; if (ptr == NULL) { - if (compl_leader != NULL) { - ptr = compl_leader; + if (compl_leader.data != NULL) { + ptr = compl_leader.data; } else { return; // nothing to do } } - if (compl_orig_text != NULL) { - char *p = compl_orig_text; + if (compl_orig_text.data != NULL) { + char *p = compl_orig_text.data; for (len = 0; p[len] != NUL && p[len] == ptr[len]; len++) {} if (len > 0) { len -= utf_head_off(p, p + len); @@ -2343,8 +2353,6 @@ static void ins_compl_fixRedoBufForLeader(char *ptr_arg) for (p += len; *p != NUL; MB_PTR_ADV(p)) { AppendCharToRedobuff(K_BS); } - } else { - len = 0; } AppendToRedobuffLit(ptr + len, -1); } @@ -2730,12 +2738,14 @@ static void set_completion(colnr_T startcol, list_T *list) compl_col = startcol; compl_length = curwin->w_cursor.col - startcol; // compl_pattern doesn't need to be set - compl_orig_text = xstrnsave(get_cursor_line_ptr() + compl_col, (size_t)compl_length); + compl_orig_text = cbuf_to_string(get_cursor_line_ptr() + compl_col, + (size_t)compl_length); save_orig_extmarks(); if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, + if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, + NULL, NULL, false, NULL, 0, flags | CP_FAST, false, NULL) != OK) { return; } @@ -2940,7 +2950,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) if (has_items || (has_matches && match->cp_in_match_array)) { dict_T *di = tv_dict_alloc(); tv_list_append_dict(li, di); - tv_dict_add_str(di, S_LEN("word"), match->cp_str); + tv_dict_add_str(di, S_LEN("word"), match->cp_str.data); tv_dict_add_str(di, S_LEN("abbr"), match->cp_text[CPT_ABBR]); tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]); tv_dict_add_str(di, S_LEN("kind"), match->cp_text[CPT_KIND]); @@ -3137,8 +3147,8 @@ done: /// included files. static void get_next_include_file_completion(int compl_type) { - find_pattern_in_path(compl_pattern, compl_direction, - compl_patternlen, false, false, + find_pattern_in_path(compl_pattern.data, compl_direction, + compl_pattern.size, false, false, ((compl_type == CTRL_X_PATH_DEFINES && !(compl_cont_status & CONT_SOL)) ? FIND_DEFINE : FIND_ANY), @@ -3150,14 +3160,14 @@ static void get_next_include_file_completion(int compl_type) static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f) { if (thesaurus_func_complete(compl_type)) { - expand_by_function(compl_type, compl_pattern); + expand_by_function(compl_type, compl_pattern.data); } else { ins_compl_dictionaries(dict != NULL ? dict : (compl_type == CTRL_X_THESAURUS ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) : (*curbuf->b_p_dict == NUL ? p_dict : curbuf->b_p_dict)), - compl_pattern, + compl_pattern.data, dict != NULL ? dict_f : 0, compl_type == CTRL_X_THESAURUS); } @@ -3168,14 +3178,14 @@ static void get_next_tag_completion(void) { // set p_ic according to p_ic, p_scs and pat for find_tags(). const int save_p_ic = p_ic; - p_ic = ignorecase(compl_pattern); + p_ic = ignorecase(compl_pattern.data); // Find up to TAG_MANY matches. Avoids that an enormous number // of matches is found when compl_pattern is empty g_tag_at_cursor = true; char **matches; int num_matches; - if (find_tags(compl_pattern, &num_matches, &matches, + if (find_tags(compl_pattern.data, &num_matches, &matches, TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { @@ -3190,13 +3200,13 @@ static void get_next_filename_completion(void) { char **matches; int num_matches; - if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + if (expand_wildcards(1, &compl_pattern.data, &num_matches, &matches, EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) != OK) { return; } // May change home directory back to "~". - tilde_replace(compl_pattern, num_matches, matches); + tilde_replace(compl_pattern.data, num_matches, matches); #ifdef BACKSLASH_IN_FILENAME if (curbuf->b_p_csl[0] != NUL) { for (int i = 0; i < num_matches; i++) { @@ -3220,8 +3230,8 @@ static void get_next_cmdline_completion(void) { char **matches; int num_matches; - if (expand_cmdline(&compl_xp, compl_pattern, - (int)compl_patternlen, &num_matches, &matches) == EXPAND_OK) { + if (expand_cmdline(&compl_xp, compl_pattern.data, + (int)compl_pattern.size, &num_matches, &matches) == EXPAND_OK) { ins_compl_add_matches(num_matches, matches, false); } } @@ -3230,7 +3240,7 @@ static void get_next_cmdline_completion(void) static void get_next_spell_completion(linenr_T lnum) { char **matches; - int num_matches = expand_spelling(lnum, compl_pattern, &matches); + int num_matches = expand_spelling(lnum, compl_pattern.data, &matches); if (num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } else { @@ -3249,22 +3259,24 @@ static char *ins_compl_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_po { *match_len = 0; char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum) + cur_match_pos->col; - int len; + int len = ml_get_buf_len(ins_buf, cur_match_pos->lnum) - cur_match_pos->col; if (ctrl_x_mode_line_or_eval()) { if (compl_status_adding()) { if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) { return NULL; } ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1); + len = ml_get_buf_len(ins_buf, cur_match_pos->lnum + 1); if (!p_paste) { - ptr = skipwhite(ptr); + char *tmp_ptr = ptr; + ptr = skipwhite(tmp_ptr); + len -= (int)(ptr - tmp_ptr); } } - len = (int)strlen(ptr); } else { char *tmp_ptr = ptr; - if (compl_status_adding() && compl_length <= (int)strlen(tmp_ptr)) { + if (compl_status_adding() && compl_length <= len) { tmp_ptr += compl_length; // Skip if already inside a word. if (vim_iswordp(tmp_ptr)) { @@ -3360,10 +3372,11 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ // has added a word that was at the beginning of the line. if (ctrl_x_mode_line_or_eval() || (compl_cont_status & CONT_SOL)) { found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, - compl_direction, compl_pattern); + compl_direction, compl_pattern.data); } else { found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, - NULL, compl_direction, compl_pattern, compl_patternlen, + NULL, compl_direction, compl_pattern.data, + compl_pattern.size, 1, SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); } msg_silent--; @@ -3460,7 +3473,7 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_ case CTRL_X_FUNCTION: case CTRL_X_OMNI: - expand_by_function(type, compl_pattern); + expand_by_function(type, compl_pattern.data); break; case CTRL_X_SPELL: @@ -3491,7 +3504,7 @@ static void get_next_bufname_token(void) FOR_ALL_BUFFERS(b) { if (b->b_p_bl && b->b_sfname != NULL) { char *tail = path_tail(b->b_sfname); - if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) { + if (strncmp(tail, compl_orig_text.data, compl_orig_text.size) == 0) { ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0, p_ic ? CP_ICASE : 0, false, NULL); } @@ -3559,7 +3572,7 @@ static int ins_compl_get_exp(pos_T *ini) // If complete() was called then compl_pattern has been reset. // The following won't work then, bail out. - if (compl_pattern == NULL) { + if (compl_pattern.data == NULL) { break; } @@ -3627,7 +3640,7 @@ static int ins_compl_get_exp(pos_T *ini) static void ins_compl_update_shown_match(void) { while (!ins_compl_equal(compl_shown_match, - compl_leader, strlen(compl_leader)) + compl_leader.data, compl_leader.size) && compl_shown_match->cp_next != NULL && !is_first_match(compl_shown_match->cp_next)) { compl_shown_match = compl_shown_match->cp_next; @@ -3636,10 +3649,10 @@ static void ins_compl_update_shown_match(void) // If we didn't find it searching forward, and compl_shows_dir is // backward, find the last match. if (compl_shows_dir_backward() - && !ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) + && !ins_compl_equal(compl_shown_match, compl_leader.data, compl_leader.size) && (compl_shown_match->cp_next == NULL || is_first_match(compl_shown_match->cp_next))) { - while (!ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) + while (!ins_compl_equal(compl_shown_match, compl_leader.data, compl_leader.size) && compl_shown_match->cp_prev != NULL && !is_first_match(compl_shown_match->cp_prev)) { compl_shown_match = compl_shown_match->cp_prev; @@ -3654,13 +3667,13 @@ void ins_compl_delete(bool new_leader) // allows marks present on the original text to shrink/grow appropriately. int orig_col = 0; if (new_leader) { - char *orig = compl_orig_text; + char *orig = compl_orig_text.data; char *leader = ins_compl_leader(); while (*orig != NUL && utf_ptr2char(orig) == utf_ptr2char(leader)) { leader += utf_ptr2len(leader); orig += utf_ptr2len(orig); } - orig_col = (int)(orig - compl_orig_text); + orig_col = (int)(orig - compl_orig_text.data); } // In insert mode: Delete the typed part. @@ -3688,8 +3701,8 @@ void ins_compl_insert(bool in_compl_func) int compl_len = get_compl_len(); // Make sure we don't go over the end of the string, this can happen with // illegal bytes. - if (compl_len < (int)strlen(compl_shown_match->cp_str)) { - ins_compl_insert_bytes(compl_shown_match->cp_str + compl_len, -1); + if (compl_len < (int)compl_shown_match->cp_str.size) { + ins_compl_insert_bytes(compl_shown_match->cp_str.data + compl_len, -1); } compl_used_match = !match_at_original_text(compl_shown_match); @@ -3759,7 +3772,7 @@ static compl_T *find_comp_when_fuzzy(void) comp = compl_first_match; do { if (comp->cp_score == score - && (str == comp->cp_str || str == comp->cp_text[CPT_ABBR])) { + && (str == comp->cp_str.data || str == comp->cp_text[CPT_ABBR])) { return comp; } comp = comp->cp_next; @@ -3842,9 +3855,9 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a found_end = false; } if (!match_at_original_text(compl_shown_match) - && compl_leader != NULL + && compl_leader.data != NULL && !ins_compl_equal(compl_shown_match, - compl_leader, strlen(compl_leader)) + compl_leader.data, compl_leader.size) && !(compl_fuzzy_match && compl_shown_match->cp_score > 0)) { todo++; } else { @@ -3900,7 +3913,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match return -1; } - if (compl_leader != NULL + if (compl_leader.data != NULL && !match_at_original_text(compl_shown_match) && !compl_fuzzy_match) { // Update "compl_shown_match" to the actually shown match @@ -3938,17 +3951,17 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match // Insert the text of the new completion, or the compl_leader. if (compl_no_insert && !started) { - ins_compl_insert_bytes(compl_orig_text + get_compl_len(), -1); + ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1); compl_used_match = false; restore_orig_extmarks(); } else if (insert_match) { if (!compl_get_longest || compl_used_match) { ins_compl_insert(in_compl_func); } else { - assert(compl_leader != NULL); - ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); + assert(compl_leader.data != NULL); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); } - if (!strcmp(compl_curr_match->cp_str, compl_orig_text)) { + if (strequal(compl_curr_match->cp_str.data, compl_orig_text.data)) { restore_orig_extmarks(); } } else { @@ -4112,8 +4125,7 @@ static bool ins_compl_use_match(int c) /// Get the pattern, column and length for normal completion (CTRL-N CTRL-P /// completion) -/// Sets the global variables: compl_col, compl_length, compl_pattern and -/// compl_patternlen. +/// Sets the global variables: compl_col, compl_length and compl_pattern. /// Uses the global variables: compl_cont_status and ctrl_x_mode static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) { @@ -4124,29 +4136,32 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) compl_length = curs_col - startcol; } if (p_ic) { - compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); + compl_pattern = cstr_as_string(str_foldcase(line + compl_col, + compl_length, NULL, 0)); } else { - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); } } else if (compl_status_adding()) { char *prefix = "\\<"; size_t prefixlen = STRLEN_LITERAL("\\<"); - // we need up to 2 extra chars for the prefix - compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, - compl_length) + prefixlen); if (!vim_iswordp(line + compl_col) || (compl_col > 0 && (vim_iswordp(mb_prevptr(line, line + compl_col))))) { prefix = ""; prefixlen = 0; } - STRCPY(compl_pattern, prefix); - quote_meta(compl_pattern + prefixlen, line + compl_col, compl_length); + + // we need up to 2 extra chars for the prefix + size_t n = quote_meta(NULL, line + compl_col, compl_length) + prefixlen; + compl_pattern.data = xmalloc(n); + STRCPY(compl_pattern.data, prefix); + quote_meta(compl_pattern.data + prefixlen, line + compl_col, compl_length); + compl_pattern.size = n - 1; } else if (--startcol < 0 || !vim_iswordp(mb_prevptr(line, line + startcol + 1))) { // Match any word of at least two chars - compl_pattern = xstrnsave(S_LEN("\\<\\k\\k")); + compl_pattern = cbuf_to_string(S_LEN("\\<\\k\\k")); compl_col += curs_col; compl_length = 0; } else { @@ -4167,19 +4182,20 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) // Only match word with at least two chars -- webb // there's no need to call quote_meta, // xmalloc(7) is enough -- Acevedo - compl_pattern = xmalloc(7); - STRCPY(compl_pattern, "\\<"); - quote_meta(compl_pattern + 2, line + compl_col, 1); - strcat(compl_pattern, "\\k"); + compl_pattern.data = xmalloc(7); + STRCPY(compl_pattern.data, "\\<"); + quote_meta(compl_pattern.data + 2, line + compl_col, 1); + strcat(compl_pattern.data, "\\k"); + compl_pattern.size = strlen(compl_pattern.data); } else { - compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); - STRCPY(compl_pattern, "\\<"); - quote_meta(compl_pattern + 2, line + compl_col, compl_length); + size_t n = quote_meta(NULL, line + compl_col, compl_length) + 2; + compl_pattern.data = xmalloc(n); + STRCPY(compl_pattern.data, "\\<"); + quote_meta(compl_pattern.data + 2, line + compl_col, compl_length); + compl_pattern.size = n - 1; } } - compl_patternlen = strlen(compl_pattern); - return OK; } @@ -4194,13 +4210,12 @@ static int get_wholeline_compl_info(char *line, colnr_T curs_col) compl_length = 0; } if (p_ic) { - compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); + compl_pattern = cstr_as_string(str_foldcase(line + compl_col, + compl_length, NULL, 0)); } else { - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); } - compl_patternlen = strlen(compl_pattern); - return OK; } @@ -4225,8 +4240,8 @@ static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) compl_col += startcol; compl_length = (int)curs_col - startcol; - compl_pattern = addstar(line + compl_col, (size_t)compl_length, EXPAND_FILES); - compl_patternlen = strlen(compl_pattern); + compl_pattern = cstr_as_string(addstar(line + compl_col, + (size_t)compl_length, EXPAND_FILES)); return OK; } @@ -4235,9 +4250,9 @@ static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) /// Sets the global variables: compl_col, compl_length and compl_pattern. static int get_cmdline_compl_info(char *line, colnr_T curs_col) { - compl_pattern = xstrnsave(line, (size_t)curs_col); - compl_patternlen = (size_t)curs_col; - set_cmd_context(&compl_xp, compl_pattern, (int)compl_patternlen, curs_col, false); + compl_pattern = cbuf_to_string(line, (size_t)curs_col); + set_cmd_context(&compl_xp, compl_pattern.data, + (int)compl_pattern.size, curs_col, false); if (compl_xp.xp_context == EXPAND_LUA) { nlua_expand_pat(&compl_xp); } @@ -4247,7 +4262,7 @@ static int get_cmdline_compl_info(char *line, colnr_T curs_col) // "pattern not found" message. compl_col = curs_col; } else { - compl_col = (int)(compl_xp.xp_pattern - compl_pattern); + compl_col = (int)(compl_xp.xp_pattern - compl_pattern.data); } compl_length = curs_col - compl_col; @@ -4325,8 +4340,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) // it may have become invalid. char *line = ml_get(curwin->w_cursor.lnum); compl_length = curs_col - compl_col; - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); - compl_patternlen = (size_t)compl_length; + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); return OK; } @@ -4351,8 +4365,7 @@ static int get_spell_compl_info(int startcol, colnr_T curs_col) } // Need to obtain "line" again, it may have become invalid. char *line = ml_get(curwin->w_cursor.lnum); - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); - compl_patternlen = (size_t)compl_length; + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); return OK; } @@ -4537,19 +4550,19 @@ static int ins_compl_start(void) ins_compl_fixRedoBufForLeader(NULL); // Always add completion for the original text. - xfree(compl_orig_text); + API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); - compl_orig_text = xstrnsave(line + compl_col, (size_t)compl_length); + compl_orig_text = cbuf_to_string(line + compl_col, (size_t)compl_length); save_orig_extmarks(); int flags = CP_ORIGINAL_TEXT; if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, + if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, + NULL, NULL, false, NULL, 0, flags, false, NULL) != OK) { - XFREE_CLEAR(compl_pattern); - compl_patternlen = 0; - XFREE_CLEAR(compl_orig_text); + API_CLEAR_STRING(compl_pattern); + API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); return FAIL; } @@ -4783,7 +4796,7 @@ static unsigned quote_meta(char *dest, char *src, int len) #if defined(EXITFREE) void free_insexpand_stuff(void) { - XFREE_CLEAR(compl_orig_text); + API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); callback_free(&cfu_cb); callback_free(&ofu_cb); -- cgit From 19c9572d3626cde8503ee9061fa334b73f257b03 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 12:32:25 +0800 Subject: Revert "refactor(options): set option value for non-current context directly" (#31924) Reverts #31112 --- src/nvim/api/deprecated.c | 10 +- src/nvim/api/options.c | 12 +- src/nvim/api/vim.c | 8 +- src/nvim/autocmd.c | 172 ++++----- src/nvim/autocmd_defs.h | 22 +- src/nvim/diff.c | 4 +- src/nvim/ex_docmd.c | 2 +- src/nvim/insexpand.c | 6 +- src/nvim/option.c | 760 +++++++++++++++++++++----------------- src/nvim/option_defs.h | 18 +- src/nvim/optionstr.c | 60 +-- src/nvim/tag.c | 2 +- src/nvim/winfloat.c | 4 +- test/functional/lua/with_spec.lua | 6 +- 14 files changed, 557 insertions(+), 529 deletions(-) diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 47a49436ab..d5eddb74de 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -649,8 +649,8 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er OptVal value = NIL_OPTVAL; if (option_has_scope(opt_idx, scope)) { - value = get_option_value_from(opt_idx, option_ctx_from(scope, from), - scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL); + value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, + scope, from, err); if (ERROR_SET(err)) { return (Object)OBJECT_INIT; } @@ -701,11 +701,7 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); WITH_SCRIPT_CONTEXT(channel_id, { - const char *errmsg - = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags); - if (errmsg) { - api_set_error(err, kErrorTypeException, "%s", errmsg); - } + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 2bbbdbbe8c..64f8a35d54 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -157,8 +157,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, &filetype, - err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, + &filetype, err)) { return (Object)OBJECT_INIT; } @@ -182,7 +182,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) from = ftbuf; } - OptVal value = get_option_value_from(opt_idx, option_ctx_from(scope, from), opt_flags); + OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); if (ftbuf != NULL) { // restore curwin/curbuf and a few other things @@ -257,11 +257,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( }); WITH_SCRIPT_CONTEXT(channel_id, { - const char *errmsg - = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags); - if (errmsg) { - api_set_error(err, kErrorTypeException, "%s", errmsg); - } + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 46e6b24c75..e3e69f4ff6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -983,10 +983,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); if (scratch) { - set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), - option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); - set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), - option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); + set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, + kOptScopeBuf, buf); + set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, + kOptScopeBuf, buf); assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already buf->b_p_swf = false; buf->b_p_ml = false; diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index aab09de6b4..eb7c8c2880 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1251,82 +1251,56 @@ bool check_nomodeline(char **argp) return true; } -/// Prepare for executing autocommands for (hidden) buffer `buf` on window `win` -/// If the buffer of `win` is not `buf`, switch the buffer of `win` to `buf` temporarily. +/// Prepare for executing autocommands for (hidden) buffer `buf`. +/// If the current buffer is not in any visible window, put it in a temporary +/// floating window using an entry in `aucmd_win[]`. +/// Set `curbuf` and `curwin` to match `buf`. /// -/// @param aco Structure to save values in. -/// @param buf New curbuf. -/// @param win New curwin. -void aucmd_prepbuf_win(aco_save_T *aco, buf_T *buf, win_T *win) +/// @param aco structure to save values in +/// @param buf new curbuf +void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) { - bool need_append = false; // Append `aucmd_win` to the window list. - int auc_idx = -1; // Index of aucmd_win[] to use. -1 if not using aucmd_win[]. - - aco->save_curtab_handle = -1; - aco->save_buf_handle = -1; + win_T *win; + bool need_append = true; // Append `aucmd_win` to the window list. - if (win == NULL) { - // Window not provided. Find a window that is for the new buffer - if (buf == curbuf) { // be quick when buf is curbuf - win = curwin; - } else { - win = NULL; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { - win = wp; - break; - } + // Find a window that is for the new buffer + if (buf == curbuf) { // be quick when buf is curbuf + win = curwin; + } else { + win = NULL; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + win = wp; + break; } } + } - // Allocate a window when needed. - if (win == NULL) { - for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { - if (!aucmd_win[auc_idx].auc_win_used) { - break; - } - } - - if (auc_idx == AUCMD_WIN_COUNT) { - kv_push(aucmd_win_vec, ((aucmdwin_T){ - .auc_win = NULL, - .auc_win_used = false, - })); - } - - if (aucmd_win[auc_idx].auc_win == NULL) { - win_alloc_aucmd_win(auc_idx); - } else { - need_append = true; + // Allocate a window when needed. + win_T *auc_win = NULL; + int auc_idx = AUCMD_WIN_COUNT; + if (win == NULL) { + for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { + if (!aucmd_win[auc_idx].auc_win_used) { + break; } - win = aucmd_win[auc_idx].auc_win; - aucmd_win[auc_idx].auc_win_used = true; } - } else { - tabpage_T *tp = win_find_tabpage(win); - // If the window is in another tab page, switch to that tab page temporarily. - if (tp != curtab) { - aco->save_curtab_handle = curtab->handle; - unuse_tabpage(curtab); - use_tabpage(tp); + if (auc_idx == AUCMD_WIN_COUNT) { + kv_push(aucmd_win_vec, ((aucmdwin_T){ + .auc_win = NULL, + .auc_win_used = false, + })); } - } - // If the buffer of the window is not the target buffer, switch to it temporarily. - if (win->w_buffer != buf) { - if (auc_idx == -1) { - // No need to store old buffer for aucmd_win[]. - aco->save_buf_handle = win->w_buffer->handle; - win->w_buffer->b_nwindows--; + if (aucmd_win[auc_idx].auc_win == NULL) { + win_alloc_aucmd_win(auc_idx); + need_append = false; } - - win->w_buffer = buf; - win->w_s = &buf->b_s; - buf->b_nwindows++; + auc_win = aucmd_win[auc_idx].auc_win; + aucmd_win[auc_idx].auc_win_used = true; } - aco->use_aucmd_win_idx = auc_idx; aco->save_curwin_handle = curwin->handle; aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle; aco->save_State = State; @@ -1334,15 +1308,26 @@ void aucmd_prepbuf_win(aco_save_T *aco, buf_T *buf, win_T *win) aco->save_prompt_insert = curbuf->b_prompt_insert; } - if (auc_idx >= 0) { - // There is no window for "buf", use "win". To minimize the side effects, insert it in the - // current tab page. Anything related to a window (e.g., setting folds) may have unexpected - // results. - win_init_empty(win); // Set cursor and topline to safe values. + if (win != NULL) { + // There is a window for "buf" in the current tab page, make it the + // curwin. This is preferred, it has the least side effects (esp. if + // "buf" is curbuf). + aco->use_aucmd_win_idx = -1; + curwin = win; + } else { + // There is no window for "buf", use "auc_win". To minimize the side + // effects, insert it in the current tab page. + // Anything related to a window (e.g., setting folds) may have + // unexpected results. + aco->use_aucmd_win_idx = auc_idx; + auc_win->w_buffer = buf; + auc_win->w_s = &buf->b_s; + buf->b_nwindows++; + win_init_empty(auc_win); // set cursor and topline to safe values - // Make sure w_localdir, tp_localdir and globaldir are NULL to avoid a chdir() in - // win_enter_ext(). - XFREE_CLEAR(win->w_localdir); + // Make sure w_localdir, tp_localdir and globaldir are NULL to avoid a + // chdir() in win_enter_ext(). + XFREE_CLEAR(auc_win->w_localdir); aco->tp_localdir = curtab->tp_localdir; curtab->tp_localdir = NULL; aco->globaldir = globaldir; @@ -1350,43 +1335,30 @@ void aucmd_prepbuf_win(aco_save_T *aco, buf_T *buf, win_T *win) block_autocmds(); // We don't want BufEnter/WinEnter autocommands. if (need_append) { - win_append(lastwin, win, NULL); - pmap_put(int)(&window_handles, win->handle, win); - win_config_float(win, win->w_config); + win_append(lastwin, auc_win, NULL); + pmap_put(int)(&window_handles, auc_win->handle, auc_win); + win_config_float(auc_win, auc_win->w_config); } // Prevent chdir() call in win_enter_ext(), through do_autochdir() const int save_acd = p_acd; p_acd = false; - // No redrawing and don't set the window title + // no redrawing and don't set the window title RedrawingDisabled++; - win_enter(win, false); + win_enter(auc_win, false); RedrawingDisabled--; p_acd = save_acd; unblock_autocmds(); + curwin = auc_win; } - - curwin = win; curbuf = buf; aco->new_curwin_handle = curwin->handle; set_bufref(&aco->new_curbuf, curbuf); - // Disable the Visual area, the position may be invalid in another buffer + // disable the Visual area, the position may be invalid in another buffer aco->save_VIsual_active = VIsual_active; VIsual_active = false; } -/// Prepare for executing autocommands for (hidden) buffer `buf`. -/// If the current buffer is not in any visible window, put it in a temporary -/// floating window using an entry in `aucmd_win[]`. -/// Set `curbuf` and `curwin` to match `buf`. -/// -/// @param aco structure to save values in -/// @param buf new curbuf -void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) -{ - aucmd_prepbuf_win(aco, buf, NULL); -} - /// Cleanup after executing autocommands for a (hidden) buffer. /// Restore the window as it was (if possible). /// @@ -1475,19 +1447,6 @@ win_found: curwin->w_topfill = 0; } } else { - // Restore old buffer of new window if it was changed. - if (aco->save_buf_handle != -1) { - win_T *new_win = win_find_by_handle(aco->new_curwin_handle); - buf_T *new_win_buf = handle_get_buffer(aco->save_buf_handle); - - if (new_win != NULL && new_win_buf != NULL) { - new_win->w_buffer->b_nwindows--; - new_win->w_buffer = new_win_buf; - new_win->w_s = &new_win_buf->b_s; - new_win_buf->b_nwindows++; - } - } - // Restore curwin. Use the window ID, a window may have been closed // and the memory re-used for another one. win_T *const save_curwin = win_find_by_handle(aco->save_curwin_handle); @@ -1523,13 +1482,6 @@ win_found: if (VIsual_active) { check_pos(curbuf, &VIsual); } - - // Switch back to the original tab page if it was switched. - if (aco->save_curtab_handle != -1) { - tabpage_T *save_curtab = handle_get_tabpage(aco->save_curtab_handle); - unuse_tabpage(curtab); - use_tabpage(save_curtab); - } } /// Execute autocommands for "event" and file name "fname". diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h index 777ee7b87a..cba947e85f 100644 --- a/src/nvim/autocmd_defs.h +++ b/src/nvim/autocmd_defs.h @@ -14,18 +14,16 @@ /// Struct to save values in before executing autocommands for a buffer that is /// not the current buffer. typedef struct { - int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 - handle_T save_curwin_handle; ///< ID of saved curwin - handle_T save_curtab_handle; ///< ID of saved curtab. -1 if not switched. - handle_T new_curwin_handle; ///< ID of new curwin - handle_T save_buf_handle; ///< ID of saved buffer of new curwin. -1 if not switched. - handle_T save_prevwin_handle; ///< ID of saved prevwin - bufref_T new_curbuf; ///< new curbuf - char *tp_localdir; ///< saved value of tp_localdir - char *globaldir; ///< saved value of globaldir - bool save_VIsual_active; ///< saved VIsual_active - int save_State; ///< saved State - int save_prompt_insert; ///< saved b_prompt_insert + int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 + handle_T save_curwin_handle; ///< ID of saved curwin + handle_T new_curwin_handle; ///< ID of new curwin + handle_T save_prevwin_handle; ///< ID of saved prevwin + bufref_T new_curbuf; ///< new curbuf + char *tp_localdir; ///< saved value of tp_localdir + char *globaldir; ///< saved value of globaldir + bool save_VIsual_active; ///< saved VIsual_active + int save_State; ///< saved State + int save_prompt_insert; ///< saved b_prompt_insert } aco_save_T; typedef struct { diff --git a/src/nvim/diff.c b/src/nvim/diff.c index ebeb9ba088..bd98a31a71 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1391,8 +1391,8 @@ void diff_win_options(win_T *wp, bool addbuf) } wp->w_p_fdm_save = xstrdup(wp->w_p_fdm); } - set_option_direct_for(kOptFoldmethod, STATIC_CSTR_AS_OPTVAL("diff"), - option_ctx_from(kOptScopeWin, wp), OPT_LOCAL, 0); + set_option_direct_for(kOptFoldmethod, STATIC_CSTR_AS_OPTVAL("diff"), OPT_LOCAL, 0, + kOptScopeWin, wp); if (!wp->w_p_diff) { wp->w_p_fen_save = wp->w_p_fen; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ceb3a074b4..6f9f0f07c9 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5294,7 +5294,7 @@ static char *findfunc_find_file(char *findarg, size_t findarg_len, int count) /// Returns NULL on success and an error message on failure. const char *did_set_findfunc(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; int retval; if (args->os_flags & OPT_LOCAL) { diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 98487be523..419c806592 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -2409,7 +2409,7 @@ static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb) /// lambda expression. const char *did_set_completefunc(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; if (option_set_callback_func(buf->b_p_cfu, &cfu_cb) == FAIL) { return e_invarg; } @@ -2430,7 +2430,7 @@ void set_buflocal_cfu_callback(buf_T *buf) /// lambda expression. const char *did_set_omnifunc(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; if (option_set_callback_func(buf->b_p_ofu, &ofu_cb) == FAIL) { return e_invarg; } @@ -2451,7 +2451,7 @@ void set_buflocal_ofu_callback(buf_T *buf) /// lambda expression. const char *did_set_thesaurusfunc(optset_T *args FUNC_ATTR_UNUSED) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; int retval; if (args->os_flags & OPT_LOCAL) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 2b102fe1bb..551ea0be20 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1324,8 +1324,7 @@ static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char * return; } - *errmsg = set_option_for(opt_idx, newval, option_ctx(), opt_flags, 0, false, - op == OP_NONE, errbuf, errbuflen); + *errmsg = set_option(opt_idx, newval, opt_flags, 0, false, op == OP_NONE, errbuf, errbuflen); } /// Parse 'arg' for option settings. @@ -1845,7 +1844,7 @@ void set_option_sctx(OptIndex opt_idx, int opt_flags, sctx_T script_ctx) /// Apply the OptionSet autocommand. static void apply_optionset_autocmd(OptIndex opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g, - OptVal oldval_l, OptVal newval, OptCtx ctx, const char *errmsg) + OptVal oldval_l, OptVal newval, const char *errmsg) { // Don't do this while starting up, failure or recursively. if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { @@ -1879,18 +1878,14 @@ static void apply_optionset_autocmd(OptIndex opt_idx, int opt_flags, OptVal oldv set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv); } - - WITH_AUCMD_CONTEXT(ctx, { - apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); - }); - + apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } /// Process the updated 'arabic' option value. static const char *did_set_arabic(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; const char *errmsg = NULL; if (win->w_p_arab) { @@ -1959,7 +1954,7 @@ static const char *did_set_autochdir(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'binary' option value. static const char *did_set_binary(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // when 'bin' is set also set some other options set_options_bin((int)args->os_oldval.boolean, buf->b_p_bin, args->os_flags); @@ -1971,7 +1966,7 @@ static const char *did_set_binary(optset_T *args) /// Process the updated 'buflisted' option value. static const char *did_set_buflisted(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // when 'buflisted' changes, trigger autocommands if (args->os_oldval.boolean != buf->b_p_bl) { @@ -2005,7 +2000,7 @@ static const char *did_set_cmdheight(optset_T *args) /// Process the updated 'diff' option value. static const char *did_set_diff(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; // May add or remove the buffer from the list of diff buffers. diff_buf_adjust(win); if (foldmethodIsDiff(win)) { @@ -2026,7 +2021,7 @@ static const char *did_set_eof_eol_fixeol_bomb(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'equalalways' option value. static const char *did_set_equalalways(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (p_ea && !args->os_oldval.boolean) { win_equal(win, false, 0); } @@ -2044,7 +2039,7 @@ static const char *did_set_foldlevel(optset_T *args FUNC_ATTR_UNUSED) /// Process the new 'foldminlines' option value. static const char *did_set_foldminlines(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; foldUpdateAll(win); return NULL; } @@ -2052,7 +2047,7 @@ static const char *did_set_foldminlines(optset_T *args) /// Process the new 'foldnestmax' option value. static const char *did_set_foldnestmax(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (foldmethodIsSyntax(win) || foldmethodIsIndent(win)) { foldUpdateAll(win); } @@ -2181,7 +2176,7 @@ static const char *did_set_lines_or_columns(optset_T *args) /// Process the updated 'lisp' option value. static const char *did_set_lisp(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // When 'lisp' option changes include/exclude '-' in keyword characters. buf_init_chartab(buf, false); // ignore errors return NULL; @@ -2199,7 +2194,7 @@ static const char *did_set_modifiable(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'modified' option value. static const char *did_set_modified(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; if (!args->os_newval.boolean) { save_file_ff(buf); // Buffer is unchanged } @@ -2211,7 +2206,7 @@ static const char *did_set_modified(optset_T *args) /// Process the updated 'number' or 'relativenumber' option value. static const char *did_set_number_relativenumber(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (*win->w_p_stc != NUL) { // When 'relativenumber'/'number' is changed and 'statuscolumn' is set, reset width. win->w_nrwidth_line_count = 0; @@ -2223,7 +2218,7 @@ static const char *did_set_number_relativenumber(optset_T *args) /// Process the new 'numberwidth' option value. static const char *did_set_numberwidth(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; win->w_nrwidth_line_count = 0; // trigger a redraw return NULL; @@ -2361,7 +2356,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'previewwindow' option value. static const char *did_set_previewwindow(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (!win->w_p_pvw) { return NULL; @@ -2393,7 +2388,7 @@ static const char *did_set_pumblend(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'readonly' option value. static const char *did_set_readonly(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // when 'readonly' is reset globally, also reset readonlymode if (!buf->b_p_ro && (args->os_flags & OPT_LOCAL) == 0) { @@ -2413,7 +2408,7 @@ static const char *did_set_readonly(optset_T *args) /// Process the new 'scrollback' option value. static const char *did_set_scrollback(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; OptInt old_value = args->os_oldval.number; OptInt value = args->os_newval.number; @@ -2427,7 +2422,7 @@ static const char *did_set_scrollback(optset_T *args) /// Process the updated 'scrollbind' option value. static const char *did_set_scrollbind(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; // when 'scrollbind' is set: snapshot the current position to avoid a jump // at the end of normal_cmd() @@ -2464,8 +2459,8 @@ static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED) /// Process the new 'shiftwidth' or the 'tabstop' option value. static const char *did_set_shiftwidth_tabstop(optset_T *args) { - buf_T *buf = args->os_ctx.buf; - win_T *win = args->os_ctx.win; + buf_T *buf = (buf_T *)args->os_buf; + win_T *win = (win_T *)args->os_win; OptInt *pp = (OptInt *)args->os_varp; if (foldmethodIsIndent(win)) { @@ -2491,7 +2486,7 @@ static const char *did_set_showtabline(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'smoothscroll' option value. static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (!win->w_p_sms) { win->w_skipcol = 0; } @@ -2502,7 +2497,7 @@ static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED) /// Process the updated 'spell' option value. static const char *did_set_spell(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (win->w_p_spell) { return parse_spelllang(win); } @@ -2513,7 +2508,7 @@ static const char *did_set_spell(optset_T *args) /// Process the updated 'swapfile' option value. static const char *did_set_swapfile(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // when 'swf' is set, create swapfile, when reset remove swapfile if (buf->b_p_swf && p_uc) { ml_open_file(buf); // create the swap file @@ -2558,7 +2553,7 @@ static const char *did_set_titlelen(optset_T *args) /// Process the updated 'undofile' option value. static const char *did_set_undofile(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // Only take action when the option was set. if (!buf->b_p_udf && !p_udf) { @@ -2609,7 +2604,7 @@ const char *did_set_buflocal_undolevels(buf_T *buf, OptInt value, OptInt old_val /// Process the new 'undolevels' option value. static const char *did_set_undolevels(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; OptInt *pp = (OptInt *)args->os_varp; if (pp == &p_ul) { // global 'undolevels' @@ -2650,7 +2645,7 @@ static const char *did_set_wildchar(optset_T *args) /// Process the new 'winblend' option value. static const char *did_set_winblend(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; OptInt old_value = args->os_oldval.number; OptInt value = args->os_newval.number; @@ -2699,7 +2694,7 @@ static const char *did_set_winwidth(optset_T *args) /// Process the updated 'wrap' option value. static const char *did_set_wrap(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; // Set w_leftcol or w_skipcol to zero. if (win->w_p_wrap) { win->w_leftcol = 0; @@ -3148,6 +3143,12 @@ static OptValType option_get_type(const OptIndex opt_idx) OptVal optval_from_varp(OptIndex opt_idx, void *varp) FUNC_ATTR_NONNULL_ARG(2) { + // Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc' + // changed. + if ((int *)varp == &curbuf->b_changed) { + return BOOLEAN_OPTVAL(curbufIsChanged()); + } + if (option_is_multitype(opt_idx)) { // Multitype options are stored as OptVal. return *(OptVal *)varp; @@ -3380,37 +3381,21 @@ uint32_t get_option_flags(OptIndex opt_idx) /// Gets the value for an option. /// /// @param opt_idx Option index in options[] table. -/// @param ctx Context to get the option value from. /// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// /// @return [allocated] Option value. Returns NIL_OPTVAL for invalid option index. -OptVal get_option_value_from(OptIndex opt_idx, OptCtx ctx, int opt_flags) +OptVal get_option_value(OptIndex opt_idx, int opt_flags) { if (opt_idx == kOptInvalid) { // option not in the options[] table. return NIL_OPTVAL; } - // Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc' - // changed. - if (opt_idx == kOptModified) { - return BOOLEAN_OPTVAL(bufIsChanged(ctx.buf)); - } + vimoption_T *opt = &options[opt_idx]; + void *varp = get_varp_scope(opt, opt_flags); - void * const varp = get_varp_scope_from(&options[opt_idx], opt_flags, ctx); return optval_copy(optval_from_varp(opt_idx, varp)); } -/// Gets the value for an option in the current context. -/// -/// @param opt_idx Option index in options[] table. -/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). -/// -/// @return [allocated] Option value. Returns NIL_OPTVAL for invalid option index. -OptVal get_option_value(OptIndex opt_idx, int opt_flags) -{ - return get_option_value_from(opt_idx, option_ctx(), opt_flags); -} - /// Return information for option at 'opt_idx' vimoption_T *get_option(OptIndex opt_idx) { @@ -3479,8 +3464,6 @@ static bool is_option_local_value_unset(OptIndex opt_idx) /// @param opt_idx Index in options[] table. Must not be kOptInvalid. /// @param[in] varp Option variable pointer, cannot be NULL. /// @param old_value Old option value. -/// @param new_value New option value. -/// @param ctx Context in which the option is set. /// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. @@ -3492,9 +3475,8 @@ static bool is_option_local_value_unset(OptIndex opt_idx) /// /// @return NULL on success, an untranslated error message on error. static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value, OptVal new_value, - OptCtx ctx, int opt_flags, scid_T set_sid, const bool direct, - const bool value_replaced, char *errbuf, // NOLINT(readability-non-const-parameter) - size_t errbuflen) + int opt_flags, scid_T set_sid, const bool direct, + const bool value_replaced, char *errbuf, size_t errbuflen) { vimoption_T *opt = &options[opt_idx]; const char *errmsg = NULL; @@ -3513,7 +3495,8 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value .os_restore_chartab = false, .os_errbuf = errbuf, .os_errbuflen = errbuflen, - .os_ctx = ctx, + .os_buf = curbuf, + .os_win = curwin }; if (direct) { @@ -3550,7 +3533,7 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value set_option_varp(opt_idx, varp, old_value, true); // When resetting some values, need to act on it. if (restore_chartab) { - buf_init_chartab(ctx.buf, true); + buf_init_chartab(curbuf, true); } return errmsg; @@ -3581,12 +3564,12 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value if (option_is_global_local(opt_idx)) { // Global option with local value set to use global value. // Free the local value and clear it. - void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, ctx); + void *varp_local = get_varp_scope(opt, OPT_LOCAL); OptVal local_unset_value = get_option_unset_value(opt_idx); set_option_varp(opt_idx, varp_local, optval_copy(local_unset_value), true); } else { // May set global value for local option. - void *varp_global = get_varp_scope_from(opt, OPT_GLOBAL, ctx); + void *varp_global = get_varp_scope(opt, OPT_GLOBAL); set_option_varp(opt_idx, varp_global, optval_copy(new_value), true); } } @@ -3597,23 +3580,17 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value } // Trigger the autocommand only after setting the flags. - if (varp == &ctx.buf->b_p_syn) { - WITH_AUCMD_CONTEXT(ctx, { - do_syntax_autocmd(ctx.buf, value_changed); - }); - } else if (varp == &ctx.buf->b_p_ft) { + if (varp == &curbuf->b_p_syn) { + do_syntax_autocmd(curbuf, value_changed); + } else if (varp == &curbuf->b_p_ft) { // 'filetype' is set, trigger the FileType autocommand // Skip this when called from a modeline // Force autocmd when the filetype was changed if (!(opt_flags & OPT_MODELINE) || value_changed) { - WITH_AUCMD_CONTEXT(ctx, { - do_filetype_autocmd(ctx.buf, value_changed); - }); + do_filetype_autocmd(curbuf, value_changed); } - } else if (varp == &ctx.win->w_s->b_p_spl) { - WITH_AUCMD_CONTEXT(ctx, { - do_spelllang_source(ctx.win); - }); + } else if (varp == &curwin->w_s->b_p_spl) { + do_spelllang_source(curwin); } // In case 'ruler' or 'showcmd' or 'columns' or 'ls' changed. @@ -3621,25 +3598,25 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value if (varp == &p_mouse) { setmouse(); // in case 'mouse' changed - } else if ((varp == &p_flp || varp == &(ctx.buf->b_p_flp)) && ctx.win->w_briopt_list) { + } else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) { // Changing Formatlistpattern when briopt includes the list setting: // redraw redraw_all_later(UPD_NOT_VALID); - } else if (varp == &p_wbr || varp == &(ctx.win->w_p_wbr)) { + } else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) { // add / remove window bars for 'winbar' set_winbar(true); } - if (ctx.win->w_curswant != MAXCOL + if (curwin->w_curswant != MAXCOL && (opt->flags & (kOptFlagCurswant | kOptFlagRedrAll)) != 0 && (opt->flags & kOptFlagHLOnly) == 0) { - ctx.win->w_set_curswant = true; + curwin->w_set_curswant = true; } - check_redraw_for(ctx.buf, ctx.win, opt->flags); + check_redraw(opt->flags); if (errmsg == NULL) { - uint32_t *p = insecure_flag(ctx.win, opt_idx, opt_flags); + uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); opt->flags |= kOptFlagWasSet; // When an option is set in the sandbox, from a modeline or in secure mode set the kOptFlagInsecure @@ -3699,7 +3676,6 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval, /// /// @param opt_idx Index in options[] table. Must not be kOptInvalid. /// @param value New option value. Might get freed. -/// @param ctx Context in which the option is set. /// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. @@ -3710,9 +3686,9 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval, /// @param errbuflen Length of error buffer. /// /// @return NULL on success, an untranslated error message on error. -static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_flags, - scid_T set_sid, const bool direct, const bool value_replaced, - char *errbuf, size_t errbuflen) +static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid, + const bool direct, const bool value_replaced, char *errbuf, + size_t errbuflen) { assert(opt_idx != kOptInvalid); @@ -3737,11 +3713,10 @@ static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, in // When using ":set opt=val" for a global option with a local value the local value will be reset, // use the global value in that case. - void *varp = scope_both && option_is_global_local(opt_idx) - ? opt->var - : get_varp_scope_from(opt, opt_flags, ctx); - void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, ctx); - void *varp_global = get_varp_scope_from(opt, OPT_GLOBAL, ctx); + void *varp + = scope_both && option_is_global_local(opt_idx) ? opt->var : get_varp_scope(opt, opt_flags); + void *varp_local = get_varp_scope(opt, OPT_LOCAL); + void *varp_global = get_varp_scope(opt, OPT_GLOBAL); OptVal old_value = optval_from_varp(opt_idx, varp); OptVal old_global_value = optval_from_varp(opt_idx, varp_global); @@ -3754,7 +3729,7 @@ static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, in // unset. In every other case, it is the same as old_value. // This value is used instead of old_value when triggering the OptionSet autocommand. OptVal used_old_value = (scope_local && is_opt_local_unset) - ? optval_from_varp(opt_idx, get_varp_from(opt, ctx)) + ? optval_from_varp(opt_idx, get_varp(opt)) : old_value; // Save the old values and the new value in case they get changed. @@ -3764,7 +3739,7 @@ static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, in // New value (and varp) may become invalid if the buffer is closed by autocommands. OptVal saved_new_value = optval_copy(value); - uint32_t *p = insecure_flag(ctx.win, opt_idx, opt_flags); + uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); const int secure_saved = secure; // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side @@ -3777,7 +3752,7 @@ static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, in // Set option through its variable pointer. set_option_varp(opt_idx, varp, value, false); // Process any side effects. - errmsg = did_set_option(opt_idx, varp, old_value, value, ctx, opt_flags, set_sid, direct, + errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, set_sid, direct, value_replaced, errbuf, errbuflen); secure = secure_saved; @@ -3785,7 +3760,7 @@ static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, in if (errmsg == NULL && !direct) { if (!starting) { apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value, - saved_old_local_value, saved_new_value, ctx, errmsg); + saved_old_local_value, saved_new_value, errmsg); } if (opt->flags & kOptFlagUIOption) { ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value)); @@ -3805,13 +3780,11 @@ static const char *set_option_for(OptIndex opt_idx, OptVal value, OptCtx ctx, in /// /// @param opt_idx Option index in options[] table. /// @param value Option value. -/// @param ctx Context in which the option is set. /// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param set_sid Script ID. Special values: /// 0: Use current script ID. /// SID_NONE: Don't set script ID. -void set_option_direct_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_flags, - scid_T set_sid) +void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid) { static char errbuf[IOSIZE]; @@ -3819,35 +3792,57 @@ void set_option_direct_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_f return; } - const char *errmsg = set_option_for(opt_idx, optval_copy(value), ctx, opt_flags, set_sid, true, - true, errbuf, sizeof(errbuf)); + const char *errmsg = set_option(opt_idx, optval_copy(value), opt_flags, set_sid, true, true, + errbuf, sizeof(errbuf)); assert(errmsg == NULL); (void)errmsg; // ignore unused warning } -/// Set option value for current context directly, without processing any side effects. +/// Set option value directly for buffer / window, without processing any side effects. /// -/// @param opt_idx Option index in options[] table. -/// @param value Option value. -/// @param ctx Context in which the option is set. -/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). -/// @param set_sid Script ID. Special values: -/// 0: Use current script ID. -/// SID_NONE: Don't set script ID. -void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid) -{ - set_option_direct_for(opt_idx, value, option_ctx(), opt_flags, set_sid); +/// @param opt_idx Option index in options[] table. +/// @param value Option value. +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). +/// @param set_sid Script ID. Special values: +/// 0: Use current script ID. +/// SID_NONE: Don't set script ID. +/// @param scope Option scope. See OptScope in option.h. +/// @param[in] from Target buffer/window. +void set_option_direct_for(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid, + OptScope scope, void *const from) +{ + buf_T *save_curbuf = curbuf; + win_T *save_curwin = curwin; + + // Don't use switch_option_context(), as that calls aucmd_prepbuf(), which may have unintended + // side-effects when setting an option directly. Just change the values of curbuf and curwin if + // needed, no need to properly switch the window / buffer. + switch (scope) { + case kOptScopeGlobal: + break; + case kOptScopeWin: + curwin = (win_T *)from; + curbuf = curwin->w_buffer; + break; + case kOptScopeBuf: + curbuf = (buf_T *)from; + break; + } + + set_option_direct(opt_idx, value, opt_flags, set_sid); + + curwin = save_curwin; + curbuf = save_curbuf; } /// Set the value of an option. /// /// @param opt_idx Index in options[] table. Must not be kOptInvalid. /// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared. -/// @param ctx Context to set option for. /// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). /// /// @return NULL on success, an untranslated error message on error. -const char *set_option_value_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int opt_flags) +const char *set_option_value(const OptIndex opt_idx, const OptVal value, int opt_flags) { assert(opt_idx != kOptInvalid); @@ -3859,21 +3854,7 @@ const char *set_option_value_for(OptIndex opt_idx, OptVal value, OptCtx ctx, int return _(e_sandbox); } - return set_option_for(opt_idx, optval_copy(value), ctx, opt_flags, 0, false, true, errbuf, - sizeof(errbuf)); -} - -/// Set the value of an option for current context. -/// -/// @param opt_idx Index in options[] table. Must not be kOptInvalid. -/// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared. -/// @param ctx Context to set option for. -/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). -/// -/// @return NULL on success, an untranslated error message on error. -const char *set_option_value(OptIndex opt_idx, OptVal value, int opt_flags) -{ - return set_option_value_for(opt_idx, value, option_ctx(), opt_flags); + return set_option(opt_idx, optval_copy(value), opt_flags, 0, false, true, errbuf, sizeof(errbuf)); } /// Unset the local value of a global-local option. @@ -3929,6 +3910,135 @@ void set_option_value_give_err(const OptIndex opt_idx, OptVal value, int opt_fla } } +/// Switch current context to get/set option value for window/buffer. +/// +/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer. +/// @param scope Option scope. See OptScope in option.h. +/// @param[in] from Target buffer/window. +/// @param[out] err Error message, if any. +/// +/// @return true if context was switched, false otherwise. +static bool switch_option_context(void *const ctx, OptScope scope, void *const from, Error *err) +{ + switch (scope) { + case kOptScopeGlobal: + return false; + case kOptScopeWin: { + win_T *const win = (win_T *)from; + switchwin_T *const switchwin = (switchwin_T *)ctx; + + if (win == curwin) { + return false; + } + + if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true) + == FAIL) { + restore_win_noblock(switchwin, true); + + if (ERROR_SET(err)) { + return false; + } + api_set_error(err, kErrorTypeException, "Problem while switching windows"); + return false; + } + return true; + } + case kOptScopeBuf: { + buf_T *const buf = (buf_T *)from; + aco_save_T *const aco = (aco_save_T *)ctx; + + if (buf == curbuf) { + return false; + } + aucmd_prepbuf(aco, buf); + return true; + } + } + UNREACHABLE; +} + +/// Restore context after getting/setting option for window/buffer. See switch_option_context() for +/// params. +static void restore_option_context(void *const ctx, OptScope scope) +{ + switch (scope) { + case kOptScopeGlobal: + break; + case kOptScopeWin: + restore_win_noblock((switchwin_T *)ctx, true); + break; + case kOptScopeBuf: + aucmd_restbuf((aco_save_T *)ctx); + break; + } +} + +/// Get option value for buffer / window. +/// +/// @param opt_idx Option index in options[] table. +/// @param[out] flagsp Set to the option flags (see OptFlags) (if not NULL). +/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination). +/// @param[out] hidden Whether option is hidden. +/// @param scope Option scope. See OptScope in option.h. +/// @param[in] from Target buffer/window. +/// @param[out] err Error message, if any. +/// +/// @return Option value. Must be freed by caller. +OptVal get_option_value_for(OptIndex opt_idx, int opt_flags, const OptScope scope, void *const from, + Error *err) +{ + switchwin_T switchwin; + aco_save_T aco; + void *ctx = scope == kOptScopeWin ? (void *)&switchwin + : (scope == kOptScopeBuf ? (void *)&aco : NULL); + + bool switched = switch_option_context(ctx, scope, from, err); + if (ERROR_SET(err)) { + return NIL_OPTVAL; + } + + OptVal retv = get_option_value(opt_idx, opt_flags); + + if (switched) { + restore_option_context(ctx, scope); + } + + return retv; +} + +/// Set option value for buffer / window. +/// +/// @param name Option name. +/// @param opt_idx Option index in options[] table. +/// @param[in] value Option value. +/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). +/// @param scope Option scope. See OptScope in option.h. +/// @param[in] from Target buffer/window. +/// @param[out] err Error message, if any. +void set_option_value_for(const char *name, OptIndex opt_idx, OptVal value, const int opt_flags, + const OptScope scope, void *const from, Error *err) + FUNC_ATTR_NONNULL_ARG(1) +{ + switchwin_T switchwin; + aco_save_T aco; + void *ctx = scope == kOptScopeWin ? (void *)&switchwin + : (scope == kOptScopeBuf ? (void *)&aco : NULL); + + bool switched = switch_option_context(ctx, scope, from, err); + if (ERROR_SET(err)) { + return; + } + + const char *const errmsg = set_option_value_handle_tty(name, opt_idx, value, opt_flags); + if (errmsg) { + api_set_error(err, kErrorTypeException, "%s", errmsg); + } + + if (switched) { + restore_option_context(ctx, scope); + } +} + /// if 'all' == false: show changed options /// if 'all' == true: show all normal options /// @@ -4344,13 +4454,13 @@ static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp) return OK; } -void *get_varp_scope_from(vimoption_T *p, int opt_flags, OptCtx ctx) +void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win) { OptIndex opt_idx = get_opt_idx(p); if ((opt_flags & OPT_GLOBAL) && !option_is_global_only(opt_idx)) { if (option_is_window_local(opt_idx)) { - return GLOBAL_WO(get_varp_from(p, ctx)); + return GLOBAL_WO(get_varp_from(p, buf, win)); } return p->var; } @@ -4358,73 +4468,88 @@ void *get_varp_scope_from(vimoption_T *p, int opt_flags, OptCtx ctx) if ((opt_flags & OPT_LOCAL) && option_is_global_local(opt_idx)) { switch (opt_idx) { case kOptFormatprg: - return &(ctx.buf->b_p_fp); + return &(buf->b_p_fp); case kOptFindfunc: - return &(ctx.buf->b_p_ffu); + return &(buf->b_p_ffu); case kOptErrorformat: - return &(ctx.buf->b_p_efm); + return &(buf->b_p_efm); case kOptGrepprg: - return &(ctx.buf->b_p_gp); + return &(buf->b_p_gp); case kOptMakeprg: - return &(ctx.buf->b_p_mp); + return &(buf->b_p_mp); case kOptEqualprg: - return &(ctx.buf->b_p_ep); + return &(buf->b_p_ep); case kOptKeywordprg: - return &(ctx.buf->b_p_kp); + return &(buf->b_p_kp); case kOptPath: - return &(ctx.buf->b_p_path); + return &(buf->b_p_path); case kOptAutoread: - return &(ctx.buf->b_p_ar); + return &(buf->b_p_ar); case kOptTags: - return &(ctx.buf->b_p_tags); + return &(buf->b_p_tags); case kOptTagcase: - return &(ctx.buf->b_p_tc); + return &(buf->b_p_tc); case kOptSidescrolloff: - return &(ctx.win->w_p_siso); + return &(win->w_p_siso); case kOptScrolloff: - return &(ctx.win->w_p_so); + return &(win->w_p_so); case kOptDefine: - return &(ctx.buf->b_p_def); + return &(buf->b_p_def); case kOptInclude: - return &(ctx.buf->b_p_inc); + return &(buf->b_p_inc); case kOptCompleteopt: - return &(ctx.buf->b_p_cot); + return &(buf->b_p_cot); case kOptDictionary: - return &(ctx.buf->b_p_dict); + return &(buf->b_p_dict); case kOptThesaurus: - return &(ctx.buf->b_p_tsr); + return &(buf->b_p_tsr); case kOptThesaurusfunc: - return &(ctx.buf->b_p_tsrfu); + return &(buf->b_p_tsrfu); case kOptTagfunc: - return &(ctx.buf->b_p_tfu); + return &(buf->b_p_tfu); case kOptShowbreak: - return &(ctx.win->w_p_sbr); + return &(win->w_p_sbr); case kOptStatusline: - return &(ctx.win->w_p_stl); + return &(win->w_p_stl); case kOptWinbar: - return &(ctx.win->w_p_wbr); + return &(win->w_p_wbr); case kOptUndolevels: - return &(ctx.buf->b_p_ul); + return &(buf->b_p_ul); case kOptLispwords: - return &(ctx.buf->b_p_lw); + return &(buf->b_p_lw); case kOptBackupcopy: - return &(ctx.buf->b_p_bkc); + return &(buf->b_p_bkc); case kOptMakeencoding: - return &(ctx.buf->b_p_menc); + return &(buf->b_p_menc); case kOptFillchars: - return &(ctx.win->w_p_fcs); + return &(win->w_p_fcs); case kOptListchars: - return &(ctx.win->w_p_lcs); + return &(win->w_p_lcs); case kOptVirtualedit: - return &(ctx.win->w_p_ve); + return &(win->w_p_ve); default: abort(); } } - return get_varp_from(p, ctx); + return get_varp_from(p, buf, win); +} + +/// Get pointer to option variable, depending on local or global scope. +/// +/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). +void *get_varp_scope(vimoption_T *p, int opt_flags) +{ + return get_varp_scope_from(p, opt_flags, curbuf, curwin); } -void *get_varp_from(vimoption_T *p, OptCtx ctx) +/// Get pointer to option variable at 'opt_idx', depending on local or global +/// scope. +void *get_option_varp_scope_from(OptIndex opt_idx, int opt_flags, buf_T *buf, win_T *win) +{ + return get_varp_scope_from(&(options[opt_idx]), opt_flags, buf, win); +} + +void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) { OptIndex opt_idx = get_opt_idx(p); @@ -4436,330 +4561,299 @@ void *get_varp_from(vimoption_T *p, OptCtx ctx) switch (opt_idx) { // global option with local value: use local value if it's been set case kOptEqualprg: - return *ctx.buf->b_p_ep != NUL ? &ctx.buf->b_p_ep : p->var; + return *buf->b_p_ep != NUL ? &buf->b_p_ep : p->var; case kOptKeywordprg: - return *ctx.buf->b_p_kp != NUL ? &ctx.buf->b_p_kp : p->var; + return *buf->b_p_kp != NUL ? &buf->b_p_kp : p->var; case kOptPath: - return *ctx.buf->b_p_path != NUL ? &(ctx.buf->b_p_path) : p->var; + return *buf->b_p_path != NUL ? &(buf->b_p_path) : p->var; case kOptAutoread: - return ctx.buf->b_p_ar >= 0 ? &(ctx.buf->b_p_ar) : p->var; + return buf->b_p_ar >= 0 ? &(buf->b_p_ar) : p->var; case kOptTags: - return *ctx.buf->b_p_tags != NUL ? &(ctx.buf->b_p_tags) : p->var; + return *buf->b_p_tags != NUL ? &(buf->b_p_tags) : p->var; case kOptTagcase: - return *ctx.buf->b_p_tc != NUL ? &(ctx.buf->b_p_tc) : p->var; + return *buf->b_p_tc != NUL ? &(buf->b_p_tc) : p->var; case kOptSidescrolloff: - return ctx.win->w_p_siso >= 0 ? &(ctx.win->w_p_siso) : p->var; + return win->w_p_siso >= 0 ? &(win->w_p_siso) : p->var; case kOptScrolloff: - return ctx.win->w_p_so >= 0 ? &(ctx.win->w_p_so) : p->var; + return win->w_p_so >= 0 ? &(win->w_p_so) : p->var; case kOptBackupcopy: - return *ctx.buf->b_p_bkc != NUL ? &(ctx.buf->b_p_bkc) : p->var; + return *buf->b_p_bkc != NUL ? &(buf->b_p_bkc) : p->var; case kOptDefine: - return *ctx.buf->b_p_def != NUL ? &(ctx.buf->b_p_def) : p->var; + return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var; case kOptInclude: - return *ctx.buf->b_p_inc != NUL ? &(ctx.buf->b_p_inc) : p->var; + return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var; case kOptCompleteopt: - return *ctx.buf->b_p_cot != NUL ? &(ctx.buf->b_p_cot) : p->var; + return *buf->b_p_cot != NUL ? &(buf->b_p_cot) : p->var; case kOptDictionary: - return *ctx.buf->b_p_dict != NUL ? &(ctx.buf->b_p_dict) : p->var; + return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var; case kOptThesaurus: - return *ctx.buf->b_p_tsr != NUL ? &(ctx.buf->b_p_tsr) : p->var; + return *buf->b_p_tsr != NUL ? &(buf->b_p_tsr) : p->var; case kOptThesaurusfunc: - return *ctx.buf->b_p_tsrfu != NUL ? &(ctx.buf->b_p_tsrfu) : p->var; + return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var; case kOptFormatprg: - return *ctx.buf->b_p_fp != NUL ? &(ctx.buf->b_p_fp) : p->var; + return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var; case kOptFindfunc: - return *ctx.buf->b_p_ffu != NUL ? &(ctx.buf->b_p_ffu) : p->var; + return *buf->b_p_ffu != NUL ? &(buf->b_p_ffu) : p->var; case kOptErrorformat: - return *ctx.buf->b_p_efm != NUL ? &(ctx.buf->b_p_efm) : p->var; + return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var; case kOptGrepprg: - return *ctx.buf->b_p_gp != NUL ? &(ctx.buf->b_p_gp) : p->var; + return *buf->b_p_gp != NUL ? &(buf->b_p_gp) : p->var; case kOptMakeprg: - return *ctx.buf->b_p_mp != NUL ? &(ctx.buf->b_p_mp) : p->var; + return *buf->b_p_mp != NUL ? &(buf->b_p_mp) : p->var; case kOptShowbreak: - return *ctx.win->w_p_sbr != NUL ? &(ctx.win->w_p_sbr) : p->var; + return *win->w_p_sbr != NUL ? &(win->w_p_sbr) : p->var; case kOptStatusline: - return *ctx.win->w_p_stl != NUL ? &(ctx.win->w_p_stl) : p->var; + return *win->w_p_stl != NUL ? &(win->w_p_stl) : p->var; case kOptWinbar: - return *ctx.win->w_p_wbr != NUL ? &(ctx.win->w_p_wbr) : p->var; + return *win->w_p_wbr != NUL ? &(win->w_p_wbr) : p->var; case kOptUndolevels: - return ctx.buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(ctx.buf->b_p_ul) : p->var; + return buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(buf->b_p_ul) : p->var; case kOptLispwords: - return *ctx.buf->b_p_lw != NUL ? &(ctx.buf->b_p_lw) : p->var; + return *buf->b_p_lw != NUL ? &(buf->b_p_lw) : p->var; case kOptMakeencoding: - return *ctx.buf->b_p_menc != NUL ? &(ctx.buf->b_p_menc) : p->var; + return *buf->b_p_menc != NUL ? &(buf->b_p_menc) : p->var; case kOptFillchars: - return *ctx.win->w_p_fcs != NUL ? &(ctx.win->w_p_fcs) : p->var; + return *win->w_p_fcs != NUL ? &(win->w_p_fcs) : p->var; case kOptListchars: - return *ctx.win->w_p_lcs != NUL ? &(ctx.win->w_p_lcs) : p->var; + return *win->w_p_lcs != NUL ? &(win->w_p_lcs) : p->var; case kOptVirtualedit: - return *ctx.win->w_p_ve != NUL ? &ctx.win->w_p_ve : p->var; + return *win->w_p_ve != NUL ? &win->w_p_ve : p->var; case kOptArabic: - return &(ctx.win->w_p_arab); + return &(win->w_p_arab); case kOptList: - return &(ctx.win->w_p_list); + return &(win->w_p_list); case kOptSpell: - return &(ctx.win->w_p_spell); + return &(win->w_p_spell); case kOptCursorcolumn: - return &(ctx.win->w_p_cuc); + return &(win->w_p_cuc); case kOptCursorline: - return &(ctx.win->w_p_cul); + return &(win->w_p_cul); case kOptCursorlineopt: - return &(ctx.win->w_p_culopt); + return &(win->w_p_culopt); case kOptColorcolumn: - return &(ctx.win->w_p_cc); + return &(win->w_p_cc); case kOptDiff: - return &(ctx.win->w_p_diff); + return &(win->w_p_diff); case kOptFoldcolumn: - return &(ctx.win->w_p_fdc); + return &(win->w_p_fdc); case kOptFoldenable: - return &(ctx.win->w_p_fen); + return &(win->w_p_fen); case kOptFoldignore: - return &(ctx.win->w_p_fdi); + return &(win->w_p_fdi); case kOptFoldlevel: - return &(ctx.win->w_p_fdl); + return &(win->w_p_fdl); case kOptFoldmethod: - return &(ctx.win->w_p_fdm); + return &(win->w_p_fdm); case kOptFoldminlines: - return &(ctx.win->w_p_fml); + return &(win->w_p_fml); case kOptFoldnestmax: - return &(ctx.win->w_p_fdn); + return &(win->w_p_fdn); case kOptFoldexpr: - return &(ctx.win->w_p_fde); + return &(win->w_p_fde); case kOptFoldtext: - return &(ctx.win->w_p_fdt); + return &(win->w_p_fdt); case kOptFoldmarker: - return &(ctx.win->w_p_fmr); + return &(win->w_p_fmr); case kOptNumber: - return &(ctx.win->w_p_nu); + return &(win->w_p_nu); case kOptRelativenumber: - return &(ctx.win->w_p_rnu); + return &(win->w_p_rnu); case kOptNumberwidth: - return &(ctx.win->w_p_nuw); + return &(win->w_p_nuw); case kOptWinfixbuf: - return &(ctx.win->w_p_wfb); + return &(win->w_p_wfb); case kOptWinfixheight: - return &(ctx.win->w_p_wfh); + return &(win->w_p_wfh); case kOptWinfixwidth: - return &(ctx.win->w_p_wfw); + return &(win->w_p_wfw); case kOptPreviewwindow: - return &(ctx.win->w_p_pvw); + return &(win->w_p_pvw); case kOptRightleft: - return &(ctx.win->w_p_rl); + return &(win->w_p_rl); case kOptRightleftcmd: - return &(ctx.win->w_p_rlc); + return &(win->w_p_rlc); case kOptScroll: - return &(ctx.win->w_p_scr); + return &(win->w_p_scr); case kOptSmoothscroll: - return &(ctx.win->w_p_sms); + return &(win->w_p_sms); case kOptWrap: - return &(ctx.win->w_p_wrap); + return &(win->w_p_wrap); case kOptLinebreak: - return &(ctx.win->w_p_lbr); + return &(win->w_p_lbr); case kOptBreakindent: - return &(ctx.win->w_p_bri); + return &(win->w_p_bri); case kOptBreakindentopt: - return &(ctx.win->w_p_briopt); + return &(win->w_p_briopt); case kOptScrollbind: - return &(ctx.win->w_p_scb); + return &(win->w_p_scb); case kOptCursorbind: - return &(ctx.win->w_p_crb); + return &(win->w_p_crb); case kOptConcealcursor: - return &(ctx.win->w_p_cocu); + return &(win->w_p_cocu); case kOptConceallevel: - return &(ctx.win->w_p_cole); + return &(win->w_p_cole); case kOptAutoindent: - return &(ctx.buf->b_p_ai); + return &(buf->b_p_ai); case kOptBinary: - return &(ctx.buf->b_p_bin); + return &(buf->b_p_bin); case kOptBomb: - return &(ctx.buf->b_p_bomb); + return &(buf->b_p_bomb); case kOptBufhidden: - return &(ctx.buf->b_p_bh); + return &(buf->b_p_bh); case kOptBuftype: - return &(ctx.buf->b_p_bt); + return &(buf->b_p_bt); case kOptBuflisted: - return &(ctx.buf->b_p_bl); + return &(buf->b_p_bl); case kOptChannel: - return &(ctx.buf->b_p_channel); + return &(buf->b_p_channel); case kOptCopyindent: - return &(ctx.buf->b_p_ci); + return &(buf->b_p_ci); case kOptCindent: - return &(ctx.buf->b_p_cin); + return &(buf->b_p_cin); case kOptCinkeys: - return &(ctx.buf->b_p_cink); + return &(buf->b_p_cink); case kOptCinoptions: - return &(ctx.buf->b_p_cino); + return &(buf->b_p_cino); case kOptCinscopedecls: - return &(ctx.buf->b_p_cinsd); + return &(buf->b_p_cinsd); case kOptCinwords: - return &(ctx.buf->b_p_cinw); + return &(buf->b_p_cinw); case kOptComments: - return &(ctx.buf->b_p_com); + return &(buf->b_p_com); case kOptCommentstring: - return &(ctx.buf->b_p_cms); + return &(buf->b_p_cms); case kOptComplete: - return &(ctx.buf->b_p_cpt); + return &(buf->b_p_cpt); #ifdef BACKSLASH_IN_FILENAME case kOptCompleteslash: - return &(ctx.buf->b_p_csl); + return &(buf->b_p_csl); #endif case kOptCompletefunc: - return &(ctx.buf->b_p_cfu); + return &(buf->b_p_cfu); case kOptOmnifunc: - return &(ctx.buf->b_p_ofu); + return &(buf->b_p_ofu); case kOptEndoffile: - return &(ctx.buf->b_p_eof); + return &(buf->b_p_eof); case kOptEndofline: - return &(ctx.buf->b_p_eol); + return &(buf->b_p_eol); case kOptFixendofline: - return &(ctx.buf->b_p_fixeol); + return &(buf->b_p_fixeol); case kOptExpandtab: - return &(ctx.buf->b_p_et); + return &(buf->b_p_et); case kOptFileencoding: - return &(ctx.buf->b_p_fenc); + return &(buf->b_p_fenc); case kOptFileformat: - return &(ctx.buf->b_p_ff); + return &(buf->b_p_ff); case kOptFiletype: - return &(ctx.buf->b_p_ft); + return &(buf->b_p_ft); case kOptFormatoptions: - return &(ctx.buf->b_p_fo); + return &(buf->b_p_fo); case kOptFormatlistpat: - return &(ctx.buf->b_p_flp); + return &(buf->b_p_flp); case kOptIminsert: - return &(ctx.buf->b_p_iminsert); + return &(buf->b_p_iminsert); case kOptImsearch: - return &(ctx.buf->b_p_imsearch); + return &(buf->b_p_imsearch); case kOptInfercase: - return &(ctx.buf->b_p_inf); + return &(buf->b_p_inf); case kOptIskeyword: - return &(ctx.buf->b_p_isk); + return &(buf->b_p_isk); case kOptIncludeexpr: - return &(ctx.buf->b_p_inex); + return &(buf->b_p_inex); case kOptIndentexpr: - return &(ctx.buf->b_p_inde); + return &(buf->b_p_inde); case kOptIndentkeys: - return &(ctx.buf->b_p_indk); + return &(buf->b_p_indk); case kOptFormatexpr: - return &(ctx.buf->b_p_fex); + return &(buf->b_p_fex); case kOptLisp: - return &(ctx.buf->b_p_lisp); + return &(buf->b_p_lisp); case kOptLispoptions: - return &(ctx.buf->b_p_lop); + return &(buf->b_p_lop); case kOptModeline: - return &(ctx.buf->b_p_ml); + return &(buf->b_p_ml); case kOptMatchpairs: - return &(ctx.buf->b_p_mps); + return &(buf->b_p_mps); case kOptModifiable: - return &(ctx.buf->b_p_ma); + return &(buf->b_p_ma); case kOptModified: - return &(ctx.buf->b_changed); + return &(buf->b_changed); case kOptNrformats: - return &(ctx.buf->b_p_nf); + return &(buf->b_p_nf); case kOptPreserveindent: - return &(ctx.buf->b_p_pi); + return &(buf->b_p_pi); case kOptQuoteescape: - return &(ctx.buf->b_p_qe); + return &(buf->b_p_qe); case kOptReadonly: - return &(ctx.buf->b_p_ro); + return &(buf->b_p_ro); case kOptScrollback: - return &(ctx.buf->b_p_scbk); + return &(buf->b_p_scbk); case kOptSmartindent: - return &(ctx.buf->b_p_si); + return &(buf->b_p_si); case kOptSofttabstop: - return &(ctx.buf->b_p_sts); + return &(buf->b_p_sts); case kOptSuffixesadd: - return &(ctx.buf->b_p_sua); + return &(buf->b_p_sua); case kOptSwapfile: - return &(ctx.buf->b_p_swf); + return &(buf->b_p_swf); case kOptSynmaxcol: - return &(ctx.buf->b_p_smc); + return &(buf->b_p_smc); case kOptSyntax: - return &(ctx.buf->b_p_syn); + return &(buf->b_p_syn); case kOptSpellcapcheck: - return &(ctx.win->w_s->b_p_spc); + return &(win->w_s->b_p_spc); case kOptSpellfile: - return &(ctx.win->w_s->b_p_spf); + return &(win->w_s->b_p_spf); case kOptSpelllang: - return &(ctx.win->w_s->b_p_spl); + return &(win->w_s->b_p_spl); case kOptSpelloptions: - return &(ctx.win->w_s->b_p_spo); + return &(win->w_s->b_p_spo); case kOptShiftwidth: - return &(ctx.buf->b_p_sw); + return &(buf->b_p_sw); case kOptTagfunc: - return &(ctx.buf->b_p_tfu); + return &(buf->b_p_tfu); case kOptTabstop: - return &(ctx.buf->b_p_ts); + return &(buf->b_p_ts); case kOptTextwidth: - return &(ctx.buf->b_p_tw); + return &(buf->b_p_tw); case kOptUndofile: - return &(ctx.buf->b_p_udf); + return &(buf->b_p_udf); case kOptWrapmargin: - return &(ctx.buf->b_p_wm); + return &(buf->b_p_wm); case kOptVarsofttabstop: - return &(ctx.buf->b_p_vsts); + return &(buf->b_p_vsts); case kOptVartabstop: - return &(ctx.buf->b_p_vts); + return &(buf->b_p_vts); case kOptKeymap: - return &(ctx.buf->b_p_keymap); + return &(buf->b_p_keymap); case kOptSigncolumn: - return &(ctx.win->w_p_scl); + return &(win->w_p_scl); case kOptWinhighlight: - return &(ctx.win->w_p_winhl); + return &(win->w_p_winhl); case kOptWinblend: - return &(ctx.win->w_p_winbl); + return &(win->w_p_winbl); case kOptStatuscolumn: - return &(ctx.win->w_p_stc); + return &(win->w_p_stc); default: iemsg(_("E356: get_varp ERROR")); } // always return a valid pointer to avoid a crash! - return &(ctx.buf->b_p_wm); + return &(buf->b_p_wm); } -/// Get the current context for options. -OptCtx option_ctx(void) -{ - return (OptCtx){ .buf = curbuf, .win = curwin }; -} - -/// Get the context for options from a scope and pointer to target. -/// -/// @param scope Option scope. See OptScope in option.h. -/// @param from Pointer to target buffer/window/etc. -OptCtx option_ctx_from(OptScope scope, void *from) -{ - switch (scope) { - case kOptScopeGlobal: - return option_ctx(); - case kOptScopeBuf: - return (OptCtx){ .buf = (buf_T *)from, .win = curwin }; - case kOptScopeWin: - return (OptCtx){ .buf = ((win_T *)from)->w_buffer, .win = (win_T *)from }; - } - UNREACHABLE; -} - -/// Get pointer to option variable, depending on local or global scope. -/// -/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination). -void *get_varp_scope(vimoption_T *p, int opt_flags) +/// Get option index from option pointer +static inline OptIndex get_opt_idx(vimoption_T *opt) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { - return get_varp_scope_from(p, opt_flags, option_ctx()); + return (OptIndex)(opt - options); } /// Get pointer to option variable. static inline void *get_varp(vimoption_T *p) { - return get_varp_from(p, option_ctx()); -} - -/// Get option index from option pointer -static inline OptIndex get_opt_idx(vimoption_T *opt) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE -{ - return (OptIndex)(opt - options); + return get_varp_from(p, curbuf, curwin); } /// Get the value of 'equalprg', either the buffer-local one or the global one. @@ -5705,7 +5799,9 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c return ExpandOldSetting(numMatches, matches); } - char *option_val = *(char **)get_varp_scope(get_option(expand_option_idx), expand_option_flags); + char *option_val = *(char **)get_option_varp_scope_from(expand_option_idx, + expand_option_flags, + curbuf, curwin); uint32_t option_flags = options[expand_option_idx].flags; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b76532b1eb..832e03148a 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -81,12 +81,6 @@ typedef struct { OptValData data; } OptVal; -/// Context that an option is being set for. -typedef struct { - win_T *win; - buf_T *buf; -} OptCtx; - /// :set operator types typedef enum { OP_NONE = 0, @@ -128,7 +122,8 @@ typedef struct { /// length of the error buffer size_t os_errbuflen; - OptCtx os_ctx; + void *os_win; + void *os_buf; } optset_T; /// Type for the callback function that is invoked after an option value is @@ -197,12 +192,3 @@ typedef struct { OptVal def_val; ///< default value LastSet last_set; ///< script in which the option was last set } vimoption_T; - -/// Execute code with autocmd context -#define WITH_AUCMD_CONTEXT(ctx, code) \ - do { \ - aco_save_T _aco; \ - aucmd_prepbuf_win(&_aco, ctx.buf, ctx.win); \ - code; \ - aucmd_restbuf(&_aco); \ - } while (0) diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 632b4af573..9b7b50ae04 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -567,7 +567,7 @@ int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches) /// The 'backupcopy' option is changed. const char *did_set_backupcopy(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; const char *oldval = args->os_oldval.string.data; int opt_flags = args->os_flags; char *bkc = p_bkc; @@ -655,7 +655,7 @@ const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED) /// The 'breakindentopt' option is changed. const char *did_set_breakindentopt(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; if (briopt_check(*varp, varp == &win->w_p_briopt ? win : NULL) == FAIL) { @@ -682,7 +682,7 @@ int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matche /// The 'bufhidden' option is changed. const char *did_set_bufhidden(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; return did_set_opt_strings(buf->b_p_bh, opt_bh_values, false); } @@ -698,8 +698,8 @@ int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches) /// The 'buftype' option is changed. const char *did_set_buftype(optset_T *args) { - buf_T *buf = args->os_ctx.buf; - win_T *win = args->os_ctx.win; + buf_T *buf = (buf_T *)args->os_buf; + win_T *win = (win_T *)args->os_win; // When 'buftype' is set, check for valid value. if ((buf->terminal && buf->b_p_bt[0] != 't') || (!buf->terminal && buf->b_p_bt[0] == 't') @@ -780,7 +780,7 @@ static const char *did_set_global_chars_option(win_T *win, char *val, CharsOptio /// The 'fillchars' option or the 'listchars' option is changed. const char *did_set_chars_option(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; const char *errmsg = NULL; @@ -815,7 +815,7 @@ int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches) /// The 'cinoptions' option is changed. const char *did_set_cinoptions(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // TODO(vim): recognize errors parse_cino(buf); @@ -840,7 +840,7 @@ int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches) /// The 'colorcolumn' option is changed. const char *did_set_colorcolumn(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; return check_colorcolumn(*varp, varp == &win->w_p_cc ? win : NULL); } @@ -985,7 +985,7 @@ const char *did_set_completeitemalign(optset_T *args) /// The 'completeopt' option is changed. const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; char *cot = p_cot; unsigned *flags = &cot_flags; @@ -1021,7 +1021,7 @@ int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches) /// The 'completeslash' option is changed. const char *did_set_completeslash(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; if (check_opt_strings(p_csl, opt_csl_values, false) != OK || check_opt_strings(buf->b_p_csl, opt_csl_values, false) != OK) { return e_invarg; @@ -1068,7 +1068,7 @@ int expand_set_cpoptions(optexpand_T *args, int *numMatches, char ***matches) /// The 'cursorlineopt' option is changed. const char *did_set_cursorlineopt(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; // This could be changed to use opt_strings_flags() instead. @@ -1176,12 +1176,12 @@ int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches) /// options is changed. const char *did_set_encoding(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; char **varp = (char **)args->os_varp; int opt_flags = args->os_flags; // Get the global option to compare with, otherwise we would have to check // two values for all local options. - char **gvarp = (char **)get_varp_scope_from(get_option(args->os_idx), OPT_GLOBAL, args->os_ctx); + char **gvarp = (char **)get_option_varp_scope_from(args->os_idx, OPT_GLOBAL, buf, NULL); if (gvarp == &p_fenc) { if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) { @@ -1246,7 +1246,7 @@ int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches) /// The 'fileformat' option is changed. const char *did_set_fileformat(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; char **varp = (char **)args->os_varp; const char *oldval = args->os_oldval.string.data; int opt_flags = args->os_flags; @@ -1347,7 +1347,7 @@ int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches) /// The 'foldexpr' option is changed. const char *did_set_foldexpr(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; did_set_optexpr(args); if (foldmethodIsExpr(win)) { foldUpdateAll(win); @@ -1358,7 +1358,7 @@ const char *did_set_foldexpr(optset_T *args) /// The 'foldignore' option is changed. const char *did_set_foldignore(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; if (foldmethodIsIndent(win)) { foldUpdateAll(win); } @@ -1368,7 +1368,7 @@ const char *did_set_foldignore(optset_T *args) /// The 'foldmarker' option is changed. const char *did_set_foldmarker(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; char *p = vim_strchr(*varp, ','); @@ -1390,7 +1390,7 @@ const char *did_set_foldmarker(optset_T *args) /// The 'foldmethod' option is changed. const char *did_set_foldmethod(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; if (check_opt_strings(*varp, opt_fdm_values, false) != OK || **varp == NUL) { return e_invarg; @@ -1536,7 +1536,7 @@ const char *did_set_iskeyword(optset_T *args) /// changed. const char *did_set_isopt(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; // 'isident', 'iskeyword', 'isprint' or 'isfname' option: refill g_chartab[] // If the new option is invalid, use old value. // 'lisp' option: refill g_chartab[] for '-' char @@ -1565,7 +1565,7 @@ int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches) /// The 'keymap' option has changed. const char *did_set_keymap(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; char **varp = (char **)args->os_varp; int opt_flags = args->os_flags; @@ -2053,7 +2053,7 @@ int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches) /// The 'signcolumn' option is changed. const char *did_set_signcolumn(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; const char *oldval = args->os_oldval.string.data; if (check_signcolumn(*varp, varp == &win->w_p_scl ? win : NULL) != OK) { @@ -2079,7 +2079,7 @@ int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches) /// The 'spellcapcheck' option is changed. const char *did_set_spellcapcheck(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; // When 'spellcapcheck' is set compile the regexp program. return compile_cap_prog(win->w_s); } @@ -2113,7 +2113,7 @@ const char *did_set_spelllang(optset_T *args) /// The 'spelloptions' option is changed. const char *did_set_spelloptions(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; int opt_flags = args->os_flags; const char *val = args->os_newval.string.data; @@ -2189,7 +2189,7 @@ const char *did_set_statusline(optset_T *args) static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerformat, bool statuscolumn) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; if (rulerformat) { // reset ru_wid first ru_wid = 0; @@ -2264,7 +2264,7 @@ const char *did_set_tabline(optset_T *args) /// The 'tagcase' option is changed. const char *did_set_tagcase(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; int opt_flags = args->os_flags; unsigned *flags; @@ -2337,7 +2337,7 @@ const char *did_set_titlestring(optset_T *args) /// The 'varsofttabstop' option is changed. const char *did_set_varsofttabstop(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; char **varp = (char **)args->os_varp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { @@ -2367,8 +2367,8 @@ const char *did_set_varsofttabstop(optset_T *args) /// The 'varstabstop' option is changed. const char *did_set_vartabstop(optset_T *args) { - buf_T *buf = args->os_ctx.buf; - win_T *win = args->os_ctx.win; + buf_T *buf = (buf_T *)args->os_buf; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { @@ -2417,7 +2417,7 @@ const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED) /// The 'virtualedit' option is changed. const char *did_set_virtualedit(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char *ve = p_ve; unsigned *flags = &ve_flags; @@ -2527,7 +2527,7 @@ const char *did_set_winbar(optset_T *args) /// The 'winhighlight' option is changed. const char *did_set_winhighlight(optset_T *args) { - win_T *win = args->os_ctx.win; + win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; if (!parse_winhl_opt(*varp, varp == &win->w_p_winhl ? win : NULL)) { return e_invarg; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 9375629abc..c676b00986 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -228,7 +228,7 @@ static Callback tfu_cb; // 'tagfunc' callback function /// a function (string), or function() or funcref() or a lambda. const char *did_set_tagfunc(optset_T *args) { - buf_T *buf = args->os_ctx.buf; + buf_T *buf = (buf_T *)args->os_buf; callback_free(&tfu_cb); callback_free(&buf->b_tfu_cb); diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 78f3551087..3e791e2beb 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -411,8 +411,8 @@ win_T *win_float_create(bool enter, bool new_buf) return handle_error_and_cleanup(wp, &err); } buf->b_p_bl = false; // unlist - set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("wipe"), - option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); + set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL, 0, + kOptScopeBuf, buf); win_set_buf(wp, buf, &err); if (ERROR_SET(&err)) { return handle_error_and_cleanup(wp, &err); diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua index a68a7722a4..92e798e7f3 100644 --- a/test/functional/lua/with_spec.lua +++ b/test/functional/lua/with_spec.lua @@ -882,7 +882,11 @@ describe('vim._with', function() eq({ bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = -123456 }, wo = { ve_cur = 'insert', ve_other = 'block', winbl_cur = 25, winbl_other = 10 }, - go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 50, lmap = 'xy,yx' }, + -- Global `winbl` inside context ideally should be untouched and equal + -- to 50. It seems to be equal to 0 because `context.buf` uses + -- `aucmd_prepbuf` C approach which has no guarantees about window or + -- window option values inside context. + go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 0, lmap = 'xy,yx' }, }, out.inner) eq(out.before, out.after) end) -- cgit From 5f85e78db3aff1c685779f7506be4d658c5e9cc8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 12:49:45 +0800 Subject: vim-patch:9.1.0997: too many strlen() calls in drawscreen.c (#31927) Problem: too many strlen() calls in drawscreen.c Solution: refactor drawscreen.c and remove calls to strlen(), make get_keymap_str() (in screen.c) return string length instead of TRUE/FALSE (John Marriott). https://github.com/vim/vim/commit/a21240b97debea2e087aee6ad1488b5f075d1259 Co-authored-by: John Marriott --- src/nvim/buffer.c | 40 ++++++++++---------- src/nvim/digraph.c | 17 +++++---- src/nvim/drawscreen.c | 4 +- src/nvim/statusline.c | 103 ++++++++++++++++++++++++++------------------------ 4 files changed, 85 insertions(+), 79 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5dcf7d8f49..9e6877cbfa 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3257,8 +3257,8 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate) n); validate_virtcol(curwin); size_t len = strlen(buffer); - col_print(buffer + len, IOSIZE - len, - (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); + (void)col_print(buffer + len, IOSIZE - len, + (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); } append_arg_number(curwin, buffer, IOSIZE); @@ -3286,13 +3286,13 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate) xfree(buffer); } -void col_print(char *buf, size_t buflen, int col, int vcol) +int col_print(char *buf, size_t buflen, int col, int vcol) { if (col == vcol) { - vim_snprintf(buf, buflen, "%d", col); - } else { - vim_snprintf(buf, buflen, "%d-%d", col, vcol); + return vim_snprintf(buf, buflen, "%d", col); } + + return vim_snprintf(buf, buflen, "%d-%d", col, vcol); } static char *lasttitle = NULL; @@ -3416,15 +3416,16 @@ void free_titles(void) /// Get relative cursor position in window into "buf[buflen]", in the localized /// percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate. -void get_rel_pos(win_T *wp, char *buf, int buflen) +int get_rel_pos(win_T *wp, char *buf, int buflen) { // Need at least 3 chars for writing. if (buflen < 3) { - return; + return 0; } linenr_T above; // number of lines above window linenr_T below; // number of lines below window + int len; above = wp->w_topline - 1; above += win_get_fill(wp, wp->w_topline) - wp->w_topfill; @@ -3435,25 +3436,24 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) } below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; if (below <= 0) { - xstrlcpy(buf, (above == 0 ? _("All") : _("Bot")), (size_t)buflen); + len = vim_snprintf(buf, (size_t)buflen, "%s", (above == 0) ? _("All") : _("Bot")); } else if (above <= 0) { - xstrlcpy(buf, _("Top"), (size_t)buflen); + len = vim_snprintf(buf, (size_t)buflen, "%s", _("Top")); } else { int perc = (above > 1000000 ? (above / ((above + below) / 100)) : (above * 100 / (above + below))); - - char *p = buf; - size_t l = (size_t)buflen; - if (perc < 10) { - // prepend one space - buf[0] = ' '; - p++; - l--; - } // localized percentage value - vim_snprintf(p, l, _("%d%%"), perc); + len = vim_snprintf(buf, (size_t)buflen, _("%s%d%%"), (perc < 10) ? " " : "", perc); } + if (len < 0) { + buf[0] = NUL; + len = 0; + } else if (len > buflen - 1) { + len = buflen - 1; + } + + return len; } /// Append (2 of 8) to "buf[buflen]", if editing more than one file. diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index aaa77f5fcf..326e929fb6 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -2183,22 +2183,22 @@ static void keymap_unload(void) /// @param fmt format string containing one %s item /// @param buf buffer for the result /// @param len length of buffer -bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) +int get_keymap_str(win_T *wp, char *fmt, char *buf, int len) { char *p; if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) { - return false; + return 0; } buf_T *old_curbuf = curbuf; win_T *old_curwin = curwin; + char to_evaluate[] = "b:keymap_name"; curbuf = wp->w_buffer; curwin = wp; - STRCPY(buf, "b:keymap_name"); // must be writable emsg_skip++; - char *s = p = eval_to_string(buf, false, false); + char *s = p = eval_to_string(to_evaluate, false, false); emsg_skip--; curbuf = old_curbuf; curwin = old_curwin; @@ -2209,9 +2209,12 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) p = "lang"; } } - if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) { + int plen = vim_snprintf(buf, (size_t)len, fmt, p); + xfree(s); + if (plen < 0 || plen > len - 1) { buf[0] = NUL; + plen = 0; } - xfree(s); - return buf[0] != NUL; + + return plen; } diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index f8aeb02229..7f4de9eab8 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1045,7 +1045,7 @@ int showmode(void) if (State & MODE_LANGMAP) { if (curwin->w_p_arab) { msg_puts_hl(_(" Arabic"), hl_id, false); - } else if (get_keymap_str(curwin, " (%s)", NameBuff, MAXPATHL)) { + } else if (get_keymap_str(curwin, " (%s)", NameBuff, MAXPATHL) > 0) { msg_puts_hl(NameBuff, hl_id, false); } } @@ -1936,7 +1936,7 @@ static void win_update(win_T *wp) pos.lnum += cursor_above ? 1 : -1) { colnr_T t; - pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum)); + pos.col = ml_get_buf_len(wp->w_buffer, pos.lnum); getvvcol(wp, &pos, NULL, NULL, &t); toc = MAX(toc, t); } diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index d47340505a..fe6892cc27 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -96,53 +96,51 @@ void win_redr_status(win_T *wp) get_trans_bufname(wp->w_buffer); char *p = NameBuff; - int len = (int)strlen(p); + int plen = (int)strlen(p); if ((bt_help(wp->w_buffer) || wp->w_p_pvw || bufIsChanged(wp->w_buffer) || wp->w_buffer->b_p_ro) - && len < MAXPATHL - 1) { - *(p + len++) = ' '; + && plen < MAXPATHL - 1) { + *(p + plen++) = ' '; } if (bt_help(wp->w_buffer)) { - snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); - len += (int)strlen(p + len); + plen += snprintf(p + plen, MAXPATHL - (size_t)plen, "%s", _("[Help]")); } if (wp->w_p_pvw) { - snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); - len += (int)strlen(p + len); + plen += snprintf(p + plen, MAXPATHL - (size_t)plen, "%s", _("[Preview]")); } if (bufIsChanged(wp->w_buffer)) { - snprintf(p + len, MAXPATHL - (size_t)len, "%s", "[+]"); - len += (int)strlen(p + len); + plen += snprintf(p + plen, MAXPATHL - (size_t)plen, "%s", "[+]"); } if (wp->w_buffer->b_p_ro) { - snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); - // len += (int)strlen(p + len); // dead assignment + plen += snprintf(p + plen, MAXPATHL - (size_t)plen, "%s", _("[RO]")); } + (void)plen; - int this_ru_col = MAX(ru_col - (Columns - stl_width), (stl_width + 1) / 2); + int n = (stl_width + 1) / 2; + int this_ru_col = ru_col - (Columns - stl_width); + this_ru_col = MAX(this_ru_col, n); if (this_ru_col <= 1) { p = "<"; // No room for file name! - len = 1; + plen = 1; } else { int i; // Count total number of display cells. - int clen = (int)mb_string2cells(p); + plen = (int)mb_string2cells(p); // Find first character that will fit. // Going from start to end is much faster for DBCS. - for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; + for (i = 0; p[i] != NUL && plen >= this_ru_col - 1; i += utfc_ptr2len(p + i)) { - clen -= utf_ptr2cells(p + i); + plen -= utf_ptr2cells(p + i); } - len = clen; if (i > 0) { p = p + i - 1; *p = '<'; - len++; + plen++; } } @@ -152,16 +150,17 @@ void win_redr_status(win_T *wp) int width = grid_line_puts(off, p, -1, attr); grid_line_fill(off + width, off + this_ru_col, fillchar, attr); - if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL) - && this_ru_col - len > (int)strlen(NameBuff) + 1) { - grid_line_puts(off + this_ru_col - (int)strlen(NameBuff) - 1, NameBuff, -1, attr); + int NameBufflen = get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL); + if (NameBufflen > 0 && this_ru_col - plen > NameBufflen + 1) { + grid_line_puts(off + this_ru_col - NameBufflen - 1, NameBuff, -1, attr); } win_redr_ruler(wp); // Draw the 'showcmd' information if 'showcmdloc' == "statusline". if (p_sc && *p_sloc == 's') { - const int sc_width = MIN(10, this_ru_col - len - 2); + n = this_ru_col - plen - 2; // perform the calculation here so we only do it once + const int sc_width = MIN(10, n); if (sc_width > 0) { grid_line_puts(off + this_ru_col - sc_width - 1, showcmd_buf, sc_width, attr); @@ -548,36 +547,40 @@ void win_redr_ruler(win_T *wp) #define RULER_BUF_LEN 70 char buffer[RULER_BUF_LEN]; - // Some sprintfs return the length, some return a pointer. - // To avoid portability problems we use strlen() here. - vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", - (wp->w_buffer->b_ml.ml_flags & - ML_EMPTY) ? 0 : (int64_t)wp->w_cursor.lnum); - size_t len = strlen(buffer); - col_print(buffer + len, RULER_BUF_LEN - len, - empty_line ? 0 : (int)wp->w_cursor.col + 1, - (int)virtcol + 1); + int bufferlen = vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", + (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0 + : (int64_t)wp->w_cursor.lnum); + bufferlen += col_print(buffer + bufferlen, RULER_BUF_LEN - (size_t)bufferlen, + empty_line ? 0 : (int)wp->w_cursor.col + 1, + (int)virtcol + 1); // Add a "50%" if there is room for it. // On the last line, don't print in the last column (scrolls the // screen up on some terminals). - int i = (int)strlen(buffer); - get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); - int o = i + vim_strsize(buffer + i + 1); + char rel_pos[RULER_BUF_LEN]; + int rel_poslen = get_rel_pos(wp, rel_pos, RULER_BUF_LEN); + int n1 = bufferlen + vim_strsize(rel_pos); if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen - o++; + n1++; } + + int this_ru_col = ru_col - (Columns - width); // Never use more than half the window/screen width, leave the other half // for the filename. - int this_ru_col = MAX(ru_col - (Columns - width), (width + 1) / 2); - if (this_ru_col + o < width) { - // Need at least 3 chars left for get_rel_pos() + NUL. - while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { - i += (int)schar_get(buffer + i, fillchar); - o++; - } - get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); + int n2 = (width + 1) / 2; + this_ru_col = MAX(this_ru_col, n2); + if (this_ru_col + n1 < width) { + // need at least space for rel_pos + NUL + while (this_ru_col + n1 < width + && RULER_BUF_LEN > bufferlen + rel_poslen + 1) { // +1 for NUL + bufferlen += (int)schar_get(buffer + bufferlen, fillchar); + n1++; + } + bufferlen += vim_snprintf(buffer + bufferlen, RULER_BUF_LEN - (size_t)bufferlen, + "%s", rel_pos); } + (void)bufferlen; if (ui_has(kUIMessages) && !part_of_status) { MAXSIZE_TEMP_ARRAY(content, 1); @@ -595,11 +598,11 @@ void win_redr_ruler(win_T *wp) did_show_ext_ruler = false; } // Truncate at window boundary. - o = 0; - for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { - o += utf_ptr2cells(buffer + i); - if (this_ru_col + o > width) { - buffer[i] = NUL; + for (n1 = 0, n2 = 0; buffer[n1] != NUL; n1 += utfc_ptr2len(buffer + n1)) { + n2 += utf_ptr2cells(buffer + n1); + if (this_ru_col + n2 > width) { + bufferlen = n1; + buffer[bufferlen] = NUL; break; } } @@ -1536,7 +1539,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op // Store the position percentage in our temporary buffer. // Note: We cannot store the value in `num` because // `get_rel_pos` can return a named position. Ex: "Top" - get_rel_pos(wp, buf_tmp, TMPLEN); + (void)get_rel_pos(wp, buf_tmp, TMPLEN); str = buf_tmp; break; @@ -1564,7 +1567,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op case STL_KEYMAP: fillable = false; - if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) { + if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN) > 0) { str = buf_tmp; } break; -- cgit From d740a4274d9e1031e05dd86909103dba54fbbaf8 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 8 Jan 2025 20:07:21 +0100 Subject: vim-patch:8ab1819: runtime(xf86conf): add section name OutputClass to syntax script References: https://man.archlinux.org/man/xorg.conf.5#DESCRIPTION closes: vim/vim#16397 https://github.com/vim/vim/commit/8ab1819df625354f6cc9b36cb46989e7b7c9ebae Co-authored-by: Jan-Arvid Harrach --- runtime/syntax/xf86conf.vim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/syntax/xf86conf.vim b/runtime/syntax/xf86conf.vim index e8162f3a35..0f4e5036ff 100644 --- a/runtime/syntax/xf86conf.vim +++ b/runtime/syntax/xf86conf.vim @@ -1,9 +1,9 @@ " Vim syntax file " Language: XF86Config (XFree86 configuration file) +" Maintainer: This runtime file is looking for a new maintainer. +" Last Change: 2025 Jan 06 by Jan-Arvid Harrach (#16397) " Former Maintainer: David Ne\v{c}as (Yeti) " Last Change By David: 2010 Nov 01 -" Last Change: 2023 Jan 23 -" Required Vim Version: 6.0 " " Options: let xf86conf_xfree86_version = 3 or 4 " to force XFree86 3.x or 4.x XF86Config syntax @@ -58,7 +58,7 @@ syn match xf86confModeLineValue "\"[^\"]\+\"\(\_s\+[0-9.]\+\)\{9}" nextgroup=xf8 " Sections and subsections if b:xf86conf_xfree86_version >= 4 - syn region xf86confSection matchgroup=xf86confSectionDelim start="^\s*Section\s\+\"\(Files\|Server[_ ]*Flags\|Input[_ ]*Device\|Device\|Video[_ ]*Adaptor\|Server[_ ]*Layout\|DRI\|Extensions\|Vendor\|Keyboard\|Pointer\|InputClass\)\"" end="^\s*EndSection\>" skip="#.*$\|\"[^\"]*\"" contains=xf86confComment,xf86confOption,xf86confKeyword,xf86confSectionError + syn region xf86confSection matchgroup=xf86confSectionDelim start="^\s*Section\s\+\"\(Files\|Server[_ ]*Flags\|Input[_ ]*Device\|Device\|Video[_ ]*Adaptor\|Server[_ ]*Layout\|DRI\|Extensions\|Vendor\|Keyboard\|Pointer\|InputClass\|OutputClass\)\"" end="^\s*EndSection\>" skip="#.*$\|\"[^\"]*\"" contains=xf86confComment,xf86confOption,xf86confKeyword,xf86confSectionError syn region xf86confSectionModule matchgroup=xf86confSectionDelim start="^\s*Section\s\+\"Module\"" end="^\s*EndSection\>" skip="#.*$\|\"[^\"]*\"" contains=xf86confSubsectionAny,xf86confComment,xf86confOption,xf86confKeyword syn region xf86confSectionMonitor matchgroup=xf86confSectionDelim start="^\s*Section\s\+\"Monitor\"" end="^\s*EndSection\>" skip="#.*$\|\"[^\"]*\"" contains=xf86confSubsectionMode,xf86confModeLine,xf86confComment,xf86confOption,xf86confKeyword syn region xf86confSectionModes matchgroup=xf86confSectionDelim start="^\s*Section\s\+\"Modes\"" end="^\s*EndSection\>" skip="#.*$\|\"[^\"]*\"" contains=xf86confSubsectionMode,xf86confModeLine,xf86confComment @@ -162,7 +162,7 @@ syn match xf86confSync "\(\s\+[+-][CHV]_*Sync\)\+" contained " Synchronization if b:xf86conf_xfree86_version >= 4 - syn sync match xf86confSyncSection grouphere xf86confSection "^\s*Section\s\+\"\(Files\|Server[_ ]*Flags\|Input[_ ]*Device\|Device\|Video[_ ]*Adaptor\|Server[_ ]*Layout\|DRI\|Extensions\|Vendor\|Keyboard\|Pointer\|InputClass\)\"" + syn sync match xf86confSyncSection grouphere xf86confSection "^\s*Section\s\+\"\(Files\|Server[_ ]*Flags\|Input[_ ]*Device\|Device\|Video[_ ]*Adaptor\|Server[_ ]*Layout\|DRI\|Extensions\|Vendor\|Keyboard\|Pointer\|InputClass\|OutputClass\)\"" syn sync match xf86confSyncSectionModule grouphere xf86confSectionModule "^\s*Section\s\+\"Module\"" syn sync match xf86confSyncSectionModes groupthere xf86confSectionModes "^\s*Section\s\+\"Modes\"" else -- cgit From 3f0adf90debb35b5a937480151a659d654106ff6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 17:30:26 +0800 Subject: vim-patch:9.1.0998: filetype: TI assembly files are not recognized (#31929) Problem: filetype: TI assembly files are not recognized Solution: inspect '*.sa' and assembly files and detect TI assembly files, include filetype plugin and syntax script for TI assembly files (Wu, Zhenyu) closes: vim/vim#15827 https://github.com/vim/vim/commit/4f73c07abff420bad9fa5befc2c284c00b984993 Co-authored-by: Wu, Zhenyu --- runtime/ftplugin/tiasm.vim | 18 +++++++ runtime/lua/vim/filetype.lua | 2 +- runtime/lua/vim/filetype/detect.lua | 15 ++++++ runtime/syntax/tiasm.vim | 102 ++++++++++++++++++++++++++++++++++++ test/old/testdir/test_filetype.vim | 17 +++++- 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 runtime/ftplugin/tiasm.vim create mode 100644 runtime/syntax/tiasm.vim diff --git a/runtime/ftplugin/tiasm.vim b/runtime/ftplugin/tiasm.vim new file mode 100644 index 0000000000..13a3dc64f7 --- /dev/null +++ b/runtime/ftplugin/tiasm.vim @@ -0,0 +1,18 @@ +" Vim filetype plugin file +" Language: TI linear assembly language +" Maintainer: Wu, Zhenyu +" Last Change: 2025 Jan 08 + +if exists("b:did_ftplugin") | finish | endif +let b:did_ftplugin = 1 + +setlocal comments=:; +setlocal commentstring=;\ %s + +let b:undo_ftplugin = "setl commentstring< comments<" + +if exists("loaded_matchit") + let b:match_words = '^\s\+\.if\>:^\s\+\.elseif:^\s\+\.else\>:^\s\+\.endif\>,^\s\+\.group:^\s\+\.gmember:^\s\+\.endgroup,^\s\+\.loop:^\s\+\.break:^\s\+\.endloop,^\s\+\.macro:^\s\+\.mexit:^\s\+\.endm,^\s\+\.asmfunc:^\s\+\.endasmfunc,^\s\+\.c\?struct:^\s\+\.endstruct,^\s\+\.c\?union:^\s\+\.endunion,^\s\+\.c\?proc:^\s\+\.return:^\s\+\.endproc' + let b:match_ignorecase = 1 + let b:undo_ftplugin ..= " | unlet! b:match_ignorecase b:match_words" +endif diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 033a04cdc7..dee1bd88ca 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1064,11 +1064,11 @@ local extension = { builder = 'ruby', rake = 'ruby', rs = 'rust', + sa = detect.sa, sage = 'sage', sls = 'salt', sas = 'sas', sass = 'sass', - sa = 'sather', sbt = 'sbt', scala = 'scala', ss = 'scheme', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index a1c17bc1af..2d989fdbac 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -34,6 +34,12 @@ local matchregex = vim.filetype._matchregex -- can be detected from the first five lines of the file. --- @type vim.filetype.mapfn function M.asm(path, bufnr) + -- tiasm uses `* commment` + local lines = table.concat(getlines(bufnr, 1, 10), '\n') + if findany(lines, { '^%*', '\n%*', 'Texas Instruments Incorporated' }) then + return 'tiasm' + end + local syntax = vim.b[bufnr].asmsyntax if not syntax or syntax == '' then syntax = M.asm_syntax(path, bufnr) @@ -1424,6 +1430,15 @@ function M.sig(_, bufnr) end end +--- @type vim.filetype.mapfn +function M.sa(_, bufnr) + local lines = table.concat(getlines(bufnr, 1, 4), '\n') + if findany(lines, { '^;', '\n;' }) then + return 'tiasm' + end + return 'sather' +end + -- This function checks the first 25 lines of file extension "sc" to resolve -- detection between scala and SuperCollider --- @type vim.filetype.mapfn diff --git a/runtime/syntax/tiasm.vim b/runtime/syntax/tiasm.vim new file mode 100644 index 0000000000..bdadc4a0a7 --- /dev/null +++ b/runtime/syntax/tiasm.vim @@ -0,0 +1,102 @@ +" Vim syntax file +" Language: TI linear assembly language +" Document: https://downloads.ti.com/docs/esd/SPRUI03B/#SPRUI03B_HTML/assembler-description.html +" Maintainer: Wu, Zhenyu +" Last Change: 2025 Jan 08 + +if exists("b:current_syntax") + finish +endif + +syn case ignore + +" storage types +syn match tiasmType "\.bits" +syn match tiasmType "\.byte" +syn match tiasmType "\.char" +syn match tiasmType "\.cstring" +syn match tiasmType "\.double" +syn match tiasmType "\.field" +syn match tiasmType "\.float" +syn match tiasmType "\.half" +syn match tiasmType "\.int" +syn match tiasmType "\.long" +syn match tiasmType "\.short" +syn match tiasmType "\.string" +syn match tiasmType "\.ubyte" +syn match tiasmType "\.uchar" +syn match tiasmType "\.uhalf" +syn match tiasmType "\.uint" +syn match tiasmType "\.ulong" +syn match tiasmType "\.ushort" +syn match tiasmType "\.uword" +syn match tiasmType "\.word" + +syn match tiasmIdentifier "[a-z_][a-z0-9_]*" + +syn match tiasmDecimal "\<[1-9]\d*\>" display +syn match tiasmOctal "\<0[0-7][0-7]\+\>\|\<[0-7]\+[oO]\>" display +syn match tiasmHexadecimal "\<0[xX][0-9a-fA-F]\+\>\|\<[0-9][0-9a-fA-F]*[hH]\>" display +syn match tiasmBinary "\<0[bB][0-1]\+\>\|\<[01]\+[bB]\>" display + +syn match tiasmFloat "\<\d\+\.\d*\%(e[+-]\=\d\+\)\=\>" display +syn match tiasmFloat "\<\d\%(e[+-]\=\d\+\)\>" display + +syn match tiasmCharacter "'.'\|''\|'[^']'" + +syn region tiasmString start="\"" end="\"" skip="\"\"" + +syn match tiasmFunction "\$[a-zA-Z_][a-zA-Z_0-9]*\ze(" + +syn keyword tiasmTodo contained TODO FIXME XXX NOTE +syn region tiasmComment start=";" end="$" keepend contains=tiasmTodo,@Spell +syn match tiasmComment "^[*!].*" contains=tiasmTodo,@Spell +syn match tiasmLabel "^[^ *!;][^ :]*" + +syn match tiasmInclude "\.include" +syn match tiasmCond "\.if" +syn match tiasmCond "\.else" +syn match tiasmCond "\.endif" +syn match tiasmMacro "\.macro" +syn match tiasmMacro "\.endm" + +syn match tiasmDirective "\.[A-Za-z][0-9A-Za-z-_]*" + +syn case match + +hi def link tiasmLabel Label +hi def link tiasmComment Comment +hi def link tiasmTodo Todo +hi def link tiasmDirective Statement + +hi def link tiasmInclude Include +hi def link tiasmCond PreCondit +hi def link tiasmMacro Macro + +if exists('g:tiasm_legacy_syntax_groups') + hi def link hexNumber Number + hi def link decNumber Number + hi def link octNumber Number + hi def link binNumber Number + hi def link tiasmHexadecimal hexNumber + hi def link tiasmDecimal decNumber + hi def link tiasmOctal octNumber + hi def link tiasmBinary binNumber +else + hi def link tiasmHexadecimal Number + hi def link tiasmDecimal Number + hi def link tiasmOctal Number + hi def link tiasmBinary Number +endif +hi def link tiasmFloat Float + +hi def link tiasmString String +hi def link tiasmStringEscape Special +hi def link tiasmCharacter Character +hi def link tiasmCharacterEscape Special + +hi def link tiasmIdentifier Identifier +hi def link tiasmType Type +hi def link tiasmFunction Function + +let b:current_syntax = "lineartiasm" diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 6c8ab3a270..d890884eb5 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -672,7 +672,6 @@ func s:GetFilenameChecks() abort \ 'samba': ['smb.conf'], \ 'sas': ['file.sas'], \ 'sass': ['file.sass'], - \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], \ 'scala': ['file.scala'], \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config', '.lips_repl_history'], @@ -2322,6 +2321,22 @@ func Test_cmd_file() filetype off endfunc +func Test_sa_file() + filetype on + + call writefile([';* XXX-a.sa: XXX for TI C6000 DSP *;', '.no_mdep'], 'Xfile.sa') + split Xfile.sa + call assert_equal('tiasm', &filetype) + bwipe! + + call writefile(['-- comment'], 'Xfile.sa') + split Xfile.sa + call assert_equal('sather', &filetype) + bwipe! + + filetype off +endfunc + func Test_sig_file() filetype on -- cgit From 33ff546b50f759bd49d9518a94f8c2416848bdd7 Mon Sep 17 00:00:00 2001 From: bfredl Date: Fri, 3 Jan 2025 12:36:36 +0100 Subject: fix(decoration): fix crash when on_lines decor provider modifies marktree If a "on_lines" callback changes the structure of the marktree, the iterator (which is used for an entire window viewport) might now point to invalid memory. Restore the iterator to the beginning of the line in this case. fixes #29484 --- src/nvim/decoration.c | 18 ++++++++++++++++++ src/nvim/decoration.h | 1 + src/nvim/decoration_provider.c | 6 ++---- src/nvim/drawline.c | 6 +++--- src/nvim/extmark.c | 7 +++++++ test/functional/ui/decorations_spec.lua | 24 ++++++++++++++++++++++++ 6 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 728a917c22..a9f3ba0c3b 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -303,12 +303,24 @@ static void decor_free_inner(DecorVirtText *vt, uint32_t first_idx) } } +/// Check if we are in a callback while drawing, which might invalidate the marktree iterator. +/// +/// This should be called whenever a structural modification has been done to a +/// marktree in a public API function (i e any change which adds or deletes marks). +void decor_state_invalidate(buf_T *buf) +{ + if (decor_state.win && decor_state.win->w_buffer == buf) { + decor_state.itr_valid = false; + } +} + void decor_check_to_be_deleted(void) { assert(!decor_state.running_decor_provider); decor_free_inner(to_free_virt, to_free_sh); to_free_virt = NULL; to_free_sh = DECOR_ID_INVALID; + decor_state.win = NULL; } void decor_state_free(DecorState *state) @@ -447,6 +459,8 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) { buf_T *buf = wp->w_buffer; state->top_row = top_row; + state->itr_valid = true; + if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) { return false; } @@ -489,7 +503,11 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state) if (state->row == -1) { decor_redraw_start(wp, row, state); + } else if (!state->itr_valid) { + marktree_itr_get(wp->w_buffer->b_marktree, row, 0, state->itr); + state->itr_valid = true; } + state->row = row; state->col_until = -1; state->eol_col = -1; diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 1d268c982b..a2f4fefd45 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -96,6 +96,7 @@ typedef struct { TriState spell; bool running_decor_provider; + bool itr_valid; } DecorState; EXTERN DecorState decor_state INIT( = { 0 }); diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index e5d2658720..7c99fbf889 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -155,7 +155,7 @@ void decor_providers_invoke_win(win_T *wp) /// @param row Row to invoke line callback for /// @param[out] has_decor Set when at least one provider invokes a line callback /// @param[out] err Provider error -void decor_providers_invoke_line(win_T *wp, int row, bool *has_decor) +void decor_providers_invoke_line(win_T *wp, int row) { decor_state.running_decor_provider = true; for (size_t i = 0; i < kv_size(decor_providers); i++) { @@ -165,9 +165,7 @@ void decor_providers_invoke_line(win_T *wp, int row, bool *has_decor) ADD_C(args, WINDOW_OBJ(wp->handle)); ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle)); ADD_C(args, INTEGER_OBJ(row)); - if (decor_provider_invoke((int)i, "line", p->redraw_line, args, true)) { - *has_decor = true; - } else { + if (!decor_provider_invoke((int)i, "line", p->redraw_line, args, true)) { // return 'false' or error: skip rest of this window kv_A(decor_providers, i).state = kDecorProviderWinDisabled; } diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 3062b0f2a3..d5273ff3d1 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1051,12 +1051,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } } - has_decor = decor_redraw_line(wp, lnum - 1, &decor_state); - if (!end_fill) { - decor_providers_invoke_line(wp, lnum - 1, &has_decor); + decor_providers_invoke_line(wp, lnum - 1); } + has_decor = decor_redraw_line(wp, lnum - 1, &decor_state); + if (has_decor) { extra_check = true; } diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 6119d838f9..79eea718f4 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -95,6 +95,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col MTKey mark = { { row, col }, ns_id, id, flags, decor.data }; marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity); + decor_state_invalidate(buf); revised: if (decor_flags || decor.ext) { @@ -184,6 +185,8 @@ void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore) } } + decor_state_invalidate(buf); + // TODO(bfredl): delete it from current undo header, opportunistically? } @@ -237,6 +240,10 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r } } + if (marks_cleared_any) { + decor_state_invalidate(buf); + } + return marks_cleared_any; } diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index da0fb9849a..e364c473b7 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -744,6 +744,30 @@ describe('decorations providers', function() ]]) eq(2, exec_lua([[return _G.cnt]])) end) + + it('can do large changes to the marktree', function() + insert("line1 with a lot of text\nline2 with a lot of text") + setup_provider([[ + function on_do(event, _, _, row) + if event == 'win' or (event == 'line' and row == 1) then + vim.api.nvim_buf_clear_namespace(0, ns1, 0, -1) + for i = 0,1 do + for j = 0,23 do + vim.api.nvim_buf_set_extmark(0, ns1, i, j, {hl_group='ErrorMsg', end_col = j+1}) + end + end + end + end + ]]) + + -- Doesn't crash when modifying the marktree between line1 and line2 + screen:expect([[ + {2:line1 with a lot of text} | + {2:line2 with a lot of tex^t} | + {1:~ }|*5 + | + ]]) + end) end) local example_text = [[ -- cgit From f8c8a245aa5bdfc2092f7e910a2d4ce798cd188e Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 7 Jan 2025 11:16:53 +0100 Subject: fix(terminal): don't crash on unprintable chars fixes #31897 --- src/nvim/vterm/screen.c | 2 +- test/functional/terminal/buffer_spec.lua | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/nvim/vterm/screen.c b/src/nvim/vterm/screen.c index f24e47e543..c91c6fb84f 100644 --- a/src/nvim/vterm/screen.c +++ b/src/nvim/vterm/screen.c @@ -909,7 +909,7 @@ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCe return 0; } - cell->schar = intcell->schar; + cell->schar = (intcell->schar == (uint32_t)-1) ? 0 : intcell->schar; cell->attrs.bold = intcell->pen.bold; cell->attrs.underline = intcell->pen.underline; diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index b6de687af9..cc807ba555 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -435,6 +435,19 @@ describe(':terminal buffer', function() ]]) end) + it('handles unprintable chars', function() + local screen = Screen.new(50, 7) + feed 'i' + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '\239\187\191') -- '\xef\xbb\xbf' + screen:expect([[ + {18:}^ | + |*5 + {5:-- TERMINAL --} | + ]]) + eq('\239\187\191', api.nvim_get_current_line()) + end) + it("handles bell respecting 'belloff' and 'visualbell'", function() local screen = Screen.new(50, 7) local chan = api.nvim_open_term(0, {}) -- cgit From ead5683ff9994c0fbfc6c38e0911d9455777550b Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 7 Jan 2025 14:20:45 +0100 Subject: feat(api): add err field to nvim_echo() opts Problem: We want to deprecate `nvim_err_write(ln)()` but there is no obvious replacement (from Lua). Meanwhile we already have `nvim_echo()` with an `opts` argument. Solution: Add `err` argument to `nvim_echo()` that directly maps to `:echoerr`. --- runtime/doc/api.txt | 2 ++ runtime/doc/news.txt | 2 ++ runtime/lua/vim/_meta/api.lua | 2 ++ runtime/lua/vim/_meta/api_keysets.lua | 1 + src/nvim/api/keysets_defs.h | 1 + src/nvim/api/private/helpers.c | 4 +-- src/nvim/api/vim.c | 6 ++-- src/nvim/eval.c | 3 +- src/nvim/ex_docmd.c | 2 +- src/nvim/lua/executor.c | 2 +- src/nvim/message.c | 55 +++++++++++++++++++++++------------ test/functional/api/vim_spec.lua | 24 +++++++++++++++ test/functional/ui/messages_spec.lua | 15 ++++++++++ 13 files changed, 92 insertions(+), 27 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index c8e0dcd0c5..514479b8ba 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -662,6 +662,8 @@ nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* `hl_group` element can be omitted for no highlight. • {history} if true, add to |message-history|. • {opts} Optional parameters. + • err: Treat the message like |:echoerr|. Omitted `hlgroup` + uses |hl-ErrorMsg| instead. • verbose: Message is printed as a result of 'verbose' option. If Nvim was invoked with -V3log_file, the message will be redirected to the log_file and suppressed from diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b7606e65f5..cb7916d0e9 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -178,6 +178,8 @@ API • Improved API "meta" docstrings and :help documentation. • |nvim__ns_set()| can set properties for a namespace +• |nvim_echo()| `err` field to print error messages and `chunks` accepts + highlight group IDs. DEFAULTS diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 7297c8ad38..b0651efca4 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1104,6 +1104,8 @@ function vim.api.nvim_del_var(name) end --- `hl_group` element can be omitted for no highlight. --- @param history boolean if true, add to `message-history`. --- @param opts vim.api.keyset.echo_opts Optional parameters. +--- - err: Treat the message like `:echoerr`. Omitted `hlgroup` +--- uses `hl-ErrorMsg` instead. --- - verbose: Message is printed as a result of 'verbose' option. --- If Nvim was invoked with -V3log_file, the message will be --- redirected to the log_file and suppressed from direct output. diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index e11dddb2d3..1baae2fd71 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -88,6 +88,7 @@ error('Cannot require a meta file') --- @field pattern? string|string[] --- @class vim.api.keyset.echo_opts +--- @field err? boolean --- @field verbose? boolean --- @class vim.api.keyset.empty diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 48f5f7246c..17287e083b 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -325,6 +325,7 @@ typedef struct { } Dict(cmd_opts); typedef struct { + Boolean err; Boolean verbose; } Dict(echo_opts); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 78aa7c00f7..c98635f8fd 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -776,7 +776,7 @@ char *api_typename(ObjectType t) UNREACHABLE; } -HlMessage parse_hl_msg(Array chunks, Error *err) +HlMessage parse_hl_msg(Array chunks, bool is_err, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; for (size_t i = 0; i < chunks.size; i++) { @@ -791,7 +791,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String str = copy_string(chunk.items[0].data.string, NULL); - int hl_id = 0; + int hl_id = is_err ? HLF_E : 0; if (chunk.size == 2) { hl_id = object_to_hl_id(chunk.items[1], "text highlight", err); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e3e69f4ff6..f0848b7e04 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -775,13 +775,15 @@ void nvim_set_vvar(String name, Object value, Error *err) /// `hl_group` element can be omitted for no highlight. /// @param history if true, add to |message-history|. /// @param opts Optional parameters. +/// - err: Treat the message like |:echoerr|. Omitted `hlgroup` +/// uses |hl-ErrorMsg| instead. /// - verbose: Message is printed as a result of 'verbose' option. /// If Nvim was invoked with -V3log_file, the message will be /// redirected to the log_file and suppressed from direct output. void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { - HlMessage hl_msg = parse_hl_msg(chunks, err); + HlMessage hl_msg = parse_hl_msg(chunks, opts->err, err); if (ERROR_SET(err)) { goto error; } @@ -790,7 +792,7 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) verbose_enter(); } - msg_multihl(hl_msg, history ? "echomsg" : "echo", history); + msg_multihl(hl_msg, opts->err ? "echoerr" : history ? "echomsg" : "echo", history, opts->err); if (opts->verbose) { verbose_leave(); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a90f275713..5b91f1248f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7968,8 +7968,7 @@ void ex_execute(exarg_T *eap) } else if (eap->cmdidx == CMD_echoerr) { // We don't want to abort following commands, restore did_emsg. int save_did_emsg = did_emsg; - msg_ext_set_kind("echoerr"); - emsg_multiline(ga.ga_data, true); + emsg_multiline(ga.ga_data, "echoerr", HLF_E, true); if (!force_abort) { did_emsg = save_did_emsg; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6f9f0f07c9..137226e2ad 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -982,7 +982,7 @@ void handle_did_throw(void) if (messages != NULL) { do { msglist_T *next = messages->next; - emsg_multiline(messages->msg, messages->multiline); + emsg_multiline(messages->msg, "emsg", HLF_E, messages->multiline); xfree(messages->msg); xfree(messages->sfile); xfree(messages); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 0a412b4ca9..68d3af6074 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -958,7 +958,7 @@ static void nlua_print_event(void **argv) HlMessage msg = KV_INITIAL_VALUE; HlMessageChunk chunk = { { .data = argv[0], .size = (size_t)(intptr_t)argv[1] - 1 }, 0 }; kv_push(msg, chunk); - msg_multihl(msg, "lua_print", true); + msg_multihl(msg, "lua_print", true, false); } /// Print as a Vim message diff --git a/src/nvim/message.c b/src/nvim/message.c index d45bc147cb..e288353ddc 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -293,20 +293,31 @@ void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_ } } -void msg_multihl(HlMessage hl_msg, const char *kind, bool history) +// Avoid starting a new message for each chunk and adding message to history in msg_keep(). +static bool is_multihl = false; + +void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) { no_wait_return++; msg_start(); msg_clr_eos(); bool need_clear = false; + msg_ext_history = history; msg_ext_set_kind(kind); + is_multihl = true; for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); - msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear); + if (err) { + emsg_multiline(chunk.text.data, kind, chunk.hl_id, true); + } else { + msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear); + } + assert(msg_ext_kind == kind); } if (history && kv_size(hl_msg)) { add_msg_hist_multihl(NULL, 0, 0, true, hl_msg); } + is_multihl = false; no_wait_return--; msg_end(); } @@ -342,18 +353,19 @@ bool msg_keep(const char *s, int hl_id, bool keep, bool multiline) } entered++; - // Add message to history (unless it's a repeated kept message or a - // truncated message) - if (s != keep_msg - || (*s != '<' - && last_msg_hist != NULL - && last_msg_hist->msg != NULL - && strcmp(s, last_msg_hist->msg) != 0)) { + // Add message to history (unless it's a truncated, repeated kept or multihl message). + if ((s != keep_msg + || (*s != '<' + && last_msg_hist != NULL + && last_msg_hist->msg != NULL + && strcmp(s, last_msg_hist->msg) != 0)) && !is_multihl) { add_msg_hist(s, -1, hl_id, multiline); } + if (!is_multihl) { + msg_start(); + } // Truncate the message if needed. - msg_start(); char *buf = msg_strtrunc(s, false); if (buf != NULL) { s = buf; @@ -368,7 +380,10 @@ bool msg_keep(const char *s, int hl_id, bool keep, bool multiline) if (need_clear) { msg_clr_eos(); } - bool retval = msg_end(); + bool retval = true; + if (!is_multihl) { + retval = msg_end(); + } if (keep && retval && vim_strsize(s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg(s, 0); @@ -618,6 +633,9 @@ void msg_source(int hl_id) msg_scroll = true; // this will take more than one line msg(p, hl_id); xfree(p); + if (is_multihl) { + msg_start(); // avoided in msg_keep() but need the "msg_didout" newline here + } } p = get_emsg_lnum(); if (p != NULL) { @@ -652,7 +670,7 @@ int emsg_not_now(void) return false; } -bool emsg_multiline(const char *s, bool multiline) +bool emsg_multiline(const char *s, const char *kind, int hl_id, bool multiline) { bool ignore = false; @@ -750,14 +768,13 @@ bool emsg_multiline(const char *s, bool multiline) } emsg_on_display = true; // remember there is an error message - int hl_id = HLF_E; // set highlight mode for error messages if (msg_scrolled != 0) { need_wait_return = true; // needed in case emsg() is called after } // wait_return() has reset need_wait_return // and a redraw is expected because // msg_scrolled is non-zero if (msg_ext_kind == NULL) { - msg_ext_set_kind("emsg"); + msg_ext_set_kind(kind); } // Display name and line number for the source of the error. @@ -765,7 +782,7 @@ bool emsg_multiline(const char *s, bool multiline) msg_source(hl_id); if (msg_ext_kind == NULL) { - msg_ext_set_kind("emsg"); + msg_ext_set_kind(kind); } // Display the error message itself. @@ -781,7 +798,7 @@ bool emsg_multiline(const char *s, bool multiline) /// @return true if wait_return() not called bool emsg(const char *s) { - return emsg_multiline(s, false); + return emsg_multiline(s, "emsg", HLF_E, false); } void emsg_invreg(int name) @@ -821,7 +838,7 @@ bool semsg_multiline(const char *const fmt, ...) vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); va_end(ap); - ret = emsg_multiline(errbuf, true); + ret = emsg_multiline(errbuf, "emsg", HLF_E, true); return ret; } @@ -905,7 +922,7 @@ void msg_schedule_semsg(const char *const fmt, ...) static void msg_semsg_multiline_event(void **argv) { char *s = argv[0]; - emsg_multiline(s, true); + emsg_multiline(s, "emsg", HLF_E, true); xfree(s); } @@ -1196,7 +1213,7 @@ void ex_messages(exarg_T *eap) msg_hist_off = true; for (; p != NULL && !got_int; p = p->next) { if (kv_size(p->multihl)) { - msg_multihl(p->multihl, p->kind, false); + msg_multihl(p->multihl, p->kind, false, false); } else if (p->msg != NULL) { msg_keep(p->msg, p->hl_id, false, p->multiline); } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 578fa361e8..e0ab31f702 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3680,6 +3680,30 @@ describe('API', function() async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {}) eq('', exec_capture('messages')) end) + + it('can print error message', function() + async_meths.nvim_echo({ { 'Error\nMessage' } }, false, { err = true }) + screen:expect([[ + | + {1:~ }|*3 + {3: }| + {9:Error} | + {9:Message} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed(':messages') + screen:expect([[ + ^ | + {1:~ }|*6 + | + ]]) + async_meths.nvim_echo({ { 'Error' }, { 'Message', 'Special' } }, false, { err = true }) + screen:expect([[ + ^ | + {1:~ }|*6 + {9:Error}{16:Message} | + ]]) + end) end) describe('nvim_open_term', function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 77ffc475b0..06048c665c 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -315,6 +315,21 @@ describe('ui/ext_messages', function() }) feed('') command('set showmode') + + -- kind=echoerr for nvim_echo() err + feed(':call nvim_echo([["Error"], ["Message", "Special"]], 1, #{ err:1 })') + screen:expect({ + cmdline = { { + abort = false, + } }, + messages = { + { + content = { { 'Error', 9, 6 }, { 'Message', 16, 99 } }, + history = true, + kind = 'echoerr', + }, + }, + }) end) it(':echoerr', function() -- cgit From 5c92b40b4b173c7d85106689fef811e41994abb0 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 7 Jan 2025 16:38:29 +0100 Subject: feat(api): deprecate nvim_out/err_write(ln) --- runtime/doc/api.txt | 24 ------------ runtime/doc/deprecated.txt | 3 ++ runtime/lua/vim/_defaults.lua | 2 +- runtime/lua/vim/_editor.lua | 10 ++--- runtime/lua/vim/_meta/api.lua | 19 +++------- runtime/lua/vim/lsp/client.lua | 6 +-- runtime/lua/vim/lsp/handlers.lua | 5 +-- src/nvim/api/deprecated.c | 80 +++++++++++++++++++++++++++++++++++++++ src/nvim/api/vim.c | 82 ---------------------------------------- 9 files changed, 98 insertions(+), 133 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 514479b8ba..852037bb34 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -669,23 +669,6 @@ nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* will be redirected to the log_file and suppressed from direct output. -nvim_err_write({str}) *nvim_err_write()* - Writes a message to the Vim error buffer. Does not append "\n", the - message is buffered (won't display) until a linefeed is written. - - Parameters: ~ - • {str} Message - -nvim_err_writeln({str}) *nvim_err_writeln()* - Writes a message to the Vim error buffer. Appends "\n", so the buffer is - flushed (and displayed). - - Parameters: ~ - • {str} Message - - See also: ~ - • nvim_err_write() - nvim_eval_statusline({str}, {opts}) *nvim_eval_statusline()* Evaluates statusline string. @@ -1156,13 +1139,6 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()* Return: ~ Channel id, or 0 on error -nvim_out_write({str}) *nvim_out_write()* - Writes a message to the Vim output buffer. Does not append "\n", the - message is buffered (won't display) until a linefeed is written. - - Parameters: ~ - • {str} Message - nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* Pastes at cursor (in any mode), and sets "redo" so dot (|.|) will repeat the input. UIs call this to implement "paste", but it's also intended for diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index ec98cc844f..dbdb8f541b 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -18,6 +18,9 @@ DEPRECATED IN 0.11 *deprecated-0.11* API • nvim_subscribe() Plugins must maintain their own "multicast" channels list. • nvim_unsubscribe() Plugins must maintain their own "multicast" channels list. +• nvim_out_write() Use |nvim_echo()|. +• nvim_err_write() Use |nvim_echo()| with `{err=true}`. +• nvim_err_writeln() Use |nvim_echo()| with `{err=true}`. DIAGNOSTICS • *vim.diagnostic.goto_next()* Use |vim.diagnostic.jump()| with `{count=1, float=true}` instead. diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 4216a2acb7..d71116117e 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -224,7 +224,7 @@ do local function cmd(opts) local ok, err = pcall(vim.api.nvim_cmd, opts, {}) if not ok then - vim.api.nvim_err_writeln(err:sub(#'Vim:' + 1)) + vim.api.nvim_echo({ { err:sub(#'Vim:' + 1) } }, true, { err = true }) end end diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 44f17b3f85..66815a967e 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -58,6 +58,7 @@ vim._extra = { --- @private vim.log = { + --- @enum vim.log.levels levels = { TRACE = 0, DEBUG = 1, @@ -620,13 +621,8 @@ end ---@param opts table|nil Optional parameters. Unused by default. ---@diagnostic disable-next-line: unused-local function vim.notify(msg, level, opts) -- luacheck: no unused args - if level == vim.log.levels.ERROR then - vim.api.nvim_err_writeln(msg) - elseif level == vim.log.levels.WARN then - vim.api.nvim_echo({ { msg, 'WarningMsg' } }, true, {}) - else - vim.api.nvim_echo({ { msg } }, true, {}) - end + local chunks = { { msg, level == vim.log.levels.WARN and 'WarningMsg' or nil } } + vim.api.nvim_echo(chunks, true, { err = level == vim.log.levels.ERROR }) end do diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index b0651efca4..f6d8153c27 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1111,17 +1111,12 @@ function vim.api.nvim_del_var(name) end --- redirected to the log_file and suppressed from direct output. function vim.api.nvim_echo(chunks, history, opts) end ---- Writes a message to the Vim error buffer. Does not append "\n", the ---- message is buffered (won't display) until a linefeed is written. ---- ---- @param str string Message +--- @deprecated +--- @param str string function vim.api.nvim_err_write(str) end ---- Writes a message to the Vim error buffer. Appends "\n", so the buffer is ---- flushed (and displayed). ---- ---- @see vim.api.nvim_err_write ---- @param str string Message +--- @deprecated +--- @param str string function vim.api.nvim_err_writeln(str) end --- Evaluates a Vimscript `expression`. Dicts and Lists are recursively expanded. @@ -1861,10 +1856,8 @@ function vim.api.nvim_open_term(buffer, opts) end --- @return integer # Window handle, or 0 on error function vim.api.nvim_open_win(buffer, enter, config) end ---- Writes a message to the Vim output buffer. Does not append "\n", the ---- message is buffered (won't display) until a linefeed is written. ---- ---- @param str string Message +--- @deprecated +--- @param str string function vim.api.nvim_out_write(str) end --- Parse command line. diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 5d11312c77..a99363d3d6 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -702,14 +702,14 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err --- --- @param ... string List to write to the buffer local function err_message(...) - local message = table.concat(vim.iter({ ... }):flatten():totable()) + local chunks = { { table.concat({ ... }) } } if vim.in_fast_event() then vim.schedule(function() - api.nvim_err_writeln(message) + vim.api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end) else - api.nvim_err_writeln(message) + vim.api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 1945040bda..3779c342e8 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -582,9 +582,8 @@ NSC['window/showMessage'] = function(_, params, ctx) if message_type == protocol.MessageType.Error then err_message('LSP[', client_name, '] ', message) else - --- @type string - local message_type_name = protocol.MessageType[message_type] - api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message)) + message = ('LSP[%s][%s] %s\n'):format(client_name, protocol.MessageType[message_type], message) + api.nvim_echo({ { message } }, true, { err = true }) end return params end diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index d5eddb74de..406d5e7b4f 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -21,9 +21,11 @@ #include "nvim/lua/executor.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/pos_defs.h" +#include "nvim/strings.h" #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -812,3 +814,81 @@ void nvim_unsubscribe(uint64_t channel_id, String event) { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } + +enum { LINE_BUFFER_MIN_SIZE = 4096, }; + +/// Writes a message to vim output or error buffer. The string is split +/// and flushed after each newline. Incomplete lines are kept for writing +/// later. +/// +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) +/// @param writeln Append a trailing newline +static void write_msg(String message, bool to_err, bool writeln) +{ + static StringBuilder out_line_buf = KV_INITIAL_VALUE; + static StringBuilder err_line_buf = KV_INITIAL_VALUE; + StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; + +#define PUSH_CHAR(c) \ + if (kv_max(*line_buf) == 0) { \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } \ + if (c == NL) { \ + kv_push(*line_buf, NUL); \ + if (to_err) { \ + emsg(line_buf->items); \ + } else { \ + msg(line_buf->items, 0); \ + } \ + if (msg_silent == 0) { \ + msg_didout = true; \ + } \ + kv_drop(*line_buf, kv_size(*line_buf)); \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } else if (c == NUL) { \ + kv_push(*line_buf, NL); \ + } else { \ + kv_push(*line_buf, c); \ + } + + no_wait_return++; + for (uint32_t i = 0; i < message.size; i++) { + if (got_int) { + break; + } + PUSH_CHAR(message.data[i]); + } + if (writeln) { + PUSH_CHAR(NL); + } + no_wait_return--; + msg_end(); +} + +/// @deprecated +/// +/// @param str Message +void nvim_out_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, false, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_writeln(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, true); +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f0848b7e04..ed9edb1beb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -86,8 +86,6 @@ #include "nvim/vim_defs.h" #include "nvim/window.h" -#define LINE_BUFFER_MIN_SIZE 4096 - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.c.generated.h" #endif @@ -808,37 +806,6 @@ error: hl_msg_free(hl_msg); } -/// Writes a message to the Vim output buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_out_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, false, false); -} - -/// Writes a message to the Vim error buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_err_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, false); -} - -/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is -/// flushed (and displayed). -/// -/// @param str Message -/// @see nvim_err_write() -void nvim_err_writeln(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, true); -} - /// Gets the current list of buffer handles /// /// Includes unlisted (unloaded/deleted) buffers, like `:ls!`. @@ -1664,55 +1631,6 @@ Array nvim_list_chans(Arena *arena) return channel_all_info(arena); } -/// Writes a message to vim output or error buffer. The string is split -/// and flushed after each newline. Incomplete lines are kept for writing -/// later. -/// -/// @param message Message to write -/// @param to_err true: message is an error (uses `emsg` instead of `msg`) -/// @param writeln Append a trailing newline -static void write_msg(String message, bool to_err, bool writeln) -{ - static StringBuilder out_line_buf = KV_INITIAL_VALUE; - static StringBuilder err_line_buf = KV_INITIAL_VALUE; - StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; - -#define PUSH_CHAR(c) \ - if (kv_max(*line_buf) == 0) { \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } \ - if (c == NL) { \ - kv_push(*line_buf, NUL); \ - if (to_err) { \ - emsg(line_buf->items); \ - } else { \ - msg(line_buf->items, 0); \ - } \ - if (msg_silent == 0) { \ - msg_didout = true; \ - } \ - kv_drop(*line_buf, kv_size(*line_buf)); \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } else if (c == NUL) { \ - kv_push(*line_buf, NL); \ - } else { \ - kv_push(*line_buf, c); \ - } - - no_wait_return++; - for (uint32_t i = 0; i < message.size; i++) { - if (got_int) { - break; - } - PUSH_CHAR(message.data[i]); - } - if (writeln) { - PUSH_CHAR(NL); - } - no_wait_return--; - msg_end(); -} - // Functions used for testing purposes /// Returns object given as argument. -- cgit From 1e47aa677a24231ec771137dadc7c2b65be775b4 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Thu, 9 Jan 2025 21:32:27 +0600 Subject: fix(api): deprecated API nvim_get_option does not validate option name #31919 Problem: Deprecated API `nvim_get_option()` doesn't validate the option name, which leads to an assertion failure. Solution: Validate option name in `nvim_get_option()`. Ref: #31894 --- src/nvim/api/deprecated.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 406d5e7b4f..5150aeebec 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -648,6 +648,10 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er }); OptIndex opt_idx = find_option(name.data); + VALIDATE_S(opt_idx != kOptInvalid, "option name", name.data, { + return (Object)OBJECT_INIT; + }); + OptVal value = NIL_OPTVAL; if (option_has_scope(opt_idx, scope)) { -- cgit From 0c296ab22484b4c009d119908d1614a6c6d96b2c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 9 Jan 2025 08:36:16 -0800 Subject: feat(docs): "yxx" runs Lua/Vimscript code examples #31904 `yxx` in Normal mode over a Lua or Vimscript code block section will execute the code. Co-authored-by: Justin M. Keyes --- runtime/doc/helphelp.txt | 1 + runtime/doc/lua.txt | 6 ++-- runtime/doc/news.txt | 1 + runtime/doc/terminal.txt | 4 +-- runtime/doc/treesitter.txt | 4 +-- runtime/doc/various.txt | 5 ++++ runtime/ftplugin/help.lua | 53 +++++++++++++++++++++++++++++++++++- runtime/lua/vim/shared.lua | 4 +-- runtime/lua/vim/treesitter/query.lua | 4 +-- 9 files changed, 70 insertions(+), 12 deletions(-) diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 46b3ab507d..f7009aebfe 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -193,6 +193,7 @@ Jump to specific subjects by using tags. This can be done in two ways: Use CTRL-T or CTRL-O to jump back. Use ":q" to close the help window. +Use `yxx` to execute the current Lua/Vimscript code block. If there are several matches for an item you are looking for, this is how you can jump to each one of them: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6547a76f56..6386240f07 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -161,7 +161,7 @@ languages like Python and C#. Example: >lua local func_with_opts = function(opts) local will_do_foo = opts.foo local filename = opts.filename - ... + -- ... end func_with_opts { foo = true, filename = "hello.world" } @@ -2428,7 +2428,7 @@ vim.validate({name}, {value}, {validator}, {optional}, {message}) function vim.startswith(s, prefix) vim.validate('s', s, 'string') vim.validate('prefix', prefix, 'string') - ... + -- ... end < 2. `vim.validate(spec)` (deprecated) where `spec` is of type @@ -2442,7 +2442,7 @@ vim.validate({name}, {value}, {validator}, {optional}, {message}) age={age, 'number'}, hobbies={hobbies, 'table'}, } - ... + -- ... end < diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 960b6f05f9..e1824e068d 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -276,6 +276,7 @@ LUA supporting two new parameters, `encoding` and `strict_indexing`. • |vim.json.encode()| has an option to enable forward slash escaping • |vim.fs.abspath()| converts paths to absolute paths. +• Lua and vimscript code examples in docs can now be run using `yxx`. OPTIONS diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index ff8e3a976f..7d79669ed8 100644 --- a/runtime/doc/terminal.txt +++ b/runtime/doc/terminal.txt @@ -165,8 +165,8 @@ directory indicated in the request. >lua end }) -To try it out, select the above code and source it with `:'<,'>lua`, then run -this command in a :terminal buffer: > +To try it out, select the above code and source it with `:'<,'>lua` (or +`yxx`), then run this command in a :terminal buffer: > printf "\033]7;file://./foo/bar\033\\" diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 28fe943359..538435e5e8 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1336,7 +1336,7 @@ parse({lang}, {query}) *vim.treesitter.query.parse()* `info.captures`). • `info.patterns`: information about predicates. - Example (select the code then run `:'<,'>lua` to try it): >lua + Example (to try it, use `yxx` or select the code then run `:'<,'>lua`): >lua local query = vim.treesitter.query.parse('vimdoc', [[ ; query ((h1) @str @@ -1422,7 +1422,7 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) -- `node` was captured by the `name` capture in the match local node_data = metadata[id] -- Node level metadata - ... use the info here ... + -- ... use the info here ... end end end diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 6074931565..611e820cab 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -554,6 +554,11 @@ gO Show a filetype-specific, navigable "outline" of the *:sl!* *:sleep!* :[N]sl[eep]! [N][m] Same as above, but hide the cursor. + *yxx* +yxx Executes the current code block. + + Works in |help| buffers. + ============================================================================== 2. Using Vim like less or more *less* diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index 8d991be0e4..689a4db408 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -31,5 +31,56 @@ vim.keymap.set('n', 'gO', function() require('vim.vimhelp').show_toc() end, { buffer = 0, silent = true }) -vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n exe "nunmap gO"' +-- Add "runnables" for Lua/Vimscript code examples. +---@type table +local code_blocks = {} +local tree = vim.treesitter.get_parser():parse()[1] +local query = vim.treesitter.query.parse( + 'vimdoc', + [[ + (codeblock + (language) @_lang + . + (code) @code + (#any-of? @_lang "lua" "vim") + (#set! @code lang @_lang)) +]] +) +local run_message_ns = vim.api.nvim_create_namespace('vimdoc/run_message') + +vim.api.nvim_buf_clear_namespace(0, run_message_ns, 0, -1) +for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do + for id, nodes in pairs(match) do + local name = query.captures[id] + local node = nodes[1] + local start, _, end_ = node:parent():range() --[[@as integer]] + + if name == 'code' then + vim.api.nvim_buf_set_extmark(0, run_message_ns, start, 0, { + virt_text = { { 'Run with `yxx`', 'LspCodeLens' } }, + }) + local code = vim.treesitter.get_node_text(node, 0) + local lang_node = match[metadata[id].lang][1] --[[@as TSNode]] + local lang = vim.treesitter.get_node_text(lang_node, 0) + for i = start + 1, end_ do + code_blocks[i] = { lang = lang, code = code } + end + end + end +end + +vim.keymap.set('n', 'yxx', function() + local pos = vim.api.nvim_win_get_cursor(0)[1] + local code_block = code_blocks[pos] + if not code_block then + vim.print('No code block found') + elseif code_block.lang == 'lua' then + vim.cmd.lua(code_block.code) + elseif code_block.lang == 'vim' then + vim.cmd(code_block.code) + end +end, { buffer = true }) + +vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') + .. '\n exe "nunmap gO" | exe "nunmap yxx"' vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | call v:lua.vim.treesitter.stop()' diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 24c3f243e5..02b12490af 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -959,7 +959,7 @@ do --- function vim.startswith(s, prefix) --- vim.validate('s', s, 'string') --- vim.validate('prefix', prefix, 'string') - --- ... + --- -- ... --- end --- ``` --- @@ -979,7 +979,7 @@ do --- age={age, 'number'}, --- hobbies={hobbies, 'table'}, --- } - --- ... + --- -- ... --- end --- ``` --- diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 1fc001b39f..b9bcbe9a80 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -292,7 +292,7 @@ end) --- - `captures`: a list of unique capture names defined in the query (alias: `info.captures`). --- - `info.patterns`: information about predicates. --- ---- Example (select the code then run `:'<,'>lua` to try it): +--- Example (to try it, use `yxx` or select the code then run `:'<,'>lua`): --- ```lua --- local query = vim.treesitter.query.parse('vimdoc', [[ --- ; query @@ -983,7 +983,7 @@ end --- -- `node` was captured by the `name` capture in the match --- --- local node_data = metadata[id] -- Node level metadata ---- ... use the info here ... +--- -- ... use the info here ... --- end --- end --- end -- cgit From 7c00e0efbb18e8627ac59eaadf564a9f1b2bafcd Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 9 Jan 2025 09:26:45 -0800 Subject: docs: misc #31867 --- .github/workflows/vim_patches.yml | 2 +- runtime/doc/api.txt | 50 ++++++----- runtime/doc/health.txt | 30 ++++--- runtime/doc/lua.txt | 8 +- runtime/doc/news.txt | 6 +- runtime/doc/starting.txt | 33 ++++--- runtime/doc/vim_diff.txt | 7 +- runtime/lua/vim/_editor.lua | 4 +- runtime/lua/vim/_meta/api.lua | 37 +++++--- runtime/lua/vim/health.lua | 30 ++++--- runtime/lua/vim/lsp/util.lua | 3 +- scripts/vimpatch.lua | 2 +- src/nvim/api/vim.c | 54 +++++++----- test/functional/func/memoize_spec.lua | 142 ------------------------------ test/functional/lua/fs_spec.lua | 5 +- test/functional/lua/func_memoize_spec.lua | 142 ++++++++++++++++++++++++++++++ test/functional/lua/vim_spec.lua | 2 - 17 files changed, 297 insertions(+), 260 deletions(-) delete mode 100644 test/functional/func/memoize_spec.lua create mode 100644 test/functional/lua/func_memoize_spec.lua diff --git a/.github/workflows/vim_patches.yml b/.github/workflows/vim_patches.yml index b0be01089f..5154565def 100644 --- a/.github/workflows/vim_patches.yml +++ b/.github/workflows/vim_patches.yml @@ -43,7 +43,7 @@ jobs: id: update-version run: | git checkout -b ${VERSION_BRANCH} - nvim -V1 -es -i NONE +'luafile scripts/vimpatch.lua' +q + nvim -l scripts/vimpatch.lua printf 'NEW_PATCHES=%s\n' $([ -z "$(git diff)" ]; echo $?) >> $GITHUB_OUTPUT - name: Automatic PR diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 852037bb34..9e84dd40ac 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -654,20 +654,22 @@ nvim_del_var({name}) *nvim_del_var()* • {name} Variable name nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* - Echo a message. + Prints a message given by a list of `[text, hl_group]` "chunks". + + Example: >lua + vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +< Parameters: ~ - • {chunks} A list of `[text, hl_group]` arrays, each representing a - text chunk with specified highlight group name or ID. - `hl_group` element can be omitted for no highlight. + • {chunks} List of `[text, hl_group]` pairs, where each is a `text` + string highlighted by the (optional) name or ID `hl_group`. • {history} if true, add to |message-history|. • {opts} Optional parameters. - • err: Treat the message like |:echoerr|. Omitted `hlgroup` - uses |hl-ErrorMsg| instead. - • verbose: Message is printed as a result of 'verbose' - option. If Nvim was invoked with -V3log_file, the message - will be redirected to the log_file and suppressed from - direct output. + • err: Treat the message like `:echoerr`. Sets `hl_group` + to |hl-ErrorMsg| by default. + • verbose: Message is controlled by the 'verbose' option. + Nvim invoked with `-V3log` will write the message to the + "log" file instead of standard output. nvim_eval_statusline({str}, {opts}) *nvim_eval_statusline()* Evaluates statusline string. @@ -760,6 +762,8 @@ nvim_get_api_info() *nvim_get_api_info()* nvim_get_chan_info({chan}) *nvim_get_chan_info()* Gets information about a channel. + See |nvim_list_uis()| for an example of how to get channel info. + Parameters: ~ • {chan} channel_id, or 0 for current channel @@ -781,7 +785,7 @@ nvim_get_chan_info({chan}) *nvim_get_chan_info()* present if a pty is used (e.g. for conpty on Windows). • "buffer" (optional) Buffer connected to |terminal| instance. • "client" (optional) Info about the peer (client on the other end of - the RPC channel), which it provided via |nvim_set_client_info()|. + the channel), as set by |nvim_set_client_info()|. nvim_get_color_by_name({name}) *nvim_get_color_by_name()* Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or @@ -1064,6 +1068,12 @@ nvim_list_tabpages() *nvim_list_tabpages()* nvim_list_uis() *nvim_list_uis()* Gets a list of dictionaries representing attached UIs. + Example: The Nvim builtin |TUI| sets its channel info as described in + |startup-tui|. In particular, it sets `client.name` to "nvim-tui". So you + can check if the TUI is running by inspecting the client name of each UI: >lua + vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +< + Return: ~ Array of UI dictionaries, each with these keys: • "height" Requested height of the UI @@ -1112,7 +1122,7 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()* Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you can use Nvim as a "scrollback pager" (for terminals - like kitty): *terminal-scrollback-pager* >lua + like kitty): *ansi-colorize* *terminal-scrollback-pager* >lua vim.api.nvim_create_user_command('TermHl', function() local b = vim.api.nvim_create_buf(false, true) local chan = vim.api.nvim_open_term(b, {}) @@ -1237,25 +1247,23 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts}) *nvim_set_client_info()* nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes}) - Self-identifies the client. + Self-identifies the client. Sets the `client` object returned by + |nvim_get_chan_info()|. - The client/plugin/application should call this after connecting, to - provide hints about its identity and purpose, for debugging and - orchestration. + Clients should call this just after connecting, to provide hints for + debugging and orchestration. (Note: Something is better than nothing! + Fields are optional, but at least set `name`.) Can be called more than once; the caller should merge old info if appropriate. Example: library first identifies the channel, then a plugin using that library later identifies itself. - Note: ~ - • "Something is better than nothing". You don't need to include all the - fields. - Attributes: ~ |RPC| only Parameters: ~ - • {name} Short name for the connected client + • {name} Client short-name. Sets the `client.name` field of + |nvim_get_chan_info()|. • {version} Dict describing the version, with these (optional) keys: • "major" major version (defaults to 0 if not set, for no release yet) diff --git a/runtime/doc/health.txt b/runtime/doc/health.txt index bca145bd8e..3d37b88321 100644 --- a/runtime/doc/health.txt +++ b/runtime/doc/health.txt @@ -21,18 +21,7 @@ To run all healthchecks, use: >vim < Plugin authors are encouraged to write new healthchecks. |health-dev| - *g:health* -g:health This global variable controls the behavior and appearance of the - `health` floating window. It should be a dictionary containing the - following optional keys: - - `style`: string? Determines the display style of the `health` window. - Set to `'float'` to enable a floating window. Other - styles are not currently supported. - - Example: >lua - vim.g.health = { style = 'float' } - -Commands *health-commands* +COMMANDS *health-commands* *:che* *:checkhealth* :che[ckhealth] Run all healthchecks. @@ -60,6 +49,23 @@ Commands *health-commands* :checkhealth vim* < +USAGE *health-usage* + +Local mappings in the healthcheck buffer: + +q Closes the window. + +Global configuration: + + *g:health* +g:health Dictionary with the following optional keys: + - `style` (`'float'|nil`) Set to "float" to display :checkhealth in + a floating window instead of the default behavior. + + Example: >lua + vim.g.health = { style = 'float' } + +-------------------------------------------------------------------------------- Create a healthcheck *health-dev* Healthchecks are functions that check the user environment, configuration, or diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6386240f07..9df9bc3ab5 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1505,7 +1505,7 @@ vim.wo[{winid}][{bufnr}] *vim.wo* Lua module: vim *lua-vim* vim.cmd({command}) *vim.cmd()* - Executes Vim script commands. + Executes Vimscript (|Ex-commands|). Note that `vim.cmd` can be indexed with a command name to return a callable function to the command. @@ -1539,9 +1539,9 @@ vim.cmd({command}) *vim.cmd()* Parameters: ~ • {command} (`string|table`) Command(s) to execute. If a string, - executes multiple lines of Vim script at once. In this - case, it is an alias to |nvim_exec2()|, where `opts.output` - is set to false. Thus it works identical to |:source|. If a + executes multiple lines of Vimscript at once. In this case, + it is an alias to |nvim_exec2()|, where `opts.output` is + set to false. Thus it works identical to |:source|. If a table, executes a single command. In this case, it is an alias to |nvim_cmd()| where `opts` is empty. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e1824e068d..181ae317e1 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -221,6 +221,7 @@ DIAGNOSTICS EDITOR +• Use |yxx| in :help docs to execute Lua and Vimscript code examples. • Improved |paste| handling for redo (dot-repeat) and macros (|recording|): • Redoing a large paste is significantly faster and ignores 'autoindent'. • Replaying a macro with |@| also replays pasted text. @@ -276,7 +277,6 @@ LUA supporting two new parameters, `encoding` and `strict_indexing`. • |vim.json.encode()| has an option to enable forward slash escaping • |vim.fs.abspath()| converts paths to absolute paths. -• Lua and vimscript code examples in docs can now be run using `yxx`. OPTIONS @@ -362,8 +362,8 @@ UI • |vim.diagnostic.setqflist()| updates an existing quickfix list with the given title if found • |ui-messages| content chunks now also contain the highlight group ID. -• |:checkhealth| can be display in a floating window and controlled by - the |g:health| variable. +• |:checkhealth| can display in a floating window, controlled by the + |g:health| variable. ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 0bfbea75fb..c0254c3fa1 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -164,8 +164,7 @@ argument. you can overwrite a file by adding an exclamation mark to the Ex command, as in ":w!". The 'readonly' option can be reset with ":set noro" (see the options chapter, |options|). - Subsequent edits will not be done in readonly mode. Calling - the executable "view" has the same effect as the -R argument. + Subsequent edits will not be done in readonly mode. The 'updatecount' option will be set to 10000, meaning that the swap file will not be updated automatically very often. See |-M| for disallowing modifications. @@ -226,7 +225,8 @@ argument. arguments. The {script} name is stored at `_G.arg[0]`. Sets 'verbose' to 1 (like "-V1"), so Lua `print()` writes to - output. + output, as well as other message-emitting functions like + |:echo|. If {script} prints messages and doesn't cause Nvim to exit, Nvim ensures output ends with a newline. @@ -288,21 +288,18 @@ argument. command from a script. |debug-mode| *-n* --n No |swap-file| will be used. Recovery after a crash will be - impossible. Handy if you want to view or edit a file on a - very slow medium (e.g., a floppy). - Can also be done with ":set updatecount=0". You can switch it - on again by setting the 'updatecount' option to some value, - e.g., ":set uc=100". - 'updatecount' is set to 0 AFTER executing commands from a - vimrc file, but before the GUI initializations. Thus it - overrides a setting for 'updatecount' in a vimrc file, but not - in a gvimrc file. See |startup|. - When you want to reduce accesses to the disk (e.g., for a - laptop), don't use "-n", but set 'updatetime' and - 'updatecount' to very big numbers, and type ":preserve" when - you want to save your work. This way you keep the possibility - for crash recovery. +-n Disables |swap-file| by setting 'updatecount' to 0 (after + executing any |vimrc|). Recovery after a crash will be + impossible. Improves peformance when working with a file on + a very slow medium (usb drive, network share). + + Enable it again by setting 'updatecount' to some value, e.g. + ":set updatecount=100". + + To reduce accesses to the disk, don't use "-n", but set + 'updatetime' and 'updatecount' to very big numbers, and type + ":preserve" when you want to save your work. This way you + keep the possibility for crash recovery. *-o* -o[N] Open N windows, split horizontally. If [N] is not given, diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index a92ddf33e6..d690460f77 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -423,8 +423,11 @@ TUI: < *'term'* *E529* *E530* *E531* - 'term' reflects the terminal type derived from |$TERM| and other environment - checks. For debugging only; not reliable during startup. >vim - :echo &term + checks. Use `:echo &term` to get its value. For debugging only; not + reliable during startup. + - Note: If you want to detect when Nvim is running in a terminal, use + `has('gui_running')` |has()| or see |nvim_list_uis()| for an example of + how to inspect the UI channel. - "builtin_x" means one of the |builtin-terms| was chosen, because the expected terminfo file was not found on the system. - Nvim will use 256-colour capability on Linux virtual terminals. Vim uses diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 66815a967e..d4e6280b06 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -391,7 +391,7 @@ end local VIM_CMD_ARG_MAX = 20 ---- Executes Vim script commands. +--- Executes Vimscript (|Ex-commands|). --- --- Note that `vim.cmd` can be indexed with a command name to return a callable function to the --- command. @@ -426,7 +426,7 @@ local VIM_CMD_ARG_MAX = 20 --- ``` --- ---@param command string|table Command(s) to execute. ---- If a string, executes multiple lines of Vim script at once. In this +--- If a string, executes multiple lines of Vimscript at once. In this --- case, it is an alias to |nvim_exec2()|, where `opts.output` is set --- to false. Thus it works identical to |:source|. --- If a table, executes a single command. In this case, it is an alias diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index f6d8153c27..b5d8a0937d 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1097,18 +1097,20 @@ function vim.api.nvim_del_user_command(name) end --- @param name string Variable name function vim.api.nvim_del_var(name) end ---- Echo a message. +--- Prints a message given by a list of `[text, hl_group]` "chunks". --- ---- @param chunks any[] A list of `[text, hl_group]` arrays, each representing a ---- text chunk with specified highlight group name or ID. ---- `hl_group` element can be omitted for no highlight. +--- Example: +--- ```lua +--- vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +--- ``` +--- +--- @param chunks any[] List of `[text, hl_group]` pairs, where each is a `text` string highlighted by +--- the (optional) name or ID `hl_group`. --- @param history boolean if true, add to `message-history`. --- @param opts vim.api.keyset.echo_opts Optional parameters. ---- - err: Treat the message like `:echoerr`. Omitted `hlgroup` ---- uses `hl-ErrorMsg` instead. ---- - verbose: Message is printed as a result of 'verbose' option. ---- If Nvim was invoked with -V3log_file, the message will be ---- redirected to the log_file and suppressed from direct output. +--- - err: Treat the message like `:echoerr`. Sets `hl_group` to `hl-ErrorMsg` by default. +--- - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log` +--- will write the message to the "log" file instead of standard output. function vim.api.nvim_echo(chunks, history, opts) end --- @deprecated @@ -1276,6 +1278,8 @@ function vim.api.nvim_get_autocmds(opts) end --- Gets information about a channel. --- +--- See `nvim_list_uis()` for an example of how to get channel info. +--- --- @param chan integer channel_id, or 0 for current channel --- @return table # Channel info dict with these keys: --- - "id" Channel id. @@ -1293,8 +1297,8 @@ function vim.api.nvim_get_autocmds(opts) end --- "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. --- for conpty on Windows). --- - "buffer" (optional) Buffer connected to |terminal| instance. ---- - "client" (optional) Info about the peer (client on the other end of the RPC channel), ---- which it provided via |nvim_set_client_info()|. +--- - "client" (optional) Info about the peer (client on the other end of the channel), as set +--- by |nvim_set_client_info()|. --- function vim.api.nvim_get_chan_info(chan) end @@ -1616,6 +1620,14 @@ function vim.api.nvim_list_tabpages() end --- Gets a list of dictionaries representing attached UIs. --- +--- Example: The Nvim builtin `TUI` sets its channel info as described in `startup-tui`. In +--- particular, it sets `client.name` to "nvim-tui". So you can check if the TUI is running by +--- inspecting the client name of each UI: +--- +--- ```lua +--- vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +--- ``` +--- --- @return any[] # Array of UI dictionaries, each with these keys: --- - "height" Requested height of the UI --- - "width" Requested width of the UI @@ -1661,7 +1673,8 @@ function vim.api.nvim_notify(msg, log_level, opts) end --- in a virtual terminal having the intended size. --- --- Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you ---- can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]() +--- can use Nvim as a "scrollback pager" (for terminals like kitty): [ansi-colorize]() +--- [terminal-scrollback-pager]() --- --- ```lua --- vim.api.nvim_create_user_command('TermHl', function() diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 6dc902489f..ee376f3a11 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -11,18 +11,7 @@ --- < --- Plugin authors are encouraged to write new healthchecks. |health-dev| --- ---- *g:health* ---- g:health This global variable controls the behavior and appearance of the ---- `health` floating window. It should be a dictionary containing the ---- following optional keys: ---- - `style`: string? Determines the display style of the `health` window. ---- Set to `'float'` to enable a floating window. Other ---- styles are not currently supported. ---- ---- Example: >lua ---- vim.g.health = { style = 'float' } ---- ---- Commands *health-commands* +--- COMMANDS *health-commands* --- --- *:che* *:checkhealth* --- :che[ckhealth] Run all healthchecks. @@ -50,6 +39,23 @@ --- :checkhealth vim* --- < --- +--- USAGE *health-usage* +--- +--- Local mappings in the healthcheck buffer: +--- +--- q Closes the window. +--- +--- Global configuration: +--- +--- *g:health* +--- g:health Dictionary with the following optional keys: +--- - `style` (`'float'|nil`) Set to "float" to display :checkhealth in +--- a floating window instead of the default behavior. +--- +--- Example: >lua +--- vim.g.health = { style = 'float' } +--- +--- -------------------------------------------------------------------------------- --- Create a healthcheck *health-dev* --- --- Healthchecks are functions that check the user environment, configuration, or diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5cccb3321f..ccd68f0fdf 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -879,8 +879,7 @@ function M.make_floating_popup_options(width, height, opts) col = col + (opts.offset_x or 0), height = height, focusable = opts.focusable, - relative = opts.relative == 'mouse' and 'mouse' - or opts.relative == 'editor' and 'editor' + relative = (opts.relative == 'mouse' or opts.relative == 'editor') and opts.relative or 'cursor', style = 'minimal', width = width, diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua index cbec50fc17..5d8bea6a98 100755 --- a/scripts/vimpatch.lua +++ b/scripts/vimpatch.lua @@ -1,7 +1,7 @@ -- Updates version.c list of applied Vim patches. -- -- Usage: --- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -V1 -es -i NONE +'luafile ./scripts/vimpatch.lua' +q +-- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -l ./scripts/vimpatch.lua local nvim = vim.api diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ed9edb1beb..e03a260280 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -766,18 +766,20 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, NULL, err); } -/// Echo a message. +/// Prints a message given by a list of `[text, hl_group]` "chunks". /// -/// @param chunks A list of `[text, hl_group]` arrays, each representing a -/// text chunk with specified highlight group name or ID. -/// `hl_group` element can be omitted for no highlight. +/// Example: +/// ```lua +/// vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +/// ``` +/// +/// @param chunks List of `[text, hl_group]` pairs, where each is a `text` string highlighted by +/// the (optional) name or ID `hl_group`. /// @param history if true, add to |message-history|. /// @param opts Optional parameters. -/// - err: Treat the message like |:echoerr|. Omitted `hlgroup` -/// uses |hl-ErrorMsg| instead. -/// - verbose: Message is printed as a result of 'verbose' option. -/// If Nvim was invoked with -V3log_file, the message will be -/// redirected to the log_file and suppressed from direct output. +/// - err: Treat the message like `:echoerr`. Sets `hl_group` to |hl-ErrorMsg| by default. +/// - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log` +/// will write the message to the "log" file instead of standard output. void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { @@ -1000,7 +1002,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// in a virtual terminal having the intended size. /// /// Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you -/// can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]() +/// can use Nvim as a "scrollback pager" (for terminals like kitty): [ansi-colorize]() +/// [terminal-scrollback-pager]() /// /// ```lua /// vim.api.nvim_create_user_command('TermHl', function() @@ -1500,20 +1503,17 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena) return rv; } -/// Self-identifies the client. -/// -/// The client/plugin/application should call this after connecting, to provide -/// hints about its identity and purpose, for debugging and orchestration. +/// Self-identifies the client. Sets the `client` object returned by |nvim_get_chan_info()|. /// -/// Can be called more than once; the caller should merge old info if -/// appropriate. Example: library first identifies the channel, then a plugin -/// using that library later identifies itself. +/// Clients should call this just after connecting, to provide hints for debugging and +/// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set +/// `name`.) /// -/// @note "Something is better than nothing". You don't need to include all the -/// fields. +/// Can be called more than once; the caller should merge old info if appropriate. Example: library +/// first identifies the channel, then a plugin using that library later identifies itself. /// /// @param channel_id -/// @param name Short name for the connected client +/// @param name Client short-name. Sets the `client.name` field of |nvim_get_chan_info()|. /// @param version Dict describing the version, with these /// (optional) keys: /// - "major" major version (defaults to 0 if not set, for no release yet) @@ -1587,6 +1587,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// Gets information about a channel. /// +/// See |nvim_list_uis()| for an example of how to get channel info. +/// /// @param chan channel_id, or 0 for current channel /// @returns Channel info dict with these keys: /// - "id" Channel id. @@ -1604,8 +1606,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. /// for conpty on Windows). /// - "buffer" (optional) Buffer connected to |terminal| instance. -/// - "client" (optional) Info about the peer (client on the other end of the RPC channel), -/// which it provided via |nvim_set_client_info()|. +/// - "client" (optional) Info about the peer (client on the other end of the channel), as set +/// by |nvim_set_client_info()|. /// Dict nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err) FUNC_API_SINCE(4) @@ -1702,6 +1704,14 @@ Dict nvim__stats(Arena *arena) /// Gets a list of dictionaries representing attached UIs. /// +/// Example: The Nvim builtin |TUI| sets its channel info as described in |startup-tui|. In +/// particular, it sets `client.name` to "nvim-tui". So you can check if the TUI is running by +/// inspecting the client name of each UI: +/// +/// ```lua +/// vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +/// ``` +/// /// @return Array of UI dictionaries, each with these keys: /// - "height" Requested height of the UI /// - "width" Requested width of the UI diff --git a/test/functional/func/memoize_spec.lua b/test/functional/func/memoize_spec.lua deleted file mode 100644 index ca518ab88d..0000000000 --- a/test/functional/func/memoize_spec.lua +++ /dev/null @@ -1,142 +0,0 @@ -local t = require('test.testutil') -local n = require('test.functional.testnvim')() -local clear = n.clear -local exec_lua = n.exec_lua -local eq = t.eq - -describe('vim.func._memoize', function() - before_each(clear) - - it('caches function results based on their parameters', function() - exec_lua([[ - _G.count = 0 - - local adder = vim.func._memoize('concat', function(arg1, arg2) - _G.count = _G.count + 1 - return arg1 + arg2 - end) - - collectgarbage('stop') - adder(3, -4) - adder(3, -4) - adder(3, -4) - adder(3, -4) - adder(3, -4) - collectgarbage('restart') - ]]) - - eq(1, exec_lua([[return _G.count]])) - end) - - it('caches function results using a weak table by default', function() - exec_lua([[ - _G.count = 0 - - local adder = vim.func._memoize('concat-2', function(arg1, arg2) - _G.count = _G.count + 1 - return arg1 + arg2 - end) - - adder(3, -4) - collectgarbage() - adder(3, -4) - collectgarbage() - adder(3, -4) - ]]) - - eq(3, exec_lua([[return _G.count]])) - end) - - it('can cache using a strong table', function() - exec_lua([[ - _G.count = 0 - - local adder = vim.func._memoize('concat-2', function(arg1, arg2) - _G.count = _G.count + 1 - return arg1 + arg2 - end, false) - - adder(3, -4) - collectgarbage() - adder(3, -4) - collectgarbage() - adder(3, -4) - ]]) - - eq(1, exec_lua([[return _G.count]])) - end) - - it('can clear a single cache entry', function() - exec_lua([[ - _G.count = 0 - - local adder = vim.func._memoize(function(arg1, arg2) - return tostring(arg1) .. '%%' .. tostring(arg2) - end, function(arg1, arg2) - _G.count = _G.count + 1 - return arg1 + arg2 - end) - - collectgarbage('stop') - adder(3, -4) - adder(3, -4) - adder(3, -4) - adder(3, -4) - adder(3, -4) - adder:clear(3, -4) - adder(3, -4) - collectgarbage('restart') - ]]) - - eq(2, exec_lua([[return _G.count]])) - end) - - it('can clear the entire cache', function() - exec_lua([[ - _G.count = 0 - - local adder = vim.func._memoize(function(arg1, arg2) - return tostring(arg1) .. '%%' .. tostring(arg2) - end, function(arg1, arg2) - _G.count = _G.count + 1 - return arg1 + arg2 - end) - - collectgarbage('stop') - adder(1, 2) - adder(3, -4) - adder(1, 2) - adder(3, -4) - adder(1, 2) - adder(3, -4) - adder:clear() - adder(1, 2) - adder(3, -4) - collectgarbage('restart') - ]]) - - eq(4, exec_lua([[return _G.count]])) - end) - - it('can cache functions that return nil', function() - exec_lua([[ - _G.count = 0 - - local adder = vim.func._memoize('concat', function(arg1, arg2) - _G.count = _G.count + 1 - return nil - end) - - collectgarbage('stop') - adder(1, 2) - adder(1, 2) - adder(1, 2) - adder(1, 2) - adder:clear() - adder(1, 2) - collectgarbage('restart') - ]]) - - eq(2, exec_lua([[return _G.count]])) - end) -end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 218f9bbc46..6bc1ddbff6 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -340,9 +340,6 @@ describe('vim.fs', function() end) describe('normalize()', function() - -- local function vim.fs.normalize(path, opts) - -- return exec_lua([[return vim.fs.vim.fs.normalize(...)]], path, opts) - -- end it('removes trailing /', function() eq('/home/user', vim.fs.normalize('/home/user/')) end) @@ -389,7 +386,7 @@ describe('vim.fs', function() eq('D:foo/test', vim.fs.normalize('d:foo/test/', win_opts)) end) - it('does not change case on paths, see #31833', function() + it('always treats paths as case-sensitive #31833', function() eq('TEST', vim.fs.normalize('TEST', win_opts)) eq('test', vim.fs.normalize('test', win_opts)) eq('C:/FOO/test', vim.fs.normalize('C:/FOO/test', win_opts)) diff --git a/test/functional/lua/func_memoize_spec.lua b/test/functional/lua/func_memoize_spec.lua new file mode 100644 index 0000000000..ca518ab88d --- /dev/null +++ b/test/functional/lua/func_memoize_spec.lua @@ -0,0 +1,142 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local clear = n.clear +local exec_lua = n.exec_lua +local eq = t.eq + +describe('vim.func._memoize', function() + before_each(clear) + + it('caches function results based on their parameters', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('caches function results using a weak table by default', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(3, exec_lua([[return _G.count]])) + end) + + it('can cache using a strong table', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end, false) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('can clear a single cache entry', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder:clear(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) + + it('can clear the entire cache', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder:clear() + adder(1, 2) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(4, exec_lua([[return _G.count]])) + end) + + it('can cache functions that return nil', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return nil + end) + + collectgarbage('stop') + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder:clear() + adder(1, 2) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9e75861aa0..55e5158596 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -3435,7 +3435,6 @@ stack traceback: end) it('can discard input', function() - clear() -- discard every other normal 'x' command exec_lua [[ n_key = 0 @@ -3461,7 +3460,6 @@ stack traceback: end) it('callback invalid return', function() - clear() -- second key produces an error which removes the callback exec_lua [[ n_call = 0 -- cgit From 846a2019c0e3b3a91477c12ec2c4ac85861b4d67 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 10 Jan 2025 08:03:44 +0800 Subject: vim-patch:9.1.0999: Vim9: leaking finished exception (#31939) Problem: leaking finished exception (after v9.1.0984) Solution: use finish_exception to clean up caught exceptions (Yee Cheng Chin) In Vimscript, v:exception/throwpoint/stacktrace are supposed to reflect the currently caught exception, and be popped after the exception is finished (via endtry, finally, or a thrown exception inside catch). Vim9script does not handle this properly, and leaks them instead. This is clearly visible when launching GVim with menu enabled. A caught exception inside the s:BMShow() in menu.vim would show up when querying `v:stacktrace` even though the exception was already caught and handled. To fix this, just use the same functionality as Vimscript by calling `finish_exception` to properly restore the states. Note that this assumes `current_exception` is always the same as `caught_stack` which believe should be the case. Added tests for this. Also fix up test_stacktrace to properly test the stack restore behavior where we have nested exceptions in catch blocks and to also test the vim9script functionality properly. - Also, remove its dependency on explicitly checking a line number in runtest.vim which is a very fragile way to write tests as any minor change in runtest.vim (shared among all tests) would require changing test_stacktrace.vim. We don't actually need such granularity in the test. closes: vim/vim#16413 https://github.com/vim/vim/commit/2051af1642843426714efc2572c3e270fe0948be Co-authored-by: Yee Cheng Chin --- test/old/testdir/test_stacktrace.vim | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/old/testdir/test_stacktrace.vim b/test/old/testdir/test_stacktrace.vim index 1e47deefdd..9e1f51e1f6 100644 --- a/test/old/testdir/test_stacktrace.vim +++ b/test/old/testdir/test_stacktrace.vim @@ -10,7 +10,7 @@ func Filepath(name) endfunc func AssertStacktrace(expect, actual) - call assert_equal(#{lnum: 581, filepath: Filepath('runtest.vim')}, a:actual[0]) + call assert_equal(Filepath('runtest.vim'), a:actual[0]['filepath']) call assert_equal(a:expect, a:actual[-len(a:expect):]) endfunc @@ -97,6 +97,12 @@ func Test_vstacktrace() call Xfunc1() catch let stacktrace = v:stacktrace + try + call Xfunc1() + catch + let stacktrace_inner = v:stacktrace + endtry + let stacktrace_after = v:stacktrace " should be restored by the exception stack to the previous one endtry call assert_equal([], v:stacktrace) call AssertStacktrace([ @@ -104,9 +110,15 @@ func Test_vstacktrace() \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], stacktrace) + call AssertStacktrace([ + \ #{funcref: funcref('Test_vstacktrace'), lnum: 101, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], stacktrace_inner) + call assert_equal(stacktrace, stacktrace_after) endfunc -func Test_zzz_stacktrace_vim9() +func Test_stacktrace_vim9() let lines =<< trim [SCRIPT] var stacktrace = getstacktrace() assert_notequal([], stacktrace) @@ -122,11 +134,9 @@ func Test_zzz_stacktrace_vim9() assert_true(has_key(d, 'lnum')) endfor endtry + call assert_equal([], v:stacktrace) [SCRIPT] call CheckDefSuccess(lines) - " FIXME: v:stacktrace is not cleared after the exception handling, and this - " test has to be run as the last one because of this. - " call assert_equal([], v:stacktrace) endfunc " vim: shiftwidth=2 sts=2 expandtab -- cgit From a37784ad831fdf31e7eeafdd41f7d3cb81b1a07f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 10 Jan 2025 08:04:02 +0800 Subject: vim-patch:9.1.1000: tests: ruby tests fail with Ruby 3.4 (#31940) Problem: tests: ruby tests fail with Ruby 3.4 Solution: adjust expected output for Ruby 3.4 (Yee Cheng Chin) Vim's Ruby tests relied on explicit matching of output texts which are fragile in design. Ruby 3.4 has changed the output slightly (using 'name' instead of `name', and also using more spaces in dictionary printouts). Modify the Vim tests to be less fragile to such changes. closes: vim/vim#16411 https://github.com/vim/vim/commit/ebea31e454b9a1731cde845226f2c28ca5c097b1 Co-authored-by: Yee Cheng Chin --- test/old/testdir/test_ruby.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/old/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim index d4a3dc3301..4d54d29df7 100644 --- a/test/old/testdir/test_ruby.vim +++ b/test/old/testdir/test_ruby.vim @@ -282,7 +282,7 @@ func Test_ruby_Vim_buffer_get() call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name')) call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name')) call assert_fails('ruby print Vim::Buffer[3].name', - \ "NoMethodError: undefined method `name' for nil") + \ "NoMethodError") %bwipe endfunc @@ -364,7 +364,7 @@ func Test_ruby_Vim_evaluate_dict() redir => l:out ruby d = Vim.evaluate("d"); print d redir END - call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n")) + call assert_equal(['{"a"=>"foo","b"=>123}'], split(substitute(l:out, '\s', '', 'g'), "\n")) endfunc " Test Vim::message({msg}) (display message {msg}) @@ -384,7 +384,7 @@ func Test_ruby_print() call assert_equal('1.23', RubyPrint('1.23')) call assert_equal('Hello World!', RubyPrint('"Hello World!"')) call assert_equal('[1, 2]', RubyPrint('[1, 2]')) - call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})')) + call assert_equal('{"k1"=>"v1","k2"=>"v2"}', substitute(RubyPrint('({"k1" => "v1", "k2" => "v2"})'), '\s', '', 'g')) call assert_equal('true', RubyPrint('true')) call assert_equal('false', RubyPrint('false')) call assert_equal('', RubyPrint('nil')) -- cgit From 87610d82db912cda8877198c25dabbf2bb08f0aa Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 9 Jan 2025 17:58:37 +0100 Subject: fix(decor): set invalid flag for end of invalidated paired marks --- src/nvim/extmark.c | 9 ++++++--- test/functional/api/extmark_spec.lua | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 79eea718f4..5b47afa4b2 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -140,8 +140,9 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool } if (invalid) { - row2 = mt_paired(key) ? marktree_get_altpos(buf->b_marktree, key, NULL).row : row; - buf_put_decor(buf, mt_decor(key), row, row2); + MTPos end = marktree_get_altpos(buf->b_marktree, key, itr); + mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID; + buf_put_decor(buf, mt_decor(key), row, end.row); } else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) { buf_signcols_count_range(buf, row1, row2, 0, kNone); } @@ -394,7 +395,8 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln bool invalidated = false; // Invalidate/delete mark if (!only_copy && !mt_invalid(mark) && mt_invalidate(mark) && !mt_end(mark)) { - MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); + MarkTreeIter enditr[1] = { *itr }; + MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, enditr); // Invalidate unpaired marks in deleted lines and paired marks whose entire // range has been deleted. if ((!mt_paired(mark) && mark.pos.row < u_row) @@ -409,6 +411,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln copy = true; invalidated = true; mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID; + mt_itr_rawkey(enditr).flags |= MT_FLAG_INVALID; marktree_revise_meta(buf->b_marktree, itr, mark); buf_decor_remove(buf, mark.pos.row, endpos.row, mark.pos.col, mt_decor(mark), false); } diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 6a94881093..c8d6110207 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1794,6 +1794,16 @@ describe('API/extmarks', function() eq({}, get_extmark_by_id(ns, 4, {})) end) + it('no crash checking invalided flag of sign pair end key #31856', function() + api.nvim_buf_set_lines(0, 0, 1, false, { '', '' }) + api.nvim_set_option_value('signcolumn', 'auto:2', {}) + set_extmark(ns, 1, 0, 0, { sign_text = 'S1', invalidate = true, end_row = 0 }) + set_extmark(ns, 2, 1, 0, { sign_text = 'S2', end_row = 1 }) + command('d') + api.nvim_buf_clear_namespace(0, ns, 0, -1) + n.assert_alive() + end) + it('can set a URL', function() local url1 = 'https://example.com' local url2 = 'http://127.0.0.1' -- cgit From b06f42b5023b2eec576e5bf22cdacd4c1ee4a939 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 10 Jan 2025 17:17:34 +0800 Subject: vim-patch:df4a7d7: runtime(tiasm): use correct syntax name tiasm in syntax script (#31942) vim-patch:df4a7d7: runtime(tiasm): use correct syntax name tiasm in syntax script closes: vim/vim#16416 https://github.com/vim/vim/commit/df4a7d761740d59a4f911c9e13ac620a459cdea6 Co-authored-by: Wu, Zhenyu --- runtime/syntax/tiasm.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/syntax/tiasm.vim b/runtime/syntax/tiasm.vim index bdadc4a0a7..c79596bdfe 100644 --- a/runtime/syntax/tiasm.vim +++ b/runtime/syntax/tiasm.vim @@ -99,4 +99,4 @@ hi def link tiasmIdentifier Identifier hi def link tiasmType Type hi def link tiasmFunction Function -let b:current_syntax = "lineartiasm" +let b:current_syntax = "tiasm" -- cgit From 0717dfbfaf36887dab277527eb0a93bf2182297c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 10 Jan 2025 11:42:04 -0800 Subject: refactor(api): deprecate nvim_notify #31938 Problem: The `nvim_notify` API (note: unrelated to `vim.notify()` Lua API) was not given any real motivation in https://github.com/neovim/neovim/pull/13843 There are, and were, idiomatic and ergonomic alternatives already. Solution: Deprecate `nvim_notify`. --- runtime/doc/api.txt | 11 ----------- runtime/doc/deprecated.txt | 1 + runtime/lua/vim/_meta/api.lua | 12 ++++-------- src/nvim/api/deprecated.c | 21 +++++++++++++++++++++ src/nvim/api/vim.c | 20 -------------------- test/functional/api/deprecated_spec.lua | 21 +++++++++++++++++++++ test/functional/api/vim_spec.lua | 12 ------------ 7 files changed, 47 insertions(+), 51 deletions(-) create mode 100644 test/functional/api/deprecated_spec.lua diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 9e84dd40ac..572e5e4267 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1094,17 +1094,6 @@ nvim_load_context({dict}) *nvim_load_context()* Parameters: ~ • {dict} |Context| map. -nvim_notify({msg}, {log_level}, {opts}) *nvim_notify()* - Notify the user with a message - - Relays the call to vim.notify . By default forwards your message in the - echo area but can be overridden to trigger desktop notifications. - - Parameters: ~ - • {msg} Message to display to the user - • {log_level} The log level - • {opts} Reserved for future use. - nvim_open_term({buffer}, {opts}) *nvim_open_term()* Open a terminal instance in a buffer diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index dbdb8f541b..4f320aeab3 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -16,6 +16,7 @@ Deprecated features DEPRECATED IN 0.11 *deprecated-0.11* API +• nvim_notify() Use |nvim_echo()| or `nvim_exec_lua("vim.notify(...)", ...)` instead. • nvim_subscribe() Plugins must maintain their own "multicast" channels list. • nvim_unsubscribe() Plugins must maintain their own "multicast" channels list. • nvim_out_write() Use |nvim_echo()|. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index b5d8a0937d..8930f131f6 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1647,14 +1647,10 @@ function vim.api.nvim_list_wins() end --- @return any function vim.api.nvim_load_context(dict) end ---- Notify the user with a message ---- ---- Relays the call to vim.notify . By default forwards your message in the ---- echo area but can be overridden to trigger desktop notifications. ---- ---- @param msg string Message to display to the user ---- @param log_level integer The log level ---- @param opts table Reserved for future use. +--- @deprecated +--- @param msg string +--- @param log_level integer +--- @param opts table --- @return any function vim.api.nvim_notify(msg, log_level, opts) end diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 5150aeebec..b9e7d7143a 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,3 +1,5 @@ +// Island of misfit toys. + #include #include #include @@ -896,3 +898,22 @@ void nvim_err_writeln(String str) { write_msg(str, true, true); } + +/// @deprecated +/// +/// Use `nvim_echo` or `nvim_exec_lua("vim.notify(...)", ...)` instead. +/// +/// @param msg Message to display to the user +/// @param log_level The log level +/// @param opts Reserved for future use. +/// @param[out] err Error details, if any +Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) + FUNC_API_SINCE(7) FUNC_API_DEPRECATED_SINCE(13) +{ + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, STRING_OBJ(msg)); + ADD_C(args, INTEGER_OBJ(log_level)); + ADD_C(args, DICT_OBJ(opts)); + + return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e03a260280..faf6c0567c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -516,26 +516,6 @@ Object nvim_exec_lua(String code, Array args, Arena *arena, Error *err) return nlua_exec(code, args, kRetObject, arena, err); } -/// Notify the user with a message -/// -/// Relays the call to vim.notify . By default forwards your message in the -/// echo area but can be overridden to trigger desktop notifications. -/// -/// @param msg Message to display to the user -/// @param log_level The log level -/// @param opts Reserved for future use. -/// @param[out] err Error details, if any -Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) - FUNC_API_SINCE(7) -{ - MAXSIZE_TEMP_ARRAY(args, 3); - ADD_C(args, STRING_OBJ(msg)); - ADD_C(args, INTEGER_OBJ(log_level)); - ADD_C(args, DICT_OBJ(opts)); - - return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); -} - /// Calculates the number of display cells occupied by `text`. /// Control characters including [] count as one cell. /// diff --git a/test/functional/api/deprecated_spec.lua b/test/functional/api/deprecated_spec.lua new file mode 100644 index 0000000000..2efcfda873 --- /dev/null +++ b/test/functional/api/deprecated_spec.lua @@ -0,0 +1,21 @@ +-- Island of misfit toys. +--- @diagnostic disable: deprecated + +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +describe('deprecated', function() + before_each(n.clear) + + describe('nvim_notify', function() + it('can notify a info message', function() + n.api.nvim_notify('hello world', 2, {}) + end) + + it('can be overridden', function() + n.command('lua vim.notify = function(...) return 42 end') + t.eq(42, n.api.nvim_exec_lua("return vim.notify('Hello world')", {})) + n.api.nvim_notify('hello world', 4, {}) + end) + end) +end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e0ab31f702..044213d83a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -781,18 +781,6 @@ describe('API', function() end) end) - describe('nvim_notify', function() - it('can notify a info message', function() - api.nvim_notify('hello world', 2, {}) - end) - - it('can be overridden', function() - command('lua vim.notify = function(...) return 42 end') - eq(42, api.nvim_exec_lua("return vim.notify('Hello world')", {})) - api.nvim_notify('hello world', 4, {}) - end) - end) - describe('nvim_input', function() it('Vimscript error: does NOT fail, updates v:errmsg', function() local status, _ = pcall(api.nvim_input, ':call bogus_fn()') -- cgit From cb02c20569b56545a1657d4f7f8f29171f1037d7 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 10 Jan 2025 12:25:46 -0800 Subject: refactor(treesitter.foldexpr): remove unused parse_injections parameter --- runtime/lua/vim/treesitter/_fold.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d16013eca2..7f1d1b14d5 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -69,8 +69,7 @@ end ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? 0-indexed, exclusive ----@param parse_injections? boolean -local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) +local function compute_folds_levels(bufnr, info, srow, erow) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) @@ -79,7 +78,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) return end - parser:parse(parse_injections and { srow, erow } or nil) + parser:parse() local enter_counts = {} ---@type table local leave_counts = {} ---@type table -- cgit From 37c77ab46baaeadb7c3cc5f3b77bd8ca1d7cd0da Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Fri, 10 Jan 2025 23:43:45 +0100 Subject: fix(messages): attaching/detaching ext_messages causes asserts #31952 Problem: Assert hit related to message kind, which is reset after a ext_messages UI is forcibly detached, so the assertion is expectedly false. Assert hit related to message grid variables after an ext_messages UI attaches while message grid is scrolled. Solution: Don't check message kind assertion if no ext_messages UI is attached. Flush message grid when first/last ext_messages UI attaches/detaches. --- src/nvim/message.c | 2 +- src/nvim/ui.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index e288353ddc..12d980f58f 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -312,7 +312,7 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) } else { msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear); } - assert(msg_ext_kind == kind); + assert(!ui_has(kUIMessages) || msg_ext_kind == kind); } if (history && kv_size(hl_msg)) { add_msg_hist_multihl(NULL, 0, 0, true, hl_msg); diff --git a/src/nvim/ui.c b/src/nvim/ui.c index d242baf83b..51815c36e1 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -223,10 +223,10 @@ void ui_refresh(void) // Reset 'cmdheight' for all tabpages when ext_messages toggles. if (had_message != ui_ext[kUIMessages]) { set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0); - command_height(); FOR_ALL_TABS(tp) { tp->tp_ch_used = had_message; } + msg_scroll_flush(); } if (!ui_active()) { -- cgit From aa2b44fbb07f3ab4dd00ea4a3ae7c5d31bc20a9d Mon Sep 17 00:00:00 2001 From: Guilherme Soares <48023091+guilhas07@users.noreply.github.com> Date: Fri, 10 Jan 2025 22:46:19 +0000 Subject: fix(treesitter): don't return error message on success #31955 Problem: The `vim.treesitter.language.add` function returns a error message even when it succeeds. Solution: Don't return error message on success. --- runtime/lua/vim/treesitter/language.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 446051dfd7..238a078703 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -133,8 +133,9 @@ function M.add(lang, opts) path = paths[1] end - return loadparser(path, lang, symbol_name) or nil, - string.format('Cannot load parser %s for language "%s"', path, lang) + local res = loadparser(path, lang, symbol_name) + return res, + res == nil and string.format('Cannot load parser %s for language "%s"', path, lang) or nil end --- @param x string|string[] -- cgit From dcaf9a60e9c0b3b4f8439897b344b4e632802beb Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 10 Jan 2025 20:03:35 +0100 Subject: vim-patch:51754c8: runtime(editorconfig): set omnifunc to syntaxcomplete func closes: vim/vim#16419 https://github.com/vim/vim/commit/51754c8a498c39592250a077f56db89dd261995d Co-authored-by: Yochem van Rosmalen --- runtime/ftplugin/editorconfig.vim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/ftplugin/editorconfig.vim b/runtime/ftplugin/editorconfig.vim index 6d437351eb..1693a95c0b 100644 --- a/runtime/ftplugin/editorconfig.vim +++ b/runtime/ftplugin/editorconfig.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: EditorConfig " Maintainer: Riley Bruins -" Last Change: 2024 Jul 06 +" Last Change: 2025 Jan 10 if exists('b:did_ftplugin') finish @@ -10,4 +10,6 @@ let b:did_ftplugin = 1 setl comments=:#,:; commentstring=#\ %s -let b:undo_ftplugin = 'setl com< cms<' +setl omnifunc=syntaxcomplete#Complete + +let b:undo_ftplugin = 'setl com< cms< ofu<' -- cgit From fbe546e25d21f3184814d696c329d23d146bd615 Mon Sep 17 00:00:00 2001 From: glepnir Date: Sat, 11 Jan 2025 07:58:45 +0800 Subject: vim-patch:9.1.0996: ComplMatchIns may highlight wrong text (#31931) Problem: ComplMatchIns may highlight wrong text Solution: don't highlight in case of fuzzy match, skip-highlight when not inserting anything (glepnir) closes: vim/vim#16404 https://github.com/vim/vim/commit/e890887b8052561ac5f8dce218e578ed28599cc6 --- src/nvim/insexpand.c | 6 +++++- test/functional/ui/popupmenu_spec.lua | 33 +++++++++++++++++++++++++++++++++ test/old/testdir/test_popup.vim | 14 ++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 419c806592..d1559b00f1 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -966,7 +966,11 @@ static void ins_compl_insert_bytes(char *p, int len) /// -1 mean normal item. int ins_compl_col_range_attr(int col) { - if (col >= compl_col && col < compl_ins_end_col) { + if (get_cot_flags() & kOptCotFlagFuzzy) { + return -1; + } + + if (col >= (compl_col + (int)compl_leader.size) && col < compl_ins_end_col) { return syn_name2attr("ComplMatchIns"); } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 60d59190ce..66b62341a9 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5853,6 +5853,39 @@ describe('builtin popupmenu', function() {2:-- INSERT --} | ]]) feed('') + + -- Does not highlight the compl leader + command('set cot+=menuone,noselect') + feed('S') + local pum_start = [[ + {10:^ }| + {n:foo }{1: }| + {n:bar }{1: }| + {n:你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]] + screen:expect(pum_start) + feed('f') + screen:expect([[ + {10:f}{9:oo}{10:^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('') + + command('set cot+=fuzzy') + feed('S') + screen:expect(pum_start) + feed('f') + screen:expect([[ + {10:foo^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('') end) end end diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index e902ea3bc2..7f570182e4 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1817,6 +1817,20 @@ func Test_pum_matchins_highlight_combine() call VerifyScreenDump(buf, 'Test_pum_matchins_combine_06', {}) call term_sendkeys(buf, "\") + " Does not highlight the compl leader + call TermWait(buf) + call term_sendkeys(buf, ":set cot+=menuone,noselect\") + call TermWait(buf) + call term_sendkeys(buf, "S\\f\") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_07', {}) + call term_sendkeys(buf, "\\") + + call term_sendkeys(buf, ":set cot+=fuzzy\") + call TermWait(buf) + call term_sendkeys(buf, "S\\f\") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_08', {}) + call term_sendkeys(buf, "\\") + call StopVimInTerminal(buf) endfunc -- cgit From df45b336f5c097609909dbc9f1e37e88961886d9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 11 Jan 2025 08:27:50 +0800 Subject: vim-patch:695522d: runtime(vim): Update base-syntax, highlight literal string quote escape (#31957) Match the '' escape sequence in literal strings. These were previously ending the current string and starting another concatenated literal string. closes: vim/vim#16415 https://github.com/vim/vim/commit/695522dea3703cf1b4cd4a894ca9a745a0d2756f Co-authored-by: Doug Kearns --- runtime/syntax/vim.vim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 12798201e2..edc69b907c 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -553,19 +553,21 @@ syn region vimPatSepZone oneline contained matchgroup=vimPatSepZ start="\\%\ syn region vimPatRegion contained transparent matchgroup=vimPatSepR start="\\[z%]\=(" end="\\)" contains=@vimSubstList oneline syn match vimNotPatSep contained "\\\\" syn cluster vimStringGroup contains=vimEscape,vimEscapeBrace,vimPatSep,vimNotPatSep,vimPatSepErr,vimPatSepZone,@Spell -syn region vimString oneline keepend start=+[^a-zA-Z>\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ contains=@vimStringGroup extend -syn region vimString oneline keepend start=+[^a-zA-Z>\\@]'+lc=1 end=+'+ extend +syn region vimString oneline keepend matchgroup=vimString start=+[^a-zA-Z>\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ contains=@vimStringGroup extend +syn region vimString oneline matchgroup=vimString start=+[^a-zA-Z>\\@]'+lc=1 end=+'+ contains=vimQuoteEscape extend "syn region vimString oneline start="\s/\s*\A"lc=1 skip="\\\\\|\\+" end="/" contains=@vimStringGroup " see tst45.vim syn match vimString contained +"[^"]*\\$+ skipnl nextgroup=vimStringCont syn match vimStringCont contained +\(\\\\\|.\)\{-}[^\\]"+ + syn match vimEscape contained "\\." " syn match vimEscape contained +\\[befnrt\"]+ syn match vimEscape contained "\\\o\{1,3}\|\\[xX]\x\{1,2}\|\\u\x\{1,4}\|\\U\x\{1,8}" syn match vimEscape contained "\\<" contains=vimNotation syn match vimEscape contained "\\<\*[^>]*>\=>" +syn match vimQuoteEscape contained "''" -syn region vimString oneline start=+$'+ skip=+''+ end=+'+ contains=@vimStringInterpolation extend -syn region vimString oneline start=+$"+ end=+"+ contains=@vimStringGroup,@vimStringInterpolation extend +syn region vimString oneline matchgroup=vimString start=+$'+ skip=+''+ end=+'+ contains=vimQuoteEscape,@vimStringInterpolation extend +syn region vimString oneline matchgroup=vimString start=+$"+ end=+"+ contains=@vimStringGroup,@vimStringInterpolation extend syn region vimStringInterpolationExpr oneline contained matchgroup=vimSep start=+{+ end=+}+ contains=@vimExprList syn match vimStringInterpolationBrace contained "{{" syn match vimStringInterpolationBrace contained "}}" @@ -1399,6 +1401,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimPattern Type hi def link vimPlainMark vimMark hi def link vimPlainRegister vimRegister + hi def link vimQuoteEscape vimEscape hi def link vimRegister SpecialChar hi def link vimScriptDelim Comment hi def link vimSearchDelim Statement -- cgit From c060a6ea640eb433197ec554ff7cf6469ee1c0e7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 11 Jan 2025 09:08:01 +0800 Subject: vim-patch:9.1.1001: ComplMatchIns highlight hard to read on light background (#31958) Problem: ComplMatchIns highlight hard to read on light background (after v9.1.0996) Solution: define the highlighting group cleared, it should be configured in colorschemes separately (glepnir) closes: vim/vim#16414 https://github.com/vim/vim/commit/ad409876d9cf7e565f99c5e21b9e2e400a83a4d4 Co-authored-by: glepnir --- src/nvim/option_vars.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index f4d0a9a4b0..9975e7870f 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -20,7 +20,7 @@ "R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind," \ "]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel," \ "_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm," \ - "Z:StatusLineTermNC,g:MsgArea,0:Whitespace,I:NormalNC" + "Z:StatusLineTermNC,g:MsgArea,h:ComplMatchIns,0:Whitespace,I:NormalNC" // Default values for 'errorformat'. // The "%f|%l| %m" one is used for when the contents of the quickfix window is -- cgit From 6a425e7045cca609d95612c0f2cd08d0265238a9 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sun, 24 Nov 2024 11:29:39 +0100 Subject: docs: misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Axel Co-authored-by: Colin Kennedy Co-authored-by: Daiki Noda Co-authored-by: Evgeni Chasnovski Co-authored-by: Jean-Jacq du Plessis <1030058+jj-du-plessis@users.noreply.github.com> Co-authored-by: Juan Giordana Co-authored-by: Lincoln Wallace Co-authored-by: Matti Hellström Co-authored-by: Steven Locorotondo Co-authored-by: Yochem van Rosmalen Co-authored-by: glepnir Co-authored-by: ifish --- BUILD.md | 20 +++++++++++--------- CMakePresets.json | 6 +++--- INSTALL.md | 2 +- Makefile | 3 +++ runtime/doc/builtin.txt | 2 +- runtime/doc/lsp.txt | 6 +++--- runtime/doc/lua-guide.txt | 2 +- runtime/doc/lua.txt | 4 ++-- runtime/doc/options.txt | 4 ++-- runtime/doc/treesitter.txt | 2 +- runtime/lua/vim/_editor.lua | 2 +- runtime/lua/vim/_meta/options.lua | 4 ++-- runtime/lua/vim/_meta/vimfn.lua | 2 +- runtime/lua/vim/_options.lua | 2 +- runtime/lua/vim/lsp.lua | 2 +- runtime/lua/vim/lsp/diagnostic.lua | 2 +- src/nvim/eval.lua | 1 + src/nvim/options.lua | 4 ++-- src/nvim/window.c | 2 +- test/unit/mbyte_spec.lua | 6 +++--- test/unit/vterm_spec.lua | 4 ++-- 21 files changed, 44 insertions(+), 38 deletions(-) diff --git a/BUILD.md b/BUILD.md index f4596723fb..7ded17138a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -5,12 +5,13 @@ 1. Install [build prerequisites](#build-prerequisites) on your system 2. `git clone https://github.com/neovim/neovim` -3. `cd neovim && make CMAKE_BUILD_TYPE=RelWithDebInfo` +3. `cd neovim` - If you want the **stable release**, also run `git checkout stable`. +4. `make CMAKE_BUILD_TYPE=RelWithDebInfo` - If you want to install to a custom location, set `CMAKE_INSTALL_PREFIX`. See also [INSTALL.md](./INSTALL.md#install-from-source). - On BSD, use `gmake` instead of `make`. - To build on Windows, see the [Building on Windows](#building-on-windows) section. _MSVC (Visual Studio) is recommended._ -4. `sudo make install` +5. `sudo make install` - Default install location is `/usr/local` - On Debian/Ubuntu, instead of `sudo make install`, you can try `cd build && cpack -G DEB && sudo dpkg -i nvim-linux64.deb` to build DEB-package and install it. This helps ensure clean removal of installed files. Note: This is an unsupported, "best-effort" feature of the Nvim build. @@ -131,7 +132,8 @@ https://github.com/cascent/neovim-cygwin was built on Cygwin 2.9.0. Newer `libuv 1. From the MSYS2 shell, install these packages: ``` pacman -S \ - mingw-w64-ucrt-x86_64-{gcc,cmake,make,ninja,diffutils} + mingw-w64-ucrt-x86_64-gcc \ + mingw-w64-x86_64-{cmake,make,ninja,diffutils} ``` 2. From the Windows Command Prompt (`cmd.exe`), set up the `PATH` and build. @@ -292,13 +294,13 @@ Platform-specific requirements are listed below. ### Ubuntu / Debian ```sh -sudo apt-get install ninja-build gettext cmake unzip curl build-essential +sudo apt-get install ninja-build gettext cmake curl build-essential ``` ### RHEL / Fedora ``` -sudo dnf -y install ninja-build cmake gcc make unzip gettext curl glibc-gconv-extra +sudo dnf -y install ninja-build cmake gcc make gettext curl glibc-gconv-extra ``` ### openSUSE @@ -310,13 +312,13 @@ sudo zypper install ninja cmake gcc-c++ gettext-tools curl ### Arch Linux ``` -sudo pacman -S base-devel cmake unzip ninja curl +sudo pacman -S base-devel cmake ninja curl ``` ### Alpine Linux ``` -apk add build-base cmake coreutils curl unzip gettext-tiny-dev +apk add build-base cmake coreutils curl gettext-tiny-dev ``` ### Void Linux @@ -380,7 +382,7 @@ or a specific SHA1 like `--override-input neovim-src github:neovim/neovim/89dc8f ### FreeBSD ``` -sudo pkg install cmake gmake sha unzip wget gettext curl +sudo pkg install cmake gmake sha wget gettext curl ``` If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 hash is `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`, then this is your issue (that's the `sha256sum` of an empty file). @@ -388,7 +390,7 @@ If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 h ### OpenBSD ```sh -doas pkg_add gmake cmake unzip curl gettext-tools +doas pkg_add gmake cmake curl gettext-tools ``` Build can sometimes fail when using the top level `Makefile`, apparently due to some third-party component (see [#2445-comment](https://github.com/neovim/neovim/issues/2445#issuecomment-108124236)). The following instructions use CMake: diff --git a/CMakePresets.json b/CMakePresets.json index b863d88608..b47f509d5f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -10,7 +10,7 @@ { "name": "default", "displayName": "RelWithDebInfo", - "description": "Enables optimizations (-O2) with debug information", + "description": "Enables optimizations with debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }, @@ -19,7 +19,7 @@ { "name": "debug", "displayName": "Debug", - "description": "Disables optimizations (-O0), enables debug information", + "description": "No optimizations, enables debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }, @@ -28,7 +28,7 @@ { "name": "release", "displayName": "Release", - "description": "Same as RelWithDebInfo, but disables debug information", + "description": "Optimized for performance, disables debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }, diff --git a/INSTALL.md b/INSTALL.md index 509213fffc..807922e2e3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -302,7 +302,7 @@ Neovim nightly and stable are available on the [snap store](https://snapcraft.io **Stable Builds** ```sh -sudo snap install --beta nvim --classic +sudo snap install nvim --classic ``` **Nightly Builds** diff --git a/Makefile b/Makefile index fe83f302e8..9154cd8782 100644 --- a/Makefile +++ b/Makefile @@ -133,6 +133,9 @@ generated-sources benchmark $(FORMAT) $(LINT) $(TEST) doc: | build/.ran-cmake test: $(TEST) +# iwyu-fix-includes can be downloaded from +# https://github.com/include-what-you-use/include-what-you-use/blob/master/fix_includes.py. +# Create a iwyu-fix-includes shell script in your $PATH that invokes the python script. iwyu: build/.ran-cmake $(CMAKE) --preset iwyu $(CMAKE) --build build > build/iwyu.log diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index f466dde861..6e05dd24d2 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -8282,7 +8282,7 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) *search()* • {skip} (`string|function?`) Return: ~ - (`any`) + (`integer`) searchcount([{options}]) *searchcount()* Get or update the last search count, like what is displayed diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 16e6abe294..e8270123d7 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -32,7 +32,7 @@ Follow these steps to get LSP features: Example: >lua vim.lsp.config['luals'] = { -- Command and arguments to start the server. - cmd = { 'lua-language-server' } + cmd = { 'lua-language-server' }, -- Filetypes to automatically attach to. filetypes = { 'lua' }, @@ -93,7 +93,7 @@ Given: >lua multilineTokenSupport = true, } } - } + }, root_markers = { '.git' }, }) @@ -878,7 +878,7 @@ foldexpr({lnum}) *vim.lsp.foldexpr()* To use, check for the "textDocument/foldingRange" capability in an |LspAttach| autocommand. Example: >lua - vim.api.nvim_create_autocommand('LspAttach', { + vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) local client = vim.lsp.get_client_by_id(args.data.client_id) if client:supports_method('textDocument/foldingRange') then diff --git a/runtime/doc/lua-guide.txt b/runtime/doc/lua-guide.txt index b40d5a0791..d0d148f689 100644 --- a/runtime/doc/lua-guide.txt +++ b/runtime/doc/lua-guide.txt @@ -153,7 +153,7 @@ its functions if this succeeds and prints an error message otherwise: if not ok then print("Module had an error") else - mymod.function() + mymod.func() end < In contrast to |:source|, |require()| not only searches through all `lua/` directories diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 9df9bc3ab5..a84a364847 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1299,7 +1299,7 @@ global value of a |global-local| option, see |:setglobal|. A special interface |vim.opt| exists for conveniently interacting with list- -and map-style option from Lua: It allows accessing them as Lua tables and +and map-style options from Lua: It allows accessing them as Lua tables and offers object-oriented method for adding and removing entries. Examples: ~ @@ -1805,7 +1805,7 @@ vim.system({cmd}, {opts}, {on_exit}) *vim.system()* -- Runs synchronously: local obj = vim.system({'echo', 'hello'}, { text = true }):wait() - -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } + -- { code = 0, signal = 0, stdout = 'hello\n', stderr = '' } < See |uv.spawn()| for more details. Note: unlike |uv.spawn()|, vim.system diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c2ed19f34f..80b391d8c9 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1560,8 +1560,8 @@ A jump table for the options with a short description can be found at |Q_op|. "menu" or "menuone". No effect if "longest" is present. noselect Same as "noinsert", except that no menu item is - pre-selected. If both "noinsert" and "noselect" are present, - "noselect" has precedence. + pre-selected. If both "noinsert" and "noselect" are + present, "noselect" has precedence. fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 538435e5e8..80d8f92af2 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -70,7 +70,7 @@ adds arbitrary metadata and conditional data to a match. Queries are written in a lisp-like language documented in https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax -Note: The predicates listed there page differ from those Nvim supports. See +Note: The predicates listed there differ from those Nvim supports. See |treesitter-predicates| for a complete list of predicates supported by Nvim. Nvim looks for queries as `*.scm` files in a `queries` directory under diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index d4e6280b06..4b28b63746 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -93,7 +93,7 @@ local utfs = { --- --- -- Runs synchronously: --- local obj = vim.system({'echo', 'hello'}, { text = true }):wait() ---- -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } +--- -- { code = 0, signal = 0, stdout = 'hello\n', stderr = '' } --- --- ``` --- diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e5cea884c5..940441a849 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1087,8 +1087,8 @@ vim.go.cia = vim.go.completeitemalign --- "menu" or "menuone". No effect if "longest" is present. --- --- noselect Same as "noinsert", except that no menu item is ---- pre-selected. If both "noinsert" and "noselect" are present, ---- "noselect" has precedence. +--- pre-selected. If both "noinsert" and "noselect" are +--- present, "noselect" has precedence. --- --- fuzzy Enable `fuzzy-matching` for completion candidates. This --- allows for more flexible and intuitive matching, where diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 031b109b38..6316ab2bfc 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -7537,7 +7537,7 @@ function vim.fn.screenstring(row, col) end --- @param stopline? integer --- @param timeout? integer --- @param skip? string|function ---- @return any +--- @return integer function vim.fn.search(pattern, flags, stopline, timeout, skip) end --- Get or update the last search count, like what is displayed diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index dc37595578..8338c5ead7 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -768,7 +768,7 @@ end --- --- --- A special interface |vim.opt| exists for conveniently interacting with list- ---- and map-style option from Lua: It allows accessing them as Lua tables and +--- and map-style options from Lua: It allows accessing them as Lua tables and --- offers object-oriented method for adding and removing entries. --- --- Examples: ~ diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5b92926a21..23f4e104d0 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1389,7 +1389,7 @@ end --- |LspAttach| autocommand. Example: --- --- ```lua ---- vim.api.nvim_create_autocommand('LspAttach', { +--- vim.api.nvim_create_autocmd('LspAttach', { --- callback = function(args) --- local client = vim.lsp.get_client_by_id(args.data.client_id) --- if client:supports_method('textDocument/foldingRange') then diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 9a879d9f38..8c1f3f10d4 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -208,7 +208,7 @@ end --- @param uri string --- @param client_id? integer ---- @param diagnostics vim.Diagnostic[] +--- @param diagnostics lsp.Diagnostic[] --- @param is_pull boolean local function handle_diagnostics(uri, client_id, diagnostics, is_pull) local fname = vim.uri_to_fname(uri) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4ce1960dcf..5c7b2791e8 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -9178,6 +9178,7 @@ M.funcs = { { 'timeout', 'integer' }, { 'skip', 'string|function' }, }, + returns = 'integer', signature = 'search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])', }, searchcount = { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 3142c30080..863f875d9d 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1531,8 +1531,8 @@ return { "menu" or "menuone". No effect if "longest" is present. noselect Same as "noinsert", except that no menu item is - pre-selected. If both "noinsert" and "noselect" are present, - "noselect" has precedence. + pre-selected. If both "noinsert" and "noselect" are + present, "noselect" has precedence. fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where diff --git a/src/nvim/window.c b/src/nvim/window.c index 8c8df72590..68f45ec5ba 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6357,7 +6357,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) fr = curfr; // put fr at window that grows } - // If not enough room thn move as far as we can + // If not enough room then move as far as we can offset = MIN(offset, room); // No room at all, quit. diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua index bdc111de2c..2c52aa9217 100644 --- a/test/unit/mbyte_spec.lua +++ b/test/unit/mbyte_spec.lua @@ -58,11 +58,11 @@ describe('mbyte', function() lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) local str = ffi.string(buf) if 1 > 2 then -- for debugging - local tabel = {} + local tbl = {} for i = 1, #str do - table.insert(tabel, string.format('0x%02x', string.byte(str, i))) + table.insert(tbl, string.format('0x%02x', string.byte(str, i))) end - print('{ ' .. table.concat(tabel, ', ') .. ' }') + print('{ ' .. table.concat(tbl, ', ') .. ' }') io.stdout:flush() end return { str, firstc[0] } diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index db0aa3c575..0bf4bf70f8 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -1131,7 +1131,7 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) push('\x1b[0F', vt) cursor(0, 0, state) - -- Cursor Horizonal Absolute + -- Cursor Horizontal Absolute push('\n', vt) cursor(1, 0, state) push('\x1b[20G', vt) @@ -3104,7 +3104,7 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) screen ) - -- Outputing CJK doublewidth in 80th column should wraparound to next line and not crash" + -- Outputting CJK doublewidth in 80th column should wraparound to next line and not crash" reset(nil, screen) push('\x1b[80G\xEF\xBC\x90', vt) screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) -- cgit From 9e0d40f7e45f483e54d38be1266f63240808b4b0 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 11 Jan 2025 10:58:10 +0100 Subject: vim-patch:668e9f2: runtime(filetype): don't detect string interpolation as angular fixes: vim/vim#16375 https://github.com/vim/vim/commit/668e9f24037fc7c362ffdf5fc1d5c5b1a8b0e855 Co-authored-by: Christian Brabandt --- runtime/lua/vim/filetype/detect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 2d989fdbac..30a9951f6a 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -757,7 +757,7 @@ function M.html(_, bufnr) if matchregex( line, - [[@\(if\|for\|defer\|switch\)\|\*\(ngIf\|ngFor\|ngSwitch\|ngTemplateOutlet\)\|ng-template\|ng-content\|{{.*}}]] + [[@\(if\|for\|defer\|switch\)\|\*\(ngIf\|ngFor\|ngSwitch\|ngTemplateOutlet\)\|ng-template\|ng-content]] ) then return 'htmlangular' -- cgit From a119aae4d3deebea453ccd7c2dea814aed2c9f21 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 11 Jan 2025 12:54:43 -0600 Subject: feat(diagnostic)!: filter diagnostics by severity before passing to handlers (#30070) BREAKING CHANGE: This changes the list of diagnostics that are passed to a diagnostic handler. If a handler is already filtering by severity itself then this won't break anything, since the handler's filtering will become a no-op. But handlers which depend on receiving the full list of diagnostics may break. Note that diagnostics are only filtered if the handler's configuration has the `severity` option set. If `severity` is not set, the handler still receives the full list of diagnostics. --- runtime/doc/diagnostic.txt | 7 +++++++ runtime/doc/news.txt | 2 ++ runtime/lua/vim/diagnostic.lua | 18 ++---------------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 3437717467..7d97a18437 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -93,6 +93,10 @@ The {opts} table passed to a handler is the full set of configuration options values in the table are already resolved (i.e. if a user specifies a function for a config option, the function has already been evaluated). +If a diagnostic handler is configured with a "severity" key then the list of +diagnostics passed to that handler will be filtered using the value of that +key (see example below). + Nvim provides these handlers by default: "virtual_text", "signs", and "underline". @@ -119,6 +123,9 @@ with |vim.notify()|: >lua vim.diagnostic.config({ ["my/notify"] = { log_level = vim.log.levels.INFO + + -- This handler will only receive "error" diagnostics. + severity = vim.diagnostic.severity.ERROR, } }) < diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 181ae317e1..810f40180a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -68,6 +68,8 @@ DIAGNOSTICS • The "underline" diagnostics handler sorts diagnostics by severity when using the "severity_sort" option. +• Diagnostics are filtered by severity before being passed to a diagnostic + handler |diagnostic-handlers|. EDITOR diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 90f967fe79..6466c7d6e8 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1395,10 +1395,6 @@ M.handlers.signs = { return end - if opts.signs and opts.signs.severity then - diagnostics = filter_by_severity(opts.signs.severity, diagnostics) - end - -- 10 is the default sign priority when none is explicitly specified local priority = opts.signs and opts.signs.priority or 10 local get_priority = severity_to_extmark_priority(priority, opts) @@ -1501,10 +1497,6 @@ M.handlers.underline = { return end - if opts.underline and opts.underline.severity then - diagnostics = filter_by_severity(opts.underline.severity, diagnostics) - end - local ns = M.get_namespace(namespace) if not ns.user_data.underline_ns then ns.user_data.underline_ns = @@ -1565,7 +1557,6 @@ M.handlers.virtual_text = { return end - local severity --- @type vim.diagnostic.SeverityFilter? if opts.virtual_text then if opts.virtual_text.format then diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics) @@ -1576,9 +1567,6 @@ M.handlers.virtual_text = { then diagnostics = prefix_source(diagnostics) end - if opts.virtual_text.severity then - severity = opts.virtual_text.severity - end end local ns = M.get_namespace(namespace) @@ -1590,9 +1578,6 @@ M.handlers.virtual_text = { local virt_text_ns = ns.user_data.virt_text_ns local buffer_line_diagnostics = diagnostic_lines(diagnostics) for line, line_diagnostics in pairs(buffer_line_diagnostics) do - if severity then - line_diagnostics = filter_by_severity(severity, line_diagnostics) - end local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text) if virt_texts then @@ -1797,7 +1782,8 @@ function M.show(namespace, bufnr, diagnostics, opts) for handler_name, handler in pairs(M.handlers) do if handler.show and opts_res[handler_name] then - handler.show(namespace, bufnr, diagnostics, opts_res) + local filtered = filter_by_severity(opts_res[handler_name].severity, diagnostics) + handler.show(namespace, bufnr, filtered, opts_res) end end end -- cgit From 88dca6a83593a0cf33866addf36cad2e847a2899 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 12 Jan 2025 07:43:58 +0800 Subject: vim-patch:partial:9598a63: runtime(doc): add package- helptags for included packages (#31972) Improve how to find the justify package closes: vim/vim#16420 https://github.com/vim/vim/commit/9598a6369bce32d3da831e8968caf4625985ac3c Co-authored-by: Christian Brabandt Co-authored-by: Peter Benjamin --- runtime/doc/quickfix.txt | 4 ++-- runtime/doc/terminal.txt | 2 +- runtime/doc/usr_02.txt | 7 +++++++ runtime/doc/usr_05.txt | 2 +- runtime/doc/usr_25.txt | 6 +++--- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index a291c0277d..70082c7835 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -538,9 +538,9 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: < Otherwise it works the same as `:ldo`. FILTERING A QUICKFIX OR LOCATION LIST: - *cfilter-plugin* *:Cfilter* *:Lfilter* + *cfilter-plugin* *:Cfilter* *:Lfilter* *package-cfilter* If you have too many entries in a quickfix list, you can use the cfilter -plugin to reduce the number of entries. Load the plugin with: > +plugin to reduce the number of entries. Load the plugin with: >vim packadd cfilter diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index 7d79669ed8..a7f278990c 100644 --- a/runtime/doc/terminal.txt +++ b/runtime/doc/terminal.txt @@ -207,7 +207,7 @@ Use |jobwait()| to check if the terminal job has finished: >vim let running = jobwait([&channel], 0)[0] == -1 < ============================================================================== -:Termdebug plugin *terminal-debug* +:Termdebug plugin *terminal-debug* *terminal-debugger* *package-termdebug* The Terminal debugging plugin can be used to debug a program with gdb and view the source code in a Vim window. Since this is completely contained inside diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt index 1fc612de26..f8cfcbe547 100644 --- a/runtime/doc/usr_02.txt +++ b/runtime/doc/usr_02.txt @@ -684,6 +684,13 @@ Summary: *help-summary* > :help E128 < takes you to the |:function| command +27) Documenction for packages distributed with Vim have the form package-. + So > + :help package-termdebug +< + will bring you to the help section for the included termdebug plugin and + how to enable it. + ============================================================================== diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index 698d1207d3..d75438cd22 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -235,7 +235,7 @@ an archive or as a repository. For an archive you can follow these steps: else. -Adding nohlsearch package *nohlsearch-install* +Adding nohlsearch package *nohlsearch-install* *package-nohlsearch* Load the plugin with this command: > packadd nohlsearch diff --git a/runtime/doc/usr_25.txt b/runtime/doc/usr_25.txt index 955d2ae5f0..8dbe1332b5 100644 --- a/runtime/doc/usr_25.txt +++ b/runtime/doc/usr_25.txt @@ -190,15 +190,15 @@ This results in the following: story. ~ -JUSTIFYING TEXT +JUSTIFYING TEXT *justify* *:Justify* *Justify()* *package-justify* Vim has no built-in way of justifying text. However, there is a neat macro package that does the job. To use this package, execute the following -command: > +command: >vim :packadd justify -Or put this line in your |vimrc|: > +Or put this line in your |vimrc|: >vim packadd! justify -- cgit From 37316fbac641ecafde29fd750a08ece490d209c1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 12 Jan 2025 08:04:25 +0800 Subject: vim-patch:9.1.1005: completion text is highlighted even with no pattern found (#31973) Problem: completion text is highlighted even with no pattern found Solution: use ins_compl_leader_len() instead of checking compl_leader.length (glepnir) closes: vim/vim#16422 https://github.com/vim/vim/commit/9fddb8ae770be3e16545dd4c2f4cfaad8f62cb40 Co-authored-by: glepnir --- src/nvim/insexpand.c | 2 +- test/functional/ui/popupmenu_spec.lua | 11 ++++++++++- test/old/testdir/test_popup.vim | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index d1559b00f1..22643457d6 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -970,7 +970,7 @@ int ins_compl_col_range_attr(int col) return -1; } - if (col >= (compl_col + (int)compl_leader.size) && col < compl_ins_end_col) { + if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) { return syn_name2attr("ComplMatchIns"); } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 66b62341a9..b763f4ba6c 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5885,7 +5885,16 @@ describe('builtin popupmenu', function() {1:~ }|*17 {2:-- }{5:match 1 of 3} | ]]) - feed('') + feed('') + + command('set cot-=fuzzy') + feed('Sf') + screen:expect([[ + {10:f^ }| + {1:~ }|*18 + {2:-- }{6:Pattern not found} | + ]]) + feed('') end) end end diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 7f570182e4..31cfc2d096 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1830,6 +1830,13 @@ func Test_pum_matchins_highlight_combine() call term_sendkeys(buf, "S\\f\") call VerifyScreenDump(buf, 'Test_pum_matchins_combine_08', {}) call term_sendkeys(buf, "\\") + call TermWait(buf) + + call term_sendkeys(buf, ":set cot-=fuzzy\") + call TermWait(buf) + call term_sendkeys(buf, "Sf\") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_09', {}) + call term_sendkeys(buf, "\\") call StopVimInTerminal(buf) endfunc -- cgit From 1a8a48d7e5f8243aff0253a82f4214241eb877d6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 12 Jan 2025 08:25:57 +0800 Subject: vim-patch:9.1.1003: [security]: heap-buffer-overflow with visual mode (#31971) Problem: [security]: heap-buffer-overflow with visual mode when using :all, causing Vim trying to access beyond end-of-line (gandalf) Solution: Reset visual mode on :all, validate position in gchar_pos() and charwise_block_prep() This fixes CVE-2025-22134 Github Advisory: https://github.com/vim/vim/security/advisories/GHSA-5rgf-26wj-48v8 https://github.com/vim/vim/commit/c9a1e257f1630a0866447e53a564f7ff96a80ead Co-authored-by: Christian Brabandt --- src/nvim/arglist.c | 5 +++++ src/nvim/memline.c | 2 +- src/nvim/ops.c | 3 ++- test/old/testdir/test_visual.vim | 26 ++++++++++++++++++++++---- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index bb639edc07..361bb8db12 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -31,6 +31,7 @@ #include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/os/input.h" @@ -1096,6 +1097,10 @@ static void do_arg_all(int count, int forceit, int keep_tabs) tabpage_T *const new_lu_tp = curtab; + // Stop Visual mode, the cursor and "VIsual" may very well be invalid after + // switching to another buffer. + reset_VIsual_and_resel(); + // Try closing all windows that are not in the argument list. // Also close windows that are not full width; // When 'hidden' or "forceit" set the buffer becomes hidden. diff --git a/src/nvim/memline.c b/src/nvim/memline.c index ce04362a3e..fb7fdfb8b2 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1860,7 +1860,7 @@ int gchar_pos(pos_T *pos) FUNC_ATTR_NONNULL_ARG(1) { // When searching columns is sometimes put at the end of a line. - if (pos->col == MAXCOL) { + if (pos->col == MAXCOL || pos->col > ml_get_len(pos->lnum)) { return NUL; } return utf_ptr2char(ml_get_pos(pos)); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 476c7ee8a4..d51b4cc88b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4345,6 +4345,7 @@ void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T colnr_T endcol = MAXCOL; colnr_T cs, ce; char *p = ml_get(lnum); + int plen = ml_get_len(lnum); bdp->startspaces = 0; bdp->endspaces = 0; @@ -4394,7 +4395,7 @@ void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T bdp->textlen = endcol - startcol + inclusive; } bdp->textcol = startcol; - bdp->textstart = p + startcol; + bdp->textstart = startcol <= plen ? p + startcol : p; } /// Handle the add/subtract operator. diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 39388cbc27..328ac502bf 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -470,7 +470,7 @@ func Test_Visual_Block() \ "\t{", \ "\t}"], getline(1, '$')) - close! + bw! endfunc " Test for 'p'ut in visual block mode @@ -1082,7 +1082,7 @@ func Test_star_register() delmarks < > call assert_fails('*yank', 'E20:') - close! + bw! endfunc " Test for changing text in visual mode with 'exclusive' selection @@ -1098,7 +1098,7 @@ func Test_exclusive_selection() call assert_equal('l one', getline(1)) set virtualedit& set selection& - close! + bw! endfunc " Test for starting linewise visual with a count. @@ -1155,7 +1155,7 @@ func Test_visual_inner_block() 8,9d call cursor(5, 1) call assert_beeps('normal ViBiB') - close! + bw! endfunc func Test_visual_put_in_block() @@ -2764,4 +2764,22 @@ func Test_visual_block_exclusive_selection_adjusted() set selection&vim endfunc +" the following caused a Heap-Overflow, because Vim was accessing outside of a +" line end +func Test_visual_pos_buffer_heap_overflow() + set virtualedit=all + args Xa Xb + all + call setline(1, ['', '', '']) + call cursor(3, 1) + wincmd w + call setline(1, 'foobar') + normal! $lv0 + all + call setreg('"', 'baz') + normal! [P + set virtualedit= + bw! Xa Xb +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From bf58b757c4c2e1bf2a4afe4279150ca68a68691f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 12 Jan 2025 11:42:02 +0100 Subject: vim-patch:9.1.1007: filetype: various ignore are not recognized Problem: filetype: various ignore are not recognized Solution: detect rg/docker/npm/vvsce ignore files as 'gitgnore' filetype (Wu, Zhenyu) Not only prettier, but many programs also support ignore files (like rg, docker, npm, vscode). So use the gitignore filetype for them due to same syntax closes: vim/vim#16428 https://github.com/vim/vim/commit/8cbe2e0a0a78f57bb545a97695bfedd6a95e6992 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 5 +++++ test/old/testdir/test_filetype.vim | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index dee1bd88ca..bc866db399 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1574,6 +1574,11 @@ local filename = { ['.gitmodules'] = 'gitconfig', ['.gitattributes'] = 'gitattributes', ['.gitignore'] = 'gitignore', + ['.ignore'] = 'gitignore', + ['.dockerignore'] = 'gitignore', + ['.npmignore'] = 'gitignore', + ['.rgignore'] = 'gitignore', + ['.vscodeignore'] = 'gitignore', ['gitolite.conf'] = 'gitolite', ['git-rebase-todo'] = 'gitrebase', gkrellmrc = 'gkrellmrc', diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index d890884eb5..5182ff5167 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -308,7 +308,7 @@ func s:GetFilenameChecks() abort \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'), \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/config'), - \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore'], + \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore', '.rgignore', '.ignore', '.dockerignore', '.npmignore', '.vscodeignore'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], -- cgit From 40bf23adaf98dc357a59f9524a16e06f990faeaa Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 12 Jan 2025 12:31:13 +0100 Subject: build(deps): bump wasmtime to v25.0.3 --- cmake.deps/deps.txt | 4 ++-- src/nvim/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index f73023ae84..c42cf88068 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -53,8 +53,8 @@ TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f907 TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.6.tar.gz TREESITTER_SHA256 03c7ee1e6f9f4f3821fd4af0ae06e1da60433b304a73ff92ee9694933009121a -WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.2.tar.gz -WASMTIME_SHA256 6d1c17c756b83f29f629963228e5fa208ba9d6578421ba2cd07132b6a120accb +WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.3.tar.gz +WASMTIME_SHA256 17850ca356fce6ea8bcd3847692b3233588ddf32ff31fcccac67ad06bcac0a3a UNCRUSTIFY_URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.80.1.tar.gz UNCRUSTIFY_SHA256 0e2616ec2f78e12816388c513f7060072ff7942b42f1175eb28b24cb75aaec48 diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 767f5087c1..5b9946db39 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -49,7 +49,7 @@ if(ENABLE_LIBINTL) endif() if(ENABLE_WASMTIME) - find_package(Wasmtime 25.0.2 EXACT REQUIRED) + find_package(Wasmtime 25.0.3 EXACT REQUIRED) target_link_libraries(main_lib INTERFACE wasmtime) target_compile_definitions(nvim_bin PRIVATE HAVE_WASMTIME) endif() -- cgit From 3fdc4302415972eb5d98ba832372236be3d22572 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 11 Jan 2025 15:44:07 -0800 Subject: perf(treesitter): cache queries strongly **Problem:** Query parsing uses a weak cache which is invalidated frequently **Solution:** Make the cache strong, and invalidate it manually when necessary (that is, when `rtp` is changed or `query.set()` is called) Co-authored-by: Christian Clason --- runtime/doc/news.txt | 3 +++ runtime/lua/vim/treesitter/query.lua | 13 +++++++++-- test/functional/treesitter/query_spec.lua | 37 ++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 810f40180a..4f4bfe9ecc 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -294,6 +294,9 @@ PERFORMANCE inflight requests). This greatly improves performance with slow LSP servers. • 10x speedup for |vim.treesitter.foldexpr()| (when no parser exists for the buffer). +• Strong |treesitter-query| caching makes repeat |vim.treesitter.query.get()| + and |vim.treesitter.query.parse()| calls significantly faster for large + queries. PLUGINS diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index b9bcbe9a80..b0b0fecd38 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -262,6 +262,7 @@ local explicit_queries = setmetatable({}, { ---@param query_name string Name of the query (e.g., "highlights") ---@param text string Query text (unparsed). function M.set(lang, query_name, text) + M.get:clear(lang, query_name) explicit_queries[lang][query_name] = M.parse(lang, text) end @@ -284,7 +285,15 @@ M.get = memoize('concat-2', function(lang, query_name) end return M.parse(lang, query_string) -end) +end, false) + +api.nvim_create_autocmd('OptionSet', { + pattern = { 'runtimepath' }, + group = api.nvim_create_augroup('ts_query_cache_reset', { clear = true }), + callback = function() + M.get:clear() + end, +}) --- Parses a {query} string and returns a `Query` object (|lua-treesitter-query|), which can be used --- to search the tree for the query patterns (via |Query:iter_captures()|, |Query:iter_matches()|), @@ -316,7 +325,7 @@ M.parse = memoize('concat-2', function(lang, query) assert(language.add(lang)) local ts_query = vim._ts_parse_query(lang, query) return Query.new(lang, ts_query) -end) +end, false) --- Implementations of predicates that can optionally be prefixed with "any-". --- diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 6e21ed1d99..6bab171ee8 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -86,7 +86,7 @@ void ui_refresh(void) local before = vim.api.nvim__stats().ts_query_parse_count collectgarbage('stop') for _ = 1, _n, 1 do - vim.treesitter.query.parse('c', long_query, _n) + vim.treesitter.query.parse('c', long_query) end collectgarbage('restart') collectgarbage('collect') @@ -96,8 +96,39 @@ void ui_refresh(void) end eq(1, q(1)) - -- cache is cleared by garbage collection even if valid "cquery" reference is kept around - eq(1, q(100)) + -- cache is retained even after garbage collection + eq(0, q(100)) + end) + + it('cache is cleared upon runtimepath changes, or setting query manually', function() + ---@return number + exec_lua(function() + _G.query_parse_count = _G.query_parse_count or 0 + local parse = vim.treesitter.query.parse + vim.treesitter.query.parse = function(...) + _G.query_parse_count = _G.query_parse_count + 1 + return parse(...) + end + end) + + local function q(_n) + return exec_lua(function() + for _ = 1, _n, 1 do + vim.treesitter.query.get('c', 'highlights') + end + return _G.query_parse_count + end) + end + + eq(1, q(10)) + exec_lua(function() + vim.opt.rtp:prepend('/another/dir') + end) + eq(2, q(100)) + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', [[; test]]) + end) + eq(3, q(100)) end) it('supports query and iter by capture (iter_captures)', function() -- cgit From 45e606b1fddbfeee8fe28385b5371ca6f2fba71b Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Wed, 18 Dec 2024 10:48:33 -0800 Subject: feat(treesitter): async parsing **Problem:** Parsing can be slow for large files, and it is a blocking operation which can be disruptive and annoying. **Solution:** Provide a function for asynchronous parsing, which accepts a callback to be run after parsing completes. Co-authored-by: Lewis Russell Co-authored-by: Luuk van Baal Co-authored-by: VanaIgr --- runtime/doc/news.txt | 4 + runtime/doc/options.txt | 4 +- runtime/doc/treesitter.txt | 38 ++++-- runtime/lua/vim/_meta/options.lua | 4 +- runtime/lua/vim/treesitter.lua | 8 +- runtime/lua/vim/treesitter/highlighter.lua | 19 ++- runtime/lua/vim/treesitter/languagetree.lua | 143 +++++++++++++++++++-- runtime/lua/vim/treesitter/query.lua | 14 +- src/nvim/lua/treesitter.c | 6 +- src/nvim/options.lua | 4 +- test/functional/treesitter/parser_spec.lua | 192 ++++++++++++++++++++++++++++ 11 files changed, 395 insertions(+), 41 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 4f4bfe9ecc..96f0ec1aa7 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -297,6 +297,8 @@ PERFORMANCE • Strong |treesitter-query| caching makes repeat |vim.treesitter.query.get()| and |vim.treesitter.query.parse()| calls significantly faster for large queries. +• Treesitter highlighting is now asynchronous. To force synchronous parsing, + use `vim.g._ts_force_sync_parsing = true`. PLUGINS @@ -339,6 +341,8 @@ TREESITTER • New |TSNode:child_with_descendant()|, which is nearly identical to |TSNode:child_containing_descendant()| except that it can return the descendant itself. +• |LanguageTree:parse()| optionally supports asynchronous invocation, which is + activated by passing the `on_parse` callback parameter. TUI diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 80b391d8c9..8d171183d6 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4657,8 +4657,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'redrawtime' 'rdt' number (default 2000) global Time in milliseconds for redrawing the display. Applies to - 'hlsearch', 'inccommand', |:match| highlighting and syntax - highlighting. + 'hlsearch', 'inccommand', |:match| highlighting, syntax highlighting, + and async |LanguageTree:parse()|. When redrawing takes more than this many milliseconds no further matches will be highlighted. For syntax highlighting the time applies per window. When over the diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 80d8f92af2..41679f80ca 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1090,6 +1090,9 @@ start({bufnr}, {lang}) *vim.treesitter.start()* required for some plugins. In this case, add `vim.bo.syntax = 'on'` after the call to `start`. + Note: By default, the highlighter parses code asynchronously, using a + segment time of 3ms. + Example: >lua vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', callback = function(args) @@ -1401,8 +1404,8 @@ Query:iter_captures({node}, {source}, {start}, {stop}) Defaults to `node:end_()`. Return: ~ - (`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch`) - capture id, capture node, metadata, match + (`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`) + capture id, capture node, metadata, match, tree *Query:iter_matches()* Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) @@ -1447,8 +1450,8 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) compatibility and will be removed in a future release. Return: ~ - (`fun(): integer, table, vim.treesitter.query.TSMetadata`) - pattern id, match, metadata + (`fun(): integer, table, vim.treesitter.query.TSMetadata, TSTree`) + pattern id, match, metadata, tree set({lang}, {query_name}, {text}) *vim.treesitter.query.set()* Sets the runtime query named {query_name} for {lang} @@ -1611,7 +1614,7 @@ LanguageTree:node_for_range({range}, {opts}) Return: ~ (`TSNode?`) -LanguageTree:parse({range}) *LanguageTree:parse()* +LanguageTree:parse({range}, {on_parse}) *LanguageTree:parse()* Recursively parse all regions in the language tree using |treesitter-parsers| for the corresponding languages and run injection queries on the parsed trees to determine whether child trees should be @@ -1622,14 +1625,27 @@ LanguageTree:parse({range}) *LanguageTree:parse()* if {range} is `true`). Parameters: ~ - • {range} (`boolean|Range?`) Parse this range in the parser's source. - Set to `true` to run a complete parse of the source (Note: - Can be slow!) Set to `false|nil` to only parse regions with - empty ranges (typically only the root tree without - injections). + • {range} (`boolean|Range?`) Parse this range in the parser's + source. Set to `true` to run a complete parse of the + source (Note: Can be slow!) Set to `false|nil` to only + parse regions with empty ranges (typically only the root + tree without injections). + • {on_parse} (`fun(err?: string, trees?: table)?`) + Function invoked when parsing completes. When provided and + `vim.g._ts_force_sync_parsing` is not set, parsing will + run asynchronously. The first argument to the function is + a string respresenting the error type, in case of a + failure (currently only possible for timeouts). The second + argument is the list of trees returned by the parse (upon + success), or `nil` if the parse timed out (determined by + 'redrawtime'). + + If parsing was still able to finish synchronously (within + 3ms), `parse()` returns the list of trees. Otherwise, it + returns `nil`. Return: ~ - (`table`) + (`table?`) *LanguageTree:register_cbs()* LanguageTree:register_cbs({cbs}, {recursive}) diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 940441a849..c9871c8660 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -4845,8 +4845,8 @@ vim.go.redrawdebug = vim.o.redrawdebug vim.go.rdb = vim.go.redrawdebug --- Time in milliseconds for redrawing the display. Applies to ---- 'hlsearch', 'inccommand', `:match` highlighting and syntax ---- highlighting. +--- 'hlsearch', 'inccommand', `:match` highlighting, syntax highlighting, +--- and async `LanguageTree:parse()`. --- When redrawing takes more than this many milliseconds no further --- matches will be highlighted. --- For syntax highlighting the time applies per window. When over the diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 89dc4e289a..9b7c8233d8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -61,7 +61,7 @@ function M._create_parser(bufnr, lang, opts) { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } ) - self:parse() + self:parse(nil, function() end) return self end @@ -397,6 +397,8 @@ end --- Note: By default, disables regex syntax highlighting, which may be required for some plugins. --- In this case, add `vim.bo.syntax = 'on'` after the call to `start`. --- +--- Note: By default, the highlighter parses code asynchronously, using a segment time of 3ms. +--- --- Example: --- --- ```lua @@ -408,8 +410,8 @@ end --- }) --- ``` --- ----@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer) ----@param lang (string|nil) Language of the parser (default: from buffer filetype) +---@param bufnr integer? Buffer to be highlighted (default: current buffer) +---@param lang string? Language of the parser (default: from buffer filetype) function M.start(bufnr, lang) bufnr = vim._resolve_bufnr(bufnr) local parser = assert(M.get_parser(bufnr, lang, { error = false })) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 96503c38ea..04e6ee8a9e 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -69,6 +69,7 @@ end ---@field private _queries table ---@field tree vim.treesitter.LanguageTree ---@field private redraw_count integer +---@field parsing boolean true if we are parsing asynchronously local TSHighlighter = { active = {}, } @@ -147,7 +148,7 @@ function TSHighlighter.new(tree, opts) vim.opt_local.spelloptions:append('noplainbuffer') end) - self.tree:parse() + self.tree:parse(nil, function() end) return self end @@ -384,19 +385,23 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) end ---@private ----@param _win integer ---@param buf integer ---@param topline integer ---@param botline integer -function TSHighlighter._on_win(_, _win, buf, topline, botline) +function TSHighlighter._on_win(_, _, buf, topline, botline) local self = TSHighlighter.active[buf] - if not self then + if not self or self.parsing then return false end - self.tree:parse({ topline, botline + 1 }) - self:prepare_highlight_states(topline, botline + 1) + self.parsing = self.tree:parse({ topline, botline + 1 }, function(_, trees) + if trees and self.parsing then + self.parsing = false + api.nvim__redraw({ buf = buf, valid = false, flush = false }) + end + end) == nil self.redraw_count = self.redraw_count + 1 - return true + self:prepare_highlight_states(topline, botline) + return #self._highlight_states > 0 end api.nvim_set_decoration_provider(ns, { diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 330eb45749..945a2301a9 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -44,6 +44,8 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') +local default_parse_timeout_ms = 3 + ---@alias TSCallbackName ---| 'changedtree' ---| 'bytes' @@ -76,6 +78,10 @@ local TSCallbackNames = { ---@field private _injections_processed boolean ---@field private _opts table Options ---@field private _parser TSParser Parser for language +---Table of regions for which the tree is currently running an async parse +---@field private _ranges_being_parsed table +---Table of callback queues, keyed by each region for which the callbacks should be run +---@field private _cb_queues table)[]> ---@field private _has_regions boolean ---@field private _regions table? ---List of regions this tree should manage and parse. If nil then regions are @@ -130,6 +136,8 @@ function LanguageTree.new(source, lang, opts) _injections_processed = false, _valid = false, _parser = vim._create_ts_parser(lang), + _ranges_being_parsed = {}, + _cb_queues = {}, _callbacks = {}, _callbacks_rec = {}, } @@ -232,6 +240,7 @@ end ---@param reload boolean|nil function LanguageTree:invalidate(reload) self._valid = false + self._parser:reset() -- buffer was reloaded, reparse all trees if reload then @@ -334,10 +343,12 @@ end --- @private --- @param range boolean|Range? +--- @param timeout integer? --- @return Range6[] changes --- @return integer no_regions_parsed --- @return number total_parse_time -function LanguageTree:_parse_regions(range) +--- @return boolean finished whether async parsing still needs time +function LanguageTree:_parse_regions(range, timeout) local changes = {} local no_regions_parsed = 0 local total_parse_time = 0 @@ -357,9 +368,14 @@ function LanguageTree:_parse_regions(range) ) then self._parser:set_included_ranges(ranges) + self._parser:set_timeout(timeout and timeout * 1000 or 0) -- ms -> micros local parse_time, tree, tree_changes = tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + if not tree then + return changes, no_regions_parsed, total_parse_time, false + end + -- Pass ranges if this is an initial parse local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) @@ -373,7 +389,7 @@ function LanguageTree:_parse_regions(range) end end - return changes, no_regions_parsed, total_parse_time + return changes, no_regions_parsed, total_parse_time, true end --- @private @@ -409,6 +425,82 @@ function LanguageTree:_add_injections() return query_time end +--- @param range boolean|Range? +--- @return string +local function range_to_string(range) + return type(range) == 'table' and table.concat(range, ',') or tostring(range) +end + +--- @private +--- @param range boolean|Range? +--- @param callback fun(err?: string, trees?: table) +function LanguageTree:_push_async_callback(range, callback) + local key = range_to_string(range) + self._cb_queues[key] = self._cb_queues[key] or {} + local queue = self._cb_queues[key] + queue[#queue + 1] = callback +end + +--- @private +--- @param range boolean|Range? +--- @param err? string +--- @param trees? table +function LanguageTree:_run_async_callbacks(range, err, trees) + local key = range_to_string(range) + for _, cb in ipairs(self._cb_queues[key]) do + cb(err, trees) + end + self._ranges_being_parsed[key] = false + self._cb_queues[key] = {} +end + +--- Run an asynchronous parse, calling {on_parse} when complete. +--- +--- @private +--- @param range boolean|Range? +--- @param on_parse fun(err?: string, trees?: table) +--- @return table? trees the list of parsed trees, if parsing completed synchronously +function LanguageTree:_async_parse(range, on_parse) + self:_push_async_callback(range, on_parse) + + -- If we are already running an async parse, just queue the callback. + local range_string = range_to_string(range) + if not self._ranges_being_parsed[range_string] then + self._ranges_being_parsed[range_string] = true + else + return + end + + local buf = vim.b[self._source] + local ct = buf.changedtick + local total_parse_time = 0 + local redrawtime = vim.o.redrawtime + local timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil + + local function step() + -- If buffer was changed in the middle of parsing, reset parse state + if buf.changedtick ~= ct then + ct = buf.changedtick + total_parse_time = 0 + end + + local parse_time, trees, finished = tcall(self._parse, self, range, timeout) + total_parse_time = total_parse_time + parse_time + + if finished then + self:_run_async_callbacks(range, nil, trees) + return trees + elseif total_parse_time > redrawtime then + self:_run_async_callbacks(range, 'TIMEOUT', nil) + return nil + else + vim.schedule(step) + end + end + + return step() +end + --- Recursively parse all regions in the language tree using |treesitter-parsers| --- for the corresponding languages and run injection queries on the parsed trees --- to determine whether child trees should be created and parsed. @@ -420,11 +512,33 @@ end --- Set to `true` to run a complete parse of the source (Note: Can be slow!) --- Set to `false|nil` to only parse regions with empty ranges (typically --- only the root tree without injections). ---- @return table -function LanguageTree:parse(range) +--- @param on_parse fun(err?: string, trees?: table)? Function invoked when parsing completes. +--- When provided and `vim.g._ts_force_sync_parsing` is not set, parsing will run +--- asynchronously. The first argument to the function is a string respresenting the error type, +--- in case of a failure (currently only possible for timeouts). The second argument is the list +--- of trees returned by the parse (upon success), or `nil` if the parse timed out (determined +--- by 'redrawtime'). +--- +--- If parsing was still able to finish synchronously (within 3ms), `parse()` returns the list +--- of trees. Otherwise, it returns `nil`. +--- @return table? +function LanguageTree:parse(range, on_parse) + if on_parse then + return self:_async_parse(range, on_parse) + end + local trees, _ = self:_parse(range) + return trees +end + +--- @private +--- @param range boolean|Range|nil +--- @param timeout integer? +--- @return table trees +--- @return boolean finished +function LanguageTree:_parse(range, timeout) if self:is_valid() then self:_log('valid') - return self._trees + return self._trees, true end local changes --- @type Range6[]? @@ -433,10 +547,15 @@ function LanguageTree:parse(range) local no_regions_parsed = 0 local query_time = 0 local total_parse_time = 0 + local is_finished --- @type boolean -- At least 1 region is invalid if not self:is_valid(true) then - changes, no_regions_parsed, total_parse_time = self:_parse_regions(range) + changes, no_regions_parsed, total_parse_time, is_finished = self:_parse_regions(range, timeout) + timeout = timeout and math.max(timeout - total_parse_time, 0) + if not is_finished then + return self._trees, is_finished + end -- Need to run injections when we parsed something if no_regions_parsed > 0 then self._injections_processed = false @@ -457,10 +576,17 @@ function LanguageTree:parse(range) }) for _, child in pairs(self._children) do - child:parse(range) + if timeout == 0 then + return self._trees, false + end + local ctime, _, child_finished = tcall(child._parse, child, range, timeout) + timeout = timeout and math.max(timeout - ctime, 0) + if not child_finished then + return self._trees, child_finished + end end - return self._trees + return self._trees, true end --- Invokes the callback for each |LanguageTree| recursively. @@ -907,6 +1033,7 @@ function LanguageTree:_edit( ) end + self._parser:reset() self._regions = nil local changed_range = { diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index b0b0fecd38..66ab0d52f0 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -913,8 +913,8 @@ end ---@param start? integer Starting line for the search. Defaults to `node:start()`. ---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`. --- ----@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch): ---- capture id, capture node, metadata, match +---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree): +--- capture id, capture node, metadata, match, tree --- ---@note Captures are only returned if the query pattern of a specific capture contained predicates. function Query:iter_captures(node, source, start, stop) @@ -924,6 +924,8 @@ function Query:iter_captures(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) + -- Copy the tree to ensure it is valid during the entire lifetime of the iterator + local tree = node:tree():copy() local cursor = vim._create_ts_querycursor(node, self.query, start, stop, { match_limit = 256 }) -- For faster checks that a match is not in the cache. @@ -970,7 +972,7 @@ function Query:iter_captures(node, source, start, stop) match_cache[match_id] = metadata end - return capture, captured_node, metadata, match + return capture, captured_node, metadata, match, tree end return iter end @@ -1011,7 +1013,7 @@ end --- (last) node instead of the full list of matching nodes. This option is only for backward --- compatibility and will be removed in a future release. --- ----@return (fun(): integer, table, vim.treesitter.query.TSMetadata): pattern id, match, metadata +---@return (fun(): integer, table, vim.treesitter.query.TSMetadata, TSTree): pattern id, match, metadata, tree function Query:iter_matches(node, source, start, stop, opts) opts = opts or {} opts.match_limit = opts.match_limit or 256 @@ -1022,6 +1024,8 @@ function Query:iter_matches(node, source, start, stop, opts) start, stop = value_or_node_range(start, stop, node) + -- Copy the tree to ensure it is valid during the entire lifetime of the iterator + local tree = node:tree():copy() local cursor = vim._create_ts_querycursor(node, self.query, start, stop, opts) local function iter() @@ -1059,7 +1063,7 @@ function Query:iter_matches(node, source, start, stop, opts) end -- TODO(lewis6991): create a new function that returns {match, metadata} - return pattern_i, captures, metadata + return pattern_i, captures, metadata, tree end return iter end diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 28ad2cf4d3..9bd2baad27 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -489,7 +489,11 @@ static int parser_parse(lua_State *L) // Sometimes parsing fails (timeout, or wrong parser ABI) // In those case, just return an error. if (!new_tree) { - return luaL_error(L, "An error occurred when parsing."); + if (ts_parser_timeout_micros(p) == 0) { + // No timeout set, must have had an error + return luaL_error(L, "An error occurred when parsing."); + } + return 0; } // The new tree will be pushed to the stack, without copy, ownership is now to the lua GC. diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 863f875d9d..15a4e8ddc2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -6520,8 +6520,8 @@ return { defaults = { if_true = 2000 }, desc = [=[ Time in milliseconds for redrawing the display. Applies to - 'hlsearch', 'inccommand', |:match| highlighting and syntax - highlighting. + 'hlsearch', 'inccommand', |:match| highlighting, syntax highlighting, + and async |LanguageTree:parse()|. When redrawing takes more than this many milliseconds no further matches will be highlighted. For syntax highlighting the time applies per window. When over the diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 2f80cee226..6f9faddbe3 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -10,6 +10,7 @@ local exec_lua = n.exec_lua local pcall_err = t.pcall_err local feed = n.feed local run_query = ts_t.run_query +local assert_alive = n.assert_alive describe('treesitter parser API', function() before_each(function() @@ -90,6 +91,197 @@ describe('treesitter parser API', function() eq(true, exec_lua('return parser:parse()[1] == tree2')) end) + it('parses buffer asynchronously', function() + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.lang = vim.treesitter.language.inspect('c') + _G.parser:parse(nil, function(_, trees) + _G.tree = trees[1] + _G.root = _G.tree:root() + end) + vim.wait(100, function() end) + end) + + eq('', exec_lua('return tostring(tree)')) + eq('', exec_lua('return tostring(root)')) + eq({ 0, 0, 3, 0 }, exec_lua('return {root:range()}')) + + eq(1, exec_lua('return root:child_count()')) + exec_lua('child = root:child(0)') + eq('', exec_lua('return tostring(child)')) + eq({ 0, 0, 2, 1 }, exec_lua('return {child:range()}')) + + eq('function_definition', exec_lua('return child:type()')) + eq(true, exec_lua('return child:named()')) + eq('number', type(exec_lua('return child:symbol()'))) + eq(true, exec_lua('return lang.symbols[child:type()]')) + + exec_lua('anon = root:descendant_for_range(0,8,0,9)') + eq('(', exec_lua('return anon:type()')) + eq(false, exec_lua('return anon:named()')) + eq('number', type(exec_lua('return anon:symbol()'))) + eq(false, exec_lua([=[return lang.symbols[string.format('"%s"', anon:type())]]=])) + + exec_lua('descendant = root:descendant_for_range(1,2,1,12)') + eq('', exec_lua('return tostring(descendant)')) + eq({ 1, 2, 1, 12 }, exec_lua('return {descendant:range()}')) + eq( + '(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))', + exec_lua('return descendant:sexpr()') + ) + + feed('2G7|ay') + exec_lua(function() + _G.parser:parse(nil, function(_, trees) + _G.tree2 = trees[1] + _G.root2 = _G.tree2:root() + _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13) + end) + vim.wait(100, function() end) + end) + eq(false, exec_lua('return tree2 == tree1')) + eq(false, exec_lua('return root2 == root')) + eq('', exec_lua('return tostring(descendant2)')) + eq({ 1, 2, 1, 13 }, exec_lua('return {descendant2:range()}')) + + eq(true, exec_lua('return child == child')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child == root:child(0)')) + eq(false, exec_lua('return child == descendant2')) + eq(false, exec_lua('return child == nil')) + eq(false, exec_lua('return child == tree')) + + eq('string', exec_lua('return type(child:id())')) + eq(true, exec_lua('return child:id() == child:id()')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child:id() == root:child(0):id()')) + eq(false, exec_lua('return child:id() == descendant2:id()')) + eq(false, exec_lua('return child:id() == nil')) + eq(false, exec_lua('return child:id() == tree')) + + -- unchanged buffer: return the same tree + eq(true, exec_lua('return parser:parse()[1] == tree2')) + end) + + it('does not crash when editing large files', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + while not _G.done do + -- Busy wait until async parsing has completed + vim.wait(100, function() end) + end + end) + + eq(true, exec_lua([[return done]])) + exec_lua(function() + vim.api.nvim_input('Lxj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + assert_alive() + end) + + it('resets parsing state on tree changes', function() + insert([[vim.api.nvim_set_hl(0, 'test2', { bg = 'green' })]]) + feed('yy1000p') + + exec_lua(function() + vim.cmd('set ft=lua') + + vim.treesitter.start(0) + local parser = assert(vim.treesitter.get_parser(0)) + + parser:parse(true, function() end) + vim.api.nvim_buf_set_lines(0, 1, -1, false, {}) + parser:parse(true) + end) + end) + + it('resets when buffer was editing during an async parse', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + feed('gg4jO// Comment') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + end) + + exec_lua(function() + vim.api.nvim_input('ggdj') + end) + + eq(false, exec_lua([[return done]])) + exec_lua(function() + while not _G.done do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq(true, exec_lua([[return done]])) + eq('comment', exec_lua([[return parser:parse()[1]:root():named_child(2):type()]])) + eq({ 2, 0, 2, 10 }, exec_lua([[return {parser:parse()[1]:root():named_child(2):range()}]])) + end) + + it('handles multiple async parse calls', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + -- Spy on vim.schedule + local schedule = vim.schedule + vim.schedule = function(fn) + _G.schedules = _G.schedules + 1 + schedule(fn) + end + _G.schedules = 0 + _G.parser = vim.treesitter.get_parser(0, 'c') + for i = 1, 5 do + _G['done' .. i] = false + _G.parser:parse(nil, function() + _G['done' .. i] = true + end) + end + schedule(function() + _G.schedules_snapshot = _G.schedules + end) + end) + + eq(2, exec_lua([[return schedules_snapshot]])) + eq( + { false, false, false, false, false }, + exec_lua([[return { done1, done2, done3, done4, done5 }]]) + ) + exec_lua(function() + while not _G.done1 do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq({ true, true, true, true, true }, exec_lua([[return { done1, done2, done3, done4, done5 }]])) + end) + local test_text = [[ void ui_refresh(void) { -- cgit From bd4ca22d0334a3323313dfd6975a80218ec65e36 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 20 Dec 2024 16:23:52 -0800 Subject: feat(treesitter)!: don't parse tree in get_parser() or start() **Problem:** `vim.treesitter.get_parser()` and `vim.treesitter.start()` both parse the tree before returning it. This is problematic because if this is a sync parse, it will stall the editor on large files. If it is an async parse, the functions return stale trees. **Solution:** Remove this parsing side effect and leave it to the user to parse the returned trees, either synchronously or asynchronously. --- runtime/doc/news.txt | 5 +++++ runtime/lua/vim/treesitter.lua | 2 -- runtime/lua/vim/treesitter/highlighter.lua | 2 -- test/functional/treesitter/language_spec.lua | 4 ++++ test/functional/treesitter/node_spec.lua | 2 ++ test/functional/treesitter/parser_spec.lua | 12 +++++++----- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 96f0ec1aa7..3b1c38b8d9 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -158,6 +158,11 @@ TREESITTER if no languages are explicitly registered. • |vim.treesitter.language.add()| returns `true` if a parser was loaded successfully and `nil,errmsg` otherwise instead of throwing an error. +• |vim.treesitter.get_parser()| and |vim.treesitter.start()| no longer parse + the tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua + local p = vim.treesitter.get_parser(0, 'c') + p:parse() +< TUI diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 9b7c8233d8..0269699dfd 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -61,8 +61,6 @@ function M._create_parser(bufnr, lang, opts) { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } ) - self:parse(nil, function() end) - return self end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 04e6ee8a9e..be138885d5 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -148,8 +148,6 @@ function TSHighlighter.new(tree, opts) vim.opt_local.spelloptions:append('noplainbuffer') end) - self.tree:parse(nil, function() end) - return self end diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 120a15d7f9..a93b1063a1 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -117,6 +117,7 @@ describe('treesitter language API', function() '', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 1, 3, 1, 3 }) return tostring(tree:root()) end) @@ -133,6 +134,7 @@ describe('treesitter language API', function() '', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 10, 10, 10, 10 }) return tostring(tree:root()) end) @@ -149,6 +151,7 @@ describe('treesitter language API', function() '', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local node = langtree:named_node_for_range({ 1, 3, 1, 3 }) return tostring(node) end) @@ -160,6 +163,7 @@ describe('treesitter language API', function() exec_lua(function() _G.langtree = vim.treesitter.get_parser(0, 'lua') + _G.langtree:parse() _G.node = _G.langtree:node_for_range({ 0, 3, 0, 3 }) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index c87a56b160..9839022c5e 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -20,6 +20,7 @@ describe('treesitter node API', function() insert('F') exec_lua(function() vim.treesitter.start(0, 'lua') + vim.treesitter.get_parser(0):parse() vim.treesitter.get_node():tree() vim.treesitter.get_node():tree() collectgarbage() @@ -45,6 +46,7 @@ describe('treesitter node API', function() -- this buffer doesn't have filetype set! insert('local foo = function() end') exec_lua(function() + vim.treesitter.get_parser(0, 'lua'):parse() _G.node = vim.treesitter.get_node({ bufnr = 0, pos = { 0, 6 }, -- on "foo" diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 6f9faddbe3..825c2b8f29 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -1142,11 +1142,13 @@ print() feed(':set ft=help') exec_lua(function() - vim.treesitter.get_parser(0, 'vimdoc', { - injections = { - vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', - }, - }) + vim.treesitter + .get_parser(0, 'vimdoc', { + injections = { + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, + }) + :parse() end) end) -- cgit From f4cff3077b21ba110bc498f3e9331c16d3121cb1 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 12 Jan 2025 19:32:36 +0100 Subject: build(deps): bump tree-sitter to v0.24.7 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index c42cf88068..cf220b0fbd 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -50,8 +50,8 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.6.tar.gz -TREESITTER_SHA256 03c7ee1e6f9f4f3821fd4af0ae06e1da60433b304a73ff92ee9694933009121a +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.7.tar.gz +TREESITTER_SHA256 7cbc13c974d6abe978cafc9da12d1e79e07e365c42af75e43ec1b5cdc03ed447 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.3.tar.gz WASMTIME_SHA256 17850ca356fce6ea8bcd3847692b3233588ddf32ff31fcccac67ad06bcac0a3a -- cgit From 2c16c849986794682a4776ff4ec100d00eeba5ca Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 13 Jan 2025 15:18:47 +0800 Subject: vim-patch:9.1.1011: popupmenu internal error with some abbr in completion item (#31988) Problem: Popup menu internal error with some abbr in completion item. Solution: Don't compute attributes when there is no corresponding text. Reduce indent in pum_redraw() while at it (zeertzjq). fixes: vim/vim#16427 closes: vim/vim#16435 https://github.com/vim/vim/commit/3a0cc36c69744a7727ce34311d39d2d9d8ddc6f9 --- src/nvim/popupmenu.c | 138 +++++++++++++++++----------------- test/functional/ui/popupmenu_spec.lua | 39 ++++++++++ test/old/testdir/test_popup.vim | 33 ++++++++ 3 files changed, 140 insertions(+), 70 deletions(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 2b1dd22b1a..d1c6f647fd 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -412,7 +412,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i /// Returns attributes for every cell, or NULL if all attributes are the same. static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr) { - if ((hlf != HLF_PSI && hlf != HLF_PNI) + if (*text == NUL || (hlf != HLF_PSI && hlf != HLF_PNI) || (win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI) && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) { return NULL; @@ -654,89 +654,87 @@ void pum_redraw(void) s = p; } int w = ptr2cells(p); + if (*p != NUL && *p != TAB && totwidth + w <= pum_width) { + width += w; + continue; + } - if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) { - // Display the text that fits or comes before a Tab. - // First convert it to printable characters. - char *st; - char saved = *p; + // Display the text that fits or comes before a Tab. + // First convert it to printable characters. + char saved = *p; - if (saved != NUL) { - *p = NUL; - } - st = transstr(s, true); - if (saved != NUL) { - *p = saved; - } - - int *attrs = NULL; - if (item_type == CPT_ABBR) { - attrs = pum_compute_text_attrs(st, hlf, pum_array[idx].pum_user_abbr_hlattr); - } + if (saved != NUL) { + *p = NUL; + } + char *st = transstr(s, true); + if (saved != NUL) { + *p = saved; + } - if (pum_rl) { - char *rt = reverse_text(st); - char *rt_start = rt; - int cells = vim_strsize(rt); - - if (cells > pum_width) { - do { - cells -= utf_ptr2cells(rt); - MB_PTR_ADV(rt); - } while (cells > pum_width); - - if (cells < pum_width) { - // Most left character requires 2-cells but only 1 cell - // is available on screen. Put a '<' on the left of the - // pum item - *(--rt) = '<'; - cells++; - } - } + int *attrs = NULL; + if (item_type == CPT_ABBR) { + attrs = pum_compute_text_attrs(st, hlf, + pum_array[idx].pum_user_abbr_hlattr); + } - if (attrs == NULL) { - grid_line_puts(grid_col - cells + 1, rt, -1, attr); - } else { - pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs); + if (pum_rl) { + char *rt = reverse_text(st); + char *rt_start = rt; + int cells = vim_strsize(rt); + + if (cells > pum_width) { + do { + cells -= utf_ptr2cells(rt); + MB_PTR_ADV(rt); + } while (cells > pum_width); + + if (cells < pum_width) { + // Most left character requires 2-cells but only 1 cell is available on + // screen. Put a '<' on the left of the pum item. + *(--rt) = '<'; + cells++; } + } - xfree(rt_start); - xfree(st); - grid_col -= width; + if (attrs == NULL) { + grid_line_puts(grid_col - cells + 1, rt, -1, attr); } else { - if (attrs == NULL) { - grid_line_puts(grid_col, st, -1, attr); - } else { - pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs); - } - - xfree(st); - grid_col += width; + pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs); } - if (attrs != NULL) { - XFREE_CLEAR(attrs); + xfree(rt_start); + xfree(st); + grid_col -= width; + } else { + if (attrs == NULL) { + grid_line_puts(grid_col, st, -1, attr); + } else { + pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs); } - if (*p != TAB) { - break; - } + xfree(st); + grid_col += width; + } - // Display two spaces for a Tab. - if (pum_rl) { - grid_line_puts(grid_col - 1, " ", 2, attr); - grid_col -= 2; - } else { - grid_line_puts(grid_col, " ", 2, attr); - grid_col += 2; - } - totwidth += 2; - // start text at next char - s = NULL; - width = 0; + if (attrs != NULL) { + XFREE_CLEAR(attrs); + } + + if (*p != TAB) { + break; + } + + // Display two spaces for a Tab. + if (pum_rl) { + grid_line_puts(grid_col - 1, " ", 2, attr); + grid_col -= 2; } else { - width += w; + grid_line_puts(grid_col, " ", 2, attr); + grid_col += 2; } + totwidth += 2; + s = NULL; // start text at next char + width = 0; } } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index b763f4ba6c..5e883d1a92 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5415,6 +5415,45 @@ describe('builtin popupmenu', function() feed('') end) + -- oldtest: Test_pum_highlights_match_with_abbr() + it('can highlight matched text with abbr', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + ]]) + feed('i') + screen:expect([[ + ^ | + {s:foobar ! }{1: }| + {n:foobaz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + feed('foo') + screen:expect([[ + foo^ | + {ms:foo}{s:bar ! }{1: }| + {mn:foo}{n:baz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + + feed('') + end) + -- oldtest: Test_pum_user_abbr_hlgroup() it('custom abbr_hlgroup override', function() exec([[ diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 31cfc2d096..e4abf978ab 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1519,6 +1519,39 @@ func Test_pum_highlights_match() call StopVimInTerminal(buf) endfunc +func Test_pum_highlights_match_with_abbr() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\\") + call TermWait(buf, 50) + call term_sendkeys(buf, "foo") + call VerifyScreenDump(buf, 'Test_pum_highlights_19', {}) + + call term_sendkeys(buf, "\\") + call TermWait(buf) + + call StopVimInTerminal(buf) +endfunc + func Test_pum_user_abbr_hlgroup() CheckScreendump let lines =<< trim END -- cgit From a3ef29d570dd892a1bcbfa80bb242d4aac89a06e Mon Sep 17 00:00:00 2001 From: Guilherme Soares <48023091+guilhas07@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:41:49 +0000 Subject: test: use temp file #31907 --- test/functional/treesitter/fold_spec.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index 9f7fdf529f..ac58df4bba 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -802,9 +802,10 @@ t2]]) end) it('can detect a new parser and refresh folds accordingly', function() - write_file('test_fold_file.txt', test_text) + local name = t.tmpname() + write_file(name, test_text) + command('edit ' .. name) command [[ - e test_fold_file.txt set filetype=some_filetype_without_treesitter_parser set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 ]] @@ -818,7 +819,7 @@ t2]]) -- reload buffer as c filetype to simulate new parser being found feed('GA// vim: ft=c') - command([[w | e]]) + command([[write | edit]]) eq({ [1] = '>1', -- cgit From 913e81c35f162c1e2647565397608f63f38d7043 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 9 Jan 2025 14:05:40 +0100 Subject: fix(getchar): do not simplify keycodes in terminal mode The code represents a useful pattern in normal mode where remapping `` will implicitly also remap `` unless you remap that explicitly. This relies on the _unmapped_ behavior being identical which is not true in terminal mode, as vterm can distinguish these keys. Vim seems to entangle this with kitty keyboard mode detection which is irrelevant for us. Conditional fallbacks depending on keyboard mode could be done completely inside `vterm/` without getchar.c getting involved, I would think. --- src/nvim/getchar.c | 8 ++-- src/nvim/terminal.c | 16 +++++--- test/functional/terminal/buffer_spec.lua | 68 ++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 60aa1055c3..6cf4556a9f 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1518,9 +1518,11 @@ int merge_modifiers(int c_arg, int *modifiers) if (*modifiers & MOD_MASK_CTRL) { if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) { - c &= 0x1f; - if (c == NUL) { - c = K_ZERO; + if (!(State & MODE_TERMINAL) || !(c == 'I' || c == 'J' || c == 'M' || c == '[')) { + c &= 0x1f; + if (c == NUL) { + c = K_ZERO; + } } } else if (c == '6') { // CTRL-6 is equivalent to CTRL-^ diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index d7ed709906..ad343bad67 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1011,7 +1011,7 @@ static void terminal_send_key(Terminal *term, int c) c = Ctrl_AT; } - VTermKey key = convert_key(c, &mod); + VTermKey key = convert_key(&c, &mod); if (key) { vterm_keyboard_key(term->vt, key, mod); @@ -1415,19 +1415,23 @@ static int term_selection_set(VTermSelectionMask mask, VTermStringFragment frag, // }}} // input handling {{{ -static void convert_modifiers(int key, VTermModifier *statep) +static void convert_modifiers(int *key, VTermModifier *statep) { if (mod_mask & MOD_MASK_SHIFT) { *statep |= VTERM_MOD_SHIFT; } if (mod_mask & MOD_MASK_CTRL) { *statep |= VTERM_MOD_CTRL; + if (!(mod_mask & MOD_MASK_SHIFT) && *key >= 'A' && *key <= 'Z') { + // vterm interprets CTRL+A as SHIFT+CTRL, change to CTRL+a + *key += ('a' - 'A'); + } } if (mod_mask & MOD_MASK_ALT) { *statep |= VTERM_MOD_ALT; } - switch (key) { + switch (*key) { case K_S_TAB: case K_S_UP: case K_S_DOWN: @@ -1459,11 +1463,11 @@ static void convert_modifiers(int key, VTermModifier *statep) } } -static VTermKey convert_key(int key, VTermModifier *statep) +static VTermKey convert_key(int *key, VTermModifier *statep) { convert_modifiers(key, statep); - switch (key) { + switch (*key) { case K_BS: return VTERM_KEY_BACKSPACE; case K_S_TAB: @@ -1791,7 +1795,7 @@ static bool send_mouse_event(Terminal *term, int c) } VTermModifier mod = VTERM_MOD_NONE; - convert_modifiers(c, &mod); + convert_modifiers(&c, &mod); mouse_action(term, button, row, col - offset, pressed, mod); return false; } diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index cc807ba555..50e23d9e23 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -625,6 +625,74 @@ describe('terminal input', function() ]]):format(key)) end end) + + -- TODO(bfredl): getcharstr() erases the distinction between and . + -- If it was enhanced or replaced this could get folded into the test above. + it('can send TAB/C-I and ESC/C-[ separately', function() + clear() + local screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + 'set notermguicolors', + '--cmd', + 'noremap echo "Tab!"', + '--cmd', + 'noremap echo "Ctrl-I!"', + '--cmd', + 'noremap echo "Esc!"', + '--cmd', + 'noremap echo "Ctrl-[!"', + }) + + screen:expect([[ + ^ | + {4:~ }|*3 + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + + feed('') + screen:expect([[ + ^ | + {4:~ }|*3 + {5:[No Name] 0,0-1 All}| + Tab! | + {3:-- TERMINAL --} | + ]]) + + feed('') + screen:expect([[ + ^ | + {4:~ }|*3 + {5:[No Name] 0,0-1 All}| + Ctrl-I! | + {3:-- TERMINAL --} | + ]]) + + feed('') + screen:expect([[ + ^ | + {4:~ }|*3 + {5:[No Name] 0,0-1 All}| + Esc! | + {3:-- TERMINAL --} | + ]]) + + feed('') + screen:expect([[ + ^ | + {4:~ }|*3 + {5:[No Name] 0,0-1 All}| + Ctrl-[! | + {3:-- TERMINAL --} | + ]]) + end) end) if is_os('win') then -- cgit From 0631492f9c8044a378dc2a17ea257badfbda6d15 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 30 Dec 2024 16:01:00 +0100 Subject: feat: add vim.fs.relpath This is needed to replace the nvim-lspconfig function is_descendant that some lspconfg configurations still use. --- runtime/doc/lua.txt | 17 ++++++++++++ runtime/doc/news.txt | 1 + runtime/lua/vim/fs.lua | 33 ++++++++++++++++++++++ test/functional/lua/fs_spec.lua | 61 ++++++++++++++++++++++++++++++++++++++--- test/testutil.lua | 17 ++++-------- 5 files changed, 114 insertions(+), 15 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index a84a364847..44cbf238cf 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -3148,6 +3148,23 @@ vim.fs.parents({start}) *vim.fs.parents()* (`nil`) (`string?`) +vim.fs.relpath({base}, {target}, {opts}) *vim.fs.relpath()* + Gets `target` path relative to `base`, or `nil` if `base` is not an + ancestor. + + Example: >lua + vim.fs.relpath('/var', '/var/lib') -- 'lib' + vim.fs.relpath('/var', '/usr/bin') -- nil +< + + Parameters: ~ + • {base} (`string`) + • {target} (`string`) + • {opts} (`table?`) Reserved for future use + + Return: ~ + (`string?`) + vim.fs.rm({path}, {opts}) *vim.fs.rm()* WARNING: This feature is experimental/unstable. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3b1c38b8d9..a1868e97d0 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -284,6 +284,7 @@ LUA supporting two new parameters, `encoding` and `strict_indexing`. • |vim.json.encode()| has an option to enable forward slash escaping • |vim.fs.abspath()| converts paths to absolute paths. +• |vim.fs.relpath()| gets relative path compared to base path. OPTIONS diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 04a6e43db1..91e06688b3 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -741,4 +741,37 @@ function M.abspath(path) return M.joinpath(cwd, path) end +--- Gets `target` path relative to `base`, or `nil` if `base` is not an ancestor. +--- +--- Example: +--- +--- ```lua +--- vim.fs.relpath('/var', '/var/lib') -- 'lib' +--- vim.fs.relpath('/var', '/usr/bin') -- nil +--- ``` +--- +--- @param base string +--- @param target string +--- @param opts table? Reserved for future use +--- @return string|nil +function M.relpath(base, target, opts) + vim.validate('base', base, 'string') + vim.validate('target', target, 'string') + vim.validate('opts', opts, 'table', true) + + base = vim.fs.normalize(vim.fs.abspath(base)) + target = vim.fs.normalize(vim.fs.abspath(target)) + if base == target then + return '.' + end + + local prefix = '' + if iswin then + prefix, base = split_windows_path(base) + end + base = prefix .. base .. (base ~= '/' and '/' or '') + + return vim.startswith(target, base) and target:sub(#base + 1) or nil +end + return M diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 6bc1ddbff6..0ba0948eee 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -168,8 +168,8 @@ describe('vim.fs', function() local function run(dir, depth, skip) return exec_lua(function() - local r = {} - local skip_f + local r = {} --- @type table + local skip_f --- @type function if skip then skip_f = function(n0) if vim.tbl_contains(skip or {}, n0) then @@ -493,8 +493,8 @@ describe('vim.fs', function() end) describe('abspath()', function() - local cwd = is_os('win') and vim.uv.cwd():gsub('\\', '/') or vim.uv.cwd() - local home = is_os('win') and vim.uv.os_homedir():gsub('\\', '/') or vim.uv.os_homedir() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local home = t.fix_slashes(assert(vim.uv.os_homedir())) it('works', function() eq(cwd .. '/foo', vim.fs.abspath('foo')) @@ -526,4 +526,57 @@ describe('vim.fs', function() end) end end) + + describe('relpath()', function() + it('works', function() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local my_dir = vim.fs.joinpath(cwd, 'foo') + + eq(nil, vim.fs.relpath('/var/lib', '/var')) + eq(nil, vim.fs.relpath('/var/lib', '/bin')) + eq(nil, vim.fs.relpath(my_dir, 'bin')) + eq(nil, vim.fs.relpath(my_dir, './bin')) + eq(nil, vim.fs.relpath(my_dir, '././')) + eq(nil, vim.fs.relpath(my_dir, '../')) + eq(nil, vim.fs.relpath('/var/lib', '/')) + eq(nil, vim.fs.relpath('/var/lib', '//')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq('.', vim.fs.relpath('/var/lib', '/var/lib')) + eq('lib', vim.fs.relpath('/var/', '/var/lib')) + eq('var/lib', vim.fs.relpath('/', '/var/lib')) + eq('bar/package.json', vim.fs.relpath('/foo/test', '/foo/test/bar/package.json')) + eq('foo/bar', vim.fs.relpath(cwd, 'foo/bar')) + eq('foo/bar', vim.fs.relpath('.', vim.fs.joinpath(cwd, 'foo/bar'))) + eq('bar', vim.fs.relpath('foo', 'foo/bar')) + eq(nil, vim.fs.relpath('/var/lib', '/var/library/foo')) + + if is_os('win') then + eq(nil, vim.fs.relpath('/', ' ')) + eq(nil, vim.fs.relpath('/', 'var')) + else + local cwd_rel_root = cwd:sub(2) + eq(cwd_rel_root .. '/ ', vim.fs.relpath('/', ' ')) + eq(cwd_rel_root .. '/var', vim.fs.relpath('/', 'var')) + end + + if is_os('win') then + eq(nil, vim.fs.relpath('c:/aaaa/', '/aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', './aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', 'aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/blah\\blah', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games/foo')) + eq(nil, vim.fs.relpath('c:/aaaa/bbbb', 'c:/aaaa')) + eq('cccc', vim.fs.relpath('c:/aaaa/', 'c:/aaaa/cccc')) + eq('aaaa/bbbb', vim.fs.relpath('C:/', 'c:\\aaaa\\bbbb')) + eq('bar/package.json', vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json')) + eq('baz', vim.fs.relpath('\\\\foo\\bar', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('a/b/c', 'a\\b')) + eq('d', vim.fs.relpath('a/b/c', 'a\\b\\c\\d')) + eq('.', vim.fs.relpath('\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\Test\\bar\\package.json')) + end + end) + end) end) diff --git a/test/testutil.lua b/test/testutil.lua index 007e017ac6..3226bfeb1e 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -22,13 +22,6 @@ local M = { paths = Paths, } ---- @param p string ---- @return string -local function relpath(p) - p = vim.fs.normalize(p) - return (p:gsub('^' .. uv.cwd, '')) -end - --- @param path string --- @return boolean function M.isdir(path) @@ -45,14 +38,15 @@ end --- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all --- string values in a table (recursively). --- ---- @param obj string|table ---- @return any +--- @generic T: string|table +--- @param obj T +--- @return T|nil function M.fix_slashes(obj) if not M.is_os('win') then return obj end if type(obj) == 'string' then - local ret = obj:gsub('\\', '/') + local ret = string.gsub(obj, '\\', '/') return ret elseif type(obj) == 'table' then --- @cast obj table @@ -482,7 +476,8 @@ function M.check_cores(app, force) -- luacheck: ignore -- "./Xtest-tmpdir/" => "Xtest%-tmpdir" local local_tmpdir = nil if tmpdir_is_local and tmpdir then - local_tmpdir = vim.pesc(relpath(tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', '')) + local_tmpdir = + vim.pesc(vim.fs.relpath(assert(vim.uv.cwd()), tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', '')) end local db_cmd --- @type string -- cgit From 47866cd8d20c62afa8a3c3929d3aada2db9162f5 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Thu, 9 Jan 2025 17:28:27 +0100 Subject: refactor: delete duplicate utf8-functionality Also remove British National Replacement Character Set. We keep the DEC Special Graphics and ASCII despite it not being unicode as some old software such as calcurse still rely on this functionality. References: - https://github.com/neovim/neovim/pull/31934#discussion_r1911046426 - https://en.wikipedia.org/wiki/DEC_Special_Graphics - https://vt100.net/docs/vt220-rm/chapter2.html#S2.4.3 --- src/nvim/mbyte_defs.h | 2 ++ src/nvim/tui/termkey/termkey.c | 43 ++++++++---------------------------------- src/nvim/vterm/encoding.c | 9 +-------- test/unit/vterm_spec.lua | 11 ++++------- 4 files changed, 15 insertions(+), 50 deletions(-) diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h index e308a81a5d..8d5ff2a8c1 100644 --- a/src/nvim/mbyte_defs.h +++ b/src/nvim/mbyte_defs.h @@ -74,3 +74,5 @@ typedef struct { } CharBoundsOff; typedef utf8proc_int32_t GraphemeState; + +enum { UNICODE_INVALID = 0xFFFD, }; diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c index 8c4a91e736..eabde2f9f7 100644 --- a/src/nvim/tui/termkey/termkey.c +++ b/src/nvim/tui/termkey/termkey.c @@ -634,40 +634,13 @@ static void eat_bytes(TermKey *tk, size_t count) tk->buffcount -= count; } -// TODO(dundargoc): we should be able to replace this with utf_char2bytes from mbyte.c int fill_utf8(int codepoint, char *str) { - int nbytes = utf_char2len(codepoint); - + int nbytes = utf_char2bytes(codepoint, str); str[nbytes] = 0; - - // This is easier done backwards - int b = nbytes; - while (b > 1) { - b--; - str[b] = (char)0x80 | (codepoint & 0x3f); - codepoint >>= 6; - } - - switch (nbytes) { - case 1: - str[0] = (codepoint & 0x7f); break; - case 2: - str[0] = (char)0xc0 | (codepoint & 0x1f); break; - case 3: - str[0] = (char)0xe0 | (codepoint & 0x0f); break; - case 4: - str[0] = (char)0xf0 | (codepoint & 0x07); break; - case 5: - str[0] = (char)0xf8 | (codepoint & 0x03); break; - case 6: - str[0] = (char)0xfc | (codepoint & 0x01); break; - } - return nbytes; } -#define UTF8_INVALID 0xFFFD static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, size_t *nbytep) { unsigned nbytes; @@ -681,7 +654,7 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, return TERMKEY_RES_KEY; } else if (b0 < 0xc0) { // Starts with a continuation byte - that's not right - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; *nbytep = 1; return TERMKEY_RES_KEY; } else if (b0 < 0xe0) { @@ -700,7 +673,7 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, nbytes = 6; *cp = b0 & 0x01; } else { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; *nbytep = 1; return TERMKEY_RES_KEY; } @@ -714,7 +687,7 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, cb = bytes[b]; if (cb < 0x80 || cb >= 0xc0) { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; *nbytep = b; return TERMKEY_RES_KEY; } @@ -725,14 +698,14 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, // Check for overlong sequences if ((int)nbytes > utf_char2len(*cp)) { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; } // Check for UTF-16 surrogates or invalid *cps if ((*cp >= 0xD800 && *cp <= 0xDFFF) || *cp == 0xFFFE || *cp == 0xFFFF) { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; } *nbytep = nbytes; @@ -962,9 +935,9 @@ static TermKeyResult peekkey_simple(TermKey *tk, TermKeyKey *key, int force, siz if (res == TERMKEY_RES_AGAIN && force) { // There weren't enough bytes for a complete UTF-8 sequence but caller // demands an answer. About the best thing we can do here is eat as many - // bytes as we have, and emit a UTF8_INVALID. If the remaining bytes + // bytes as we have, and emit a UNICODE_INVALID. If the remaining bytes // arrive later, they'll be invalid too. - codepoint = UTF8_INVALID; + codepoint = UNICODE_INVALID; *nbytep = tk->buffcount; res = TERMKEY_RES_KEY; } diff --git a/src/nvim/vterm/encoding.c b/src/nvim/vterm/encoding.c index cc3208cfa2..f9061e8e50 100644 --- a/src/nvim/vterm/encoding.c +++ b/src/nvim/vterm/encoding.c @@ -210,6 +210,7 @@ static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi } } +// https://en.wikipedia.org/wiki/DEC_Special_Graphics static const struct StaticTableEncoding encoding_DECdrawing = { { .decode = &decode_table }, { @@ -247,13 +248,6 @@ static const struct StaticTableEncoding encoding_DECdrawing = { } }; -static const struct StaticTableEncoding encoding_uk = { - { .decode = &decode_table }, - { - [0x23] = 0x00a3, // £ - } -}; - static struct { VTermEncodingType type; char designation; @@ -262,7 +256,6 @@ static struct { encodings[] = { { ENC_UTF8, 'u', &encoding_utf8 }, { ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing }, - { ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk }, { ENC_SINGLE_94, 'B', &encoding_usascii }, { 0 }, }; diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 0bf4bf70f8..6ff3c18d2a 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -28,6 +28,7 @@ local bit = require('bit') --- @field parser_sos function --- @field parser_text function --- @field print_color function +--- @field schar_get fun(any, any):integer --- @field screen_sb_clear function --- @field screen_sb_popline function --- @field screen_sb_pushline function @@ -43,6 +44,8 @@ local bit = require('bit') --- @field state_setpenattr function --- @field state_settermprop function --- @field term_output function +--- @field utf_ptr2char fun(any):integer +--- @field utf_ptr2len fun(any):integer --- @field vterm_input_write function --- @field vterm_keyboard_end_paste function --- @field vterm_keyboard_key function @@ -360,7 +363,7 @@ local function screen_cell(row, col, expected, screen) pos['row'] = row pos['col'] = col - local cell = t.ffi.new('VTermScreenCell') + local cell = t.ffi.new('VTermScreenCell') ---@type any vterm.vterm_screen_get_cell(screen, pos, cell) local buf = t.ffi.new('unsigned char[32]') @@ -1705,12 +1708,6 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) push('#', vt) expect('putglyph 23 1 0,0') - -- Designate G0=UK - reset(state, nil) - push('\x1b(A', vt) - push('#', vt) - expect('putglyph a3 1 0,0') - -- Designate G0=DEC drawing reset(state, nil) push('\x1b(0', vt) -- cgit From cb7b4e296238b46025de05203c886d67da401728 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 13 Jan 2025 13:59:34 +0100 Subject: feat(messages): "verbose" message kind #31991 --- runtime/doc/news.txt | 2 +- runtime/doc/ui.txt | 1 + src/nvim/message.c | 24 +++++++++++------ test/functional/ui/messages_spec.lua | 50 ++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index a1868e97d0..3edf146b19 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -96,7 +96,7 @@ EVENTS • `msg_show`: • `history` argument indicating if the message was added to the history. • new message kinds: "bufwrite", "completion", "list_cmd", "lua_print", - "search_cmd", "undo", "wildlist". + "search_cmd", "undo", "verbose", wildlist". HIGHLIGHTS diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 8f25133e7a..26ea03d2be 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -806,6 +806,7 @@ must handle. "search_cmd" Entered search command "search_count" Search count message ("S" flag of 'shortmess') "undo" |:undo| and |:redo| message + "verbose" 'verbose' message "wildlist" 'wildmode' "list" message "wmsg" Warning ("search hit BOTTOM", |W10|, …) New kinds may be added in the future; clients should treat unknown diff --git a/src/nvim/message.c b/src/nvim/message.c index 12d980f58f..f87eba27d0 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -3324,6 +3324,10 @@ int redirecting(void) || redir_reg || redir_vname || capture_ga != NULL; } +// Save and restore message kind when emitting a verbose message. +static const char *pre_verbose_kind = NULL; +static const char *verbose_kind = "verbose"; + /// Before giving verbose message. /// Must always be called paired with verbose_leave()! void verbose_enter(void) @@ -3331,6 +3335,10 @@ void verbose_enter(void) if (*p_vfile != NUL) { msg_silent++; } + if (msg_ext_kind != verbose_kind) { + pre_verbose_kind = msg_ext_kind; + msg_ext_set_kind("verbose"); + } } /// After giving verbose message. @@ -3342,14 +3350,17 @@ void verbose_leave(void) msg_silent = 0; } } + if (pre_verbose_kind != NULL) { + msg_ext_set_kind(pre_verbose_kind); + pre_verbose_kind = NULL; + } } /// Like verbose_enter() and set msg_scroll when displaying the message. void verbose_enter_scroll(void) { - if (*p_vfile != NUL) { - msg_silent++; - } else { + verbose_enter(); + if (*p_vfile == NUL) { // always scroll up, don't overwrite msg_scroll = true; } @@ -3358,11 +3369,8 @@ void verbose_enter_scroll(void) /// Like verbose_leave() and set cmdline_row when displaying the message. void verbose_leave_scroll(void) { - if (*p_vfile != NUL) { - if (--msg_silent < 0) { - msg_silent = 0; - } - } else { + verbose_leave(); + if (*p_vfile == NUL) { cmdline_row = msg_row; } } diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 06048c665c..ea4edefe8a 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -330,6 +330,56 @@ describe('ui/ext_messages', function() }, }, }) + + feed(':1verbose filter Diff[AC] hi') + screen:expect({ + cmdline = { { + abort = false, + } }, + messages = { + { + content = { + { '\nDiffAdd ' }, + { 'xxx', 22, 30 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '81 ' }, + { 'guibg=', 18, 5 }, + { 'LightBlue' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { + { '\nDiffChange ' }, + { 'xxx', 4, 31 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '225 ' }, + { 'guibg=', 18, 5 }, + { 'LightMagenta' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) end) it(':echoerr', function() -- cgit From 34e2185022ab698827b72751d77e218a1b6b6afe Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Jan 2025 10:20:43 +0000 Subject: fix(options): better handling of empty values Problem: Whether an option is allowed to be empty isn't well defined and isn't properly checked. Solution: - For non-list string options, explicitly check the option value if it is empty. - Annotate non-list string options that can accept an empty value. - Adjust command completion to ignore the empty value. - Render values in Lua meta files --- runtime/doc/vim_diff.txt | 7 +++++++ runtime/lua/vim/_meta/options.lua | 36 ++++++++++++++++++------------------ scripts/gen_eval_files.lua | 11 ++++++++++- src/nvim/options.lua | 17 ++++++++++------- src/nvim/optionstr.c | 16 ++++++++++++---- test/old/testdir/gen_opt_test.vim | 14 +++++++------- test/old/testdir/test_options.vim | 3 ++- test/old/testdir/test_termdebug.vim | 6 ++++-- test/unit/optionstr_spec.lua | 4 ++-- 9 files changed, 72 insertions(+), 42 deletions(-) diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index d690460f77..a59312208a 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -347,11 +347,15 @@ Options: - `:set {option}<` removes local value for all |global-local| options. - `:setlocal {option}<` copies global value to local value for all options. +- 'ambiwidth' cannot be set to empty. - 'autoread' works in the terminal (if it supports "focus" events) +- 'background' cannot be set to empty. - 'cpoptions' flags: |cpo-_| - 'diffopt' "linematch" feature +- 'eadirection' cannot be set to empty. - 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is prompted whether to trust the file. +- 'fileformat' cannot be set to empty. - 'fillchars' flags: "msgsep", "horiz", "horizup", "horizdown", "vertleft", "vertright", "verthoriz" - 'foldcolumn' supports up to 9 dynamic/fixed columns @@ -363,14 +367,17 @@ Options: - "clean" removes unloaded buffers from the jumplist. - the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. - 'laststatus' global statusline support +- 'mousemodel' cannot be set to empty. - 'mousescroll' amount to scroll by when scrolling with a mouse - 'pumblend' pseudo-transparent popupmenu - 'scrollback' - 'shortmess' - "F" flag does not affect output from autocommands. - "q" flag fully hides macro recording message. +- 'showcmdloc' cannot be set to empty. - 'signcolumn' can show multiple signs (dynamic or fixed columns) - 'statuscolumn' full control of columns using 'statusline' format +- 'splitkeep' cannot be set to empty. - 'tabline' middle-click on tabpage label closes tabpage, and %@Func@foo%X can call any function on mouse-click - 'termpastefilter' diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index c9871c8660..14f252516a 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -52,7 +52,7 @@ vim.go.ari = vim.go.allowrevins --- set to one of CJK locales. See Unicode Standard Annex #11 --- (https://www.unicode.org/reports/tr11). --- ---- @type string +--- @type 'single'|'double' vim.o.ambiwidth = "single" vim.o.ambw = vim.o.ambiwidth vim.go.ambiwidth = vim.o.ambiwidth @@ -208,7 +208,7 @@ vim.go.awa = vim.go.autowriteall --- will change. To use other settings, place ":highlight" commands AFTER --- the setting of the 'background' option. --- ---- @type string +--- @type 'light'|'dark' vim.o.background = "dark" vim.o.bg = vim.o.background vim.go.background = vim.o.background @@ -595,7 +595,7 @@ vim.wo.briopt = vim.wo.breakindentopt --- This option is used together with 'buftype' and 'swapfile' to specify --- special kinds of buffers. See `special-buffers`. --- ---- @type string +--- @type ''|'hide'|'unload'|'delete'|'wipe' vim.o.bufhidden = "" vim.o.bh = vim.o.bufhidden vim.bo.bufhidden = vim.o.bufhidden @@ -658,7 +658,7 @@ vim.bo.bl = vim.bo.buflisted --- without saving. For writing there must be matching `BufWriteCmd|, --- |FileWriteCmd` or `FileAppendCmd` autocommands. --- ---- @type string +--- @type ''|'acwrite'|'help'|'nofile'|'nowrite'|'quickfix'|'terminal'|'prompt' vim.o.buftype = "" vim.o.bt = vim.o.buftype vim.bo.buftype = vim.o.buftype @@ -1118,7 +1118,7 @@ vim.go.cot = vim.go.completeopt --- For Insert mode completion the buffer-local value is used. For --- command line completion the global value is used. --- ---- @type string +--- @type ''|'slash'|'backslash' vim.o.completeslash = "" vim.o.csl = vim.o.completeslash vim.bo.completeslash = vim.o.completeslash @@ -1824,7 +1824,7 @@ vim.go.dy = vim.go.display --- hor horizontally, height of windows is not affected --- both width and height of windows is affected --- ---- @type string +--- @type 'both'|'ver'|'hor' vim.o.eadirection = "both" vim.o.ead = vim.o.eadirection vim.go.eadirection = vim.o.eadirection @@ -2126,7 +2126,7 @@ vim.go.fencs = vim.go.fileencodings --- option is set, because the file would be different when written. --- This option cannot be changed when 'modifiable' is off. --- ---- @type string +--- @type 'unix'|'dos'|'mac' vim.o.fileformat = "unix" vim.o.ff = vim.o.fileformat vim.bo.fileformat = vim.o.fileformat @@ -2382,7 +2382,7 @@ vim.go.fcl = vim.go.foldclose --- "[1-9]": to display a fixed number of columns --- See `folding`. --- ---- @type string +--- @type 'auto'|'auto:1'|'auto:2'|'auto:3'|'auto:4'|'auto:5'|'auto:6'|'auto:7'|'auto:8'|'auto:9'|'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' vim.o.foldcolumn = "0" vim.o.fdc = vim.o.foldcolumn vim.wo.foldcolumn = vim.o.foldcolumn @@ -2479,7 +2479,7 @@ vim.wo.fmr = vim.wo.foldmarker --- `fold-syntax` syntax Syntax highlighting items specify folds. --- `fold-diff` diff Fold text that is not changed. --- ---- @type string +--- @type 'manual'|'expr'|'marker'|'indent'|'syntax'|'diff' vim.o.foldmethod = "manual" vim.o.fdm = vim.o.foldmethod vim.wo.foldmethod = vim.o.foldmethod @@ -3144,7 +3144,7 @@ vim.bo.ims = vim.bo.imsearch --- 'redrawtime') then 'inccommand' is automatically disabled until --- `Command-line-mode` is done. --- ---- @type string +--- @type 'nosplit'|'split'|'' vim.o.inccommand = "nosplit" vim.o.icm = vim.o.inccommand vim.go.inccommand = vim.o.inccommand @@ -4354,7 +4354,7 @@ vim.go.mh = vim.go.mousehide --- "g" is " (jump to tag under mouse click) --- "g" is " ("CTRL-T") --- ---- @type string +--- @type 'extend'|'popup'|'popup_setpos' vim.o.mousemodel = "popup_setpos" vim.o.mousem = vim.o.mousemodel vim.go.mousemodel = vim.o.mousemodel @@ -4947,7 +4947,7 @@ vim.wo.rl = vim.wo.rightleft --- This is useful for languages such as Hebrew, Arabic and Farsi. --- The 'rightleft' option must be set for 'rightleftcmd' to take effect. --- ---- @type string +--- @type 'search' vim.o.rightleftcmd = "search" vim.o.rlc = vim.o.rightleftcmd vim.wo.rightleftcmd = vim.o.rightleftcmd @@ -5222,7 +5222,7 @@ vim.go.sect = vim.go.sections --- backwards, you cannot include the last character of a line, when --- starting in Normal mode and 'virtualedit' empty. --- ---- @type string +--- @type 'inclusive'|'exclusive'|'old' vim.o.selection = "inclusive" vim.o.sel = vim.o.selection vim.go.selection = vim.o.selection @@ -5788,7 +5788,7 @@ vim.go.sc = vim.go.showcmd --- place the text. Without a custom 'statusline' or 'tabline' it will be --- displayed in a convenient location. --- ---- @type string +--- @type 'last'|'statusline'|'tabline' vim.o.showcmdloc = "last" vim.o.sloc = vim.o.showcmdloc vim.go.showcmdloc = vim.o.showcmdloc @@ -5920,7 +5920,7 @@ vim.go.siso = vim.go.sidescrolloff --- "number" display signs in the 'number' column. If the number --- column is not present, then behaves like "auto". --- ---- @type string +--- @type 'yes'|'no'|'auto'|'auto:1'|'auto:2'|'auto:3'|'auto:4'|'auto:5'|'auto:6'|'auto:7'|'auto:8'|'auto:9'|'yes:1'|'yes:2'|'yes:3'|'yes:4'|'yes:5'|'yes:6'|'yes:7'|'yes:8'|'yes:9'|'number' vim.o.signcolumn = "auto" vim.o.scl = vim.o.signcolumn vim.wo.signcolumn = vim.o.signcolumn @@ -6228,7 +6228,7 @@ vim.go.sb = vim.go.splitbelow --- with the previous cursor position. For "screen", the text cannot always --- be kept on the same screen line when 'wrap' is enabled. --- ---- @type string +--- @type 'cursor'|'screen'|'topline' vim.o.splitkeep = "cursor" vim.o.spk = vim.o.splitkeep vim.go.splitkeep = vim.o.splitkeep @@ -6876,7 +6876,7 @@ vim.go.tbs = vim.go.tagbsearch --- match Match case --- smart Ignore case unless an upper case letter is used --- ---- @type string +--- @type 'followic'|'ignore'|'match'|'followscs'|'smart' vim.o.tagcase = "followic" vim.o.tc = vim.o.tagcase vim.bo.tagcase = vim.o.tagcase @@ -7758,7 +7758,7 @@ vim.go.wop = vim.go.wildoptions --- key is never used for the menu. --- This option is not used for ; on Win32. --- ---- @type string +--- @type 'yes'|'menu'|'no' vim.o.winaltkeys = "menu" vim.o.wak = vim.o.winaltkeys vim.go.winaltkeys = vim.o.winaltkeys diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index f888972f0d..58d3eeeadc 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -666,7 +666,16 @@ local function render_option_meta(_f, opt, write) write('--- ' .. l) end - write('--- @type ' .. OPTION_TYPES[opt.type]) + if opt.type == 'string' and not opt.list and opt.values then + local values = {} --- @type string[] + for _, e in ipairs(opt.values) do + values[#values + 1] = fmt("'%s'", e) + end + write('--- @type ' .. table.concat(values, '|')) + else + write('--- @type ' .. OPTION_TYPES[opt.type]) + end + write('vim.o.' .. opt.full_name .. ' = ' .. render_option_default(opt.defaults)) if opt.abbreviation then write('vim.o.' .. opt.abbreviation .. ' = vim.o.' .. opt.full_name) diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 15a4e8ddc2..2425dcb93e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7,7 +7,7 @@ --- @field alias? string|string[] --- @field short_desc? string|fun(): string --- @field varname? string ---- @field type vim.option_type|vim.option_type[] +--- @field type vim.option_type --- @field immutable? boolean --- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma' --- @field scope vim.option_scope[] @@ -834,7 +834,7 @@ return { abbreviation = 'bh', cb = 'did_set_bufhidden', defaults = { if_true = '' }, - values = { 'hide', 'unload', 'delete', 'wipe' }, + values = { '', 'hide', 'unload', 'delete', 'wipe' }, desc = [=[ This option specifies what happens when a buffer is no longer displayed in a window: @@ -888,11 +888,12 @@ return { cb = 'did_set_buftype', defaults = { if_true = '' }, values = { + '', + 'acwrite', + 'help', 'nofile', 'nowrite', 'quickfix', - 'help', - 'acwrite', 'terminal', 'prompt', }, @@ -1554,7 +1555,7 @@ return { abbreviation = 'csl', cb = 'did_set_completeslash', defaults = { if_true = '' }, - values = { 'slash', 'backslash' }, + values = { '', 'slash', 'backslash' }, desc = [=[ only modifiable in MS-Windows When this option is set it overrules 'shellslash' for completion: @@ -2017,8 +2018,10 @@ return { "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or 'indentexpr'. ]=], + -- TODO(lewis6991): bug, values currently cannot be combined expand_cb = 'expand_set_debug', full_name = 'debug', + list = 'comma', scope = { 'global' }, short_desc = N_('to "msg" to see all error messages'), type = 'string', @@ -4299,7 +4302,7 @@ return { abbreviation = 'icm', cb = 'did_set_inccommand', defaults = { if_true = 'nosplit' }, - values = { 'nosplit', 'split' }, + values = { 'nosplit', 'split', '' }, desc = [=[ When nonempty, shows the effects of |:substitute|, |:smagic|, |:snomagic| and user commands with the |:command-preview| flag as you @@ -5735,7 +5738,7 @@ return { abbreviation = 'mousem', cb = 'did_set_mousemodel', defaults = { if_true = 'popup_setpos' }, - values = { 'extend', 'popup', 'popup_setpos', 'mac' }, + values = { 'extend', 'popup', 'popup_setpos' }, desc = [=[ Sets the model to use for the mouse. The name mostly specifies what the right mouse button is used for: diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 9b7b50ae04..eac9ea02e0 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -395,7 +395,9 @@ static int expand_set_opt_string(optexpand_T *args, const char **values, size_t } for (const char **val = values; *val != NULL; val++) { - if (include_orig_val && *option_val != NUL) { + if (**val == NUL) { + continue; // Ignore empty + } else if (include_orig_val && *option_val != NUL) { if (strcmp(*val, option_val) == 0) { continue; } @@ -1091,7 +1093,7 @@ int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches /// The 'debug' option is changed. const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_debug, opt_debug_values, false); + return did_set_opt_strings(p_debug, opt_debug_values, true); } int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches) @@ -2545,7 +2547,7 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char *val, const char **values, int list) +static int check_opt_strings(char *val, const char **values, bool list) { return opt_strings_flags(val, values, NULL, list); } @@ -2562,7 +2564,10 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla { unsigned new_flags = 0; - while (*val) { + // If not list and val is empty, then force one iteration of the while loop + bool iter_one = (*val == NUL) && !list; + + while (*val || iter_one) { for (unsigned i = 0;; i++) { if (values[i] == NULL) { // val not found in values[] return FAIL; @@ -2577,6 +2582,9 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla break; // check next item in val list } } + if (iter_one) { + break; + } } if (flagp != NULL) { *flagp = new_flags; diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 3802ee3fc9..bcb0f3d4c4 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -145,8 +145,8 @@ let test_values = { \ 'winwidth': [[1, 10, 999], [-1, 0]], \ "\ string options - \ 'ambiwidth': [['', 'single', 'double'], ['xxx']], - \ 'background': [['', 'light', 'dark'], ['xxx']], + \ 'ambiwidth': [['single', 'double'], ['xxx']], + \ 'background': [['light', 'dark'], ['xxx']], "\ 'backspace': [[0, 1, 2, 3, '', 'indent', 'eol', 'start', 'nostop', "\ " 'eol,start', 'indent,eol,nostop'], "\ " [-1, 4, 'xxx']], @@ -214,12 +214,12 @@ let test_values = { \ ['xxx', 'foldcolumn:xxx', 'algorithm:xxx', 'algorithm:']], \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'], \ ['xxx']], - \ 'eadirection': [['', 'both', 'ver', 'hor'], ['xxx', 'ver,hor']], + \ 'eadirection': [['both', 'ver', 'hor'], ['xxx', 'ver,hor']], "\ 'encoding': [['latin1'], ['xxx', '']], \ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'], \ ['xxx']], \ 'fileencoding': [['', 'latin1', 'xxx'], []], - \ 'fileformat': [['', 'dos', 'unix', 'mac'], ['xxx']], + \ 'fileformat': [['dos', 'unix', 'mac'], ['xxx']], \ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']], \ 'fillchars': [['', 'stl:x', 'stlnc:x', 'vert:x', 'fold:x', 'foldopen:x', \ 'foldclose:x', 'foldsep:x', 'diff:x', 'eob:x', 'lastline:x', @@ -274,7 +274,7 @@ let test_values = { \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']], \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'], \ ['xxx', 'n,v,i']], - \ 'mousemodel': [['', 'extend', 'popup', 'popup_setpos'], ['xxx']], + \ 'mousemodel': [['extend', 'popup', 'popup_setpos'], ['xxx']], \ 'mouseshape': [['', 'n:arrow'], ['xxx']], \ 'nrformats': [['', 'alpha', 'octal', 'hex', 'bin', 'unsigned', 'blank', \ 'alpha,hex,bin'], @@ -299,7 +299,7 @@ let test_values = { \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir', \ 'help,options,slash'], \ ['xxx', 'curdir,sesdir']], - \ 'showcmdloc': [['', 'last', 'statusline', 'tabline'], ['xxx']], + \ 'showcmdloc': [['last', 'statusline', 'tabline'], ['xxx']], "\ 'signcolumn': [['', 'auto', 'no', 'yes', 'number'], ['xxx', 'no,yes']], \ 'spellfile': [['', 'file.en.add', 'xxx.en.add,yyy.gb.add,zzz.ja.add', \ '/tmp/dir\ with\ space/en.utf-8.add', @@ -311,7 +311,7 @@ let test_values = { \ 'spellsuggest': [['', 'best', 'double', 'fast', '100', 'timeout:100', \ 'timeout:-1', 'file:/tmp/file', 'expr:Func()', 'double,33'], \ ['xxx', '-1', 'timeout:', 'best,double', 'double,fast']], - \ 'splitkeep': [['', 'cursor', 'screen', 'topline'], ['xxx']], + \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']], \ 'statusline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']], "\ 'swapsync': [['', 'sync', 'fsync'], ['xxx']], \ 'switchbuf': [['', 'useopen', 'usetab', 'split', 'vsplit', 'newtab', diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index d090186b62..dfa140b163 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -504,7 +504,8 @@ func Test_set_completion_string_values() call assert_equal('current', getcompletion('set browsedir=', 'cmdline')[1]) endif call assert_equal('unload', getcompletion('set bufhidden=', 'cmdline')[1]) - call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + "call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + call assert_equal('help', getcompletion('set buftype=', 'cmdline')[1]) call assert_equal('internal', getcompletion('set casemap=', 'cmdline')[1]) if exists('+clipboard') " call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1]) diff --git a/test/old/testdir/test_termdebug.vim b/test/old/testdir/test_termdebug.vim index eb88ea6f5f..6ff233fff0 100644 --- a/test/old/testdir/test_termdebug.vim +++ b/test/old/testdir/test_termdebug.vim @@ -391,7 +391,8 @@ endfunc function Test_termdebug_save_restore_variables() " saved mousemodel - let &mousemodel='' + "let &mousemodel='' + let &mousemodel='extend' " saved keys nnoremap K :echo "hello world!" @@ -414,7 +415,8 @@ function Test_termdebug_save_restore_variables() quit! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_true(empty(&mousemodel)) + "call assert_true(empty(&mousemodel)) + call assert_equal(&mousemodel, 'extend') call assert_true(empty(expected_map_minus)) call assert_equal(expected_map_K.rhs, maparg('K', 'n', 0, 1).rhs) diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua index b9c9ceaa85..1f5b42485f 100644 --- a/test/unit/optionstr_spec.lua +++ b/test/unit/optionstr_spec.lua @@ -11,8 +11,8 @@ local check_ff_value = function(ff) end describe('check_ff_value', function() - itp('views empty string as valid', function() - eq(1, check_ff_value('')) + itp('views empty string as invalid', function() + eq(0, check_ff_value('')) end) itp('views "unix", "dos" and "mac" as valid', function() -- cgit From a4f575abd85e734340ee303daace1a63e5ca9782 Mon Sep 17 00:00:00 2001 From: Xuyuan Pang Date: Tue, 14 Jan 2025 07:17:23 +0800 Subject: fix(lsp): minimum height for floating popup #31990 Problem: The floating window for hover and signature help always cuts off a few lines, because the `_make_floating_popup_size` function counts empty lines as having zero height. Solution: Ensure the height is at least 1. --- runtime/lua/vim/lsp/util.lua | 2 +- test/functional/plugin/lsp_spec.lua | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ccd68f0fdf..14633adf0c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1432,7 +1432,7 @@ function M._make_floating_popup_size(contents, opts) if vim.tbl_isempty(line_widths) then for _, line in ipairs(contents) do local line_width = vim.fn.strdisplaywidth(line:gsub('%z', '\n')) - height = height + math.ceil(line_width / wrap_at) + height = height + math.max(1, math.ceil(line_width / wrap_at)) end else for i = 1, #contents do diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 9cefe96e79..5e9766c784 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3501,6 +3501,19 @@ describe('LSP', function() end) ) end) + it('handles empty line', function() + exec_lua(function() + _G.contents = { + '', + } + end) + eq( + { 20, 1 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents, { width = 20 }) } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() -- cgit From 0dfa4de9933b548d050feeff5676d580c7103787 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 14 Jan 2025 08:33:11 +0100 Subject: build(deps): bump luajit to HEAD - a4f56a459 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index cf220b0fbd..602d2e2d03 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -1,8 +1,8 @@ LIBUV_URL https://github.com/libuv/libuv/archive/v1.49.2.tar.gz LIBUV_SHA256 388ffcf3370d4cf7c4b3a3205504eea06c4be5f9e80d2ab32d19f8235accc1cf -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/f73e649a954b599fc184726c376476e7a5c439ca.tar.gz -LUAJIT_SHA256 bc992b3ae0a8f5f0ebbf141626b7c99fac794c94ec6896d973582525c7ef868d +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/a4f56a459a588ae768801074b46ba0adcfb49eb1.tar.gz +LUAJIT_SHA256 b4120332a4191db9c9da2d81f9f11f0d4504fc4cff2dea0f642d3d8f1fcebd0e LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 -- cgit From 5a54681025ec28129c21c875943a3f9c37959f75 Mon Sep 17 00:00:00 2001 From: Horror Proton <107091537+horror-proton@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:43:33 +0800 Subject: fix(treesitter): uv_dlclose after uv_dlerror --- src/nvim/lua/treesitter.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 9bd2baad27..3493384a8f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -128,9 +128,9 @@ static const TSLanguage *load_language_from_object(lua_State *L, const char *pat { uv_lib_t lib; if (uv_dlopen(path, &lib)) { + xstrlcpy(IObuff, uv_dlerror(&lib), sizeof(IObuff)); uv_dlclose(&lib); - luaL_error(L, "Failed to load parser for language '%s': uv_dlopen: %s", - lang_name, uv_dlerror(&lib)); + luaL_error(L, "Failed to load parser for language '%s': uv_dlopen: %s", lang_name, IObuff); } char symbol_buf[128]; @@ -138,8 +138,9 @@ static const TSLanguage *load_language_from_object(lua_State *L, const char *pat TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + xstrlcpy(IObuff, uv_dlerror(&lib), sizeof(IObuff)); uv_dlclose(&lib); - luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + luaL_error(L, "Failed to load parser: uv_dlsym: %s", IObuff); } TSLanguage *lang = lang_parser(); -- cgit From b192d58284a791c55f5ae000250fc948e9098d47 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Mon, 13 Jan 2025 09:42:39 -0800 Subject: perf(treesitter): calculate folds asynchronously **Problem:** The treesitter `foldexpr` runs synchronous parses to calculate fold levels, which eliminates async parsing performance in the highlighter. **Solution:** Migrate the `foldexpr` to also calculate and apply fold levels asynchronously. --- runtime/doc/news.txt | 1 + runtime/lua/vim/treesitter/_fold.lua | 203 +++++++++++++++++++---------------- 2 files changed, 110 insertions(+), 94 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3edf146b19..89504ae244 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -305,6 +305,7 @@ PERFORMANCE queries. • Treesitter highlighting is now asynchronous. To force synchronous parsing, use `vim.g._ts_force_sync_parsing = true`. +• Treesitter folding is now calculated asynchronously. PLUGINS diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7f1d1b14d5..2777241e9f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -69,7 +69,8 @@ end ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? 0-indexed, exclusive -local function compute_folds_levels(bufnr, info, srow, erow) +---@param callback function? +local function compute_folds_levels(bufnr, info, srow, erow, callback) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) @@ -78,104 +79,112 @@ local function compute_folds_levels(bufnr, info, srow, erow) return end - parser:parse() - - local enter_counts = {} ---@type table - local leave_counts = {} ---@type table - local prev_start = -1 - local prev_stop = -1 - - parser:for_each_tree(function(tree, ltree) - local query = ts.query.get(ltree:lang(), 'folds') - if not query then + parser:parse(nil, function(_, trees) + if not trees then return end - -- Collect folds starting from srow - 1, because we should first subtract the folds that end at - -- srow - 1 from the level of srow - 1 to get accurate level of srow. - for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do - for id, nodes in pairs(match) do - if query.captures[id] == 'fold' then - local range = ts.get_range(nodes[1], bufnr, metadata[id]) - local start, _, stop, stop_col = Range.unpack4(range) - - if #nodes > 1 then - -- assumes nodes are ordered by range - local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id]) - local _, _, end_stop, end_stop_col = Range.unpack4(end_range) - stop = end_stop - stop_col = end_stop_col - end + local enter_counts = {} ---@type table + local leave_counts = {} ---@type table + local prev_start = -1 + local prev_stop = -1 - if stop_col == 0 then - stop = stop - 1 - end + parser:for_each_tree(function(tree, ltree) + local query = ts.query.get(ltree:lang(), 'folds') + if not query then + return + end - local fold_length = stop - start + 1 - - -- Fold only multiline nodes that are not exactly the same as previously met folds - -- Checking against just the previously found fold is sufficient if nodes - -- are returned in preorder or postorder when traversing tree - if - fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) - then - enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 - leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 - prev_start = start - prev_stop = stop + -- Collect folds starting from srow - 1, because we should first subtract the folds that end at + -- srow - 1 from the level of srow - 1 to get accurate level of srow. + for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do + for id, nodes in pairs(match) do + if query.captures[id] == 'fold' then + local range = ts.get_range(nodes[1], bufnr, metadata[id]) + local start, _, stop, stop_col = Range.unpack4(range) + + if #nodes > 1 then + -- assumes nodes are ordered by range + local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id]) + local _, _, end_stop, end_stop_col = Range.unpack4(end_range) + stop = end_stop + stop_col = end_stop_col + end + + if stop_col == 0 then + stop = stop - 1 + end + + local fold_length = stop - start + 1 + + -- Fold only multiline nodes that are not exactly the same as previously met folds + -- Checking against just the previously found fold is sufficient if nodes + -- are returned in preorder or postorder when traversing tree + if + fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) + then + enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 + leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 + prev_start = start + prev_stop = stop + end end end end - end - end) + end) - local nestmax = vim.wo.foldnestmax - local level0_prev = info.levels0[srow] or 0 - local leave_prev = leave_counts[srow] or 0 - - -- We now have the list of fold opening and closing, fill the gaps and mark where fold start - for lnum = srow + 1, erow do - local enter_line = enter_counts[lnum] or 0 - local leave_line = leave_counts[lnum] or 0 - local level0 = level0_prev - leave_prev + enter_line - - -- Determine if it's the start/end of a fold - -- NB: vim's fold-expr interface does not have a mechanism to indicate that - -- two (or more) folds start at this line, so it cannot distinguish between - -- ( \n ( \n )) \n (( \n ) \n ) - -- versus - -- ( \n ( \n ) \n ( \n ) \n ) - -- Both are represented by ['>1', '>2', '2', '>2', '2', '1'], and - -- vim interprets as the second case. - -- If it did have such a mechanism, (clamped - clamped_prev) - -- would be the correct number of starts to pass on. - local adjusted = level0 ---@type integer - local prefix = '' - if enter_line > 0 then - prefix = '>' - if leave_line > 0 then - -- If this line ends a fold f1 and starts a fold f2, then move f1's end to the previous line - -- so that f2 gets the correct level on this line. This may reduce the size of f1 below - -- foldminlines, but we don't handle it for simplicity. - adjusted = level0 - leave_line - leave_line = 0 + local nestmax = vim.wo.foldnestmax + local level0_prev = info.levels0[srow] or 0 + local leave_prev = leave_counts[srow] or 0 + + -- We now have the list of fold opening and closing, fill the gaps and mark where fold start + for lnum = srow + 1, erow do + local enter_line = enter_counts[lnum] or 0 + local leave_line = leave_counts[lnum] or 0 + local level0 = level0_prev - leave_prev + enter_line + + -- Determine if it's the start/end of a fold + -- NB: vim's fold-expr interface does not have a mechanism to indicate that + -- two (or more) folds start at this line, so it cannot distinguish between + -- ( \n ( \n )) \n (( \n ) \n ) + -- versus + -- ( \n ( \n ) \n ( \n ) \n ) + -- Both are represented by ['>1', '>2', '2', '>2', '2', '1'], and + -- vim interprets as the second case. + -- If it did have such a mechanism, (clamped - clamped_prev) + -- would be the correct number of starts to pass on. + local adjusted = level0 ---@type integer + local prefix = '' + if enter_line > 0 then + prefix = '>' + if leave_line > 0 then + -- If this line ends a fold f1 and starts a fold f2, then move f1's end to the previous line + -- so that f2 gets the correct level on this line. This may reduce the size of f1 below + -- foldminlines, but we don't handle it for simplicity. + adjusted = level0 - leave_line + leave_line = 0 + end end - end - -- Clamp at foldnestmax. - local clamped = adjusted - if adjusted > nestmax then - prefix = '' - clamped = nestmax - end + -- Clamp at foldnestmax. + local clamped = adjusted + if adjusted > nestmax then + prefix = '' + clamped = nestmax + end - -- Record the "real" level, so that it can be used as "base" of later compute_folds_levels(). - info.levels0[lnum] = adjusted - info.levels[lnum] = prefix .. tostring(clamped) + -- Record the "real" level, so that it can be used as "base" of later compute_folds_levels(). + info.levels0[lnum] = adjusted + info.levels[lnum] = prefix .. tostring(clamped) - leave_prev = leave_line - level0_prev = adjusted - end + leave_prev = leave_line + level0_prev = adjusted + end + + if callback then + callback() + end + end) end local M = {} @@ -266,6 +275,8 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) schedule_if_loaded(bufnr, function() local srow_upd, erow_upd ---@type integer?, integer? local max_erow = api.nvim_buf_line_count(bufnr) + -- TODO(ribru17): Replace this with a proper .all() awaiter once #19624 is resolved + local iterations = 0 for _, change in ipairs(tree_changes) do local srow, _, erow, ecol = Range.unpack4(change) -- If a parser doesn't have any ranges explicitly set, treesitter will @@ -279,12 +290,14 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) end -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. srow = math.max(srow - vim.wo.foldminlines, 0) - compute_folds_levels(bufnr, foldinfo, srow, erow) srow_upd = srow_upd and math.min(srow_upd, srow) or srow erow_upd = erow_upd and math.max(erow_upd, erow) or erow - end - if #tree_changes > 0 then - foldinfo:foldupdate(bufnr, srow_upd, erow_upd) + compute_folds_levels(bufnr, foldinfo, srow, erow, function() + iterations = iterations + 1 + if iterations == #tree_changes then + foldinfo:foldupdate(bufnr, srow_upd, erow_upd) + end + end) end end) end @@ -342,8 +355,9 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, foldinfo.on_bytes_range = nil -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. srow = math.max(srow - vim.wo.foldminlines, 0) - compute_folds_levels(bufnr, foldinfo, srow, erow) - foldinfo:foldupdate(bufnr, srow, erow) + compute_folds_levels(bufnr, foldinfo, srow, erow, function() + foldinfo:foldupdate(bufnr, srow, erow) + end) end) end end @@ -400,9 +414,10 @@ api.nvim_create_autocmd('OptionSet', { for _, bufnr in ipairs(bufs) do foldinfos[bufnr] = FoldInfo.new(bufnr) api.nvim_buf_call(bufnr, function() - compute_folds_levels(bufnr, foldinfos[bufnr]) + compute_folds_levels(bufnr, foldinfos[bufnr], nil, nil, function() + foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) + end) end) - foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) end end, }) -- cgit From c5f93d7ab04f93db1470d58ca1f70e947e716c2b Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 28 Dec 2024 14:55:22 +0600 Subject: refactor(options): remove code for multitype options Problem: It was decided on Matrix chat that multitype options won't be necessary for Neovim options, and that options should only have a single canonical type. Therefore the code for supporting multitype options is unnecessary. Solution: Remove the additional code that's used to provide multitype option support. --- src/nvim/eval/vars.c | 11 +++---- src/nvim/generators/gen_options.lua | 17 +--------- src/nvim/option.c | 65 +++---------------------------------- src/nvim/option_defs.h | 6 +--- 4 files changed, 11 insertions(+), 88 deletions(-) diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index b9b5a055fb..012d23b567 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -843,11 +843,10 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, goto theend; } - // Don't assume current and new values are of the same type in order to future-proof the code for - // when an option can have multiple types. - const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean) - && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean)); - const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString; + // Current value and new value must have the same type. + assert(curval.type == newval.type); + const bool is_num = curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean; + const bool is_string = curval.type == kOptValTypeString; if (op != NULL && *op != '=') { if (!hidden && is_num) { // number or bool @@ -1900,8 +1899,6 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off) /// /// @return Typval converted to OptVal. Must be freed by caller. /// Returns NIL_OPTVAL for invalid option name. -/// -/// TODO(famiu): Refactor this to support multitype options. static OptVal tv_to_optval(typval_T *tv, OptIndex opt_idx, const char *option, bool *error) { OptVal value = NIL_OPTVAL; diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index c79683dc00..0298381ece 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -312,21 +312,6 @@ local function opt_scope_enum(scope) return ('kOptScope%s'):format(lowercase_to_titlecase(scope)) end ---- @param o vim.option_meta ---- @return string -local function get_type_flags(o) - local opt_types = (type(o.type) == 'table') and o.type or { o.type } - local type_flags = '0' - assert(type(opt_types) == 'table') - - for _, opt_type in ipairs(opt_types) do - assert(type(opt_type) == 'string') - type_flags = ('%s | (1 << %s)'):format(type_flags, opt_type_enum(opt_type)) - end - - return type_flags -end - --- @param o vim.option_meta --- @return string local function get_scope_flags(o) @@ -427,8 +412,8 @@ local function dump_option(i, o) if o.abbreviation then w(' .shortname=' .. cstr(o.abbreviation)) end + w(' .type=' .. opt_type_enum(o.type)) w(' .flags=' .. get_flags(o)) - w(' .type_flags=' .. get_type_flags(o)) w(' .scope_flags=' .. get_scope_flags(o)) w(' .scope_idx=' .. get_scope_idx(o)) if o.enable_if then diff --git a/src/nvim/option.c b/src/nvim/option.c index 551ea0be20..593ac62172 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3121,17 +3121,10 @@ bool optval_equal(OptVal o1, OptVal o2) UNREACHABLE; } -/// Get type of option. Does not support multitype options. +/// Get type of option. static OptValType option_get_type(const OptIndex opt_idx) { - assert(!option_is_multitype(opt_idx)); - - // If the option only supports a single type, it means that the index of the option's type flag - // corresponds to the value of the type enum. So get the index of the type flag using xctz() and - // use that as the option's type. - OptValType type = xctz(options[opt_idx].type_flags); - assert(type > kOptValTypeNil && type < kOptValTypeSize); - return type; + return options[opt_idx].type; } /// Create OptVal from var pointer. @@ -3149,11 +3142,6 @@ OptVal optval_from_varp(OptIndex opt_idx, void *varp) return BOOLEAN_OPTVAL(curbufIsChanged()); } - if (option_is_multitype(opt_idx)) { - // Multitype options are stored as OptVal. - return *(OptVal *)varp; - } - OptValType type = option_get_type(opt_idx); switch (type) { @@ -3264,33 +3252,6 @@ OptVal object_as_optval(Object o, bool *error) UNREACHABLE; } -/// Get an allocated string containing a list of valid types for an option. -/// For options with a singular type, it returns the name of the type. For options with multiple -/// possible types, it returns a slash separated list of types. For example, if an option can be a -/// number, boolean or string, the function returns "number/boolean/string" -static char *option_get_valid_types(OptIndex opt_idx) -{ - StringBuilder str = KV_INITIAL_VALUE; - kv_resize(str, 32); - - // Iterate through every valid option value type and check if the option supports that type - for (OptValType type = 0; type < kOptValTypeSize; type++) { - if (option_has_type(opt_idx, type)) { - const char *typename = optval_type_get_name(type); - - if (str.size == 0) { - kv_concat(str, typename); - } else { - kv_printf(str, "/%s", typename); - } - } - } - - // Ensure that the string is NUL-terminated. - kv_push(str, NUL); - return str.items; -} - /// Check if option is hidden. /// /// @param opt_idx Option index in options[] table. @@ -3303,25 +3264,10 @@ bool is_option_hidden(OptIndex opt_idx) && options[opt_idx].var == &options[opt_idx].def_val.data; } -/// Check if option is multitype (supports multiple types). -static bool option_is_multitype(OptIndex opt_idx) -{ - const OptTypeFlags type_flags = get_option(opt_idx)->type_flags; - assert(type_flags != 0); - return !is_power_of_two(type_flags); -} - /// Check if option supports a specific type. bool option_has_type(OptIndex opt_idx, OptValType type) { - // Ensure that type flags variable can hold all types. - STATIC_ASSERT(kOptValTypeSize <= sizeof(OptTypeFlags) * 8, - "Option type_flags cannot fit all option types"); - // Ensure that the type is valid before accessing type_flags. - assert(type > kOptValTypeNil && type < kOptValTypeSize); - // Bitshift 1 by the value of type to get the type's corresponding flag, and check if it's set in - // the type_flags bit field. - return get_option(opt_idx)->type_flags & (1 << type); + return options[opt_idx].type == type; } /// Check if option supports a specific scope. @@ -3658,11 +3604,10 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval, } } else if (!option_has_type(opt_idx, newval->type)) { char *rep = optval_to_cstr(*newval); - char *valid_types = option_get_valid_types(opt_idx); + const char *type_str = optval_type_get_name(opt->type); snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"), - opt->fullname, valid_types, optval_type_get_name(newval->type), rep); + opt->fullname, type_str, optval_type_get_name(newval->type), rep); xfree(rep); - xfree(valid_types); errmsg = errbuf; } else if (newval->type == kOptValTypeNumber) { // Validate and bound check num option values. diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 832e03148a..2b51547004 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -54,9 +54,6 @@ typedef enum { kOptValTypeNumber, kOptValTypeString, } OptValType; -/// Always update this whenever a new option type is added. -#define kOptValTypeSize (kOptValTypeString + 1) -typedef uint32_t OptTypeFlags; /// Scopes that an option can support. typedef enum { @@ -99,7 +96,6 @@ typedef struct { int os_flags; /// Old value of the option. - /// TODO(famiu): Convert `os_oldval` and `os_newval` to `OptVal` to accommodate multitype options. OptValData os_oldval; /// New value of the option. OptValData os_newval; @@ -173,7 +169,7 @@ typedef struct { char *fullname; ///< full option name char *shortname; ///< permissible abbreviation uint32_t flags; ///< see above - OptTypeFlags type_flags; ///< option type flags, see OptValType + OptValType type; ///< option type OptScopeFlags scope_flags; ///< option scope flags, see OptScope void *var; ///< global option: pointer to variable; ///< window-local option: NULL; -- cgit From 69ad6b12ec473a54f09a11596da724178185eb7a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 14 Jan 2025 18:55:06 +0800 Subject: vim-patch:9.1.1010: filetype: VisualCode setting file not recognized (#32003) Problem: filetype: VisualCode setting file not recognized Solution: detect json files in VSCode config directory as jsonc filetype (Konfekt) closes: vim/vim#16400 https://github.com/vim/vim/commit/c200f53cbb03fa11e489a27791d5b9dfc34a6564 Co-authored-by: Konfekt --- runtime/lua/vim/filetype.lua | 2 ++ test/old/testdir/test_filetype.vim | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index bc866db399..1960bca52b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2374,6 +2374,8 @@ local pattern = { ['%.html%.m4$'] = 'htmlm4', ['^JAM.*%.'] = starsetf('jam'), ['^Prl.*%.'] = starsetf('jam'), + ['^${HOME}/.*/Code/User/.*%.json$'] = 'jsonc', + ['^${HOME}/.*/VSCodium/User/.*%.json$'] = 'jsonc', ['%.properties_..$'] = 'jproperties', ['%.properties_.._..$'] = 'jproperties', ['%.properties_.._.._'] = starsetf('jproperties'), diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 5182ff5167..2c6b1bd0f4 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -395,7 +395,7 @@ func s:GetFilenameChecks() abort \ 'jq': ['file.jq'], \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'], \ 'json5': ['file.json5'], - \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock'], + \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock', expand("$HOME/.config/VSCodium/User/settings.json")], \ 'jsonl': ['file.jsonl'], \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'], \ 'jsp': ['file.jsp'], -- cgit From e8ddb7a46938f8843abc1c321cfd83cee2ba0020 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 14 Jan 2025 08:32:36 +0100 Subject: vim-patch:30377e0: runtime(lyrics): support milliseconds in syntax script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following tool creates LRC files using three fractional digits after the seconds (i.e. milliseconds). References: https://github.com/magic-akari/lrc-maker https://lrc-maker.github.io/ closes: vim/vim#16436 https://github.com/vim/vim/commit/30377e0fe084496911e108cbb33c84cf075e6e33 Co-authored-by: Denilson Sá Maia --- runtime/syntax/lyrics.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/syntax/lyrics.vim b/runtime/syntax/lyrics.vim index fd127988f2..48a5b1171c 100644 --- a/runtime/syntax/lyrics.vim +++ b/runtime/syntax/lyrics.vim @@ -2,7 +2,7 @@ " Language: LyRiCs " Maintainer: ObserverOfTime " Filenames: *.lrc -" Last Change: 2024 Sep 20 +" Last Change: 2025 Jan 13 if exists('b:current_syntax') finish @@ -23,7 +23,7 @@ syn match lrcTagName contained nextgroup=lrcTagValue syn match lrcTagValue /:\zs.\+\ze\]/ contained " Lyrics -syn match lrcLyricTime /^\s*\(\[\d\d:\d\d\.\d\d\]\)\+/ +syn match lrcLyricTime /^\s*\(\[\d\d:\d\d\.\d\d\d\?\]\)\+/ \ contains=lrcNumber nextgroup=lrcLyricLine syn match lrcLyricLine /.*$/ contained contains=lrcWordTime,@Spell syn match lrcWordTime /<\d\d:\d\d\.\d\d>/ contained contains=lrcNumber,@NoSpell -- cgit From 25d8c3a5ad7e9c5668841e66540ebe34ceda73a7 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 14 Jan 2025 14:02:46 +0100 Subject: feat(api): nvim_open_win() relative to tabline and laststatus #32006 Problem: Anchoring a floating window to the tabline and laststatus is cumbersome; requiring autocommands and looping over all windows/tabpages. Solution: Add new "tabline" and "laststatus" options to the `relative` field of nvim_open_win() to place a window relative to. --- runtime/doc/api.txt | 8 ++-- runtime/doc/news.txt | 1 + runtime/lua/vim/_meta/api.lua | 10 +++-- src/nvim/api/win_config.c | 18 +++++--- src/nvim/buffer_defs.h | 2 + src/nvim/window.c | 6 +++ src/nvim/winfloat.c | 9 ++++ test/functional/ui/float_spec.lua | 91 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 133 insertions(+), 12 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 572e5e4267..5a6361d45f 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3150,11 +3150,13 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* • {config} Map defining the window configuration. Keys: • relative: Sets the window layout to "floating", placed at (row,col) coordinates relative to: - • "editor" The global editor grid + • "cursor" Cursor position in current window. + • "editor" The global editor grid. + • "laststatus" 'laststatus' if present, or last row. + • "mouse" Mouse position. + • "tabline" Tabline if present, or first row. • "win" Window given by the `win` field, or current window. - • "cursor" Cursor position in current window. - • "mouse" Mouse position • win: |window-ID| window to split, or relative window when creating a float (relative="win"). • anchor: Decides which corner of the float to place at diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 89504ae244..e6a1adf15b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -187,6 +187,7 @@ API • |nvim__ns_set()| can set properties for a namespace • |nvim_echo()| `err` field to print error messages and `chunks` accepts highlight group IDs. +• |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline". DEFAULTS diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 8930f131f6..670e867c1e 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1754,10 +1754,12 @@ function vim.api.nvim_open_term(buffer, opts) end --- @param config vim.api.keyset.win_config Map defining the window configuration. Keys: --- - relative: Sets the window layout to "floating", placed at (row,col) --- coordinates relative to: ---- - "editor" The global editor grid ---- - "win" Window given by the `win` field, or current window. ---- - "cursor" Cursor position in current window. ---- - "mouse" Mouse position +--- - "cursor" Cursor position in current window. +--- - "editor" The global editor grid. +--- - "laststatus" 'laststatus' if present, or last row. +--- - "mouse" Mouse position. +--- - "tabline" Tabline if present, or first row. +--- - "win" Window given by the `win` field, or current window. --- - win: `window-ID` window to split, or relative window when creating a --- float (relative="win"). --- - anchor: Decides which corner of the float to place at (row,col): diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 45c9e3f56c..225189a3f9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -101,10 +101,12 @@ /// @param config Map defining the window configuration. Keys: /// - relative: Sets the window layout to "floating", placed at (row,col) /// coordinates relative to: -/// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window. -/// - "cursor" Cursor position in current window. -/// - "mouse" Mouse position +/// - "cursor" Cursor position in current window. +/// - "editor" The global editor grid. +/// - "laststatus" 'laststatus' if present, or last row. +/// - "mouse" Mouse position. +/// - "tabline" Tabline if present, or first row. +/// - "win" Window given by the `win` field, or current window. /// - win: |window-ID| window to split, or relative window when creating a /// float (relative="win"). /// - anchor: Decides which corner of the float to place at (row,col): @@ -699,7 +701,9 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) FUNC_API_SINCE(6) { /// Keep in sync with FloatRelative in buffer_defs.h - static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; + static const char *const float_relative_str[] = { + "editor", "win", "cursor", "mouse", "tabline", "laststatus" + }; /// Keep in sync with WinSplit in buffer_defs.h static const char *const win_split_str[] = { "left", "right", "above", "below" }; @@ -805,6 +809,10 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeCursor; } else if (striequal(str, "mouse")) { *out = kFloatRelativeMouse; + } else if (striequal(str, "tabline")) { + *out = kFloatRelativeTabline; + } else if (striequal(str, "laststatus")) { + *out = kFloatRelativeLaststatus; } else { return false; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3fe44beab9..bffb29578f 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -900,6 +900,8 @@ typedef enum { kFloatRelativeWindow = 1, kFloatRelativeCursor = 2, kFloatRelativeMouse = 3, + kFloatRelativeTabline = 4, + kFloatRelativeLaststatus = 5, } FloatRelative; /// Keep in sync with win_split_str[] in nvim_win_get_config() (api/win_config.c) diff --git a/src/nvim/window.c b/src/nvim/window.c index 68f45ec5ba..1c0d8c1027 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -835,6 +835,10 @@ void ui_ext_win_position(win_T *wp, bool validate) col += tcol - 1; } } + } else if (c.relative == kFloatRelativeLaststatus) { + row += Rows - (int)p_ch - last_stl_height(false); + } else if (c.relative == kFloatRelativeTabline) { + row += tabline_height(); } bool resort = wp->w_grid_alloc.comp_index != 0 @@ -1066,6 +1070,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl return NULL; } need_status = STATUS_HEIGHT; + win_float_anchor_laststatus(); } bool do_equal = false; @@ -6803,6 +6808,7 @@ void last_status(bool morewin) { // Don't make a difference between horizontal or vertical split. last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0); + win_float_anchor_laststatus(); } // Remove status line from window, replacing it with a horizontal separator if needed. diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 3e791e2beb..d11b965dfc 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -307,6 +307,15 @@ void win_check_anchored_floats(win_T *win) } } +void win_float_anchor_laststatus(void) +{ + FOR_ALL_WINDOWS_IN_TAB(win, curtab) { + if (win->w_config.relative == kFloatRelativeLaststatus) { + win->w_pos_changed = true; + } + } +} + void win_reconfig_floats(void) { for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) { diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index be0a9b80cf..ca26c46fc5 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -1012,6 +1012,97 @@ describe('float window', function() end) end) + it('placed relative to tabline and laststatus', function() + local screen = Screen.new(20, 10) + screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.Magenta } }) + command('set showtabline=1 laststatus=1') + api.nvim_open_win(0, false, { + relative = 'laststatus', + border = 'single', + anchor = 'SE', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + local tabwin = api.nvim_open_win(0, false, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + screen:expect([[ + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*3 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabnew | tabnext') + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('vsplit') + screen:expect([[ + {5: }{100:4}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }{2:│}{1:~}| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {3:[No Name] }{2:<}| + | + ]]) + command('quit') + api.nvim_win_set_config(tabwin, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 1, + col = 0, + }) + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabonly') + screen:expect([[ + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + end) + local function with_ext_multigrid(multigrid) local screen, attrs before_each(function() -- cgit From 59da82abd91e3be7eb5403c14de012cd149a1c84 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 16 Dec 2024 13:31:59 +0100 Subject: fix(wininfo): when freeing windows, free the lowest priority wininfo On master (and also before #31539) closing a window could cause the used wininfo for a buffer to change. This is due to always removing the previous NULL wininfo when deleting a window, even if that wininfo had higher priority than the the deleted window's own wininfo. Instead delete the wininfo with lowest priority. This retains the memory saving efect while not affecting the effective value of window options and so on. --- src/nvim/window.c | 11 ++++++---- test/functional/api/window_spec.lua | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index b1c483547c..190ac500b0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5242,11 +5242,13 @@ void win_free(win_T *wp, tabpage_T *tp) // freed memory is re-used for another window. FOR_ALL_BUFFERS(buf) { WinInfo *wip_wp = NULL; + size_t pos_wip = kv_size(buf->b_wininfo); size_t pos_null = kv_size(buf->b_wininfo); for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) { WinInfo *wip = kv_A(buf->b_wininfo, i); if (wip->wi_win == wp) { wip_wp = wip; + pos_wip = i; } else if (wip->wi_win == NULL) { pos_null = i; } @@ -5254,11 +5256,12 @@ void win_free(win_T *wp, tabpage_T *tp) if (wip_wp) { wip_wp->wi_win = NULL; - // If there already is an entry with "wi_win" set to NULL it - // must be removed, it would never be used. + // If there already is an entry with "wi_win" set to NULL, only + // the first entry with NULL will ever be used, delete the other one. if (pos_null < kv_size(buf->b_wininfo)) { - free_wininfo(kv_A(buf->b_wininfo, pos_null), buf); - kv_shift(buf->b_wininfo, pos_null, 1); + size_t pos_delete = MAX(pos_null, pos_wip); + free_wininfo(kv_A(buf->b_wininfo, pos_delete), buf); + kv_shift(buf->b_wininfo, pos_delete, 1); } } } diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 92999f383a..5d7dab2a7e 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -488,6 +488,48 @@ describe('API/win', function() assert_alive() end) + describe('after closing', function() + local buf, win0, win1, win2 + + before_each(function() + win0 = api.nvim_get_current_win() + command('new') + buf = api.nvim_get_current_buf() + win1 = api.nvim_get_current_win() + command('set numberwidth=10') + command('split') + win2 = api.nvim_get_current_win() + command('set numberwidth=15') + command('enew') + api.nvim_set_current_win(win1) + command('normal ix') + command('enew') + api.nvim_set_current_win(win0) + eq(4, api.nvim_get_option_value('numberwidth', {})) + end) + + -- at this point buffer `buf` is current in no windows. Closing shouldn't affect its defaults + it('0 windows', function() + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + + it('1 window', function() + api.nvim_win_close(win1, false) + + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + + it('2 windows', function() + api.nvim_win_close(win1, false) + api.nvim_win_close(win2, false) + + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + end) + it('returns values for unset local options', function() eq(-1, api.nvim_get_option_value('scrolloff', { win = 0, scope = 'local' })) end) -- cgit From f1c45fc7a4a595e460cd245172a5767bddeb09e9 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 14 Jan 2025 08:18:59 -0600 Subject: feat(terminal): support theme update notifications (DEC mode 2031) (#31999) --- runtime/doc/news.txt | 2 ++ src/nvim/optionstr.c | 10 ++++++++++ src/nvim/terminal.c | 29 +++++++++++++++++++++++++++++ src/nvim/vterm/screen.c | 12 ++++++++++++ src/nvim/vterm/state.c | 19 +++++++++++++++++++ src/nvim/vterm/vterm_defs.h | 3 +++ src/nvim/vterm/vterm_internal_defs.h | 1 + test/functional/terminal/tui_spec.lua | 26 ++++++++++++++++++++++++++ test/unit/fixtures/vterm_test.c | 2 ++ 9 files changed, 104 insertions(+) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e6a1adf15b..23266d536f 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -337,6 +337,8 @@ TERMINAL unfocused terminal window will have no cursor at all (so there is nothing to highlight). • |jobstart()| gained the "term" flag. +• The |terminal| will send theme update notifications when 'background' is + changed and DEC mode 2031 is enabled. TREESITTER diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index eac9ea02e0..93871905db 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -44,6 +44,7 @@ #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" +#include "nvim/terminal.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" #include "nvim/window.h" @@ -532,6 +533,15 @@ const char *did_set_background(optset_T *args) check_string_option(&p_bg); init_highlight(false, false); } + + // Notify all terminal buffers that the background color changed so they can + // send a theme update notification + FOR_ALL_BUFFERS(buf) { + if (buf->terminal) { + terminal_notify_theme(buf->terminal, dark); + } + } + return NULL; } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index ad343bad67..2ad5ac49ca 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -179,6 +179,8 @@ struct terminal { StringBuilder *send; ///< When there is a pending TermRequest autocommand, block and store input. } pending; + bool theme_updates; ///< Send a theme update notification when 'bg' changes + bool color_set[16]; char *selection_buffer; /// libvterm selection buffer @@ -193,6 +195,7 @@ static VTermScreenCallbacks vterm_screen_callbacks = { .movecursor = term_movecursor, .settermprop = term_settermprop, .bell = term_bell, + .theme = term_theme, .sb_pushline = term_sb_push, // Called before a line goes offscreen. .sb_popline = term_sb_pop, }; @@ -1141,6 +1144,20 @@ bool terminal_running(const Terminal *term) return !term->closed; } +void terminal_notify_theme(Terminal *term, bool dark) + FUNC_ATTR_NONNULL_ALL +{ + if (!term->theme_updates) { + return; + } + + char buf[10]; + ssize_t ret = snprintf(buf, sizeof(buf), "\x1b[997;%cn", dark ? '1' : '2'); + assert(ret > 0); + assert((size_t)ret <= sizeof(buf)); + terminal_send(term, buf, (size_t)ret); +} + static void terminal_focus(const Terminal *term, bool focus) FUNC_ATTR_NONNULL_ALL { @@ -1259,6 +1276,10 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) invalidate_terminal(term, -1, -1); break; + case VTERM_PROP_THEMEUPDATES: + term->theme_updates = val->boolean; + break; + default: return 0; } @@ -1273,6 +1294,14 @@ static int term_bell(void *data) return 1; } +/// Called when the terminal wants to query the system theme. +static int term_theme(bool *dark, void *data) + FUNC_ATTR_NONNULL_ALL +{ + *dark = (*p_bg == 'd'); + return 1; +} + /// Scrollback push handler: called just before a line goes offscreen (and libvterm will forget it), /// giving us a chance to store it. /// diff --git a/src/nvim/vterm/screen.c b/src/nvim/vterm/screen.c index c91c6fb84f..45bd5e2a27 100644 --- a/src/nvim/vterm/screen.c +++ b/src/nvim/vterm/screen.c @@ -784,6 +784,17 @@ static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *us return 1; } +static int theme(bool *dark, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->theme) { + return (*screen->callbacks->theme)(dark, screen->cbdata); + } + + return 1; +} + static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) { @@ -838,6 +849,7 @@ static VTermStateCallbacks state_cbs = { .settermprop = &settermprop, .bell = &bell, .resize = &resize, + .theme = &theme, .setlineinfo = &setlineinfo, .sb_clear = &sb_clear, }; diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c index 9e787acd9b..4ad07377de 100644 --- a/src/nvim/vterm/state.c +++ b/src/nvim/vterm/state.c @@ -819,6 +819,10 @@ static void set_dec_mode(VTermState *state, int num, int val) state->mode.bracketpaste = (unsigned)val; break; + case 2031: + settermprop_bool(state, VTERM_PROP_THEMEUPDATES, val); + break; + default: DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); return; @@ -894,6 +898,10 @@ static void request_dec_mode(VTermState *state, int num) reply = state->mode.bracketpaste; break; + case 2031: + reply = state->mode.theme_updates; + break; + default: vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); return; @@ -1387,6 +1395,7 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha { char *qmark = (leader_byte == '?') ? "?" : ""; + bool dark = false; switch (val) { case 0: @@ -1403,6 +1412,13 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); break; + case 996: + if (state->callbacks && state->callbacks->theme) { + if (state->callbacks->theme(&dark, state->cbdata)) { + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?997;%cn", dark ? '1' : '2'); + } + } + break; } } break; @@ -2268,6 +2284,9 @@ int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) case VTERM_PROP_FOCUSREPORT: state->mode.report_focus = (unsigned)val->boolean; return 1; + case VTERM_PROP_THEMEUPDATES: + state->mode.theme_updates = (unsigned)val->boolean; + return 1; case VTERM_N_PROPS: return 0; diff --git a/src/nvim/vterm/vterm_defs.h b/src/nvim/vterm/vterm_defs.h index d0a8ba8814..9aa933bef0 100644 --- a/src/nvim/vterm/vterm_defs.h +++ b/src/nvim/vterm/vterm_defs.h @@ -86,6 +86,7 @@ typedef enum { VTERM_PROP_CURSORSHAPE, // number VTERM_PROP_MOUSE, // number VTERM_PROP_FOCUSREPORT, // bool + VTERM_PROP_THEMEUPDATES, // bool VTERM_N_PROPS, } VTermProp; @@ -111,6 +112,7 @@ typedef struct { int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, void *user); + int (*theme)(bool *dark, void *user); int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); int (*sb_clear)(void *user); @@ -263,6 +265,7 @@ typedef struct { int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); + int (*theme)(bool *dark, void *user); int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); int (*sb_clear)(void *user); diff --git a/src/nvim/vterm/vterm_internal_defs.h b/src/nvim/vterm/vterm_internal_defs.h index 770f862ce3..d4d59867bf 100644 --- a/src/nvim/vterm/vterm_internal_defs.h +++ b/src/nvim/vterm/vterm_internal_defs.h @@ -119,6 +119,7 @@ struct VTermState { unsigned leftrightmargin:1; unsigned bracketpaste:1; unsigned report_focus:1; + unsigned theme_updates:1; } mode; VTermEncodingInstance encoding[4], encoding_utf8; diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3624a7bc2b..0a7dca8e6e 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -3309,6 +3309,32 @@ describe('TUI bg color', function() {3:-- TERMINAL --} | ]]) end) + + it('sends theme update notifications when background changes #31652', function() + command('set background=dark') -- set outer Nvim background + local child_server = new_pipename() + local screen = tt.setup_child_nvim({ + '--listen', + child_server, + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + 'set noswapfile', + }) + screen:expect({ any = '%[No Name%]' }) + local child_session = n.connect(child_server) + retry(nil, nil, function() + eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) + end) + command('set background=light') -- set outer Nvim background + retry(nil, nil, function() + eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) + end) + end) end) -- These tests require `tt` because --headless/--embed diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c index 7522962a05..6744305960 100644 --- a/test/unit/fixtures/vterm_test.c +++ b/test/unit/fixtures/vterm_test.c @@ -345,6 +345,8 @@ static VTermValueType vterm_get_prop_type(VTermProp prop) return VTERM_VALUETYPE_INT; case VTERM_PROP_FOCUSREPORT: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_THEMEUPDATES: + return VTERM_VALUETYPE_BOOL; case VTERM_N_PROPS: return 0; -- cgit From e8a6c1b02122852da83dc52184e78369598d8240 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 14 Jan 2025 08:19:54 -0600 Subject: fix(lsp): schedule call to vim.lsp.start for async root_dir (#31998) When `root_dir` is a function it can (and often will) call the provided callback function in a fast API context (e.g. in the `on_exit` handler of `vim.system`). When the callback function is executed we should ensure that it runs vim.lsp.start on the main event loop. --- runtime/lua/vim/lsp.lua | 4 ++- test/functional/plugin/lsp_spec.lua | 53 ++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 23f4e104d0..7812f31db1 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -513,7 +513,9 @@ local function lsp_enable_callback(bufnr) ---@param root_dir string config.root_dir(function(root_dir) config.root_dir = root_dir - start(config) + vim.schedule(function() + start(config) + end) end) else start(config) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5e9766c784..db3ab8ed98 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6259,37 +6259,42 @@ describe('LSP', function() ) end) - it('supports a function for root_dir', function() + it('supports async function for root_dir', function() exec_lua(create_server_definition) local tmp1 = t.tmpname(true) + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) - eq( - 'some_dir', - exec_lua(function() - local server = _G._create_server({ - handlers = { - initialize = function(_, _, callback) - callback(nil, { capabilities = {} }) - end, - }, - }) - - vim.lsp.config('foo', { - cmd = server.cmd, - filetypes = { 'foo' }, - root_dir = function(cb) + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_dir = function(cb) + vim.system({ 'sleep', '0' }, {}, function() cb('some_dir') - end, - }) - vim.lsp.enable('foo') + end) + end, + }) + vim.lsp.enable('foo') - vim.cmd.edit(assert(tmp1)) - vim.bo.filetype = 'foo' + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + end) - return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir - end) - ) + retry(nil, 1000, function() + eq( + 'some_dir', + exec_lua(function() + return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir + end) + ) + end) end) end) end) -- cgit From 611ef354919f1c6564efd2ff8074545941458ccc Mon Sep 17 00:00:00 2001 From: Mike <4576770+mike325@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:39:17 +0100 Subject: feat(vim.fs): find(), dir() can "follow" symlinks #31551 Problem: vim.fs.dir(), vim.fs.find() do not follow symlinks. Solution: - Add "follow" flag. - Enable it by default. --- runtime/doc/lua.txt | 11 ++++-- runtime/doc/news.txt | 2 + runtime/lua/vim/fs.lua | 23 ++++++++++-- test/functional/lua/fs_spec.lua | 83 ++++++++++++++++++++++++++++++++++++++--- test/testutil.lua | 7 +++- 5 files changed, 113 insertions(+), 13 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 44cbf238cf..6e5a77ff7a 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2983,6 +2983,7 @@ vim.fs.dir({path}, {opts}) *vim.fs.dir()* • skip: (fun(dir_name: string): boolean)|nil Predicate to control traversal. Return false to stop searching the current directory. Only useful when depth > 1 + • follow: boolean|nil Follow symbolic links. (default: true) Return: ~ (`Iterator`) over items in {path}. Each iteration yields two values: @@ -3024,7 +3025,7 @@ vim.fs.find({names}, {opts}) *vim.fs.find()* -- get all files ending with .cpp or .hpp inside lib/ local cpp_hpp = vim.fs.find(function(name, path) - return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$') + return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$') end, {limit = math.huge, type = 'file'}) < @@ -3038,8 +3039,10 @@ vim.fs.find({names}, {opts}) *vim.fs.find()* If {names} is a function, it is called for each traversed item with args: • name: base name of the current item - • path: full path of the current item The function should - return `true` if the given item is considered a match. + • path: full path of the current item + + The function should return `true` if the given item is + considered a match. • {opts} (`table`) Optional keyword arguments: • {path}? (`string`) Path to begin searching from. If omitted, the |current-directory| is used. @@ -3053,6 +3056,8 @@ vim.fs.find({names}, {opts}) *vim.fs.find()* • {limit}? (`number`, default: `1`) Stop the search after finding this many matches. Use `math.huge` to place no limit on the number of matches. + • {follow}? (`boolean`, default: `true`) Follow symbolic + links. Return: ~ (`string[]`) Normalized paths |vim.fs.normalize()| of all matching diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 23266d536f..8aa0a8d57b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -286,6 +286,8 @@ LUA • |vim.json.encode()| has an option to enable forward slash escaping • |vim.fs.abspath()| converts paths to absolute paths. • |vim.fs.relpath()| gets relative path compared to base path. +• |vim.fs.dir()| and |vim.fs.find()| now follow symbolic links by default, + the behavior can be turn off using the new `follow` option. OPTIONS diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 91e06688b3..5940fa4386 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -136,6 +136,7 @@ end --- - skip: (fun(dir_name: string): boolean)|nil Predicate --- to control traversal. Return false to stop searching the current directory. --- Only useful when depth > 1 +--- - follow: boolean|nil Follow symbolic links. (default: true) --- ---@return Iterator over items in {path}. Each iteration yields two values: "name" and "type". --- "name" is the basename of the item relative to {path}. @@ -147,6 +148,7 @@ function M.dir(path, opts) vim.validate('path', path, 'string') vim.validate('depth', opts.depth, 'number', true) vim.validate('skip', opts.skip, 'function', true) + vim.validate('follow', opts.follow, 'boolean', true) path = M.normalize(path) if not opts.depth or opts.depth == 1 then @@ -177,7 +179,9 @@ function M.dir(path, opts) if opts.depth and level < opts.depth - and t == 'directory' + and (t == 'directory' or (t == 'link' and opts.follow ~= false and (vim.uv.fs_stat( + M.joinpath(path, f) + ) or {}).type == 'directory')) and (not opts.skip or opts.skip(f) ~= false) then dirs[#dirs + 1] = { f, level + 1 } @@ -211,6 +215,10 @@ end --- Use `math.huge` to place no limit on the number of matches. --- (default: `1`) --- @field limit? number +--- +--- Follow symbolic links. +--- (default: `true`) +--- @field follow? boolean --- Find files or directories (or other items as specified by `opts.type`) in the given path. --- @@ -234,7 +242,7 @@ end --- --- -- get all files ending with .cpp or .hpp inside lib/ --- local cpp_hpp = vim.fs.find(function(name, path) ---- return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$') +--- return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$') --- end, {limit = math.huge, type = 'file'}) --- ``` --- @@ -244,6 +252,7 @@ end --- If {names} is a function, it is called for each traversed item with args: --- - name: base name of the current item --- - path: full path of the current item +--- --- The function should return `true` if the given item is considered a match. --- ---@param opts vim.fs.find.Opts Optional keyword arguments: @@ -256,6 +265,7 @@ function M.find(names, opts) vim.validate('stop', opts.stop, 'string', true) vim.validate('type', opts.type, 'string', true) vim.validate('limit', opts.limit, 'number', true) + vim.validate('follow', opts.follow, 'boolean', true) if type(names) == 'string' then names = { names } @@ -345,7 +355,14 @@ function M.find(names, opts) end end - if type_ == 'directory' then + if + type_ == 'directory' + or ( + type_ == 'link' + and opts.follow ~= false + and (vim.uv.fs_stat(f) or {}).type == 'directory' + ) + then dirs[#dirs + 1] = f end end diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 0ba0948eee..33af25f629 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -17,6 +17,8 @@ local mkdir = t.mkdir local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim' +local link_limit = is_os('win') and 64 or (is_os('mac') or is_os('bsd')) and 33 or 41 + local test_basename_dirname_eq = { '~/foo/', '~/foo', @@ -152,7 +154,7 @@ describe('vim.fs', function() ) end) - it('works with opts.depth and opts.skip', function() + it('works with opts.depth, opts.skip and opts.follow', function() io.open('testd/a1', 'w'):close() io.open('testd/b1', 'w'):close() io.open('testd/c1', 'w'):close() @@ -166,8 +168,8 @@ describe('vim.fs', function() io.open('testd/a/b/c/b4', 'w'):close() io.open('testd/a/b/c/c4', 'w'):close() - local function run(dir, depth, skip) - return exec_lua(function() + local function run(dir, depth, skip, follow) + return exec_lua(function(follow_) local r = {} --- @type table local skip_f --- @type function if skip then @@ -177,11 +179,11 @@ describe('vim.fs', function() end end end - for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f }) do + for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f, follow = follow_ }) do r[name] = type_ end return r - end) + end, follow) end local exp = {} @@ -197,6 +199,7 @@ describe('vim.fs', function() exp['a/b2'] = 'file' exp['a/c2'] = 'file' exp['a/b'] = 'directory' + local lexp = vim.deepcopy(exp) eq(exp, run('testd', 2)) @@ -213,6 +216,29 @@ describe('vim.fs', function() exp['a/b/c/c4'] = 'file' eq(exp, run('testd', 999)) + + vim.uv.fs_symlink(vim.uv.fs_realpath('testd/a'), 'testd/l', { junction = true, dir = true }) + lexp['l'] = 'link' + eq(lexp, run('testd', 2, nil, false)) + + lexp['l/a2'] = 'file' + lexp['l/b2'] = 'file' + lexp['l/c2'] = 'file' + lexp['l/b'] = 'directory' + eq(lexp, run('testd', 2, nil, true)) + end) + + it('follow=true handles symlink loop', function() + local cwd = 'testd/a/b/c' + local symlink = cwd .. '/link_loop' ---@type string + vim.uv.fs_symlink(vim.uv.fs_realpath(cwd), symlink, { junction = true, dir = true }) + + eq( + link_limit, + exec_lua(function() + return #vim.iter(vim.fs.dir(cwd, { depth = math.huge, follow = true })):totable() + end) + ) end) end) @@ -228,6 +254,53 @@ describe('vim.fs', function() eq({ nvim_dir }, vim.fs.find(name, { path = parent, upward = true, type = 'directory' })) end) + it('follows symlinks', function() + local build_dir = test_source_path .. '/build' ---@type string + local symlink = test_source_path .. '/build_link' ---@type string + vim.uv.fs_symlink(build_dir, symlink, { junction = true, dir = true }) + + finally(function() + vim.uv.fs_unlink(symlink) + end) + + eq( + { nvim_prog, symlink .. '/bin/' .. nvim_prog_basename }, + vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = 2, + follow = true, + }) + ) + + eq( + { nvim_prog }, + vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = 2, + follow = false, + }) + ) + end) + + it('follow=true handles symlink loop', function() + local cwd = test_source_path ---@type string + local symlink = test_source_path .. '/loop_link' ---@type string + vim.uv.fs_symlink(cwd, symlink, { junction = true, dir = true }) + + finally(function() + vim.uv.fs_unlink(symlink) + end) + + eq(link_limit, #vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = math.huge, + follow = true, + })) + end) + it('accepts predicate as names', function() local opts = { path = nvim_dir, upward = true, type = 'directory' } eq( diff --git a/test/testutil.lua b/test/testutil.lua index 3226bfeb1e..e69dcae120 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -388,15 +388,18 @@ end local sysname = uv.os_uname().sysname:lower() ---- @param s 'win'|'mac'|'freebsd'|'openbsd'|'bsd' +--- @param s 'win'|'mac'|'linux'|'freebsd'|'openbsd'|'bsd' --- @return boolean function M.is_os(s) - if not (s == 'win' or s == 'mac' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') then + if + not (s == 'win' or s == 'mac' or s == 'linux' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') + then error('unknown platform: ' .. tostring(s)) end return not not ( (s == 'win' and (sysname:find('windows') or sysname:find('mingw'))) or (s == 'mac' and sysname == 'darwin') + or (s == 'linux' and sysname == 'linux') or (s == 'freebsd' and sysname == 'freebsd') or (s == 'openbsd' and sysname == 'openbsd') or (s == 'bsd' and sysname:find('bsd')) -- cgit From 850084b519e18122820478a71bb4bfa4c15e528a Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 13 Jan 2025 19:39:03 -0800 Subject: refactor: use nvim.foo.bar format for namespaces --- runtime/ftplugin/help.lua | 2 +- runtime/lua/vim/diagnostic.lua | 6 +++--- runtime/lua/vim/hl.lua | 2 +- runtime/lua/vim/lsp/buf.lua | 4 ++-- runtime/lua/vim/lsp/codelens.lua | 2 +- runtime/lua/vim/lsp/handlers.lua | 2 +- runtime/lua/vim/lsp/inlay_hint.lua | 2 +- runtime/lua/vim/lsp/semantic_tokens.lua | 4 ++-- runtime/lua/vim/lsp/util.lua | 2 +- runtime/lua/vim/snippet.lua | 2 +- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/dev.lua | 6 +++--- runtime/lua/vim/treesitter/highlighter.lua | 2 +- runtime/lua/vim/vimhelp.lua | 2 +- test/functional/lua/hl_spec.lua | 4 ++-- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index 689a4db408..ed8f93c71c 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -46,7 +46,7 @@ local query = vim.treesitter.query.parse( (#set! @code lang @_lang)) ]] ) -local run_message_ns = vim.api.nvim_create_namespace('vimdoc/run_message') +local run_message_ns = vim.api.nvim_create_namespace('nvim.vimdoc.run_message') vim.api.nvim_buf_clear_namespace(0, run_message_ns, 0, -1) for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 6466c7d6e8..5df4399b93 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1402,7 +1402,7 @@ M.handlers.signs = { local ns = M.get_namespace(namespace) if not ns.user_data.sign_ns then ns.user_data.sign_ns = - api.nvim_create_namespace(string.format('%s/diagnostic/signs', ns.name)) + api.nvim_create_namespace(string.format('nvim.%s.diagnostic.signs', ns.name)) end -- Handle legacy diagnostic sign definitions @@ -1500,7 +1500,7 @@ M.handlers.underline = { local ns = M.get_namespace(namespace) if not ns.user_data.underline_ns then ns.user_data.underline_ns = - api.nvim_create_namespace(string.format('%s/diagnostic/underline', ns.name)) + api.nvim_create_namespace(string.format('nvim.%s.diagnostic.underline', ns.name)) end local underline_ns = ns.user_data.underline_ns @@ -1572,7 +1572,7 @@ M.handlers.virtual_text = { local ns = M.get_namespace(namespace) if not ns.user_data.virt_text_ns then ns.user_data.virt_text_ns = - api.nvim_create_namespace(string.format('%s/diagnostic/virtual_text', ns.name)) + api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_text', ns.name)) end local virt_text_ns = ns.user_data.virt_text_ns diff --git a/runtime/lua/vim/hl.lua b/runtime/lua/vim/hl.lua index 099efa3c61..f5ace7fdc5 100644 --- a/runtime/lua/vim/hl.lua +++ b/runtime/lua/vim/hl.lua @@ -115,7 +115,7 @@ function M.range(bufnr, ns, higroup, start, finish, opts) end end -local yank_ns = api.nvim_create_namespace('hlyank') +local yank_ns = api.nvim_create_namespace('nvim.hlyank') local yank_timer --- @type uv.uv_timer_t? local yank_cancel --- @type fun()? diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 69407bc6f8..0acbc50003 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -20,7 +20,7 @@ local function client_positional_params(params) end end -local hover_ns = api.nvim_create_namespace('vim_lsp_hover_range') +local hover_ns = api.nvim_create_namespace('nvim.lsp.hover_range') --- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts --- @field silent? boolean @@ -324,7 +324,7 @@ local function process_signature_help_results(results) return signatures end -local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help') +local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help') --- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts --- @field silent? boolean diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 3ccd165d0b..65f794229c 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -21,7 +21,7 @@ local lens_cache_by_buf = setmetatable({}, { ---client_id -> namespace local namespaces = setmetatable({}, { __index = function(t, key) - local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key) + local value = api.nvim_create_namespace('nvim.lsp.codelens:' .. key) rawset(t, key, value) return value end, diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 3779c342e8..425e3206aa 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -382,7 +382,7 @@ end --- @diagnostic disable-next-line: deprecated RCS[ms.textDocument_hover] = M.hover -local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help') +local sig_help_ns = api.nvim_create_namespace('nvim.lsp.signature_help') --- @deprecated remove in 0.13 --- |lsp-handler| for the method "textDocument/signatureHelp". diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 50cf9f5f29..83bf276bff 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -29,7 +29,7 @@ local bufstates = vim.defaulttable(function(_) }) end) -local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') +local namespace = api.nvim_create_namespace('nvim.lsp.inlayhint') local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) --- |lsp-handler| for the method `textDocument/inlayHint` diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 7cc3f95aed..f2a8125d1a 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -225,7 +225,7 @@ function STHighlighter:attach(client_id) local state = self.client_state[client_id] if not state then state = { - namespace = api.nvim_create_namespace('vim_lsp_semantic_tokens:' .. client_id), + namespace = api.nvim_create_namespace('nvim.lsp.semantic_tokens:' .. client_id), active_request = {}, current_result = {}, } @@ -805,7 +805,7 @@ function M._refresh(err, _, ctx) return vim.NIL end -local namespace = api.nvim_create_namespace('vim_lsp_semantic_tokens') +local namespace = api.nvim_create_namespace('nvim.lsp.semantic_tokens') api.nvim_set_decoration_provider(namespace, { on_win = function(_, _, bufnr, topline, botline) local highlighter = STHighlighter.active[bufnr] diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 14633adf0c..e9bf58a01b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1650,7 +1650,7 @@ function M.open_floating_preview(contents, syntax, opts) end do --[[ References ]] - local reference_ns = api.nvim_create_namespace('vim_lsp_references') + local reference_ns = api.nvim_create_namespace('nvim.lsp.references') --- Removes document highlights from a buffer. --- diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index af7e3c6d33..9d4409b48a 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -1,6 +1,6 @@ local G = vim.lsp._snippet_grammar local snippet_group = vim.api.nvim_create_augroup('vim/snippet', {}) -local snippet_ns = vim.api.nvim_create_namespace('vim/snippet') +local snippet_ns = vim.api.nvim_create_namespace('nvim.snippet') local hl_group = 'SnippetTabstop' local jump_forward_key = '' local jump_backward_key = '' diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index a825505378..f6645beb28 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -1,6 +1,6 @@ local api = vim.api -local namespace = api.nvim_create_namespace('vim.treesitter.query_linter') +local namespace = api.nvim_create_namespace('nvim.treesitter.query_linter') local M = {} diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 26817cdba5..0e886d0e27 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -119,7 +119,7 @@ function TSTreeView:new(bufnr, lang) end local t = { - ns = api.nvim_create_namespace('treesitter/dev-inspect'), + ns = api.nvim_create_namespace('nvim.treesitter.dev_inspect'), nodes = nodes, named = named, ---@type vim.treesitter.dev.TSTreeViewOpts @@ -135,7 +135,7 @@ function TSTreeView:new(bufnr, lang) return t end -local decor_ns = api.nvim_create_namespace('ts.dev') +local decor_ns = api.nvim_create_namespace('nvim.treesitter.dev') ---@param range Range4 ---@return string @@ -547,7 +547,7 @@ function M.inspect_tree(opts) }) end -local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') +local edit_ns = api.nvim_create_namespace('nvim.treesitter.dev_edit') ---@param query_win integer ---@param base_win integer diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index be138885d5..c11fa1999d 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -2,7 +2,7 @@ local api = vim.api local query = vim.treesitter.query local Range = require('vim.treesitter._range') -local ns = api.nvim_create_namespace('treesitter/highlighter') +local ns = api.nvim_create_namespace('nvim.treesitter.highlighter') ---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua index 5579cc0174..a494d311b1 100644 --- a/runtime/lua/vim/vimhelp.lua +++ b/runtime/lua/vim/vimhelp.lua @@ -7,7 +7,7 @@ local M = {} --- Note: {patterns} is assumed to be sorted by occurrence in the file. --- @param patterns {start:string,stop:string,match:string}[] function M.highlight_groups(patterns) - local ns = vim.api.nvim_create_namespace('vimhelp') + local ns = vim.api.nvim_create_namespace('nvim.vimhelp') vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) local save_cursor = vim.fn.getcurpos() diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua index 89881973bf..3f903d43a9 100644 --- a/test/functional/lua/hl_spec.lua +++ b/test/functional/lua/hl_spec.lua @@ -144,7 +144,7 @@ describe('vim.hl.on_yank', function() vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) vim.hl.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) end) - local ns = api.nvim_create_namespace('hlyank') + local ns = api.nvim_create_namespace('nvim.hlyank') local win = api.nvim_get_current_win() eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') @@ -158,7 +158,7 @@ describe('vim.hl.on_yank', function() vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) vim.hl.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) end) - local ns = api.nvim_create_namespace('hlyank') + local ns = api.nvim_create_namespace('nvim.hlyank') eq(api.nvim_get_current_win(), api.nvim__ns_get(ns).wins[1]) command('wincmd w') exec_lua(function() -- cgit From a78eddd54112033eea0212865efd2f75cc59fc93 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 15 Jan 2025 12:05:00 +0800 Subject: vim-patch:9.1.1015: Coverity complains about dereferencing NULL value (#32020) Problem: Coverity complains about dereferencing NULL value Solution: Check that cms2 is not null closes: vim/vim#16438 https://github.com/vim/vim/commit/1ac53b84ada3a992f320566737e66fa00d48611f Co-authored-by: Christian Brabandt --- src/nvim/fold.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index c9699cb161..b59933d600 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1658,7 +1658,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker if (*cms != NUL) { // Also delete 'commentstring' if it matches. char *cms2 = strstr(cms, "%s"); - if (p - line >= cms2 - cms + if (cms2 != NULL && p - line >= cms2 - cms && strncmp(p - (cms2 - cms), cms, (size_t)(cms2 - cms)) == 0 && strncmp(p + len, cms2 + 2, strlen(cms2 + 2)) == 0) { p -= cms2 - cms; -- cgit From 09e01437c968be4c6e9f6bb3ac8811108c58008c Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 13 Jan 2025 19:45:11 -0800 Subject: refactor: use nvim.foo.bar format for autocommand groups --- runtime/doc/vim_diff.txt | 2 +- runtime/ftplugin/query.lua | 2 +- runtime/lua/_vim9script.lua | 2 +- runtime/lua/editorconfig.lua | 6 +++--- runtime/lua/vim/_defaults.lua | 10 +++++----- runtime/lua/vim/diagnostic.lua | 2 +- runtime/lua/vim/lsp.lua | 2 +- runtime/lua/vim/lsp/_folding_range.lua | 2 +- runtime/lua/vim/lsp/codelens.lua | 2 +- runtime/lua/vim/lsp/completion.lua | 2 +- runtime/lua/vim/lsp/diagnostic.lua | 2 +- runtime/lua/vim/lsp/inlay_hint.lua | 2 +- runtime/lua/vim/lsp/semantic_tokens.lua | 2 +- runtime/lua/vim/lsp/util.lua | 4 ++-- runtime/lua/vim/snippet.lua | 2 +- runtime/lua/vim/treesitter/_fold.lua | 2 +- runtime/lua/vim/treesitter/dev.lua | 4 ++-- runtime/lua/vim/treesitter/query.lua | 2 +- runtime/plugin/editorconfig.lua | 2 +- runtime/plugin/man.lua | 2 +- test/functional/editor/defaults_spec.lua | 2 +- test/functional/ex_cmds/swapfile_preserve_recover_spec.lua | 8 ++++---- test/functional/terminal/buffer_spec.lua | 2 +- test/functional/terminal/ex_terminal_spec.lua | 8 ++++---- test/functional/terminal/tui_spec.lua | 2 +- test/functional/ui/multigrid_spec.lua | 2 +- test/functional/ui/popupmenu_spec.lua | 8 ++++---- test/old/testdir/setup.vim | 2 +- 28 files changed, 45 insertions(+), 45 deletions(-) diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index a59312208a..59c1c4b21c 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -122,7 +122,7 @@ fully disable the mouse or popup-menu, do any of the following: < To remove the default popup-menu without disabling mouse: >vim aunmenu PopUp - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu To remove only the "How-to disable mouse" menu item (and its separator): >vim aunmenu PopUp.How-to\ disable\ mouse diff --git a/runtime/ftplugin/query.lua b/runtime/ftplugin/query.lua index 711ee35775..aa5cac2f07 100644 --- a/runtime/ftplugin/query.lua +++ b/runtime/ftplugin/query.lua @@ -21,7 +21,7 @@ local query_lint_on = vim.g.query_lint_on or { 'BufEnter', 'BufWrite' } if not vim.b.disable_query_linter and #query_lint_on > 0 then vim.api.nvim_create_autocmd(query_lint_on, { - group = vim.api.nvim_create_augroup('querylint', { clear = false }), + group = vim.api.nvim_create_augroup('nvim.querylint', { clear = false }), buffer = buf, callback = function() vim.treesitter.query.lint(buf) diff --git a/runtime/lua/_vim9script.lua b/runtime/lua/_vim9script.lua index 23c078cb87..29bafc53b2 100644 --- a/runtime/lua/_vim9script.lua +++ b/runtime/lua/_vim9script.lua @@ -145,7 +145,7 @@ local vim9 = (function() -- work well for calling ":source X" from within a vimscript/vim9script -- function M.make_source_cmd = function() - local group = vim.api.nvim_create_augroup('vim9script-source', {}) + local group = vim.api.nvim_create_augroup('nvim.vim9script_source', {}) vim.api.nvim_create_autocmd('SourceCmd', { pattern = '*.vim', group = group, diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index e65d267a70..2093c4eb5c 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -151,7 +151,7 @@ function properties.trim_trailing_whitespace(bufnr, val) ) if val == 'true' then vim.api.nvim_create_autocmd('BufWritePre', { - group = 'editorconfig', + group = 'nvim.editorconfig', buffer = bufnr, callback = function() local view = vim.fn.winsaveview() @@ -163,7 +163,7 @@ function properties.trim_trailing_whitespace(bufnr, val) else vim.api.nvim_clear_autocmds({ event = 'BufWritePre', - group = 'editorconfig', + group = 'nvim.editorconfig', buffer = bufnr, }) end @@ -180,7 +180,7 @@ function properties.insert_final_newline(bufnr, val) local endofline = val == 'true' if vim.bo[bufnr].endofline ~= endofline then vim.api.nvim_create_autocmd('BufWritePre', { - group = 'editorconfig', + group = 'nvim.editorconfig', buffer = bufnr, once = true, callback = function() diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index d71116117e..28f1542f64 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -412,7 +412,7 @@ do end end - local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim_popupmenu', {}) + local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim.popupmenu', {}) vim.api.nvim_create_autocmd('MenuPopup', { pattern = '*', group = nvim_popupmenu_augroup, @@ -429,7 +429,7 @@ end --- Default autocommands. See |default-autocmds| do - local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim_terminal', {}) + local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim.terminal', {}) vim.api.nvim_create_autocmd('BufReadCmd', { pattern = 'term://*', group = nvim_terminal_augroup, @@ -509,14 +509,14 @@ do vim.api.nvim_create_autocmd('CmdwinEnter', { pattern = '[:>]', desc = 'Limit syntax sync to maxlines=1 in the command window', - group = vim.api.nvim_create_augroup('nvim_cmdwin', {}), + group = vim.api.nvim_create_augroup('nvim.cmdwin', {}), command = 'syntax sync minlines=1 maxlines=1', }) vim.api.nvim_create_autocmd('SwapExists', { pattern = '*', desc = 'Skip the swapfile prompt when the swapfile is owned by a running Nvim process', - group = vim.api.nvim_create_augroup('nvim_swapfile', {}), + 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 @@ -543,7 +543,7 @@ do end if tty then - local group = vim.api.nvim_create_augroup('nvim_tty', {}) + local group = vim.api.nvim_create_augroup('nvim.tty', {}) --- Set an option after startup (so that OptionSet is fired), but only if not --- already set by the user. diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 5df4399b93..0939ff591e 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -356,7 +356,7 @@ local bufnr_and_namespace_cacher_mt = { -- bufnr -> ns -> Diagnostic[] local diagnostic_cache = {} --- @type table> do - local group = api.nvim_create_augroup('DiagnosticBufWipeout', {}) + local group = api.nvim_create_augroup('nvim.diagnostic.buf_wipeout', {}) setmetatable(diagnostic_cache, { --- @param t table --- @param bufnr integer diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 23f4e104d0..40caf01e86 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -841,7 +841,7 @@ local function buf_attach(bufnr) attached_buffers[bufnr] = true local uri = vim.uri_from_bufnr(bufnr) - local augroup = ('lsp_b_%d_save'):format(bufnr) + local augroup = ('nvim.lsp.b_%d_save'):format(bufnr) local group = api.nvim_create_augroup(augroup, { clear = true }) api.nvim_create_autocmd('BufWritePre', { group = group, diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua index 2f1767aaf5..66eb81db6e 100644 --- a/runtime/lua/vim/lsp/_folding_range.lua +++ b/runtime/lua/vim/lsp/_folding_range.lua @@ -171,7 +171,7 @@ end -- 1. Implement clearing `bufstate` and event hooks -- when no clients in the buffer support the corresponding method. -- 2. Then generalize this state management to other LSP modules. -local augroup_setup = api.nvim_create_augroup('vim_lsp_folding_range/setup', {}) +local augroup_setup = api.nvim_create_augroup('nvim.lsp.folding_range.setup', {}) --- Initialize `bufstate` and event hooks, then request folding ranges. --- Manage their lifecycle within this function. diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 65f794229c..e36d8fee27 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -30,7 +30,7 @@ local namespaces = setmetatable({}, { ---@private M.__namespaces = namespaces -local augroup = api.nvim_create_augroup('vim_lsp_codelens', {}) +local augroup = api.nvim_create_augroup('nvim.lsp.codelens', {}) api.nvim_create_autocmd('LspDetach', { group = augroup, diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index dbf0a62eeb..edbc329939 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -630,7 +630,7 @@ local function enable_completions(client_id, bufnr, opts) -- Set up autocommands. local group = - api.nvim_create_augroup(string.format('vim/lsp/completion-%d', bufnr), { clear = true }) + api.nvim_create_augroup(string.format('nvim.lsp.completion_%d', bufnr), { clear = true }) api.nvim_create_autocmd('CompleteDone', { group = group, buffer = bufnr, diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 8c1f3f10d4..cf39338cc1 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -5,7 +5,7 @@ local api = vim.api local M = {} -local augroup = api.nvim_create_augroup('vim_lsp_diagnostic', {}) +local augroup = api.nvim_create_augroup('nvim.lsp.diagnostic', {}) local DEFAULT_CLIENT_ID = -1 diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 83bf276bff..37e1202d1d 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -30,7 +30,7 @@ local bufstates = vim.defaulttable(function(_) end) local namespace = api.nvim_create_namespace('nvim.lsp.inlayhint') -local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) +local augroup = api.nvim_create_augroup('nvim.lsp.inlayhint', {}) --- |lsp-handler| for the method `textDocument/inlayHint` --- Store hints for a specific buffer and client diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index f2a8125d1a..a31202553b 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -166,7 +166,7 @@ function STHighlighter.new(bufnr) local self = setmetatable({}, { __index = STHighlighter }) self.bufnr = bufnr - self.augroup = api.nvim_create_augroup('vim_lsp_semantic_tokens:' .. bufnr, { clear = true }) + self.augroup = api.nvim_create_augroup('nvim.lsp.semantic_tokens:' .. bufnr, { clear = true }) self.client_state = {} STHighlighter.active[bufnr] = self diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index e9bf58a01b..4e0adf3366 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1357,7 +1357,7 @@ end ---@param bufnrs table list of buffers where the preview window will remain visible ---@see autocmd-events local function close_preview_autocmd(events, winnr, bufnrs) - local augroup = api.nvim_create_augroup('preview_window_' .. winnr, { + local augroup = api.nvim_create_augroup('nvim.preview_window_' .. winnr, { clear = true, }) @@ -1619,7 +1619,7 @@ function M.open_floating_preview(contents, syntax, opts) api.nvim_buf_set_var(bufnr, 'lsp_floating_preview', floating_winnr) end - local augroup_name = ('closing_floating_preview_%d'):format(floating_winnr) + local augroup_name = ('nvim.closing_floating_preview_%d'):format(floating_winnr) local ok = pcall(api.nvim_get_autocmds, { group = augroup_name, pattern = tostring(floating_winnr) }) if not ok then diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 9d4409b48a..bfd439181e 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -1,5 +1,5 @@ local G = vim.lsp._snippet_grammar -local snippet_group = vim.api.nvim_create_augroup('vim/snippet', {}) +local snippet_group = vim.api.nvim_create_augroup('nvim.snippet', {}) local snippet_ns = vim.api.nvim_create_namespace('nvim.snippet') local hl_group = 'SnippetTabstop' local jump_forward_key = '' diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7f1d1b14d5..cf5c40cd1e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -183,7 +183,7 @@ local M = {} ---@type table local foldinfos = {} -local group = api.nvim_create_augroup('treesitter/fold', {}) +local group = api.nvim_create_augroup('nvim.treesitter.fold', {}) --- Update the folds in the windows that contain the buffer and use expr foldmethod (assuming that --- the user doesn't use different foldexpr for the same buffer). diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 0e886d0e27..42c25dbdad 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -442,7 +442,7 @@ function M.inspect_tree(opts) end, }) - local group = api.nvim_create_augroup('treesitter/dev', {}) + local group = api.nvim_create_augroup('nvim.treesitter.dev', {}) api.nvim_create_autocmd('CursorMoved', { group = group, @@ -633,7 +633,7 @@ function M.edit_query(lang) -- can infer the language later. api.nvim_buf_set_name(query_buf, string.format('%s/query_editor.scm', lang)) - local group = api.nvim_create_augroup('treesitter/dev-edit', {}) + local group = api.nvim_create_augroup('nvim.treesitter.dev_edit', {}) api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { group = group, buffer = query_buf, diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 66ab0d52f0..ad648f36cc 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -289,7 +289,7 @@ end, false) api.nvim_create_autocmd('OptionSet', { pattern = { 'runtimepath' }, - group = api.nvim_create_augroup('ts_query_cache_reset', { clear = true }), + group = api.nvim_create_augroup('nvim.treesitter.query_cache_reset', { clear = true }), callback = function() M.get:clear() end, diff --git a/runtime/plugin/editorconfig.lua b/runtime/plugin/editorconfig.lua index a96919e1fe..357146cabd 100644 --- a/runtime/plugin/editorconfig.lua +++ b/runtime/plugin/editorconfig.lua @@ -1,4 +1,4 @@ -local group = vim.api.nvim_create_augroup('editorconfig', {}) +local group = vim.api.nvim_create_augroup('nvim.editorconfig', {}) vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead', 'BufFilePost' }, { group = group, callback = function(args) diff --git a/runtime/plugin/man.lua b/runtime/plugin/man.lua index d6d90208a2..e707b68859 100644 --- a/runtime/plugin/man.lua +++ b/runtime/plugin/man.lua @@ -24,7 +24,7 @@ end, { end, }) -local augroup = vim.api.nvim_create_augroup('man', {}) +local augroup = vim.api.nvim_create_augroup('nvim.man', {}) vim.api.nvim_create_autocmd('BufReadCmd', { group = augroup, diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua index b62c5afcfc..ee6bfa1be9 100644 --- a/test/functional/editor/defaults_spec.lua +++ b/test/functional/editor/defaults_spec.lua @@ -32,7 +32,7 @@ describe('default', function() describe('popupmenu', function() it('can be disabled by user', function() n.clear { - args = { '+autocmd! nvim_popupmenu', '+aunmenu PopUp' }, + args = { '+autocmd! nvim.popupmenu', '+aunmenu PopUp' }, } local screen = Screen.new(40, 8) n.insert([[ diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index d1f598a9d8..f84fc140d2 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -173,7 +173,7 @@ describe('swapfile detection', function() local screen2 = Screen.new(256, 40) screen2._default_attr_ids = nil exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). -- With shortmess+=F command('set shortmess+=F') @@ -277,7 +277,7 @@ describe('swapfile detection', function() set_session(nvim1) screen:attach() exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). feed(':split Xfile1\n') -- The default SwapExists handler does _not_ skip this prompt. screen:expect({ @@ -296,7 +296,7 @@ describe('swapfile detection', function() set_session(nvim2) screen:attach() exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). command('set more') command('au bufadd * let foo_w = wincol()') feed(':e Xfile1') @@ -327,7 +327,7 @@ describe('swapfile detection', function() exec(init) if not swapexists then - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). end command('set nohidden') diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index cc807ba555..d1b3fc8459 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -351,7 +351,7 @@ describe(':terminal buffer', function() end) it('TermRequest synchronization #27572', function() - command('autocmd! nvim_terminal TermRequest') + command('autocmd! nvim.terminal TermRequest') local term = exec_lua([[ _G.input = {} local term = vim.api.nvim_open_term(0, { diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 5224d322d3..c29a1e9cd4 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -176,7 +176,7 @@ local function test_terminal_with_fake_shell(backslash) end) it('with no argument, acts like jobstart(…,{term=true})', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') screen:expect([[ ^ready $ | @@ -246,7 +246,7 @@ local function test_terminal_with_fake_shell(backslash) end) it('ignores writes if the backing stream closes', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') feed('iiXXXXXXX') poke_eventloop() @@ -258,14 +258,14 @@ local function test_terminal_with_fake_shell(backslash) end) it('works with findfile()', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') eq('term://', string.match(eval('bufname("%")'), '^term://')) eq('scripts/shadacat.py', eval('findfile("scripts/shadacat.py", ".")')) end) it('works with :find', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') screen:expect([[ ^ready $ | diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3624a7bc2b..356361735e 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -644,7 +644,7 @@ describe('TUI', function() aunmenu PopUp " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu menu PopUp.foo :let g:menustr = 'foo' menu PopUp.bar :let g:menustr = 'bar' menu PopUp.baz :let g:menustr = 'baz' diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 3afda0c4af..cac7174cb6 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1094,7 +1094,7 @@ describe('ext_multigrid', function() end) it('supports mouse', function() - command('autocmd! nvim_popupmenu') -- Delete the default MenuPopup event handler. + command('autocmd! nvim.popupmenu') -- Delete the default MenuPopup event handler. insert('some text\nto be clicked') screen:expect{grid=[[ ## grid 1 diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 5e883d1a92..4c5b1d2bd2 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -841,7 +841,7 @@ describe('ui/ext_popupmenu', function() aunmenu PopUp " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu menu PopUp.foo :let g:menustr = 'foo' menu PopUp.bar :let g:menustr = 'bar' menu PopUp.baz :let g:menustr = 'baz' @@ -4089,7 +4089,7 @@ describe('builtin popupmenu', function() set mouse=a mousemodel=popup " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu aunmenu PopUp menu PopUp.foo :let g:menustr = 'foo' menu PopUp.bar :let g:menustr = 'bar' @@ -4946,7 +4946,7 @@ describe('builtin popupmenu', function() it(':popup command', function() exec([[ " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu func ChangeMenu() aunmenu PopUp.&Paste @@ -5106,7 +5106,7 @@ describe('builtin popupmenu', function() exec([[ set mousemodel=popup_setpos " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu aunmenu * source $VIMRUNTIME/menu.vim call setline(1, join(range(20))) diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index b104d733f0..61596fc83a 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -65,7 +65,7 @@ mapclear mapclear! aunmenu * tlunmenu * -autocmd! nvim_popupmenu +autocmd! nvim.popupmenu " Undo the 'grepprg' and 'grepformat' setting in _defaults.lua. set grepprg& grepformat& -- cgit From 575f4bc7d5069792188520d1f0e5ed12cc035002 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 13 Jan 2025 19:51:09 -0800 Subject: docs: document namespace/augroup convention --- runtime/doc/develop.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index da64475465..1e4889747d 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -508,6 +508,15 @@ be a parameter (typically manifest as mutually-exclusive buf/win/… flags like - Example: `nvim_buf_del_mark` acts on a `Buffer` object (the first parameter) and uses the "del" {verb}. + *dev-namespace-name* +Use namespace names like `nvim.foo.bar`: > + vim.api.nvim_create_namespace('nvim.lsp.codelens') +< + + *dev-augroup-name* +Use autocommand group names like `nvim.foo.bar`: > + vim.api.nvim_create_augroup('nvim.treesitter.dev') +< INTERFACE PATTERNS *dev-api-patterns* -- cgit From bc69f2723737cfe8916c117483ce32f48ff83544 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 14 Jan 2025 17:18:48 +0100 Subject: vim-patch:045564d: runtime(colors): Update colorschemes, include new unokai colorscheme - new unokai colorscheme (similar/inspired by monokai) - the rest: add explicit PopupSelected link to PmenuSel closes: vim/vim#16443 https://github.com/vim/vim/commit/045564d0a73218594691953c0c8bf2035e1e176e Co-authored-by: Maxim Kim --- runtime/colors/blue.vim | 6 +- runtime/colors/darkblue.vim | 4 +- runtime/colors/delek.vim | 4 +- runtime/colors/desert.vim | 4 +- runtime/colors/evening.vim | 5 +- runtime/colors/habamax.vim | 4 +- runtime/colors/industry.vim | 5 +- runtime/colors/lunaperche.vim | 4 +- runtime/colors/morning.vim | 4 +- runtime/colors/murphy.vim | 4 +- runtime/colors/pablo.vim | 4 +- runtime/colors/peachpuff.vim | 4 +- runtime/colors/quiet.vim | 3 +- runtime/colors/retrobox.vim | 3 +- runtime/colors/shine.vim | 4 +- runtime/colors/slate.vim | 4 +- runtime/colors/sorbet.vim | 3 +- runtime/colors/torte.vim | 4 +- runtime/colors/unokai.vim | 522 ++++++++++++++++++++++++++++++++++++++++++ runtime/colors/wildcharm.vim | 4 +- runtime/colors/zaibatsu.vim | 6 +- runtime/colors/zellner.vim | 4 +- 22 files changed, 588 insertions(+), 21 deletions(-) create mode 100644 runtime/colors/unokai.vim diff --git a/runtime/colors/blue.vim b/runtime/colors/blue.vim index 9ee878e103..7a17fc38b7 100644 --- a/runtime/colors/blue.vim +++ b/runtime/colors/blue.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Steven Vertigan " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -82,6 +82,7 @@ hi Type guifg=#ffa500 guibg=NONE gui=bold cterm=NONE hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline hi Label guifg=#ffd700 guibg=NONE gui=NONE cterm=NONE hi! link Terminal Normal +hi! link PopupSelected PmenuSel hi! link Debug Special hi! link Added String hi! link Removed WarningMsg @@ -194,6 +195,7 @@ if s:t_Co >= 256 hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline hi Label ctermfg=220 ctermbg=NONE cterm=NONE hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link Debug Special hi! link Added String hi! link Removed WarningMsg @@ -309,6 +311,7 @@ if s:t_Co >= 16 hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline hi Label ctermfg=yellow ctermbg=NONE cterm=NONE hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link Debug Special hi! link Added String hi! link Removed WarningMsg @@ -423,6 +426,7 @@ if s:t_Co >= 8 hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline hi Label ctermfg=yellow ctermbg=NONE cterm=NONE hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link Debug Special hi! link Added String hi! link Removed WarningMsg diff --git a/runtime/colors/darkblue.vim b/runtime/colors/darkblue.vim index 9451e397e5..90dc304791 100644 --- a/runtime/colors/darkblue.vim +++ b/runtime/colors/darkblue.vim @@ -4,7 +4,7 @@ " Maintainer: Original author Bohdan Vlasyuk " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -24,6 +24,7 @@ if (has('termguicolors') && &termguicolors) || has('gui_running') endfor endif hi! link Terminal Normal +hi! link PopupSelected PmenuSel hi! link CursorColumn CursorLine hi! link CursorIM Cursor hi! link EndOfBuffer NonText @@ -134,6 +135,7 @@ hi DiffDelete guifg=#ffffff guibg=#af5faf gui=NONE cterm=NONE if s:t_Co >= 256 hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link CursorColumn CursorLine hi! link CursorIM Cursor hi! link EndOfBuffer NonText diff --git a/runtime/colors/delek.vim b/runtime/colors/delek.vim index 35d4934f5c..d29980d685 100644 --- a/runtime/colors/delek.vim +++ b/runtime/colors/delek.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer David Schweikert " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -24,6 +24,7 @@ if (has('termguicolors') && &termguicolors) || has('gui_running') endfor endif hi! link Terminal Normal +hi! link PopupSelected PmenuSel hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link CurSearch Search @@ -100,6 +101,7 @@ hi DiffDelete guifg=#ffffff guibg=#af5faf gui=NONE cterm=NONE if s:t_Co >= 256 hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link CurSearch Search diff --git a/runtime/colors/desert.vim b/runtime/colors/desert.vim index 07ef937ab6..b5f404a14e 100644 --- a/runtime/colors/desert.vim +++ b/runtime/colors/desert.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Hans Fugal " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -32,6 +32,7 @@ hi! link CursorLineSign CursorLine hi! link EndOfBuffer NonText hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#ffffff guibg=#333333 gui=NONE cterm=NONE hi StatusLine guifg=#333333 guibg=#c2bfa5 gui=NONE cterm=NONE hi StatusLineNC guifg=#7f7f8c guibg=#c2bfa5 gui=NONE cterm=NONE @@ -108,6 +109,7 @@ if s:t_Co >= 256 hi! link EndOfBuffer NonText hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=231 ctermbg=236 cterm=NONE hi StatusLine ctermfg=236 ctermbg=144 cterm=NONE hi StatusLineNC ctermfg=242 ctermbg=144 cterm=NONE diff --git a/runtime/colors/evening.vim b/runtime/colors/evening.vim index d5e081337a..2f6859ad10 100644 --- a/runtime/colors/evening.vim +++ b/runtime/colors/evening.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Steven Vertigan " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -24,6 +24,7 @@ if (has('termguicolors') && &termguicolors) || has('gui_running') endfor endif hi! link VertSplit StatusLineNC +hi! link PopupSelected PmenuSel hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link TabLineFill TabLine @@ -134,6 +135,7 @@ hi DiffDelete guifg=#ffffff guibg=#af5faf gui=NONE cterm=NONE if s:t_Co >= 256 hi! link VertSplit StatusLineNC + hi! link PopupSelected PmenuSel hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link TabLineFill TabLine @@ -247,6 +249,7 @@ endif if s:t_Co >= 16 hi! link VertSplit StatusLineNC + hi! link PopupSelected PmenuSel hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link TabLineFill TabLine diff --git a/runtime/colors/habamax.vim b/runtime/colors/habamax.vim index 4b4c95e050..e4113d80bb 100644 --- a/runtime/colors/habamax.vim +++ b/runtime/colors/habamax.vim @@ -4,7 +4,7 @@ " Maintainer: Maxim Kim " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -27,6 +27,7 @@ hi! link Terminal Normal hi! link StatuslineTerm Statusline hi! link StatuslineTermNC StatuslineNC hi! link MessageWindow Pmenu +hi! link PopupSelected PmenuSel hi! link javaScriptFunction Statement hi! link javaScriptIdentifier Statement hi! link sqlKeyword Statement @@ -122,6 +123,7 @@ if s:t_Co >= 256 hi! link StatuslineTerm Statusline hi! link StatuslineTermNC StatuslineNC hi! link MessageWindow Pmenu + hi! link PopupSelected PmenuSel hi! link javaScriptFunction Statement hi! link javaScriptIdentifier Statement hi! link sqlKeyword Statement diff --git a/runtime/colors/industry.vim b/runtime/colors/industry.vim index 6e66a4a791..edb86188a8 100644 --- a/runtime/colors/industry.vim +++ b/runtime/colors/industry.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Shian Lee. " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -86,6 +86,7 @@ hi Conceal guifg=#6c6c6c guibg=NONE gui=NONE cterm=NONE hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE hi Title guifg=#ff00ff guibg=NONE gui=bold cterm=bold hi! link Terminal Normal +hi! link PopupSelected PmenuSel hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link CurSearch Search @@ -162,6 +163,7 @@ if s:t_Co >= 256 hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE hi Title ctermfg=201 ctermbg=NONE cterm=bold hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link CurSearch Search @@ -241,6 +243,7 @@ if s:t_Co >= 16 hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE hi Title ctermfg=magenta ctermbg=NONE cterm=bold hi! link Terminal Normal + hi! link PopupSelected PmenuSel hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link CurSearch Search diff --git a/runtime/colors/lunaperche.vim b/runtime/colors/lunaperche.vim index 75d25e2a23..464b7af00e 100644 --- a/runtime/colors/lunaperche.vim +++ b/runtime/colors/lunaperche.vim @@ -4,7 +4,7 @@ " Maintainer: Maxim Kim " Website: https://www.github.com/vim/colorschemes " License: Vim License (see `:help license`) -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -93,6 +93,7 @@ hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link MessageWindow PMenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel if &background ==# 'dark' if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#af5f5f', '#5faf5f', '#af875f', '#5f87af', '#d787d7', '#5fafaf', '#c6c6c6', '#767676', '#ff5f5f', '#5fd75f', '#ffd787', '#5fafff', '#ff87ff', '#5fd7d7', '#ffffff'] @@ -369,6 +370,7 @@ if s:t_Co >= 256 hi! link LineNrBelow LineNr hi! link MessageWindow PMenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel if &background ==# 'dark' hi Normal ctermfg=251 ctermbg=16 cterm=NONE hi Statusline ctermfg=251 ctermbg=16 cterm=bold,reverse diff --git a/runtime/colors/morning.vim b/runtime/colors/morning.vim index 82a3d6d97d..463d009e88 100644 --- a/runtime/colors/morning.vim +++ b/runtime/colors/morning.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Bram Moolenaar " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -33,6 +33,7 @@ hi! link StatuslineTerm Statusline hi! link StatuslineTermNC StatuslineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#000000 guibg=#e4e4e4 gui=NONE cterm=NONE hi EndOfBuffer guifg=#0000ff guibg=#cccccc gui=bold cterm=bold hi Folded guifg=#00008b guibg=#d3d3d3 gui=NONE cterm=NONE @@ -107,6 +108,7 @@ if s:t_Co >= 256 hi! link StatuslineTermNC StatuslineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=16 ctermbg=254 cterm=NONE hi EndOfBuffer ctermfg=21 ctermbg=252 cterm=bold hi Folded ctermfg=18 ctermbg=252 cterm=NONE diff --git a/runtime/colors/murphy.vim b/runtime/colors/murphy.vim index f38c8259dd..f00bdbd608 100644 --- a/runtime/colors/murphy.vim +++ b/runtime/colors/murphy.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Ron Aaron . " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -33,6 +33,7 @@ hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi! link Added Constant hi Normal guifg=#87ff87 guibg=#000000 gui=NONE cterm=NONE hi EndOfBuffer guifg=#0000ff guibg=#000000 gui=NONE cterm=NONE @@ -108,6 +109,7 @@ if s:t_Co >= 256 hi! link StatusLineTermNC StatusLineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi! link Added Constant hi Normal ctermfg=120 ctermbg=16 cterm=NONE hi EndOfBuffer ctermfg=21 ctermbg=16 cterm=NONE diff --git a/runtime/colors/pablo.vim b/runtime/colors/pablo.vim index de585adfe2..202f505136 100644 --- a/runtime/colors/pablo.vim +++ b/runtime/colors/pablo.vim @@ -3,7 +3,7 @@ " Maintainer: Original maintainerRon Aaron " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -30,6 +30,7 @@ hi! link CursorLineFold CursorLine hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#ffffff guibg=#000000 gui=NONE cterm=NONE hi Comment guifg=#808080 guibg=NONE gui=NONE cterm=NONE hi Constant guifg=#00ffff guibg=NONE gui=NONE cterm=NONE @@ -105,6 +106,7 @@ if s:t_Co >= 256 hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=231 ctermbg=16 cterm=NONE hi Comment ctermfg=244 ctermbg=NONE cterm=NONE hi Constant ctermfg=51 ctermbg=NONE cterm=NONE diff --git a/runtime/colors/peachpuff.vim b/runtime/colors/peachpuff.vim index 91c98119b3..508346a7ce 100644 --- a/runtime/colors/peachpuff.vim +++ b/runtime/colors/peachpuff.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer David Ne\v{c}as (Yeti) " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -31,6 +31,7 @@ hi! link CursorLineFold CursorLine hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#000000 guibg=#ffdab9 gui=NONE cterm=NONE hi Folded guifg=#000000 guibg=#e3c1a5 gui=NONE cterm=NONE hi CursorLine guifg=NONE guibg=#f5c195 gui=NONE cterm=NONE @@ -105,6 +106,7 @@ if s:t_Co >= 256 hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=16 ctermbg=223 cterm=NONE hi Folded ctermfg=16 ctermbg=252 cterm=NONE hi CursorLine ctermfg=NONE ctermbg=180 cterm=NONE diff --git a/runtime/colors/quiet.vim b/runtime/colors/quiet.vim index bcf2eced16..38349d76e8 100644 --- a/runtime/colors/quiet.vim +++ b/runtime/colors/quiet.vim @@ -4,7 +4,7 @@ " Maintainer: Maxence Weynans " Website: https://github.com/vim/colorschemes " License: Vim License (see `:help license`)` -" Last Change: 2024 Aug 05 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -22,6 +22,7 @@ hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi! link Boolean Constant hi! link Character Constant hi! link Conditional Statement diff --git a/runtime/colors/retrobox.vim b/runtime/colors/retrobox.vim index a89bf0557e..f34fc99dc5 100644 --- a/runtime/colors/retrobox.vim +++ b/runtime/colors/retrobox.vim @@ -4,7 +4,7 @@ " Maintainer: Maxim Kim , ported from gruvbox8 of Lifepillar " Website: https://www.github.com/vim/colorschemes " License: Vim License (see `:help license`) -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -22,6 +22,7 @@ hi! link Tag Special hi! link lCursor Cursor hi! link MessageWindow PMenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi! link CurSearch IncSearch hi! link Terminal Normal diff --git a/runtime/colors/shine.vim b/runtime/colors/shine.vim index f3697c9ad6..39e6dae956 100644 --- a/runtime/colors/shine.vim +++ b/runtime/colors/shine.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer is Yasuhiro Matsumoto " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -35,6 +35,7 @@ hi! link Tag Special hi! link Operator Statement hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE hi Folded guifg=#00008b guibg=#dadada gui=NONE cterm=NONE hi CursorLine guifg=NONE guibg=#dadada gui=NONE cterm=NONE @@ -115,6 +116,7 @@ if s:t_Co >= 256 hi! link Operator Statement hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=16 ctermbg=231 cterm=NONE hi Folded ctermfg=18 ctermbg=253 cterm=NONE hi CursorLine ctermfg=NONE ctermbg=253 cterm=NONE diff --git a/runtime/colors/slate.vim b/runtime/colors/slate.vim index c9ce78946b..319ab9e0aa 100644 --- a/runtime/colors/slate.vim +++ b/runtime/colors/slate.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Ralph Amissah " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -31,6 +31,7 @@ hi! link CursorLineFold CursorLine hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#ffffff guibg=#262626 gui=NONE cterm=NONE hi EndOfBuffer guifg=#5f87d7 guibg=NONE gui=NONE cterm=NONE hi StatusLine guifg=#000000 guibg=#afaf87 gui=NONE cterm=NONE @@ -110,6 +111,7 @@ if s:t_Co >= 256 hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=231 ctermbg=235 cterm=NONE hi EndOfBuffer ctermfg=68 ctermbg=NONE cterm=NONE hi StatusLine ctermfg=16 ctermbg=144 cterm=NONE diff --git a/runtime/colors/sorbet.vim b/runtime/colors/sorbet.vim index bd4fb7baf7..25c27bf635 100644 --- a/runtime/colors/sorbet.vim +++ b/runtime/colors/sorbet.vim @@ -4,7 +4,7 @@ " Maintainer: Maxence Weynans " Website: https://github.com/vim/colorschemes " License: Vim License (see `:help license`)` -" Last Change: 2024 Aug 05 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -21,6 +21,7 @@ hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi! link Boolean Constant hi! link Character Constant hi! link Conditional Statement diff --git a/runtime/colors/torte.vim b/runtime/colors/torte.vim index 7271188f0d..0709263f7c 100644 --- a/runtime/colors/torte.vim +++ b/runtime/colors/torte.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Thorsten Maerz " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -33,6 +33,7 @@ hi! link StatusLineTerm StatusLine hi! link StatusLineTermNC StatusLineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#cccccc guibg=#000000 gui=NONE cterm=NONE hi Comment guifg=#80a0ff guibg=NONE gui=NONE cterm=NONE hi Constant guifg=#ffa0a0 guibg=NONE gui=NONE cterm=NONE @@ -108,6 +109,7 @@ if s:t_Co >= 256 hi! link StatusLineTermNC StatusLineNC hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=251 ctermbg=16 cterm=NONE hi Comment ctermfg=111 ctermbg=NONE cterm=NONE hi Constant ctermfg=217 ctermbg=NONE cterm=NONE diff --git a/runtime/colors/unokai.vim b/runtime/colors/unokai.vim new file mode 100644 index 0000000000..168eda1483 --- /dev/null +++ b/runtime/colors/unokai.vim @@ -0,0 +1,522 @@ +" Name: unokai +" Description: Color scheme similar to Monokai originally created by Wimer Hazenberg for TextMate +" Author: k-37 <60838818+k-37@users.noreply.github.com> +" Maintainer: k-37 <60838818+k-37@users.noreply.github.com> +" Website: https://github.com/vim/colorschemes +" License: Vim License (see `:help license`) +" Last Change: 2024 Dec 15 + +" Generated by Colortemplate v2.2.3 + +set background=dark + +" hi clear +source $VIMRUNTIME/colors/vim.lua " Nvim: revert to Vim default color scheme +let g:colors_name = 'unokai' + +let s:t_Co = &t_Co + +if (has('termguicolors') && &termguicolors) || has('gui_running') + let g:terminal_ansi_colors = ['#282923', '#c61e5c', '#81af24', '#fd971f', '#51aebe', '#ae81ff', '#80beb5', '#bababa', '#74705d', '#f92672', '#a6e22e', '#e6db74', '#66d9ef', '#fd5ff0', '#a1efe4', '#f8f8f2'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor +endif +hi! link CursorLineFold FoldColumn +hi! link CursorLineSign SignColumn +hi! link MessageWindow Pmenu +hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel +hi! link StatusLineTerm StatusLine +hi! link StatusLineTermNC StatusLineNC +hi! link Terminal Normal +hi! link Delimiter PreProc +hi! link Operator PreProc +hi! link StorageClass PreProc +hi! link Structure PreProc +hi! link Define Identifier +hi! link Label String +hi! link markdownCode Comment +hi! link markdownCodeBlock markdownCode +hi! link markdownCodeDelimiter markdownCode +hi Normal guifg=#f8f8f2 guibg=#282923 gui=NONE cterm=NONE +hi StatusLine guifg=#282923 guibg=#bababa gui=NONE cterm=NONE +hi StatusLineNC guifg=#282923 guibg=#74705d gui=NONE cterm=NONE +hi VertSplit guifg=#74705d guibg=#74705d gui=NONE cterm=NONE +hi TabLine guifg=#282923 guibg=#74705d gui=NONE cterm=NONE +hi TabLineFill guifg=#282923 guibg=#74705d gui=NONE cterm=NONE +hi TabLineSel guifg=#282923 guibg=#bababa gui=bold cterm=bold +hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE +hi ToolbarButton guifg=#74705d guibg=#f8f8f2 gui=bold,reverse cterm=bold,reverse +hi QuickFixLine guifg=#282923 guibg=#51aebe gui=NONE cterm=NONE +hi CursorLineNr guifg=#dadada guibg=NONE gui=bold cterm=bold +hi LineNr guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi LineNrAbove guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi LineNrBelow guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi NonText guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi EndOfBuffer guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi SpecialKey guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi FoldColumn guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi Visual guifg=#a1efe4 guibg=#282923 gui=reverse cterm=reverse +hi VisualNOS guifg=#282923 guibg=#80beb5 gui=NONE cterm=NONE +hi Pmenu guifg=NONE guibg=#585858 gui=NONE cterm=NONE +hi PmenuThumb guifg=NONE guibg=#74705d gui=NONE cterm=NONE +hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE +hi PmenuSel guifg=NONE guibg=#8a8a8a gui=NONE cterm=NONE +hi PmenuKind guifg=#80beb5 guibg=#585858 gui=NONE cterm=NONE +hi PmenuKindSel guifg=#80beb5 guibg=#8a8a8a gui=NONE cterm=NONE +hi PmenuExtra guifg=#bababa guibg=#585858 gui=NONE cterm=NONE +hi PmenuExtraSel guifg=#bababa guibg=#8a8a8a gui=NONE cterm=NONE +hi PmenuMatch guifg=#ffaf5f guibg=#585858 gui=NONE cterm=NONE +hi PmenuMatchSel guifg=#ffaf5f guibg=#8a8a8a gui=NONE cterm=NONE +hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE +hi Error guifg=#f92672 guibg=#000000 gui=reverse cterm=reverse +hi ErrorMsg guifg=#f92672 guibg=#000000 gui=reverse cterm=reverse +hi ModeMsg guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold +hi MoreMsg guifg=#81af24 guibg=NONE gui=NONE cterm=NONE +hi Question guifg=#e6db74 guibg=NONE gui=NONE cterm=NONE +hi WarningMsg guifg=#f92672 guibg=NONE gui=NONE cterm=NONE +hi Todo guifg=#dadada guibg=NONE gui=bold cterm=bold +hi MatchParen guifg=#fd971f guibg=NONE gui=bold cterm=bold +hi Search guifg=#66d9ef guibg=#282923 gui=reverse cterm=reverse +hi IncSearch guifg=#ffaf5f guibg=#282923 gui=reverse cterm=reverse +hi CurSearch guifg=#ffaf5f guibg=#282923 gui=reverse cterm=reverse +hi WildMenu guifg=#282923 guibg=#e6db74 gui=bold cterm=bold +hi debugPC guifg=#282923 guibg=#51aebe gui=NONE cterm=NONE +hi debugBreakpoint guifg=#282923 guibg=#f92672 gui=NONE cterm=NONE +hi Cursor guifg=#000000 guibg=#dadada gui=NONE cterm=NONE +hi lCursor guifg=#282923 guibg=#5fff00 gui=NONE cterm=NONE +hi CursorLine guifg=NONE guibg=#3a392f gui=NONE cterm=NONE +hi CursorColumn guifg=NONE guibg=#3a392f gui=NONE cterm=NONE +hi Folded guifg=#bababa guibg=#414141 gui=NONE cterm=NONE +hi ColorColumn guifg=NONE guibg=#585858 gui=NONE cterm=NONE +hi SpellBad guifg=NONE guibg=NONE guisp=#d75f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline +hi SpellCap guifg=NONE guibg=NONE guisp=#ffaf5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline +hi SpellLocal guifg=NONE guibg=NONE guisp=#5fd75f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline +hi SpellRare guifg=NONE guibg=NONE guisp=#fd5ff0 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline +hi Constant guifg=#ae81ff guibg=NONE gui=NONE cterm=NONE +hi Type guifg=#fd971f guibg=NONE gui=bold cterm=bold +hi Character guifg=#a6e22e guibg=NONE gui=NONE cterm=NONE +hi Comment guifg=#74705d guibg=NONE gui=NONE cterm=NONE +hi String guifg=#e6db74 guibg=NONE gui=NONE cterm=NONE +hi Function guifg=#a6e22e guibg=NONE gui=NONE cterm=NONE +hi Identifier guifg=#66d9ef guibg=NONE gui=NONE cterm=NONE +hi PreProc guifg=#f92672 guibg=NONE gui=NONE cterm=NONE +hi Special guifg=#80beb5 guibg=NONE gui=NONE cterm=NONE +hi Statement guifg=#f92672 guibg=NONE gui=bold cterm=bold +hi Underlined guifg=#66d9ef guibg=NONE gui=underline cterm=underline +hi Title guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold +hi Debug guifg=#80beb5 guibg=NONE gui=NONE cterm=NONE +hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE +hi Directory guifg=#a1efe4 guibg=NONE gui=bold cterm=bold +hi Conceal guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE +hi DiffAdd guifg=#5faf5f guibg=NONE gui=reverse cterm=reverse +hi DiffChange guifg=#5f87af guibg=NONE gui=reverse cterm=reverse +hi DiffText guifg=#af87af guibg=NONE gui=reverse cterm=reverse +hi DiffDelete guifg=#af5f5f guibg=NONE gui=reverse cterm=reverse +hi Added guifg=#5fd75f guibg=NONE gui=NONE cterm=NONE +hi Changed guifg=#ffaf5f guibg=NONE gui=NONE cterm=NONE +hi Removed guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE +hi htmlBold guifg=#f8f8f2 guibg=NONE gui=bold cterm=bold +hi htmlItalic guifg=#f8f8f2 guibg=NONE gui=italic cterm=italic +hi markdownHeadingDelimiter guifg=#f8f8f2 guibg=NONE gui=NONE cterm=NONE +hi markdownH1Delimiter guifg=#f92672 guibg=NONE gui=NONE cterm=NONE +hi markdownH2Delimiter guifg=#e6db74 guibg=NONE gui=NONE cterm=NONE +hi markdownH4Delimiter guifg=#66d9ef guibg=NONE gui=NONE cterm=NONE +hi markdownH6Delimiter guifg=#a6e22e guibg=NONE gui=NONE cterm=NONE +hi markdownH3Delimiter guifg=#fd971f guibg=NONE gui=NONE cterm=NONE +hi markdownH5Delimiter guifg=#51aebe guibg=NONE gui=NONE cterm=NONE + +if s:t_Co >= 256 + hi! link CursorLineFold FoldColumn + hi! link CursorLineSign SignColumn + hi! link MessageWindow Pmenu + hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel + hi! link StatusLineTerm StatusLine + hi! link StatusLineTermNC StatusLineNC + hi! link Terminal Normal + hi! link Delimiter PreProc + hi! link Operator PreProc + hi! link StorageClass PreProc + hi! link Structure PreProc + hi! link Define Identifier + hi! link Label String + hi! link markdownCode Comment + hi! link markdownCodeBlock markdownCode + hi! link markdownCodeDelimiter markdownCode + hi Normal ctermfg=255 ctermbg=235 cterm=NONE + hi StatusLine ctermfg=235 ctermbg=250 cterm=NONE + hi StatusLineNC ctermfg=235 ctermbg=244 cterm=NONE + hi VertSplit ctermfg=244 ctermbg=244 cterm=NONE + hi TabLine ctermfg=235 ctermbg=244 cterm=NONE + hi TabLineFill ctermfg=235 ctermbg=244 cterm=NONE + hi TabLineSel ctermfg=235 ctermbg=250 cterm=bold + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=244 ctermbg=255 cterm=bold,reverse + hi QuickFixLine ctermfg=235 ctermbg=141 cterm=NONE + hi CursorLineNr ctermfg=253 ctermbg=NONE cterm=bold + hi LineNr ctermfg=245 ctermbg=NONE cterm=NONE + hi LineNrAbove ctermfg=245 ctermbg=NONE cterm=NONE + hi LineNrBelow ctermfg=245 ctermbg=NONE cterm=NONE + hi NonText ctermfg=245 ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=245 ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=245 ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=245 ctermbg=NONE cterm=NONE + hi Visual ctermfg=116 ctermbg=235 cterm=reverse + hi VisualNOS ctermfg=235 ctermbg=73 cterm=NONE + hi Pmenu ctermfg=NONE ctermbg=240 cterm=NONE + hi PmenuThumb ctermfg=NONE ctermbg=244 cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE + hi PmenuSel ctermfg=NONE ctermbg=245 cterm=NONE + hi PmenuKind ctermfg=73 ctermbg=240 cterm=NONE + hi PmenuKindSel ctermfg=73 ctermbg=245 cterm=NONE + hi PmenuExtra ctermfg=250 ctermbg=240 cterm=NONE + hi PmenuExtraSel ctermfg=250 ctermbg=245 cterm=NONE + hi PmenuMatch ctermfg=215 ctermbg=240 cterm=NONE + hi PmenuMatchSel ctermfg=215 ctermbg=245 cterm=NONE + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=197 ctermbg=16 cterm=reverse + hi ErrorMsg ctermfg=197 ctermbg=16 cterm=reverse + hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold + hi MoreMsg ctermfg=106 ctermbg=NONE cterm=NONE + hi Question ctermfg=185 ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=197 ctermbg=NONE cterm=NONE + hi Todo ctermfg=253 ctermbg=NONE cterm=bold + hi MatchParen ctermfg=208 ctermbg=NONE cterm=bold + hi Search ctermfg=81 ctermbg=235 cterm=reverse + hi IncSearch ctermfg=215 ctermbg=235 cterm=reverse + hi CurSearch ctermfg=215 ctermbg=235 cterm=reverse + hi WildMenu ctermfg=235 ctermbg=185 cterm=bold + hi debugPC ctermfg=235 ctermbg=73 cterm=NONE + hi debugBreakpoint ctermfg=235 ctermbg=197 cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=237 cterm=NONE + hi CursorColumn ctermfg=NONE ctermbg=237 cterm=NONE + hi Folded ctermfg=250 ctermbg=238 cterm=NONE + hi ColorColumn ctermfg=NONE ctermbg=240 cterm=NONE + hi SpellBad ctermfg=167 ctermbg=NONE cterm=underline + hi SpellCap ctermfg=215 ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=77 ctermbg=NONE cterm=underline + hi SpellRare ctermfg=207 ctermbg=NONE cterm=underline + hi Constant ctermfg=141 ctermbg=NONE cterm=NONE + hi Type ctermfg=208 ctermbg=NONE cterm=bold + hi Character ctermfg=112 ctermbg=NONE cterm=NONE + hi Comment ctermfg=244 ctermbg=NONE cterm=NONE + hi String ctermfg=185 ctermbg=NONE cterm=NONE + hi Function ctermfg=112 ctermbg=NONE cterm=NONE + hi Identifier ctermfg=81 ctermbg=NONE cterm=NONE + hi PreProc ctermfg=197 ctermbg=NONE cterm=NONE + hi Special ctermfg=73 ctermbg=NONE cterm=NONE + hi Statement ctermfg=197 ctermbg=NONE cterm=bold + hi Underlined ctermfg=81 ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Debug ctermfg=73 ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi Directory ctermfg=116 ctermbg=NONE cterm=bold + hi Conceal ctermfg=245 ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=71 ctermbg=NONE cterm=reverse + hi DiffChange ctermfg=67 ctermbg=NONE cterm=reverse + hi DiffText ctermfg=139 ctermbg=NONE cterm=reverse + hi DiffDelete ctermfg=131 ctermbg=NONE cterm=reverse + hi Added ctermfg=77 ctermbg=NONE cterm=NONE + hi Changed ctermfg=215 ctermbg=NONE cterm=NONE + hi Removed ctermfg=167 ctermbg=NONE cterm=NONE + hi htmlBold ctermfg=255 ctermbg=NONE cterm=bold + hi htmlItalic ctermfg=255 ctermbg=NONE cterm=underline + hi markdownHeadingDelimiter ctermfg=255 ctermbg=NONE cterm=NONE + hi markdownH1Delimiter ctermfg=197 ctermbg=NONE cterm=NONE + hi markdownH2Delimiter ctermfg=185 ctermbg=NONE cterm=NONE + hi markdownH4Delimiter ctermfg=81 ctermbg=NONE cterm=NONE + hi markdownH6Delimiter ctermfg=112 ctermbg=NONE cterm=NONE + hi markdownH3Delimiter ctermfg=208 ctermbg=NONE cterm=NONE + hi markdownH5Delimiter ctermfg=73 ctermbg=NONE cterm=NONE + unlet s:t_Co + finish +endif + +if s:t_Co >= 16 + hi Normal ctermfg=white ctermbg=black cterm=NONE + hi StatusLine ctermfg=black ctermbg=gray cterm=NONE + hi StatusLineNC ctermfg=black ctermbg=darkgray cterm=NONE + hi VertSplit ctermfg=darkgray ctermbg=darkgray cterm=NONE + hi TabLine ctermfg=black ctermbg=darkgray cterm=NONE + hi TabLineFill ctermfg=black ctermbg=darkgray cterm=NONE + hi TabLineSel ctermfg=black ctermbg=gray cterm=bold + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=darkgray ctermbg=white cterm=bold,reverse + hi QuickFixLine ctermfg=black ctermbg=darkmagenta cterm=NONE + hi CursorLineNr ctermfg=white ctermbg=NONE cterm=bold + hi LineNr ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi LineNrAbove ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi LineNrBelow ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi NonText ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi FoldColumn ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi Visual ctermfg=cyan ctermbg=black cterm=reverse + hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE + hi Pmenu ctermfg=black ctermbg=gray cterm=NONE + hi PmenuThumb ctermfg=gray ctermbg=black cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=gray cterm=NONE + hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE + hi PmenuKind ctermfg=darkred ctermbg=gray cterm=NONE + hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE + hi PmenuExtra ctermfg=darkgray ctermbg=gray cterm=NONE + hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE + hi PmenuMatch ctermfg=black ctermbg=gray cterm=bold + hi PmenuMatchSel ctermfg=black ctermbg=darkyellow cterm=bold + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=red ctermbg=black cterm=reverse + hi ErrorMsg ctermfg=red ctermbg=black cterm=reverse + hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold + hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Question ctermfg=yellow ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=red ctermbg=NONE cterm=NONE + hi Todo ctermfg=white ctermbg=NONE cterm=bold + hi MatchParen ctermfg=darkyellow ctermbg=NONE cterm=bold + hi Search ctermfg=blue ctermbg=black cterm=reverse + hi IncSearch ctermfg=red ctermbg=black cterm=reverse + hi CurSearch ctermfg=red ctermbg=black cterm=reverse + hi WildMenu ctermfg=black ctermbg=yellow cterm=bold + hi debugPC ctermfg=black ctermbg=darkblue cterm=NONE + hi debugBreakpoint ctermfg=black ctermbg=red cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline + hi CursorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi Folded ctermfg=black ctermbg=darkyellow cterm=NONE + hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline + hi SpellCap ctermfg=darkyellow ctermbg=NONE cterm=underline + hi SpellLocal ctermfg=darkgreen ctermbg=NONE cterm=underline + hi SpellRare ctermfg=magenta ctermbg=NONE cterm=underline + hi Constant ctermfg=darkmagenta ctermbg=NONE cterm=NONE + hi Type ctermfg=darkyellow ctermbg=NONE cterm=bold + hi Character ctermfg=green ctermbg=NONE cterm=NONE + hi Comment ctermfg=darkgray ctermbg=NONE cterm=NONE + hi String ctermfg=yellow ctermbg=NONE cterm=NONE + hi Function ctermfg=green ctermbg=NONE cterm=NONE + hi Identifier ctermfg=blue ctermbg=NONE cterm=NONE + hi PreProc ctermfg=red ctermbg=NONE cterm=NONE + hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Statement ctermfg=red ctermbg=NONE cterm=bold + hi Underlined ctermfg=blue ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Debug ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi Directory ctermfg=cyan ctermbg=NONE cterm=bold + hi Conceal ctermfg=darkgrey ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=darkgreen ctermbg=NONE cterm=reverse + hi DiffChange ctermfg=darkblue ctermbg=NONE cterm=reverse + hi DiffText ctermfg=darkmagenta ctermbg=NONE cterm=reverse + hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=reverse + hi Added ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Changed ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Removed ctermfg=darkred ctermbg=NONE cterm=NONE + hi htmlBold ctermfg=white ctermbg=NONE cterm=bold + hi htmlItalic ctermfg=white ctermbg=NONE cterm=underline + hi markdownHeadingDelimiter ctermfg=white ctermbg=NONE cterm=NONE + hi markdownH1Delimiter ctermfg=red ctermbg=NONE cterm=NONE + hi markdownH2Delimiter ctermfg=yellow ctermbg=NONE cterm=NONE + hi markdownH4Delimiter ctermfg=blue ctermbg=NONE cterm=NONE + hi markdownH6Delimiter ctermfg=green ctermbg=NONE cterm=NONE + hi markdownH3Delimiter ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi markdownH5Delimiter ctermfg=darkblue ctermbg=NONE cterm=NONE + unlet s:t_Co + finish +endif + +if s:t_Co >= 8 + hi Normal ctermfg=gray ctermbg=black cterm=NONE + hi StatusLine ctermfg=gray ctermbg=black cterm=bold,reverse + hi StatusLineNC ctermfg=gray ctermbg=black cterm=reverse + hi VertSplit ctermfg=gray ctermbg=gray cterm=NONE + hi TabLine ctermfg=black ctermbg=gray cterm=NONE + hi TabLineFill ctermfg=gray ctermbg=gray cterm=NONE + hi TabLineSel ctermfg=black ctermbg=gray cterm=bold + hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE + hi ToolbarButton ctermfg=gray ctermbg=black cterm=reverse + hi QuickFixLine ctermfg=black ctermbg=darkyellow cterm=NONE + hi CursorLineNr ctermfg=darkyellow ctermbg=NONE cterm=bold + hi LineNr ctermfg=gray ctermbg=NONE cterm=bold + hi LineNrAbove ctermfg=gray ctermbg=NONE cterm=bold + hi LineNrBelow ctermfg=gray ctermbg=NONE cterm=bold + hi NonText ctermfg=gray ctermbg=NONE cterm=bold + hi EndOfBuffer ctermfg=gray ctermbg=NONE cterm=bold + hi SpecialKey ctermfg=gray ctermbg=NONE cterm=bold + hi FoldColumn ctermfg=gray ctermbg=NONE cterm=bold + hi Visual ctermfg=black ctermbg=darkcyan cterm=NONE + hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE + hi Pmenu ctermfg=black ctermbg=gray cterm=NONE + hi PmenuThumb ctermfg=gray ctermbg=black cterm=NONE + hi PmenuSbar ctermfg=NONE ctermbg=gray cterm=NONE + hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE + hi PmenuKind ctermfg=darkred ctermbg=gray cterm=NONE + hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE + hi PmenuExtra ctermfg=black ctermbg=gray cterm=NONE + hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE + hi PmenuMatch ctermfg=black ctermbg=gray cterm=bold + hi PmenuMatchSel ctermfg=black ctermbg=darkyellow cterm=bold + hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE + hi Error ctermfg=darkred ctermbg=gray cterm=bold,reverse + hi ErrorMsg ctermfg=darkred ctermbg=gray cterm=bold,reverse + hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold + hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Question ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi WarningMsg ctermfg=darkred ctermbg=NONE cterm=NONE + hi Todo ctermfg=gray ctermbg=NONE cterm=bold + hi MatchParen ctermfg=darkyellow ctermbg=NONE cterm=bold + hi Search ctermfg=black ctermbg=darkblue cterm=NONE + hi IncSearch ctermfg=black ctermbg=darkyellow cterm=NONE + hi CurSearch ctermfg=black ctermbg=darkyellow cterm=NONE + hi WildMenu ctermfg=black ctermbg=darkyellow cterm=NONE + hi debugPC ctermfg=black ctermbg=darkblue cterm=NONE + hi debugBreakpoint ctermfg=black ctermbg=darkcyan cterm=NONE + hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline + hi CursorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi Folded ctermfg=black ctermbg=darkyellow cterm=NONE + hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE + hi SpellBad ctermfg=darkred ctermbg=gray cterm=reverse + hi SpellCap ctermfg=darkblue ctermbg=gray cterm=reverse + hi SpellLocal ctermfg=darkgreen ctermbg=black cterm=reverse + hi SpellRare ctermfg=darkmagenta ctermbg=gray cterm=reverse + hi Constant ctermfg=darkmagenta ctermbg=NONE cterm=NONE + hi Type ctermfg=darkyellow ctermbg=NONE cterm=bold + hi Character ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Comment ctermfg=gray ctermbg=NONE cterm=bold + hi String ctermfg=darkyellow ctermbg=NONE cterm=bold + hi Function ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Identifier ctermfg=darkblue ctermbg=NONE cterm=NONE + hi PreProc ctermfg=darkred ctermbg=NONE cterm=NONE + hi Special ctermfg=darkcyan ctermbg=NONE cterm=bold + hi Statement ctermfg=darkred ctermbg=NONE cterm=bold + hi Underlined ctermfg=darkblue ctermbg=NONE cterm=underline + hi Title ctermfg=NONE ctermbg=NONE cterm=bold + hi Debug ctermfg=darkcyan ctermbg=NONE cterm=NONE + hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE + hi Directory ctermfg=darkcyan ctermbg=NONE cterm=bold + hi Conceal ctermfg=gray ctermbg=NONE cterm=NONE + hi DiffAdd ctermfg=darkgreen ctermbg=NONE cterm=reverse + hi DiffChange ctermfg=darkblue ctermbg=NONE cterm=reverse + hi DiffText ctermfg=darkmagenta ctermbg=NONE cterm=reverse + hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=reverse + hi Added ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi Changed ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi Removed ctermfg=darkred ctermbg=NONE cterm=NONE + hi htmlBold ctermfg=gray ctermbg=NONE cterm=bold + hi htmlItalic ctermfg=gray ctermbg=NONE cterm=underline + hi markdownHeadingDelimiter ctermfg=gray ctermbg=NONE cterm=NONE + hi markdownH1Delimiter ctermfg=darkred ctermbg=NONE cterm=NONE + hi markdownH2Delimiter ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi markdownH4Delimiter ctermfg=darkblue ctermbg=NONE cterm=NONE + hi markdownH6Delimiter ctermfg=darkgreen ctermbg=NONE cterm=NONE + hi markdownH3Delimiter ctermfg=darkyellow ctermbg=NONE cterm=NONE + hi markdownH5Delimiter ctermfg=darkblue ctermbg=NONE cterm=NONE + unlet s:t_Co + finish +endif + +if s:t_Co >= 0 + hi Normal term=NONE + hi ColorColumn term=reverse + hi Conceal term=NONE + hi Cursor term=reverse + hi CursorColumn term=NONE + hi CursorLine term=underline + hi CursorLineNr term=bold + hi DiffAdd term=reverse + hi DiffChange term=NONE + hi DiffDelete term=reverse + hi DiffText term=reverse + hi Directory term=NONE + hi EndOfBuffer term=NONE + hi ErrorMsg term=bold,reverse + hi FoldColumn term=NONE + hi Folded term=NONE + hi IncSearch term=bold,reverse,underline + hi LineNr term=NONE + hi MatchParen term=bold,underline + hi ModeMsg term=bold + hi MoreMsg term=NONE + hi NonText term=NONE + hi Pmenu term=reverse + hi PmenuSbar term=reverse + hi PmenuSel term=bold + hi PmenuThumb term=NONE + hi Question term=standout + hi Search term=reverse + hi SignColumn term=reverse + hi SpecialKey term=bold + hi SpellBad term=underline + hi SpellCap term=underline + hi SpellLocal term=underline + hi SpellRare term=underline + hi StatusLine term=bold,reverse + hi StatusLineNC term=bold,underline + hi TabLine term=bold,underline + hi TabLineFill term=NONE + hi Terminal term=NONE + hi TabLineSel term=bold,reverse + hi Title term=NONE + hi VertSplit term=NONE + hi Visual term=reverse + hi VisualNOS term=NONE + hi WarningMsg term=standout + hi WildMenu term=bold + hi CursorIM term=NONE + hi ToolbarLine term=reverse + hi ToolbarButton term=bold,reverse + hi CurSearch term=reverse + hi CursorLineFold term=underline + hi CursorLineSign term=underline + hi Comment term=bold + hi Constant term=NONE + hi Error term=bold,reverse + hi Identifier term=NONE + hi Ignore term=NONE + hi PreProc term=NONE + hi Special term=NONE + hi Statement term=NONE + hi Todo term=bold,reverse + hi Type term=NONE + hi Underlined term=underline + unlet s:t_Co + finish +endif + +" Background: dark +" Color: color00 #282923 235 black +" Color: color08 #74705d 244 darkgray +" Color: color01 #c61e5c 125 darkred +" Color: color09 #f92672 197 red +" Color: color02 #81af24 106 darkgreen +" Color: color10 #a6e22e 112 green +" Color: color03 #fd971f 208 darkyellow +" Color: color11 #e6db74 185 yellow +" Color: color04 #51aebe 73 darkblue +" Color: color12 #66d9ef 81 blue +" Color: color05 #ae81ff 141 darkmagenta +" Color: color13 #fd5ff0 207 magenta +" Color: color06 #80beb5 73 darkcyan +" Color: color14 #a1efe4 116 cyan +" Color: color07 #bababa 250 gray +" Color: color15 #f8f8f2 255 white +" Color: colorLine #3a392f 237 darkgrey +" Color: colorB #585858 240 darkgrey +" Color: colorF #414141 238 darkgrey +" Color: colorNonT #8a8a8a 245 darkgrey +" Color: colorC #ffaf5f 215 red +" Color: colorlC #5fff00 82 green +" Color: colorV #1f3f5f 109 cyan +" Color: colorMP #fd971f 208 darkyellow +" Color: diffAdd #5faf5f 71 darkgreen +" Color: diffDelete #af5f5f 131 darkred +" Color: diffChange #5f87af 67 darkblue +" Color: diffText #af87af 139 darkmagenta +" Color: black #000000 16 black +" Color: white #dadada 253 white +" Color: Added #5fd75f 77 darkgreen +" Color: Changed #ffaf5f 215 darkyellow +" Color: Removed #d75f5f 167 darkred +" Term colors: color00 color01 color02 color03 color04 color05 color06 color07 +" Term colors: color08 color09 color10 color11 color12 color13 color14 color15 +" vim: et ts=8 sw=2 sts=2 diff --git a/runtime/colors/wildcharm.vim b/runtime/colors/wildcharm.vim index 47ca5a1408..00ebc16529 100644 --- a/runtime/colors/wildcharm.vim +++ b/runtime/colors/wildcharm.vim @@ -4,7 +4,7 @@ " Maintainer: Maxim Kim " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -21,6 +21,7 @@ hi! link LineNrAbove LineNr hi! link LineNrBelow LineNr hi! link MessageWindow PMenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi! link CurSearch IncSearch if &background ==# 'dark' if (has('termguicolors') && &termguicolors) || has('gui_running') @@ -194,6 +195,7 @@ if s:t_Co >= 256 hi! link LineNrBelow LineNr hi! link MessageWindow PMenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi! link CurSearch IncSearch if &background ==# 'dark' hi Normal ctermfg=252 ctermbg=16 cterm=NONE diff --git a/runtime/colors/zaibatsu.vim b/runtime/colors/zaibatsu.vim index 90c6104afe..49047617c4 100644 --- a/runtime/colors/zaibatsu.vim +++ b/runtime/colors/zaibatsu.vim @@ -4,7 +4,7 @@ " Maintainer: Romain Lafourcade " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -96,6 +96,7 @@ hi! link TabLineFill StatusLineNC hi! link TabLineSel StatusLine hi! link Terminal Normal hi! link lCursor Cursor +hi! link PopupSelected PmenuSel hi! link Boolean Constant hi! link Character Constant hi! link Conditional Statement @@ -201,6 +202,7 @@ if s:t_Co >= 256 hi! link TabLineSel StatusLine hi! link Terminal Normal hi! link lCursor Cursor + hi! link PopupSelected PmenuSel hi! link Boolean Constant hi! link Character Constant hi! link Conditional Statement @@ -309,6 +311,7 @@ if s:t_Co >= 16 hi! link TabLineSel StatusLine hi! link Terminal Normal hi! link lCursor Cursor + hi! link PopupSelected PmenuSel hi! link Boolean Constant hi! link Character Constant hi! link Conditional Statement @@ -417,6 +420,7 @@ if s:t_Co >= 8 hi! link TabLineSel StatusLine hi! link Terminal Normal hi! link lCursor Cursor + hi! link PopupSelected PmenuSel hi! link Boolean Constant hi! link Character Constant hi! link Conditional Statement diff --git a/runtime/colors/zellner.vim b/runtime/colors/zellner.vim index 7781ca9ab8..298ec0f700 100644 --- a/runtime/colors/zellner.vim +++ b/runtime/colors/zellner.vim @@ -4,7 +4,7 @@ " Maintainer: Original maintainer Ron Aaron " Website: https://github.com/vim/colorschemes " License: Same as Vim -" Last Change: 2024 Aug 15 +" Last Change: 2025 Jan 07 " Generated by Colortemplate v2.2.3 @@ -31,6 +31,7 @@ hi! link CursorLineFold CursorLine hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo +hi! link PopupSelected PmenuSel hi Normal guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE hi Folded guifg=#00008b guibg=#d3d3d3 gui=NONE cterm=NONE hi CursorLine guifg=NONE guibg=#e5e5e5 gui=NONE cterm=NONE @@ -106,6 +107,7 @@ if s:t_Co >= 256 hi! link CursorLineSign CursorLine hi! link MessageWindow Pmenu hi! link PopupNotification Todo + hi! link PopupSelected PmenuSel hi Normal ctermfg=16 ctermbg=231 cterm=NONE hi Folded ctermfg=18 ctermbg=252 cterm=NONE hi CursorLine ctermfg=NONE ctermbg=254 cterm=NONE -- cgit From 5bae80899d9d29d80c129ca92cde75a1583b5efe Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 14 Jan 2025 12:05:23 +0100 Subject: feat(messages): add :!cmd shell message kinds Also print stderr error messages with ErrorMsg highlight group. --- runtime/doc/news.txt | 2 +- runtime/doc/ui.txt | 3 +++ src/nvim/event/proc.h | 4 ++-- src/nvim/os/shell.c | 12 +++++++----- test/functional/ui/messages_spec.lua | 38 ++++++++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 89504ae244..e12c307b5f 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -96,7 +96,7 @@ EVENTS • `msg_show`: • `history` argument indicating if the message was added to the history. • new message kinds: "bufwrite", "completion", "list_cmd", "lua_print", - "search_cmd", "undo", "verbose", wildlist". + "search_cmd", "shell_out/err/ret", "undo", "verbose", wildlist". HIGHLIGHTS diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 26ea03d2be..4d212e2779 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -805,6 +805,9 @@ must handle. "quickfix" Quickfix navigation message "search_cmd" Entered search command "search_count" Search count message ("S" flag of 'shortmess') + "shell_err" |:!cmd| shell stderr output + "shell_out" |:!cmd| shell stdout output + "shell_ret" |:!cmd| shell return code "undo" |:undo| and |:redo| message "verbose" 'verbose' message "wildlist" 'wildmode' "list" message diff --git a/src/nvim/event/proc.h b/src/nvim/event/proc.h index f525d46f87..fb14041049 100644 --- a/src/nvim/event/proc.h +++ b/src/nvim/event/proc.h @@ -21,8 +21,8 @@ static inline Proc proc_init(Loop *loop, ProcType type, void *data) .argv = NULL, .exepath = NULL, .in = { .closed = false }, - .out = { .s.closed = false }, - .err = { .s.closed = false }, + .out = { .s.closed = false, .s.fd = STDOUT_FILENO }, + .err = { .s.closed = false, .s.fd = STDERR_FILENO }, .cb = NULL, .closed = false, .internal_close_cb = NULL, diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 81b75dc4d3..5347c8db9a 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -700,6 +700,7 @@ int os_call_shell(char *cmd, int opts, char *extra_args) } if (!emsg_silent && exitcode != 0 && !(opts & kShellOptSilent)) { + msg_ext_set_kind("shell_ret"); msg_puts(_("\nshell returned ")); msg_outnum(exitcode); msg_putchar('\n'); @@ -1067,7 +1068,7 @@ static void out_data_ring(const char *output, size_t size) } if (output == NULL && size == SIZE_MAX) { // Print mode - out_data_append_to_screen(last_skipped, &last_skipped_len, true); + out_data_append_to_screen(last_skipped, &last_skipped_len, STDOUT_FILENO, true); return; } @@ -1095,14 +1096,15 @@ static void out_data_ring(const char *output, size_t size) /// @param output Data to append to screen lines. /// @param count Size of data. /// @param eof If true, there will be no more data output. -static void out_data_append_to_screen(const char *output, size_t *count, bool eof) +static void out_data_append_to_screen(const char *output, size_t *count, int fd, bool eof) FUNC_ATTR_NONNULL_ALL { const char *p = output; const char *end = output + *count; + msg_ext_set_kind(fd == STDERR_FILENO ? "shell_err" : "shell_out"); while (p < end) { if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) { - msg_putchar_hl((uint8_t)(*p), 0); + msg_putchar_hl((uint8_t)(*p), fd == STDERR_FILENO ? HLF_E : 0); p++; } else { // Note: this is not 100% precise: @@ -1118,7 +1120,7 @@ static void out_data_append_to_screen(const char *output, size_t *count, bool eo goto end; } - msg_outtrans_len(p, i, 0, false); + msg_outtrans_len(p, i, fd == STDERR_FILENO ? HLF_E : 0, false); p += i; } } @@ -1133,7 +1135,7 @@ static size_t out_data_cb(RStream *stream, const char *ptr, size_t count, void * // Save the skipped output. If it is the final chunk, we display it later. out_data_ring(ptr, count); } else if (count > 0) { - out_data_append_to_screen(ptr, &count, eof); + out_data_append_to_screen(ptr, &count, stream->s.fd, eof); } return count; diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index ea4edefe8a..4d53daa9d6 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -331,6 +331,7 @@ describe('ui/ext_messages', function() }, }) + -- kind=verbose for :verbose messages feed(':1verbose filter Diff[AC] hi') screen:expect({ cmdline = { { @@ -380,6 +381,43 @@ describe('ui/ext_messages', function() }, }, }) + + -- kind=shell for :!cmd messages + local cmd = t.is_os('win') and 'echo stdout& echo stderr>&2& exit 3' + or '{ echo stdout; echo stderr >&2; exit 3; }' + feed((':!%s'):format(cmd)) + screen:expect({ + cmdline = { { + abort = false, + } }, + messages = { + { + content = { { (':!%s\r\n[No write since last change]\n'):format(cmd) } }, + history = false, + kind = '', + }, + { + content = { { ('stdout%s\n'):format(t.is_os('win') and '\r' or '') } }, + history = false, + kind = 'shell_out', + }, + { + content = { { ('stderr%s\n'):format(t.is_os('win') and '\r' or ''), 9, 6 } }, + history = false, + kind = 'shell_err', + }, + { + content = { { '\nshell returned 3\n\n' } }, + history = false, + kind = 'shell_ret', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) end) it(':echoerr', function() -- cgit From d55b17e2b4e061fd8b330f928785a217c99c9d11 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 14 Jan 2025 17:23:11 +0100 Subject: fix(messages): verbose kind for nvim_echo() Problem: No "verbose" kind for nvim_echo() opts->verbose. Solution: Pass NULL "kind" to indicate no new kind. --- src/nvim/api/vim.c | 3 ++- src/nvim/message.c | 12 ++++++++++-- test/functional/ui/messages_spec.lua | 29 +++++++++++++++++------------ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index faf6c0567c..950c70026b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -772,7 +772,8 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) verbose_enter(); } - msg_multihl(hl_msg, opts->err ? "echoerr" : history ? "echomsg" : "echo", history, opts->err); + char *kind = opts->verbose ? NULL : opts->err ? "echoerr" : history ? "echomsg" : "echo"; + msg_multihl(hl_msg, kind, history, opts->err); if (opts->verbose) { verbose_leave(); diff --git a/src/nvim/message.c b/src/nvim/message.c index f87eba27d0..5423446ef9 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -296,6 +296,12 @@ void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_ // Avoid starting a new message for each chunk and adding message to history in msg_keep(). static bool is_multihl = false; +/// Print message chunks, each with their own highlight ID. +/// +/// @param hl_msg Message chunks +/// @param kind Message kind (can be NULL to avoid setting kind) +/// @param history Whether to add message to history +/// @param err Whether to print message as an error void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) { no_wait_return++; @@ -303,7 +309,9 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) msg_clr_eos(); bool need_clear = false; msg_ext_history = history; - msg_ext_set_kind(kind); + if (kind != NULL) { + msg_ext_set_kind(kind); + } is_multihl = true; for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); @@ -312,7 +320,7 @@ void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) } else { msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear); } - assert(!ui_has(kUIMessages) || msg_ext_kind == kind); + assert(!ui_has(kUIMessages) || kind == NULL || msg_ext_kind == kind); } if (history && kv_size(hl_msg)) { add_msg_hist_multihl(NULL, 0, 0, true, hl_msg); diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 4d53daa9d6..b70bd0e808 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -319,9 +319,7 @@ describe('ui/ext_messages', function() -- kind=echoerr for nvim_echo() err feed(':call nvim_echo([["Error"], ["Message", "Special"]], 1, #{ err:1 })') screen:expect({ - cmdline = { { - abort = false, - } }, + cmdline = { { abort = false } }, messages = { { content = { { 'Error', 9, 6 }, { 'Message', 16, 99 } }, @@ -331,12 +329,23 @@ describe('ui/ext_messages', function() }, }) + -- kind=verbose for nvim_echo() verbose + feed(':call nvim_echo([["Verbose Message"]], 1, #{ verbose:1 })') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { 'Verbose Message' } }, + history = true, + kind = 'verbose', + }, + }, + }) + -- kind=verbose for :verbose messages feed(':1verbose filter Diff[AC] hi') screen:expect({ - cmdline = { { - abort = false, - } }, + cmdline = { { abort = false } }, messages = { { content = { @@ -387,9 +396,7 @@ describe('ui/ext_messages', function() or '{ echo stdout; echo stderr >&2; exit 3; }' feed((':!%s'):format(cmd)) screen:expect({ - cmdline = { { - abort = false, - } }, + cmdline = { { abort = false } }, messages = { { content = { { (':!%s\r\n[No write since last change]\n'):format(cmd) } }, @@ -1126,9 +1133,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - cmdline = { { - abort = false, - } }, + cmdline = { { abort = false } }, }) eq(0, eval('&cmdheight')) end) -- cgit From 0a7e4e9e5f28f3b6b3c83040430d0a36fcd71fad Mon Sep 17 00:00:00 2001 From: Andrew Braxton <42975660+andrewbraxton@users.noreply.github.com> Date: Wed, 15 Jan 2025 04:58:36 -0500 Subject: fix(lsp): vim.lsp.enable(...,false) does not disable #32002 Problem: Per the documentation, passing `false` as the `enable` parameter of `vim.lsp.enable()` should disable the given LSP(s), but it does not work due to a logic error. Specifically, `enable == false and nil or {}` will always evaluate to `{}` because `nil` is falsy. Solution: Correct the conditional statement. --- runtime/lua/vim/lsp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 7812f31db1..147d29d6be 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -546,7 +546,7 @@ function lsp.enable(name, enable) if nm == '*' then error('Invalid name') end - lsp._enabled_configs[nm] = enable == false and nil or {} + lsp._enabled_configs[nm] = enable ~= false and {} or nil end if not next(lsp._enabled_configs) then -- cgit From 09bcb310681e3b87d5b8c5eb547b182554cff7b4 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Wed, 15 Jan 2025 12:36:00 +0200 Subject: fix(docs): replace `yxx` mappings with `g==` #31947 Problem: `yx` uses "y" prefix, which shadows a builtin operator. Solution: Use `g=` (in the form of `g==` currently), drawing from precedent of CTRL-= and 'tpope/vim-scriptease'. --- runtime/doc/helphelp.txt | 2 +- runtime/doc/news.txt | 2 +- runtime/doc/terminal.txt | 2 +- runtime/doc/treesitter.txt | 2 +- runtime/doc/various.txt | 4 ++-- runtime/ftplugin/help.lua | 6 +++--- runtime/lua/vim/treesitter/query.lua | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index f7009aebfe..72d37f6088 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -193,7 +193,7 @@ Jump to specific subjects by using tags. This can be done in two ways: Use CTRL-T or CTRL-O to jump back. Use ":q" to close the help window. -Use `yxx` to execute the current Lua/Vimscript code block. +Use `g==` to execute the current Lua/Vimscript code block. If there are several matches for an item you are looking for, this is how you can jump to each one of them: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 8c4130b3f2..f897220374 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -229,7 +229,7 @@ DIAGNOSTICS EDITOR -• Use |yxx| in :help docs to execute Lua and Vimscript code examples. +• Use |g==| in :help docs to execute Lua and Vimscript code examples. • Improved |paste| handling for redo (dot-repeat) and macros (|recording|): • Redoing a large paste is significantly faster and ignores 'autoindent'. • Replaying a macro with |@| also replays pasted text. diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index a7f278990c..0ab7151728 100644 --- a/runtime/doc/terminal.txt +++ b/runtime/doc/terminal.txt @@ -166,7 +166,7 @@ directory indicated in the request. >lua }) To try it out, select the above code and source it with `:'<,'>lua` (or -`yxx`), then run this command in a :terminal buffer: > +`g==`), then run this command in a :terminal buffer: > printf "\033]7;file://./foo/bar\033\\" diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 41679f80ca..83fa2c5a17 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1339,7 +1339,7 @@ parse({lang}, {query}) *vim.treesitter.query.parse()* `info.captures`). • `info.patterns`: information about predicates. - Example (to try it, use `yxx` or select the code then run `:'<,'>lua`): >lua + Example (to try it, use `g==` or select the code then run `:'<,'>lua`): >lua local query = vim.treesitter.query.parse('vimdoc', [[ ; query ((h1) @str diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 611e820cab..662519d415 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -554,8 +554,8 @@ gO Show a filetype-specific, navigable "outline" of the *:sl!* *:sleep!* :[N]sl[eep]! [N][m] Same as above, but hide the cursor. - *yxx* -yxx Executes the current code block. + *g==* +g== Executes the current code block. Works in |help| buffers. diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index ed8f93c71c..479e4d8b9f 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -57,7 +57,7 @@ for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do if name == 'code' then vim.api.nvim_buf_set_extmark(0, run_message_ns, start, 0, { - virt_text = { { 'Run with `yxx`', 'LspCodeLens' } }, + virt_text = { { 'Run with `g==`', 'LspCodeLens' } }, }) local code = vim.treesitter.get_node_text(node, 0) local lang_node = match[metadata[id].lang][1] --[[@as TSNode]] @@ -69,7 +69,7 @@ for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do end end -vim.keymap.set('n', 'yxx', function() +vim.keymap.set('n', 'g==', function() local pos = vim.api.nvim_win_get_cursor(0)[1] local code_block = code_blocks[pos] if not code_block then @@ -82,5 +82,5 @@ vim.keymap.set('n', 'yxx', function() end, { buffer = true }) vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') - .. '\n exe "nunmap gO" | exe "nunmap yxx"' + .. '\n exe "nunmap gO" | exe "nunmap g=="' vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | call v:lua.vim.treesitter.stop()' diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index ad648f36cc..e43d0a8ad4 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -301,7 +301,7 @@ api.nvim_create_autocmd('OptionSet', { --- - `captures`: a list of unique capture names defined in the query (alias: `info.captures`). --- - `info.patterns`: information about predicates. --- ---- Example (to try it, use `yxx` or select the code then run `:'<,'>lua`): +--- Example (to try it, use `g==` or select the code then run `:'<,'>lua`): --- ```lua --- local query = vim.treesitter.query.parse('vimdoc', [[ --- ; query -- cgit From 5cc93ef4729c65d6a539c8d0a8a2bf767cf17ced Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 15 Jan 2025 11:38:45 +0100 Subject: fix(marks): revise metadata for start mark of revalidated pair #32017 Problem: Metadata may be revised for end mark of a revalidated pair. Solution: Revise metadata for start mark of a revalidated pair. --- src/nvim/extmark.c | 27 ++++++++++++++------------- test/functional/api/extmark_spec.lua | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 5b47afa4b2..7e6dfb8dea 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -112,10 +112,10 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool { MarkTreeIter itr[1] = { 0 }; MTKey key = marktree_lookup(buf->b_marktree, mark, itr); - bool move = key.pos.row >= 0 && (key.pos.row != row || key.pos.col != col); - // Already valid keys were being revalidated, presumably when encountering a - // SavePos from a modified mark. Avoid adding that to the decor again. - invalid = invalid && mt_invalid(key); + bool move = key.pos.row != row || key.pos.col != col; + if (key.pos.row < 0 || (!move && !invalid)) { + return; // Mark was deleted or no change needed + } // Only the position before undo needs to be redrawn here, // as the position after undo should be marked as changed. @@ -125,13 +125,16 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool int row1 = 0; int row2 = 0; + MarkTreeIter altitr[1] = { *itr }; + MTKey alt = marktree_get_alt(buf->b_marktree, key, altitr); + if (invalid) { mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID; - marktree_revise_meta(buf->b_marktree, itr, key); - } else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) { - MTPos end = marktree_get_altpos(buf->b_marktree, key, NULL); - row1 = MIN(end.row, MIN(key.pos.row, row)); - row2 = MAX(end.row, MAX(key.pos.row, row)); + mt_itr_rawkey(altitr).flags &= (uint16_t) ~MT_FLAG_INVALID; + marktree_revise_meta(buf->b_marktree, mt_end(key) ? altitr : itr, mt_end(key) ? alt : key); + } else if (!mt_invalid(key) && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) { + row1 = MIN(alt.pos.row, MIN(key.pos.row, row)); + row2 = MAX(alt.pos.row, MAX(key.pos.row, row)); buf_signcols_count_range(buf, row1, row2, 0, kTrue); } @@ -140,10 +143,8 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool } if (invalid) { - MTPos end = marktree_get_altpos(buf->b_marktree, key, itr); - mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID; - buf_put_decor(buf, mt_decor(key), row, end.row); - } else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) { + buf_put_decor(buf, mt_decor(key), MIN(row, key.pos.row), MAX(row, key.pos.row)); + } else if (!mt_invalid(key) && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) { buf_signcols_count_range(buf, row1, row2, 0, kNone); } } diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index c8d6110207..8a4aea1efe 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1794,7 +1794,7 @@ describe('API/extmarks', function() eq({}, get_extmark_by_id(ns, 4, {})) end) - it('no crash checking invalided flag of sign pair end key #31856', function() + it('no crash checking invalidated flag of sign pair end key #31856', function() api.nvim_buf_set_lines(0, 0, 1, false, { '', '' }) api.nvim_set_option_value('signcolumn', 'auto:2', {}) set_extmark(ns, 1, 0, 0, { sign_text = 'S1', invalidate = true, end_row = 0 }) -- cgit From bbf36ef8ef86534e317e4e0153730a40ae4c936e Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 15 Jan 2025 15:55:21 +0100 Subject: fix(cmdline): prevent cmdline_show events after exiting cmdline #32033 Problem: If a (vim.ui_attach) cmdline_hide callback triggers a redraw, it may cause cmdline_show events for an already exited cmdline. Solution: Avoid emitting cmdline_show event when ccline.cmdbuff is already NULL. Unset ccline.cmdbuff before emitting cmdline_hide. --- src/nvim/ex_getln.c | 8 +++-- test/functional/lua/ui_event_spec.lua | 63 +++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 0b5d0864e5..baff795e71 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -951,6 +951,8 @@ theend: kv_destroy(ccline.last_colors.colors); char *p = ccline.cmdbuff; + // Prevent show events triggered by a (vim.ui_attach) hide callback. + ccline.cmdbuff = NULL; if (ui_has(kUICmdline)) { ui_call_cmdline_hide(ccline.level, s->gotesc); @@ -965,8 +967,6 @@ theend: if (did_save_ccline) { restore_cmdline(&save_ccline); - } else { - ccline.cmdbuff = NULL; } return (uint8_t *)p; @@ -3415,6 +3415,10 @@ static void draw_cmdline(int start, int len) static void ui_ext_cmdline_show(CmdlineInfo *line) { + if (line->cmdbuff == NULL) { + return; + } + Arena arena = ARENA_EMPTY; Array content; if (cmdline_star) { diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index af6b2ceac3..c6f98c8640 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -168,18 +168,67 @@ describe('vim.ui_attach', function() vim.ui_attach(ns, { ext_messages = true }, function(ev) if ev == 'msg_show' then vim.schedule(function() vim.cmd.redraw() end) - else - vim.cmd.redraw() + elseif ev:find('cmdline') then + _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) + vim.api.nvim_buf_set_lines(0, 0, -1, false, { tostring(_G.cmdline) }) + vim.cmd('redraw') end - _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) end )]]) + screen:expect([[ + ^ | + {1:~ }|*4 + ]]) feed(':') - n.assert_alive() - eq(2, exec_lua('return _G.cmdline')) - n.assert_alive() + screen:expect({ + grid = [[ + ^1 | + {1:~ }|*4 + ]], + cmdline = { { + content = { { '' } }, + firstc = ':', + pos = 0, + } }, + }) feed('versionv') - n.assert_alive() + screen:expect({ + grid = [[ + ^2 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) + feed([[:call confirm("Save changes?", "&Yes\n&No\n&Cancel")]]) + screen:expect({ + grid = [[ + ^5 | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[Y]es, (N)o, (C)ancel: ', + }, + }, + messages = { + { + content = { { '\nSave changes?\n', 6, 10 } }, + history = false, + kind = 'confirm', + }, + }, + }) + feed('n') + screen:expect({ + grid = [[ + ^5 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) end) it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function() -- cgit From 7c652242579b6f734b57de106afbe1d5c32ed2fd Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 15 Jan 2025 18:18:04 +0100 Subject: build: fix lint error on macos --- src/nvim/event/proc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/event/proc.h b/src/nvim/event/proc.h index fb14041049..cd4d5913ac 100644 --- a/src/nvim/event/proc.h +++ b/src/nvim/event/proc.h @@ -4,6 +4,7 @@ #include #include "nvim/event/defs.h" // IWYU pragma: keep +#include "nvim/os/os_defs.h" #include "nvim/types_defs.h" static inline Proc proc_init(Loop *loop, ProcType type, void *data) -- cgit From f0fdc1de6c950a015ada9550473aedde43b946ce Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 15 Jan 2025 19:24:48 +0100 Subject: build(deps): bump libuv to v1.50.0 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 602d2e2d03..b7a9812676 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -1,5 +1,5 @@ -LIBUV_URL https://github.com/libuv/libuv/archive/v1.49.2.tar.gz -LIBUV_SHA256 388ffcf3370d4cf7c4b3a3205504eea06c4be5f9e80d2ab32d19f8235accc1cf +LIBUV_URL https://github.com/libuv/libuv/archive/v1.50.0.tar.gz +LIBUV_SHA256 b1ec56444ee3f1e10c8bd3eed16ba47016ed0b94fe42137435aaf2e0bd574579 LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/a4f56a459a588ae768801074b46ba0adcfb49eb1.tar.gz LUAJIT_SHA256 b4120332a4191db9c9da2d81f9f11f0d4504fc4cff2dea0f642d3d8f1fcebd0e -- cgit From 524be56042335db589b9fe62dfdae39be3f69a15 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 16 Jan 2025 00:34:25 +0100 Subject: vim-patch:9.1.1019: filetype: fd ignore files are not recognized (#32042) Problem: filetype: fd ignore files are not recognized Solution: detect .fdignore files as gitignore filetype closes: vim/vim#16444 https://github.com/vim/vim/commit/3058087f6f04be788118e94e942e0f0c9fca25f0 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 ++ test/old/testdir/test_filetype.vim | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1960bca52b..e5ba3b1211 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1576,6 +1576,7 @@ local filename = { ['.gitignore'] = 'gitignore', ['.ignore'] = 'gitignore', ['.dockerignore'] = 'gitignore', + ['.fdignore'] = 'gitignore', ['.npmignore'] = 'gitignore', ['.rgignore'] = 'gitignore', ['.vscodeignore'] = 'gitignore', @@ -2244,6 +2245,7 @@ local pattern = { ['^dictd.*%.conf$'] = 'dictdconf', ['/lxqt/.*%.conf$'] = 'dosini', ['/screengrab/.*%.conf$'] = 'dosini', + ['/%.config/fd/ignore$'] = 'gitignore', ['^${GNUPGHOME}/gpg%.conf$'] = 'gpg', ['/boot/grub/grub%.conf$'] = 'grub', ['/hypr/.*%.conf$'] = 'hyprlang', diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 2c6b1bd0f4..6b07c66dc0 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -308,7 +308,7 @@ func s:GetFilenameChecks() abort \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'), \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/config'), - \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore', '.rgignore', '.ignore', '.dockerignore', '.npmignore', '.vscodeignore'], + \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore', '.fdignore', '/.config/fd/ignore', '.ignore', '.rgignore', '.dockerignore', '.npmignore', '.vscodeignore'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], -- cgit From 718e16536052c0e75de61a32ef237a9e87fc03f2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 16 Jan 2025 09:16:25 +0800 Subject: vim-patch:9.1.1018: v9.1.0743 causes regression with diff mode (#32047) Problem: v9.1.0743 causes regression with diff mode Solution: Fix the regression with overlapping regions closes: vim/vim#16454 https://github.com/vim/vim/commit/01f6509fb2de1627cc4ec2c109cd0aa2e3346d50 Co-authored-by: Yukihiro Nakadaira --- src/nvim/diff.c | 10 ++++++++++ test/functional/ui/diff_spec.lua | 20 ++++++++++++++++++++ test/old/testdir/test_diffmode.vim | 6 ++++++ 3 files changed, 36 insertions(+) diff --git a/src/nvim/diff.c b/src/nvim/diff.c index bd98a31a71..99f70793b3 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1622,6 +1622,11 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne } else { // second overlap of new block with existing block dp->df_count[idx_new] += (linenr_T)hunk->count_new; + if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1) + > curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) { + dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count + - dp->df_lnum[idx_new] + 1; + } } // Adjust the size of the block to include all the lines to the @@ -1632,6 +1637,11 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne if (off < 0) { // new change ends in existing block, adjust the end dp->df_count[idx_new] += -off; + if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1) + > curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) { + dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count + - dp->df_lnum[idx_new] + 1; + } off = 0; } diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 95159011f1..dae373297a 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -2044,6 +2044,26 @@ it('diff mode overlapped diff blocks will be merged', function() {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| | ]]) + + WriteDiffFiles3('a\nb\nc', 'd\ne', 'b\nf') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:d}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:e}{4: }│{7: }{27:f}{4: }| + {7: }{22:c }│{7: }{23:---------}│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'd\ne', 'b') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:d}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:e}{4: }│{7: }{23:---------}| + {7: }{22:c }│{7: }{23:---------}│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) end) -- oldtest: Test_diff_topline_noscroll() diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 880286d329..695aeeac75 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -2004,6 +2004,12 @@ func Test_diff_overlapped_diff_blocks_will_be_merged() call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "b", "y", "c"]) call VerifyBoth(buf, "Test_diff_overlapped_3.37", "") + call WriteDiffFiles3(buf, ["a", "b", "c"], ["d", "e"], ["b", "f"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.38", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["d", "e"], ["b"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.39", "") + call StopVimInTerminal(buf) endfunc -- cgit From f8680d009741d01e137aeb2232aa7e033cd70d7b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 16 Jan 2025 09:27:08 +0800 Subject: vim-patch:9.1.1013: Vim9: Regression caused by patch v9.1.0646 Problem: Vim9: Regression caused by patch v9.1.0646 Solution: Translate the function name before invoking it in call() (Yegappan Lakshmanan) fixes: vim/vim#16430 closes: vim/vim#16445 https://github.com/vim/vim/commit/6289f9159102e0855bedc566636b5e7ca6ced72c N/A patch: vim-patch:8.2.4176: Vim9: cannot use imported function with call() Co-authored-by: Yegappan Lakshmanan --- src/nvim/errors.h | 1 + src/nvim/eval/funcs.c | 15 +++++++++++---- src/nvim/eval/userfunc.c | 1 - test/functional/vimscript/ctx_functions_spec.lua | 7 +++++-- test/old/testdir/test_user_func.vim | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/nvim/errors.h b/src/nvim/errors.h index df94945a3d..baea005f15 100644 --- a/src/nvim/errors.h +++ b/src/nvim/errors.h @@ -129,6 +129,7 @@ EXTERN const char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s")); EXTERN const char e_empty_buffer[] INIT(= N_("E749: Empty buffer")); EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist")); +EXTERN const char e_unknown_function_str[] INIT(= N_("E117: Unknown function: %s")); EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function")); EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter")); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ed3efca0d7..0d2653ef21 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -575,22 +575,29 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (func == NULL || *func == NUL) { return; // type error, empty name or null function } + char *p = func; + char *tofree = trans_function_name(&p, false, TFN_INT|TFN_QUIET, NULL, NULL); + if (tofree == NULL) { + emsg_funcname(e_unknown_function_str, func); + return; + } + func = tofree; dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { if (tv_check_for_dict_arg(argvars, 2) == FAIL) { - if (owned) { - func_unref(func); - } - return; + goto done; } selfdict = argvars[2].vval.v_dict; } func_call(func, &argvars[1], partial, selfdict, rettv); + +done: if (owned) { func_unref(func); } + xfree(tofree); } /// "changenr()" function diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 4f1098632c..a24a3d5622 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -78,7 +78,6 @@ static funccall_T *current_funccal = NULL; // item in it is still being used. static funccall_T *previous_funccal = NULL; -static const char *e_unknown_function_str = N_("E117: Unknown function: %s"); static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); static const char *e_funcdict = N_("E717: Dictionary entry already exists"); static const char *e_funcref = N_("E718: Funcref required"); diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index 873e4f820d..85a74c0ab6 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -188,7 +188,10 @@ describe('context functions', function() function RestoreFuncs() call ctxpop() endfunction + + let g:sid = expand('') ]]) + local sid = api.nvim_get_var('sid') eq('Hello, World!', exec_capture([[call Greet('World')]])) eq( @@ -200,11 +203,11 @@ describe('context functions', function() call('DeleteSFuncs') eq( - 'function Greet, line 1: Vim(call):E117: Unknown function: s:greet', + ('function Greet, line 1: Vim(call):E117: Unknown function: %sgreet'):format(sid), pcall_err(command, [[call Greet('World')]]) ) eq( - 'function GreetAll, line 1: Vim(call):E117: Unknown function: s:greet_all', + ('function GreetAll, line 1: Vim(call):E117: Unknown function: %sgreet_all'):format(sid), pcall_err(command, [[call GreetAll('World', 'One', 'Two', 'Three')]]) ) diff --git a/test/old/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim index 3c24412eb7..b509b03778 100644 --- a/test/old/testdir/test_user_func.vim +++ b/test/old/testdir/test_user_func.vim @@ -380,7 +380,7 @@ func Test_script_local_func() " Try to call a script local function in global scope let lines =<< trim [CODE] :call assert_fails('call s:Xfunc()', 'E81:') - :call assert_fails('let x = call("Xfunc", [])', 'E120:') + :call assert_fails('let x = call("Xfunc", [])', ['E81:', 'E117:']) :call writefile(v:errors, 'Xresult') :qall -- cgit From 47a4e4239203fe96d404874bdc1ea6910f72b695 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 16 Jan 2025 09:35:52 +0800 Subject: vim-patch:9.1.1017: Vim9: Patch 9.1.1013 causes a few problems Problem: Vim9: Patch 9.1.1013 causes a few problems Solution: Translate the function name only when it is a string (Yegappan Lakshmanan) fixes: vim/vim#16453 closes: vim/vim#16450 https://github.com/vim/vim/commit/9904cbca4132f7376246a1a31305eb53e9530023 Cherry-pick call() change from patch 9.0.0345. Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/funcs.c | 18 ++++++++++-------- test/old/testdir/test_functions.vim | 4 +++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0d2653ef21..c125bd8893 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -548,8 +548,7 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "call(func, arglist [, dict])" function static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[1].v_type != VAR_LIST) { - emsg(_(e_listreq)); + if (tv_check_for_list_arg(argvars, 1) == FAIL) { return; } if (argvars[1].vval.v_list == NULL) { @@ -575,13 +574,16 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (func == NULL || *func == NUL) { return; // type error, empty name or null function } - char *p = func; - char *tofree = trans_function_name(&p, false, TFN_INT|TFN_QUIET, NULL, NULL); - if (tofree == NULL) { - emsg_funcname(e_unknown_function_str, func); - return; + char *tofree = NULL; + if (argvars[0].v_type == VAR_STRING) { + char *p = func; + tofree = trans_function_name(&p, false, TFN_INT|TFN_QUIET, NULL, NULL); + if (tofree == NULL) { + emsg_funcname(e_unknown_function_str, func); + return; + } + func = tofree; } - func = tofree; dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index 327ea98e1c..01e6001dcc 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -2672,7 +2672,9 @@ endfunc func Test_call() call assert_equal(3, call('len', [123])) call assert_equal(3, 'len'->call([123])) - call assert_fails("call call('len', 123)", 'E714:') + call assert_equal(4, call({ x -> len(x) }, ['xxxx'])) + call assert_equal(2, call(function('len'), ['xx'])) + call assert_fails("call call('len', 123)", 'E1211:') call assert_equal(0, call('', [])) call assert_equal(0, call('len', v:_null_list)) -- cgit From fb564ddff0b4ec9dad5afa7548777af1c3044273 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 16 Jan 2025 20:53:17 +0000 Subject: refactor(options): generic expand and did_set callbacks (#32011) * refactor(options): generic expand and did_set callbacks Problem: Many options have similar callbacks to check the values are valid. Solution: Generalize these callbacks into a single function that reads the option table. * refactor: gen_options.lua refactor: gen_options.lua - inline get_cond * refactor(options): use a simpler format for the common default --- runtime/lua/vim/_meta/options.lua | 2 +- src/nvim/generators/gen_options.lua | 662 ++++++++++++++++-------------- src/nvim/generators/hashy.lua | 2 +- src/nvim/option.c | 1 + src/nvim/option_defs.h | 5 + src/nvim/options.lua | 788 ++++++++++++++++++------------------ src/nvim/optionstr.c | 758 +++++----------------------------- 7 files changed, 845 insertions(+), 1373 deletions(-) diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 14f252516a..107b1ffdfb 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -4947,7 +4947,7 @@ vim.wo.rl = vim.wo.rightleft --- This is useful for languages such as Hebrew, Arabic and Farsi. --- The 'rightleft' option must be set for 'rightleftcmd' to take effect. --- ---- @type 'search' +--- @type string vim.o.rightleftcmd = "search" vim.o.rlc = vim.o.rightleftcmd vim.wo.rightleftcmd = vim.o.rightleftcmd diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 0298381ece..e5dba90925 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -1,237 +1,30 @@ -local options_file = arg[1] -local options_enum_file = arg[2] -local options_map_file = arg[3] -local option_vars_file = arg[4] - -local opt_fd = assert(io.open(options_file, 'w')) -local opt_enum_fd = assert(io.open(options_enum_file, 'w')) -local opt_map_fd = assert(io.open(options_map_file, 'w')) -local opt_vars_fd = assert(io.open(option_vars_file, 'w')) - -local w = function(s) - if s:match('^ %.') then - opt_fd:write(s .. ',\n') - else - opt_fd:write(s .. '\n') - end -end - ---- @param s string -local function enum_w(s) - opt_enum_fd:write(s .. '\n') -end - ---- @param s string -local function map_w(s) - opt_map_fd:write(s .. '\n') -end - -local function vars_w(s) - opt_vars_fd:write(s .. '\n') -end - --- @module 'nvim.options' local options = require('options') local options_meta = options.options - local cstr = options.cstr local valid_scopes = options.valid_scopes ---- Options for each scope. ---- @type table -local scope_options = {} -for _, scope in ipairs(valid_scopes) do - scope_options[scope] = {} +--- @param o vim.option_meta +--- @return string +local function get_values_var(o) + return ('opt_%s_values'):format(o.abbreviation or o.full_name) end --- @param s string --- @return string -local lowercase_to_titlecase = function(s) +local function lowercase_to_titlecase(s) return table.concat(vim.tbl_map(function(word) --- @param word string return word:sub(1, 1):upper() .. word:sub(2) end, vim.split(s, '[-_]'))) end --- Generate options enum file -enum_w('// IWYU pragma: private, include "nvim/option_defs.h"') -enum_w('') - ---- Map of option name to option index ---- @type table -local option_index = {} - --- Generate option index enum and populate the `option_index` and `scope_option` dicts. -enum_w('typedef enum {') -enum_w(' kOptInvalid = -1,') - -for i, o in ipairs(options_meta) do - local enum_val_name = 'kOpt' .. lowercase_to_titlecase(o.full_name) - enum_w((' %s = %u,'):format(enum_val_name, i - 1)) - - option_index[o.full_name] = enum_val_name - - if o.abbreviation then - option_index[o.abbreviation] = enum_val_name - end - - if o.alias then - o.alias = type(o.alias) == 'string' and { o.alias } or o.alias - - for _, v in ipairs(o.alias) do - option_index[v] = enum_val_name - end - end - - for _, scope in ipairs(o.scope) do - table.insert(scope_options[scope], o) - end -end - -enum_w(' // Option count') -enum_w('#define kOptCount ' .. tostring(#options_meta)) -enum_w('} OptIndex;') - --- @param scope string --- @param option_name string --- @return string -local get_scope_option = function(scope, option_name) +local function get_scope_option(scope, option_name) return ('k%sOpt%s'):format(lowercase_to_titlecase(scope), lowercase_to_titlecase(option_name)) end --- Generate option index enum for each scope -for _, scope in ipairs(valid_scopes) do - enum_w('') - - local scope_name = lowercase_to_titlecase(scope) - enum_w('typedef enum {') - enum_w((' %s = -1,'):format(get_scope_option(scope, 'Invalid'))) - - for idx, option in ipairs(scope_options[scope]) do - enum_w((' %s = %u,'):format(get_scope_option(scope, option.full_name), idx - 1)) - end - - enum_w((' // %s option count'):format(scope_name)) - enum_w(('#define %s %d'):format(get_scope_option(scope, 'Count'), #scope_options[scope])) - enum_w(('} %sOptIndex;'):format(scope_name)) -end - --- Generate reverse lookup from option scope index to option index for each scope. -for _, scope in ipairs(valid_scopes) do - enum_w('') - enum_w(('EXTERN const OptIndex %s_opt_idx[] INIT( = {'):format(scope)) - for _, option in ipairs(scope_options[scope]) do - local idx = option_index[option.full_name] - enum_w((' [%s] = %s,'):format(get_scope_option(scope, option.full_name), idx)) - end - enum_w('});') -end - -opt_enum_fd:close() - --- Generate option index map. -local hashy = require('generators.hashy') -local neworder, hashfun = hashy.hashy_hash('find_option', vim.tbl_keys(option_index), function(idx) - return ('option_hash_elems[%s].name'):format(idx) -end) - -map_w('static const struct { const char *name; OptIndex opt_idx; } option_hash_elems[] = {') - -for _, name in ipairs(neworder) do - assert(option_index[name] ~= nil) - map_w((' { .name = "%s", .opt_idx = %s },'):format(name, option_index[name])) -end - -map_w('};\n') -map_w('static ' .. hashfun) - -opt_map_fd:close() - -vars_w('// IWYU pragma: private, include "nvim/option_vars.h"') - --- Generate enums for option flags. -for _, option in ipairs(options_meta) do - if option.flags and (type(option.flags) == 'table' or option.values) then - vars_w('') - vars_w('typedef enum {') - - local opt_name = lowercase_to_titlecase(option.abbreviation or option.full_name) - --- @type table - local enum_values - - if type(option.flags) == 'table' then - enum_values = option.flags --[[ @as table ]] - else - enum_values = {} - for i, flag_name in ipairs(option.values) do - assert(type(flag_name) == 'string') - enum_values[flag_name] = math.pow(2, i - 1) - end - end - - -- Sort the keys by the flag value so that the enum can be generated in order. - --- @type string[] - local flag_names = vim.tbl_keys(enum_values) - table.sort(flag_names, function(a, b) - return enum_values[a] < enum_values[b] - end) - - for _, flag_name in pairs(flag_names) do - vars_w( - (' kOpt%sFlag%s = 0x%02x,'):format( - opt_name, - lowercase_to_titlecase(flag_name:gsub(':$', '')), - enum_values[flag_name] - ) - ) - end - - vars_w(('} Opt%sFlags;'):format(opt_name)) - end -end - --- Generate valid values for each option. -for _, option in ipairs(options_meta) do - --- @type function - local preorder_traversal - --- @param prefix string - --- @param values vim.option_valid_values - preorder_traversal = function(prefix, values) - vars_w('') - vars_w( - ('EXTERN const char *(%s_values[%s]) INIT( = {'):format(prefix, #vim.tbl_keys(values) + 1) - ) - - --- @type [string,vim.option_valid_values][] - local children = {} - - for _, value in ipairs(values) do - if type(value) == 'string' then - vars_w((' "%s",'):format(value)) - else - assert(type(value) == 'table' and type(value[1]) == 'string' and type(value[2]) == 'table') - - vars_w((' "%s",'):format(value[1])) - table.insert(children, value) - end - end - - vars_w(' NULL') - vars_w('});') - - for _, value in pairs(children) do - -- Remove trailing colon from the added prefix to prevent syntax errors. - preorder_traversal(prefix .. '_' .. value[1]:gsub(':$', ''), value[2]) - end - end - - -- Since option values can be nested, we need to do preorder traversal to generate the values. - if option.values then - preorder_traversal(('opt_%s'):format(option.abbreviation or option.full_name), option.values) - end -end - -opt_vars_fd:close() - local redraw_flags = { ui_option = 'kOptFlagUIOption', tabline = 'kOptFlagRedrTabl', @@ -255,28 +48,29 @@ local list_flags = { --- @param o vim.option_meta --- @return string local function get_flags(o) - --- @type string - local flags = '0' + --- @type string[] + local flags = { '0' } --- @param f string - local add_flag = function(f) - flags = flags .. '|' .. f + local function add_flag(f) + table.insert(flags, f) end if o.list then add_flag(list_flags[o.list]) end - if o.redraw then - for _, r_flag in ipairs(o.redraw) do - add_flag(redraw_flags[r_flag]) - end + + for _, r_flag in ipairs(o.redraw or {}) do + add_flag(redraw_flags[r_flag]) end + if o.expand then add_flag('kOptFlagExpand') if o.expand == 'nodefault' then add_flag('kOptFlagNoDefExp') end end + for _, flag_desc in ipairs({ { 'nodefault', 'NoDefault' }, { 'no_mkrc', 'NoMkrc' }, @@ -291,13 +85,14 @@ local function get_flags(o) { 'modelineexpr', 'MLE' }, { 'func' }, }) do - local key_name = flag_desc[1] - local def_name = 'kOptFlag' .. (flag_desc[2] or lowercase_to_titlecase(key_name)) + local key_name, flag_suffix = flag_desc[1], flag_desc[2] if o[key_name] then + local def_name = 'kOptFlag' .. (flag_suffix or lowercase_to_titlecase(key_name)) add_flag(def_name) end end - return flags + + return table.concat(flags, '|') end --- @param opt_type vim.option_type @@ -341,35 +136,15 @@ local function get_scope_idx(o) return ('{\n%s\n }'):format(table.concat(strs, ',\n')) end ---- @param c string|string[] ---- @param base_string? string ---- @return string -local function get_cond(c, base_string) - local cond_string = base_string or '#if ' - if type(c) == 'table' then - cond_string = cond_string .. get_cond(c[1], '') - for i, subc in ipairs(c) do - if i > 1 then - cond_string = cond_string .. ' && ' .. get_cond(subc, '') - end - end - elseif c:sub(1, 1) == '!' then - cond_string = cond_string .. '!defined(' .. c:sub(2) .. ')' - else - cond_string = cond_string .. 'defined(' .. c .. ')' - end - return cond_string -end - --- @param s string --- @return string -local static_cstr_as_string = function(s) +local function static_cstr_as_string(s) return ('{ .data = %s, .size = sizeof(%s) - 1 }'):format(s, s) end --- @param v vim.option_value|function --- @return string -local get_opt_val = function(v) +local function get_opt_val(v) --- @type vim.option_type local v_type @@ -387,6 +162,7 @@ local get_opt_val = function(v) elseif v_type == 'number' then v = ('%iL'):format(v) elseif v_type == 'string' then + --- @cast v string v = static_cstr_as_string(cstr(v)) end end @@ -397,7 +173,7 @@ end --- @param d vim.option_value|function --- @param n string --- @return string -local get_defaults = function(d, n) +local function get_defaults(d, n) if d == nil then error("option '" .. n .. "' should have a default value") end @@ -406,84 +182,354 @@ end --- @param i integer --- @param o vim.option_meta -local function dump_option(i, o) - w(' [' .. ('%u'):format(i - 1) .. ']={') - w(' .fullname=' .. cstr(o.full_name)) +--- @param write fun(...: string) +local function dump_option(i, o, write) + write(' [', ('%u'):format(i - 1) .. ']={') + write(' .fullname=', cstr(o.full_name)) if o.abbreviation then - w(' .shortname=' .. cstr(o.abbreviation)) + write(' .shortname=', cstr(o.abbreviation)) end - w(' .type=' .. opt_type_enum(o.type)) - w(' .flags=' .. get_flags(o)) - w(' .scope_flags=' .. get_scope_flags(o)) - w(' .scope_idx=' .. get_scope_idx(o)) + write(' .type=', opt_type_enum(o.type)) + write(' .flags=', get_flags(o)) + write(' .scope_flags=', get_scope_flags(o)) + write(' .scope_idx=', get_scope_idx(o)) + write(' .values=', (o.values and get_values_var(o) or 'NULL')) + write(' .values_len=', (o.values and #o.values or '0')) + write(' .flags_var=', (o.flags_varname and ('&%s'):format(o.flags_varname) or 'NULL')) if o.enable_if then - w(get_cond(o.enable_if)) + write(('#if defined(%s)'):format(o.enable_if)) end local is_window_local = #o.scope == 1 and o.scope[1] == 'win' - if not is_window_local then - if o.varname then - w(' .var=&' .. o.varname) - elseif o.immutable then - -- Immutable options can directly point to the default value. - w((' .var=&options[%u].def_val.data'):format(i - 1)) - else - -- Option must be immutable or have a variable. - assert(false) - end + if is_window_local then + write(' .var=NULL') + elseif o.varname then + write(' .var=&', o.varname) + elseif o.immutable then + -- Immutable options can directly point to the default value. + write((' .var=&options[%u].def_val.data'):format(i - 1)) else - w(' .var=NULL') - end - w(' .immutable=' .. (o.immutable and 'true' or 'false')) - if o.cb then - w(' .opt_did_set_cb=' .. o.cb) - end - if o.expand_cb then - w(' .opt_expand_cb=' .. o.expand_cb) + error('Option must be immutable or have a variable.') end + + write(' .immutable=', (o.immutable and 'true' or 'false')) + write(' .opt_did_set_cb=', o.cb or 'NULL') + write(' .opt_expand_cb=', o.expand_cb or 'NULL') + if o.enable_if then - w('#else') + write('#else') -- Hidden option directly points to default value. - w((' .var=&options[%u].def_val.data'):format(i - 1)) + write((' .var=&options[%u].def_val.data'):format(i - 1)) -- Option is always immutable on the false branch of `enable_if`. - w(' .immutable=true') - w('#endif') + write(' .immutable=true') + write('#endif') end - if o.defaults then - if o.defaults.condition then - w(get_cond(o.defaults.condition)) + + if not o.defaults then + write(' .def_val=NIL_OPTVAL') + elseif o.defaults.condition then + write(('#if defined(%s)'):format(o.defaults.condition)) + write(' .def_val=', get_defaults(o.defaults.if_true, o.full_name)) + if o.defaults.if_false then + write('#else') + write(' .def_val=', get_defaults(o.defaults.if_false, o.full_name)) end - w(' .def_val=' .. get_defaults(o.defaults.if_true, o.full_name)) - if o.defaults.condition then - if o.defaults.if_false then - w('#else') - w(' .def_val=' .. get_defaults(o.defaults.if_false, o.full_name)) - end - w('#endif') + write('#endif') + else + write(' .def_val=', get_defaults(o.defaults.if_true, o.full_name)) + end + + write(' },') +end + +--- @param prefix string +--- @param values vim.option_valid_values +local function preorder_traversal(prefix, values) + local out = {} --- @type string[] + + local function add(s) + table.insert(out, s) + end + + add('') + add(('EXTERN const char *(%s_values[%s]) INIT( = {'):format(prefix, #vim.tbl_keys(values) + 1)) + + --- @type [string,vim.option_valid_values][] + local children = {} + + for _, value in ipairs(values) do + if type(value) == 'string' then + add((' "%s",'):format(value)) + else + assert(type(value) == 'table' and type(value[1]) == 'string' and type(value[2]) == 'table') + add((' "%s",'):format(value[1])) + table.insert(children, value) end + end + + add(' NULL') + add('});') + + for _, value in pairs(children) do + -- Remove trailing colon from the added prefix to prevent syntax errors. + add(preorder_traversal(prefix .. '_' .. value[1]:gsub(':$', ''), value[2])) + end + + return table.concat(out, '\n') +end + +--- @param o vim.option_meta +--- @return string +local function gen_opt_enum(o) + local out = {} --- @type string[] + + local function add(s) + table.insert(out, s) + end + + add('') + add('typedef enum {') + + local opt_name = lowercase_to_titlecase(o.abbreviation or o.full_name) + --- @type table + local enum_values + + if type(o.flags) == 'table' then + enum_values = o.flags --[[ @as table ]] else - w(' .def_val=NIL_OPTVAL') + enum_values = {} + for i, flag_name in ipairs(o.values) do + assert(type(flag_name) == 'string') + enum_values[flag_name] = math.pow(2, i - 1) + end + end + + -- Sort the keys by the flag value so that the enum can be generated in order. + --- @type string[] + local flag_names = vim.tbl_keys(enum_values) + table.sort(flag_names, function(a, b) + return enum_values[a] < enum_values[b] + end) + + for _, flag_name in pairs(flag_names) do + add( + (' kOpt%sFlag%s = 0x%02x,'):format( + opt_name, + lowercase_to_titlecase(flag_name:gsub(':$', '')), + enum_values[flag_name] + ) + ) + end + + add(('} Opt%sFlags;'):format(opt_name)) + + return table.concat(out, '\n') +end + +--- @param output_file string +--- @return table options_index Map of option name to option index +local function gen_enums(output_file) + --- Options for each scope. + --- @type table + local scope_options = {} + for _, scope in ipairs(valid_scopes) do + scope_options[scope] = {} + end + + local fd = assert(io.open(output_file, 'w')) + + --- @param s string + local function write(s) + fd:write(s) + fd:write('\n') + end + + -- Generate options enum file + write('// IWYU pragma: private, include "nvim/option_defs.h"') + write('') + + --- Map of option name to option index + --- @type table + local option_index = {} + + -- Generate option index enum and populate the `option_index` and `scope_option` dicts. + write('typedef enum {') + write(' kOptInvalid = -1,') + + for i, o in ipairs(options_meta) do + local enum_val_name = 'kOpt' .. lowercase_to_titlecase(o.full_name) + write((' %s = %u,'):format(enum_val_name, i - 1)) + + option_index[o.full_name] = enum_val_name + + if o.abbreviation then + option_index[o.abbreviation] = enum_val_name + end + + local alias = o.alias or {} --[[@as string[] ]] + for _, v in ipairs(alias) do + option_index[v] = enum_val_name + end + + for _, scope in ipairs(o.scope) do + table.insert(scope_options[scope], o) + end + end + + write(' // Option count') + write('#define kOptCount ' .. tostring(#options_meta)) + write('} OptIndex;') + + -- Generate option index enum for each scope + for _, scope in ipairs(valid_scopes) do + write('') + + local scope_name = lowercase_to_titlecase(scope) + write('typedef enum {') + write((' %s = -1,'):format(get_scope_option(scope, 'Invalid'))) + + for idx, option in ipairs(scope_options[scope]) do + write((' %s = %u,'):format(get_scope_option(scope, option.full_name), idx - 1)) + end + + write((' // %s option count'):format(scope_name)) + write(('#define %s %d'):format(get_scope_option(scope, 'Count'), #scope_options[scope])) + write(('} %sOptIndex;'):format(scope_name)) + end + + -- Generate reverse lookup from option scope index to option index for each scope. + for _, scope in ipairs(valid_scopes) do + write('') + write(('EXTERN const OptIndex %s_opt_idx[] INIT( = {'):format(scope)) + for _, option in ipairs(scope_options[scope]) do + local idx = option_index[option.full_name] + write((' [%s] = %s,'):format(get_scope_option(scope, option.full_name), idx)) + end + write('});') + end + + fd:close() + + return option_index +end + +--- @param output_file string +--- @param option_index table +local function gen_map(output_file, option_index) + -- Generate option index map. + local hashy = require('generators.hashy') + + local neworder, hashfun = hashy.hashy_hash( + 'find_option', + vim.tbl_keys(option_index), + function(idx) + return ('option_hash_elems[%s].name'):format(idx) + end + ) + + local fd = assert(io.open(output_file, 'w')) + + --- @param s string + local function write(s) + fd:write(s) + fd:write('\n') + end + + write('static const struct { const char *name; OptIndex opt_idx; } option_hash_elems[] = {') + + for _, name in ipairs(neworder) do + assert(option_index[name] ~= nil) + write((' { .name = "%s", .opt_idx = %s },'):format(name, option_index[name])) + end + + write('};') + write('') + write('static ' .. hashfun) + + fd:close() +end + +--- @param output_file string +local function gen_vars(output_file) + local fd = assert(io.open(output_file, 'w')) + + --- @param s string + local function write(s) + fd:write(s) + fd:write('\n') + end + + write('// IWYU pragma: private, include "nvim/option_vars.h"') + + -- Generate enums for option flags. + for _, o in ipairs(options_meta) do + if o.flags and (type(o.flags) == 'table' or o.values) then + write(gen_opt_enum(o)) + end + end + + -- Generate valid values for each option. + for _, option in ipairs(options_meta) do + -- Since option values can be nested, we need to do preorder traversal to generate the values. + if option.values then + local values_var = ('opt_%s'):format(option.abbreviation or option.full_name) + write(preorder_traversal(values_var, option.values)) + end end - w(' },') + + fd:close() +end + +--- @param output_file string +local function gen_options(output_file) + local fd = assert(io.open(output_file, 'w')) + + --- @param ... string + local function write(...) + local s = table.concat({ ... }, '') + fd:write(s) + if s:match('^ %.') then + fd:write(',') + end + fd:write('\n') + end + + -- Generate options[] array. + write([[ + #include "nvim/ex_docmd.h" + #include "nvim/ex_getln.h" + #include "nvim/insexpand.h" + #include "nvim/mapping.h" + #include "nvim/ops.h" + #include "nvim/option.h" + #include "nvim/optionstr.h" + #include "nvim/quickfix.h" + #include "nvim/runtime.h" + #include "nvim/tag.h" + #include "nvim/window.h" + + static vimoption_T options[] = {]]) + + for i, o in ipairs(options_meta) do + dump_option(i, o, write) + end + + write('};') + + fd:close() end --- Generate options[] array. -w([[ -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/insexpand.h" -#include "nvim/mapping.h" -#include "nvim/ops.h" -#include "nvim/option.h" -#include "nvim/optionstr.h" -#include "nvim/quickfix.h" -#include "nvim/runtime.h" -#include "nvim/tag.h" -#include "nvim/window.h" - -static vimoption_T options[] = {]]) -for i, o in ipairs(options.options) do - dump_option(i, o) +local function main() + local options_file = arg[1] + local options_enum_file = arg[2] + local options_map_file = arg[3] + local option_vars_file = arg[4] + + local option_index = gen_enums(options_enum_file) + gen_map(options_map_file, option_index) + gen_vars(option_vars_file) + gen_options(options_file) end -w('};') + +main() diff --git a/src/nvim/generators/hashy.lua b/src/nvim/generators/hashy.lua index ea35064962..74b7655324 100644 --- a/src/nvim/generators/hashy.lua +++ b/src/nvim/generators/hashy.lua @@ -55,7 +55,7 @@ function M.build_pos_hash(strings) end function M.switcher(put, tab, maxlen, worst_buck_size) - local neworder = {} + local neworder = {} --- @type string[] put ' switch (len) {\n' local bucky = worst_buck_size > 1 for len = 1, maxlen do diff --git a/src/nvim/option.c b/src/nvim/option.c index 593ac62172..073a816d0c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5715,6 +5715,7 @@ int ExpandStringSetting(expand_T *xp, regmatch_T *regmatch, int *numMatches, cha optexpand_T args = { .oe_varp = get_varp_scope(&options[expand_option_idx], expand_option_flags), + .oe_idx = expand_option_idx, .oe_append = expand_option_append, .oe_regmatch = regmatch, .oe_xp = xp, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 2b51547004..7dd4bd2bc7 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -134,6 +134,7 @@ typedef const char *(*opt_did_set_cb_T)(optset_T *args); typedef struct { /// Pointer to the option variable. It's always a string. char *oe_varp; + OptIndex oe_idx; /// The original option value, escaped. char *oe_opt_value; @@ -174,9 +175,13 @@ typedef struct { void *var; ///< global option: pointer to variable; ///< window-local option: NULL; ///< buffer-local option: global value + unsigned *flags_var; ssize_t scope_idx[kOptScopeSize]; ///< index of option at every scope. bool immutable; ///< option is immutable, trying to set it will give an error. + const char **values; ///< possible values for string options + const size_t values_len; ///< length of values array + /// callback function to invoke after an option is modified to validate and /// apply the new value. opt_did_set_cb_T opt_did_set_cb; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2425dcb93e..ee8034a871 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7,13 +7,14 @@ --- @field alias? string|string[] --- @field short_desc? string|fun(): string --- @field varname? string +--- @field flags_varname? string --- @field type vim.option_type --- @field immutable? boolean --- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma' --- @field scope vim.option_scope[] --- @field deny_duplicates? boolean --- @field enable_if? string ---- @field defaults? vim.option_defaults +--- @field defaults? vim.option_defaults|vim.option_value|fun(): string --- @field values? vim.option_valid_values --- @field flags? true|table --- @field secure? true @@ -29,7 +30,11 @@ --- @field no_mkrc? true --- @field alloced? true --- @field redraw? vim.option_redraw[] +--- +--- If not provided and `values` is present, then is set to 'did_set_str_generic' --- @field cb? string +--- +--- If not provided and `values` is present, then is set to 'expand_set_str_generic' --- @field expand_cb? string --- @field tags? string[] @@ -37,14 +42,14 @@ --- @field condition? string --- string: #ifdef string --- !string: #ifndef string ---- @field if_true integer|boolean|string|fun(): string ---- @field if_false? integer|boolean|string +--- @field if_true vim.option_value|fun(): string +--- @field if_false? vim.option_value --- @field doc? string Default to show in options.txt ---- @field meta? integer|boolean|string Default to use in Lua meta files +--- @field meta? string Default to use in Lua meta files --- @alias vim.option_scope 'global'|'buf'|'win' --- @alias vim.option_type 'boolean'|'number'|'string' ---- @alias vim.option_value boolean|number|string +--- @alias vim.option_value boolean|integer|string --- @alias vim.option_valid_values (string|[string,vim.option_valid_values])[] --- @alias vim.option_redraw @@ -81,7 +86,7 @@ local function N_(s) -- luacheck: ignore 211 (currently unused) end -- luacheck: ignore 621 -return { +local options = { cstr = cstr, --- @type string[] valid_scopes = { 'global', 'buf', 'win' }, @@ -90,7 +95,7 @@ return { options = { { abbreviation = 'al', - defaults = { if_true = 224 }, + defaults = 224, full_name = 'aleph', scope = { 'global' }, short_desc = N_('ASCII code of the letter Aleph (Hebrew)'), @@ -99,7 +104,7 @@ return { }, { abbreviation = 'ari', - defaults = { if_true = false }, + defaults = false, desc = [=[ Allow CTRL-_ in Insert mode. This is default off, to avoid that users that accidentally type CTRL-_ instead of SHIFT-_ get into reverse @@ -114,7 +119,7 @@ return { { abbreviation = 'ambw', cb = 'did_set_ambiwidth', - defaults = { if_true = 'single' }, + defaults = 'single', values = { 'single', 'double' }, desc = [=[ Tells Vim what to do with characters with East Asian Width Class @@ -148,7 +153,6 @@ return { set to one of CJK locales. See Unicode Standard Annex #11 (https://www.unicode.org/reports/tr11). ]=], - expand_cb = 'expand_set_ambiwidth', full_name = 'ambiwidth', redraw = { 'all_windows', 'ui_option' }, scope = { 'global' }, @@ -159,7 +163,7 @@ return { { abbreviation = 'arab', cb = 'did_set_arabic', - defaults = { if_true = false }, + defaults = false, desc = [=[ This option can be set to start editing Arabic text. Setting this option will: @@ -184,7 +188,7 @@ return { }, { abbreviation = 'arshape', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on and 'termbidi' is off, the required visual character corrections that need to take place for displaying the Arabic language @@ -209,7 +213,7 @@ return { { abbreviation = 'acd', cb = 'did_set_autochdir', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, Vim will change the current working directory whenever you open a file, switch buffers, delete a buffer or open/close a window. @@ -226,7 +230,7 @@ return { }, { abbreviation = 'ai', - defaults = { if_true = true }, + defaults = true, desc = [=[ Copy indent from current line when starting a new line (typing in Insert mode or when using the "o" or "O" command). If you do not @@ -248,7 +252,7 @@ return { }, { abbreviation = 'ar', - defaults = { if_true = true }, + defaults = true, desc = [=[ When a file has been detected to have been changed outside of Vim and it has not been changed inside of Vim, automatically read it again. @@ -268,7 +272,7 @@ return { }, { abbreviation = 'aw', - defaults = { if_true = false }, + defaults = false, desc = [=[ Write the contents of the file, if it has been modified, on each `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`, @@ -293,7 +297,7 @@ return { }, { abbreviation = 'awa', - defaults = { if_true = false }, + defaults = false, desc = [=[ Like 'autowrite', but also used for commands ":edit", ":enew", ":quit", ":qall", ":exit", ":xit", ":recover" and closing the Vim window. @@ -309,7 +313,7 @@ return { { abbreviation = 'bg', cb = 'did_set_background', - defaults = { if_true = 'dark' }, + defaults = 'dark', values = { 'light', 'dark' }, desc = [=[ When set to "dark" or "light", adjusts the default color groups for @@ -335,7 +339,6 @@ return { will change. To use other settings, place ":highlight" commands AFTER the setting of the 'background' option. ]=], - expand_cb = 'expand_set_background', full_name = 'background', scope = { 'global' }, short_desc = N_('"dark" or "light", used for highlight colors'), @@ -345,7 +348,7 @@ return { { abbreviation = 'bs', cb = 'did_set_backspace', - defaults = { if_true = 'indent,eol,start' }, + defaults = 'indent,eol,start', values = { 'indent', 'eol', 'start', 'nostop' }, deny_duplicates = true, desc = [=[ @@ -363,7 +366,6 @@ return { When the value is empty, Vi compatible backspacing is used, none of the ways mentioned for the items above are possible. ]=], - expand_cb = 'expand_set_backspace', full_name = 'backspace', list = 'onecomma', scope = { 'global' }, @@ -373,7 +375,7 @@ return { }, { abbreviation = 'bk', - defaults = { if_true = false }, + defaults = false, desc = [=[ Make a backup before overwriting a file. Leave it around after the file has been successfully written. If you do not want to keep the @@ -463,17 +465,17 @@ return { the system may refuse to do this. In that case the "auto" value will again not rename the file. ]=], - expand_cb = 'expand_set_backupcopy', full_name = 'backupcopy', list = 'onecomma', scope = { 'global', 'buf' }, short_desc = N_("make backup as a copy, don't rename the file"), type = 'string', varname = 'p_bkc', + flags_varname = 'bkc_flags', }, { abbreviation = 'bdir', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ List of directories for the backup file, separated with commas. @@ -529,7 +531,7 @@ return { { abbreviation = 'bex', cb = 'did_set_backupext_or_patchmode', - defaults = { if_true = '~' }, + defaults = '~', desc = [=[ String which is appended to a file name to make the name of the backup file. The default is quite unusual, because this avoids @@ -593,8 +595,7 @@ return { }, { abbreviation = 'bo', - cb = 'did_set_belloff', - defaults = { if_true = 'all' }, + defaults = 'all', values = { 'all', 'backspace', @@ -657,18 +658,18 @@ return { indicate that an error occurred. It can be silenced by adding the "error" keyword. ]=], - expand_cb = 'expand_set_belloff', full_name = 'belloff', list = 'comma', scope = { 'global' }, short_desc = N_('do not ring the bell for these reasons'), type = 'string', varname = 'p_bo', + flags_varname = 'bo_flags', }, { abbreviation = 'bin', cb = 'did_set_binary', - defaults = { if_true = false }, + defaults = false, desc = [=[ This option should be set before editing a binary file. You can also use the |-b| Vim argument. When this option is switched on a few @@ -706,7 +707,7 @@ return { }, { cb = 'did_set_eof_eol_fixeol_bomb', - defaults = { if_true = false }, + defaults = false, desc = [=[ When writing a file and the following conditions are met, a BOM (Byte Order Mark) is prepended to the file: @@ -754,7 +755,7 @@ return { }, { abbreviation = 'bri', - defaults = { if_true = false }, + defaults = false, desc = [=[ Every wrapped line will continue visually indented (same amount of space as the beginning of that line), thus preserving horizontal blocks @@ -769,7 +770,7 @@ return { { abbreviation = 'briopt', cb = 'did_set_breakindentopt', - defaults = { if_true = '' }, + defaults = '', -- Keep this in sync with briopt_check(). values = { 'shift:', 'min:', 'sbr', 'list:', 'column:' }, deny_duplicates = true, @@ -802,7 +803,6 @@ return { added for the 'showbreak' setting. (default: off) ]=], - expand_cb = 'expand_set_breakindentopt', full_name = 'breakindentopt', list = 'onecomma', redraw = { 'current_buffer' }, @@ -833,7 +833,7 @@ return { { abbreviation = 'bh', cb = 'did_set_bufhidden', - defaults = { if_true = '' }, + defaults = '', values = { '', 'hide', 'unload', 'delete', 'wipe' }, desc = [=[ This option specifies what happens when a buffer is no longer @@ -856,7 +856,6 @@ return { This option is used together with 'buftype' and 'swapfile' to specify special kinds of buffers. See |special-buffers|. ]=], - expand_cb = 'expand_set_bufhidden', full_name = 'bufhidden', noglob = true, scope = { 'buf' }, @@ -867,7 +866,7 @@ return { { abbreviation = 'bl', cb = 'did_set_buflisted', - defaults = { if_true = true }, + defaults = true, desc = [=[ When this option is set, the buffer shows up in the buffer list. If it is reset it is not used for ":bnext", "ls", the Buffers menu, etc. @@ -886,7 +885,7 @@ return { { abbreviation = 'bt', cb = 'did_set_buftype', - defaults = { if_true = '' }, + defaults = '', values = { '', 'acwrite', @@ -943,7 +942,6 @@ return { without saving. For writing there must be matching |BufWriteCmd|, |FileWriteCmd| or |FileAppendCmd| autocommands. ]=], - expand_cb = 'expand_set_buftype', full_name = 'buftype', noglob = true, scope = { 'buf' }, @@ -954,8 +952,7 @@ return { }, { abbreviation = 'cmp', - cb = 'did_set_casemap', - defaults = { if_true = 'internal,keepascii' }, + defaults = 'internal,keepascii', values = { 'internal', 'keepascii' }, flags = true, deny_duplicates = true, @@ -970,17 +967,17 @@ return { case mapping, the current locale is not effective. This probably only matters for Turkish. ]=], - expand_cb = 'expand_set_casemap', full_name = 'casemap', list = 'onecomma', scope = { 'global' }, short_desc = N_('specifies how case of letters is changed'), type = 'string', varname = 'p_cmp', + flags_varname = 'cmp_flags', }, { abbreviation = 'cdh', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, |:cd|, |:tcd| and |:lcd| without an argument changes the current working directory to the |$HOME| directory like in Unix. @@ -1054,7 +1051,7 @@ return { varname = 'p_cedit', }, { - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ |channel| connected to the buffer, or 0 if no channel is connected. In a |:terminal| buffer this is the terminal channel. @@ -1071,7 +1068,7 @@ return { { abbreviation = 'ccv', cb = 'did_set_optexpr', - defaults = { if_true = '' }, + defaults = '', desc = [=[ An expression that is used for character encoding conversion. It is evaluated when a file that is to be read or has been written has a @@ -1127,7 +1124,7 @@ return { }, { abbreviation = 'cin', - defaults = { if_true = false }, + defaults = false, desc = [=[ Enables automatic C program indenting. See 'cinkeys' to set the keys that trigger reindenting in insert mode and 'cinoptions' to set your @@ -1148,7 +1145,7 @@ return { }, { abbreviation = 'cink', - defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' }, + defaults = '0{,0},0),0],:,0#,!^F,o,O,e', deny_duplicates = true, desc = [=[ A list of keys that, when typed in Insert mode, cause reindenting of @@ -1167,7 +1164,7 @@ return { { abbreviation = 'cino', cb = 'did_set_cinoptions', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ The 'cinoptions' affect the way 'cindent' reindents lines in a C @@ -1183,7 +1180,7 @@ return { }, { abbreviation = 'cinsd', - defaults = { if_true = 'public,protected,private' }, + defaults = 'public,protected,private', deny_duplicates = true, desc = [=[ Keywords that are interpreted as a C++ scope declaration by |cino-g|. @@ -1201,7 +1198,7 @@ return { }, { abbreviation = 'cinw', - defaults = { if_true = 'if,else,while,do,for,switch' }, + defaults = 'if,else,while,do,for,switch', deny_duplicates = true, desc = [=[ These keywords start an extra indent in the next line when @@ -1220,8 +1217,7 @@ return { }, { abbreviation = 'cb', - cb = 'did_set_clipboard', - defaults = { if_true = '' }, + defaults = '', values = { 'unnamed', 'unnamedplus' }, flags = true, desc = [=[ @@ -1249,18 +1245,18 @@ return { "*". See |clipboard|. ]=], deny_duplicates = true, - expand_cb = 'expand_set_clipboard', full_name = 'clipboard', list = 'onecomma', scope = { 'global' }, short_desc = N_('use the clipboard as the unnamed register'), type = 'string', varname = 'p_cb', + flags_varname = 'cb_flags', }, { abbreviation = 'ch', cb = 'did_set_cmdheight', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ Number of screen lines to use for the command-line. Helps avoiding |hit-enter| prompts. @@ -1285,7 +1281,7 @@ return { }, { abbreviation = 'cwh', - defaults = { if_true = 7 }, + defaults = 7, desc = [=[ Number of screen lines to use for the command-line window. |cmdwin| ]=], @@ -1298,7 +1294,7 @@ return { { abbreviation = 'cc', cb = 'did_set_colorcolumn', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ 'colorcolumn' is a comma-separated list of screen columns that are @@ -1353,7 +1349,7 @@ return { { abbreviation = 'com', cb = 'did_set_comments', - defaults = { if_true = 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•' }, + defaults = 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•', deny_duplicates = true, desc = [=[ A comma-separated list of strings that can start a comment line. See @@ -1371,7 +1367,7 @@ return { { abbreviation = 'cms', cb = 'did_set_commentstring', - defaults = { if_true = '' }, + defaults = '', desc = [=[ A template for a comment. The "%s" in the value is replaced with the comment text, and should be padded with a space when possible. @@ -1386,7 +1382,7 @@ return { }, { abbreviation = 'cp', - defaults = { if_true = false }, + defaults = false, full_name = 'compatible', scope = { 'global' }, short_desc = N_('No description'), @@ -1396,7 +1392,7 @@ return { { abbreviation = 'cpt', cb = 'did_set_complete', - defaults = { if_true = '.,w,b,u,t' }, + defaults = '.,w,b,u,t', values = { '.', 'w', 'b', 'u', 'k', 'kspell', 's', 'i', 'd', ']', 't', 'U', 'f' }, deny_duplicates = true, desc = [=[ @@ -1433,7 +1429,6 @@ return { based expansion (e.g., dictionary |i_CTRL-X_CTRL-K|, included patterns |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions). ]=], - expand_cb = 'expand_set_complete', full_name = 'complete', list = 'onecomma', scope = { 'buf' }, @@ -1445,7 +1440,7 @@ return { { abbreviation = 'cfu', cb = 'did_set_completefunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This option specifies a function to be used for Insert mode completion with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U| @@ -1467,7 +1462,7 @@ return { { abbreviation = 'cia', cb = 'did_set_completeitemalign', - defaults = { if_true = 'abbr,kind,menu' }, + defaults = 'abbr,kind,menu', flags = true, deny_duplicates = true, desc = [=[ @@ -1488,7 +1483,7 @@ return { { abbreviation = 'cot', cb = 'did_set_completeopt', - defaults = { if_true = 'menu,preview' }, + defaults = 'menu,preview', values = { 'menu', 'menuone', @@ -1543,18 +1538,18 @@ return { list of alternatives, but not how the candidates are collected (using different completion types). ]=], - expand_cb = 'expand_set_completeopt', full_name = 'completeopt', list = 'onecomma', scope = { 'global', 'buf' }, short_desc = N_('options for Insert mode completion'), type = 'string', varname = 'p_cot', + flags_varname = 'cot_flags', }, { abbreviation = 'csl', cb = 'did_set_completeslash', - defaults = { if_true = '' }, + defaults = '', values = { '', 'slash', 'backslash' }, desc = [=[ only modifiable in MS-Windows @@ -1570,7 +1565,6 @@ return { command line completion the global value is used. ]=], enable_if = 'BACKSLASH_IN_FILENAME', - expand_cb = 'expand_set_completeslash', full_name = 'completeslash', scope = { 'buf' }, type = 'string', @@ -1579,7 +1573,7 @@ return { { abbreviation = 'cocu', cb = 'did_set_concealcursor', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Sets the modes in which text in the cursor line can also be concealed. When the current mode is listed then concealing happens just like in @@ -1607,7 +1601,7 @@ return { }, { abbreviation = 'cole', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Determine how text with the "conceal" syntax attribute |:syn-conceal| is shown: @@ -1636,7 +1630,7 @@ return { }, { abbreviation = 'cf', - defaults = { if_true = false }, + defaults = false, desc = [=[ When 'confirm' is on, certain operations that would normally fail because of unsaved changes to a buffer, e.g. ":q" and ":e", @@ -1655,7 +1649,7 @@ return { }, { abbreviation = 'ci', - defaults = { if_true = false }, + defaults = false, desc = [=[ Copy the structure of the existing lines indent when autoindenting a new line. Normally the new indent is reconstructed by a series of @@ -1676,7 +1670,7 @@ return { { abbreviation = 'cpo', cb = 'did_set_cpoptions', - defaults = { if_true = macros('CPO_VIM', 'string') }, + defaults = macros('CPO_VIM', 'string'), desc = [=[ A sequence of single character flags. When a character is present this indicates Vi-compatible behavior. This is used for things where @@ -1918,7 +1912,7 @@ return { }, { abbreviation = 'crb', - defaults = { if_true = false }, + defaults = false, desc = [=[ When this option is set, as the cursor in the current window moves other cursorbound windows (windows that also have @@ -1935,7 +1929,7 @@ return { }, { abbreviation = 'cuc', - defaults = { if_true = false }, + defaults = false, desc = [=[ Highlight the screen column of the cursor with CursorColumn |hl-CursorColumn|. Useful to align text. Will make screen redrawing @@ -1954,7 +1948,7 @@ return { }, { abbreviation = 'cul', - defaults = { if_true = false }, + defaults = false, desc = [=[ Highlight the text line of the cursor with CursorLine |hl-CursorLine|. Useful to easily spot the cursor. Will make screen redrawing slower. @@ -1970,7 +1964,7 @@ return { { abbreviation = 'culopt', cb = 'did_set_cursorlineopt', - defaults = { if_true = 'both' }, + defaults = 'both', -- Keep this in sync with fill_culopt_flags(). values = { 'line', 'screenline', 'number', 'both' }, flags = { @@ -1994,7 +1988,6 @@ return { "line" and "screenline" cannot be used together. ]=], - expand_cb = 'expand_set_cursorlineopt', full_name = 'cursorlineopt', list = 'onecomma', redraw = { 'current_window', 'highlight_only' }, @@ -2003,8 +1996,7 @@ return { type = 'string', }, { - cb = 'did_set_debug', - defaults = { if_true = '' }, + defaults = '', values = { 'msg', 'throw', 'beep' }, desc = [=[ These values can be used: @@ -2019,7 +2011,6 @@ return { 'indentexpr'. ]=], -- TODO(lewis6991): bug, values currently cannot be combined - expand_cb = 'expand_set_debug', full_name = 'debug', list = 'comma', scope = { 'global' }, @@ -2029,7 +2020,7 @@ return { }, { abbreviation = 'def', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Pattern to be used to find a macro definition. It is a search pattern, just like for the "/" command. This option is used for the @@ -2059,7 +2050,7 @@ return { }, { abbreviation = 'deco', - defaults = { if_true = false }, + defaults = false, desc = [=[ If editing Unicode and this option is set, backspace and Normal mode "x" delete each combining character on its own. When it is off (the @@ -2079,7 +2070,7 @@ return { }, { abbreviation = 'dict', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ List of file names, separated by commas, that are used to lookup words @@ -2116,7 +2107,7 @@ return { }, { cb = 'did_set_diff', - defaults = { if_true = false }, + defaults = false, desc = [=[ Join the current window in the group of windows that shows differences between files. See |diff-mode|. @@ -2131,7 +2122,7 @@ return { { abbreviation = 'dex', cb = 'did_set_optexpr', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Expression which is evaluated to obtain a diff file (either ed-style or unified-style) from two versions of a file. See |diff-diffexpr|. @@ -2149,7 +2140,7 @@ return { { abbreviation = 'dip', cb = 'did_set_diffopt', - defaults = { if_true = 'internal,filler,closeoff' }, + defaults = 'internal,filler,closeoff', -- Keep this in sync with diffopt_changed(). values = { 'filler', @@ -2290,7 +2281,7 @@ return { }, { abbreviation = 'dg', - defaults = { if_true = false }, + defaults = false, desc = [=[ Enable the entering of digraphs in Insert mode with {char1} {char2}. See |digraphs|. @@ -2303,7 +2294,7 @@ return { }, { abbreviation = 'dir', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ List of directory names for the swap file, separated with commas. @@ -2361,7 +2352,7 @@ return { { abbreviation = 'dy', cb = 'did_set_display', - defaults = { if_true = 'lastline' }, + defaults = 'lastline', values = { 'lastline', 'truncate', 'uhex', 'msgsep' }, flags = true, deny_duplicates = true, @@ -2384,7 +2375,6 @@ return { The "@" character can be changed by setting the "lastline" item in 'fillchars'. The character is highlighted with |hl-NonText|. ]=], - expand_cb = 'expand_set_display', full_name = 'display', list = 'onecomma', redraw = { 'all_windows' }, @@ -2392,11 +2382,11 @@ return { short_desc = N_('list of flags for how to display text'), type = 'string', varname = 'p_dy', + flags_varname = 'dy_flags', }, { abbreviation = 'ead', - cb = 'did_set_eadirection', - defaults = { if_true = 'both' }, + defaults = 'both', values = { 'both', 'ver', 'hor' }, desc = [=[ Tells when the 'equalalways' option applies: @@ -2404,7 +2394,6 @@ return { hor horizontally, height of windows is not affected both width and height of windows is affected ]=], - expand_cb = 'expand_set_eadirection', full_name = 'eadirection', scope = { 'global' }, short_desc = N_("in which direction 'equalalways' works"), @@ -2413,7 +2402,7 @@ return { }, { abbreviation = 'ed', - defaults = { if_true = false }, + defaults = false, full_name = 'edcompatible', scope = { 'global' }, short_desc = N_('No description'), @@ -2422,8 +2411,8 @@ return { }, { abbreviation = 'emo', - cb = 'did_set_ambiwidth', - defaults = { if_true = true }, + cb = 'did_set_emoji', + defaults = true, desc = [=[ When on all Unicode emoji characters are considered to be full width. This excludes "text emoji" characters, which are normally displayed as @@ -2444,7 +2433,7 @@ return { { abbreviation = 'enc', cb = 'did_set_encoding', - defaults = { if_true = macros('ENC_DFLT', 'string') }, + defaults = macros('ENC_DFLT', 'string'), deny_in_modelines = true, desc = [=[ String-encoding used internally and for |RPC| communication. @@ -2461,7 +2450,7 @@ return { { abbreviation = 'eof', cb = 'did_set_eof_eol_fixeol_bomb', - defaults = { if_true = false }, + defaults = false, desc = [=[ Indicates that a CTRL-Z character was found at the end of the file when reading it. Normally only happens when 'fileformat' is "dos". @@ -2481,7 +2470,7 @@ return { { abbreviation = 'eol', cb = 'did_set_eof_eol_fixeol_bomb', - defaults = { if_true = true }, + defaults = true, desc = [=[ When writing a file and this option is off and the 'binary' option is on, or 'fixeol' option is off, no will be written for the @@ -2507,7 +2496,7 @@ return { { abbreviation = 'ea', cb = 'did_set_equalalways', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on, all the windows are automatically made the same size after splitting or closing a window. This also happens the moment the @@ -2532,7 +2521,7 @@ return { }, { abbreviation = 'ep', - defaults = { if_true = '' }, + defaults = '', desc = [=[ External program to use for "=" command. When this option is empty the internal formatting functions are used; either 'lisp', 'cindent' @@ -2552,7 +2541,7 @@ return { }, { abbreviation = 'eb', - defaults = { if_true = false }, + defaults = false, desc = [=[ Ring the bell (beep or screen flash) for error messages. This only makes a difference for error messages, the bell will be used always @@ -2568,7 +2557,7 @@ return { }, { abbreviation = 'ef', - defaults = { if_true = macros('DFLT_ERRORFILE', 'string') }, + defaults = macros('DFLT_ERRORFILE', 'string'), desc = [=[ Name of the errorfile for the QuickFix mode (see |:cf|). When the "-q" command-line argument is used, 'errorfile' is set to the @@ -2608,7 +2597,7 @@ return { { abbreviation = 'ei', cb = 'did_set_eventignore', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ A list of autocommand event names, which are to be ignored. @@ -2628,7 +2617,7 @@ return { }, { abbreviation = 'et', - defaults = { if_true = false }, + defaults = false, desc = [=[ In Insert mode: Use the appropriate number of spaces to insert a . Spaces are used in indents with the '>' and '<' commands and @@ -2643,7 +2632,7 @@ return { }, { abbreviation = 'ex', - defaults = { if_true = false }, + defaults = false, desc = [=[ Automatically execute .nvim.lua, .nvimrc, and .exrc files in the current directory, if the file is in the |trust| list. Use |:trust| to @@ -2666,7 +2655,7 @@ return { { abbreviation = 'fenc', cb = 'did_set_encoding', - defaults = { if_true = '' }, + defaults = '', desc = [=[ File-content encoding for the current buffer. Conversion is done with iconv() or as specified with 'charconvert'. @@ -2718,7 +2707,7 @@ return { }, { abbreviation = 'fencs', - defaults = { if_true = 'ucs-bom,utf-8,default,latin1' }, + defaults = 'ucs-bom,utf-8,default,latin1', deny_duplicates = true, desc = [=[ This is a list of character encodings considered when starting to edit @@ -2803,7 +2792,6 @@ return { option is set, because the file would be different when written. This option cannot be changed when 'modifiable' is off. ]=], - expand_cb = 'expand_set_fileformat', full_name = 'fileformat', no_mkrc = true, redraw = { 'curswant', 'statuslines' }, @@ -2814,7 +2802,7 @@ return { }, { abbreviation = 'ffs', - cb = 'did_set_fileformats', + cb = 'did_set_str_generic', defaults = { condition = 'USE_CRNL', if_true = 'dos,unix', @@ -2869,7 +2857,7 @@ return { used. Also see |file-formats|. ]=], - expand_cb = 'expand_set_fileformat', + expand_cb = 'expand_set_str_generic', full_name = 'fileformats', list = 'onecomma', scope = { 'global' }, @@ -2899,7 +2887,7 @@ return { { abbreviation = 'ft', cb = 'did_set_filetype_or_syntax', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When this option is set, the FileType autocommand event is triggered. All autocommands that match with the value of this option will be @@ -2934,7 +2922,7 @@ return { { abbreviation = 'fcs', cb = 'did_set_chars_option', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ Characters to fill the statuslines, vertical separators and special @@ -3009,7 +2997,7 @@ return { { abbreviation = 'ffu', cb = 'did_set_findfunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Function that is called to obtain the filename(s) for the |:find| command. When this option is empty, the internal |file-searching| @@ -3069,7 +3057,7 @@ return { { abbreviation = 'fixeol', cb = 'did_set_eof_eol_fixeol_bomb', - defaults = { if_true = true }, + defaults = true, desc = [=[ When writing a file and this option is on, at the end of file will be restored if missing. Turn this option off if you want to @@ -3088,8 +3076,7 @@ return { }, { abbreviation = 'fcl', - cb = 'did_set_foldclose', - defaults = { if_true = '' }, + defaults = '', values = { 'all' }, deny_duplicates = true, desc = [=[ @@ -3097,7 +3084,6 @@ return { its level is higher than 'foldlevel'. Useful if you want folds to automatically close when moving out of them. ]=], - expand_cb = 'expand_set_foldclose', full_name = 'foldclose', list = 'onecomma', redraw = { 'current_window' }, @@ -3108,8 +3094,7 @@ return { }, { abbreviation = 'fdc', - cb = 'did_set_foldcolumn', - defaults = { if_true = '0' }, + defaults = '0', values = { 'auto', 'auto:1', @@ -3141,7 +3126,6 @@ return { "[1-9]": to display a fixed number of columns See |folding|. ]=], - expand_cb = 'expand_set_foldcolumn', full_name = 'foldcolumn', redraw = { 'current_window' }, scope = { 'win' }, @@ -3150,7 +3134,7 @@ return { }, { abbreviation = 'fen', - defaults = { if_true = true }, + defaults = true, desc = [=[ When off, all folds are open. This option can be used to quickly switch between showing all text unfolded and viewing the text with @@ -3169,7 +3153,7 @@ return { { abbreviation = 'fde', cb = 'did_set_foldexpr', - defaults = { if_true = '0' }, + defaults = '0', desc = [=[ The expression used for when 'foldmethod' is "expr". It is evaluated for each line to obtain its fold level. The context is set to the @@ -3194,7 +3178,7 @@ return { { abbreviation = 'fdi', cb = 'did_set_foldignore', - defaults = { if_true = '#' }, + defaults = '#', desc = [=[ Used only when 'foldmethod' is "indent". Lines starting with characters in 'foldignore' will get their fold level from surrounding @@ -3210,7 +3194,7 @@ return { { abbreviation = 'fdl', cb = 'did_set_foldlevel', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Sets the fold level: Folds with a higher level will be closed. Setting this option to zero will close all folds. Higher numbers will @@ -3226,7 +3210,7 @@ return { }, { abbreviation = 'fdls', - defaults = { if_true = -1 }, + defaults = -1, desc = [=[ Sets 'foldlevel' when starting to edit another buffer in a window. Useful to always start editing with all folds closed (value zero), @@ -3248,7 +3232,7 @@ return { { abbreviation = 'fmr', cb = 'did_set_foldmarker', - defaults = { if_true = '{{{,}}}' }, + defaults = '{{{,}}}', deny_duplicates = true, desc = [=[ The start and end marker used when 'foldmethod' is "marker". There @@ -3267,7 +3251,7 @@ return { { abbreviation = 'fdm', cb = 'did_set_foldmethod', - defaults = { if_true = 'manual' }, + defaults = 'manual', values = { 'manual', 'expr', 'marker', 'indent', 'syntax', 'diff' }, desc = [=[ The kind of folding used for the current window. Possible values: @@ -3278,7 +3262,6 @@ return { |fold-syntax| syntax Syntax highlighting items specify folds. |fold-diff| diff Fold text that is not changed. ]=], - expand_cb = 'expand_set_foldmethod', full_name = 'foldmethod', redraw = { 'current_window' }, scope = { 'win' }, @@ -3288,7 +3271,7 @@ return { { abbreviation = 'fml', cb = 'did_set_foldminlines', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ Sets the number of screen lines above which a fold can be displayed closed. Also for manually closed folds. With the default value of @@ -3307,7 +3290,7 @@ return { { abbreviation = 'fdn', cb = 'did_set_foldnestmax', - defaults = { if_true = 20 }, + defaults = 20, desc = [=[ Sets the maximum nesting of folds for the "indent" and "syntax" methods. This avoids that too many folds will be created. Using more @@ -3321,8 +3304,7 @@ return { }, { abbreviation = 'fdo', - cb = 'did_set_foldopen', - defaults = { if_true = 'block,hor,mark,percent,quickfix,search,tag,undo' }, + defaults = 'block,hor,mark,percent,quickfix,search,tag,undo', values = { 'all', 'block', @@ -3370,7 +3352,6 @@ return { To close folds you can re-apply 'foldlevel' with the |zx| command or set the 'foldclose' option to "all". ]=], - expand_cb = 'expand_set_foldopen', full_name = 'foldopen', list = 'onecomma', redraw = { 'curswant' }, @@ -3378,11 +3359,12 @@ return { short_desc = N_('for which commands a fold will be opened'), type = 'string', varname = 'p_fdo', + flags_varname = 'fdo_flags', }, { abbreviation = 'fdt', cb = 'did_set_optexpr', - defaults = { if_true = 'foldtext()' }, + defaults = 'foldtext()', desc = [=[ An expression which is used to specify the text displayed for a closed fold. The context is set to the script where 'foldexpr' was set, @@ -3409,7 +3391,7 @@ return { { abbreviation = 'fex', cb = 'did_set_optexpr', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Expression which is evaluated to format a range of lines for the |gq| operator or automatic formatting (see 'formatoptions'). When this @@ -3461,7 +3443,7 @@ return { }, { abbreviation = 'flp', - defaults = { if_true = '^\\s*\\d\\+[\\]:.)}\\t ]\\s*' }, + defaults = '^\\s*\\d\\+[\\]:.)}\\t ]\\s*', desc = [=[ A pattern that is used to recognize a list header. This is used for the "n" flag in 'formatoptions'. @@ -3482,7 +3464,7 @@ return { { abbreviation = 'fo', cb = 'did_set_formatoptions', - defaults = { if_true = macros('DFLT_FO_VIM', 'string') }, + defaults = macros('DFLT_FO_VIM', 'string'), desc = [=[ This is a sequence of letters which describes how automatic formatting is to be done. @@ -3501,7 +3483,7 @@ return { }, { abbreviation = 'fp', - defaults = { if_true = '' }, + defaults = '', desc = [=[ The name of an external program that will be used to format the lines selected with the |gq| operator. The program must take the input on @@ -3525,7 +3507,7 @@ return { }, { abbreviation = 'fs', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on, the OS function fsync() will be called after saving a file (|:write|, |writefile()|, …), |swap-file|, |undo-persistence| and |shada-file|. @@ -3551,7 +3533,7 @@ return { }, { abbreviation = 'gd', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, the ":substitute" flag 'g' is default on. This means that all matches in a line are substituted instead of one. When a 'g' flag @@ -3575,7 +3557,7 @@ return { }, { abbreviation = 'gfm', - defaults = { if_true = macros('DFLT_GREPFORMAT', 'string') }, + defaults = macros('DFLT_GREPFORMAT', 'string'), deny_duplicates = true, desc = [=[ Format to recognize for the ":grep" command output. @@ -3634,9 +3616,7 @@ return { { abbreviation = 'gcr', cb = 'did_set_guicursor', - defaults = { - if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,t:block-blinkon500-blinkoff500-TermCursor', - }, + defaults = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,t:block-blinkon500-blinkoff500-TermCursor', deny_duplicates = true, desc = [=[ Configures the cursor style for each mode. Works in the GUI and many @@ -3735,7 +3715,7 @@ return { }, { abbreviation = 'gfn', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This is a list of fonts which will be used for the GUI version of Vim. In its simplest form the value is just one font name. When @@ -3807,7 +3787,7 @@ return { }, { abbreviation = 'gfw', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ Comma-separated list of fonts to be used for double-width characters. @@ -3943,7 +3923,7 @@ return { }, { abbreviation = 'gtl', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty describes the text to use in a label of the GUI tab pages line. When empty and when the result is empty Vim will use a @@ -3969,7 +3949,7 @@ return { }, { abbreviation = 'gtt', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty describes the text to use in a tooltip for the GUI tab pages line. When empty Vim will use a default tooltip. @@ -4015,7 +3995,7 @@ return { { abbreviation = 'hh', cb = 'did_set_helpheight', - defaults = { if_true = 20 }, + defaults = 20, desc = [=[ Minimal initial height of the help window when it is opened with the ":help" command. The initial height of the help window is half of the @@ -4060,7 +4040,7 @@ return { }, { abbreviation = 'hid', - defaults = { if_true = true }, + defaults = true, desc = [=[ When off a buffer is unloaded (including loss of undo information) when it is |abandon|ed. When on a buffer becomes hidden when it is @@ -4086,7 +4066,7 @@ return { { abbreviation = 'hl', cb = 'did_set_highlight', - defaults = { if_true = macros('HIGHLIGHT_INIT', 'string') }, + defaults = macros('HIGHLIGHT_INIT', 'string'), deny_duplicates = true, full_name = 'highlight', list = 'onecomma', @@ -4097,7 +4077,7 @@ return { }, { abbreviation = 'hi', - defaults = { if_true = 10000 }, + defaults = 10000, desc = [=[ A history of ":" commands, and a history of previous search patterns is remembered. This option decides how many entries may be stored in @@ -4113,7 +4093,7 @@ return { }, { abbreviation = 'hk', - defaults = { if_true = false }, + defaults = false, full_name = 'hkmap', scope = { 'global' }, short_desc = N_('No description'), @@ -4122,7 +4102,7 @@ return { }, { abbreviation = 'hkp', - defaults = { if_true = false }, + defaults = false, full_name = 'hkmapp', scope = { 'global' }, short_desc = N_('No description'), @@ -4132,7 +4112,7 @@ return { { abbreviation = 'hls', cb = 'did_set_hlsearch', - defaults = { if_true = true }, + defaults = true, desc = [=[ When there is a previous search pattern, highlight all its matches. The |hl-Search| highlight group determines the highlighting for all @@ -4182,7 +4162,7 @@ return { }, { cb = 'did_set_iconstring', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When this option is not empty, it will be used for the icon text of the window. This happens only when the 'icon' option is on. @@ -4202,7 +4182,7 @@ return { { abbreviation = 'ic', cb = 'did_set_ignorecase', - defaults = { if_true = false }, + defaults = false, desc = [=[ Ignore case in search patterns, |cmdline-completion|, when searching in the tags file, and |expr-==|. @@ -4218,7 +4198,7 @@ return { }, { abbreviation = 'imc', - defaults = { if_true = false }, + defaults = false, desc = [=[ When set the Input Method is always on when starting to edit a command line, unless entering a search pattern (see 'imsearch' for that). @@ -4253,7 +4233,7 @@ return { { abbreviation = 'imi', cb = 'did_set_iminsert', - defaults = { if_true = macros('B_IMODE_NONE', 'number') }, + defaults = macros('B_IMODE_NONE', 'number'), desc = [=[ Specifies whether :lmap or an Input Method (IM) is to be used in Insert mode. Valid values: @@ -4278,7 +4258,7 @@ return { }, { abbreviation = 'ims', - defaults = { if_true = macros('B_IMODE_USE_INSERT', 'number') }, + defaults = macros('B_IMODE_USE_INSERT', 'number'), desc = [=[ Specifies whether :lmap or an Input Method (IM) is to be used when entering a search pattern. Valid values: @@ -4301,7 +4281,7 @@ return { { abbreviation = 'icm', cb = 'did_set_inccommand', - defaults = { if_true = 'nosplit' }, + defaults = 'nosplit', values = { 'nosplit', 'split', '' }, desc = [=[ When nonempty, shows the effects of |:substitute|, |:smagic|, @@ -4318,7 +4298,6 @@ return { 'redrawtime') then 'inccommand' is automatically disabled until |Command-line-mode| is done. ]=], - expand_cb = 'expand_set_inccommand', full_name = 'inccommand', scope = { 'global' }, short_desc = N_('Live preview of substitution'), @@ -4327,7 +4306,7 @@ return { }, { abbreviation = 'inc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Pattern to be used to find an include command. It is a search pattern, just like for the "/" command (See |pattern|). This option @@ -4349,7 +4328,7 @@ return { { abbreviation = 'inex', cb = 'did_set_optexpr', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Expression to be used to transform the string found with the 'include' option to a file name. Mostly useful to change "." to "/" for Java: >vim @@ -4390,7 +4369,7 @@ return { }, { abbreviation = 'is', - defaults = { if_true = true }, + defaults = true, desc = [=[ While typing a search command, show where the pattern, as it was typed so far, matches. The matched string is highlighted. If the pattern @@ -4433,7 +4412,7 @@ return { { abbreviation = 'inde', cb = 'did_set_optexpr', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Expression which is evaluated to obtain the proper indent for a line. It is used when a new line is created, for the |=| operator and @@ -4485,7 +4464,7 @@ return { }, { abbreviation = 'indk', - defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' }, + defaults = '0{,0},0),0],:,0#,!^F,o,O,e', deny_duplicates = true, desc = [=[ A list of keys that, when typed in Insert mode, cause reindenting of @@ -4502,7 +4481,7 @@ return { }, { abbreviation = 'inf', - defaults = { if_true = false }, + defaults = false, desc = [=[ When doing keyword completion in insert mode |ins-completion|, and 'ignorecase' is also on, the case of the match is adjusted depending @@ -4521,7 +4500,7 @@ return { }, { abbreviation = 'im', - defaults = { if_true = false }, + defaults = false, full_name = 'insertmode', scope = { 'global' }, short_desc = N_('No description'), @@ -4627,7 +4606,7 @@ return { { abbreviation = 'isk', cb = 'did_set_iskeyword', - defaults = { if_true = '@,48-57,_,192-255' }, + defaults = '@,48-57,_,192-255', deny_duplicates = true, desc = [=[ Keywords are used in searching and recognizing with many commands: @@ -4653,7 +4632,7 @@ return { { abbreviation = 'isp', cb = 'did_set_isopt', - defaults = { if_true = '@,161-255' }, + defaults = '@,161-255', deny_duplicates = true, desc = [=[ The characters given by this option are displayed directly on the @@ -4693,7 +4672,7 @@ return { }, { abbreviation = 'js', - defaults = { if_true = false }, + defaults = false, desc = [=[ Insert two spaces after a '.', '?' and '!' with a join command. Otherwise only one space is inserted. @@ -4706,8 +4685,7 @@ return { }, { abbreviation = 'jop', - cb = 'did_set_jumpoptions', - defaults = { if_true = 'clean' }, + defaults = 'clean', values = { 'stack', 'view', 'clean' }, flags = true, deny_duplicates = true, @@ -4726,18 +4704,18 @@ return { clean Remove unloaded buffers from the jumplist. EXPERIMENTAL: this flag may change in the future. ]=], - expand_cb = 'expand_set_jumpoptions', full_name = 'jumpoptions', list = 'onecomma', scope = { 'global' }, short_desc = N_('Controls the behavior of the jumplist'), type = 'string', varname = 'p_jop', + flags_varname = 'jop_flags', }, { abbreviation = 'kmp', cb = 'did_set_keymap', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Name of a keyboard mapping. See |mbyte-keymap|. Setting this option to a valid keymap name has the side effect of @@ -4757,7 +4735,7 @@ return { { abbreviation = 'km', cb = 'did_set_keymodel', - defaults = { if_true = '' }, + defaults = '', values = { 'startsel', 'stopsel' }, deny_duplicates = true, desc = [=[ @@ -4770,7 +4748,6 @@ return { Special keys in this context are the cursor keys, , , and . ]=], - expand_cb = 'expand_set_keymodel', full_name = 'keymodel', list = 'onecomma', scope = { 'global' }, @@ -4811,7 +4788,7 @@ return { { abbreviation = 'lmap', cb = 'did_set_langmap', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ This option allows switching your keyboard into a special language @@ -4865,7 +4842,7 @@ return { }, { abbreviation = 'lm', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Language to use for menu translation. Tells which file is loaded from the "lang" directory in 'runtimepath': >vim @@ -4896,7 +4873,7 @@ return { { abbreviation = 'lnr', cb = 'did_set_langnoremap', - defaults = { if_true = true }, + defaults = true, full_name = 'langnoremap', scope = { 'global' }, short_desc = N_("do not apply 'langmap' to mapped characters"), @@ -4906,7 +4883,7 @@ return { { abbreviation = 'lrm', cb = 'did_set_langremap', - defaults = { if_true = false }, + defaults = false, desc = [=[ When off, setting 'langmap' does not apply to characters resulting from a mapping. If setting 'langmap' disables some of your mappings, make @@ -4921,7 +4898,7 @@ return { { abbreviation = 'ls', cb = 'did_set_laststatus', - defaults = { if_true = 2 }, + defaults = 2, desc = [=[ The value of this option influences when the last window will have a status line: @@ -4941,7 +4918,7 @@ return { }, { abbreviation = 'lz', - defaults = { if_true = false }, + defaults = false, desc = [=[ When this option is set, the screen will not be redrawn while executing macros, registers and other commands that have not been @@ -4959,7 +4936,7 @@ return { }, { abbreviation = 'lbr', - defaults = { if_true = false }, + defaults = false, desc = [=[ If on, Vim will wrap long lines at a character in 'breakat' rather than at the last character that fits on the screen. Unlike @@ -5005,7 +4982,7 @@ return { }, { abbreviation = 'lsp', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ only in the GUI Number of pixel lines inserted between characters. Useful if the font @@ -5025,7 +5002,7 @@ return { }, { cb = 'did_set_lisp', - defaults = { if_true = false }, + defaults = false, desc = [=[ Lisp mode: When is typed in insert mode set the indent for the next line to Lisp standards (well, sort of). Also happens with @@ -5045,7 +5022,7 @@ return { { abbreviation = 'lop', cb = 'did_set_lispoptions', - defaults = { if_true = '' }, + defaults = '', values = { 'expr:0', 'expr:1' }, deny_duplicates = true, desc = [=[ @@ -5057,7 +5034,6 @@ return { Note that when using 'indentexpr' the `=` operator indents all the lines, otherwise the first line is not indented (Vi-compatible). ]=], - expand_cb = 'expand_set_lispoptions', full_name = 'lispoptions', list = 'onecomma', scope = { 'buf' }, @@ -5084,7 +5060,7 @@ return { varname = 'p_lispwords', }, { - defaults = { if_true = false }, + defaults = false, desc = [=[ List mode: By default, show tabs as ">", trailing spaces as "-", and non-breakable space characters as "+". Useful to see the difference @@ -5112,7 +5088,7 @@ return { { abbreviation = 'lcs', cb = 'did_set_chars_option', - defaults = { if_true = 'tab:> ,trail:-,nbsp:+' }, + defaults = 'tab:> ,trail:-,nbsp:+', deny_duplicates = true, desc = [=[ Strings to use in 'list' mode and for the |:list| command. It is a @@ -5223,7 +5199,7 @@ return { }, { abbreviation = 'lpl', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on the plugin scripts are loaded when starting up |load-plugins|. This option can be reset in your |vimrc| file to disable the loading @@ -5238,7 +5214,7 @@ return { varname = 'p_lpl', }, { - defaults = { if_true = true }, + defaults = true, desc = [=[ Changes the special characters that can be used in search patterns. See |pattern|. @@ -5256,7 +5232,7 @@ return { }, { abbreviation = 'mef', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Name of the errorfile for the |:make| command (see |:make_makeprg|) and the |:grep| command. @@ -5281,7 +5257,7 @@ return { { abbreviation = 'menc', cb = 'did_set_encoding', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Encoding used for reading the output of external commands. When empty, encoding is not converted. @@ -5304,7 +5280,7 @@ return { }, { abbreviation = 'mp', - defaults = { if_true = 'make' }, + defaults = 'make', desc = [=[ Program to use for the ":make" command. See |:make_makeprg|. This option may contain '%' and '#' characters (see |:_%| and |:_#|), @@ -5333,7 +5309,7 @@ return { { abbreviation = 'mps', cb = 'did_set_matchpairs', - defaults = { if_true = '(:),{:},[:]' }, + defaults = '(:),{:},[:]', deny_duplicates = true, desc = [=[ Characters that form pairs. The |%| command jumps from one to the @@ -5361,7 +5337,7 @@ return { }, { abbreviation = 'mat', - defaults = { if_true = 5 }, + defaults = 5, desc = [=[ Tenths of a second to show the matching paren, when 'showmatch' is set. Note that this is not in milliseconds, like other options that @@ -5375,7 +5351,7 @@ return { }, { abbreviation = 'mco', - defaults = { if_true = macros('MAX_MCO', 'number') }, + defaults = macros('MAX_MCO', 'number'), full_name = 'maxcombine', scope = { 'global' }, short_desc = N_('maximum nr of combining characters displayed'), @@ -5384,7 +5360,7 @@ return { }, { abbreviation = 'mfd', - defaults = { if_true = 100 }, + defaults = 100, desc = [=[ Maximum depth of function calls for user functions. This normally catches endless recursion. When using a recursive function with @@ -5403,7 +5379,7 @@ return { }, { abbreviation = 'mmd', - defaults = { if_true = 1000 }, + defaults = 1000, desc = [=[ Maximum number of times a mapping is done without resulting in a character to be used. This normally catches endless mappings, like @@ -5420,7 +5396,7 @@ return { }, { abbreviation = 'mmp', - defaults = { if_true = 1000 }, + defaults = 1000, desc = [=[ Maximum amount of memory (in Kbyte) to use for pattern matching. The maximum value is about 2000000. Use this to work without a limit. @@ -5443,7 +5419,7 @@ return { }, { abbreviation = 'mis', - defaults = { if_true = 25 }, + defaults = 25, desc = [=[ Maximum number of items to use in a menu. Used for menus that are generated from a list of items, e.g., the Buffers menu. Changing this @@ -5458,7 +5434,7 @@ return { { abbreviation = 'mopt', cb = 'did_set_messagesopt', - defaults = { if_true = 'hit-enter,history:500' }, + defaults = 'hit-enter,history:500', values = { 'hit-enter', 'wait:', 'history:' }, flags = true, deny_duplicates = true, @@ -5482,7 +5458,6 @@ return { Setting it to zero clears the message history. This item must always be present. ]=], - expand_cb = 'expand_set_messagesopt', full_name = 'messagesopt', list = 'onecommacolon', scope = { 'global' }, @@ -5493,7 +5468,7 @@ return { { abbreviation = 'msm', cb = 'did_set_mkspellmem', - defaults = { if_true = '460000,2000,500' }, + defaults = '460000,2000,500', desc = [=[ Parameters for |:mkspell|. This tunes when to start compressing the word tree. Compression can be slow when there are many words, but @@ -5558,7 +5533,7 @@ return { }, { abbreviation = 'mle', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on allow some options that are an expression to be set in the modeline. Check the option for whether it is affected by @@ -5575,7 +5550,7 @@ return { }, { abbreviation = 'mls', - defaults = { if_true = 5 }, + defaults = 5, desc = [=[ If 'modeline' is on 'modelines' gives the number of lines that is checked for set commands. If 'modeline' is off or 'modelines' is zero @@ -5591,7 +5566,7 @@ return { { abbreviation = 'ma', cb = 'did_set_modifiable', - defaults = { if_true = true }, + defaults = true, desc = [=[ When off the buffer contents cannot be changed. The 'fileformat' and 'fileencoding' options also can't be changed. @@ -5608,7 +5583,7 @@ return { { abbreviation = 'mod', cb = 'did_set_modified', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, the buffer is considered to be modified. This option is set when: @@ -5641,7 +5616,7 @@ return { varname = 'p_mod', }, { - defaults = { if_true = true }, + defaults = true, desc = [=[ When on, listings pause when the whole screen is filled. You will get the |more-prompt|. When this option is off there are no pauses, the @@ -5655,7 +5630,7 @@ return { }, { cb = 'did_set_mouse', - defaults = { if_true = 'nvi' }, + defaults = 'nvi', desc = [=[ Enables mouse support. For example, to enable the mouse in Normal mode and Visual mode: >vim @@ -5704,7 +5679,7 @@ return { }, { abbreviation = 'mousef', - defaults = { if_true = false }, + defaults = false, desc = [=[ The window that the mouse pointer is on is automatically activated. When changing the window layout or window focus in another way, the @@ -5721,7 +5696,7 @@ return { }, { abbreviation = 'mh', - defaults = { if_true = true }, + defaults = true, desc = [=[ only in the GUI When on, the mouse pointer is hidden when characters are typed. @@ -5736,8 +5711,7 @@ return { }, { abbreviation = 'mousem', - cb = 'did_set_mousemodel', - defaults = { if_true = 'popup_setpos' }, + defaults = 'popup_setpos', values = { 'extend', 'popup', 'popup_setpos' }, desc = [=[ Sets the model to use for the mouse. The name mostly specifies what @@ -5788,7 +5762,6 @@ return { "g" is " (jump to tag under mouse click) "g" is " ("CTRL-T") ]=], - expand_cb = 'expand_set_mousemodel', full_name = 'mousemodel', scope = { 'global' }, short_desc = N_('changes meaning of mouse buttons'), @@ -5797,7 +5770,7 @@ return { }, { abbreviation = 'mousemev', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, mouse move events are delivered to the input queue and are available for mapping. The default, off, avoids the mouse movement @@ -5814,7 +5787,7 @@ return { }, { cb = 'did_set_mousescroll', - defaults = { if_true = 'ver:3,hor:6' }, + defaults = 'ver:3,hor:6', values = { 'hor:', 'ver:' }, desc = [=[ This option controls the number of lines / columns to scroll by when @@ -5835,7 +5808,6 @@ return { < Will make Nvim scroll 5 lines at a time when scrolling vertically, and scroll 2 columns at a time when scrolling horizontally. ]=], - expand_cb = 'expand_set_mousescroll', full_name = 'mousescroll', list = 'comma', scope = { 'global' }, @@ -5923,7 +5895,7 @@ return { }, { abbreviation = 'mouset', - defaults = { if_true = 500 }, + defaults = 500, desc = [=[ Defines the maximum time in msec between two mouse clicks for the second click to be recognized as a multi click. @@ -5936,8 +5908,7 @@ return { }, { abbreviation = 'nf', - cb = 'did_set_nrformats', - defaults = { if_true = 'bin,hex' }, + defaults = 'bin,hex', values = { 'bin', 'octal', 'hex', 'alpha', 'unsigned', 'blank' }, deny_duplicates = true, desc = [=[ @@ -5982,7 +5953,6 @@ return { considered decimal. This also happens for numbers that are not recognized as octal or hex. ]=], - expand_cb = 'expand_set_nrformats', full_name = 'nrformats', list = 'onecomma', scope = { 'buf' }, @@ -5993,7 +5963,7 @@ return { { abbreviation = 'nu', cb = 'did_set_number_relativenumber', - defaults = { if_true = false }, + defaults = false, desc = [=[ Print the line number in front of each line. When the 'n' option is excluded from 'cpoptions' a wrapped line will not use the column of @@ -6026,7 +5996,7 @@ return { { abbreviation = 'nuw', cb = 'did_set_numberwidth', - defaults = { if_true = 4 }, + defaults = 4, desc = [=[ Minimal number of columns to use for the line number. Only relevant when the 'number' or 'relativenumber' option is set or printing lines @@ -6048,7 +6018,7 @@ return { { abbreviation = 'ofu', cb = 'did_set_omnifunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This option specifies a function to be used for Insert mode omni completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O| @@ -6071,7 +6041,7 @@ return { }, { abbreviation = 'odev', - defaults = { if_true = false }, + defaults = false, desc = [=[ only for Windows Enable reading and writing from devices. This may get Vim stuck on a @@ -6089,7 +6059,7 @@ return { { abbreviation = 'opfunc', cb = 'did_set_operatorfunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This option specifies a function to be called by the |g@| operator. See |:map-operator| for more info and an example. The value can be @@ -6133,7 +6103,7 @@ return { }, { abbreviation = 'para', - defaults = { if_true = 'IPLPPPQPP TPHPLIPpLpItpplpipbp' }, + defaults = 'IPLPPPQPP TPHPLIPpLpItpplpipbp', desc = [=[ Specifies the nroff macros that separate paragraphs. These are pairs of two letters (see |object-motions|). @@ -6146,7 +6116,7 @@ return { }, { cb = 'did_set_paste', - defaults = { if_true = false }, + defaults = false, full_name = 'paste', pri_mkrc = true, scope = { 'global' }, @@ -6156,7 +6126,7 @@ return { }, { abbreviation = 'pt', - defaults = { if_true = '' }, + defaults = '', full_name = 'pastetoggle', scope = { 'global' }, short_desc = N_('No description'), @@ -6166,7 +6136,7 @@ return { { abbreviation = 'pex', cb = 'did_set_optexpr', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Expression which is evaluated to apply a patch to a file and generate the resulting new version of the file. See |diff-patchexpr|. @@ -6183,7 +6153,7 @@ return { { abbreviation = 'pm', cb = 'did_set_backupext_or_patchmode', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty the oldest version of a file is kept. This can be used to keep the original version of a file if you are changing files in a @@ -6212,7 +6182,7 @@ return { }, { abbreviation = 'pa', - defaults = { if_true = '.,,' }, + defaults = '.,,', deny_duplicates = true, desc = [=[ This is a list of directories which will be searched when using the @@ -6274,7 +6244,7 @@ return { }, { abbreviation = 'pi', - defaults = { if_true = false }, + defaults = false, desc = [=[ When changing the indent of the current line, preserve as much of the indent structure as possible. Normally the indent is replaced by a @@ -6297,7 +6267,7 @@ return { }, { abbreviation = 'pvh', - defaults = { if_true = 12 }, + defaults = 12, desc = [=[ Default height for a preview window. Used for |:ptag| and associated commands. Used for |CTRL-W_}| when no count is given. @@ -6311,7 +6281,7 @@ return { { abbreviation = 'pvw', cb = 'did_set_previewwindow', - defaults = { if_true = false }, + defaults = false, desc = [=[ Identifies the preview window. Only one window can have this option set. It's normally not set directly, but by using one of the commands @@ -6326,7 +6296,7 @@ return { type = 'boolean', }, { - defaults = { if_true = true }, + defaults = true, full_name = 'prompt', scope = { 'global' }, short_desc = N_('enable prompt in Ex mode'), @@ -6336,7 +6306,7 @@ return { { abbreviation = 'pb', cb = 'did_set_pumblend', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Enables pseudo-transparency for the |popup-menu|. Valid values are in the range of 0 for fully opaque popupmenu (disabled) to 100 for fully @@ -6360,7 +6330,7 @@ return { }, { abbreviation = 'ph', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Maximum number of items to show in the popup menu (|ins-completion-menu|). Zero means "use available screen space". @@ -6373,7 +6343,7 @@ return { }, { abbreviation = 'pw', - defaults = { if_true = 15 }, + defaults = 15, desc = [=[ Minimum width for the popup menu (|ins-completion-menu|). If the cursor column + 'pumwidth' exceeds screen width, the popup menu is @@ -6387,7 +6357,7 @@ return { }, { abbreviation = 'pyx', - defaults = { if_true = 3 }, + defaults = 3, desc = [=[ Specifies the python version used for pyx* functions and commands |python_x|. As only Python 3 is supported, this always has the value @@ -6406,7 +6376,7 @@ return { { abbreviation = 'qftf', cb = 'did_set_quickfixtextfunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This option specifies a function to be used to get the text to display in the quickfix and location list windows. This can be used to @@ -6430,7 +6400,7 @@ return { }, { abbreviation = 'qe', - defaults = { if_true = '\\' }, + defaults = '\\', desc = [=[ The characters that are used to escape quotes in a string. Used for objects like a', a" and a` |a'|. @@ -6447,7 +6417,7 @@ return { { abbreviation = 'ro', cb = 'did_set_readonly', - defaults = { if_true = false }, + defaults = false, desc = [=[ If on, writes fail unless you use a '!'. Protects you from accidentally overwriting a file. Default on when Vim is started @@ -6468,8 +6438,7 @@ return { }, { abbreviation = 'rdb', - cb = 'did_set_redrawdebug', - defaults = { if_true = '' }, + defaults = '', values = { 'compositor', 'nothrottle', @@ -6517,10 +6486,11 @@ return { short_desc = N_('Changes the way redrawing works (debug)'), type = 'string', varname = 'p_rdb', + flags_varname = 'rdb_flags', }, { abbreviation = 'rdt', - defaults = { if_true = 2000 }, + defaults = 2000, desc = [=[ Time in milliseconds for redrawing the display. Applies to 'hlsearch', 'inccommand', |:match| highlighting, syntax highlighting, @@ -6540,7 +6510,7 @@ return { }, { abbreviation = 're', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ This selects the default regexp engine. |two-engines| The possible values are: @@ -6564,7 +6534,7 @@ return { { abbreviation = 'rnu', cb = 'did_set_number_relativenumber', - defaults = { if_true = false }, + defaults = false, desc = [=[ Show the line number relative to the line with the cursor in front of each line. Relative line numbers help you use the |count| you can @@ -6591,7 +6561,7 @@ return { type = 'boolean', }, { - defaults = { if_true = true }, + defaults = true, full_name = 'remap', scope = { 'global' }, short_desc = N_('No description'), @@ -6599,7 +6569,7 @@ return { immutable = true, }, { - defaults = { if_true = 2 }, + defaults = 2, desc = [=[ Threshold for reporting number of lines changed. When the number of changed lines is more than 'report' a message will be given for most @@ -6615,7 +6585,7 @@ return { }, { abbreviation = 'ri', - defaults = { if_true = false }, + defaults = false, desc = [=[ Inserting characters in Insert mode will work backwards. See "typing backwards" |ins-reverse|. This option can be toggled with the CTRL-_ @@ -6629,7 +6599,7 @@ return { }, { abbreviation = 'rl', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, display orientation becomes right-to-left, i.e., characters that are stored in the file appear from the right to the left. @@ -6649,8 +6619,7 @@ return { }, { abbreviation = 'rlc', - cb = 'did_set_rightleftcmd', - defaults = { if_true = 'search' }, + defaults = 'search', values = { 'search' }, desc = [=[ Each word in this option enables the command line editing to work in @@ -6661,8 +6630,8 @@ return { This is useful for languages such as Hebrew, Arabic and Farsi. The 'rightleft' option must be set for 'rightleftcmd' to take effect. ]=], - expand_cb = 'expand_set_rightleftcmd', full_name = 'rightleftcmd', + list = 'comma', redraw = { 'current_window' }, scope = { 'win' }, short_desc = N_('commands for which editing works right-to-left'), @@ -6670,7 +6639,7 @@ return { }, { abbreviation = 'ru', - defaults = { if_true = true }, + defaults = true, desc = [=[ Show the line and column number of the cursor position, separated by a comma. When there is room, the relative position of the displayed @@ -6705,7 +6674,7 @@ return { { abbreviation = 'ruf', cb = 'did_set_rulerformat', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When this option is not empty, it determines the content of the ruler string, as displayed for the 'ruler' option. @@ -6877,7 +6846,7 @@ return { { abbreviation = 'scb', cb = 'did_set_scrollbind', - defaults = { if_true = false }, + defaults = false, desc = [=[ See also |scroll-binding|. When this option is set, scrolling the current window also scrolls other scrollbind windows (windows that @@ -6896,7 +6865,7 @@ return { }, { abbreviation = 'sj', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ Minimal number of lines to scroll when the cursor gets off the screen (e.g., with "j"). Not used for scroll commands (e.g., CTRL-E, @@ -6913,7 +6882,7 @@ return { }, { abbreviation = 'so', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Minimal number of screen lines to keep above and below the cursor. This will make some context visible around where you are working. If @@ -6934,8 +6903,7 @@ return { }, { abbreviation = 'sbo', - cb = 'did_set_scrollopt', - defaults = { if_true = 'ver,jump' }, + defaults = 'ver,jump', values = { 'ver', 'hor', 'jump' }, deny_duplicates = true, desc = [=[ @@ -6967,7 +6935,6 @@ return { When 'diff' mode is active there always is vertical scroll binding, even when "ver" isn't there. ]=], - expand_cb = 'expand_set_scrollopt', full_name = 'scrollopt', list = 'onecomma', scope = { 'global' }, @@ -6977,7 +6944,7 @@ return { }, { abbreviation = 'sect', - defaults = { if_true = 'SHNHH HUnhsh' }, + defaults = 'SHNHH HUnhsh', desc = [=[ Specifies the nroff macros that separate sections. These are pairs of two letters (See |object-motions|). The default makes a section start @@ -6990,7 +6957,7 @@ return { varname = 'p_sections', }, { - defaults = { if_true = false }, + defaults = false, full_name = 'secure', scope = { 'global' }, secure = true, @@ -7001,7 +6968,7 @@ return { { abbreviation = 'sel', cb = 'did_set_selection', - defaults = { if_true = 'inclusive' }, + defaults = 'inclusive', values = { 'inclusive', 'exclusive', 'old' }, desc = [=[ This option defines the behavior of the selection. It is only used @@ -7024,7 +6991,6 @@ return { backwards, you cannot include the last character of a line, when starting in Normal mode and 'virtualedit' empty. ]=], - expand_cb = 'expand_set_selection', full_name = 'selection', scope = { 'global' }, short_desc = N_('what type of selection to use'), @@ -7033,8 +6999,7 @@ return { }, { abbreviation = 'slm', - cb = 'did_set_selectmode', - defaults = { if_true = '' }, + defaults = '', values = { 'mouse', 'key', 'cmd' }, deny_duplicates = true, desc = [=[ @@ -7046,7 +7011,6 @@ return { cmd when using "v", "V" or CTRL-V See |Select-mode|. ]=], - expand_cb = 'expand_set_selectmode', full_name = 'selectmode', list = 'onecomma', scope = { 'global' }, @@ -7057,7 +7021,7 @@ return { { abbreviation = 'ssop', cb = 'did_set_sessionoptions', - defaults = { if_true = 'blank,buffers,curdir,folds,help,tabpages,winsize,terminal' }, + defaults = 'blank,buffers,curdir,folds,help,tabpages,winsize,terminal', -- Also used for 'viewoptions'. values = { 'buffers', @@ -7120,13 +7084,13 @@ return { If you leave out "options" many things won't work well after restoring the session. ]=], - expand_cb = 'expand_set_sessionoptions', full_name = 'sessionoptions', list = 'onecomma', scope = { 'global' }, short_desc = N_('options for |:mksession|'), type = 'string', varname = 'p_ssop', + flags_varname = 'ssop_flags', }, { abbreviation = 'sd', @@ -7263,7 +7227,7 @@ return { { abbreviation = 'sdf', alias = { 'vif', 'viminfofile' }, - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ When non-empty, overrides the file name used for |shada| (viminfo). @@ -7517,7 +7481,7 @@ return { }, { abbreviation = 'stmp', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on, use temp files for shell commands. When off use a pipe. When using a pipe is not possible temp files are used anyway. @@ -7538,7 +7502,7 @@ return { }, { abbreviation = 'sxe', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When 'shellxquote' is set to "(" then the characters listed in this option will be escaped with a '^' character. This makes it possible @@ -7581,7 +7545,7 @@ return { }, { abbreviation = 'sr', - defaults = { if_true = false }, + defaults = false, desc = [=[ Round indent to multiple of 'shiftwidth'. Applies to > and < commands. CTRL-T and CTRL-D in Insert mode always round the indent to @@ -7596,7 +7560,7 @@ return { { abbreviation = 'sw', cb = 'did_set_shiftwidth_tabstop', - defaults = { if_true = 8 }, + defaults = 8, desc = [=[ Number of spaces to use for each step of (auto)indent. Used for |'cindent'|, |>>|, |<<|, etc. @@ -7612,7 +7576,7 @@ return { { abbreviation = 'shm', cb = 'did_set_shortmess', - defaults = { if_true = 'ltToOCF' }, + defaults = 'ltToOCF', desc = [=[ This option helps to avoid all the |hit-enter| prompts caused by file messages, for example with CTRL-G, and to avoid some other messages. @@ -7682,7 +7646,7 @@ return { { abbreviation = 'sbr', cb = 'did_set_showbreak', - defaults = { if_true = '' }, + defaults = '', desc = [=[ String to put at the start of lines that have been wrapped. Useful values are "> " or "+++ ": >vim @@ -7710,7 +7674,7 @@ return { }, { abbreviation = 'sc', - defaults = { if_true = true }, + defaults = true, desc = [=[ Show (partial) command in the last line of the screen. Set this option off if your terminal is slow. @@ -7733,7 +7697,7 @@ return { { abbreviation = 'sloc', cb = 'did_set_showcmdloc', - defaults = { if_true = 'last' }, + defaults = 'last', values = { 'last', 'statusline', 'tabline' }, desc = [=[ This option can be used to display the (partially) entered command in @@ -7748,7 +7712,6 @@ return { place the text. Without a custom 'statusline' or 'tabline' it will be displayed in a convenient location. ]=], - expand_cb = 'expand_set_showcmdloc', full_name = 'showcmdloc', scope = { 'global' }, short_desc = N_('change location of partial command'), @@ -7757,7 +7720,7 @@ return { }, { abbreviation = 'sft', - defaults = { if_true = false }, + defaults = false, desc = [=[ When completing a word in insert mode (see |ins-completion|) from the tags file, show both the tag name and a tidied-up form of the search @@ -7776,7 +7739,7 @@ return { }, { abbreviation = 'sm', - defaults = { if_true = false }, + defaults = false, desc = [=[ When a bracket is inserted, briefly jump to the matching one. The jump is only done if the match can be seen on the screen. The time to @@ -7802,7 +7765,7 @@ return { }, { abbreviation = 'smd', - defaults = { if_true = true }, + defaults = true, desc = [=[ If in Insert, Replace or Visual mode put a message on the last line. The |hl-ModeMsg| highlight group determines the highlighting. @@ -7817,7 +7780,7 @@ return { { abbreviation = 'stal', cb = 'did_set_showtabline', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ The value of this option specifies when the line with tab page labels will be displayed: @@ -7837,7 +7800,7 @@ return { }, { abbreviation = 'ss', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ The minimal number of columns to scroll horizontally. Used only when the 'wrap' option is off and the cursor is moved off of the screen. @@ -7853,7 +7816,7 @@ return { }, { abbreviation = 'siso', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ The minimal number of screen columns to keep to the left and to the right of the cursor if 'nowrap' is set. Setting this option to a @@ -7885,7 +7848,7 @@ return { { abbreviation = 'scl', cb = 'did_set_signcolumn', - defaults = { if_true = 'auto' }, + defaults = 'auto', values = { 'yes', 'no', @@ -7928,7 +7891,6 @@ return { "number" display signs in the 'number' column. If the number column is not present, then behaves like "auto". ]=], - expand_cb = 'expand_set_signcolumn', full_name = 'signcolumn', redraw = { 'current_window' }, scope = { 'win' }, @@ -7937,7 +7899,7 @@ return { }, { abbreviation = 'scs', - defaults = { if_true = false }, + defaults = false, desc = [=[ Override the 'ignorecase' option if the search pattern contains upper case characters. Only used when the search pattern is typed and @@ -7954,7 +7916,7 @@ return { }, { abbreviation = 'si', - defaults = { if_true = false }, + defaults = false, desc = [=[ Do smart autoindenting when starting a new line. Works for C-like programs, but can also be used for other languages. 'cindent' does @@ -7984,7 +7946,7 @@ return { }, { abbreviation = 'sta', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on, a in front of a line inserts blanks according to 'shiftwidth'. 'tabstop' or 'softtabstop' is used in other places. A @@ -8006,7 +7968,7 @@ return { { abbreviation = 'sms', cb = 'did_set_smoothscroll', - defaults = { if_true = false }, + defaults = false, desc = [=[ Scrolling works with screen lines. When 'wrap' is set and the first line in the window wraps part of it may not be visible, as if it is @@ -8024,7 +7986,7 @@ return { }, { abbreviation = 'sts', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Number of spaces that a counts for while performing editing operations, like inserting a or using . It "feels" like @@ -8050,7 +8012,7 @@ return { }, { cb = 'did_set_spell', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on spell checking will be done. See |spell|. The languages are specified with 'spelllang'. @@ -8064,7 +8026,7 @@ return { { abbreviation = 'spc', cb = 'did_set_spellcapcheck', - defaults = { if_true = '[.?!]\\_[\\])\'"\\t ]\\+' }, + defaults = '[.?!]\\_[\\])\'"\\t ]\\+', desc = [=[ Pattern to locate the end of a sentence. The following word will be checked to start with a capital letter. If not then it is highlighted @@ -8086,7 +8048,7 @@ return { { abbreviation = 'spf', cb = 'did_set_spellfile', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ Name of the word list file where words are added for the |zg| and |zw| @@ -8123,7 +8085,7 @@ return { { abbreviation = 'spl', cb = 'did_set_spelllang', - defaults = { if_true = 'en' }, + defaults = 'en', deny_duplicates = true, desc = [=[ A comma-separated list of word list names. When the 'spell' option is @@ -8175,7 +8137,7 @@ return { { abbreviation = 'spo', cb = 'did_set_spelloptions', - defaults = { if_true = '' }, + defaults = '', values = { 'camel', 'noplainbuffer' }, flags = true, deny_duplicates = true, @@ -8190,7 +8152,6 @@ return { designated regions of the buffer are spellchecked in this case. ]=], - expand_cb = 'expand_set_spelloptions', full_name = 'spelloptions', list = 'onecomma', redraw = { 'current_buffer', 'highlight_only' }, @@ -8202,7 +8163,7 @@ return { { abbreviation = 'sps', cb = 'did_set_spellsuggest', - defaults = { if_true = 'best' }, + defaults = 'best', -- Keep this in sync with spell_check_sps(). values = { 'best', 'fast', 'double', 'expr:', 'file:', 'timeout:' }, deny_duplicates = true, @@ -8274,7 +8235,6 @@ return { security reasons. ]=], expand = true, - expand_cb = 'expand_set_spellsuggest', full_name = 'spellsuggest', list = 'onecomma', scope = { 'global' }, @@ -8285,7 +8245,7 @@ return { }, { abbreviation = 'sb', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, splitting a window will put the new window below the current one. |:split| @@ -8298,8 +8258,7 @@ return { }, { abbreviation = 'spk', - cb = 'did_set_splitkeep', - defaults = { if_true = 'cursor' }, + defaults = 'cursor', values = { 'cursor', 'screen', 'topline' }, desc = [=[ The value of this option determines the scroll behavior when opening, @@ -8315,7 +8274,6 @@ return { with the previous cursor position. For "screen", the text cannot always be kept on the same screen line when 'wrap' is enabled. ]=], - expand_cb = 'expand_set_splitkeep', full_name = 'splitkeep', scope = { 'global' }, short_desc = N_('determines scroll behavior for split windows'), @@ -8324,7 +8282,7 @@ return { }, { abbreviation = 'spr', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, splitting a window will put the new window right of the current one. |:vsplit| @@ -8337,7 +8295,7 @@ return { }, { abbreviation = 'sol', - defaults = { if_true = false }, + defaults = false, desc = [=[ When "on" the commands listed below move the cursor to the first non-blank of the line. When off the cursor is kept in the same column @@ -8361,7 +8319,7 @@ return { { abbreviation = 'stc', cb = 'did_set_statuscolumn', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty, this option determines the content of the area to the side of a window, normally containing the fold, sign and number columns. @@ -8426,7 +8384,7 @@ return { { abbreviation = 'stl', cb = 'did_set_statusline', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty, this option determines the content of the status line. Also see |status-line|. @@ -8651,7 +8609,7 @@ return { }, { abbreviation = 'su', - defaults = { if_true = '.bak,~,.o,.h,.info,.swp,.obj' }, + defaults = '.bak,~,.o,.h,.info,.swp,.obj', deny_duplicates = true, desc = [=[ Files with these suffixes get a lower priority when multiple files @@ -8674,7 +8632,7 @@ return { }, { abbreviation = 'sua', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ Comma-separated list of suffixes, which are used when searching for a @@ -8692,7 +8650,7 @@ return { { abbreviation = 'swf', cb = 'did_set_swapfile', - defaults = { if_true = true }, + defaults = true, desc = [=[ Use a swapfile for the buffer. This option can be reset when a swapfile is not wanted for a specific buffer. For example, with @@ -8722,8 +8680,7 @@ return { }, { abbreviation = 'swb', - cb = 'did_set_switchbuf', - defaults = { if_true = 'uselast' }, + defaults = 'uselast', values = { 'useopen', 'usetab', 'split', 'newtab', 'vsplit', 'uselast' }, flags = true, deny_duplicates = true, @@ -8756,17 +8713,17 @@ return { If a window has 'winfixbuf' enabled, 'switchbuf' is currently not applied to the split window. ]=], - expand_cb = 'expand_set_switchbuf', full_name = 'switchbuf', list = 'onecomma', scope = { 'global' }, short_desc = N_('sets behavior when switching to another buffer'), type = 'string', varname = 'p_swb', + flags_varname = 'swb_flags', }, { abbreviation = 'smc', - defaults = { if_true = 3000 }, + defaults = 3000, desc = [=[ Maximum column in which to search for syntax items. In long lines the text after this column is not highlighted and following lines may not @@ -8785,7 +8742,7 @@ return { { abbreviation = 'syn', cb = 'did_set_filetype_or_syntax', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When this option is set, the syntax with this name is loaded, unless syntax highlighting has been switched off with ":syntax off". @@ -8821,8 +8778,7 @@ return { }, { abbreviation = 'tcl', - cb = 'did_set_tabclose', - defaults = { if_true = '' }, + defaults = '', values = { 'left', 'uselast' }, flags = true, deny_duplicates = true, @@ -8837,18 +8793,18 @@ return { possible. This option takes precedence over the others. ]=], - expand_cb = 'expand_set_tabclose', full_name = 'tabclose', list = 'onecomma', scope = { 'global' }, short_desc = N_('which tab page to focus when closing a tab'), type = 'string', varname = 'p_tcl', + flags_varname = 'tcl_flags', }, { abbreviation = 'tal', cb = 'did_set_tabline', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty, this option determines the content of the tab pages line at the top of the Vim window. When empty Vim will use a default @@ -8881,7 +8837,7 @@ return { }, { abbreviation = 'tpm', - defaults = { if_true = 50 }, + defaults = 50, desc = [=[ Maximum number of tab pages to be opened by the |-p| command line argument or the ":tab all" command. |tabpage| @@ -8895,7 +8851,7 @@ return { { abbreviation = 'ts', cb = 'did_set_shiftwidth_tabstop', - defaults = { if_true = 8 }, + defaults = 8, desc = [=[ Number of spaces that a in the file counts for. Also see the |:retab| command, and the 'softtabstop' option. @@ -8946,7 +8902,7 @@ return { }, { abbreviation = 'tbs', - defaults = { if_true = true }, + defaults = true, desc = [=[ When searching for a tag (e.g., for the |:ta| command), Vim can either use a binary search or a linear search in a tags file. Binary @@ -9006,7 +8962,7 @@ return { { abbreviation = 'tc', cb = 'did_set_tagcase', - defaults = { if_true = 'followic' }, + defaults = 'followic', values = { 'followic', 'ignore', 'match', 'followscs', 'smart' }, flags = true, desc = [=[ @@ -9018,17 +8974,17 @@ return { match Match case smart Ignore case unless an upper case letter is used ]=], - expand_cb = 'expand_set_tagcase', full_name = 'tagcase', scope = { 'global', 'buf' }, short_desc = N_('how to handle case when searching in tags files'), type = 'string', varname = 'p_tc', + flags_varname = 'tc_flags', }, { abbreviation = 'tfu', cb = 'did_set_tagfunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This option specifies a function to be used to perform tag searches. The function gets the tag pattern and should return a List of matching @@ -9049,7 +9005,7 @@ return { }, { abbreviation = 'tl', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ If non-zero, tags are significant up to this number of characters. ]=], @@ -9061,7 +9017,7 @@ return { }, { abbreviation = 'tr', - defaults = { if_true = true }, + defaults = true, desc = [=[ If on and using a tags file in another directory, file names in that tags file are relative to the directory where the tags file is. @@ -9074,7 +9030,7 @@ return { }, { abbreviation = 'tag', - defaults = { if_true = './tags;,tags' }, + defaults = './tags;,tags', deny_duplicates = true, desc = [=[ Filenames for the tag command, separated by spaces or commas. To @@ -9106,7 +9062,7 @@ return { }, { abbreviation = 'tgst', - defaults = { if_true = true }, + defaults = true, desc = [=[ When on, the |tagstack| is used normally. When off, a ":tag" or ":tselect" command with an argument will not push the tag onto the @@ -9124,7 +9080,7 @@ return { }, { abbreviation = 'tbidi', - defaults = { if_true = false }, + defaults = false, desc = [=[ The terminal is in charge of Bi-directionality of text (as specified by Unicode). The terminal is also expected to do the required shaping @@ -9143,7 +9099,7 @@ return { }, { abbreviation = 'tenc', - defaults = { if_true = '' }, + defaults = '', full_name = 'termencoding', scope = { 'global' }, short_desc = N_('Terminal encoding'), @@ -9152,7 +9108,7 @@ return { }, { abbreviation = 'tgc', - defaults = { if_true = false }, + defaults = false, desc = [=[ Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight| attributes instead of "cterm" attributes. |guifg| @@ -9171,8 +9127,7 @@ return { }, { abbreviation = 'tpf', - cb = 'did_set_termpastefilter', - defaults = { if_true = 'BS,HT,ESC,DEL' }, + defaults = 'BS,HT,ESC,DEL', values = { 'BS', 'HT', 'FF', 'ESC', 'DEL', 'C0', 'C1' }, flags = true, deny_duplicates = true, @@ -9196,15 +9151,15 @@ return { C1 Control characters 0x80...0x9F ]=], - expand_cb = 'expand_set_termpastefilter', full_name = 'termpastefilter', list = 'onecomma', scope = { 'global' }, type = 'string', varname = 'p_tpf', + flags_varname = 'tpf_flags', }, { - defaults = { if_true = true }, + defaults = true, desc = [=[ If the host terminal supports it, buffer all screen updates made during a redraw cycle so that each screen is displayed in @@ -9219,7 +9174,7 @@ return { varname = 'p_termsync', }, { - defaults = { if_true = false }, + defaults = false, full_name = 'terse', scope = { 'global' }, short_desc = N_('No description'), @@ -9229,7 +9184,7 @@ return { { abbreviation = 'tw', cb = 'did_set_textwidth', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Maximum width of text that is being inserted. A longer line will be broken after white space to get this width. A zero value disables @@ -9247,7 +9202,7 @@ return { }, { abbreviation = 'tsr', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ List of file names, separated by commas, that are used to lookup words @@ -9277,7 +9232,7 @@ return { { abbreviation = 'tsrfu', cb = 'did_set_thesaurusfunc', - defaults = { if_true = '' }, + defaults = '', desc = [=[ This option specifies a function to be used for thesaurus completion with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|. @@ -9297,7 +9252,7 @@ return { }, { abbreviation = 'top', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on: The tilde command "~" behaves like an operator. ]=], @@ -9309,7 +9264,7 @@ return { }, { abbreviation = 'to', - defaults = { if_true = true }, + defaults = true, desc = [=[ This option and 'timeoutlen' determine the behavior when part of a mapped key sequence has been received. For example, if is @@ -9324,7 +9279,7 @@ return { }, { abbreviation = 'tm', - defaults = { if_true = 1000 }, + defaults = 1000, desc = [=[ Time in milliseconds to wait for a mapped sequence to complete. ]=], @@ -9336,7 +9291,7 @@ return { }, { cb = 'did_set_title_icon', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, the title of the window will be set to the value of 'titlestring' (if it is not empty), or to: @@ -9358,7 +9313,7 @@ return { }, { cb = 'did_set_titlelen', - defaults = { if_true = 85 }, + defaults = 85, desc = [=[ Gives the percentage of 'columns' to use for the length of the window title. When the title is longer, only the end of the path name is @@ -9377,7 +9332,7 @@ return { varname = 'p_titlelen', }, { - defaults = { if_true = '' }, + defaults = '', desc = [=[ If not empty, this option will be used to set the window title when exiting. Only if 'title' is enabled. @@ -9394,7 +9349,7 @@ return { }, { cb = 'did_set_titlestring', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When this option is not empty, it will be used for the title of the window. This happens only when the 'title' option is on. @@ -9430,7 +9385,7 @@ return { varname = 'p_titlestring', }, { - defaults = { if_true = true }, + defaults = true, desc = [=[ This option and 'ttimeoutlen' determine the behavior when part of a key code sequence has been received by the |TUI|. @@ -9453,7 +9408,7 @@ return { }, { abbreviation = 'ttm', - defaults = { if_true = 50 }, + defaults = 50, desc = [=[ Time in milliseconds to wait for a key code sequence to complete. Also used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has @@ -9468,7 +9423,7 @@ return { }, { abbreviation = 'tf', - defaults = { if_true = true }, + defaults = true, full_name = 'ttyfast', no_mkrc = true, scope = { 'global' }, @@ -9478,7 +9433,7 @@ return { }, { abbreviation = 'udir', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ List of directory names for undo files, separated with commas. @@ -9515,7 +9470,7 @@ return { { abbreviation = 'udf', cb = 'did_set_undofile', - defaults = { if_true = false }, + defaults = false, desc = [=[ When on, Vim automatically saves undo history to an undo file when writing a buffer to a file, and restores undo history from the same @@ -9535,7 +9490,7 @@ return { { abbreviation = 'ul', cb = 'did_set_undolevels', - defaults = { if_true = 1000 }, + defaults = 1000, desc = [=[ Maximum number of changes that can be undone. Since undo information is kept in memory, higher numbers will cause more memory to be used. @@ -9563,7 +9518,7 @@ return { }, { abbreviation = 'ur', - defaults = { if_true = 10000 }, + defaults = 10000, desc = [=[ Save the whole buffer for undo when reloading it. This applies to the ":e!" command and reloading for when the buffer changed outside of @@ -9586,7 +9541,7 @@ return { { abbreviation = 'uc', cb = 'did_set_updatecount', - defaults = { if_true = 200 }, + defaults = 200, desc = [=[ After typing this many characters the swap file will be written to disk. When zero, no swap file will be created at all (see chapter on @@ -9608,7 +9563,7 @@ return { }, { abbreviation = 'ut', - defaults = { if_true = 4000 }, + defaults = 4000, desc = [=[ If this many milliseconds nothing is typed the swap file will be written to disk (see |crash-recovery|). Also used for the @@ -9623,7 +9578,7 @@ return { { abbreviation = 'vsts', cb = 'did_set_varsofttabstop', - defaults = { if_true = '' }, + defaults = '', desc = [=[ A list of the number of spaces that a counts for while editing, such as inserting a or using . It "feels" like variable- @@ -9651,7 +9606,7 @@ return { { abbreviation = 'vts', cb = 'did_set_vartabstop', - defaults = { if_true = '' }, + defaults = '', desc = [=[ A list of the number of spaces that a in the file counts for, separated by commas. Each value corresponds to one tab, with the @@ -9673,7 +9628,7 @@ return { }, { abbreviation = 'vbs', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Sets the verbosity level. Also set by |-V| and |:verbose|. @@ -9712,7 +9667,7 @@ return { { abbreviation = 'vfile', cb = 'did_set_verbosefile', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When not empty all messages are written in a file with this name. When the file exists messages are appended. @@ -9734,7 +9689,7 @@ return { }, { abbreviation = 'vdir', - defaults = { if_true = '' }, + defaults = '', desc = [=[ Name of the directory where to store files for |:mkview|. This option cannot be set from a |modeline| or in the |sandbox|, for @@ -9750,8 +9705,8 @@ return { }, { abbreviation = 'vop', - cb = 'did_set_viewoptions', - defaults = { if_true = 'folds,cursor,curdir' }, + cb = 'did_set_str_generic', + defaults = 'folds,cursor,curdir', flags = true, deny_duplicates = true, desc = [=[ @@ -9768,18 +9723,19 @@ return { slash |deprecated| Always enabled. Uses "/" in filenames. unix |deprecated| Always enabled. Uses "\n" line endings. ]=], - expand_cb = 'expand_set_sessionoptions', + expand_cb = 'expand_set_str_generic', full_name = 'viewoptions', list = 'onecomma', scope = { 'global' }, short_desc = N_('specifies what to save for :mkview'), type = 'string', varname = 'p_vop', + flags_varname = 'vop_flags', }, { abbreviation = 've', cb = 'did_set_virtualedit', - defaults = { if_true = '' }, + defaults = '', values = { 'block', 'insert', 'all', 'onemore', 'none', 'NONE' }, flags = { Block = 5, @@ -9818,7 +9774,6 @@ return { not get a warning for it. When combined with other words, "none" is ignored. ]=], - expand_cb = 'expand_set_virtualedit', full_name = 'virtualedit', list = 'onecomma', redraw = { 'curswant' }, @@ -9826,10 +9781,11 @@ return { short_desc = N_('when to use virtual editing'), type = 'string', varname = 'p_ve', + flags_varname = 've_flags', }, { abbreviation = 'vb', - defaults = { if_true = false }, + defaults = false, desc = [=[ Use visual bell instead of beeping. Also see 'errorbells'. ]=], @@ -9840,7 +9796,7 @@ return { varname = 'p_vb', }, { - defaults = { if_true = true }, + defaults = true, desc = [=[ Give a warning message when a shell command is used while the buffer has been changed. @@ -9854,7 +9810,7 @@ return { { abbreviation = 'ww', cb = 'did_set_whichwrap', - defaults = { if_true = 'b,s' }, + defaults = 'b,s', desc = [=[ Allow specified keys that move the cursor left/right to move to the previous/next line when the cursor is on the first/last character in @@ -9925,7 +9881,7 @@ return { { abbreviation = 'wcm', cb = 'did_set_wildchar', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ 'wildcharm' works exactly like 'wildchar', except that it is recognized when used inside a macro. You can find "spare" command-line @@ -9944,7 +9900,7 @@ return { }, { abbreviation = 'wig', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ A list of file patterns. A file that matches with one of these @@ -9968,7 +9924,7 @@ return { }, { abbreviation = 'wic', - defaults = { if_true = false }, + defaults = false, desc = [=[ When set case is ignored when completing file names and directories. Has no effect when 'fileignorecase' is set. @@ -9983,7 +9939,7 @@ return { }, { abbreviation = 'wmnu', - defaults = { if_true = true }, + defaults = true, desc = [=[ When 'wildmenu' is on, command-line completion operates in an enhanced mode. On pressing 'wildchar' (usually ) to invoke completion, @@ -10032,7 +9988,7 @@ return { { abbreviation = 'wim', cb = 'did_set_wildmode', - defaults = { if_true = 'full' }, + defaults = 'full', -- Keep this in sync with check_opt_wim(). values = { 'full', 'longest', 'list', 'lastused' }, flags = true, @@ -10082,7 +10038,6 @@ return { < Complete longest common string, then list alternatives. More info here: |cmdline-completion|. ]=], - expand_cb = 'expand_set_wildmode', full_name = 'wildmode', list = 'onecommacolon', scope = { 'global' }, @@ -10092,8 +10047,7 @@ return { }, { abbreviation = 'wop', - cb = 'did_set_wildoptions', - defaults = { if_true = 'pum,tagfile' }, + defaults = 'pum,tagfile', values = { 'fuzzy', 'tagfile', 'pum' }, flags = true, deny_duplicates = true, @@ -10116,18 +10070,17 @@ return { d #define f function ]=], - expand_cb = 'expand_set_wildoptions', full_name = 'wildoptions', list = 'onecomma', scope = { 'global' }, short_desc = N_('specifies how command line completion is done'), type = 'string', varname = 'p_wop', + flags_varname = 'wop_flags', }, { abbreviation = 'wak', - cb = 'did_set_winaltkeys', - defaults = { if_true = 'menu' }, + defaults = 'menu', values = { 'yes', 'menu', 'no' }, desc = [=[ only used in Win32 @@ -10146,7 +10099,6 @@ return { key is never used for the menu. This option is not used for ; on Win32. ]=], - expand_cb = 'expand_set_winaltkeys', full_name = 'winaltkeys', scope = { 'global' }, short_desc = N_('when the windows system handles ALT keys'), @@ -10156,7 +10108,7 @@ return { { abbreviation = 'wbr', cb = 'did_set_winbar', - defaults = { if_true = '' }, + defaults = '', desc = [=[ When non-empty, this option enables the window bar and determines its contents. The window bar is a bar that's shown at the top of every @@ -10183,7 +10135,7 @@ return { { abbreviation = 'winbl', cb = 'did_set_winblend', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Enables pseudo-transparency for a floating window. Valid values are in the range of 0 for fully opaque window (disabled) to 100 for fully @@ -10223,7 +10175,7 @@ return { }, { abbreviation = 'wfb', - defaults = { if_true = false }, + defaults = false, desc = [=[ If enabled, the window and the buffer it is displaying are paired. For example, attempting to change the buffer with |:edit| will fail. @@ -10238,7 +10190,7 @@ return { }, { abbreviation = 'wfh', - defaults = { if_true = false }, + defaults = false, desc = [=[ Keep the window height when windows are opened or closed and 'equalalways' is set. Also for |CTRL-W_=|. Set by default for the @@ -10253,7 +10205,7 @@ return { }, { abbreviation = 'wfw', - defaults = { if_true = false }, + defaults = false, desc = [=[ Keep the window width when windows are opened or closed and 'equalalways' is set. Also for |CTRL-W_=|. @@ -10268,7 +10220,7 @@ return { { abbreviation = 'wh', cb = 'did_set_winheight', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ Minimal number of lines for the current window. This is not a hard minimum, Vim will use fewer lines if there is not enough room. If the @@ -10297,7 +10249,7 @@ return { { abbreviation = 'winhl', cb = 'did_set_winhighlight', - defaults = { if_true = '' }, + defaults = '', deny_duplicates = true, desc = [=[ Window-local highlights. Comma-delimited list of highlight @@ -10329,7 +10281,7 @@ return { { abbreviation = 'wmh', cb = 'did_set_winminheight', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ The minimal height of a window, when it's not the current window. This is a hard minimum, windows will never become smaller. @@ -10350,7 +10302,7 @@ return { { abbreviation = 'wmw', cb = 'did_set_winminwidth', - defaults = { if_true = 1 }, + defaults = 1, desc = [=[ The minimal width of a window, when it's not the current window. This is a hard minimum, windows will never become smaller. @@ -10372,7 +10324,7 @@ return { { abbreviation = 'wiw', cb = 'did_set_winwidth', - defaults = { if_true = 20 }, + defaults = 20, desc = [=[ Minimal number of columns for the current window. This is not a hard minimum, Vim will use fewer columns if there is not enough room. If @@ -10393,7 +10345,7 @@ return { }, { cb = 'did_set_wrap', - defaults = { if_true = true }, + defaults = true, desc = [=[ This option changes how text is displayed. It doesn't change the text in the buffer, see 'textwidth' for that. @@ -10419,7 +10371,7 @@ return { }, { abbreviation = 'wm', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Number of characters from the right window border where wrapping starts. When typing text beyond this limit, an will be inserted @@ -10437,7 +10389,7 @@ return { }, { abbreviation = 'ws', - defaults = { if_true = true }, + defaults = true, desc = [=[ Searches wrap around the end of the file. Also applies to |]s| and |[s|, searching for spelling mistakes. @@ -10450,7 +10402,7 @@ return { varname = 'p_ws', }, { - defaults = { if_true = true }, + defaults = true, desc = [=[ Allows writing files. When not set, writing a file is not allowed. Can be used for a view-only mode, where modifications to the text are @@ -10466,7 +10418,7 @@ return { }, { abbreviation = 'wa', - defaults = { if_true = false }, + defaults = false, desc = [=[ Allows writing to any file with no need for "!" override. ]=], @@ -10478,7 +10430,7 @@ return { }, { abbreviation = 'wb', - defaults = { if_true = true }, + defaults = true, desc = [=[ Make a backup before overwriting a file. The backup is removed after the file was successfully written, unless the 'backup' option is @@ -10501,7 +10453,7 @@ return { }, { abbreviation = 'wd', - defaults = { if_true = 0 }, + defaults = 0, desc = [=[ Only takes effect together with 'redrawdebug'. The number of milliseconds to wait after each line or each flush @@ -10514,3 +10466,29 @@ return { }, }, } + +--- @param o vim.option_meta +local function preprocess(o) + if o.values then + o.cb = o.cb or 'did_set_str_generic' + o.expand_cb = o.expand_cb or 'expand_set_str_generic' + end + + if type(o.alias) == 'string' then + o.alias = { + o.alias --[[@as string]], + } + end + + if type(o.defaults) ~= 'table' then + o.defaults = { + if_true = o.defaults --[[@as string|boolean|number ]], + } + end +end + +for _, o in ipairs(options.options) do + preprocess(o) +end + +return options diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 93871905db..be5047f814 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -80,23 +80,23 @@ static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES, /// option values. void didset_string_options(void) { - opt_strings_flags(p_cmp, opt_cmp_values, &cmp_flags, true); - opt_strings_flags(p_bkc, opt_bkc_values, &bkc_flags, true); - opt_strings_flags(p_bo, opt_bo_values, &bo_flags, true); - opt_strings_flags(p_cot, opt_cot_values, &cot_flags, true); - opt_strings_flags(p_ssop, opt_ssop_values, &ssop_flags, true); - opt_strings_flags(p_vop, opt_ssop_values, &vop_flags, true); - opt_strings_flags(p_fdo, opt_fdo_values, &fdo_flags, true); - opt_strings_flags(p_dy, opt_dy_values, &dy_flags, true); - opt_strings_flags(p_jop, opt_jop_values, &jop_flags, true); - opt_strings_flags(p_rdb, opt_rdb_values, &rdb_flags, true); - opt_strings_flags(p_tc, opt_tc_values, &tc_flags, false); - opt_strings_flags(p_tpf, opt_tpf_values, &tpf_flags, true); - opt_strings_flags(p_ve, opt_ve_values, &ve_flags, true); - opt_strings_flags(p_swb, opt_swb_values, &swb_flags, true); - opt_strings_flags(p_tcl, opt_tcl_values, &tcl_flags, true); - opt_strings_flags(p_wop, opt_wop_values, &wop_flags, true); - opt_strings_flags(p_cb, opt_cb_values, &cb_flags, true); + check_str_opt(kOptCasemap, NULL); + check_str_opt(kOptBackupcopy, NULL); + check_str_opt(kOptBelloff, NULL); + check_str_opt(kOptCompleteopt, NULL); + check_str_opt(kOptSessionoptions, NULL); + check_str_opt(kOptViewoptions, NULL); + check_str_opt(kOptFoldopen, NULL); + check_str_opt(kOptDisplay, NULL); + check_str_opt(kOptJumpoptions, NULL); + check_str_opt(kOptRedrawdebug, NULL); + check_str_opt(kOptTagcase, NULL); + check_str_opt(kOptTermpastefilter, NULL); + check_str_opt(kOptVirtualedit, NULL); + check_str_opt(kOptSwitchbuf, NULL); + check_str_opt(kOptTabclose, NULL); + check_str_opt(kOptWildoptions, NULL); + check_str_opt(kOptClipboard, NULL); } char *illegal_char(char *errbuf, size_t errbuflen, int c) @@ -222,7 +222,7 @@ int check_signcolumn(char *scl, win_T *wp) return FAIL; } - if (check_opt_strings(val, opt_scl_values, false) == OK) { + if (opt_strings_flags(val, opt_scl_values, NULL, false) == OK) { if (wp == NULL) { return OK; } @@ -358,11 +358,40 @@ static const char *did_set_opt_flags(char *val, const char **values, unsigned *f return NULL; } -/// An option that accepts a list of string values is changed. -/// e.g. 'nrformats', 'scrollopt', 'wildoptions', etc. -static const char *did_set_opt_strings(char *val, const char **values, bool list) +static const char **opt_values(OptIndex idx, size_t *values_len) { - return did_set_opt_flags(val, values, NULL, list); + OptIndex idx1 = idx == kOptViewoptions ? kOptSessionoptions + : idx == kOptFileformats ? kOptFileformat + : idx; + + vimoption_T *opt = get_option(idx1); + if (values_len != NULL) { + *values_len = opt->values_len; + } + return opt->values; +} + +static int check_str_opt(OptIndex idx, char **varp) +{ + vimoption_T *opt = get_option(idx); + if (varp == NULL) { + varp = opt->var; + } + bool list = opt->flags & (kOptFlagComma | kOptFlagOneComma); + const char **values = opt_values(idx, NULL); + return opt_strings_flags(*varp, values, opt->flags_var, list); +} + +int expand_set_str_generic(optexpand_T *args, int *numMatches, char ***matches) +{ + size_t values_len; + const char **values = opt_values(args->oe_idx, &values_len); + return expand_set_opt_string(args, values, values_len, numMatches, matches); +} + +const char *did_set_str_generic(optset_T *args) +{ + return check_str_opt(args->os_idx, args->os_varp) != OK ? e_invarg : NULL; } /// An option which is a list of flags is set. Valid values are in "flags". @@ -490,28 +519,30 @@ static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatch } /// The 'ambiwidth' option is changed. -const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED) +const char *did_set_ambiwidth(optset_T *args) { - if (check_opt_strings(p_ambw, opt_ambw_values, false) != OK) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } return check_chars_options(); } -int expand_set_ambiwidth(optexpand_T *args, int *numMatches, char ***matches) +/// The 'emoji' option is changed. +const char *did_set_emoji(optset_T *args) { - return expand_set_opt_string(args, - opt_ambw_values, - ARRAY_SIZE(opt_ambw_values) - 1, - numMatches, - matches); + if (check_str_opt(kOptAmbiwidth, NULL) != OK) { + return e_invarg; + } + return check_chars_options(); } /// The 'background' option is changed. const char *did_set_background(optset_T *args) { - if (check_opt_strings(p_bg, opt_bg_values, false) != OK) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } if (args->os_oldval.string.data[0] == *p_bg) { @@ -545,15 +576,6 @@ const char *did_set_background(optset_T *args) return NULL; } -int expand_set_background(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_bg_values, - ARRAY_SIZE(opt_bg_values) - 1, - numMatches, - matches); -} - /// The 'backspace' option is changed. const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED) { @@ -561,19 +583,9 @@ const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED) if (*p_bs != '2') { return e_invarg; } - } else if (check_opt_strings(p_bs, opt_bs_values, true) != OK) { - return e_invarg; + return NULL; } - return NULL; -} - -int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_bs_values, - ARRAY_SIZE(opt_bs_values) - 1, - numMatches, - matches); + return did_set_str_generic(args); } /// The 'backupcopy' option is changed. @@ -613,15 +625,6 @@ const char *did_set_backupcopy(optset_T *args) return NULL; } -int expand_set_backupcopy(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_bkc_values, - ARRAY_SIZE(opt_bkc_values) - 1, - numMatches, - matches); -} - /// The 'backupext' or the 'patchmode' option is changed. const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED) { @@ -633,21 +636,6 @@ const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -/// The 'belloff' option is changed. -const char *did_set_belloff(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_bo, opt_bo_values, &bo_flags, true); -} - -int expand_set_belloff(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_bo_values, - ARRAY_SIZE(opt_bo_values) - 1, - numMatches, - matches); -} - /// The 'breakat' option is changed. const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED) { @@ -682,29 +670,11 @@ const char *did_set_breakindentopt(optset_T *args) return NULL; } -int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_briopt_values, - ARRAY_SIZE(opt_briopt_values) - 1, - numMatches, - matches); -} - /// The 'bufhidden' option is changed. const char *did_set_bufhidden(optset_T *args) { buf_T *buf = (buf_T *)args->os_buf; - return did_set_opt_strings(buf->b_p_bh, opt_bh_values, false); -} - -int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_bh_values, - ARRAY_SIZE(opt_bh_values) - 1, - numMatches, - matches); + return did_set_opt_flags(buf->b_p_bh, opt_bh_values, NULL, false); } /// The 'buftype' option is changed. @@ -715,7 +685,7 @@ const char *did_set_buftype(optset_T *args) // When 'buftype' is set, check for valid value. if ((buf->terminal && buf->b_p_bt[0] != 't') || (!buf->terminal && buf->b_p_bt[0] == 't') - || check_opt_strings(buf->b_p_bt, opt_bt_values, false) != OK) { + || opt_strings_flags(buf->b_p_bt, opt_bt_values, NULL, false) != OK) { return e_invarg; } if (win->w_status_height || global_stl_height()) { @@ -727,30 +697,6 @@ const char *did_set_buftype(optset_T *args) return NULL; } -int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_bt_values, - ARRAY_SIZE(opt_bt_values) - 1, - numMatches, - matches); -} - -/// The 'casemap' option is changed. -const char *did_set_casemap(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_cmp, opt_cmp_values, &cmp_flags, true); -} - -int expand_set_casemap(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_cmp_values, - ARRAY_SIZE(opt_cmp_values) - 1, - numMatches, - matches); -} - /// The global 'listchars' or 'fillchars' option is changed. static const char *did_set_global_chars_option(win_T *win, char *val, CharsOption what, int opt_flags, char *errbuf, size_t errbuflen) @@ -834,21 +780,6 @@ const char *did_set_cinoptions(optset_T *args) return NULL; } -/// The 'clipboard' option is changed. -const char *did_set_clipboard(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_cb, opt_cb_values, &cb_flags, true); -} - -int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_cb_values, - ARRAY_SIZE(opt_cb_values) - 1, - numMatches, - matches); -} - /// The 'colorcolumn' option is changed. const char *did_set_colorcolumn(optset_T *args) { @@ -940,15 +871,6 @@ const char *did_set_complete(optset_T *args) return NULL; } -int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_cpt_values, - ARRAY_SIZE(opt_cpt_values) - 1, - numMatches, - matches); -} - /// The 'completeitemalign' option is changed. const char *did_set_completeitemalign(optset_T *args) { @@ -1009,7 +931,7 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) buf->b_cot_flags = 0; } - if (check_opt_strings(cot, opt_cot_values, true) != OK) { + if (opt_strings_flags(cot, opt_cot_values, NULL, true) != OK) { return e_invarg; } @@ -1020,35 +942,17 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_cot_values, - ARRAY_SIZE(opt_cot_values) - 1, - numMatches, - matches); -} - #ifdef BACKSLASH_IN_FILENAME /// The 'completeslash' option is changed. const char *did_set_completeslash(optset_T *args) { buf_T *buf = (buf_T *)args->os_buf; - if (check_opt_strings(p_csl, opt_csl_values, false) != OK - || check_opt_strings(buf->b_p_csl, opt_csl_values, false) != OK) { + if (opt_strings_flags(p_csl, opt_csl_values, NULL, false) != OK + || opt_strings_flags(buf->b_p_csl, opt_csl_values, NULL, false) != OK) { return e_invarg; } return NULL; } - -int expand_set_completeslash(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_csl_values, - ARRAY_SIZE(opt_csl_values) - 1, - numMatches, - matches); -} #endif /// The 'concealcursor' option is changed. @@ -1091,37 +995,10 @@ const char *did_set_cursorlineopt(optset_T *args) return NULL; } -int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_culopt_values, - ARRAY_SIZE(opt_culopt_values) - 1, - numMatches, - matches); -} - -/// The 'debug' option is changed. -const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_debug, opt_debug_values, true); -} - -int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_debug_values, - ARRAY_SIZE(opt_debug_values) - 1, - numMatches, - matches); -} - /// The 'diffopt' option is changed. const char *did_set_diffopt(optset_T *args FUNC_ATTR_UNUSED) { - if (diffopt_changed() == FAIL) { - return e_invarg; - } - return NULL; + return diffopt_changed() == FAIL ? e_invarg : NULL; } int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) @@ -1142,48 +1019,21 @@ int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) return FAIL; } - return expand_set_opt_string(args, - opt_dip_values, - ARRAY_SIZE(opt_dip_values) - 1, - numMatches, - matches); + return expand_set_str_generic(args, numMatches, matches); } /// The 'display' option is changed. -const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED) +const char *did_set_display(optset_T *args) { - if (opt_strings_flags(p_dy, opt_dy_values, &dy_flags, true) != OK) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } init_chartab(); msg_grid_validate(); return NULL; } -int expand_set_display(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_dy_values, - ARRAY_SIZE(opt_dy_values) - 1, - numMatches, - matches); -} - -/// The 'eadirection' option is changed. -const char *did_set_eadirection(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_ead, opt_ead_values, false); -} - -int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_ead_values, - ARRAY_SIZE(opt_ead_values) - 1, - numMatches, - matches); -} - /// One of the 'encoding', 'fileencoding' or 'makeencoding' /// options is changed. const char *did_set_encoding(optset_T *args) @@ -1259,14 +1109,17 @@ int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches) const char *did_set_fileformat(optset_T *args) { buf_T *buf = (buf_T *)args->os_buf; - char **varp = (char **)args->os_varp; const char *oldval = args->os_oldval.string.data; int opt_flags = args->os_flags; if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { return e_modifiable; - } else if (check_opt_strings(*varp, opt_ff_values, false) != OK) { - return e_invarg; } + + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; + } + redraw_titles(); // update flag in swap file ml_setflags(buf); @@ -1278,15 +1131,6 @@ const char *did_set_fileformat(optset_T *args) return NULL; } -int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_ff_values, - ARRAY_SIZE(opt_ff_values) - 1, - numMatches, - matches); -} - /// Function given to ExpandGeneric() to obtain the possible arguments of the /// fileformat options. char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) @@ -1298,12 +1142,6 @@ char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) return (char *)opt_ff_values[idx]; } -/// The 'fileformats' option is changed. -const char *did_set_fileformats(optset_T *args) -{ - return did_set_opt_strings(p_ffs, opt_ff_values, true); -} - /// The 'filetype' or the 'syntax' option is changed. const char *did_set_filetype_or_syntax(optset_T *args) { @@ -1322,40 +1160,6 @@ const char *did_set_filetype_or_syntax(optset_T *args) return NULL; } -/// The 'foldclose' option is changed. -const char *did_set_foldclose(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_fcl, opt_fcl_values, true); -} - -int expand_set_foldclose(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_fcl_values, - ARRAY_SIZE(opt_fcl_values) - 1, - numMatches, - matches); -} - -/// The 'foldcolumn' option is changed. -const char *did_set_foldcolumn(optset_T *args) -{ - char **varp = (char **)args->os_varp; - if (**varp == NUL || check_opt_strings(*varp, opt_fdc_values, false) != OK) { - return e_invarg; - } - return NULL; -} - -int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_fdc_values, - ARRAY_SIZE(opt_fdc_values) - 1, - numMatches, - matches); -} - /// The 'foldexpr' option is changed. const char *did_set_foldexpr(optset_T *args) { @@ -1402,11 +1206,11 @@ const char *did_set_foldmarker(optset_T *args) /// The 'foldmethod' option is changed. const char *did_set_foldmethod(optset_T *args) { - win_T *win = (win_T *)args->os_win; - char **varp = (char **)args->os_varp; - if (check_opt_strings(*varp, opt_fdm_values, false) != OK || **varp == NUL) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } + win_T *win = (win_T *)args->os_win; foldUpdateAll(win); if (foldmethodIsDiff(win)) { newFoldLevel(); @@ -1414,30 +1218,6 @@ const char *did_set_foldmethod(optset_T *args) return NULL; } -int expand_set_foldmethod(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_fdm_values, - ARRAY_SIZE(opt_fdm_values) - 1, - numMatches, - matches); -} - -/// The 'foldopen' option is changed. -const char *did_set_foldopen(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_fdo, opt_fdo_values, &fdo_flags, true); -} - -int expand_set_foldopen(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_fdo_values, - ARRAY_SIZE(opt_fdo_values) - 1, - numMatches, - matches); -} - /// The 'formatoptions' option is changed. const char *did_set_formatoptions(optset_T *args) { @@ -1516,16 +1296,7 @@ const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED) if (cmdpreview) { return e_invarg; } - return did_set_opt_strings(p_icm, opt_icm_values, false); -} - -int expand_set_inccommand(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_icm_values, - ARRAY_SIZE(opt_icm_values) - 1, - numMatches, - matches); + return did_set_str_generic(args); } /// The 'iskeyword' option is changed. @@ -1559,21 +1330,6 @@ const char *did_set_isopt(optset_T *args) return NULL; } -/// The 'jumpoptions' option is changed. -const char *did_set_jumpoptions(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_jop, opt_jop_values, &jop_flags, true); -} - -int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_jop_values, - ARRAY_SIZE(opt_jop_values) - 1, - numMatches, - matches); -} - /// The 'keymap' option has changed. const char *did_set_keymap(optset_T *args) { @@ -1629,23 +1385,15 @@ const char *did_set_keymap(optset_T *args) /// The 'keymodel' option is changed. const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED) { - if (check_opt_strings(p_km, opt_km_values, true) != OK) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } km_stopsel = (vim_strchr(p_km, 'o') != NULL); km_startsel = (vim_strchr(p_km, 'a') != NULL); return NULL; } -int expand_set_keymodel(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_km_values, - ARRAY_SIZE(opt_km_values) - 1, - numMatches, - matches); -} - /// The 'lispoptions' option is changed. const char *did_set_lispoptions(optset_T *args) { @@ -1657,15 +1405,6 @@ const char *did_set_lispoptions(optset_T *args) return NULL; } -int expand_set_lispoptions(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_lop_values, - ARRAY_SIZE(opt_lop_values) - 1, - numMatches, - matches); -} - /// The 'matchpairs' option is changed. const char *did_set_matchpairs(optset_T *args) { @@ -1702,15 +1441,6 @@ const char *did_set_messagesopt(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -int expand_set_messagesopt(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_mopt_values, - ARRAY_SIZE(opt_mopt_values) - 1, - numMatches, - matches); -} - /// The 'mkspellmem' option is changed. const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED) { @@ -1733,21 +1463,6 @@ int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches) return expand_set_opt_listflag(args, MOUSE_ALL, numMatches, matches); } -/// The 'mousemodel' option is changed. -const char *did_set_mousemodel(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_mousem, opt_mousem_values, false); -} - -int expand_set_mousemodel(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_mousem_values, - ARRAY_SIZE(opt_mousem_values) - 1, - numMatches, - matches); -} - /// Handle setting 'mousescroll'. /// @return error message, NULL if it's OK. const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED) @@ -1813,32 +1528,6 @@ const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -int expand_set_mousescroll(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_mousescroll_values, - ARRAY_SIZE(opt_mousescroll_values) - 1, - numMatches, - matches); -} - -/// The 'nrformats' option is changed. -const char *did_set_nrformats(optset_T *args) -{ - char **varp = (char **)args->os_varp; - - return did_set_opt_strings(*varp, opt_nf_values, true); -} - -int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_nf_values, - ARRAY_SIZE(opt_nf_values) - 1, - numMatches, - matches); -} - /// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext', /// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'. const char *did_set_optexpr(optset_T *args) @@ -1855,60 +1544,18 @@ const char *did_set_optexpr(optset_T *args) return NULL; } -/// The 'redrawdebug' option is changed. -const char *did_set_redrawdebug(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_rdb, opt_rdb_values, &rdb_flags, true); -} - -/// The 'rightleftcmd' option is changed. -const char *did_set_rightleftcmd(optset_T *args) -{ - char **varp = (char **)args->os_varp; - - // Currently only "search" is a supported value. - if (**varp != NUL && strcmp(*varp, "search") != 0) { - return e_invarg; - } - - return NULL; -} - -int expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_rlc_values, - ARRAY_SIZE(opt_rlc_values) - 1, - numMatches, - matches); -} - /// The 'rulerformat' option is changed. const char *did_set_rulerformat(optset_T *args) { return did_set_statustabline_rulerformat(args, true, false); } -/// The 'scrollopt' option is changed. -const char *did_set_scrollopt(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_sbo, opt_sbo_values, true); -} - -int expand_set_scrollopt(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_sbo_values, - ARRAY_SIZE(opt_sbo_values) - 1, - numMatches, - matches); -} - /// The 'selection' option is changed. const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED) { - if (*p_sel == NUL || check_opt_strings(p_sel, opt_sel_values, false) != OK) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } if (VIsual_active) { // Visual selection may be drawn differently. @@ -1917,35 +1564,12 @@ const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -int expand_set_selection(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_sel_values, - ARRAY_SIZE(opt_sel_values) - 1, - numMatches, - matches); -} - -/// The 'selectmode' option is changed. -const char *did_set_selectmode(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_slm, opt_slm_values, true); -} - -int expand_set_selectmode(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_slm_values, - ARRAY_SIZE(opt_slm_values) - 1, - numMatches, - matches); -} - /// The 'sessionoptions' option is changed. const char *did_set_sessionoptions(optset_T *args) { - if (opt_strings_flags(p_ssop, opt_ssop_values, &ssop_flags, true) != OK) { - return e_invarg; + const char *errmsg = did_set_str_generic(args); + if (errmsg != NULL) { + return errmsg; } if ((ssop_flags & kOptSsopFlagCurdir) && (ssop_flags & kOptSsopFlagSesdir)) { // Don't allow both "sesdir" and "curdir". @@ -1956,15 +1580,6 @@ const char *did_set_sessionoptions(optset_T *args) return NULL; } -int expand_set_sessionoptions(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_ssop_values, - ARRAY_SIZE(opt_ssop_values) - 1, - numMatches, - matches); -} - const char *did_set_shada(optset_T *args) { char *errbuf = args->os_errbuf; @@ -2044,7 +1659,7 @@ const char *did_set_showbreak(optset_T *args) /// The 'showcmdloc' option is changed. const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED) { - const char *errmsg = did_set_opt_strings(p_sloc, opt_sloc_values, false); + const char *errmsg = did_set_str_generic(args); if (errmsg == NULL) { comp_col(); @@ -2053,15 +1668,6 @@ const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED) return errmsg; } -int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_sloc_values, - ARRAY_SIZE(opt_sloc_values) - 1, - numMatches, - matches); -} - /// The 'signcolumn' option is changed. const char *did_set_signcolumn(optset_T *args) { @@ -2079,15 +1685,6 @@ const char *did_set_signcolumn(optset_T *args) return NULL; } -int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_scl_values, - ARRAY_SIZE(opt_scl_values) - 1, - numMatches, - matches); -} - /// The 'spellcapcheck' option is changed. const char *did_set_spellcapcheck(optset_T *args) { @@ -2140,15 +1737,6 @@ const char *did_set_spelloptions(optset_T *args) return NULL; } -int expand_set_spelloptions(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_spo_values, - ARRAY_SIZE(opt_spo_values) - 1, - numMatches, - matches); -} - /// The 'spellsuggest' option is changed. const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED) { @@ -2158,30 +1746,6 @@ const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_sps_values, - ARRAY_SIZE(opt_sps_values) - 1, - numMatches, - matches); -} - -/// The 'splitkeep' option is changed. -const char *did_set_splitkeep(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_strings(p_spk, opt_spk_values, false); -} - -int expand_set_splitkeep(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_spk_values, - ARRAY_SIZE(opt_spk_values) - 1, - numMatches, - matches); -} - /// The 'statuscolumn' option is changed. const char *did_set_statuscolumn(optset_T *args) { @@ -2237,36 +1801,6 @@ static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerf return errmsg; } -/// The 'switchbuf' option is changed. -const char *did_set_switchbuf(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_swb, opt_swb_values, &swb_flags, true); -} - -int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_swb_values, - ARRAY_SIZE(opt_swb_values) - 1, - numMatches, - matches); -} - -/// The 'tabclose' option is changed. -const char *did_set_tabclose(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_tcl, opt_tcl_values, &tcl_flags, true); -} - -int expand_set_tabclose(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_tcl_values, - ARRAY_SIZE(opt_tcl_values) - 1, - numMatches, - matches); -} - /// The 'tabline' option is changed. const char *did_set_tabline(optset_T *args) { @@ -2293,37 +1827,12 @@ const char *did_set_tagcase(optset_T *args) if ((opt_flags & OPT_LOCAL) && *p == NUL) { // make the local value empty: use the global value *flags = 0; - } else if (*p == NUL - || opt_strings_flags(p, opt_tc_values, flags, false) != OK) { + } else if (opt_strings_flags(p, opt_tc_values, flags, false) != OK) { return e_invarg; } return NULL; } -int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_tc_values, - ARRAY_SIZE(opt_tc_values) - 1, - numMatches, - matches); -} - -/// The 'termpastefilter' option is changed. -const char *did_set_termpastefilter(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_tpf, opt_tpf_values, &tpf_flags, true); -} - -int expand_set_termpastefilter(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_tpf_values, - ARRAY_SIZE(opt_tpf_values) - 1, - numMatches, - matches); -} - /// The 'titlestring' or the 'iconstring' option is changed. static const char *did_set_titleiconstring(optset_T *args, int flagval) { @@ -2420,12 +1929,6 @@ const char *did_set_verbosefile(optset_T *args) return NULL; } -/// The 'viewoptions' option is changed. -const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_vop, opt_ssop_values, &vop_flags, true); -} - /// The 'virtualedit' option is changed. const char *did_set_virtualedit(optset_T *args) { @@ -2455,15 +1958,6 @@ const char *did_set_virtualedit(optset_T *args) return NULL; } -int expand_set_virtualedit(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_ve_values, - ARRAY_SIZE(opt_ve_values) - 1, - numMatches, - matches); -} - /// The 'whichwrap' option is changed. const char *did_set_whichwrap(optset_T *args) { @@ -2488,48 +1982,6 @@ const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED) return NULL; } -int expand_set_wildmode(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_wim_values, - ARRAY_SIZE(opt_wim_values) - 1, - numMatches, - matches); -} - -/// The 'wildoptions' option is changed. -const char *did_set_wildoptions(optset_T *args FUNC_ATTR_UNUSED) -{ - return did_set_opt_flags(p_wop, opt_wop_values, &wop_flags, true); -} - -int expand_set_wildoptions(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_wop_values, - ARRAY_SIZE(opt_wop_values) - 1, - numMatches, - matches); -} - -/// The 'winaltkeys' option is changed. -const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED) -{ - if (*p_wak == NUL || check_opt_strings(p_wak, opt_wak_values, false) != OK) { - return e_invarg; - } - return NULL; -} - -int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char ***matches) -{ - return expand_set_opt_string(args, - opt_wak_values, - ARRAY_SIZE(opt_wak_values) - 1, - numMatches, - matches); -} - /// The 'winbar' option is changed. const char *did_set_winbar(optset_T *args) { @@ -2552,16 +2004,6 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) return expand_set_opt_generic(args, get_highlight_name, numMatches, matches); } -/// Check an option that can be a range of string values. -/// -/// @param list when true: accept a list of values -/// -/// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char *val, const char **values, bool list) -{ - return opt_strings_flags(val, values, NULL, list); -} - /// Handle an option that can be a range of string values. /// Set a flag in "*flagp" for each string present. /// @@ -2606,7 +2048,7 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla /// @return OK if "p" is a valid fileformat name, FAIL otherwise. int check_ff_value(char *p) { - return check_opt_strings(p, opt_ff_values, false); + return opt_strings_flags(p, opt_ff_values, NULL, false); } static const char e_conflicts_with_value_of_listchars[] -- cgit From 6f0bde11ccd82d257fcda25ecad26227eba3335e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 15 Jan 2025 11:07:51 -0600 Subject: feat(terminal): add support for kitty keyboard protocol This commit adds basic support for the kitty keyboard protocol to Neovim's builtin terminal. For now only the first mode ("Disambiguate escape codes") is supported. --- runtime/doc/news.txt | 3 + src/nvim/getchar.c | 16 ++-- src/nvim/terminal.c | 13 ++- src/nvim/vterm/keyboard.c | 140 +++++++++++++++++++++++-------- src/nvim/vterm/state.c | 138 ++++++++++++++++++++++++++++++ src/nvim/vterm/vterm_internal_defs.h | 25 ++++++ test/functional/terminal/buffer_spec.lua | 8 ++ test/unit/vterm_spec.lua | 5 ++ 8 files changed, 301 insertions(+), 47 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f897220374..33ffeae2bb 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -341,6 +341,9 @@ TERMINAL • |jobstart()| gained the "term" flag. • The |terminal| will send theme update notifications when 'background' is changed and DEC mode 2031 is enabled. +• The |terminal| has experimental support for the Kitty keyboard protocol + (sometimes called "CSI u" key encoding). Only the "Disambiguate escape + codes" mode is currently supported. TREESITTER diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 6cf4556a9f..6ec84ff543 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1517,12 +1517,10 @@ int merge_modifiers(int c_arg, int *modifiers) int c = c_arg; if (*modifiers & MOD_MASK_CTRL) { - if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) { - if (!(State & MODE_TERMINAL) || !(c == 'I' || c == 'J' || c == 'M' || c == '[')) { - c &= 0x1f; - if (c == NUL) { - c = K_ZERO; - } + if (c >= '@' && c <= 0x7f) { + c &= 0x1f; + if (c == NUL) { + c = K_ZERO; } } else if (c == '6') { // CTRL-6 is equivalent to CTRL-^ @@ -2058,6 +2056,12 @@ static bool at_ins_compl_key(void) /// @return the length of the replaced bytes, 0 if nothing changed, -1 for error. static int check_simplify_modifier(int max_offset) { + // We want full modifiers in Terminal mode so that the key can be correctly + // encoded + if (State & MODE_TERMINAL) { + return 0; + } + for (int offset = 0; offset < max_offset; offset++) { if (offset + 3 >= typebuf.tb_len) { break; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 2ad5ac49ca..197a225209 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -783,7 +783,12 @@ static int terminal_execute(VimState *state, int key) { TerminalState *s = (TerminalState *)state; - switch (key) { + // Check for certain control keys like Ctrl-C and Ctrl-\. We still send the + // unmerged key and modifiers to the terminal. + int tmp_mod_mask = mod_mask; + int mod_key = merge_modifiers(key, &tmp_mod_mask); + + switch (mod_key) { case K_LEFTMOUSE: case K_LEFTDRAG: case K_LEFTRELEASE: @@ -841,13 +846,13 @@ static int terminal_execute(VimState *state, int key) FALLTHROUGH; default: - if (key == Ctrl_C) { + if (mod_key == Ctrl_C) { // terminal_enter() always sets `mapped_ctrl_c` to avoid `got_int`. 8eeda7169aa4 // But `got_int` may be set elsewhere, e.g. by interrupt() or an autocommand, // so ensure that it is cleared. got_int = false; } - if (key == Ctrl_BSL && !s->got_bsl) { + if (mod_key == Ctrl_BSL && !s->got_bsl) { s->got_bsl = true; break; } @@ -1016,7 +1021,7 @@ static void terminal_send_key(Terminal *term, int c) VTermKey key = convert_key(&c, &mod); - if (key) { + if (key != VTERM_KEY_NONE) { vterm_keyboard_key(term->vt, key, mod); } else if (!IS_SPECIAL(c)) { vterm_keyboard_unichar(term->vt, (uint32_t)c, mod); diff --git a/src/nvim/vterm/keyboard.c b/src/nvim/vterm/keyboard.c index 696b09157e..dd088ac40e 100644 --- a/src/nvim/vterm/keyboard.c +++ b/src/nvim/vterm/keyboard.c @@ -10,54 +10,77 @@ # include "vterm/keyboard.c.generated.h" #endif +static VTermKeyEncodingFlags vterm_state_get_key_encoding_flags(const VTermState *state) +{ + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + const struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + assert(stack->size > 0); + return stack->items[stack->size - 1]; +} + void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) { - // The shift modifier is never important for Unicode characters apart from Space - if (c != ' ') { - mod &= (unsigned)~VTERM_MOD_SHIFT; + bool passthru = false; + if (c == ' ') { + // Space is passed through only when there are no modifiers (including shift) + passthru = mod == VTERM_MOD_NONE; + } else { + // Otherwise pass through when there are no modifiers (ignoring shift) + passthru = (mod & (unsigned)~VTERM_MOD_SHIFT) == 0; } - if (mod == 0) { - // Normal text - ignore just shift + if (passthru) { char str[6]; int seqlen = fill_utf8((int)c, str); vterm_push_output_bytes(vt, str, (size_t)seqlen); return; } - int needs_CSIu; - switch (c) { - // Special Ctrl- letters that can't be represented elsewise - case 'i': - case 'j': - case 'm': - case '[': - needs_CSIu = 1; - break; - // Ctrl-\ ] ^ _ don't need CSUu - case '\\': - case ']': - case '^': - case '_': - needs_CSIu = 0; - break; - // Shift-space needs CSIu - case ' ': - needs_CSIu = !!(mod & VTERM_MOD_SHIFT); - break; - // All other characters needs CSIu except for letters a-z - default: - needs_CSIu = (c < 'a' || c > 'z'); - } + VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state); + if (flags.disambiguate) { + // Always use unshifted codepoint + if (c >= 'A' && c <= 'Z') { + c += 'a' - 'A'; + mod |= VTERM_MOD_SHIFT; + } - // ALT we can just prefix with ESC; anything else requires CSI u - if (needs_CSIu && (mod & (unsigned)~VTERM_MOD_ALT)) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1); return; } if (mod & VTERM_MOD_CTRL) { - c &= 0x1f; + // Handle special cases. These are taken from kitty, but seem mostly + // consistent across terminals. + switch (c) { + case '2': + case ' ': + // Ctrl+2 is NUL to match Ctrl+@ (which is Shift+2 on US keyboards) + // Ctrl+Space is also NUL for some reason + c = 0x00; + break; + case '3': + case '4': + case '5': + case '6': + case '7': + // Ctrl+3 through Ctrl+7 are sequential starting from 0x1b. Importantly, + // this means that Ctrl+6 emits 0x1e (the same as Ctrl+^ on US keyboards) + c = 0x1b + c - '3'; + break; + case '8': + // Ctrl+8 is DEL + c = 0x7f; + break; + case '/': + // Ctrl+/ is equivalent to Ctrl+_ for historic reasons + c = 0x1f; + break; + default: + if (c >= '@' && c <= 0x7f) { + c &= 0x1f; + } + break; + } } vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); @@ -75,7 +98,7 @@ typedef struct { KEYCODE_CSINUM, KEYCODE_KEYPAD, } type; - char literal; + int literal; int csinum; } keycodes_s; @@ -137,12 +160,35 @@ static keycodes_s keycodes_kp[] = { { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL }; +static keycodes_s keycodes_kp_csiu[] = { + { KEYCODE_KEYPAD, 57399, 'p' }, // KP_0 + { KEYCODE_KEYPAD, 57400, 'q' }, // KP_1 + { KEYCODE_KEYPAD, 57401, 'r' }, // KP_2 + { KEYCODE_KEYPAD, 57402, 's' }, // KP_3 + { KEYCODE_KEYPAD, 57403, 't' }, // KP_4 + { KEYCODE_KEYPAD, 57404, 'u' }, // KP_5 + { KEYCODE_KEYPAD, 57405, 'v' }, // KP_6 + { KEYCODE_KEYPAD, 57406, 'w' }, // KP_7 + { KEYCODE_KEYPAD, 57407, 'x' }, // KP_8 + { KEYCODE_KEYPAD, 57408, 'y' }, // KP_9 + { KEYCODE_KEYPAD, 57411, 'j' }, // KP_MULT + { KEYCODE_KEYPAD, 57413, 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, 57416, 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, 57412, 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, 57409, 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, 57410, 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, 57414, 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, 57415, 'X' }, // KP_EQUAL +}; + void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) { if (key == VTERM_KEY_NONE) { return; } + VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state); + keycodes_s k; if (key < VTERM_KEY_FUNCTION_0) { if (key >= sizeof(keycodes)/sizeof(keycodes[0])) { @@ -158,7 +204,12 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) { return; } - k = keycodes_kp[key - VTERM_KEY_KP_0]; + + if (flags.disambiguate) { + k = keycodes_kp_csiu[key - VTERM_KEY_KP_0]; + } else { + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } } switch (k.type) { @@ -167,7 +218,9 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) case KEYCODE_TAB: // Shift-Tab is CSI Z but plain Tab is 0x09 - if (mod == VTERM_MOD_SHIFT) { + if (flags.disambiguate) { + goto case_LITERAL; + } else if (mod == VTERM_MOD_SHIFT) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); } else if (mod & VTERM_MOD_SHIFT) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1); @@ -187,7 +240,20 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) case KEYCODE_LITERAL: case_LITERAL: - if (mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) { + if (flags.disambiguate) { + switch (key) { + case VTERM_KEY_TAB: + case VTERM_KEY_ENTER: + case VTERM_KEY_BACKSPACE: + // If there are no mods then leave these as-is + flags.disambiguate = mod != VTERM_MOD_NONE; + break; + default: + break; + } + } + + if (flags.disambiguate) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1); } else { vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); @@ -229,7 +295,7 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) case KEYCODE_KEYPAD: if (vt->state->mode.keypad) { - k.literal = (char)k.csinum; + k.literal = k.csinum; goto case_SS3; } else { goto case_LITERAL; diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c index 4ad07377de..0e43107347 100644 --- a/src/nvim/vterm/state.c +++ b/src/nvim/vterm/state.c @@ -1,3 +1,4 @@ +#include #include #include @@ -116,6 +117,15 @@ static VTermState *vterm_state_new(VTerm *vt) (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); } + for (size_t i = 0; i < ARRAY_SIZE(state->key_encoding_stacks); i++) { + struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[i]; + for (size_t j = 0; j < ARRAY_SIZE(stack->items); j++) { + memset(&stack->items[j], 0, sizeof(stack->items[j])); + } + + stack->size = 1; + } + return state; } @@ -916,6 +926,115 @@ static void request_version_string(VTermState *state) VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); } +static void request_key_encoding_flags(VTermState *state) +{ + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + + int reply = 0; + + assert(stack->size > 0); + VTermKeyEncodingFlags flags = stack->items[stack->size - 1]; + + if (flags.disambiguate) { + reply |= KEY_ENCODING_DISAMBIGUATE; + } + + if (flags.report_events) { + reply |= KEY_ENCODING_REPORT_EVENTS; + } + + if (flags.report_alternate) { + reply |= KEY_ENCODING_REPORT_ALTERNATE; + } + + if (flags.report_all_keys) { + reply |= KEY_ENCODING_REPORT_ALL_KEYS; + } + + if (flags.report_associated) { + reply |= KEY_ENCODING_REPORT_ASSOCIATED; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du", reply); +} + +static void set_key_encoding_flags(VTermState *state, int arg, int mode) +{ + // When mode is 3, bits set in arg reset the corresponding mode + bool set = mode != 3; + + // When mode is 1, unset bits are reset + bool reset_unset = mode == 1; + + struct VTermKeyEncodingFlags flags = { 0 }; + if (arg & KEY_ENCODING_DISAMBIGUATE) { + flags.disambiguate = set; + } else if (reset_unset) { + flags.disambiguate = false; + } + + if (arg & KEY_ENCODING_REPORT_EVENTS) { + flags.report_events = set; + } else if (reset_unset) { + flags.report_events = false; + } + + if (arg & KEY_ENCODING_REPORT_ALTERNATE) { + flags.report_alternate = set; + } else if (reset_unset) { + flags.report_alternate = false; + } + if (arg & KEY_ENCODING_REPORT_ALL_KEYS) { + flags.report_all_keys = set; + } else if (reset_unset) { + flags.report_all_keys = false; + } + + if (arg & KEY_ENCODING_REPORT_ASSOCIATED) { + flags.report_associated = set; + } else if (reset_unset) { + flags.report_associated = false; + } + + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + assert(stack->size > 0); + stack->items[stack->size - 1] = flags; +} + +static void push_key_encoding_flags(VTermState *state, int arg) +{ + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + assert(stack->size <= ARRAY_SIZE(stack->items)); + + if (stack->size == ARRAY_SIZE(stack->items)) { + // Evict oldest entry when stack is full + for (size_t i = 0; i < ARRAY_SIZE(stack->items) - 1; i++) { + stack->items[i] = stack->items[i + 1]; + } + } else { + stack->size++; + } + + set_key_encoding_flags(state, arg, 1); +} + +static void pop_key_encoding_flags(VTermState *state, int arg) +{ + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + if (arg >= stack->size) { + stack->size = 1; + + // If a pop request is received that empties the stack, all flags are reset. + memset(&stack->items[0], 0, sizeof(stack->items[0])); + } else if (arg > 0) { + stack->size -= arg; + } +} + static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) { @@ -932,6 +1051,8 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha switch (leader[0]) { case '?': case '>': + case '<': + case '=': leader_byte = (int)leader[0]; break; default: @@ -1542,6 +1663,23 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha break; + case LEADER('?', 0x75): // Kitty query + request_key_encoding_flags(state); + break; + + case LEADER('>', 0x75): // Kitty push flags + push_key_encoding_flags(state, CSI_ARG_OR(args[0], 0)); + break; + + case LEADER('<', 0x75): // Kitty pop flags + pop_key_encoding_flags(state, CSI_ARG_OR(args[0], 1)); + break; + + case LEADER('=', 0x75): // Kitty set flags + val = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + set_key_encoding_flags(state, CSI_ARG_OR(args[0], 0), val); + break; + case INTERMED('\'', 0x7D): // DECIC count = CSI_ARG_COUNT(args[0]); diff --git a/src/nvim/vterm/vterm_internal_defs.h b/src/nvim/vterm/vterm_internal_defs.h index d4d59867bf..19e809490f 100644 --- a/src/nvim/vterm/vterm_internal_defs.h +++ b/src/nvim/vterm/vterm_internal_defs.h @@ -21,7 +21,14 @@ #define BUFIDX_PRIMARY 0 #define BUFIDX_ALTSCREEN 1 +#define KEY_ENCODING_DISAMBIGUATE 0x1 +#define KEY_ENCODING_REPORT_EVENTS 0x2 +#define KEY_ENCODING_REPORT_ALTERNATE 0x4 +#define KEY_ENCODING_REPORT_ALL_KEYS 0x8 +#define KEY_ENCODING_REPORT_ASSOCIATED 0x10 + typedef struct VTermEncoding VTermEncoding; +typedef struct VTermKeyEncodingFlags VTermKeyEncodingFlags; typedef struct { VTermEncoding *enc; @@ -46,6 +53,21 @@ struct VTermPen { unsigned baseline:2; }; +// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement +struct VTermKeyEncodingFlags { + bool disambiguate:1; + bool report_events:1; + bool report_alternate:1; + bool report_all_keys:1; + bool report_associated:1; +}; + +struct VTermKeyEncodingStack { + VTermKeyEncodingFlags items[16]; + uint8_t size; ///< Number of items in the stack. This is at least 1 and at + ///< most the length of the "items" array. +}; + struct VTermState { VTerm *vt; @@ -171,6 +193,9 @@ struct VTermState { char *buffer; size_t buflen; } selection; + + // Maintain two stacks, one for primary screen and one for altscreen + struct VTermKeyEncodingStack key_encoding_stacks[2]; }; struct VTerm { diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 66b75a4ea2..a524e49ef4 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -629,6 +629,14 @@ describe('terminal input', function() -- TODO(bfredl): getcharstr() erases the distinction between and . -- If it was enhanced or replaced this could get folded into the test above. it('can send TAB/C-I and ESC/C-[ separately', function() + if + skip( + is_os('win'), + "The escape sequence to enable kitty keyboard mode doesn't work on Windows" + ) + then + return + end clear() local screen = tt.setup_child_nvim({ '-u', diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 6ff3c18d2a..9f70187fad 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -2324,6 +2324,9 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) local vt = init() local state = wantstate(vt) + -- Disambiguate escape codes + push('\x1b[>1u', vt) + -- Unmodified ASCII inchar(41, vt) expect('output 29') @@ -2478,6 +2481,8 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) expect_output('\x1b[I') vterm.vterm_state_focus_out(state) expect_output('\x1b[O') + + push('\x1b[ Date: Wed, 15 Jan 2025 13:08:18 -0600 Subject: test: use esc sequences in vterm unit tests --- test/unit/vterm_spec.lua | 253 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 198 insertions(+), 55 deletions(-) diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 9f70187fad..bad2b3e658 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -17,6 +17,9 @@ local bit = require('bit') --- @field VTERM_KEY_NONE integer --- @field VTERM_KEY_TAB integer --- @field VTERM_KEY_UP integer +--- @field VTERM_KEY_BACKSPACE integer +--- @field VTERM_KEY_ESCAPE integer +--- @field VTERM_KEY_DEL integer --- @field VTERM_MOD_ALT integer --- @field VTERM_MOD_CTRL integer --- @field VTERM_MOD_SHIFT integer @@ -505,6 +508,18 @@ local function strp_key(input_key) return vterm.VTERM_KEY_ENTER end + if input_key == 'bs' then + return vterm.VTERM_KEY_BACKSPACE + end + + if input_key == 'del' then + return vterm.VTERM_KEY_DEL + end + + if input_key == 'esc' then + return vterm.VTERM_KEY_ESCAPE + end + if input_key == 'f1' then return vterm.VTERM_KEY_FUNCTION_0 + 1 end @@ -2324,68 +2339,83 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) local vt = init() local state = wantstate(vt) - -- Disambiguate escape codes + -- Disambiguate escape codes enabled push('\x1b[>1u', vt) -- Unmodified ASCII - inchar(41, vt) - expect('output 29') - inchar(61, vt) - expect('output 3d') + inchar(0x41, vt) + expect_output('A') + inchar(0x61, vt) + expect_output('a') -- Ctrl modifier on ASCII letters - inchar(41, vt, { C = true }) - expect('output 1b,5b,34,31,3b,35,75') - inchar(61, vt, { C = true }) - expect('output 1b,5b,36,31,3b,35,75') + inchar(0x41, vt, { C = true }) + expect_output('\x1b[97;6u') + inchar(0x61, vt, { C = true }) + expect_output('\x1b[97;5u') -- Alt modifier on ASCII letters - inchar(41, vt, { A = true }) - expect('output 1b,29') - inchar(61, vt, { A = true }) - expect('output 1b,3d') + inchar(0x41, vt, { A = true }) + expect_output('\x1b[97;4u') + inchar(0x61, vt, { A = true }) + expect_output('\x1b[97;3u') -- Ctrl-Alt modifier on ASCII letters - inchar(41, vt, { C = true, A = true }) - expect('output 1b,5b,34,31,3b,37,75') - inchar(61, vt, { C = true, A = true }) - expect('output 1b,5b,36,31,3b,37,75') - - -- Special handling of Ctrl-I - inchar(49, vt) - expect('output 31') - inchar(69, vt) - expect('output 45') - inchar(49, vt, { C = true }) - expect('output 1b,5b,34,39,3b,35,75') - inchar(69, vt, { C = true }) - expect('output 1b,5b,36,39,3b,35,75') - inchar(49, vt, { A = true }) - expect('output 1b,31') - inchar(69, vt, { A = true }) - expect('output 1b,45') - inchar(49, vt, { A = true, C = true }) - expect('output 1b,5b,34,39,3b,37,75') - inchar(69, vt, { A = true, C = true }) - expect('output 1b,5b,36,39,3b,37,75') + inchar(0x41, vt, { C = true, A = true }) + expect_output('\x1b[97;8u') + inchar(0x61, vt, { C = true, A = true }) + expect_output('\x1b[97;7u') + + -- Ctrl-I is disambiguated + inchar(0x49, vt) + expect_output('I') + inchar(0x69, vt) + expect_output('i') + inchar(0x49, vt, { C = true }) + expect_output('\x1b[105;6u') + inchar(0x69, vt, { C = true }) + expect_output('\x1b[105;5u') + inchar(0x49, vt, { A = true }) + expect_output('\x1b[105;4u') + inchar(0x69, vt, { A = true }) + expect_output('\x1b[105;3u') + inchar(0x49, vt, { A = true, C = true }) + expect_output('\x1b[105;8u') + inchar(0x69, vt, { A = true, C = true }) + expect_output('\x1b[105;7u') + + -- Ctrl+Digits + for i = 0, 9 do + local c = 0x30 + i + inchar(c, vt) + expect_output(tostring(i)) + inchar(c, vt, { C = true }) + expect_output(string.format('\x1b[%d;5u', c)) + inchar(c, vt, { C = true, S = true }) + expect_output(string.format('\x1b[%d;6u', c)) + inchar(c, vt, { C = true, A = true }) + expect_output(string.format('\x1b[%d;7u', c)) + inchar(c, vt, { C = true, A = true, S = true }) + expect_output(string.format('\x1b[%d;8u', c)) + end -- Special handling of Space - inchar(20, vt) - expect('output 14') - inchar(20, vt, { S = true }) - expect('output 14') - inchar(20, vt, { C = true }) - expect('output 1b,5b,32,30,3b,35,75') - inchar(20, vt, { C = true, S = true }) - expect('output 1b,5b,32,30,3b,35,75') - inchar(20, vt, { A = true }) - expect('output 1b,14') - inchar(20, vt, { S = true, A = true }) - expect('output 1b,14') - inchar(20, vt, { C = true, A = true }) - expect('output 1b,5b,32,30,3b,37,75') - inchar(20, vt, { S = true, C = true, A = true }) - expect('output 1b,5b,32,30,3b,37,75') + inchar(0x20, vt) + expect_output(' ') + inchar(0x20, vt, { S = true }) + expect_output('\x1b[32;2u') + inchar(0x20, vt, { C = true }) + expect_output('\x1b[32;5u') + inchar(0x20, vt, { C = true, S = true }) + expect_output('\x1b[32;6u') + inchar(0x20, vt, { A = true }) + expect_output('\x1b[32;3u') + inchar(0x20, vt, { S = true, A = true }) + expect_output('\x1b[32;4u') + inchar(0x20, vt, { C = true, A = true }) + expect_output('\x1b[32;7u') + inchar(0x20, vt, { S = true, C = true, A = true }) + expect_output('\x1b[32;8u') -- Cursor keys in reset (cursor) mode inkey('up', vt) @@ -2416,21 +2446,65 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) inkey('up', vt, { C = true }) expect_output('\x1b[1;5A') - -- Shift-Tab should be different + -- Tab inkey('tab', vt) expect_output('\x09') inkey('tab', vt, { S = true }) - expect_output('\x1b[Z') + expect_output('\x1b[9;2u') inkey('tab', vt, { C = true }) expect_output('\x1b[9;5u') inkey('tab', vt, { A = true }) - expect_output('\x1b\x09') + expect_output('\x1b[9;3u') inkey('tab', vt, { C = true, A = true }) expect_output('\x1b[9;7u') + -- Backspace + inkey('bs', vt) + expect_output('\x7f') + inkey('bs', vt, { S = true }) + expect_output('\x1b[127;2u') + inkey('bs', vt, { C = true }) + expect_output('\x1b[127;5u') + inkey('bs', vt, { A = true }) + expect_output('\x1b[127;3u') + inkey('bs', vt, { C = true, A = true }) + expect_output('\x1b[127;7u') + + -- DEL + inkey('del', vt) + expect_output('\x1b[3~') + inkey('del', vt, { S = true }) + expect_output('\x1b[3;2~') + inkey('del', vt, { C = true }) + expect_output('\x1b[3;5~') + inkey('del', vt, { A = true }) + expect_output('\x1b[3;3~') + inkey('del', vt, { C = true, A = true }) + expect_output('\x1b[3;7~') + + -- ESC + inkey('esc', vt) + expect_output('\x1b[27;1u') + inkey('esc', vt, { S = true }) + expect_output('\x1b[27;2u') + inkey('esc', vt, { C = true }) + expect_output('\x1b[27;5u') + inkey('esc', vt, { A = true }) + expect_output('\x1b[27;3u') + inkey('esc', vt, { C = true, A = true }) + expect_output('\x1b[27;7u') + -- Enter in linefeed mode inkey('enter', vt) expect_output('\x0d') + inkey('enter', vt, { S = true }) + expect_output('\x1b[13;2u') + inkey('enter', vt, { C = true }) + expect_output('\x1b[13;5u') + inkey('enter', vt, { A = true }) + expect_output('\x1b[13;3u') + inkey('enter', vt, { C = true, A = true }) + expect_output('\x1b[13;7u') -- Enter in newline mode push('\x1b[20h', vt) @@ -2451,7 +2525,7 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) -- Keypad in DECKPNM inkey('kp0', vt) - expect_output('0') + expect_output('\x1b[57399;1u') -- Keypad in DECKPAM push('\x1b=', vt) @@ -2482,7 +2556,76 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) vterm.vterm_state_focus_out(state) expect_output('\x1b[O') + -- Disambiguate escape codes disabled push('\x1b[ Date: Fri, 17 Jan 2025 08:38:58 +0800 Subject: vim-patch:9.1.1020: no way to get current selected item in a async context (#32056) Problem: no way to get current selected item in a async context Solution: add completed flag to show the entries of currently selected index item (glepnir) closes: vim/vim#16451 https://github.com/vim/vim/commit/037b028a2219d09bc97be04b300b2c0490c4268d Co-authored-by: glepnir --- runtime/doc/builtin.txt | 3 +- runtime/lua/vim/_meta/vimfn.lua | 3 +- src/nvim/eval.lua | 3 +- src/nvim/insexpand.c | 54 ++++++++++++++++++++-------------- test/old/testdir/test_ins_complete.vim | 30 +++++++++++++++++++ 5 files changed, 68 insertions(+), 25 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 6e05dd24d2..d4a6bbf9d8 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1188,7 +1188,8 @@ complete_info([{what}]) *complete_info()* typed text only, or the last completion after no item is selected when using the or keys) - inserted Inserted string. [NOT IMPLEMENTED YET] + completed Return a dictionary containing the entries of + the currently selected index item. preview_winid Info floating preview window id. preview_bufnr Info floating preview buffer id. diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 6316ab2bfc..4b5b276023 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1037,7 +1037,8 @@ function vim.fn.complete_check() end --- typed text only, or the last completion after --- no item is selected when using the or --- keys) ---- inserted Inserted string. [NOT IMPLEMENTED YET] +--- completed Return a dictionary containing the entries of +--- the currently selected index item. --- preview_winid Info floating preview window id. --- preview_bufnr Info floating preview buffer id. --- diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 5c7b2791e8..ecb213c811 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -1398,7 +1398,8 @@ M.funcs = { typed text only, or the last completion after no item is selected when using the or keys) - inserted Inserted string. [NOT IMPLEMENTED YET] + completed Return a dictionary containing the entries of + the currently selected index item. preview_winid Info floating preview window id. preview_bufnr Info floating preview buffer id. diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 22643457d6..a5f5a4eda3 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -2884,6 +2884,25 @@ static void ins_compl_update_sequence_numbers(void) } } +/// Fill the dict of complete_info +static void fill_complete_info_dict(dict_T *di, compl_T *match, bool add_match) +{ + tv_dict_add_str(di, S_LEN("word"), match->cp_str.data); + tv_dict_add_str(di, S_LEN("abbr"), match->cp_text[CPT_ABBR]); + tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]); + tv_dict_add_str(di, S_LEN("kind"), match->cp_text[CPT_KIND]); + tv_dict_add_str(di, S_LEN("info"), match->cp_text[CPT_INFO]); + if (add_match) { + tv_dict_add_bool(di, S_LEN("match"), match->cp_in_match_array); + } + if (match->cp_user_data.v_type == VAR_UNKNOWN) { + // Add an empty string for backwards compatibility + tv_dict_add_str(di, S_LEN("user_data"), ""); + } else { + tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); + } +} + /// Get complete information static void get_complete_info(list_T *what_list, dict_T *retdict) { @@ -2891,13 +2910,13 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) #define CI_WHAT_PUM_VISIBLE 0x02 #define CI_WHAT_ITEMS 0x04 #define CI_WHAT_SELECTED 0x08 -#define CI_WHAT_INSERTED 0x10 +#define CI_WHAT_COMPLETED 0x10 #define CI_WHAT_MATCHES 0x20 #define CI_WHAT_ALL 0xff int what_flag; if (what_list == NULL) { - what_flag = CI_WHAT_ALL & ~CI_WHAT_MATCHES; + what_flag = CI_WHAT_ALL & ~(CI_WHAT_MATCHES|CI_WHAT_COMPLETED); } else { what_flag = 0; for (listitem_T *item = tv_list_first(what_list) @@ -2913,8 +2932,8 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) what_flag |= CI_WHAT_ITEMS; } else if (strcmp(what, "selected") == 0) { what_flag |= CI_WHAT_SELECTED; - } else if (strcmp(what, "inserted") == 0) { - what_flag |= CI_WHAT_INSERTED; + } else if (strcmp(what, "completed") == 0) { + what_flag |= CI_WHAT_COMPLETED; } else if (strcmp(what, "matches") == 0) { what_flag |= CI_WHAT_MATCHES; } @@ -2930,12 +2949,13 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible()); } - if (ret == OK && (what_flag & CI_WHAT_ITEMS || what_flag & CI_WHAT_SELECTED - || what_flag & CI_WHAT_MATCHES)) { + if (ret == OK && (what_flag & (CI_WHAT_ITEMS|CI_WHAT_SELECTED + |CI_WHAT_MATCHES|CI_WHAT_COMPLETED))) { list_T *li = NULL; int selected_idx = -1; bool has_items = what_flag & CI_WHAT_ITEMS; bool has_matches = what_flag & CI_WHAT_MATCHES; + bool has_completed = what_flag & CI_WHAT_COMPLETED; if (has_items || has_matches) { li = tv_list_alloc(kListLenMayKnow); const char *key = (has_matches && !has_items) ? "matches" : "items"; @@ -2954,20 +2974,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) if (has_items || (has_matches && match->cp_in_match_array)) { dict_T *di = tv_dict_alloc(); tv_list_append_dict(li, di); - tv_dict_add_str(di, S_LEN("word"), match->cp_str.data); - tv_dict_add_str(di, S_LEN("abbr"), match->cp_text[CPT_ABBR]); - tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]); - tv_dict_add_str(di, S_LEN("kind"), match->cp_text[CPT_KIND]); - tv_dict_add_str(di, S_LEN("info"), match->cp_text[CPT_INFO]); - if (has_matches && has_items) { - tv_dict_add_bool(di, S_LEN("match"), match->cp_in_match_array); - } - if (match->cp_user_data.v_type == VAR_UNKNOWN) { - // Add an empty string for backwards compatibility - tv_dict_add_str(di, S_LEN("user_data"), ""); - } else { - tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); - } + fill_complete_info_dict(di, match, has_matches && has_items); } if (compl_curr_match != NULL && compl_curr_match->cp_number == match->cp_number) { @@ -2986,11 +2993,14 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) tv_dict_add_nr(retdict, S_LEN("preview_bufnr"), wp->w_buffer->handle); } } + if (ret == OK && selected_idx != -1 && has_completed) { + dict_T *di = tv_dict_alloc(); + fill_complete_info_dict(di, compl_curr_match, false); + ret = tv_dict_add_dict(retdict, S_LEN("completed"), di); + } } (void)ret; - // TODO(vim): - // if (ret == OK && (what_flag & CI_WHAT_INSERTED)) } /// "complete_info()" function diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 52fd24881c..f1ff45ff90 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -2914,4 +2914,34 @@ func Test_complete_info_matches() set cot& endfunc +func Test_complete_info_completed() + func ShownInfo() + let g:compl_info = complete_info(['completed']) + return '' + endfunc + set completeopt+=noinsert + + new + call setline(1, ['aaa', 'aab', 'aba', 'abb']) + inoremap =ShownInfo() + + call feedkeys("Go\\\\dd", 'tx') + call assert_equal({'word': 'aaa', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, g:compl_info['completed']) + + call feedkeys("Go\\\\\dd", 'tx') + call assert_equal({'word': 'aab', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, g:compl_info['completed']) + + call feedkeys("Go\\\\\\\dd", 'tx') + call assert_equal({'word': 'abb', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, g:compl_info['completed']) + + set completeopt+=noselect + call feedkeys("Go\\\\dd", 'tx') + call assert_equal({}, g:compl_info) + + bw! + bw! + delfunc ShownInfo + set cot& +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable -- cgit From cd92924896ab6edeb4d3219befc59ac52a60bcf2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 17 Jan 2025 08:53:10 +0800 Subject: vim-patch:9.1.1021: string might be used without a trailing NUL (#32062) Problem: string might be used without a trailing NUL (after v9.1.0997) Solution: Make sure that the buffer is NUL terminated closes: vim/vim#16457 https://github.com/vim/vim/commit/70dfc374ec72634a0a61aea8344178779675d516 Co-authored-by: John Marriott --- src/nvim/statusline.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index fe6892cc27..434d4c8a6f 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -103,7 +103,8 @@ void win_redr_status(win_T *wp) || bufIsChanged(wp->w_buffer) || wp->w_buffer->b_p_ro) && plen < MAXPATHL - 1) { - *(p + plen++) = ' '; + *(p + plen++) = ' '; // replace NUL with space + *(p + plen) = NUL; // NUL terminate the string } if (bt_help(wp->w_buffer)) { plen += snprintf(p + plen, MAXPATHL - (size_t)plen, "%s", _("[Help]")); -- cgit From ac3859a4410e50794a083f23796e4f8ae2a24b04 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 17 Jan 2025 09:09:47 +0800 Subject: vim-patch:a234a46: runtime(doc): fix typo in usr_02.txt (#32063) fixes: vim/vim#16455 https://github.com/vim/vim/commit/a234a46651ef174549792bd64d4bef64a32072bb Co-authored-by: Christian Brabandt --- runtime/doc/usr_02.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt index f8cfcbe547..41cee4e540 100644 --- a/runtime/doc/usr_02.txt +++ b/runtime/doc/usr_02.txt @@ -684,8 +684,8 @@ Summary: *help-summary* > :help E128 < takes you to the |:function| command -27) Documenction for packages distributed with Vim have the form package-. - So > +27) Documentation for packages distributed with Vim have the form + package-. So > :help package-termdebug < will bring you to the help section for the included termdebug plugin and -- cgit From 7ce27381fb49ac7d6ef1e115c3952f998e979b15 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 16 Jan 2025 08:47:29 +0100 Subject: fix(messages): lsp window/showMessage is not an error Ref https://github.com/neovim/neovim/discussions/32015 --- runtime/lua/vim/lsp/client.lua | 6 +++--- runtime/lua/vim/lsp/handlers.lua | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index a99363d3d6..a082613bb0 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -702,14 +702,14 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err --- --- @param ... string List to write to the buffer local function err_message(...) - local chunks = { { table.concat({ ... }) } } + local chunks = { { table.concat(vim.iter({ ... }):flatten():totable()) } } if vim.in_fast_event() then vim.schedule(function() - vim.api.nvim_echo(chunks, true, { err = true }) + api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end) else - vim.api.nvim_echo(chunks, true, { err = true }) + api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 425e3206aa..5da4033f89 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -583,7 +583,7 @@ NSC['window/showMessage'] = function(_, params, ctx) err_message('LSP[', client_name, '] ', message) else message = ('LSP[%s][%s] %s\n'):format(client_name, protocol.MessageType[message_type], message) - api.nvim_echo({ { message } }, true, { err = true }) + api.nvim_echo({ { message } }, true, {}) end return params end -- cgit From 5dd60e01ace2621f2307eebeb92e9e7351210d3a Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 16 Jan 2025 01:11:07 +0100 Subject: refactor(cmdline): more idiomatic way to avoid cmdline_show Problem: Fix applied in #32033 can be more idiomatic. Solution: Unset redraw_state instead of cmdbuff. --- src/nvim/ex_getln.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index baff795e71..423b50cd32 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -951,10 +951,9 @@ theend: kv_destroy(ccline.last_colors.colors); char *p = ccline.cmdbuff; - // Prevent show events triggered by a (vim.ui_attach) hide callback. - ccline.cmdbuff = NULL; if (ui_has(kUICmdline)) { + ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level, s->gotesc); msg_ext_clear_later(); } @@ -967,6 +966,8 @@ theend: if (did_save_ccline) { restore_cmdline(&save_ccline); + } else { + ccline.cmdbuff = NULL; } return (uint8_t *)p; @@ -3415,10 +3416,6 @@ static void draw_cmdline(int start, int len) static void ui_ext_cmdline_show(CmdlineInfo *line) { - if (line->cmdbuff == NULL) { - return; - } - Arena arena = ARENA_EMPTY; Array content; if (cmdline_star) { -- cgit From 97d58553515552afbac2999409e9bbf9a338dfb0 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Jan 2025 10:48:37 +0100 Subject: docs(gh): use new issue types --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/lsp_bug_report.yml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ce0dbd40de..8cbd3d4ef5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug Report description: Report a problem in Nvim -labels: [bug] +type: 'bug' body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 49bd8158bf..f7cf5980b3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: Feature request description: Request an enhancement for Nvim -labels: [enhancement] +type: 'enhancement' body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml index 277fabca5e..a186a1ee98 100644 --- a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml @@ -1,7 +1,8 @@ name: Language server (LSP) client bug description: Report an issue with Nvim LSP title: "LSP: " -labels: [bug, lsp] +type: bug +labels: [lsp] body: - type: markdown -- cgit From 3530182ba491ba8663b40bdff0c044d74e89bb82 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Jan 2025 13:46:58 +0100 Subject: vim-patch:9.1.1026: filetype: swc configuration files are not recognized Problem: filetype: swc configuration files are not recognized Solution: detect .swcrc files as json filetype (Marces Engel) References: https://swc.rs/docs/configuration/swcrc closes: vim/vim#16462 https://github.com/vim/vim/commit/3a738fccaaf6737c91641856ea00579dbe68bd4e Co-authored-by: Marces Engel --- runtime/lua/vim/filetype.lua | 1 + test/old/testdir/test_filetype.vim | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index e5ba3b1211..efc41269f8 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1636,6 +1636,7 @@ local filename = { ['.lintstagedrc'] = 'json', ['deno.lock'] = 'json', ['flake.lock'] = 'json', + ['.swcrc'] = 'json', ['.babelrc'] = 'jsonc', ['.eslintrc'] = 'jsonc', ['.hintrc'] = 'jsonc', diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 6b07c66dc0..7213f3b032 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -393,7 +393,7 @@ func s:GetFilenameChecks() abort \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'], \ 'jq': ['file.jq'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock', '.swcrc'], \ 'json5': ['file.json5'], \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock', expand("$HOME/.config/VSCodium/User/settings.json")], \ 'jsonl': ['file.jsonl'], -- cgit From b9e6fa7ec81c463d77cc919392b52f6df2d8d304 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Fri, 17 Jan 2025 15:27:50 +0100 Subject: fix(lsp): use filterText as word if textEdit/label doesn't match Problem: With language servers like lemminx, completing xml tags like ` Date: Fri, 17 Jan 2025 23:56:30 +0100 Subject: docs: change augroup names to new convention #32061 Ref: 09e01437c968be4c6e9f6bb3ac8811108c58008c --- runtime/doc/cmdline.txt | 4 ++-- runtime/doc/vim_diff.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index f6a3e95b35..3fc17af185 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -1222,10 +1222,10 @@ Thus you can resize the command-line window, but not others. The |getcmdwintype()| function returns the type of the command-line being edited as described in |cmdwin-char|. -Nvim defines this default CmdWinEnter autocmd in the "nvim_cmdwin" group: > +Nvim defines this default CmdWinEnter autocmd in the "nvim.cmdwin" group: > autocmd CmdWinEnter [:>] syntax sync minlines=1 maxlines=1 < -You can disable this in your config with "autocmd! nvim_cmdwin". |default-autocmds| +You can disable this in your config with "autocmd! nvim.cmdwin". |default-autocmds| AUTOCOMMANDS diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 59c1c4b21c..c870de00ef 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -170,7 +170,7 @@ DEFAULT AUTOCOMMANDS Default autocommands exist in the following groups. Use ":autocmd! {group}" to remove them and ":autocmd {group}" to see how they're defined. -nvim_terminal: +nvim.terminal: - BufReadCmd: Treats "term://" buffers as |terminal| buffers. |terminal-start| - TermClose: A |terminal| buffer started with no arguments (which thus uses 'shell') and which exits with no error is closed automatically. @@ -193,10 +193,10 @@ nvim_terminal: - 'winhighlight' uses |hl-StatusLineTerm| and |hl-StatusLineTermNC| in place of |hl-StatusLine| and |hl-StatusLineNC| -nvim_cmdwin: +nvim.cmdwin: - CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|. -nvim_swapfile: +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. -- cgit From c9000a6b13fd6695f6e28a890b82b490a123f25e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 18 Jan 2025 10:03:13 +0800 Subject: vim-patch:9.1.1028: too many strlen() calls in screen.c (#32083) Problem: too many strlen() calls in screen.c Solution: refactor screen.c and remove calls to strlen(), verify that leadmultispace != NULL (John Marriott) closes: vim/vim#16460 https://github.com/vim/vim/commit/c15de972e8131def2f506bb9eb6b306ca089629c Co-authored-by: John Marriott --- src/nvim/drawscreen.c | 4 +- src/nvim/optionstr.c | 112 ++++++++++++++++++++++++++------------------------ src/nvim/statusline.c | 9 ++-- 3 files changed, 65 insertions(+), 60 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 7f4de9eab8..b1ea38e280 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2580,9 +2580,9 @@ int compute_foldcolumn(win_T *wp, int col) { int fdc = win_fdccol_count(wp); int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw; - int wwidth = wp->w_grid.cols; + int n = wp->w_grid.cols - (col + wmw); - return MIN(fdc, wwidth - (col + wmw)); + return MIN(fdc, n); } /// Return the width of the 'number' and 'relativenumber' column. diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index be5047f814..645bb23638 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -3,6 +3,7 @@ #include #include +#include "nvim/api/private/defs.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" @@ -2086,49 +2087,54 @@ static schar_T get_encoded_char_adv(const char **p) } struct chars_tab { - schar_T *cp; ///< char value - const char *name; ///< char id - const char *def; ///< default value - const char *fallback; ///< default value when "def" isn't single-width + schar_T *cp; ///< char value + String name; ///< char id + const char *def; ///< default value + const char *fallback; ///< default value when "def" isn't single-width }; +#define CHARSTAB_ENTRY(cp, name, def, fallback) \ + { (cp), { name, STRLEN_LITERAL(name) }, def, fallback } + static fcs_chars_T fcs_chars; static const struct chars_tab fcs_tab[] = { - { &fcs_chars.stl, "stl", " ", NULL }, - { &fcs_chars.stlnc, "stlnc", " ", NULL }, - { &fcs_chars.wbr, "wbr", " ", NULL }, - { &fcs_chars.horiz, "horiz", "─", "-" }, - { &fcs_chars.horizup, "horizup", "┴", "-" }, - { &fcs_chars.horizdown, "horizdown", "┬", "-" }, - { &fcs_chars.vert, "vert", "│", "|" }, - { &fcs_chars.vertleft, "vertleft", "┤", "|" }, - { &fcs_chars.vertright, "vertright", "├", "|" }, - { &fcs_chars.verthoriz, "verthoriz", "┼", "+" }, - { &fcs_chars.fold, "fold", "·", "-" }, - { &fcs_chars.foldopen, "foldopen", "-", NULL }, - { &fcs_chars.foldclosed, "foldclose", "+", NULL }, - { &fcs_chars.foldsep, "foldsep", "│", "|" }, - { &fcs_chars.diff, "diff", "-", NULL }, - { &fcs_chars.msgsep, "msgsep", " ", NULL }, - { &fcs_chars.eob, "eob", "~", NULL }, - { &fcs_chars.lastline, "lastline", "@", NULL }, + CHARSTAB_ENTRY(&fcs_chars.stl, "stl", " ", NULL), + CHARSTAB_ENTRY(&fcs_chars.stlnc, "stlnc", " ", NULL), + CHARSTAB_ENTRY(&fcs_chars.wbr, "wbr", " ", NULL), + CHARSTAB_ENTRY(&fcs_chars.horiz, "horiz", "─", "-"), + CHARSTAB_ENTRY(&fcs_chars.horizup, "horizup", "┴", "-"), + CHARSTAB_ENTRY(&fcs_chars.horizdown, "horizdown", "┬", "-"), + CHARSTAB_ENTRY(&fcs_chars.vert, "vert", "│", "|"), + CHARSTAB_ENTRY(&fcs_chars.vertleft, "vertleft", "┤", "|"), + CHARSTAB_ENTRY(&fcs_chars.vertright, "vertright", "├", "|"), + CHARSTAB_ENTRY(&fcs_chars.verthoriz, "verthoriz", "┼", "+"), + CHARSTAB_ENTRY(&fcs_chars.fold, "fold", "·", "-"), + CHARSTAB_ENTRY(&fcs_chars.foldopen, "foldopen", "-", NULL), + CHARSTAB_ENTRY(&fcs_chars.foldclosed, "foldclose", "+", NULL), + CHARSTAB_ENTRY(&fcs_chars.foldsep, "foldsep", "│", "|"), + CHARSTAB_ENTRY(&fcs_chars.diff, "diff", "-", NULL), + CHARSTAB_ENTRY(&fcs_chars.msgsep, "msgsep", " ", NULL), + CHARSTAB_ENTRY(&fcs_chars.eob, "eob", "~", NULL), + CHARSTAB_ENTRY(&fcs_chars.lastline, "lastline", "@", NULL), }; static lcs_chars_T lcs_chars; static const struct chars_tab lcs_tab[] = { - { &lcs_chars.eol, "eol", NULL, NULL }, - { &lcs_chars.ext, "extends", NULL, NULL }, - { &lcs_chars.nbsp, "nbsp", NULL, NULL }, - { &lcs_chars.prec, "precedes", NULL, NULL }, - { &lcs_chars.space, "space", NULL, NULL }, - { &lcs_chars.tab2, "tab", NULL, NULL }, - { &lcs_chars.lead, "lead", NULL, NULL }, - { &lcs_chars.trail, "trail", NULL, NULL }, - { &lcs_chars.conceal, "conceal", NULL, NULL }, - { NULL, "multispace", NULL, NULL }, - { NULL, "leadmultispace", NULL, NULL }, + CHARSTAB_ENTRY(&lcs_chars.eol, "eol", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.ext, "extends", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.nbsp, "nbsp", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.prec, "precedes", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.space, "space", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.tab2, "tab", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.lead, "lead", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.trail, "trail", NULL, NULL), + CHARSTAB_ENTRY(&lcs_chars.conceal, "conceal", NULL, NULL), + CHARSTAB_ENTRY(NULL, "multispace", NULL, NULL), + CHARSTAB_ENTRY(NULL, "leadmultispace", NULL, NULL), }; +#undef CHARSTAB_ENTRY + static char *field_value_err(char *errbuf, size_t errbuflen, const char *fmt, const char *field) { if (errbuf == NULL) { @@ -2209,13 +2215,13 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo while (*p) { int i; for (i = 0; i < entries; i++) { - const size_t len = strlen(tab[i].name); - if (!(strncmp(p, tab[i].name, len) == 0 && p[len] == ':')) { + if (!(strncmp(p, tab[i].name.data, + tab[i].name.size) == 0 && p[tab[i].name.size] == ':')) { continue; } - if (what == kListchars && strcmp(tab[i].name, "multispace") == 0) { - const char *s = p + len + 1; + const char *s = p + tab[i].name.size + 1; + if (what == kListchars && strcmp(tab[i].name.data, "multispace") == 0) { if (round == 0) { // Get length of lcs-multispace string in the first round last_multispace = p; @@ -2225,7 +2231,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo if (c1 == 0) { return field_value_err(errbuf, errbuflen, e_wrong_character_width_for_field_str, - tab[i].name); + tab[i].name.data); } multispace_len++; } @@ -2233,7 +2239,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo // lcs-multispace cannot be an empty string return field_value_err(errbuf, errbuflen, e_wrong_number_of_characters_for_field_str, - tab[i].name); + tab[i].name.data); } p = s; } else { @@ -2249,8 +2255,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo break; } - if (what == kListchars && strcmp(tab[i].name, "leadmultispace") == 0) { - const char *s = p + len + 1; + if (what == kListchars && strcmp(tab[i].name.data, "leadmultispace") == 0) { if (round == 0) { // get length of lcs-leadmultispace string in first round last_lmultispace = p; @@ -2260,7 +2265,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo if (c1 == 0) { return field_value_err(errbuf, errbuflen, e_wrong_character_width_for_field_str, - tab[i].name); + tab[i].name.data); } lead_multispace_len++; } @@ -2268,7 +2273,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo // lcs-leadmultispace cannot be an empty string return field_value_err(errbuf, errbuflen, e_wrong_number_of_characters_for_field_str, - tab[i].name); + tab[i].name.data); } p = s; } else { @@ -2284,17 +2289,16 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo break; } - const char *s = p + len + 1; if (*s == NUL) { return field_value_err(errbuf, errbuflen, e_wrong_number_of_characters_for_field_str, - tab[i].name); + tab[i].name.data); } schar_T c1 = get_encoded_char_adv(&s); if (c1 == 0) { return field_value_err(errbuf, errbuflen, e_wrong_character_width_for_field_str, - tab[i].name); + tab[i].name.data); } schar_T c2 = 0; schar_T c3 = 0; @@ -2302,20 +2306,20 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo if (*s == NUL) { return field_value_err(errbuf, errbuflen, e_wrong_number_of_characters_for_field_str, - tab[i].name); + tab[i].name.data); } c2 = get_encoded_char_adv(&s); if (c2 == 0) { return field_value_err(errbuf, errbuflen, e_wrong_character_width_for_field_str, - tab[i].name); + tab[i].name.data); } if (!(*s == ',' || *s == NUL)) { c3 = get_encoded_char_adv(&s); if (c3 == 0) { return field_value_err(errbuf, errbuflen, e_wrong_character_width_for_field_str, - tab[i].name); + tab[i].name.data); } } } @@ -2335,7 +2339,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo } else { return field_value_err(errbuf, errbuflen, e_wrong_number_of_characters_for_field_str, - tab[i].name); + tab[i].name.data); } } @@ -2366,22 +2370,22 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo /// 'fillchars' option. char *get_fillchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) { - if (idx >= (int)ARRAY_SIZE(fcs_tab)) { + if (idx < 0 || idx >= (int)ARRAY_SIZE(fcs_tab)) { return NULL; } - return (char *)fcs_tab[idx].name; + return fcs_tab[idx].name.data; } /// Function given to ExpandGeneric() to obtain possible arguments of the /// 'listchars' option. char *get_listchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) { - if (idx >= (int)ARRAY_SIZE(lcs_tab)) { + if (idx < 0 || idx >= (int)ARRAY_SIZE(lcs_tab)) { return NULL; } - return (char *)lcs_tab[idx].name; + return lcs_tab[idx].name.data; } /// Check all global and local values of 'listchars' and 'fillchars'. diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 434d4c8a6f..b8515fa3e2 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -772,8 +772,7 @@ void draw_tabline(void) if (modified || wincount > 1) { if (wincount > 1) { - vim_snprintf(NameBuff, MAXPATHL, "%d", wincount); - int len = (int)strlen(NameBuff); + int len = vim_snprintf(NameBuff, MAXPATHL, "%d", wincount); if (col + len >= Columns - 3) { break; } @@ -798,7 +797,8 @@ void draw_tabline(void) len -= ptr2cells(p); MB_PTR_ADV(p); } - len = MIN(len, Columns - col - 1); + int n = Columns - col - 1; + len = MIN(len, n); grid_line_puts(col, p, -1, attr); col += len; @@ -832,7 +832,8 @@ void draw_tabline(void) // Draw the 'showcmd' information if 'showcmdloc' == "tabline". if (p_sc && *p_sloc == 't') { - const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3); + int n = Columns - col - (tabcount > 1) * 3; + const int sc_width = MIN(10, n); if (sc_width > 0) { grid_line_puts(Columns - sc_width - (tabcount > 1) * 2, -- cgit From 136cb642a0022fd005481e729dcc917552103322 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Jan 2025 18:42:07 +0100 Subject: vim-patch:c2a967a: runtime(c): Update syntax and ftplugin files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - highlight more C keywords, including some from C23 Conditionally highlight C23 features: - #embed, #elifdef and #elifndef preprocessor directives - predefined macros - UTF-8 character constants - binary integer constants, _BitInt literals, and digit separators - nullptr_t type and associated constant - decimal real floating-point, bit precise and char types - typeof operators Matchit: - update for new preprocessor directives fixes: vim/vim#13667 fixes: vim/vim#13679 closes: vim/vim#12984 https://github.com/vim/vim/commit/c2a967a1b9adca6c929e3dc5c218dfada00059b6 Co-authored-by: Doug Kearns Co-authored-by: Albin Ahlbäck --- runtime/doc/syntax.txt | 1 + runtime/ftplugin/c.vim | 2 +- runtime/syntax/c.vim | 135 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 18 deletions(-) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index f5183e3247..e3a43eaf1a 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -593,6 +593,7 @@ Variable Highlight ~ *c_no_cformat* don't highlight %-formats in strings *c_no_c99* don't highlight C99 standard items *c_no_c11* don't highlight C11 standard items +*c_no_c23* don't highlight C23 standard items *c_no_bsd* don't highlight BSD specific types *c_functions* highlight function calls and definitions *c_function_pointers* highlight function pointers definitions diff --git a/runtime/ftplugin/c.vim b/runtime/ftplugin/c.vim index 8b2b784eb4..3b05ce182a 100644 --- a/runtime/ftplugin/c.vim +++ b/runtime/ftplugin/c.vim @@ -39,7 +39,7 @@ endif " When the matchit plugin is loaded, this makes the % command skip parens and " braces in comments properly. if !exists("b:match_words") - let b:match_words = '^\s*#\s*if\(\|def\|ndef\)\>:^\s*#\s*elif\>:^\s*#\s*else\>:^\s*#\s*endif\>' + let b:match_words = '^\s*#\s*if\%(\|def\|ndef\)\>:^\s*#\s*elif\%(\|def\|ndef\)\>:^\s*#\s*else\>:^\s*#\s*endif\>' let b:match_skip = 's:comment\|string\|character\|special' let b:undo_ftplugin ..= " | unlet! b:match_skip b:match_words" endif diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim index 30db9438d0..eb56239f3d 100644 --- a/runtime/syntax/c.vim +++ b/runtime/syntax/c.vim @@ -1,7 +1,7 @@ " Vim syntax file -" Language: C -" Maintainer: The Vim Project -" Last Change: 2023 Aug 10 +" Language: C +" Maintainer: The Vim Project +" Last Change: 2025 Jan 15 " Former Maintainer: Bram Moolenaar " Quit when a (custom) syntax file was already loaded @@ -111,6 +111,20 @@ if (s:ft ==# "c" && !exists("c_no_c11")) || (s:in_cpp_family && !exists("cpp_no_ syn match cSpecialCharacter display "[Uu]'\\x\x\+'" endif +if (s:ft ==# "c" && !exists("c_no_c23")) || (s:in_cpp_family && !exists("cpp_no_cpp17")) + syn match cCharacter "u8'[^\\]'" + syn match cCharacter "u8'[^']*'" contains=cSpecial + if exists("c_gnu") + syn match cSpecialError "u8'\\[^'\"?\\abefnrtv]'" + syn match cSpecialCharacter "u8'\\['\"?\\abefnrtv]'" + else + syn match cSpecialError "u8'\\[^'\"?\\abfnrtv]'" + syn match cSpecialCharacter "u8'\\['\"?\\abfnrtv]'" + endif + syn match cSpecialCharacter display "u8'\\\o\{1,3}'" + syn match cSpecialCharacter display "u8'\\x\x\+'" +endif + "when wanted, highlight trailing white space if exists("c_space_errors") if !exists("c_no_trail_space_error") @@ -191,12 +205,25 @@ syn case ignore syn match cNumbers display transparent "\<\d\|\.\d" contains=cNumber,cFloat,cOctalError,cOctal " Same, but without octal error (for comments) syn match cNumbersCom display contained transparent "\<\d\|\.\d" contains=cNumber,cFloat,cOctal -syn match cNumber display contained "\d\+\%(u\=l\{0,2}\|ll\=u\)\>" -"hex number -syn match cNumber display contained "0x\x\+\%(u\=l\{0,2}\|ll\=u\)\>" -" Flag the first zero of an octal number as something special -syn match cOctal display contained "0\o\+\%(u\=l\{0,2}\|ll\=u\)\>" contains=cOctalZero -syn match cOctalZero display contained "\<0" + +" cpp.vim handles these +if !exists("c_no_c23") && !s:in_cpp_family + syn match cNumber display contained "\d\%('\=\d\+\)*\%(u\=l\{0,2}\|ll\=u\|u\=wb\|wbu\=\)\>" + "hex number + syn match cNumber display contained "0x\x\%('\=\x\+\)*\%(u\=l\{0,2}\|ll\=u\|u\=wb\|wbu\=\)\>" + " Flag the first zero of an octal number as something special + syn match cOctal display contained "0\o\%('\=\o\+\)*\%(u\=l\{0,2}\|ll\=u\|u\=wb\|wbu\=\)\>" contains=cOctalZero + "binary number + syn match cNumber display contained "0b[01]\%('\=[01]\+\)*\%(u\=l\{0,2}\|ll\=u\|u\=wb\|wbu\=\)\>" +else + syn match cNumber display contained "\d\+\%(u\=l\{0,2}\|ll\=u\)\>" + "hex number + syn match cNumber display contained "0x\x\+\%(u\=l\{0,2}\|ll\=u\)\>" + " Flag the first zero of an octal number as something special + syn match cOctal display contained "0\o\+\%(u\=l\{0,2}\|ll\=u\)\>" contains=cOctalZero + syn match cOctalZero display contained "\<0" +endif + "floating point number, with dot, optional exponent syn match cFloat display contained "\d\+\.\d*\%(e[-+]\=\d\+\)\=[fl]\=" "floating point number, starting with a dot, optional exponent @@ -277,6 +304,13 @@ if !exists("c_no_c99") " ISO C99 syn keyword cType intptr_t uintptr_t syn keyword cType intmax_t uintmax_t endif +if !exists("c_no_c23") && !s:in_cpp_family + syn keyword cOperator typeof typeof_unqual + syn keyword cType _BitInt _Decimal32 _Decimal64 _Decimal128 +endif +if (s:ft ==# "c" && !exists("c_no_c23")) || (s:in_cpp_family && !exists("cpp_no_cpp11")) + syn keyword cType nullptr_t +endif syn keyword cTypedef typedef syn keyword cStructure struct union enum @@ -312,10 +346,15 @@ if !exists("c_no_c11") syn keyword cType atomic_intmax_t atomic_uintmax_t endif +if (s:ft ==# "c" && !exists("c_no_c23")) || (s:in_cpp_family && !exists("cpp_no_cpp20")) + syn keyword cType char8_t +endif + if !exists("c_no_ansi") || exists("c_ansi_constants") || exists("c_gnu") if exists("c_gnu") syn keyword cConstant __GNUC__ __FUNCTION__ __PRETTY_FUNCTION__ __func__ endif + " TODO: __STDC_HOSTED__ is C99 and C++11 syn keyword cConstant __LINE__ __FILE__ __DATE__ __TIME__ __STDC__ __STDC_VERSION__ __STDC_HOSTED__ syn keyword cConstant CHAR_BIT MB_LEN_MAX MB_CUR_MAX syn keyword cConstant UCHAR_MAX UINT_MAX ULONG_MAX USHRT_MAX @@ -324,6 +363,8 @@ if !exists("c_no_ansi") || exists("c_ansi_constants") || exists("c_gnu") syn keyword cConstant SCHAR_MIN SINT_MIN SLONG_MIN SSHRT_MIN syn keyword cConstant SCHAR_MAX SINT_MAX SLONG_MAX SSHRT_MAX if !exists("c_no_c99") + syn keyword cConstant __STDC_ISO_10646__ __STDC_IEC_559_COMPLEX__ + syn keyword cConstant __STDC_MB_MIGHT_NEQ_WC__ syn keyword cConstant __func__ __VA_ARGS__ syn keyword cConstant LLONG_MIN LLONG_MAX ULLONG_MAX syn keyword cConstant INT8_MIN INT16_MIN INT32_MIN INT64_MIN @@ -340,6 +381,26 @@ if !exists("c_no_ansi") || exists("c_ansi_constants") || exists("c_gnu") syn keyword cConstant PTRDIFF_MIN PTRDIFF_MAX SIG_ATOMIC_MIN SIG_ATOMIC_MAX syn keyword cConstant SIZE_MAX WCHAR_MIN WCHAR_MAX WINT_MIN WINT_MAX endif + if !exists("c_no_c11") + syn keyword cConstant __STDC_UTF_16__ __STDC_UTF_32__ __STDC_ANALYZABLE__ + syn keyword cConstant __STDC_LIB_EXT1__ __STDC_NO_ATOMICS__ + syn keyword cConstant __STDC_NO_COMPLEX__ __STDC_NO_THREADS__ + syn keyword cConstant __STDC_NO_VLA__ + endif + if !exists("c_no_c23") + syn keyword cConstant __STDC_UTF_16__ __STDC_UTF_32__ + syn keyword cConstant __STDC_EMBED_NOT_FOUND__ __STDC_EMBED_FOUND__ + syn keyword cConstant __STDC_EMBED_EMPTY__ __STDC_IEC_60559_BFP__ + syn keyword cConstant __STDC_IEC_60559_DFP__ __STDC_IEC_60559_COMPLEX__ + syn keyword cConstant __STDC_IEC_60559_TYPES__ + syn keyword cConstant BITINT_MAXWIDTH + endif + if (s:ft ==# "c" && !exists("c_no_c23")) || (s:in_cpp_family && !exists("cpp_no_cpp20")) + syn keyword cConstant __VA_OPT__ + endif + if (s:ft ==# "c" && !exists("c_no_c23")) || (s:in_cpp_family && !exists("cpp_no_cpp11")) + syn keyword cConstant nullptr + endif syn keyword cConstant FLT_RADIX FLT_ROUNDS FLT_DIG FLT_MANT_DIG FLT_EPSILON DBL_DIG DBL_MANT_DIG DBL_EPSILON syn keyword cConstant LDBL_DIG LDBL_MANT_DIG LDBL_EPSILON FLT_MIN FLT_MAX FLT_MIN_EXP FLT_MAX_EXP FLT_MIN_10_EXP FLT_MAX_10_EXP syn keyword cConstant DBL_MIN DBL_MAX DBL_MIN_EXP DBL_MAX_EXP DBL_MIN_10_EXP DBL_MAX_10_EXP LDBL_MIN LDBL_MAX LDBL_MIN_EXP LDBL_MAX_EXP @@ -377,37 +438,77 @@ if !exists("c_no_ansi") || exists("c_ansi_constants") || exists("c_gnu") endif if !exists("c_no_c99") " ISO C99 syn keyword cConstant true false + syn keyword cConstant INFINITY NAN + " math.h + syn keyword cConstant HUGE_VAL HUGE_VALF HUGE_VALL + syn keyword cConstant FP_FAST_FMAF FP_FAST_FMA FP_FAST_FMAL + syn keyword cConstant FP_ILOGB0 FP_ILOGBNAN + syn keyword cConstant math_errhandling MATH_ERRNO MATH_ERREXCEPT + syn keyword cConstant FP_NORMAL FP_SUBNORMAL FP_ZERO FP_INFINITE FP_NAN endif " Accept %: for # (C99) -syn region cPreCondit start="^\s*\zs\%(%:\|#\)\s*\%(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError +syn cluster cPreProcGroup contains=cPreCondit,cIncluded,cInclude,cDefine,cErrInParen,cErrInBracket,cUserLabel,cSpecial,cOctalZero,cCppOutWrapper,cCppInWrapper,@cCppOutInGroup,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom,cString,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cParen,cBracket,cMulti,cBadBlock +if !exists("c_no_c23") + syn region cPreCondit start="^\s*\zs\%(%:\|#\)\s*\%(el\)\=\%(if\|ifdef\|ifndef\)\>" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError +else + syn region cPreCondit start="^\s*\zs\%(%:\|#\)\s*\%(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError +endif syn match cPreConditMatch display "^\s*\zs\%(%:\|#\)\s*\%(else\|endif\)\>" if !exists("c_no_if0") syn cluster cCppOutInGroup contains=cCppInIf,cCppInElse,cCppInElse2,cCppOutIf,cCppOutIf2,cCppOutElse,cCppInSkip,cCppOutSkip syn region cCppOutWrapper start="^\s*\zs\%(%:\|#\)\s*if\s\+0\+\s*\%($\|//\|/\*\|&\)" end=".\@=\|$" contains=cCppOutIf,cCppOutElse,@NoSpell fold syn region cCppOutIf contained start="0\+" matchgroup=cCppOutWrapper end="^\s*\%(%:\|#\)\s*endif\>" contains=cCppOutIf2,cCppOutElse if !exists("c_no_if0_fold") - syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell fold + if !exists("c_no_c23") + syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|el\%(if\|ifdef\|ifndef\)\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell fold + else + syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell fold + endif else - syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell + if !exists("c_no_c23") + syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|el\%(if\|ifdef\|ifndef\)\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell + else + syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0\+\s*\%($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell + endif + endif + if !exists("c_no_c23") + syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\%(%:\|#\)\s*\%(else\|el\%(if\|ifdef\|ifndef\)\)" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit + else + syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\%(%:\|#\)\s*\%(else\|elif\)" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit endif - syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\%(%:\|#\)\s*\%(else\|elif\)" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit syn region cCppInWrapper start="^\s*\zs\%(%:\|#\)\s*if\s\+0*[1-9]\d*\s*\%($\|//\|/\*\||\)" end=".\@=\|$" contains=cCppInIf,cCppInElse fold syn region cCppInIf contained matchgroup=cCppInWrapper start="\d\+" end="^\s*\%(%:\|#\)\s*endif\>" contains=TOP,cPreCondit if !exists("c_no_if0_fold") - syn region cCppInElse contained start="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold + if !exists("c_no_c23") + syn region cCppInElse contained start="^\s*\%(%:\|#\)\s*\%(else\>\|el\%(if\|ifdef\|ifndef\)\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold + else + syn region cCppInElse contained start="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold + endif + else + if !exists("c_no_c23") + syn region cCppInElse contained start="^\s*\%(%:\|#\)\s*\%(else\>\|el\%(if\|ifdef\|ifndef\)\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 + else + syn region cCppInElse contained start="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 + endif + endif + if !exists("c_no_c23") + syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\%(%:\|#\)\s*\%(else\|el\%(if\|ifdef\|ifndef\)\)\%([^/]\|/[^/*]\)*" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell else - syn region cCppInElse contained start="^\s*\%(%:\|#\)\s*\%(else\>\|elif\s\+\%(0*[1-9]\d*\s*\%($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 + syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\%(%:\|#\)\s*\%(else\|elif\)\%([^/]\|/[^/*]\)*" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell endif - syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\%(%:\|#\)\s*\%(else\|elif\)\%([^/]\|/[^/*]\)*" end="^\s*\%(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell syn region cCppOutSkip contained start="^\s*\%(%:\|#\)\s*\%(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\%(%:\|#\)\s*endif\>" contains=cSpaceError,cCppOutSkip syn region cCppInSkip contained matchgroup=cCppInWrapper start="^\s*\%(%:\|#\)\s*\%(if\s\+\%(\d\+\s*\%($\|//\|/\*\||\|&\)\)\@!\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\%(%:\|#\)\s*endif\>" containedin=cCppOutElse,cCppInIf,cCppInSkip contains=TOP,cPreProc endif syn region cIncluded display contained start=+"+ skip=+\\\\\|\\"+ end=+"+ syn match cIncluded display contained "<[^>]*>" syn match cInclude display "^\s*\zs\%(%:\|#\)\s*include\>\s*["<]" contains=cIncluded +if !exists("c_no_c23") && !s:in_cpp_family + syn region cInclude start="^\s*\zs\%(%:\|#\)\s*embed\>" skip="\\$" end="$" keepend contains=cEmbed,cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError + syn match cEmbed contained "\%(%:\|#\)\s*embed\>" nextgroup=cIncluded skipwhite transparent + syn cluster cPreProcGroup add=cEmbed +endif "syn match cLineSkip "\\$" -syn cluster cPreProcGroup contains=cPreCondit,cIncluded,cInclude,cDefine,cErrInParen,cErrInBracket,cUserLabel,cSpecial,cOctalZero,cCppOutWrapper,cCppInWrapper,@cCppOutInGroup,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom,cString,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cParen,cBracket,cMulti,cBadBlock syn region cDefine start="^\s*\zs\%(%:\|#\)\s*\%(define\|undef\)\>" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell syn region cPreProc start="^\s*\zs\%(%:\|#\)\s*\%(pragma\>\|line\>\|warning\>\|warn\>\|error\>\)" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell -- cgit From e962167245755e900a7bd154075106026dbb4eff Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Jan 2025 13:48:47 +0100 Subject: vim-patch:9cfdabb: runtime(netrw): change netrw maintainer Dr. Chip retired some time ago and is no longer maintaining the netrw plugin. However as a runtime plugin distributed by Vim, it important to maintain the netrw plugin in the future and fix bugs as they are reported. So, split out the netrw plugin as an additional package, however include some stubs to make sure the plugin is still loaded by default and the documentation is accessible as well. closes: vim/vim#16368 https://github.com/vim/vim/commit/9cfdabb074feefc9848e9f7a4538f201e28c7f06 Co-authored-by: Luca Saccarola --- runtime/autoload/netrw.vim | 11977 ------------------- runtime/autoload/netrwFileHandlers.vim | 0 runtime/autoload/netrwSettings.vim | 249 - runtime/autoload/netrw_gitignore.vim | 22 - runtime/doc/pi_netrw.txt | 4397 ------- runtime/pack/dist/opt/netrw/LICENSE.txt | 16 + runtime/pack/dist/opt/netrw/README.md | 544 + runtime/pack/dist/opt/netrw/autoload/netrw.vim | 11927 ++++++++++++++++++ .../pack/dist/opt/netrw/autoload/netrwSettings.vim | 242 + .../dist/opt/netrw/autoload/netrw_gitignore.vim | 26 + runtime/pack/dist/opt/netrw/doc/netrw.txt | 3786 ++++++ runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim | 222 + runtime/pack/dist/opt/netrw/syntax/netrw.vim | 145 + runtime/plugin/netrwPlugin.vim | 235 +- runtime/syntax/netrw.vim | 148 - 15 files changed, 16913 insertions(+), 17023 deletions(-) delete mode 100644 runtime/autoload/netrw.vim delete mode 100644 runtime/autoload/netrwFileHandlers.vim delete mode 100644 runtime/autoload/netrwSettings.vim delete mode 100644 runtime/autoload/netrw_gitignore.vim delete mode 100644 runtime/doc/pi_netrw.txt create mode 100644 runtime/pack/dist/opt/netrw/LICENSE.txt create mode 100644 runtime/pack/dist/opt/netrw/README.md create mode 100644 runtime/pack/dist/opt/netrw/autoload/netrw.vim create mode 100644 runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim create mode 100644 runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim create mode 100644 runtime/pack/dist/opt/netrw/doc/netrw.txt create mode 100644 runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim create mode 100644 runtime/pack/dist/opt/netrw/syntax/netrw.vim delete mode 100644 runtime/syntax/netrw.vim diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim deleted file mode 100644 index 8e991e88f1..0000000000 --- a/runtime/autoload/netrw.vim +++ /dev/null @@ -1,11977 +0,0 @@ -" netrw.vim: Handles file transfer and remote directory listing across -" AUTOLOAD SECTION -" Maintainer: This runtime file is looking for a new maintainer. -" Date: May 03, 2023 -" Version: 173a -" Last Change: {{{1 -" 2023 Nov 21 by Vim Project: ignore wildignore when expanding $COMSPEC (v173a) -" 2023 Nov 22 by Vim Project: fix handling of very long filename on longlist style (v173a) -" 2024 Feb 19 by Vim Project: (announce adoption) -" 2024 Feb 29 by Vim Project: handle symlinks in tree mode correctly -" 2024 Apr 03 by Vim Project: detect filetypes for remote edited files -" 2024 May 08 by Vim Project: cleanup legacy Win9X checks -" 2024 May 09 by Vim Project: remove hard-coded private.ppk -" 2024 May 10 by Vim Project: recursively delete directories by default -" 2024 May 13 by Vim Project: prefer scp over pscp -" 2024 Jun 04 by Vim Project: set bufhidden if buffer changed, nohidden is set and buffer shall be switched (#14915) -" 2024 Jun 13 by Vim Project: glob() on Windows fails when a directory name contains [] (#14952) -" 2024 Jun 23 by Vim Project: save ad restore registers when liststyle = WIDELIST (#15077, #15114) -" 2024 Jul 22 by Vim Project: avoid endless recursion (#15318) -" 2024 Jul 23 by Vim Project: escape filename before trying to delete it (#15330) -" 2024 Jul 30 by Vim Project: handle mark-copy to same target directory (#12112) -" 2024 Aug 02 by Vim Project: honor g:netrw_alt{o,v} for :{S,H,V}explore (#15417) -" 2024 Aug 15 by Vim Project: style changes, prevent E121 (#15501) -" 2024 Aug 22 by Vim Project: fix mf-selection highlight (#15551) -" 2024 Aug 22 by Vim Project: adjust echo output of mx command (#15550) -" 2024 Sep 15 by Vim Project: more strict confirmation dialog (#15680) -" 2024 Sep 19 by Vim Project: mf-selection highlight uses wrong pattern (#15700) -" 2024 Sep 21 by Vim Project: remove extraneous closing bracket (#15718) -" 2024 Oct 21 by Vim Project: remove netrwFileHandlers (#15895) -" 2024 Oct 27 by Vim Project: clean up gx mapping (#15721) -" 2024 Oct 30 by Vim Project: fix filetype detection for remote files (#15961) -" 2024 Oct 30 by Vim Project: fix x mapping on cygwin (#13687) -" 2024 Oct 31 by Vim Project: add netrw#Launch() and netrw#Open() (#15962) -" 2024 Oct 31 by Vim Project: fix E874 when browsing remote dir (#15964) -" 2024 Nov 07 by Vim Project: use keeppatterns to prevent polluting the search history -" 2024 Nov 07 by Vim Project: fix a few issues with netrw tree listing (#15996) -" 2024 Nov 10 by Vim Project: directory symlink not resolved in tree view (#16020) -" 2024 Nov 14 by Vim Project: small fixes to netrw#BrowseX (#16056) -" 2024 Nov 23 by Vim Project: update decompress defaults (#16104) -" 2024 Nov 23 by Vim Project: fix powershell escaping issues (#16094) -" 2024 Dec 04 by Vim Project: do not detach for gvim (#16168) -" 2024 Dec 08 by Vim Project: check the first arg of netrw_browsex_viewer for being executable (#16185) -" 2024 Dec 12 by Vim Project: do not pollute the search history (#16206) -" 2024 Dec 19 by Vim Project: change style (#16248) -" 2024 Dec 20 by Vim Project: change style continued (#16266) -" }}} -" Former Maintainer: Charles E Campbell -" GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim -" Copyright: Copyright (C) 2016 Charles E. Campbell {{{1 -" Permission is hereby granted to use and distribute this code, -" with or without modifications, provided that this copyright -" notice is copied with it. Like anything else that's free, -" netrw.vim, netrwPlugin.vim, and netrwSettings.vim are provided -" *as is* and come with no warranty of any kind, either -" expressed or implied. By using this plugin, you agree that -" in no event will the copyright holder be liable for any damages -" resulting from the use of this software. -" -" Note: the code here was started in 1999 under a much earlier version of vim. The directory browsing -" code was written using vim v6, which did not have Lists (Lists were first offered with vim-v7). -" -"redraw!|call DechoSep()|call inputsave()|call input("Press to continue")|call inputrestore() -" -" But be doers of the Word, and not only hearers, deluding your own selves {{{1 -" (James 1:22 RSV) -" =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -" Load Once: {{{1 -if &cp || exists("g:loaded_netrw") - finish -endif - -" Check that vim has patches that netrw requires. -" Patches needed for v7.4: 1557, and 213. -" (netrw will benefit from vim's having patch#656, too) -let s:needspatches=[1557,213] -if exists("s:needspatches") - for ptch in s:needspatches - if v:version < 704 || (v:version == 704 && !has("patch".ptch)) - if !exists("s:needpatch{ptch}") - unsilent echomsg "***sorry*** this version of netrw requires vim v7.4 with patch#".ptch - endif - let s:needpatch{ptch}= 1 - finish - endif - endfor -endif - -let g:loaded_netrw = "v173" - -let s:keepcpo= &cpo -setl cpo&vim -"DechoFuncName 1 -"DechoRemOn -"call Decho("doing autoload/netrw.vim version ".g:loaded_netrw,'~'.expand("")) - -" ====================== -" Netrw Variables: {{{1 -" ====================== - -" --------------------------------------------------------------------- -" netrw#ErrorMsg: {{{2 -" 0=note = s:NOTE -" 1=warning = s:WARNING -" 2=error = s:ERROR -" Usage: netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,"some message",error-number) -" netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,["message1","message2",...],error-number) -" (this function can optionally take a list of messages) -" Dec 2, 2019 : max errnum currently is 106 -fun! netrw#ErrorMsg(level,msg,errnum) - " call Dfunc("netrw#ErrorMsg(level=".a:level." msg<".a:msg."> errnum=".a:errnum.") g:netrw_use_errorwindow=".g:netrw_use_errorwindow) - - if a:level < g:netrw_errorlvl - " call Dret("netrw#ErrorMsg : suppressing level=".a:level." since g:netrw_errorlvl=".g:netrw_errorlvl) - return - endif - - if a:level == 1 - let level= "**warning** (netrw) " - elseif a:level == 2 - let level= "**error** (netrw) " - else - let level= "**note** (netrw) " - endif - " call Decho("level=".level,'~'.expand("")) - - if g:netrw_use_errorwindow == 2 && exists("*popup_atcursor") - " use popup window - if type(a:msg) == 3 - let msg = [level]+a:msg - else - let msg= level.a:msg - endif - let s:popuperr_id = popup_atcursor(msg,{}) - let s:popuperr_text= "" - elseif g:netrw_use_errorwindow - " (default) netrw creates a one-line window to show error/warning - " messages (reliably displayed) - - " record current window number - let s:winBeforeErr= winnr() - " call Decho("s:winBeforeErr=".s:winBeforeErr,'~'.expand("")) - - " getting messages out reliably is just plain difficult! - " This attempt splits the current window, creating a one line window. - if bufexists("NetrwMessage") && bufwinnr("NetrwMessage") > 0 - " call Decho("write to NetrwMessage buffer",'~'.expand("")) - exe bufwinnr("NetrwMessage")."wincmd w" - " call Decho("setl ma noro",'~'.expand("")) - setl ma noro - if type(a:msg) == 3 - for msg in a:msg - NetrwKeepj call setline(line("$")+1,level.msg) - endfor - else - NetrwKeepj call setline(line("$")+1,level.a:msg) - endif - NetrwKeepj $ - else - " call Decho("create a NetrwMessage buffer window",'~'.expand("")) - bo 1split - sil! call s:NetrwEnew() - sil! NetrwKeepj call s:NetrwOptionsSafe(1) - setl bt=nofile - NetrwKeepj file NetrwMessage - " call Decho("setl ma noro",'~'.expand("")) - setl ma noro - if type(a:msg) == 3 - for msg in a:msg - NetrwKeepj call setline(line("$")+1,level.msg) - endfor - else - NetrwKeepj call setline(line("$"),level.a:msg) - endif - NetrwKeepj $ - endif - " call Decho("wrote msg<".level.a:msg."> to NetrwMessage win#".winnr(),'~'.expand("")) - if &fo !~ '[ta]' - syn clear - syn match netrwMesgNote "^\*\*note\*\*" - syn match netrwMesgWarning "^\*\*warning\*\*" - syn match netrwMesgError "^\*\*error\*\*" - hi link netrwMesgWarning WarningMsg - hi link netrwMesgError Error - endif - " call Decho("setl noma ro bh=wipe",'~'.expand("")) - setl ro nomod noma bh=wipe - - else - " (optional) netrw will show messages using echomsg. Even if the - " message doesn't appear, at least it'll be recallable via :messages - " redraw! - if a:level == s:WARNING - echohl WarningMsg - elseif a:level == s:ERROR - echohl Error - endif - - if type(a:msg) == 3 - for msg in a:msg - unsilent echomsg level.msg - endfor - else - unsilent echomsg level.a:msg - endif - - " call Decho("echomsg ***netrw*** ".a:msg,'~'.expand("")) - echohl None - endif - - " call Dret("netrw#ErrorMsg") -endfun - -" --------------------------------------------------------------------- -" s:NetrwInit: initializes variables if they haven't been defined {{{2 -" Loosely, varname = value. -fun s:NetrwInit(varname,value) - " call Decho("varname<".a:varname."> value=".a:value,'~'.expand("")) - if !exists(a:varname) - if type(a:value) == 0 - exe "let ".a:varname."=".a:value - elseif type(a:value) == 1 && a:value =~ '^[{[]' - exe "let ".a:varname."=".a:value - elseif type(a:value) == 1 - exe "let ".a:varname."="."'".a:value."'" - else - exe "let ".a:varname."=".a:value - endif - endif -endfun - -" --------------------------------------------------------------------- -" Netrw Constants: {{{2 -call s:NetrwInit("g:netrw_dirhistcnt",0) -if !exists("s:LONGLIST") - call s:NetrwInit("s:THINLIST",0) - call s:NetrwInit("s:LONGLIST",1) - call s:NetrwInit("s:WIDELIST",2) - call s:NetrwInit("s:TREELIST",3) - call s:NetrwInit("s:MAXLIST" ,4) -endif - -let s:NOTE = 0 -let s:WARNING = 1 -let s:ERROR = 2 -call s:NetrwInit("g:netrw_errorlvl", s:NOTE) - -" --------------------------------------------------------------------- -" Default option values: {{{2 -let g:netrw_localcopycmdopt = "" -let g:netrw_localcopydircmdopt = "" -let g:netrw_localmkdiropt = "" -let g:netrw_localmovecmdopt = "" - -" --------------------------------------------------------------------- -" Default values for netrw's global protocol variables {{{2 -if !exists("g:netrw_use_errorwindow") - let g:netrw_use_errorwindow = 0 -endif - -if !exists("g:netrw_dav_cmd") - if executable("cadaver") - let g:netrw_dav_cmd = "cadaver" - elseif executable("curl") - let g:netrw_dav_cmd = "curl" - else - let g:netrw_dav_cmd = "" - endif -endif -if !exists("g:netrw_fetch_cmd") - if executable("fetch") - let g:netrw_fetch_cmd = "fetch -o" - else - let g:netrw_fetch_cmd = "" - endif -endif -if !exists("g:netrw_file_cmd") - if executable("elinks") - call s:NetrwInit("g:netrw_file_cmd","elinks") - elseif executable("links") - call s:NetrwInit("g:netrw_file_cmd","links") - endif -endif -if !exists("g:netrw_ftp_cmd") - let g:netrw_ftp_cmd = "ftp" -endif -let s:netrw_ftp_cmd= g:netrw_ftp_cmd -if !exists("g:netrw_ftp_options") - let g:netrw_ftp_options= "-i -n" -endif -if !exists("g:netrw_http_cmd") - if executable("wget") - let g:netrw_http_cmd = "wget" - call s:NetrwInit("g:netrw_http_xcmd","-q -O") - elseif executable("curl") - let g:netrw_http_cmd = "curl" - call s:NetrwInit("g:netrw_http_xcmd","-L -o") - elseif executable("elinks") - let g:netrw_http_cmd = "elinks" - call s:NetrwInit("g:netrw_http_xcmd","-source >") - elseif executable("fetch") - let g:netrw_http_cmd = "fetch" - call s:NetrwInit("g:netrw_http_xcmd","-o") - elseif executable("links") - let g:netrw_http_cmd = "links" - call s:NetrwInit("g:netrw_http_xcmd","-http.extra-header ".shellescape("Accept-Encoding: identity", 1)." -source >") - else - let g:netrw_http_cmd = "" - endif -endif -call s:NetrwInit("g:netrw_http_put_cmd","curl -T") -call s:NetrwInit("g:netrw_keepj","keepj") -call s:NetrwInit("g:netrw_rcp_cmd" , "rcp") -call s:NetrwInit("g:netrw_rsync_cmd", "rsync") -call s:NetrwInit("g:netrw_rsync_sep", "/") -if !exists("g:netrw_scp_cmd") - if executable("scp") - call s:NetrwInit("g:netrw_scp_cmd" , "scp -q") - elseif executable("pscp") - call s:NetrwInit("g:netrw_scp_cmd", 'pscp -q') - else - call s:NetrwInit("g:netrw_scp_cmd" , "scp -q") - endif -endif -call s:NetrwInit("g:netrw_sftp_cmd" , "sftp") -call s:NetrwInit("g:netrw_ssh_cmd" , "ssh") - -if has("win32") - \ && exists("g:netrw_use_nt_rcp") - \ && g:netrw_use_nt_rcp - \ && executable( $SystemRoot .'/system32/rcp.exe') - let s:netrw_has_nt_rcp = 1 - let s:netrw_rcpmode = '-b' -else - let s:netrw_has_nt_rcp = 0 - let s:netrw_rcpmode = '' -endif - -" --------------------------------------------------------------------- -" Default values for netrw's global variables {{{2 -" Cygwin Detection ------- {{{3 -if !exists("g:netrw_cygwin") - if has("win32unix") && &shell =~ '\%(\\|\\)\%(\.exe\)\=$' - let g:netrw_cygwin= 1 - else - let g:netrw_cygwin= 0 - endif -endif -" Default values - a-c ---------- {{{3 -call s:NetrwInit("g:netrw_alto" , &sb) -call s:NetrwInit("g:netrw_altv" , &spr) -call s:NetrwInit("g:netrw_banner" , 1) -call s:NetrwInit("g:netrw_browse_split", 0) -call s:NetrwInit("g:netrw_bufsettings" , "noma nomod nonu nobl nowrap ro nornu") -call s:NetrwInit("g:netrw_chgwin" , -1) -call s:NetrwInit("g:netrw_clipboard" , 1) -call s:NetrwInit("g:netrw_compress" , "gzip") -call s:NetrwInit("g:netrw_ctags" , "ctags") -if exists("g:netrw_cursorline") && !exists("g:netrw_cursor") - call netrw#ErrorMsg(s:NOTE,'g:netrw_cursorline is deprecated; use g:netrw_cursor instead',77) - let g:netrw_cursor= g:netrw_cursorline -endif -call s:NetrwInit("g:netrw_cursor" , 2) -let s:netrw_usercul = &cursorline -let s:netrw_usercuc = &cursorcolumn -"call Decho("(netrw) COMBAK: cuc=".&l:cuc." cul=".&l:cul." initialization of s:netrw_cu[cl]") -call s:NetrwInit("g:netrw_cygdrive","/cygdrive") -" Default values - d-g ---------- {{{3 -call s:NetrwInit("s:didstarstar",0) -call s:NetrwInit("g:netrw_dirhistcnt" , 0) -let s:xz_opt = has('unix') ? "XZ_OPT=-T0" : - \ (has("win32") && &shell =~? '\vcmd(\.exe)?$' ? - \ "setx XZ_OPT=-T0 &&" : "") -call s:NetrwInit("g:netrw_decompress ", "{" - \ .."'.lz4': 'lz4 -d'," - \ .."'.lzo': 'lzop -d'," - \ .."'.lz': 'lzip -dk'," - \ .."'.7z': '7za x'," - \ .."'.001': '7za x'," - \ .."'.zip': 'unzip'," - \ .."'.bz': 'bunzip2 -k'," - \ .."'.bz2': 'bunzip2 -k'," - \ .."'.gz': 'gunzip -k'," - \ .."'.lzma': 'unlzma -T0 -k'," - \ .."'.xz': 'unxz -T0 -k'," - \ .."'.zst': 'zstd -T0 -d'," - \ .."'.Z': 'uncompress -k'," - \ .."'.tar': 'tar -xvf'," - \ .."'.tar.bz': 'tar -xvjf'," - \ .."'.tar.bz2': 'tar -xvjf'," - \ .."'.tbz': 'tar -xvjf'," - \ .."'.tbz2': 'tar -xvjf'," - \ .."'.tar.gz': 'tar -xvzf'," - \ .."'.tgz': 'tar -xvzf'," - \ .."'.tar.lzma': '"..s:xz_opt.." tar -xvf --lzma'," - \ .."'.tlz': '"..s:xz_opt.." tar -xvf --lzma'," - \ .."'.tar.xz': '"..s:xz_opt.." tar -xvfJ'," - \ .."'.txz': '"..s:xz_opt.." tar -xvfJ'," - \ .."'.tar.zst': '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd'," - \ .."'.tzst': '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd'," - \ .."'.rar': '"..(executable("unrar")?"unrar x -ad":"rar x -ad").."'" - \ .."}") -unlet s:xz_opt -call s:NetrwInit("g:netrw_dirhistmax" , 10) -call s:NetrwInit("g:netrw_fastbrowse" , 1) -call s:NetrwInit("g:netrw_ftp_browse_reject", '^total\s\+\d\+$\|^Trying\s\+\d\+.*$\|^KERBEROS_V\d rejected\|^Security extensions not\|No such file\|: connect to address [0-9a-fA-F:]*: No route to host$') -if !exists("g:netrw_ftp_list_cmd") - if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin) - let g:netrw_ftp_list_cmd = "ls -lF" - let g:netrw_ftp_timelist_cmd = "ls -tlF" - let g:netrw_ftp_sizelist_cmd = "ls -slF" - else - let g:netrw_ftp_list_cmd = "dir" - let g:netrw_ftp_timelist_cmd = "dir" - let g:netrw_ftp_sizelist_cmd = "dir" - endif -endif -call s:NetrwInit("g:netrw_ftpmode",'binary') -" Default values - h-lh ---------- {{{3 -call s:NetrwInit("g:netrw_hide",1) -if !exists("g:netrw_ignorenetrc") - if &shell =~ '\c\<\%(cmd\|4nt\)\.exe$' - let g:netrw_ignorenetrc= 1 - else - let g:netrw_ignorenetrc= 0 - endif -endif -call s:NetrwInit("g:netrw_keepdir",1) -if !exists("g:netrw_list_cmd") - if g:netrw_scp_cmd =~ '^pscp' && executable("pscp") - if exists("g:netrw_list_cmd_options") - let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME: ".g:netrw_list_cmd_options - else - let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME:" - endif - elseif executable(g:netrw_ssh_cmd) - " provide a scp-based default listing command - if exists("g:netrw_list_cmd_options") - let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa ".g:netrw_list_cmd_options - else - let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa" - endif - else - " call Decho(g:netrw_ssh_cmd." is not executable",'~'.expand("")) - let g:netrw_list_cmd= "" - endif -endif -call s:NetrwInit("g:netrw_list_hide","") -" Default values - lh-lz ---------- {{{3 -if exists("g:netrw_local_copycmd") - let g:netrw_localcopycmd= g:netrw_local_copycmd - call netrw#ErrorMsg(s:NOTE,"g:netrw_local_copycmd is deprecated in favor of g:netrw_localcopycmd",84) -endif -if !exists("g:netrw_localcmdshell") - let g:netrw_localcmdshell= "" -endif -if !exists("g:netrw_localcopycmd") - if has("win32") - if g:netrw_cygwin - let g:netrw_localcopycmd= "cp" - else - let g:netrw_localcopycmd = expand("$COMSPEC", v:true) - let g:netrw_localcopycmdopt= " /c copy" - endif - elseif has("unix") || has("macunix") - let g:netrw_localcopycmd= "cp" - else - let g:netrw_localcopycmd= "" - endif -endif -if !exists("g:netrw_localcopydircmd") - if has("win32") - if g:netrw_cygwin - let g:netrw_localcopydircmd = "cp" - let g:netrw_localcopydircmdopt= " -R" - else - let g:netrw_localcopydircmd = expand("$COMSPEC", v:true) - let g:netrw_localcopydircmdopt= " /c xcopy /e /c /h /i /k" - endif - elseif has("unix") - let g:netrw_localcopydircmd = "cp" - let g:netrw_localcopydircmdopt= " -R" - elseif has("macunix") - let g:netrw_localcopydircmd = "cp" - let g:netrw_localcopydircmdopt= " -R" - else - let g:netrw_localcopydircmd= "" - endif -endif -if exists("g:netrw_local_mkdir") - let g:netrw_localmkdir= g:netrw_local_mkdir - call netrw#ErrorMsg(s:NOTE,"g:netrw_local_mkdir is deprecated in favor of g:netrw_localmkdir",87) -endif -if has("win32") - if g:netrw_cygwin - call s:NetrwInit("g:netrw_localmkdir","mkdir") - else - let g:netrw_localmkdir = expand("$COMSPEC", v:true) - let g:netrw_localmkdiropt= " /c mkdir" - endif -else - call s:NetrwInit("g:netrw_localmkdir","mkdir") -endif -call s:NetrwInit("g:netrw_remote_mkdir","mkdir") -if exists("g:netrw_local_movecmd") - let g:netrw_localmovecmd= g:netrw_local_movecmd - call netrw#ErrorMsg(s:NOTE,"g:netrw_local_movecmd is deprecated in favor of g:netrw_localmovecmd",88) -endif -if !exists("g:netrw_localmovecmd") - if has("win32") - if g:netrw_cygwin - let g:netrw_localmovecmd= "mv" - else - let g:netrw_localmovecmd = expand("$COMSPEC", v:true) - let g:netrw_localmovecmdopt= " /c move" - endif - elseif has("unix") || has("macunix") - let g:netrw_localmovecmd= "mv" - else - let g:netrw_localmovecmd= "" - endif -endif -" following serves as an example for how to insert a version&patch specific test -"if v:version < 704 || (v:version == 704 && !has("patch1107")) -"endif -call s:NetrwInit("g:netrw_liststyle" , s:THINLIST) -" sanity checks -if g:netrw_liststyle < 0 || g:netrw_liststyle >= s:MAXLIST - let g:netrw_liststyle= s:THINLIST -endif -if g:netrw_liststyle == s:LONGLIST && g:netrw_scp_cmd !~ '^pscp' - let g:netrw_list_cmd= g:netrw_list_cmd." -l" -endif -" Default values - m-r ---------- {{{3 -call s:NetrwInit("g:netrw_markfileesc" , '*./[\~') -call s:NetrwInit("g:netrw_maxfilenamelen", 32) -call s:NetrwInit("g:netrw_menu" , 1) -call s:NetrwInit("g:netrw_mkdir_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME mkdir") -call s:NetrwInit("g:netrw_mousemaps" , (exists("+mouse") && &mouse =~# '[anh]')) -call s:NetrwInit("g:netrw_retmap" , 0) -if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin) - call s:NetrwInit("g:netrw_chgperm" , "chmod PERM FILENAME") -elseif has("win32") - call s:NetrwInit("g:netrw_chgperm" , "cacls FILENAME /e /p PERM") -else - call s:NetrwInit("g:netrw_chgperm" , "chmod PERM FILENAME") -endif -call s:NetrwInit("g:netrw_preview" , 0) -call s:NetrwInit("g:netrw_scpport" , "-P") -call s:NetrwInit("g:netrw_servername" , "NETRWSERVER") -call s:NetrwInit("g:netrw_sshport" , "-p") -call s:NetrwInit("g:netrw_rename_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME mv") -call s:NetrwInit("g:netrw_rm_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rm") -call s:NetrwInit("g:netrw_rmdir_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rmdir") -call s:NetrwInit("g:netrw_rmf_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rm -f ") -" Default values - q-s ---------- {{{3 -call s:NetrwInit("g:netrw_quickhelp",0) -let s:QuickHelp= ["-:go up dir D:delete R:rename s:sort-by x:special", - \ "(create new) %:file d:directory", - \ "(windows split&open) o:horz v:vert p:preview", - \ "i:style qf:file info O:obtain r:reverse", - \ "(marks) mf:mark file mt:set target mm:move mc:copy", - \ "(bookmarks) mb:make mB:delete qb:list gb:go to", - \ "(history) qb:list u:go up U:go down", - \ "(targets) mt:target Tb:use bookmark Th:use history"] -" g:netrw_sepchr: picking a character that doesn't appear in filenames that can be used to separate priority from filename -call s:NetrwInit("g:netrw_sepchr" , (&enc == "euc-jp")? "\" : "\") -if !exists("g:netrw_keepj") || g:netrw_keepj == "keepj" - call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil keepj " : "keepj ") -else - call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil " : " ") -endif -call s:NetrwInit("g:netrw_sort_by" , "name") " alternatives: date , size -call s:NetrwInit("g:netrw_sort_options" , "") -call s:NetrwInit("g:netrw_sort_direction", "normal") " alternative: reverse (z y x ...) -if !exists("g:netrw_sort_sequence") - if has("unix") - let g:netrw_sort_sequence= '[\/]$,\,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$' - else - let g:netrw_sort_sequence= '[\/]$,\.h$,\.c$,\.cpp$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$' - endif -endif -call s:NetrwInit("g:netrw_special_syntax" , 0) -call s:NetrwInit("g:netrw_ssh_browse_reject", '^total\s\+\d\+$') -call s:NetrwInit("g:netrw_use_noswf" , 1) -call s:NetrwInit("g:netrw_sizestyle" ,"b") -" Default values - t-w ---------- {{{3 -call s:NetrwInit("g:netrw_timefmt","%c") -if !exists("g:netrw_xstrlen") - if exists("g:Align_xstrlen") - let g:netrw_xstrlen= g:Align_xstrlen - elseif exists("g:drawit_xstrlen") - let g:netrw_xstrlen= g:drawit_xstrlen - elseif &enc == "latin1" || !has("multi_byte") - let g:netrw_xstrlen= 0 - else - let g:netrw_xstrlen= 1 - endif -endif -call s:NetrwInit("g:NetrwTopLvlMenu","Netrw.") -call s:NetrwInit("g:netrw_winsize",50) -call s:NetrwInit("g:netrw_wiw",1) -if g:netrw_winsize > 100|let g:netrw_winsize= 100|endif -" --------------------------------------------------------------------- -" Default values for netrw's script variables: {{{2 -call s:NetrwInit("g:netrw_fname_escape",' ?&;%') -if has("win32") - call s:NetrwInit("g:netrw_glob_escape",'*?`{[]$') -else - call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\') -endif -call s:NetrwInit("g:netrw_menu_escape",'.&? \') -call s:NetrwInit("g:netrw_tmpfile_escape",' &;') -call s:NetrwInit("s:netrw_map_escape","<|\n\r\\\\"") -if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4') - let s:treedepthstring= "│ " -else - let s:treedepthstring= "| " -endif -call s:NetrwInit("s:netrw_posn",'{}') - -" BufEnter event ignored by decho when following variable is true -" Has a side effect that doau BufReadPost doesn't work, so -" files read by network transfer aren't appropriately highlighted. -"let g:decho_bufenter = 1 "Decho - -" ====================== -" Netrw Initialization: {{{1 -" ====================== -if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on") - " call Decho("installed beval events",'~'.expand("")) - let &l:bexpr = "netrw#BalloonHelp()" - " call Decho("&l:bexpr<".&l:bexpr."> buf#".bufnr()) - au FileType netrw setl beval - au WinLeave * if &ft == "netrw" && exists("s:initbeval")|let &beval= s:initbeval|endif - au VimEnter * let s:initbeval= &beval - "else " Decho - " if v:version < 700 | call Decho("did not install beval events: v:version=".v:version." < 700","~".expand("")) | endif - " if !has("balloon_eval") | call Decho("did not install beval events: does not have balloon_eval","~".expand("")) | endif - " if exists("s:initbeval") | call Decho("did not install beval events: s:initbeval exists","~".expand("")) | endif - " if exists("g:netrw_nobeval") | call Decho("did not install beval events: g:netrw_nobeval exists","~".expand("")) | endif - " if !has("syntax") | call Decho("did not install beval events: does not have syntax highlighting","~".expand("")) | endif - " if exists("g:syntax_on") | call Decho("did not install beval events: g:syntax_on exists","~".expand("")) | endif -endif -au WinEnter * if &ft == "netrw"|call s:NetrwInsureWinVars()|endif - -if g:netrw_keepj =~# "keepj" - com! -nargs=* NetrwKeepj keepj -else - let g:netrw_keepj= "" - com! -nargs=* NetrwKeepj -endif - -" ============================== -" Netrw Utility Functions: {{{1 -" ============================== - -" --------------------------------------------------------------------- -" netrw#BalloonHelp: {{{2 -if v:version >= 700 && has("balloon_eval") && has("syntax") && exists("g:syntax_on") && !exists("g:netrw_nobeval") - " call Decho("loading netrw#BalloonHelp()",'~'.expand("")) - fun! netrw#BalloonHelp() - if &ft != "netrw" - return "" - endif - if exists("s:popuperr_id") && popup_getpos(s:popuperr_id) != {} - " popup error window is still showing - " s:pouperr_id and s:popuperr_text are set up in netrw#ErrorMsg() - if exists("s:popuperr_text") && s:popuperr_text != "" && v:beval_text != s:popuperr_text - " text under mouse hasn't changed; only close window when it changes - call popup_close(s:popuperr_id) - unlet s:popuperr_text - else - let s:popuperr_text= v:beval_text - endif - let mesg= "" - elseif !exists("w:netrw_bannercnt") || v:beval_lnum >= w:netrw_bannercnt || (exists("g:netrw_nobeval") && g:netrw_nobeval) - let mesg= "" - elseif v:beval_text == "Netrw" || v:beval_text == "Directory" || v:beval_text == "Listing" - let mesg = "i: thin-long-wide-tree gh: quick hide/unhide of dot-files qf: quick file info %:open new file" - elseif getline(v:beval_lnum) =~ '^"\s*/' - let mesg = ": edit/enter o: edit/enter in horiz window t: edit/enter in new tab v:edit/enter in vert window" - elseif v:beval_text == "Sorted" || v:beval_text == "by" - let mesg = 's: sort by name, time, file size, extension r: reverse sorting order mt: mark target' - elseif v:beval_text == "Sort" || v:beval_text == "sequence" - let mesg = "S: edit sorting sequence" - elseif v:beval_text == "Hiding" || v:beval_text == "Showing" - let mesg = "a: hiding-showing-all ctrl-h: editing hiding list mh: hide/show by suffix" - elseif v:beval_text == "Quick" || v:beval_text == "Help" - let mesg = "Help: press " - elseif v:beval_text == "Copy/Move" || v:beval_text == "Tgt" - let mesg = "mt: mark target mc: copy marked file to target mm: move marked file to target" - else - let mesg= "" - endif - return mesg - endfun - "else " Decho - " if v:version < 700 |call Decho("did not load netrw#BalloonHelp(): vim version ".v:version." < 700 -","~".expand(""))|endif - " if !has("balloon_eval") |call Decho("did not load netrw#BalloonHelp(): does not have balloon eval","~".expand("")) |endif - " if !has("syntax") |call Decho("did not load netrw#BalloonHelp(): syntax disabled","~".expand("")) |endif - " if !exists("g:syntax_on") |call Decho("did not load netrw#BalloonHelp(): g:syntax_on n/a","~".expand("")) |endif - " if exists("g:netrw_nobeval") |call Decho("did not load netrw#BalloonHelp(): g:netrw_nobeval exists","~".expand("")) |endif -endif - -" ------------------------------------------------------------------------ -" netrw#Explore: launch the local browser in the directory of the current file {{{2 -" indx: == -1: Nexplore -" == -2: Pexplore -" == +: this is overloaded: -" * If Nexplore/Pexplore is in use, then this refers to the -" indx'th item in the w:netrw_explore_list[] of items which -" matched the */pattern **/pattern *//pattern **//pattern -" * If Hexplore or Vexplore, then this will override -" g:netrw_winsize to specify the qty of rows or columns the -" newly split window should have. -" dosplit==0: the window will be split iff the current file has been modified and hidden not set -" dosplit==1: the window will be split before running the local browser -" style == 0: Explore style == 1: Explore! -" == 2: Hexplore style == 3: Hexplore! -" == 4: Vexplore style == 5: Vexplore! -" == 6: Texplore -fun! netrw#Explore(indx,dosplit,style,...) - if !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - endif - - " record current file for Rexplore's benefit - if &ft != "netrw" - let w:netrw_rexfile= expand("%:p") - endif - - " record current directory - let curdir = simplify(b:netrw_curdir) - let curfiledir = substitute(expand("%:p"),'^\(.*[/\\]\)[^/\\]*$','\1','e') - if !exists("g:netrw_cygwin") && has("win32") - let curdir= substitute(curdir,'\','/','g') - endif - - " using completion, directories with spaces in their names (thanks, Bill Gates, for a truly dumb idea) - " will end up with backslashes here. Solution: strip off backslashes that precede white space and - " try Explore again. - if a:0 > 0 - if a:1 =~ "\\\s" && !filereadable(s:NetrwFile(a:1)) && !isdirectory(s:NetrwFile(a:1)) - let a1 = substitute(a:1, '\\\(\s\)', '\1', 'g') - if a1 != a:1 - call netrw#Explore(a:indx, a:dosplit, a:style, a1) - return - endif - endif - endif - - " save registers - if !has('nvim') && has("clipboard") && g:netrw_clipboard - " call Decho("(netrw#Explore) save @* and @+",'~'.expand("")) - sil! let keepregstar = @* - sil! let keepregplus = @+ - endif - sil! let keepregslash= @/ - - " if dosplit - " -or- file has been modified AND file not hidden when abandoned - " -or- Texplore used - if a:dosplit || (&modified && &hidden == 0 && &bufhidden != "hide") || a:style == 6 - call s:SaveWinVars() - let winsz= g:netrw_winsize - if a:indx > 0 - let winsz= a:indx - endif - - if a:style == 0 " Explore, Sexplore - let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz - if winsz == 0|let winsz= ""|endif - exe "noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s" - - elseif a:style == 1 " Explore!, Sexplore! - let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz - if winsz == 0|let winsz= ""|endif - exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v" - - elseif a:style == 2 " Hexplore - let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz - if winsz == 0|let winsz= ""|endif - exe "keepalt noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s" - - elseif a:style == 3 " Hexplore! - let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz - if winsz == 0|let winsz= ""|endif - exe "keepalt noswapfile ".(!g:netrw_alto ? "below " : "above ").winsz."wincmd s" - - elseif a:style == 4 " Vexplore - let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz - if winsz == 0|let winsz= ""|endif - exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v" - - elseif a:style == 5 " Vexplore! - let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz - if winsz == 0|let winsz= ""|endif - exe "keepalt noswapfile ".(!g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v" - - elseif a:style == 6 " Texplore - call s:SaveBufVars() - exe "keepalt tabnew ".fnameescape(curdir) - call s:RestoreBufVars() - endif - call s:RestoreWinVars() - endif - NetrwKeepj norm! 0 - - if a:0 > 0 - if a:1 =~ '^\~' && (has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin)) - let dirname= simplify(substitute(a:1,'\~',expand("$HOME"),'')) - elseif a:1 == '.' - let dirname= simplify(exists("b:netrw_curdir")? b:netrw_curdir : getcwd()) - if dirname !~ '/$' - let dirname= dirname."/" - endif - elseif a:1 =~ '\$' - let dirname= simplify(expand(a:1)) - elseif a:1 !~ '^\*\{1,2}/' && a:1 !~ '^\a\{3,}://' - let dirname= simplify(a:1) - else - let dirname= a:1 - endif - else - " clear explore - call s:NetrwClearExplore() - return - endif - - if dirname =~ '\.\./\=$' - let dirname= simplify(fnamemodify(dirname,':p:h')) - elseif dirname =~ '\.\.' || dirname == '.' - let dirname= simplify(fnamemodify(dirname,':p')) - endif - - if dirname =~ '^\*//' - " starpat=1: Explore *//pattern (current directory only search for files containing pattern) - let pattern= substitute(dirname,'^\*//\(.*\)$','\1','') - let starpat= 1 - if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif - - elseif dirname =~ '^\*\*//' - " starpat=2: Explore **//pattern (recursive descent search for files containing pattern) - let pattern= substitute(dirname,'^\*\*//','','') - let starpat= 2 - - elseif dirname =~ '/\*\*/' - " handle .../**/.../filepat - let prefixdir= substitute(dirname,'^\(.\{-}\)\*\*.*$','\1','') - if prefixdir =~ '^/' || (prefixdir =~ '^\a:/' && has("win32")) - let b:netrw_curdir = prefixdir - else - let b:netrw_curdir= getcwd().'/'.prefixdir - endif - let dirname= substitute(dirname,'^.\{-}\(\*\*/.*\)$','\1','') - let starpat= 4 - - elseif dirname =~ '^\*/' - " case starpat=3: Explore */filepat (search in current directory for filenames matching filepat) - let starpat= 3 - - elseif dirname=~ '^\*\*/' - " starpat=4: Explore **/filepat (recursive descent search for filenames matching filepat) - let starpat= 4 - - else - let starpat= 0 - endif - - if starpat == 0 && a:indx >= 0 - " [Explore Hexplore Vexplore Sexplore] [dirname] - if dirname == "" - let dirname= curfiledir - endif - if dirname =~# '^scp://' || dirname =~ '^ftp://' - call netrw#Nread(2,dirname) - else - if dirname == "" - let dirname= getcwd() - elseif has("win32") && !g:netrw_cygwin - " Windows : check for a drive specifier, or else for a remote share name ('\\Foo' or '//Foo', - " depending on whether backslashes have been converted to forward slashes by earlier code). - if dirname !~ '^[a-zA-Z]:' && dirname !~ '^\\\\\w\+' && dirname !~ '^//\w\+' - let dirname= b:netrw_curdir."/".dirname - endif - elseif dirname !~ '^/' - let dirname= b:netrw_curdir."/".dirname - endif - call netrw#LocalBrowseCheck(dirname) - endif - if exists("w:netrw_bannercnt") - " done to handle P08-Ingelrest. :Explore will _Always_ go to the line just after the banner. - " If one wants to return the same place in the netrw window, use :Rex instead. - exe w:netrw_bannercnt - endif - - - " starpat=1: Explore *//pattern (current directory only search for files containing pattern) - " starpat=2: Explore **//pattern (recursive descent search for files containing pattern) - " starpat=3: Explore */filepat (search in current directory for filenames matching filepat) - " starpat=4: Explore **/filepat (recursive descent search for filenames matching filepat) - elseif a:indx <= 0 - " Nexplore, Pexplore, Explore: handle starpat - if !mapcheck("","n") && !mapcheck("","n") && exists("b:netrw_curdir") - let s:didstarstar= 1 - nnoremap :Pexplore - nnoremap :Nexplore - endif - - if has("path_extra") - if !exists("w:netrw_explore_indx") - let w:netrw_explore_indx= 0 - endif - - let indx = a:indx - - if indx == -1 - " Nexplore - if !exists("w:netrw_explore_list") " sanity check - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or improperly; see help for netrw-starstar",40) - if !has('nvim') && has("clipboard") && g:netrw_clipboard - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - sil! let @/ = keepregslash - return - endif - let indx= w:netrw_explore_indx - if indx < 0 | let indx= 0 | endif - if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif - let curfile= w:netrw_explore_list[indx] - while indx < w:netrw_explore_listlen && curfile == w:netrw_explore_list[indx] - let indx= indx + 1 - endwhile - if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif - - elseif indx == -2 - " Pexplore - if !exists("w:netrw_explore_list") " sanity check - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or improperly; see help for netrw-starstar",41) - if !has('nvim') && has("clipboard") && g:netrw_clipboard - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - sil! let @/ = keepregslash - return - endif - let indx= w:netrw_explore_indx - if indx < 0 | let indx= 0 | endif - if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif - let curfile= w:netrw_explore_list[indx] - while indx >= 0 && curfile == w:netrw_explore_list[indx] - let indx= indx - 1 - endwhile - if indx < 0 | let indx= 0 | endif - - else - " Explore -- initialize - " build list of files to Explore with Nexplore/Pexplore - NetrwKeepj keepalt call s:NetrwClearExplore() - let w:netrw_explore_indx= 0 - if !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - endif - - " switch on starpat to build the w:netrw_explore_list of files - if starpat == 1 - " starpat=1: Explore *//pattern (current directory only search for files containing pattern) - try - exe "NetrwKeepj noautocmd vimgrep /".pattern."/gj ".fnameescape(b:netrw_curdir)."/*" - catch /^Vim\%((\a\+)\)\=:E480/ - keepalt call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pattern.">",76) - return - endtry - let w:netrw_explore_list = s:NetrwExploreListUniq(map(getqflist(),'bufname(v:val.bufnr)')) - if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif - - elseif starpat == 2 - " starpat=2: Explore **//pattern (recursive descent search for files containing pattern) - try - exe "sil NetrwKeepj noautocmd keepalt vimgrep /".pattern."/gj "."**/*" - catch /^Vim\%((\a\+)\)\=:E480/ - keepalt call netrw#ErrorMsg(s:WARNING,'no files matched pattern<'.pattern.'>',45) - if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif - if !has('nvim') && has("clipboard") && g:netrw_clipboard - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - sil! let @/ = keepregslash - return - endtry - let s:netrw_curdir = b:netrw_curdir - let w:netrw_explore_list = getqflist() - let w:netrw_explore_list = s:NetrwExploreListUniq(map(w:netrw_explore_list,'s:netrw_curdir."/".bufname(v:val.bufnr)')) - if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif - - elseif starpat == 3 - " starpat=3: Explore */filepat (search in current directory for filenames matching filepat) - let filepat= substitute(dirname,'^\*/','','') - let filepat= substitute(filepat,'^[%#<]','\\&','') - let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".filepat),'\n')) - if &hls | let keepregslash= s:ExplorePatHls(filepat) | endif - - elseif starpat == 4 - " starpat=4: Explore **/filepat (recursive descent search for filenames matching filepat) - let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".dirname),'\n')) - if &hls | let keepregslash= s:ExplorePatHls(dirname) | endif - endif " switch on starpat to build w:netrw_explore_list - - let w:netrw_explore_listlen = len(w:netrw_explore_list) - - if w:netrw_explore_listlen == 0 || (w:netrw_explore_listlen == 1 && w:netrw_explore_list[0] =~ '\*\*\/') - keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no files matched",42) - if !has('nvim') && has("clipboard") && g:netrw_clipboard - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - sil! let @/ = keepregslash - return - endif - endif " if indx ... endif - - " NetrwStatusLine support - for exploring support - let w:netrw_explore_indx= indx - - " wrap the indx around, but issue a note - if indx >= w:netrw_explore_listlen || indx < 0 - let indx = (indx < 0)? ( w:netrw_explore_listlen - 1 ) : 0 - let w:netrw_explore_indx= indx - keepalt NetrwKeepj call netrw#ErrorMsg(s:NOTE,"no more files match Explore pattern",43) - endif - - exe "let dirfile= w:netrw_explore_list[".indx."]" - let newdir= substitute(dirfile,'/[^/]*$','','e') - - call netrw#LocalBrowseCheck(newdir) - if !exists("w:netrw_liststyle") - let w:netrw_liststyle= g:netrw_liststyle - endif - if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:LONGLIST - keepalt NetrwKeepj call search('^'.substitute(dirfile,"^.*/","","").'\>',"W") - else - keepalt NetrwKeepj call search('\<'.substitute(dirfile,"^.*/","","").'\>',"w") - endif - let w:netrw_explore_mtchcnt = indx + 1 - let w:netrw_explore_bufnr = bufnr("%") - let w:netrw_explore_line = line(".") - keepalt NetrwKeepj call s:SetupNetrwStatusLine('%f %h%m%r%=%9*%{NetrwStatusLine()}') - - else - if !exists("g:netrw_quiet") - keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44) - endif - if !has('nvim') && has("clipboard") && g:netrw_clipboard - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - sil! let @/ = keepregslash - return - endif - - else - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && dirname =~ '/' - sil! unlet w:netrw_treedict - sil! unlet w:netrw_treetop - endif - let newdir= dirname - if !exists("b:netrw_curdir") - NetrwKeepj call netrw#LocalBrowseCheck(getcwd()) - else - NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,newdir,0)) - endif - endif - - " visual display of **/ **// */ Exploration files - if exists("w:netrw_explore_indx") && exists("b:netrw_curdir") - if !exists("s:explore_prvdir") || s:explore_prvdir != b:netrw_curdir - " only update match list when current directory isn't the same as before - let s:explore_prvdir = b:netrw_curdir - let s:explore_match = "" - let dirlen = strlen(b:netrw_curdir) - if b:netrw_curdir !~ '/$' - let dirlen= dirlen + 1 - endif - let prvfname= "" - for fname in w:netrw_explore_list - if fname =~ '^'.b:netrw_curdir - if s:explore_match == "" - let s:explore_match= '\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>' - else - let s:explore_match= s:explore_match.'\|\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>' - endif - elseif fname !~ '^/' && fname != prvfname - if s:explore_match == "" - let s:explore_match= '\<'.escape(fname,g:netrw_markfileesc).'\>' - else - let s:explore_match= s:explore_match.'\|\<'.escape(fname,g:netrw_markfileesc).'\>' - endif - endif - let prvfname= fname - endfor - if has("syntax") && exists("g:syntax_on") && g:syntax_on - exe "2match netrwMarkFile /".s:explore_match."/" - endif - endif - echo "==Pexplore ==Nexplore" - else - 2match none - if exists("s:explore_match") | unlet s:explore_match | endif - if exists("s:explore_prvdir") | unlet s:explore_prvdir | endif - endif - - " since Explore may be used to initialize netrw's browser, - " there's no danger of a late FocusGained event on initialization. - " Consequently, set s:netrw_events to 2. - let s:netrw_events= 2 - if !has('nvim') && has("clipboard") && g:netrw_clipboard - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - sil! let @/ = keepregslash -endfun - -" --------------------------------------------------------------------- -" netrw#Lexplore: toggle Explorer window, keeping it on the left of the current tab {{{2 -" Uses g:netrw_chgwin : specifies the window where Lexplore files are to be opened -" t:netrw_lexposn : winsaveview() output (used on Lexplore window) -" t:netrw_lexbufnr: the buffer number of the Lexplore buffer (internal to this function) -" s:lexplore_win : window number of Lexplore window (serves to indicate which window is a Lexplore window) -" w:lexplore_buf : buffer number of Lexplore window (serves to indicate which window is a Lexplore window) -fun! netrw#Lexplore(count,rightside,...) - " call Dfunc("netrw#Lexplore(count=".a:count." rightside=".a:rightside.",...) a:0=".a:0." ft=".&ft) - let curwin= winnr() - - if a:0 > 0 && a:1 != "" - " if a netrw window is already on the left-side of the tab - " and a directory has been specified, explore with that - " directory. - let a1 = expand(a:1) - exe "1wincmd w" - if &ft == "netrw" - exe "Explore ".fnameescape(a1) - exe curwin."wincmd w" - let s:lexplore_win= curwin - let w:lexplore_buf= bufnr("%") - if exists("t:netrw_lexposn") - unlet t:netrw_lexposn - endif - return - endif - exe curwin."wincmd w" - else - let a1= "" - endif - - if exists("t:netrw_lexbufnr") - " check if t:netrw_lexbufnr refers to a netrw window - let lexwinnr = bufwinnr(t:netrw_lexbufnr) - else - let lexwinnr= 0 - endif - - if lexwinnr > 0 - " close down netrw explorer window - exe lexwinnr."wincmd w" - let g:netrw_winsize = -winwidth(0) - let t:netrw_lexposn = winsaveview() - close - if lexwinnr < curwin - let curwin= curwin - 1 - endif - if lexwinnr != curwin - exe curwin."wincmd w" - endif - unlet t:netrw_lexbufnr - - else - " open netrw explorer window - exe "1wincmd w" - let keep_altv = g:netrw_altv - let g:netrw_altv = 0 - if a:count != 0 - let netrw_winsize = g:netrw_winsize - let g:netrw_winsize = a:count - endif - let curfile= expand("%") - exe (a:rightside? "botright" : "topleft")." vertical ".((g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize) . " new" - if a:0 > 0 && a1 != "" - call netrw#Explore(0,0,0,a1) - exe "Explore ".fnameescape(a1) - elseif curfile =~ '^\a\{3,}://' - call netrw#Explore(0,0,0,substitute(curfile,'[^/\\]*$','','')) - else - call netrw#Explore(0,0,0,".") - endif - if a:count != 0 - let g:netrw_winsize = netrw_winsize - endif - setlocal winfixwidth - let g:netrw_altv = keep_altv - let t:netrw_lexbufnr = bufnr("%") - " done to prevent build-up of hidden buffers due to quitting and re-invocation of :Lexplore. - " Since the intended use of :Lexplore is to have an always-present explorer window, the extra - " effort to prevent mis-use of :Lex is warranted. - set bh=wipe - if exists("t:netrw_lexposn") - call winrestview(t:netrw_lexposn) - unlet t:netrw_lexposn - endif - endif - - " set up default window for editing via - if exists("g:netrw_chgwin") && g:netrw_chgwin == -1 - if a:rightside - let g:netrw_chgwin= 1 - else - let g:netrw_chgwin= 2 - endif - endif - -endfun - -" --------------------------------------------------------------------- -" netrw#Clean: remove netrw {{{2 -" supports :NetrwClean -- remove netrw from first directory on runtimepath -" :NetrwClean! -- remove netrw from all directories on runtimepath -fun! netrw#Clean(sys) - " call Dfunc("netrw#Clean(sys=".a:sys.")") - - if a:sys - let choice= confirm("Remove personal and system copies of netrw?","&Yes\n&No") - else - let choice= confirm("Remove personal copy of netrw?","&Yes\n&No") - endif - " call Decho("choice=".choice,'~'.expand("")) - let diddel= 0 - let diddir= "" - - if choice == 1 - for dir in split(&rtp,',') - if filereadable(dir."/plugin/netrwPlugin.vim") - " call Decho("removing netrw-related files from ".dir,'~'.expand("")) - if s:NetrwDelete(dir."/plugin/netrwPlugin.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/plugin/netrwPlugin.vim",55) |endif - if s:NetrwDelete(dir."/autoload/netrwFileHandlers.vim")|call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwFileHandlers.vim",55)|endif - if s:NetrwDelete(dir."/autoload/netrwSettings.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwSettings.vim",55) |endif - if s:NetrwDelete(dir."/autoload/netrw.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrw.vim",55) |endif - if s:NetrwDelete(dir."/syntax/netrw.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrw.vim",55) |endif - if s:NetrwDelete(dir."/syntax/netrwlist.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrwlist.vim",55) |endif - let diddir= dir - let diddel= diddel + 1 - if !a:sys|break|endif - endif - endfor - endif - - echohl WarningMsg - if diddel == 0 - echomsg "netrw is either not installed or not removable" - elseif diddel == 1 - echomsg "removed one copy of netrw from <".diddir.">" - else - echomsg "removed ".diddel." copies of netrw" - endif - echohl None - - " call Dret("netrw#Clean") -endfun - -" --------------------------------------------------------------------- -" netrw#MakeTgt: make a target out of the directory name provided {{{2 -fun! netrw#MakeTgt(dname) - " call Dfunc("netrw#MakeTgt(dname<".a:dname.">)") - " simplify the target (eg. /abc/def/../ghi -> /abc/ghi) - let svpos = winsaveview() - " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) - let s:netrwmftgt_islocal= (a:dname !~ '^\a\{3,}://') - " call Decho("s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand("")) - if s:netrwmftgt_islocal - let netrwmftgt= simplify(a:dname) - else - let netrwmftgt= a:dname - endif - if exists("s:netrwmftgt") && netrwmftgt == s:netrwmftgt - " re-selected target, so just clear it - unlet s:netrwmftgt s:netrwmftgt_islocal - else - let s:netrwmftgt= netrwmftgt - endif - if g:netrw_fastbrowse <= 1 - call s:NetrwRefresh((b:netrw_curdir !~ '\a\{3,}://'),b:netrw_curdir) - endif - " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))" - call winrestview(svpos) - " call Dret("netrw#MakeTgt") -endfun - -" --------------------------------------------------------------------- -" netrw#Obtain: {{{2 -" netrw#Obtain(islocal,fname[,tgtdirectory]) -" islocal=0 obtain from remote source -" =1 obtain from local source -" fname : a filename or a list of filenames -" tgtdir : optional place where files are to go (not present, uses getcwd()) -fun! netrw#Obtain(islocal,fname,...) - " call Dfunc("netrw#Obtain(islocal=".a:islocal." fname<".((type(a:fname) == 1)? a:fname : string(a:fname)).">) a:0=".a:0) - " NetrwStatusLine support - for obtaining support - - if type(a:fname) == 1 - let fnamelist= [ a:fname ] - elseif type(a:fname) == 3 - let fnamelist= a:fname - else - call netrw#ErrorMsg(s:ERROR,"attempting to use NetrwObtain on something not a filename or a list",62) - " call Dret("netrw#Obtain") - return - endif - " call Decho("fnamelist<".string(fnamelist).">",'~'.expand("")) - if a:0 > 0 - let tgtdir= a:1 - else - let tgtdir= getcwd() - endif - " call Decho("tgtdir<".tgtdir.">",'~'.expand("")) - - if exists("b:netrw_islocal") && b:netrw_islocal - " obtain a file from local b:netrw_curdir to (local) tgtdir - " call Decho("obtain a file from local ".b:netrw_curdir." to ".tgtdir,'~'.expand("")) - if exists("b:netrw_curdir") && getcwd() != b:netrw_curdir - let topath= s:ComposePath(tgtdir,"") - if has("win32") - " transfer files one at time - " call Decho("transfer files one at a time",'~'.expand("")) - for fname in fnamelist - " call Decho("system(".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath).")",'~'.expand("")) - call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".s:ShellEscape(fname)." ".s:ShellEscape(topath)) - if v:shell_error != 0 - call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80) - " call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath)) - return - endif - endfor - else - " transfer files with one command - " call Decho("transfer files with one command",'~'.expand("")) - let filelist= join(map(deepcopy(fnamelist),"s:ShellEscape(v:val)")) - " call Decho("system(".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath).")",'~'.expand("")) - call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".filelist." ".s:ShellEscape(topath)) - if v:shell_error != 0 - call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80) - " call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath)) - return - endif - endif - elseif !exists("b:netrw_curdir") - call netrw#ErrorMsg(s:ERROR,"local browsing directory doesn't exist!",36) - else - call netrw#ErrorMsg(s:WARNING,"local browsing directory and current directory are identical",37) - endif - - else - " obtain files from remote b:netrw_curdir to local tgtdir - " call Decho("obtain a file from remote ".b:netrw_curdir." to ".tgtdir,'~'.expand("")) - if type(a:fname) == 1 - call s:SetupNetrwStatusLine('%f %h%m%r%=%9*Obtaining '.a:fname) - endif - call s:NetrwMethod(b:netrw_curdir) - - if b:netrw_method == 4 - " obtain file using scp - " call Decho("obtain via scp (method#4)",'~'.expand("")) - if exists("g:netrw_port") && g:netrw_port != "" - let useport= " ".g:netrw_scpport." ".g:netrw_port - else - let useport= "" - endif - if b:netrw_fname =~ '/' - let path= substitute(b:netrw_fname,'^\(.*/\).\{-}$','\1','') - else - let path= "" - endif - let filelist= join(map(deepcopy(fnamelist),'escape(s:ShellEscape(g:netrw_machine.":".path.v:val,1)," ")')) - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".filelist." ".s:ShellEscape(tgtdir,1)) - - elseif b:netrw_method == 2 - " obtain file using ftp + .netrc - " call Decho("obtain via ftp+.netrc (method #2)",'~'.expand("")) - call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars() - let tmpbufnr= bufnr("%") - setl ff=unix - if exists("g:netrw_ftpmode") && g:netrw_ftpmode != "" - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("b:netrw_fname") && b:netrw_fname != "" - call setline(line("$")+1,'cd "'.b:netrw_fname.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - for fname in fnamelist - call setline(line("$")+1,'get "'.fname.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endfor - if exists("g:netrw_port") && g:netrw_port != "" - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) - else - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) - endif - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' - let debugkeep= &debug - setl debug=msg - call netrw#ErrorMsg(s:ERROR,getline(1),4) - let &debug= debugkeep - endif - - elseif b:netrw_method == 3 - " obtain with ftp + machine, id, passwd, and fname (ie. no .netrc) - " call Decho("obtain via ftp+mipf (method #3)",'~'.expand("")) - call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars() - let tmpbufnr= bufnr("%") - setl ff=unix - - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - " call Decho("filter input: ".getline('$'),'~'.expand("")) - else - NetrwKeepj put ='open '.g:netrw_machine - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("g:netrw_uid") && g:netrw_uid != "" - if exists("g:netrw_ftp") && g:netrw_ftp == 1 - NetrwKeepj put =g:netrw_uid - " call Decho("filter input: ".getline('$'),'~'.expand("")) - if exists("s:netrw_passwd") && s:netrw_passwd != "" - NetrwKeepj put ='\"'.s:netrw_passwd.'\"' - endif - " call Decho("filter input: ".getline('$'),'~'.expand("")) - elseif exists("s:netrw_passwd") - NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - endif - - if exists("g:netrw_ftpmode") && g:netrw_ftpmode != "" - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("b:netrw_fname") && b:netrw_fname != "" - NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - for fname in fnamelist - NetrwKeepj call setline(line("$")+1,'get "'.fname.'"') - endfor - " call Decho("filter input: ".getline('$'),'~'.expand("")) - - " perform ftp: - " -i : turns off interactive prompting from ftp - " -n unix : DON'T use <.netrc>, even though it exists - " -n win32: quit being obnoxious about password - " Note: using "_dd to delete to the black hole register; avoids messing up @@ - NetrwKeepj norm! 1G"_dd - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - if getline(1) !~ "^$" - " call Decho("error<".getline(1).">",'~'.expand("")) - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),5) - endif - endif - - elseif b:netrw_method == 9 - " obtain file using sftp - " call Decho("obtain via sftp (method #9)",'~'.expand("")) - if a:fname =~ '/' - let localfile= substitute(a:fname,'^.*/','','') - else - let localfile= a:fname - endif - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1).s:ShellEscape(localfile)." ".s:ShellEscape(tgtdir)) - - elseif !exists("b:netrw_method") || b:netrw_method < 0 - " probably a badly formed url; protocol not recognized - " call Dret("netrw#Obtain : unsupported method") - return - - else - " protocol recognized but not supported for Obtain (yet?) - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"current protocol not supported for obtaining file",97) - endif - " call Dret("netrw#Obtain : current protocol not supported for obtaining file") - return - endif - - " restore status line - if type(a:fname) == 1 && exists("s:netrw_users_stl") - NetrwKeepj call s:SetupNetrwStatusLine(s:netrw_users_stl) - endif - - endif - - " cleanup - if exists("tmpbufnr") - if bufnr("%") != tmpbufnr - exe tmpbufnr."bw!" - else - q! - endif - endif - - " call Dret("netrw#Obtain") -endfun - -" --------------------------------------------------------------------- -" netrw#Nread: save position, call netrw#NetRead(), and restore position {{{2 -fun! netrw#Nread(mode,fname) - " call Dfunc("netrw#Nread(mode=".a:mode." fname<".a:fname.">)") - let svpos= winsaveview() - " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) - call netrw#NetRead(a:mode,a:fname) - " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("")) - call winrestview(svpos) - - if exists("w:netrw_liststyle") && w:netrw_liststyle != s:TREELIST - if exists("w:netrw_bannercnt") - " start with cursor just after the banner - exe w:netrw_bannercnt - endif - endif - " call Dret("netrw#Nread") -endfun - -" ------------------------------------------------------------------------ -" s:NetrwOptionsSave: save options prior to setting to "netrw-buffer-standard" form {{{2 -" Options get restored by s:NetrwOptionsRestore() -" -" Option handling: -" * save user's options (s:NetrwOptionsSave) -" * set netrw-safe options (s:NetrwOptionsSafe) -" - change an option only when user option != safe option (s:netrwSetSafeSetting) -" * restore user's options (s:netrwOPtionsRestore) -" - restore a user option when != safe option (s:NetrwRestoreSetting) -" vt: (variable type) normally its either "w:" or "s:" -fun! s:NetrwOptionsSave(vt) - " call Dfunc("s:NetrwOptionsSave(vt<".a:vt.">) win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%")).">"." winnr($)=".winnr("$")." mod=".&mod." ma=".&ma) - " call Decho(a:vt."netrw_optionsave".(exists("{a:vt}netrw_optionsave")? ("=".{a:vt}netrw_optionsave) : " doesn't exist"),'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt." hid=".&hid,'~'.expand("")) - " call Decho("(s:NetrwOptionsSave) lines=".&lines) - - if !exists("{a:vt}netrw_optionsave") - let {a:vt}netrw_optionsave= 1 - else - " call Dret("s:NetrwOptionsSave : options already saved") - return - endif - " call Decho("prior to save: fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." diff=".&l:diff,'~'.expand("")) - - " Save current settings and current directory - " call Decho("saving current settings and current directory",'~'.expand("")) - let s:yykeep = @@ - if exists("&l:acd")|let {a:vt}netrw_acdkeep = &l:acd|endif - let {a:vt}netrw_aikeep = &l:ai - let {a:vt}netrw_awkeep = &l:aw - let {a:vt}netrw_bhkeep = &l:bh - let {a:vt}netrw_blkeep = &l:bl - let {a:vt}netrw_btkeep = &l:bt - let {a:vt}netrw_bombkeep = &l:bomb - let {a:vt}netrw_cedit = &cedit - let {a:vt}netrw_cikeep = &l:ci - let {a:vt}netrw_cinkeep = &l:cin - let {a:vt}netrw_cinokeep = &l:cino - let {a:vt}netrw_comkeep = &l:com - let {a:vt}netrw_cpokeep = &l:cpo - let {a:vt}netrw_cuckeep = &l:cuc - let {a:vt}netrw_culkeep = &l:cul - " call Decho("(s:NetrwOptionsSave) COMBAK: cuc=".&l:cuc." cul=".&l:cul) - let {a:vt}netrw_diffkeep = &l:diff - let {a:vt}netrw_fenkeep = &l:fen - if !exists("g:netrw_ffkeep") || g:netrw_ffkeep - let {a:vt}netrw_ffkeep = &l:ff - endif - let {a:vt}netrw_fokeep = &l:fo " formatoptions - let {a:vt}netrw_gdkeep = &l:gd " gdefault - let {a:vt}netrw_gokeep = &go " guioptions - let {a:vt}netrw_hidkeep = &l:hidden - let {a:vt}netrw_imkeep = &l:im - let {a:vt}netrw_iskkeep = &l:isk - let {a:vt}netrw_lines = &lines - let {a:vt}netrw_lskeep = &l:ls - let {a:vt}netrw_makeep = &l:ma - let {a:vt}netrw_magickeep = &l:magic - let {a:vt}netrw_modkeep = &l:mod - let {a:vt}netrw_nukeep = &l:nu - let {a:vt}netrw_rnukeep = &l:rnu - let {a:vt}netrw_repkeep = &l:report - let {a:vt}netrw_rokeep = &l:ro - let {a:vt}netrw_selkeep = &l:sel - let {a:vt}netrw_spellkeep = &l:spell - if !g:netrw_use_noswf - let {a:vt}netrw_swfkeep = &l:swf - endif - let {a:vt}netrw_tskeep = &l:ts - let {a:vt}netrw_twkeep = &l:tw " textwidth - let {a:vt}netrw_wigkeep = &l:wig " wildignore - let {a:vt}netrw_wrapkeep = &l:wrap - let {a:vt}netrw_writekeep = &l:write - - " save a few selected netrw-related variables - " call Decho("saving a few selected netrw-related variables",'~'.expand("")) - if g:netrw_keepdir - let {a:vt}netrw_dirkeep = getcwd() - " call Decho("saving to ".a:vt."netrw_dirkeep<".{a:vt}netrw_dirkeep.">",'~'.expand("")) - endif - if !has('nvim') && has("clipboard") && g:netrw_clipboard - sil! let {a:vt}netrw_starkeep = @* - sil! let {a:vt}netrw_pluskeep = @+ - endif - sil! let {a:vt}netrw_slashkeep= @/ - - " call Decho("(s:NetrwOptionsSave) lines=".&lines) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("")) - " call Dret("s:NetrwOptionsSave : tab#".tabpagenr()." win#".winnr()) -endfun - -" --------------------------------------------------------------------- -" s:NetrwOptionsSafe: sets options to help netrw do its job {{{2 -" Use s:NetrwSaveOptions() to save user settings -" Use s:NetrwOptionsRestore() to restore user settings -fun! s:NetrwOptionsSafe(islocal) - " call Dfunc("s:NetrwOptionsSafe(islocal=".a:islocal.") win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%"))."> winnr($)=".winnr("$")) - " call Decho("win#".winnr()."'s ft=".&ft,'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - if exists("+acd") | call s:NetrwSetSafeSetting("&l:acd",0)|endif - call s:NetrwSetSafeSetting("&l:ai",0) - call s:NetrwSetSafeSetting("&l:aw",0) - call s:NetrwSetSafeSetting("&l:bl",0) - call s:NetrwSetSafeSetting("&l:bomb",0) - if a:islocal - call s:NetrwSetSafeSetting("&l:bt","nofile") - else - call s:NetrwSetSafeSetting("&l:bt","acwrite") - endif - call s:NetrwSetSafeSetting("&l:ci",0) - call s:NetrwSetSafeSetting("&l:cin",0) - if g:netrw_fastbrowse > a:islocal - call s:NetrwSetSafeSetting("&l:bh","hide") - else - call s:NetrwSetSafeSetting("&l:bh","delete") - endif - call s:NetrwSetSafeSetting("&l:cino","") - call s:NetrwSetSafeSetting("&l:com","") - if &cpo =~ 'a' | call s:NetrwSetSafeSetting("&cpo",substitute(&cpo,'a','','g')) | endif - if &cpo =~ 'A' | call s:NetrwSetSafeSetting("&cpo",substitute(&cpo,'A','','g')) | endif - setl fo=nroql2 - if &go =~ 'a' | set go-=a | endif - if &go =~ 'A' | set go-=A | endif - if &go =~ 'P' | set go-=P | endif - call s:NetrwSetSafeSetting("&l:hid",0) - call s:NetrwSetSafeSetting("&l:im",0) - setl isk+=@ isk+=* isk+=/ - call s:NetrwSetSafeSetting("&l:magic",1) - if g:netrw_use_noswf - call s:NetrwSetSafeSetting("swf",0) - endif - call s:NetrwSetSafeSetting("&l:report",10000) - call s:NetrwSetSafeSetting("&l:sel","inclusive") - call s:NetrwSetSafeSetting("&l:spell",0) - call s:NetrwSetSafeSetting("&l:tw",0) - call s:NetrwSetSafeSetting("&l:wig","") - setl cedit& - - " set up cuc and cul based on g:netrw_cursor and listing style - " COMBAK -- cuc cul related - call s:NetrwCursor(0) - - " allow the user to override safe options - " call Decho("ft<".&ft."> ei=".&ei,'~'.expand("")) - if &ft == "netrw" - " call Decho("do any netrw FileType autocmds (doau FileType netrw)",'~'.expand("")) - keepalt NetrwKeepj doau FileType netrw - endif - - " call Decho("fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." bh=".&l:bh." bt<".&bt.">",'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Dret("s:NetrwOptionsSafe") -endfun - -" --------------------------------------------------------------------- -" s:NetrwOptionsRestore: restore options (based on prior s:NetrwOptionsSave) {{{2 -fun! s:NetrwOptionsRestore(vt) - if !exists("{a:vt}netrw_optionsave") - " filereadable() returns zero for remote files (e.g. scp://user@localhost//etc/fstab) - " Note: @ may not be in 'isfname', so '^\w\+://\f\+/' may not match - if filereadable(expand("%")) || expand("%") =~# '^\w\+://\f\+' - filetype detect - else - setl ft=netrw - endif - return - endif - unlet {a:vt}netrw_optionsave - - if exists("+acd") - if exists("{a:vt}netrw_acdkeep") - let curdir = getcwd() - let &l:acd = {a:vt}netrw_acdkeep - unlet {a:vt}netrw_acdkeep - if &l:acd - call s:NetrwLcd(curdir) - endif - endif - endif - call s:NetrwRestoreSetting(a:vt."netrw_aikeep","&l:ai") - call s:NetrwRestoreSetting(a:vt."netrw_awkeep","&l:aw") - call s:NetrwRestoreSetting(a:vt."netrw_blkeep","&l:bl") - call s:NetrwRestoreSetting(a:vt."netrw_btkeep","&l:bt") - call s:NetrwRestoreSetting(a:vt."netrw_bombkeep","&l:bomb") - call s:NetrwRestoreSetting(a:vt."netrw_cedit","&cedit") - call s:NetrwRestoreSetting(a:vt."netrw_cikeep","&l:ci") - call s:NetrwRestoreSetting(a:vt."netrw_cinkeep","&l:cin") - call s:NetrwRestoreSetting(a:vt."netrw_cinokeep","&l:cino") - call s:NetrwRestoreSetting(a:vt."netrw_comkeep","&l:com") - call s:NetrwRestoreSetting(a:vt."netrw_cpokeep","&l:cpo") - call s:NetrwRestoreSetting(a:vt."netrw_diffkeep","&l:diff") - call s:NetrwRestoreSetting(a:vt."netrw_fenkeep","&l:fen") - if exists("g:netrw_ffkeep") && g:netrw_ffkeep - call s:NetrwRestoreSetting(a:vt."netrw_ffkeep")","&l:ff") - endif - call s:NetrwRestoreSetting(a:vt."netrw_fokeep" ,"&l:fo") - call s:NetrwRestoreSetting(a:vt."netrw_gdkeep" ,"&l:gd") - call s:NetrwRestoreSetting(a:vt."netrw_gokeep" ,"&go") - call s:NetrwRestoreSetting(a:vt."netrw_hidkeep" ,"&l:hidden") - call s:NetrwRestoreSetting(a:vt."netrw_imkeep" ,"&l:im") - call s:NetrwRestoreSetting(a:vt."netrw_iskkeep" ,"&l:isk") - call s:NetrwRestoreSetting(a:vt."netrw_lines" ,"&lines") - call s:NetrwRestoreSetting(a:vt."netrw_lskeep" ,"&l:ls") - call s:NetrwRestoreSetting(a:vt."netrw_makeep" ,"&l:ma") - call s:NetrwRestoreSetting(a:vt."netrw_magickeep","&l:magic") - call s:NetrwRestoreSetting(a:vt."netrw_modkeep" ,"&l:mod") - call s:NetrwRestoreSetting(a:vt."netrw_nukeep" ,"&l:nu") - call s:NetrwRestoreSetting(a:vt."netrw_rnukeep" ,"&l:rnu") - call s:NetrwRestoreSetting(a:vt."netrw_repkeep" ,"&l:report") - call s:NetrwRestoreSetting(a:vt."netrw_rokeep" ,"&l:ro") - call s:NetrwRestoreSetting(a:vt."netrw_selkeep" ,"&l:sel") - call s:NetrwRestoreSetting(a:vt."netrw_spellkeep","&l:spell") - call s:NetrwRestoreSetting(a:vt."netrw_twkeep" ,"&l:tw") - call s:NetrwRestoreSetting(a:vt."netrw_wigkeep" ,"&l:wig") - call s:NetrwRestoreSetting(a:vt."netrw_wrapkeep" ,"&l:wrap") - call s:NetrwRestoreSetting(a:vt."netrw_writekeep","&l:write") - call s:NetrwRestoreSetting("s:yykeep","@@") - " former problem: start with liststyle=0; press : result, following line resets l:ts. - " Fixed; in s:PerformListing, when w:netrw_liststyle is s:LONGLIST, will use a printf to pad filename with spaces - " rather than by appending a tab which previously was using "&ts" to set the desired spacing. (Sep 28, 2018) - call s:NetrwRestoreSetting(a:vt."netrw_tskeep","&l:ts") - - if exists("{a:vt}netrw_swfkeep") - if &directory == "" - " user hasn't specified a swapfile directory; - " netrw will temporarily set the swapfile directory - " to the current directory as returned by getcwd(). - let &l:directory= getcwd() - sil! let &l:swf = {a:vt}netrw_swfkeep - setl directory= - unlet {a:vt}netrw_swfkeep - elseif &l:swf != {a:vt}netrw_swfkeep - if !g:netrw_use_noswf - " following line causes a Press ENTER in windows -- can't seem to work around it!!! - sil! let &l:swf= {a:vt}netrw_swfkeep - endif - unlet {a:vt}netrw_swfkeep - endif - endif - if exists("{a:vt}netrw_dirkeep") && isdirectory(s:NetrwFile({a:vt}netrw_dirkeep)) && g:netrw_keepdir - let dirkeep = substitute({a:vt}netrw_dirkeep,'\\','/','g') - if exists("{a:vt}netrw_dirkeep") - call s:NetrwLcd(dirkeep) - unlet {a:vt}netrw_dirkeep - endif - endif - if !has('nvim') && has("clipboard") && g:netrw_clipboard - call s:NetrwRestoreSetting(a:vt."netrw_starkeep","@*") - call s:NetrwRestoreSetting(a:vt."netrw_pluskeep","@+") - endif - call s:NetrwRestoreSetting(a:vt."netrw_slashkeep","@/") - - " Moved the filetype detect here from NetrwGetFile() because remote files - " were having their filetype detect-generated settings overwritten by - " NetrwOptionRestore. - if &ft != "netrw" - filetype detect - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwSetSafeSetting: sets an option to a safe setting {{{2 -" but only when the options' value and the safe setting differ -" Doing this means that netrw will not come up as having changed a -" setting last when it really didn't actually change it. -" -" Called from s:NetrwOptionsSafe -" ex. call s:NetrwSetSafeSetting("&l:sel","inclusive") -fun! s:NetrwSetSafeSetting(setting,safesetting) - " call Dfunc("s:NetrwSetSafeSetting(setting<".a:setting."> safesetting<".a:safesetting.">)") - - if a:setting =~ '^&' - " call Decho("fyi: a:setting starts with &") - exe "let settingval= ".a:setting - " call Decho("fyi: settingval<".settingval.">") - - if settingval != a:safesetting - " call Decho("set setting<".a:setting."> to option value<".a:safesetting.">") - if type(a:safesetting) == 0 - exe "let ".a:setting."=".a:safesetting - elseif type(a:safesetting) == 1 - exe "let ".a:setting."= '".a:safesetting."'" - else - call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:setting." with a safesetting of type#".type(a:safesetting),105) - endif - endif - endif - - " call Dret("s:NetrwSetSafeSetting") -endfun - -" ------------------------------------------------------------------------ -" s:NetrwRestoreSetting: restores specified setting using associated keepvar, {{{2 -" but only if the setting value differs from the associated keepvar. -" Doing this means that netrw will not come up as having changed a -" setting last when it really didn't actually change it. -" -" Used by s:NetrwOptionsRestore() to restore each netrw-sensitive setting -" keepvars are set up by s:NetrwOptionsSave -fun! s:NetrwRestoreSetting(keepvar,setting) - """ call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)") - - " typically called from s:NetrwOptionsRestore - " call s:NetrwRestoreSettings(keep-option-variable-name,'associated-option') - " ex. call s:NetrwRestoreSetting(a:vt."netrw_selkeep","&l:sel") - " Restores option (but only if different) from a:keepvar - if exists(a:keepvar) - exe "let keepvarval= ".a:keepvar - exe "let setting= ".a:setting - - "" call Decho("fyi: a:keepvar<".a:keepvar."> exists") - "" call Decho("fyi: keepvarval=".keepvarval) - "" call Decho("fyi: a:setting<".a:setting."> setting<".setting.">") - - if setting != keepvarval - "" call Decho("restore setting<".a:setting."> (currently=".setting.") to keepvarval<".keepvarval.">") - if type(a:setting) == 0 - exe "let ".a:setting."= ".keepvarval - elseif type(a:setting) == 1 - exe "let ".a:setting."= '".substitute(keepvarval,"'","''","g")."'" - else - call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:keepvar." with a setting of type#".type(a:setting),105) - endif - endif - - exe "unlet ".a:keepvar - endif - - "" call Dret("s:NetrwRestoreSetting") -endfun - -" --------------------------------------------------------------------- -" NetrwStatusLine: {{{2 -fun! NetrwStatusLine() - - " vvv NetrwStatusLine() debugging vvv - " let g:stlmsg="" - " if !exists("w:netrw_explore_bufnr") - " let g:stlmsg="!X" - " elseif w:netrw_explore_bufnr != bufnr("%") - " let g:stlmsg="explore_bufnr!=".bufnr("%") - " endif - " if !exists("w:netrw_explore_line") - " let g:stlmsg=" !X" - " elseif w:netrw_explore_line != line(".") - " let g:stlmsg=" explore_line!={line(.)<".line(".").">" - " endif - " if !exists("w:netrw_explore_list") - " let g:stlmsg=" !X" - " endif - " ^^^ NetrwStatusLine() debugging ^^^ - - if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list") - " restore user's status line - let &l:stl = s:netrw_users_stl - let &laststatus = s:netrw_users_ls - if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif - if exists("w:netrw_explore_line") |unlet w:netrw_explore_line |endif - return "" - else - return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen - endif -endfun - -" =============================== -" Netrw Transfer Functions: {{{1 -" =============================== - -" ------------------------------------------------------------------------ -" netrw#NetRead: responsible for reading a file over the net {{{2 -" mode: =0 read remote file and insert before current line -" =1 read remote file and insert after current line -" =2 replace with remote file -" =3 obtain file, but leave in temporary format -fun! netrw#NetRead(mode,...) - " call Dfunc("netrw#NetRead(mode=".a:mode.",...) a:0=".a:0." ".g:loaded_netrw.((a:0 > 0)? " a:1<".a:1.">" : "")) - - " NetRead: save options {{{3 - call s:NetrwOptionsSave("w:") - call s:NetrwOptionsSafe(0) - call s:RestoreCursorline() - " NetrwSafeOptions sets a buffer up for a netrw listing, which includes buflisting off. - " However, this setting is not wanted for a remote editing session. The buffer should be "nofile", still. - setl bl - " call Decho("buf#".bufnr("%")."<".bufname("%")."> bl=".&bl." bt=".&bt." bh=".&bh,'~'.expand("")) - - " NetRead: interpret mode into a readcmd {{{3 - if a:mode == 0 " read remote file before current line - let readcmd = "0r" - elseif a:mode == 1 " read file after current line - let readcmd = "r" - elseif a:mode == 2 " replace with remote file - let readcmd = "%r" - elseif a:mode == 3 " skip read of file (leave as temporary) - let readcmd = "t" - else - exe a:mode - let readcmd = "r" - endif - let ichoice = (a:0 == 0)? 0 : 1 - " call Decho("readcmd<".readcmd."> ichoice=".ichoice,'~'.expand("")) - - " NetRead: get temporary filename {{{3 - let tmpfile= s:GetTempfile("") - if tmpfile == "" - " call Dret("netrw#NetRead : unable to get a tempfile!") - return - endif - - while ichoice <= a:0 - - " attempt to repeat with previous host-file-etc - if exists("b:netrw_lastfile") && a:0 == 0 - " call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand("")) - let choice = b:netrw_lastfile - let ichoice= ichoice + 1 - - else - exe "let choice= a:" . ichoice - " call Decho("no lastfile: choice<" . choice . ">",'~'.expand("")) - - if match(choice,"?") == 0 - " give help - echomsg 'NetRead Usage:' - echomsg ':Nread machine:path uses rcp' - echomsg ':Nread "machine path" uses ftp with <.netrc>' - echomsg ':Nread "machine id password path" uses ftp' - echomsg ':Nread dav://machine[:port]/path uses cadaver' - echomsg ':Nread fetch://machine/path uses fetch' - echomsg ':Nread ftp://[user@]machine[:port]/path uses ftp autodetects <.netrc>' - echomsg ':Nread http://[user@]machine/path uses http wget' - echomsg ':Nread file:///path uses elinks' - echomsg ':Nread https://[user@]machine/path uses http wget' - echomsg ':Nread rcp://[user@]machine/path uses rcp' - echomsg ':Nread rsync://machine[:port]/path uses rsync' - echomsg ':Nread scp://[user@]machine[[:#]port]/path uses scp' - echomsg ':Nread sftp://[user@]machine[[:#]port]/path uses sftp' - sleep 4 - break - - elseif match(choice,'^"') != -1 - " Reconstruct Choice if choice starts with '"' - " call Decho("reconstructing choice",'~'.expand("")) - if match(choice,'"$') != -1 - " case "..." - let choice= strpart(choice,1,strlen(choice)-2) - else - " case "... ... ..." - let choice = strpart(choice,1,strlen(choice)-1) - let wholechoice = "" - - while match(choice,'"$') == -1 - let wholechoice = wholechoice . " " . choice - let ichoice = ichoice + 1 - if ichoice > a:0 - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",3) - endif - " call Dret("netrw#NetRead :2 getcwd<".getcwd().">") - return - endif - let choice= a:{ichoice} - endwhile - let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1) - endif - endif - endif - - " call Decho("choice<" . choice . ">",'~'.expand("")) - let ichoice= ichoice + 1 - - " NetRead: Determine method of read (ftp, rcp, etc) {{{3 - call s:NetrwMethod(choice) - if !exists("b:netrw_method") || b:netrw_method < 0 - " call Dret("netrw#NetRead : unsupported method") - return - endif - let tmpfile= s:GetTempfile(b:netrw_fname) " apply correct suffix - - " Check whether or not NetrwBrowse() should be handling this request - " call Decho("checking if NetrwBrowse() should handle choice<".choice."> with netrw_list_cmd<".g:netrw_list_cmd.">",'~'.expand("")) - if choice =~ "^.*[\/]$" && b:netrw_method != 5 && choice !~ '^https\=://' - " call Decho("yes, choice matches '^.*[\/]$'",'~'.expand("")) - NetrwKeepj call s:NetrwBrowse(0,choice) - " call Dret("netrw#NetRead :3 getcwd<".getcwd().">") - return - endif - - " ============ - " NetRead: Perform Protocol-Based Read {{{3 - " =========================== - if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1 - echo "(netrw) Processing your read request..." - endif - - "......................................... - " NetRead: (rcp) NetRead Method #1 {{{3 - if b:netrw_method == 1 " read with rcp - " call Decho("read via rcp (method #1)",'~'.expand("")) - " ER: nothing done with g:netrw_uid yet? - " ER: on Win2K" rcp machine[.user]:file tmpfile - " ER: when machine contains '.' adding .user is required (use $USERNAME) - " ER: the tmpfile is full path: rcp sees C:\... as host C - if s:netrw_has_nt_rcp == 1 - if exists("g:netrw_uid") && ( g:netrw_uid != "" ) - let uid_machine = g:netrw_machine .'.'. g:netrw_uid - else - " Any way needed it machine contains a '.' - let uid_machine = g:netrw_machine .'.'. $USERNAME - endif - else - if exists("g:netrw_uid") && ( g:netrw_uid != "" ) - let uid_machine = g:netrw_uid .'@'. g:netrw_machine - else - let uid_machine = g:netrw_machine - endif - endif - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1)) - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (ftp + <.netrc>) NetRead Method #2 {{{3 - elseif b:netrw_method == 2 " read with ftp + <.netrc> - " call Decho("read via ftp+.netrc (method #2)",'~'.expand("")) - let netrw_fname= b:netrw_fname - NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars() - let filtbuf= bufnr("%") - setl ff=unix - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline(line("$")),'~'.expand("")) - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline(line("$")),'~'.expand("")) - endif - call setline(line("$")+1,'get "'.netrw_fname.'" '.tmpfile) - " call Decho("filter input: ".getline(line("$")),'~'.expand("")) - if exists("g:netrw_port") && g:netrw_port != "" - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) - else - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) - endif - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' - let debugkeep = &debug - setl debug=msg - NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),4) - let &debug = debugkeep - endif - call s:SaveBufVars() - keepj bd! - if bufname("%") == "" && getline("$") == "" && line('$') == 1 - " needed when one sources a file in a nolbl setting window via ftp - q! - endif - call s:RestoreBufVars() - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (ftp + machine,id,passwd,filename) NetRead Method #3 {{{3 - elseif b:netrw_method == 3 " read with ftp + machine, id, passwd, and fname - " Construct execution string (four lines) which will be passed through filter - " call Decho("read via ftp+mipf (method #3)",'~'.expand("")) - let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape) - NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars() - let filtbuf= bufnr("%") - setl ff=unix - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - " call Decho("filter input: ".getline('.'),'~'.expand("")) - else - NetrwKeepj put ='open '.g:netrw_machine - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - - if exists("g:netrw_uid") && g:netrw_uid != "" - if exists("g:netrw_ftp") && g:netrw_ftp == 1 - NetrwKeepj put =g:netrw_uid - " call Decho("filter input: ".getline('.'),'~'.expand("")) - if exists("s:netrw_passwd") - NetrwKeepj put ='\"'.s:netrw_passwd.'\"' - endif - " call Decho("filter input: ".getline('.'),'~'.expand("")) - elseif exists("s:netrw_passwd") - NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - endif - - if exists("g:netrw_ftpmode") && g:netrw_ftpmode != "" - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - NetrwKeepj put ='get \"'.netrw_fname.'\" '.tmpfile - " call Decho("filter input: ".getline('.'),'~'.expand("")) - - " perform ftp: - " -i : turns off interactive prompting from ftp - " -n unix : DON'T use <.netrc>, even though it exists - " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1G"_dd - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - if getline(1) !~ "^$" - " call Decho("error<".getline(1).">",'~'.expand("")) - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,getline(1),5) - endif - endif - call s:SaveBufVars()|keepj bd!|call s:RestoreBufVars() - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (scp) NetRead Method #4 {{{3 - elseif b:netrw_method == 4 " read with scp - " call Decho("read via scp (method #4)",'~'.expand("")) - if exists("g:netrw_port") && g:netrw_port != "" - let useport= " ".g:netrw_scpport." ".g:netrw_port - else - let useport= "" - endif - " 'C' in 'C:\path\to\file' is handled as hostname on windows. - " This is workaround to avoid mis-handle windows local-path: - if g:netrw_scp_cmd =~ '^scp' && has("win32") - let tmpfile_get = substitute(tr(tmpfile, '\', '/'), '^\(\a\):[/\\]\(.*\)$', '/\1/\2', '') - else - let tmpfile_get = tmpfile - endif - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".escape(s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1),' ')." ".s:ShellEscape(tmpfile_get,1)) - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (http) NetRead Method #5 (wget) {{{3 - elseif b:netrw_method == 5 - " call Decho("read via http (method #5)",'~'.expand("")) - if g:netrw_http_cmd == "" - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,"neither the wget nor the fetch command is available",6) - endif - " call Dret("netrw#NetRead :4 getcwd<".getcwd().">") - return - endif - - if match(b:netrw_fname,"#") == -1 || exists("g:netrw_http_xcmd") - " using g:netrw_http_cmd (usually elinks, links, curl, wget, or fetch) - " call Decho('using '.g:netrw_http_cmd.' (# not in b:netrw_fname<'.b:netrw_fname.">)",'~'.expand("")) - if exists("g:netrw_http_xcmd") - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".s:ShellEscape(tmpfile,1)) - else - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)) - endif - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - - else - " wget/curl/fetch plus a jump to an in-page marker (ie. http://abc/def.html#aMarker) - " call Decho("wget/curl plus jump (# in b:netrw_fname<".b:netrw_fname.">)",'~'.expand("")) - let netrw_html= substitute(b:netrw_fname,"#.*$","","") - let netrw_tag = substitute(b:netrw_fname,"^.*#","","") - " call Decho("netrw_html<".netrw_html.">",'~'.expand("")) - " call Decho("netrw_tag <".netrw_tag.">",'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.netrw_html,1)) - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - " call Decho('<\s*a\s*name=\s*"'.netrw_tag.'"/','~'.expand("")) - exe 'NetrwKeepj norm! 1G/<\s*a\s*name=\s*"'.netrw_tag.'"/'."\" - endif - let b:netrw_lastfile = choice - " call Decho("setl ro",'~'.expand("")) - setl ro nomod - - "......................................... - " NetRead: (dav) NetRead Method #6 {{{3 - elseif b:netrw_method == 6 - " call Decho("read via cadaver (method #6)",'~'.expand("")) - - if !executable(g:netrw_dav_cmd) - call netrw#ErrorMsg(s:ERROR,g:netrw_dav_cmd." is not executable",73) - " call Dret("netrw#NetRead : ".g:netrw_dav_cmd." not executable") - return - endif - if g:netrw_dav_cmd =~ "curl" - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_dav_cmd." ".s:ShellEscape("dav://".g:netrw_machine.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1)) - else - " Construct execution string (four lines) which will be passed through filter - let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape) - new - setl ff=unix - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - else - NetrwKeepj put ='open '.g:netrw_machine - endif - if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != "" - NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd - endif - NetrwKeepj put ='get '.netrw_fname.' '.tmpfile - NetrwKeepj put ='quit' - - " perform cadaver operation: - NetrwKeepj norm! 1G"_dd - call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd) - keepj bd! - endif - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (rsync) NetRead Method #7 {{{3 - elseif b:netrw_method == 7 - " call Decho("read via rsync (method #7)",'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1)) - let result = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (fetch) NetRead Method #8 {{{3 - " fetch://[user@]host[:http]/path - elseif b:netrw_method == 8 - " call Decho("read via fetch (method #8)",'~'.expand("")) - if g:netrw_fetch_cmd == "" - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"fetch command not available",7) - endif - " call Dret("NetRead") - return - endif - if exists("g:netrw_option") && g:netrw_option =~ ":https\=" - let netrw_option= "http" - else - let netrw_option= "ftp" - endif - " call Decho("read via fetch for ".netrw_option,'~'.expand("")) - - if exists("g:netrw_uid") && g:netrw_uid != "" && exists("s:netrw_passwd") && s:netrw_passwd != "" - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1)) - else - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1)) - endif - - let result = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - " call Decho("setl ro",'~'.expand("")) - setl ro nomod - - "......................................... - " NetRead: (sftp) NetRead Method #9 {{{3 - elseif b:netrw_method == 9 - " call Decho("read via sftp (method #9)",'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile) - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: (file) NetRead Method #10 {{{3 - elseif b:netrw_method == 10 && exists("g:netrw_file_cmd") - " " call Decho("read via ".b:netrw_file_cmd." (method #10)",'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_file_cmd." ".s:ShellEscape(b:netrw_fname,1)." ".tmpfile) - let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) - let b:netrw_lastfile = choice - - "......................................... - " NetRead: Complain {{{3 - else - call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",8) - endif - endwhile - - " NetRead: cleanup {{{3 - if exists("b:netrw_method") - " call Decho("cleanup b:netrw_method and b:netrw_fname",'~'.expand("")) - unlet b:netrw_method - unlet b:netrw_fname - endif - if s:FileReadable(tmpfile) && tmpfile !~ '.tar.bz2$' && tmpfile !~ '.tar.gz$' && tmpfile !~ '.zip' && tmpfile !~ '.tar' && readcmd != 't' && tmpfile !~ '.tar.xz$' && tmpfile !~ '.txz' - " call Decho("cleanup by deleting tmpfile<".tmpfile.">",'~'.expand("")) - NetrwKeepj call s:NetrwDelete(tmpfile) - endif - NetrwKeepj call s:NetrwOptionsRestore("w:") - - " call Dret("netrw#NetRead :5 getcwd<".getcwd().">") -endfun - -" ------------------------------------------------------------------------ -" netrw#NetWrite: responsible for writing a file over the net {{{2 -fun! netrw#NetWrite(...) range - " call Dfunc("netrw#NetWrite(a:0=".a:0.") ".g:loaded_netrw) - - " NetWrite: option handling {{{3 - let mod= 0 - call s:NetrwOptionsSave("w:") - call s:NetrwOptionsSafe(0) - - " NetWrite: Get Temporary Filename {{{3 - let tmpfile= s:GetTempfile("") - if tmpfile == "" - " call Dret("netrw#NetWrite : unable to get a tempfile!") - return - endif - - if a:0 == 0 - let ichoice = 0 - else - let ichoice = 1 - endif - - let curbufname= expand("%") - " call Decho("curbufname<".curbufname.">",'~'.expand("")) - if &binary - " For binary writes, always write entire file. - " (line numbers don't really make sense for that). - " Also supports the writing of tar and zip files. - " call Decho("(write entire file) sil exe w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand("")) - exe "sil NetrwKeepj w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile) - elseif g:netrw_cygwin - " write (selected portion of) file to temporary - let cygtmpfile= substitute(tmpfile,g:netrw_cygdrive.'/\(.\)','\1:','') - " call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile),'~'.expand("")) - exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile) - else - " write (selected portion of) file to temporary - " call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand("")) - exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile) - endif - - if curbufname == "" - " when the file is [No Name], and one attempts to Nwrite it, the buffer takes - " on the temporary file's name. Deletion of the temporary file during - " cleanup then causes an error message. - 0file! - endif - - " NetWrite: while choice loop: {{{3 - while ichoice <= a:0 - - " Process arguments: {{{4 - " attempt to repeat with previous host-file-etc - if exists("b:netrw_lastfile") && a:0 == 0 - " call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand("")) - let choice = b:netrw_lastfile - let ichoice= ichoice + 1 - else - exe "let choice= a:" . ichoice - - " Reconstruct Choice when choice starts with '"' - if match(choice,"?") == 0 - echomsg 'NetWrite Usage:"' - echomsg ':Nwrite machine:path uses rcp' - echomsg ':Nwrite "machine path" uses ftp with <.netrc>' - echomsg ':Nwrite "machine id password path" uses ftp' - echomsg ':Nwrite dav://[user@]machine/path uses cadaver' - echomsg ':Nwrite fetch://[user@]machine/path uses fetch' - echomsg ':Nwrite ftp://machine[#port]/path uses ftp (autodetects <.netrc>)' - echomsg ':Nwrite rcp://machine/path uses rcp' - echomsg ':Nwrite rsync://[user@]machine/path uses rsync' - echomsg ':Nwrite scp://[user@]machine[[:#]port]/path uses scp' - echomsg ':Nwrite sftp://[user@]machine/path uses sftp' - sleep 4 - break - - elseif match(choice,"^\"") != -1 - if match(choice,"\"$") != -1 - " case "..." - let choice=strpart(choice,1,strlen(choice)-2) - else - " case "... ... ..." - let choice = strpart(choice,1,strlen(choice)-1) - let wholechoice = "" - - while match(choice,"\"$") == -1 - let wholechoice= wholechoice . " " . choice - let ichoice = ichoice + 1 - if choice > a:0 - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",13) - endif - " call Dret("netrw#NetWrite") - return - endif - let choice= a:{ichoice} - endwhile - let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1) - endif - endif - endif - let ichoice= ichoice + 1 - " call Decho("choice<" . choice . "> ichoice=".ichoice,'~'.expand("")) - - " Determine method of write (ftp, rcp, etc) {{{4 - NetrwKeepj call s:NetrwMethod(choice) - if !exists("b:netrw_method") || b:netrw_method < 0 - " call Dfunc("netrw#NetWrite : unsupported method") - return - endif - - " ============= - " NetWrite: Perform Protocol-Based Write {{{3 - " ============================ - if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1 - echo "(netrw) Processing your write request..." - " call Decho("Processing your write request...",'~'.expand("")) - endif - - "......................................... - " NetWrite: (rcp) NetWrite Method #1 {{{3 - if b:netrw_method == 1 - " call Decho("write via rcp (method #1)",'~'.expand("")) - if s:netrw_has_nt_rcp == 1 - if exists("g:netrw_uid") && ( g:netrw_uid != "" ) - let uid_machine = g:netrw_machine .'.'. g:netrw_uid - else - let uid_machine = g:netrw_machine .'.'. $USERNAME - endif - else - if exists("g:netrw_uid") && ( g:netrw_uid != "" ) - let uid_machine = g:netrw_uid .'@'. g:netrw_machine - else - let uid_machine = g:netrw_machine - endif - endif - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1)) - let b:netrw_lastfile = choice - - "......................................... - " NetWrite: (ftp + <.netrc>) NetWrite Method #2 {{{3 - elseif b:netrw_method == 2 - " call Decho("write via ftp+.netrc (method #2)",'~'.expand("")) - let netrw_fname = b:netrw_fname - - " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead - let bhkeep = &l:bh - let curbuf = bufnr("%") - setl bh=hide - keepj keepalt enew - - " call Decho("filter input window#".winnr(),'~'.expand("")) - setl ff=unix - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline('$'),'~'.expand("")) - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline("$"),'~'.expand("")) - endif - NetrwKeepj call setline(line("$")+1,'put "'.tmpfile.'" "'.netrw_fname.'"') - " call Decho("filter input: ".getline("$"),'~'.expand("")) - if exists("g:netrw_port") && g:netrw_port != "" - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) - else - " call Decho("filter input window#".winnr(),'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) - endif - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - if getline(1) !~ "^$" - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),14) - endif - let mod=1 - endif - - " remove enew buffer (quietly) - let filtbuf= bufnr("%") - exe curbuf."b!" - let &l:bh = bhkeep - exe filtbuf."bw!" - - let b:netrw_lastfile = choice - - "......................................... - " NetWrite: (ftp + machine, id, passwd, filename) NetWrite Method #3 {{{3 - elseif b:netrw_method == 3 - " Construct execution string (three or more lines) which will be passed through filter - " call Decho("read via ftp+mipf (method #3)",'~'.expand("")) - let netrw_fname = b:netrw_fname - let bhkeep = &l:bh - - " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead - let curbuf = bufnr("%") - setl bh=hide - keepj keepalt enew - setl ff=unix - - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - " call Decho("filter input: ".getline('.'),'~'.expand("")) - else - NetrwKeepj put ='open '.g:netrw_machine - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - if exists("g:netrw_uid") && g:netrw_uid != "" - if exists("g:netrw_ftp") && g:netrw_ftp == 1 - NetrwKeepj put =g:netrw_uid - " call Decho("filter input: ".getline('.'),'~'.expand("")) - if exists("s:netrw_passwd") && s:netrw_passwd != "" - NetrwKeepj put ='\"'.s:netrw_passwd.'\"' - endif - " call Decho("filter input: ".getline('.'),'~'.expand("")) - elseif exists("s:netrw_passwd") && s:netrw_passwd != "" - NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - endif - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline('$'),'~'.expand("")) - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline("$"),'~'.expand("")) - endif - NetrwKeepj put ='put \"'.tmpfile.'\" \"'.netrw_fname.'\"' - " call Decho("filter input: ".getline('.'),'~'.expand("")) - " save choice/id/password for future use - let b:netrw_lastfile = choice - - " perform ftp: - " -i : turns off interactive prompting from ftp - " -n unix : DON'T use <.netrc>, even though it exists - " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1G"_dd - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - if getline(1) !~ "^$" - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,getline(1),15) - endif - let mod=1 - endif - - " remove enew buffer (quietly) - let filtbuf= bufnr("%") - exe curbuf."b!" - let &l:bh= bhkeep - exe filtbuf."bw!" - - "......................................... - " NetWrite: (scp) NetWrite Method #4 {{{3 - elseif b:netrw_method == 4 - " call Decho("write via scp (method #4)",'~'.expand("")) - if exists("g:netrw_port") && g:netrw_port != "" - let useport= " ".g:netrw_scpport." ".fnameescape(g:netrw_port) - else - let useport= "" - endif - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)) - let b:netrw_lastfile = choice - - "......................................... - " NetWrite: (http) NetWrite Method #5 {{{3 - elseif b:netrw_method == 5 - " call Decho("write via http (method #5)",'~'.expand("")) - let curl= substitute(g:netrw_http_put_cmd,'\s\+.*$',"","") - if executable(curl) - let url= g:netrw_choice - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(url,1) ) - elseif !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,"can't write to http using <".g:netrw_http_put_cmd.">",16) - endif - - "......................................... - " NetWrite: (dav) NetWrite Method #6 (cadaver) {{{3 - elseif b:netrw_method == 6 - " call Decho("write via cadaver (method #6)",'~'.expand("")) - - " Construct execution string (four lines) which will be passed through filter - let netrw_fname = escape(b:netrw_fname,g:netrw_fname_escape) - let bhkeep = &l:bh - - " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead - let curbuf = bufnr("%") - setl bh=hide - keepj keepalt enew - - setl ff=unix - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - else - NetrwKeepj put ='open '.g:netrw_machine - endif - if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != "" - NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd - endif - NetrwKeepj put ='put '.tmpfile.' '.netrw_fname - - " perform cadaver operation: - NetrwKeepj norm! 1G"_dd - call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd) - - " remove enew buffer (quietly) - let filtbuf= bufnr("%") - exe curbuf."b!" - let &l:bh = bhkeep - exe filtbuf."bw!" - - let b:netrw_lastfile = choice - - "......................................... - " NetWrite: (rsync) NetWrite Method #7 {{{3 - elseif b:netrw_method == 7 - " call Decho("write via rsync (method #7)",'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)) - let b:netrw_lastfile = choice - - "......................................... - " NetWrite: (sftp) NetWrite Method #9 {{{3 - elseif b:netrw_method == 9 - " call Decho("write via sftp (method #9)",'~'.expand("")) - let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape) - if exists("g:netrw_uid") && ( g:netrw_uid != "" ) - let uid_machine = g:netrw_uid .'@'. g:netrw_machine - else - let uid_machine = g:netrw_machine - endif - - " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead - let bhkeep = &l:bh - let curbuf = bufnr("%") - setl bh=hide - keepj keepalt enew - - setl ff=unix - call setline(1,'put "'.escape(tmpfile,'\').'" '.netrw_fname) - " call Decho("filter input: ".getline('.'),'~'.expand("")) - let sftpcmd= substitute(g:netrw_sftp_cmd,"%TEMPFILE%",escape(tmpfile,'\'),"g") - call s:NetrwExe(s:netrw_silentxfer."%!".sftpcmd.' '.s:ShellEscape(uid_machine,1)) - let filtbuf= bufnr("%") - exe curbuf."b!" - let &l:bh = bhkeep - exe filtbuf."bw!" - let b:netrw_lastfile = choice - - "......................................... - " NetWrite: Complain {{{3 - else - call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",17) - let leavemod= 1 - endif - endwhile - - " NetWrite: Cleanup: {{{3 - " call Decho("cleanup",'~'.expand("")) - if s:FileReadable(tmpfile) - " call Decho("tmpfile<".tmpfile."> readable, will now delete it",'~'.expand("")) - call s:NetrwDelete(tmpfile) - endif - call s:NetrwOptionsRestore("w:") - - if a:firstline == 1 && a:lastline == line("$") - " restore modifiability; usually equivalent to set nomod - let &l:mod= mod - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - elseif !exists("leavemod") - " indicate that the buffer has not been modified since last written - " call Decho("set nomod",'~'.expand("")) - setl nomod - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - endif - - " call Dret("netrw#NetWrite") -endfun - -" --------------------------------------------------------------------- -" netrw#NetSource: source a remotely hosted vim script {{{2 -" uses NetRead to get a copy of the file into a temporarily file, -" then sources that file, -" then removes that file. -fun! netrw#NetSource(...) - " call Dfunc("netrw#NetSource() a:0=".a:0) - if a:0 > 0 && a:1 == '?' - " give help - echomsg 'NetSource Usage:' - echomsg ':Nsource dav://machine[:port]/path uses cadaver' - echomsg ':Nsource fetch://machine/path uses fetch' - echomsg ':Nsource ftp://[user@]machine[:port]/path uses ftp autodetects <.netrc>' - echomsg ':Nsource http[s]://[user@]machine/path uses http wget' - echomsg ':Nsource rcp://[user@]machine/path uses rcp' - echomsg ':Nsource rsync://machine[:port]/path uses rsync' - echomsg ':Nsource scp://[user@]machine[[:#]port]/path uses scp' - echomsg ':Nsource sftp://[user@]machine[[:#]port]/path uses sftp' - sleep 4 - else - let i= 1 - while i <= a:0 - call netrw#NetRead(3,a:{i}) - " call Decho("s:netread_tmpfile<".s:netrw_tmpfile.">",'~'.expand("")) - if s:FileReadable(s:netrw_tmpfile) - " call Decho("exe so ".fnameescape(s:netrw_tmpfile),'~'.expand("")) - exe "so ".fnameescape(s:netrw_tmpfile) - " call Decho("delete(".s:netrw_tmpfile.")",'~'.expand("")) - if delete(s:netrw_tmpfile) - call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".s:netrw_tmpfile.">!",103) - endif - unlet s:netrw_tmpfile - else - call netrw#ErrorMsg(s:ERROR,"unable to source <".a:{i}.">!",48) - endif - let i= i + 1 - endwhile - endif - " call Dret("netrw#NetSource") -endfun - -" --------------------------------------------------------------------- -" netrw#SetTreetop: resets the tree top to the current directory/specified directory {{{2 -" (implements the :Ntree command) -fun! netrw#SetTreetop(iscmd,...) - - " iscmd==0: netrw#SetTreetop called using gn mapping - " iscmd==1: netrw#SetTreetop called using :Ntree from the command line - " clear out the current tree - if exists("w:netrw_treetop") - let inittreetop= w:netrw_treetop - unlet w:netrw_treetop - endif - if exists("w:netrw_treedict") - unlet w:netrw_treedict - endif - - if (a:iscmd == 0 || a:1 == "") && exists("inittreetop") - let treedir = s:NetrwTreePath(inittreetop) - else - if isdirectory(s:NetrwFile(a:1)) - let treedir = a:1 - let s:netrw_treetop = treedir - elseif exists("b:netrw_curdir") && (isdirectory(s:NetrwFile(b:netrw_curdir."/".a:1)) || a:1 =~ '^\a\{3,}://') - let treedir = b:netrw_curdir."/".a:1 - let s:netrw_treetop = treedir - else - " normally the cursor is left in the message window. - " However, here this results in the directory being listed in the message window, which is not wanted. - let netrwbuf= bufnr("%") - call netrw#ErrorMsg(s:ERROR,"sorry, ".a:1." doesn't seem to be a directory!",95) - exe bufwinnr(netrwbuf)."wincmd w" - let treedir = "." - let s:netrw_treetop = getcwd() - endif - endif - - " determine if treedir is remote or local - let islocal= expand("%") !~ '^\a\{3,}://' - - " browse the resulting directory - if islocal - call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(islocal,treedir,0)) - else - call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,treedir,0)) - endif - -endfun - -" =========================================== -" s:NetrwGetFile: Function to read temporary file "tfile" with command "readcmd". {{{2 -" readcmd == %r : replace buffer with newly read file -" == 0r : read file at top of buffer -" == r : read file after current line -" == t : leave file in temporary form (ie. don't read into buffer) -fun! s:NetrwGetFile(readcmd, tfile, method) - " call Dfunc("NetrwGetFile(readcmd<".a:readcmd.">,tfile<".a:tfile."> method<".a:method.">)") - - " readcmd=='t': simply do nothing - if a:readcmd == 't' - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - " call Dret("NetrwGetFile : skip read of tfile<".a:tfile.">") - return - endif - - " get name of remote filename (ie. url and all) - let rfile= bufname("%") - " call Decho("rfile<".rfile.">",'~'.expand("")) - - if exists("*NetReadFixup") - " for the use of NetReadFixup (not otherwise used internally) - let line2= line("$") - endif - - if a:readcmd[0] == '%' - " get file into buffer - " call Decho("get file into buffer",'~'.expand("")) - - " rename the current buffer to the temp file (ie. tfile) - if g:netrw_cygwin - let tfile= substitute(a:tfile,g:netrw_cygdrive.'/\(.\)','\1:','') - else - let tfile= a:tfile - endif - call s:NetrwBufRename(tfile) - - " edit temporary file (ie. read the temporary file in) - if rfile =~ '\.zip$' - " call Decho("handling remote zip file with zip#Browse(tfile<".tfile.">)",'~'.expand("")) - call zip#Browse(tfile) - elseif rfile =~ '\.tar$' - " call Decho("handling remote tar file with tar#Browse(tfile<".tfile.">)",'~'.expand("")) - call tar#Browse(tfile) - elseif rfile =~ '\.tar\.gz$' - " call Decho("handling remote gzip-compressed tar file",'~'.expand("")) - call tar#Browse(tfile) - elseif rfile =~ '\.tar\.bz2$' - " call Decho("handling remote bz2-compressed tar file",'~'.expand("")) - call tar#Browse(tfile) - elseif rfile =~ '\.tar\.xz$' - " call Decho("handling remote xz-compressed tar file",'~'.expand("")) - call tar#Browse(tfile) - elseif rfile =~ '\.txz$' - " call Decho("handling remote xz-compressed tar file (.txz)",'~'.expand("")) - call tar#Browse(tfile) - else - " call Decho("edit temporary file",'~'.expand("")) - NetrwKeepj e! - endif - - " rename buffer back to remote filename - call s:NetrwBufRename(rfile) - - " Jan 19, 2022: COMBAK -- bram problem with https://github.com/vim/vim/pull/9554.diff filetype - " Detect filetype of local version of remote file. - " Note that isk must not include a "/" for scripts.vim - " to process this detection correctly. - " call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand("")) - " call Decho("..did_filetype()=".did_filetype()) - " setl ft= - " call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">") - let iskkeep= &isk - setl isk-=/ - filetype detect - " call Decho("..local filetype<".&ft."> for buf#".bufnr()."<".bufname().">") - let &l:isk= iskkeep - " call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)") - let line1 = 1 - let line2 = line("$") - - elseif !&ma - " attempting to read a file after the current line in the file, but the buffer is not modifiable - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"attempt to read<".a:tfile."> into a non-modifiable buffer!",94) - " call Dret("NetrwGetFile : attempt to read<".a:tfile."> into a non-modifiable buffer!") - return - - elseif s:FileReadable(a:tfile) - " read file after current line - " call Decho("read file<".a:tfile."> after current line",'~'.expand("")) - let curline = line(".") - let lastline= line("$") - " call Decho("exe<".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)."> line#".curline,'~'.expand("")) - exe "NetrwKeepj ".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile) - let line1= curline + 1 - let line2= line("$") - lastline + 1 - - else - " not readable - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - " call Decho("tfile<".a:tfile."> not readable",'~'.expand("")) - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"file <".a:tfile."> not readable",9) - " call Dret("NetrwGetFile : tfile<".a:tfile."> not readable") - return - endif - - " User-provided (ie. optional) fix-it-up command - if exists("*NetReadFixup") - " call Decho("calling NetReadFixup(method<".a:method."> line1=".line1." line2=".line2.")",'~'.expand("")) - NetrwKeepj call NetReadFixup(a:method, line1, line2) - " else " Decho - " call Decho("NetReadFixup() not called, doesn't exist (line1=".line1." line2=".line2.")",'~'.expand("")) - endif - - if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu - " update the Buffers menu - NetrwKeepj call s:UpdateBuffersMenu() - endif - - " call Decho("readcmd<".a:readcmd."> cmdarg<".v:cmdarg."> tfile<".a:tfile."> readable=".s:FileReadable(a:tfile),'~'.expand("")) - - " make sure file is being displayed - " redraw! - - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - " call Dret("NetrwGetFile") -endfun - -" ------------------------------------------------------------------------ -" s:NetrwMethod: determine method of transfer {{{2 -" Input: -" choice = url [protocol:]//[userid@]hostname[:port]/[path-to-file] -" Output: -" b:netrw_method= 1: rcp -" 2: ftp + <.netrc> -" 3: ftp + machine, id, password, and [path]filename -" 4: scp -" 5: http[s] (wget) -" 6: dav -" 7: rsync -" 8: fetch -" 9: sftp -" 10: file -" g:netrw_machine= hostname -" b:netrw_fname = filename -" g:netrw_port = optional port number (for ftp) -" g:netrw_choice = copy of input url (choice) -fun! s:NetrwMethod(choice) - " call Dfunc("s:NetrwMethod(a:choice<".a:choice.">)") - - " sanity check: choice should have at least three slashes in it - if strlen(substitute(a:choice,'[^/]','','g')) < 3 - call netrw#ErrorMsg(s:ERROR,"not a netrw-style url; netrw uses protocol://[user@]hostname[:port]/[path])",78) - let b:netrw_method = -1 - " call Dret("s:NetrwMethod : incorrect url format<".a:choice.">") - return - endif - - " record current g:netrw_machine, if any - " curmachine used if protocol == ftp and no .netrc - if exists("g:netrw_machine") - let curmachine= g:netrw_machine - " call Decho("curmachine<".curmachine.">",'~'.expand("")) - else - let curmachine= "N O T A HOST" - endif - if exists("g:netrw_port") - let netrw_port= g:netrw_port - endif - - " insure that netrw_ftp_cmd starts off every method determination - " with the current g:netrw_ftp_cmd - let s:netrw_ftp_cmd= g:netrw_ftp_cmd - - " initialization - let b:netrw_method = 0 - let g:netrw_machine = "" - let b:netrw_fname = "" - let g:netrw_port = "" - let g:netrw_choice = a:choice - - " Patterns: - " mipf : a:machine a:id password filename Use ftp - " mf : a:machine filename Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd - " ftpurm : ftp://[user@]host[[#:]port]/filename Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd - " rcpurm : rcp://[user@]host/filename Use rcp - " rcphf : [user@]host:filename Use rcp - " scpurm : scp://[user@]host[[#:]port]/filename Use scp - " httpurm : http[s]://[user@]host/filename Use wget - " davurm : dav[s]://host[:port]/path Use cadaver/curl - " rsyncurm : rsync://host[:port]/path Use rsync - " fetchurm : fetch://[user@]host[:http]/filename Use fetch (defaults to ftp, override for http) - " sftpurm : sftp://[user@]host/filename Use scp - " fileurm : file://[user@]host/filename Use elinks or links - let mipf = '^\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)$' - let mf = '^\(\S\+\)\s\+\(\S\+\)$' - let ftpurm = '^ftp://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\([#:]\d\+\)\=/\(.*\)$' - let rcpurm = '^rcp://\%(\([^/]*\)@\)\=\([^/]\{-}\)/\(.*\)$' - let rcphf = '^\(\(\h\w*\)@\)\=\(\h\w*\):\([^@]\+\)$' - let scpurm = '^scp://\([^/#:]\+\)\%([#:]\(\d\+\)\)\=/\(.*\)$' - let httpurm = '^https\=://\([^/]\{-}\)\(/.*\)\=$' - let davurm = '^davs\=://\([^/]\+\)/\(.*/\)\([-_.~[:alnum:]]\+\)$' - let rsyncurm = '^rsync://\([^/]\{-}\)/\(.*\)\=$' - let fetchurm = '^fetch://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\(:http\)\=/\(.*\)$' - let sftpurm = '^sftp://\([^/]\{-}\)/\(.*\)\=$' - let fileurm = '^file\=://\(.*\)$' - - " call Decho("determine method:",'~'.expand("")) - " Determine Method - " Method#1: rcp://user@hostname/...path-to-file {{{3 - if match(a:choice,rcpurm) == 0 - " call Decho("rcp://...",'~'.expand("")) - let b:netrw_method = 1 - let userid = substitute(a:choice,rcpurm,'\1',"") - let g:netrw_machine = substitute(a:choice,rcpurm,'\2',"") - let b:netrw_fname = substitute(a:choice,rcpurm,'\3',"") - if userid != "" - let g:netrw_uid= userid - endif - - " Method#4: scp://user@hostname/...path-to-file {{{3 - elseif match(a:choice,scpurm) == 0 - " call Decho("scp://...",'~'.expand("")) - let b:netrw_method = 4 - let g:netrw_machine = substitute(a:choice,scpurm,'\1',"") - let g:netrw_port = substitute(a:choice,scpurm,'\2',"") - let b:netrw_fname = substitute(a:choice,scpurm,'\3',"") - - " Method#5: http[s]://user@hostname/...path-to-file {{{3 - elseif match(a:choice,httpurm) == 0 - " call Decho("http[s]://...",'~'.expand("")) - let b:netrw_method = 5 - let g:netrw_machine= substitute(a:choice,httpurm,'\1',"") - let b:netrw_fname = substitute(a:choice,httpurm,'\2',"") - let b:netrw_http = (a:choice =~ '^https:')? "https" : "http" - - " Method#6: dav://hostname[:port]/..path-to-file.. {{{3 - elseif match(a:choice,davurm) == 0 - " call Decho("dav://...",'~'.expand("")) - let b:netrw_method= 6 - if a:choice =~ 'davs:' - let g:netrw_machine= 'https://'.substitute(a:choice,davurm,'\1/\2',"") - else - let g:netrw_machine= 'http://'.substitute(a:choice,davurm,'\1/\2',"") - endif - let b:netrw_fname = substitute(a:choice,davurm,'\3',"") - - " Method#7: rsync://user@hostname/...path-to-file {{{3 - elseif match(a:choice,rsyncurm) == 0 - " call Decho("rsync://...",'~'.expand("")) - let b:netrw_method = 7 - let g:netrw_machine= substitute(a:choice,rsyncurm,'\1',"") - let b:netrw_fname = substitute(a:choice,rsyncurm,'\2',"") - - " Methods 2,3: ftp://[user@]hostname[[:#]port]/...path-to-file {{{3 - elseif match(a:choice,ftpurm) == 0 - " call Decho("ftp://...",'~'.expand("")) - let userid = substitute(a:choice,ftpurm,'\2',"") - let g:netrw_machine= substitute(a:choice,ftpurm,'\3',"") - let g:netrw_port = substitute(a:choice,ftpurm,'\4',"") - let b:netrw_fname = substitute(a:choice,ftpurm,'\5',"") - " call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand("")) - if userid != "" - let g:netrw_uid= userid - endif - - if curmachine != g:netrw_machine - if exists("s:netrw_hup[".g:netrw_machine."]") - call NetUserPass("ftp:".g:netrw_machine) - elseif exists("s:netrw_passwd") - " if there's a change in hostname, require password re-entry - unlet s:netrw_passwd - endif - if exists("netrw_port") - unlet netrw_port - endif - endif - - if exists("g:netrw_uid") && exists("s:netrw_passwd") - let b:netrw_method = 3 - else - let host= substitute(g:netrw_machine,'\..*$','','') - if exists("s:netrw_hup[host]") - call NetUserPass("ftp:".host) - - elseif has("win32") && s:netrw_ftp_cmd =~# '-[sS]:' - " call Decho("has -s: : s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand("")) - " call Decho(" g:netrw_ftp_cmd<".g:netrw_ftp_cmd.">",'~'.expand("")) - if g:netrw_ftp_cmd =~# '-[sS]:\S*MACHINE\>' - let s:netrw_ftp_cmd= substitute(g:netrw_ftp_cmd,'\',g:netrw_machine,'') - " call Decho("s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand("")) - endif - let b:netrw_method= 2 - elseif s:FileReadable(expand("$HOME/.netrc")) && !g:netrw_ignorenetrc - " call Decho("using <".expand("$HOME/.netrc")."> (readable)",'~'.expand("")) - let b:netrw_method= 2 - else - if !exists("g:netrw_uid") || g:netrw_uid == "" - call NetUserPass() - elseif !exists("s:netrw_passwd") || s:netrw_passwd == "" - call NetUserPass(g:netrw_uid) - " else just use current g:netrw_uid and s:netrw_passwd - endif - let b:netrw_method= 3 - endif - endif - - " Method#8: fetch {{{3 - elseif match(a:choice,fetchurm) == 0 - " call Decho("fetch://...",'~'.expand("")) - let b:netrw_method = 8 - let g:netrw_userid = substitute(a:choice,fetchurm,'\2',"") - let g:netrw_machine= substitute(a:choice,fetchurm,'\3',"") - let b:netrw_option = substitute(a:choice,fetchurm,'\4',"") - let b:netrw_fname = substitute(a:choice,fetchurm,'\5',"") - - " Method#3: Issue an ftp : "machine id password [path/]filename" {{{3 - elseif match(a:choice,mipf) == 0 - " call Decho("(ftp) host id pass file",'~'.expand("")) - let b:netrw_method = 3 - let g:netrw_machine = substitute(a:choice,mipf,'\1',"") - let g:netrw_uid = substitute(a:choice,mipf,'\2',"") - let s:netrw_passwd = substitute(a:choice,mipf,'\3',"") - let b:netrw_fname = substitute(a:choice,mipf,'\4',"") - call NetUserPass(g:netrw_machine,g:netrw_uid,s:netrw_passwd) - - " Method#3: Issue an ftp: "hostname [path/]filename" {{{3 - elseif match(a:choice,mf) == 0 - " call Decho("(ftp) host file",'~'.expand("")) - if exists("g:netrw_uid") && exists("s:netrw_passwd") - let b:netrw_method = 3 - let g:netrw_machine = substitute(a:choice,mf,'\1',"") - let b:netrw_fname = substitute(a:choice,mf,'\2',"") - - elseif s:FileReadable(expand("$HOME/.netrc")) - let b:netrw_method = 2 - let g:netrw_machine = substitute(a:choice,mf,'\1',"") - let b:netrw_fname = substitute(a:choice,mf,'\2',"") - endif - - " Method#9: sftp://user@hostname/...path-to-file {{{3 - elseif match(a:choice,sftpurm) == 0 - " call Decho("sftp://...",'~'.expand("")) - let b:netrw_method = 9 - let g:netrw_machine= substitute(a:choice,sftpurm,'\1',"") - let b:netrw_fname = substitute(a:choice,sftpurm,'\2',"") - - " Method#1: Issue an rcp: hostname:filename" (this one should be last) {{{3 - elseif match(a:choice,rcphf) == 0 - " call Decho("(rcp) [user@]host:file) rcphf<".rcphf.">",'~'.expand("")) - let b:netrw_method = 1 - let userid = substitute(a:choice,rcphf,'\2',"") - let g:netrw_machine = substitute(a:choice,rcphf,'\3',"") - let b:netrw_fname = substitute(a:choice,rcphf,'\4',"") - " call Decho('\1<'.substitute(a:choice,rcphf,'\1',"").">",'~'.expand("")) - " call Decho('\2<'.substitute(a:choice,rcphf,'\2',"").">",'~'.expand("")) - " call Decho('\3<'.substitute(a:choice,rcphf,'\3',"").">",'~'.expand("")) - " call Decho('\4<'.substitute(a:choice,rcphf,'\4',"").">",'~'.expand("")) - if userid != "" - let g:netrw_uid= userid - endif - - " Method#10: file://user@hostname/...path-to-file {{{3 - elseif match(a:choice,fileurm) == 0 && exists("g:netrw_file_cmd") - " call Decho("http[s]://...",'~'.expand("")) - let b:netrw_method = 10 - let b:netrw_fname = substitute(a:choice,fileurm,'\1',"") - " call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">",'~'.expand("")) - - " Cannot Determine Method {{{3 - else - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:WARNING,"cannot determine method (format: protocol://[user@]hostname[:port]/[path])",45) - endif - let b:netrw_method = -1 - endif - "}}}3 - - if g:netrw_port != "" - " remove any leading [:#] from port number - let g:netrw_port = substitute(g:netrw_port,'[#:]\+','','') - elseif exists("netrw_port") - " retain port number as implicit for subsequent ftp operations - let g:netrw_port= netrw_port - endif - - " call Decho("a:choice <".a:choice.">",'~'.expand("")) - " call Decho("b:netrw_method <".b:netrw_method.">",'~'.expand("")) - " call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand("")) - " call Decho("g:netrw_port <".g:netrw_port.">",'~'.expand("")) - " if exists("g:netrw_uid") "Decho - " call Decho("g:netrw_uid <".g:netrw_uid.">",'~'.expand("")) - " endif "Decho - " if exists("s:netrw_passwd") "Decho - " call Decho("s:netrw_passwd <".s:netrw_passwd.">",'~'.expand("")) - " endif "Decho - " call Decho("b:netrw_fname <".b:netrw_fname.">",'~'.expand("")) - " call Dret("s:NetrwMethod : b:netrw_method=".b:netrw_method." g:netrw_port=".g:netrw_port) -endfun - -" --------------------------------------------------------------------- -" NetUserPass: set username and password for subsequent ftp transfer {{{2 -" Usage: :call NetUserPass() -- will prompt for userid and password -" :call NetUserPass("uid") -- will prompt for password -" :call NetUserPass("uid","password") -- sets global userid and password -" :call NetUserPass("ftp:host") -- looks up userid and password using hup dictionary -" :call NetUserPass("host","uid","password") -- sets hup dictionary with host, userid, password -fun! NetUserPass(...) - - " call Dfunc("NetUserPass() a:0=".a:0) - - if !exists('s:netrw_hup') - let s:netrw_hup= {} - endif - - if a:0 == 0 - " case: no input arguments - - " change host and username if not previously entered; get new password - if !exists("g:netrw_machine") - let g:netrw_machine= input('Enter hostname: ') - endif - if !exists("g:netrw_uid") || g:netrw_uid == "" - " get username (user-id) via prompt - let g:netrw_uid= input('Enter username: ') - endif - " get password via prompting - let s:netrw_passwd= inputsecret("Enter Password: ") - - " set up hup database - let host = substitute(g:netrw_machine,'\..*$','','') - if !exists('s:netrw_hup[host]') - let s:netrw_hup[host]= {} - endif - let s:netrw_hup[host].uid = g:netrw_uid - let s:netrw_hup[host].passwd = s:netrw_passwd - - elseif a:0 == 1 - " case: one input argument - - if a:1 =~ '^ftp:' - " get host from ftp:... url - " access userid and password from hup (host-user-passwd) dictionary - " call Decho("case a:0=1: a:1<".a:1."> (get host from ftp:... url)",'~'.expand("")) - let host = substitute(a:1,'^ftp:','','') - let host = substitute(host,'\..*','','') - if exists("s:netrw_hup[host]") - let g:netrw_uid = s:netrw_hup[host].uid - let s:netrw_passwd = s:netrw_hup[host].passwd - " call Decho("get s:netrw_hup[".host."].uid <".s:netrw_hup[host].uid.">",'~'.expand("")) - " call Decho("get s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand("")) - else - let g:netrw_uid = input("Enter UserId: ") - let s:netrw_passwd = inputsecret("Enter Password: ") - endif - - else - " case: one input argument, not an url. Using it as a new user-id. - " call Decho("case a:0=1: a:1<".a:1."> (get host from input argument, not an url)",'~'.expand("")) - if exists("g:netrw_machine") - if g:netrw_machine =~ '[0-9.]\+' - let host= g:netrw_machine - else - let host= substitute(g:netrw_machine,'\..*$','','') - endif - else - let g:netrw_machine= input('Enter hostname: ') - endif - let g:netrw_uid = a:1 - " call Decho("set g:netrw_uid= <".g:netrw_uid.">",'~'.expand("")) - if exists("g:netrw_passwd") - " ask for password if one not previously entered - let s:netrw_passwd= g:netrw_passwd - else - let s:netrw_passwd = inputsecret("Enter Password: ") - endif - endif - - " call Decho("host<".host.">",'~'.expand("")) - if exists("host") - if !exists('s:netrw_hup[host]') - let s:netrw_hup[host]= {} - endif - let s:netrw_hup[host].uid = g:netrw_uid - let s:netrw_hup[host].passwd = s:netrw_passwd - endif - - elseif a:0 == 2 - let g:netrw_uid = a:1 - let s:netrw_passwd = a:2 - - elseif a:0 == 3 - " enter hostname, user-id, and password into the hup dictionary - let host = substitute(a:1,'^\a\+:','','') - let host = substitute(host,'\..*$','','') - if !exists('s:netrw_hup[host]') - let s:netrw_hup[host]= {} - endif - let s:netrw_hup[host].uid = a:2 - let s:netrw_hup[host].passwd = a:3 - let g:netrw_uid = s:netrw_hup[host].uid - let s:netrw_passwd = s:netrw_hup[host].passwd - " call Decho("set s:netrw_hup[".host."].uid <".s:netrw_hup[host].uid.">",'~'.expand("")) - " call Decho("set s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand("")) - endif - - " call Dret("NetUserPass : uid<".g:netrw_uid."> passwd<".s:netrw_passwd.">") -endfun - -" ================================= -" Shared Browsing Support: {{{1 -" ================================= - -" --------------------------------------------------------------------- -" s:ExplorePatHls: converts an Explore pattern into a regular expression search pattern {{{2 -fun! s:ExplorePatHls(pattern) - " call Dfunc("s:ExplorePatHls(pattern<".a:pattern.">)") - let repat= substitute(a:pattern,'^**/\{1,2}','','') - " call Decho("repat<".repat.">",'~'.expand("")) - let repat= escape(repat,'][.\') - " call Decho("repat<".repat.">",'~'.expand("")) - let repat= '\<'.substitute(repat,'\*','\\(\\S\\+ \\)*\\S\\+','g').'\>' - " call Dret("s:ExplorePatHls repat<".repat.">") - return repat -endfun - -" --------------------------------------------------------------------- -" s:NetrwBookHistHandler: {{{2 -" 0: (user: ) bookmark current directory -" 1: (user: ) change to the bookmarked directory -" 2: (user: ) list bookmarks -" 3: (browsing) records current directory history -" 4: (user: ) go up (previous) directory, using history -" 5: (user: ) go down (next) directory, using history -" 6: (user: ) delete bookmark -fun! s:NetrwBookHistHandler(chg,curdir) - " call Dfunc("s:NetrwBookHistHandler(chg=".a:chg." curdir<".a:curdir.">) cnt=".v:count." histcnt=".g:netrw_dirhistcnt." histmax=".g:netrw_dirhistmax) - if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0 - " " call Dret("s:NetrwBookHistHandler - suppressed due to g:netrw_dirhistmax") - return - endif - - let ykeep = @@ - let curbufnr = bufnr("%") - - if a:chg == 0 - " bookmark the current directory - " call Decho("(user: ) bookmark the current directory",'~'.expand("")) - if exists("s:netrwmarkfilelist_{curbufnr}") - call s:NetrwBookmark(0) - echo "bookmarked marked files" - else - call s:MakeBookmark(a: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("")) - if exists("g:netrw_bookmarklist[v:count-1]") - " call Decho("(user: <".v:count."gb>) bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("")) - exe "NetrwKeepj e ".fnameescape(g:netrw_bookmarklist[v:count-1]) - else - echomsg "Sorry, bookmark#".v:count." doesn't exist!" - endif - - elseif a:chg == 2 - " redraw! - let didwork= 0 - " list user's bookmarks - " call Decho("(user: ) list user's bookmarks",'~'.expand("")) - if exists("g:netrw_bookmarklist") - " call Decho('list '.len(g:netrw_bookmarklist).' bookmarks','~'.expand("")) - let cnt= 1 - for bmd in g:netrw_bookmarklist - " call Decho("Netrw Bookmark#".cnt.": ".g:netrw_bookmarklist[cnt-1],'~'.expand("")) - echo printf("Netrw Bookmark#%-2d: %s",cnt,g:netrw_bookmarklist[cnt-1]) - let didwork = 1 - let cnt = cnt + 1 - endfor - endif - - " list directory history - " Note: history is saved only when PerformListing is done; - " ie. when netrw can re-use a netrw buffer, the current directory is not saved in the history. - let cnt = g:netrw_dirhistcnt - let first = 1 - let histcnt = 0 - if g:netrw_dirhistmax > 0 - while ( first || cnt != g:netrw_dirhistcnt ) - " call Decho("first=".first." cnt=".cnt." dirhistcnt=".g:netrw_dirhistcnt,'~'.expand("")) - if exists("g:netrw_dirhist_{cnt}") - " call Decho("Netrw History#".histcnt.": ".g:netrw_dirhist_{cnt},'~'.expand("")) - echo printf("Netrw History#%-2d: %s",histcnt,g:netrw_dirhist_{cnt}) - let didwork= 1 - endif - let histcnt = histcnt + 1 - let first = 0 - let cnt = ( cnt - 1 ) % g:netrw_dirhistmax - if cnt < 0 - let cnt= cnt + g:netrw_dirhistmax - endif - endwhile - else - let g:netrw_dirhistcnt= 0 - endif - if didwork - call inputsave()|call input("Press to continue")|call inputrestore() - endif - - elseif a:chg == 3 - " saves most recently visited directories (when they differ) - " call Decho("(browsing) record curdir history",'~'.expand("")) - if !exists("g:netrw_dirhistcnt") || !exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") || g:netrw_dirhist_{g:netrw_dirhistcnt} != a:curdir - if g:netrw_dirhistmax > 0 - let g:netrw_dirhistcnt = ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax - let g:netrw_dirhist_{g:netrw_dirhistcnt} = a:curdir - endif - " call Decho("save dirhist#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand("")) - endif - - elseif a:chg == 4 - " u: change to the previous directory stored on the history list - " call Decho("(user: ) chg to prev dir from history",'~'.expand("")) - if g:netrw_dirhistmax > 0 - let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - v:count1 ) % g:netrw_dirhistmax - if g:netrw_dirhistcnt < 0 - let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax - endif - else - let g:netrw_dirhistcnt= 0 - endif - if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") - " call Decho("changedir u#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand("")) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir") - setl ma noro - " call Decho("setl ma noro",'~'.expand("")) - sil! NetrwKeepj %d _ - setl nomod - " call Decho("setl nomod",'~'.expand("")) - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - endif - " call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand("")) - exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}) - else - if g:netrw_dirhistmax > 0 - let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + v:count1 ) % g:netrw_dirhistmax - else - let g:netrw_dirhistcnt= 0 - endif - echo "Sorry, no predecessor directory exists yet" - endif - - elseif a:chg == 5 - " U: change to the subsequent directory stored on the history list - " call Decho("(user: ) chg to next dir from history",'~'.expand("")) - if g:netrw_dirhistmax > 0 - let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax - if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") - " call Decho("changedir U#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand("")) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir") - " call Decho("setl ma noro",'~'.expand("")) - setl ma noro - sil! NetrwKeepj %d _ - " call Decho("removed all lines from buffer (%d)",'~'.expand("")) - " call Decho("setl nomod",'~'.expand("")) - setl nomod - " call Decho("(set nomod) ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - endif - " call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand("")) - exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}) - else - let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - 1 ) % g:netrw_dirhistmax - if g:netrw_dirhistcnt < 0 - let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax - endif - echo "Sorry, no successor directory exists yet" - endif - else - let g:netrw_dirhistcnt= 0 - echo "Sorry, no successor directory exists yet (g:netrw_dirhistmax is ".g:netrw_dirhistmax.")" - endif - - elseif a:chg == 6 - " call Decho("(user: ) delete bookmark'd directory",'~'.expand("")) - if exists("s:netrwmarkfilelist_{curbufnr}") - call s:NetrwBookmark(1) - echo "removed marked files from bookmarks" - else - " delete the v:count'th bookmark - let iremove = v:count - let dremove = g:netrw_bookmarklist[iremove - 1] - " call Decho("delete bookmark#".iremove."<".g:netrw_bookmarklist[iremove - 1].">",'~'.expand("")) - call s:MergeBookmarks() - " call Decho("remove g:netrw_bookmarklist[".(iremove-1)."]<".g:netrw_bookmarklist[(iremove-1)].">",'~'.expand("")) - NetrwKeepj call remove(g:netrw_bookmarklist,iremove-1) - echo "removed ".dremove." from g:netrw_bookmarklist" - " call Decho("g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("")) - endif - " call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("")) - - try - call s:NetrwBookHistSave() - catch - endtry - endif - call s:NetrwBookmarkMenu() - call s:NetrwTgtMenu() - let @@= ykeep - " call Dret("s:NetrwBookHistHandler") -endfun - -" --------------------------------------------------------------------- -" s:NetrwBookHistRead: this function reads bookmarks and history {{{2 -" Will source the history file (.netrwhist) only if the g:netrw_disthistmax is > 0. -" Sister function: s:NetrwBookHistSave() -fun! s:NetrwBookHistRead() - " call Dfunc("s:NetrwBookHistRead()") - if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0 - " call Dret("s:NetrwBookHistRead - nothing read (suppressed due to dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a").")") - return - endif - let ykeep= @@ - - " read bookmarks - if !exists("s:netrw_initbookhist") - let home = s:NetrwHome() - let savefile= home."/.netrwbook" - if filereadable(s:NetrwFile(savefile)) - " call Decho("sourcing .netrwbook",'~'.expand("")) - exe "keepalt NetrwKeepj so ".savefile - endif - - " read history - if g:netrw_dirhistmax > 0 - let savefile= home."/.netrwhist" - if filereadable(s:NetrwFile(savefile)) - " call Decho("sourcing .netrwhist",'~'.expand("")) - exe "keepalt NetrwKeepj so ".savefile - endif - let s:netrw_initbookhist= 1 - au VimLeave * call s:NetrwBookHistSave() - endif - endif - - let @@= ykeep - " call Decho("dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a"),'~'.expand("")) - " call Decho("dirhistcnt=".(exists("g:netrw_dirhistcnt")? g:netrw_dirhistcnt : "n/a"),'~'.expand("")) - " call Dret("s:NetrwBookHistRead") -endfun - -" --------------------------------------------------------------------- -" s:NetrwBookHistSave: this function saves bookmarks and history to files {{{2 -" Sister function: s:NetrwBookHistRead() -" I used to do this via viminfo but that appears to -" be unreliable for long-term storage -" If g:netrw_dirhistmax is <= 0, no history or bookmarks -" will be saved. -" (s:NetrwBookHistHandler(3,...) used to record history) -fun! s:NetrwBookHistSave() - " call Dfunc("s:NetrwBookHistSave() dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt) - if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0 - " call Dret("s:NetrwBookHistSave : nothing saved (dirhistmax=".g:netrw_dirhistmax.")") - return - endif - - let savefile= s:NetrwHome()."/.netrwhist" - " call Decho("savefile<".savefile.">",'~'.expand("")) - 1split - - " setting up a new buffer which will become .netrwhist - call s:NetrwEnew() - " call Decho("case g:netrw_use_noswf=".g:netrw_use_noswf.(exists("+acd")? " +acd" : " -acd"),'~'.expand("")) - if g:netrw_use_noswf - setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000 noswf - else - setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000 - endif - setl nocin noai noci magic nospell nohid wig= noaw - setl ma noro write - if exists("+acd") | setl noacd | endif - sil! NetrwKeepj keepalt %d _ - - " rename enew'd file: .netrwhist -- no attempt to merge - " record dirhistmax and current dirhistcnt - " save history - " call Decho("saving history: dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt." lastline=".line("$"),'~'.expand("")) - sil! keepalt file .netrwhist - call setline(1,"let g:netrw_dirhistmax =".g:netrw_dirhistmax) - call setline(2,"let g:netrw_dirhistcnt =".g:netrw_dirhistcnt) - if g:netrw_dirhistmax > 0 - let lastline = line("$") - let cnt = g:netrw_dirhistcnt - let first = 1 - while ( first || cnt != g:netrw_dirhistcnt ) - let lastline= lastline + 1 - if exists("g:netrw_dirhist_{cnt}") - call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'") - " call Decho("..".lastline.'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'",'~'.expand("")) - endif - let first = 0 - let cnt = ( cnt - 1 ) % g:netrw_dirhistmax - if cnt < 0 - let cnt= cnt + g:netrw_dirhistmax - endif - endwhile - exe "sil! w! ".savefile - " call Decho("exe sil! w! ".savefile,'~'.expand("")) - endif - - " save bookmarks - sil NetrwKeepj %d _ - if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] - " call Decho("saving bookmarks",'~'.expand("")) - " merge and write .netrwbook - let savefile= s:NetrwHome()."/.netrwbook" - - if filereadable(s:NetrwFile(savefile)) - let booklist= deepcopy(g:netrw_bookmarklist) - exe "sil NetrwKeepj keepalt so ".savefile - for bdm in booklist - if index(g:netrw_bookmarklist,bdm) == -1 - call add(g:netrw_bookmarklist,bdm) - endif - endfor - call sort(g:netrw_bookmarklist) - endif - - " construct and save .netrwbook - call setline(1,"let g:netrw_bookmarklist= ".string(g:netrw_bookmarklist)) - exe "sil! w! ".savefile - " call Decho("exe sil! w! ".savefile,'~'.expand("")) - endif - - " cleanup -- remove buffer used to construct history - let bgone= bufnr("%") - q! - exe "keepalt ".bgone."bwipe!" - - " call Dret("s:NetrwBookHistSave") -endfun - -" --------------------------------------------------------------------- -" s:NetrwBrowse: This function uses the command in g:netrw_list_cmd to provide a {{{2 -" list of the contents of a local or remote directory. It is assumed that the -" g:netrw_list_cmd has a string, USEPORT HOSTNAME, that needs to be substituted -" with the requested remote hostname first. -" Often called via: Explore/e dirname/etc -> netrw#LocalBrowseCheck() -> s:NetrwBrowse() -fun! s:NetrwBrowse(islocal,dirname) - if !exists("w:netrw_liststyle")|let w:netrw_liststyle= g:netrw_liststyle|endif - - " save alternate-file's filename if w:netrw_rexlocal doesn't exist - " This is useful when one edits a local file, then :e ., then :Rex - if a:islocal && !exists("w:netrw_rexfile") && bufname("#") != "" - let w:netrw_rexfile= bufname("#") - endif - - " s:NetrwBrowse : initialize history {{{3 - if !exists("s:netrw_initbookhist") - NetrwKeepj call s:NetrwBookHistRead() - endif - - " s:NetrwBrowse : simplify the dirname (especially for ".."s in dirnames) {{{3 - if a:dirname !~ '^\a\{3,}://' - let dirname= simplify(a:dirname) - else - let dirname= a:dirname - endif - - " repoint t:netrw_lexbufnr if appropriate - if exists("t:netrw_lexbufnr") && bufnr("%") == t:netrw_lexbufnr - let repointlexbufnr= 1 - endif - - " s:NetrwBrowse : sanity checks: {{{3 - if exists("s:netrw_skipbrowse") - unlet s:netrw_skipbrowse - return - endif - if !exists("*shellescape") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing shellescape()",69) - return - endif - if !exists("*fnameescape") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing fnameescape()",70) - return - endif - - " s:NetrwBrowse : save options: {{{3 - call s:NetrwOptionsSave("w:") - - " s:NetrwBrowse : re-instate any marked files {{{3 - if has("syntax") && exists("g:syntax_on") && g:syntax_on - if exists("s:netrwmarkfilelist_{bufnr('%')}") - exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/" - endif - endif - - if a:islocal && exists("w:netrw_acdkeep") && w:netrw_acdkeep - " s:NetrwBrowse : set up "safe" options for local directory/file {{{3 - if s:NetrwLcd(dirname) - return - endif - - elseif !a:islocal && dirname !~ '[\/]$' && dirname !~ '^"' - " s:NetrwBrowse : remote regular file handler {{{3 - if bufname(dirname) != "" - exe "NetrwKeepj b ".bufname(dirname) - else - " attempt transfer of remote regular file - - " remove any filetype indicator from end of dirname, except for the - " "this is a directory" indicator (/). - " There shouldn't be one of those here, anyway. - let path= substitute(dirname,'[*=@|]\r\=$','','e') - call s:RemotePathAnalysis(dirname) - - " s:NetrwBrowse : remote-read the requested file into current buffer {{{3 - call s:NetrwEnew(dirname) - call s:NetrwOptionsSafe(a:islocal) - setl ma noro - let b:netrw_curdir = dirname - let url = s:method."://".((s:user == "")? "" : s:user."@").s:machine.(s:port ? ":".s:port : "")."/".s:path - call s:NetrwBufRename(url) - exe "sil! NetrwKeepj keepalt doau BufReadPre ".fnameescape(s:fname) - sil call netrw#NetRead(2,url) - " netrw.vim and tar.vim have already handled decompression of the tarball; avoiding gzip.vim error - if s:path =~ '.bz2' - exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.bz2$','','')) - elseif s:path =~ '.gz' - exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.gz$','','')) - elseif s:path =~ '.gz' - exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.txz$','','')) - else - exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(s:fname) - endif - endif - - " s:NetrwBrowse : save certain window-oriented variables into buffer-oriented variables {{{3 - call s:SetBufWinVars() - call s:NetrwOptionsRestore("w:") - setl ma nomod noro - return - endif - - " use buffer-oriented WinVars if buffer variables exist but associated window variables don't {{{3 - call s:UseBufWinVars() - - " set up some variables {{{3 - let b:netrw_browser_active = 1 - let dirname = dirname - let s:last_sort_by = g:netrw_sort_by - - " set up menu {{{3 - NetrwKeepj call s:NetrwMenu(1) - - " get/set-up buffer {{{3 - let svpos = winsaveview() - - " NetrwGetBuffer might change buffers but s:rexposn_X was set for the - " previous buffer - let prevbufnr = bufnr('%') - let reusing= s:NetrwGetBuffer(a:islocal,dirname) - if exists("s:rexposn_".prevbufnr) - let s:rexposn_{bufnr('%')} = s:rexposn_{prevbufnr} - endif - - " maintain markfile highlighting - if has("syntax") && exists("g:syntax_on") && g:syntax_on - if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != "" - exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/" - else - 2match none - endif - endif - if reusing && line("$") > 1 - call s:NetrwOptionsRestore("w:") - setl noma nomod nowrap - return - endif - - " set b:netrw_curdir to the new directory name {{{3 - let b:netrw_curdir= dirname - if b:netrw_curdir =~ '[/\\]$' - let b:netrw_curdir= substitute(b:netrw_curdir,'[/\\]$','','e') - endif - if b:netrw_curdir =~ '\a:$' && has("win32") - let b:netrw_curdir= b:netrw_curdir."/" - endif - if b:netrw_curdir == '' - if has("amiga") - " On the Amiga, the empty string connotes the current directory - let b:netrw_curdir= getcwd() - else - " under unix, when the root directory is encountered, the result - " from the preceding substitute is an empty string. - let b:netrw_curdir= '/' - endif - endif - if !a:islocal && b:netrw_curdir !~ '/$' - let b:netrw_curdir= b:netrw_curdir.'/' - endif - - " ------------ - " (local only) {{{3 - " ------------ - if a:islocal - " Set up ShellCmdPost handling. Append current buffer to browselist - call s:LocalFastBrowser() - - " handle g:netrw_keepdir: set vim's current directory to netrw's notion of the current directory {{{3 - if !g:netrw_keepdir - if !exists("&l:acd") || !&l:acd - if s:NetrwLcd(b:netrw_curdir) - return - endif - endif - endif - - " -------------------------------- - " remote handling: {{{3 - " -------------------------------- - else - - " analyze dirname and g:netrw_list_cmd {{{3 - if dirname =~# "^NetrwTreeListing\>" - let dirname= b:netrw_curdir - elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir") - let dirname= substitute(b:netrw_curdir,'\\','/','g') - if dirname !~ '/$' - let dirname= dirname.'/' - endif - let b:netrw_curdir = dirname - else - let dirname = substitute(dirname,'\\','/','g') - endif - - let dirpat = '^\(\w\{-}\)://\(\w\+@\)\=\([^/]\+\)/\(.*\)$' - if dirname !~ dirpat - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw doesn't understand your dirname<".dirname.">",20) - endif - NetrwKeepj call s:NetrwOptionsRestore("w:") - setl noma nomod nowrap - return - endif - let b:netrw_curdir= dirname - endif " (additional remote handling) - - " ------------------------------- - " Perform Directory Listing: {{{3 - " ------------------------------- - NetrwKeepj call s:NetrwMaps(a:islocal) - NetrwKeepj call s:NetrwCommands(a:islocal) - NetrwKeepj call s:PerformListing(a:islocal) - - " restore option(s) - call s:NetrwOptionsRestore("w:") - - " If there is a rexposn: restore position with rexposn - " Otherwise : set rexposn - if exists("s:rexposn_".bufnr("%")) - NetrwKeepj call winrestview(s:rexposn_{bufnr('%')}) - if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt - NetrwKeepj exe w:netrw_bannercnt - endif - else - NetrwKeepj call s:SetRexDir(a:islocal,b:netrw_curdir) - endif - if v:version >= 700 && has("balloon_eval") && &beval == 0 && &l:bexpr == "" && !exists("g:netrw_nobeval") - let &l:bexpr= "netrw#BalloonHelp()" - setl beval - endif - - " repoint t:netrw_lexbufnr if appropriate - if exists("repointlexbufnr") - let t:netrw_lexbufnr= bufnr("%") - endif - - " restore position - if reusing - call winrestview(svpos) - endif - - " The s:LocalBrowseRefresh() function is called by an autocmd - " installed by s:LocalFastBrowser() when g:netrw_fastbrowse <= 1 (ie. slow or medium speed). - " However, s:NetrwBrowse() causes the FocusGained event to fire the first time. - return -endfun - -" --------------------------------------------------------------------- -" s:NetrwFile: because of g:netrw_keepdir, isdirectory(), type(), etc may or {{{2 -" may not apply correctly; ie. netrw's idea of the current directory may -" differ from vim's. This function insures that netrw's idea of the current -" directory is used. -" Returns a path to the file specified by a:fname -fun! s:NetrwFile(fname) - " "" call Dfunc("s:NetrwFile(fname<".a:fname.">) win#".winnr()) - " "" call Decho("g:netrw_keepdir =".(exists("g:netrw_keepdir")? g:netrw_keepdir : 'n/a'),'~'.expand("")) - " "" call Decho("g:netrw_cygwin =".(exists("g:netrw_cygwin")? g:netrw_cygwin : 'n/a'),'~'.expand("")) - " "" call Decho("g:netrw_liststyle=".(exists("g:netrw_liststyle")? g:netrw_liststyle : 'n/a'),'~'.expand("")) - " "" call Decho("w:netrw_liststyle=".(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand("")) - - " clean up any leading treedepthstring - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - let fname= substitute(a:fname,'^'.s:treedepthstring.'\+','','') - " "" call Decho("clean up any leading treedepthstring: fname<".fname.">",'~'.expand("")) - else - let fname= a:fname - endif - - if g:netrw_keepdir - " vim's idea of the current directory possibly may differ from netrw's - if !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - endif - - if !exists("g:netrw_cygwin") && has("win32") - if fname =~ '^\' || fname =~ '^\a:\' - " windows, but full path given - let ret= fname - " "" call Decho("windows+full path: isdirectory(".fname.")",'~'.expand("")) - else - " windows, relative path given - let ret= s:ComposePath(b:netrw_curdir,fname) - " "" call Decho("windows+rltv path: isdirectory(".fname.")",'~'.expand("")) - endif - - elseif fname =~ '^/' - " not windows, full path given - let ret= fname - " "" call Decho("unix+full path: isdirectory(".fname.")",'~'.expand("")) - else - " not windows, relative path given - let ret= s:ComposePath(b:netrw_curdir,fname) - " "" call Decho("unix+rltv path: isdirectory(".fname.")",'~'.expand("")) - endif - else - " vim and netrw agree on the current directory - let ret= fname - " "" call Decho("vim and netrw agree on current directory (g:netrw_keepdir=".g:netrw_keepdir.")",'~'.expand("")) - " "" call Decho("vim directory: ".getcwd(),'~'.expand("")) - " "" call Decho("netrw directory: ".(exists("b:netrw_curdir")? b:netrw_curdir : 'n/a'),'~'.expand("")) - endif - - " "" call Dret("s:NetrwFile ".ret) - return ret -endfun - -" --------------------------------------------------------------------- -" s:NetrwFileInfo: supports qf (query for file information) {{{2 -fun! s:NetrwFileInfo(islocal,fname) - " call Dfunc("s:NetrwFileInfo(islocal=".a:islocal." fname<".a:fname.">) b:netrw_curdir<".b:netrw_curdir.">") - let ykeep= @@ - if a:islocal - let lsopt= "-lsad" - if g:netrw_sizestyle =~# 'H' - let lsopt= "-lsadh" - elseif g:netrw_sizestyle =~# 'h' - let lsopt= "-lsadh --si" - endif - " call Decho("(s:NetrwFileInfo) lsopt<".lsopt.">") - if (has("unix") || has("macunix")) && executable("/bin/ls") - - if getline(".") == "../" - echo system("/bin/ls ".lsopt." ".s:ShellEscape("..")) - " call Decho("#1: echo system(/bin/ls -lsad ".s:ShellEscape(..).")",'~'.expand("")) - - elseif w:netrw_liststyle == s:TREELIST && getline(".") !~ '^'.s:treedepthstring - echo system("/bin/ls ".lsopt." ".s:ShellEscape(b:netrw_curdir)) - " call Decho("#2: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir).")",'~'.expand("")) - - elseif exists("b:netrw_curdir") - echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,a:fname))) - " call Decho("#3: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir.a:fname).")",'~'.expand("")) - - else - " call Decho('using ls '.a:fname." using cwd<".getcwd().">",'~'.expand("")) - echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:NetrwFile(a:fname))) - " call Decho("#5: echo system(/bin/ls -lsad ".s:ShellEscape(a:fname).")",'~'.expand("")) - endif - else - " use vim functions to return information about file below cursor - " call Decho("using vim functions to query for file info",'~'.expand("")) - if !isdirectory(s:NetrwFile(a:fname)) && !filereadable(s:NetrwFile(a:fname)) && a:fname =~ '[*@/]' - let fname= substitute(a:fname,".$","","") - else - let fname= a:fname - endif - let t = getftime(s:NetrwFile(fname)) - let sz = getfsize(s:NetrwFile(fname)) - if g:netrw_sizestyle =~# "[hH]" - let sz= s:NetrwHumanReadable(sz) - endif - echo a:fname.": ".sz." ".strftime(g:netrw_timefmt,getftime(s:NetrwFile(fname))) - " call Decho("fname.": ".sz." ".strftime(g:netrw_timefmt,getftime(fname)),'~'.expand("")) - endif - else - echo "sorry, \"qf\" not supported yet for remote files" - endif - let @@= ykeep - " call Dret("s:NetrwFileInfo") -endfun - -" --------------------------------------------------------------------- -" s:NetrwFullPath: returns the full path to a directory and/or file {{{2 -fun! s:NetrwFullPath(filename) - " " call Dfunc("s:NetrwFullPath(filename<".a:filename.">)") - let filename= a:filename - if filename !~ '^/' - let filename= resolve(getcwd().'/'.filename) - endif - if filename != "/" && filename =~ '/$' - let filename= substitute(filename,'/$','','') - endif - " " call Dret("s:NetrwFullPath <".filename.">") - return filename -endfun - -" --------------------------------------------------------------------- -" s:NetrwGetBuffer: [get a new|find an old netrw] buffer for a netrw listing {{{2 -" returns 0=cleared buffer -" 1=re-used buffer (buffer not cleared) -" Nov 09, 2020: tst952 shows that when user does :set hidden that NetrwGetBuffer will come up with a [No Name] buffer (hid fix) -fun! s:NetrwGetBuffer(islocal,dirname) - " call Dfunc("s:NetrwGetBuffer(islocal=".a:islocal." dirname<".a:dirname.">) liststyle=".g:netrw_liststyle) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." hid=".&hid,'~'.expand("")) - " call Decho("netrwbuf dictionary=".(exists("s:netrwbuf")? string(s:netrwbuf) : 'n/a'),'~'.expand("")) - " call Dredir("ls!","s:NetrwGetBuffer") - let dirname= a:dirname - - " re-use buffer if possible {{{3 - " call Decho("--re-use a buffer if possible--",'~'.expand("")) - if !exists("s:netrwbuf") - " call Decho(" s:netrwbuf initialized to {}",'~'.expand("")) - let s:netrwbuf= {} - endif - " call Decho(" s:netrwbuf =".string(s:netrwbuf),'~'.expand("")) - " call Decho(" w:netrw_liststyle =".(exists("w:netrw_liststyle")? w:netrw_liststyle : "n/a"),'~'.expand("")) - - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - let bufnum = -1 - - if !empty(s:netrwbuf) && has_key(s:netrwbuf,s:NetrwFullPath(dirname)) - if has_key(s:netrwbuf,"NetrwTreeListing") - let bufnum= s:netrwbuf["NetrwTreeListing"] - else - let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)] - endif - " call Decho(" NetrwTreeListing: bufnum#".bufnum,'~'.expand("")) - if !bufexists(bufnum) - call remove(s:netrwbuf,"NetrwTreeListing") - let bufnum= -1 - endif - elseif bufnr("NetrwTreeListing") != -1 - let bufnum= bufnr("NetrwTreeListing") - " call Decho(" NetrwTreeListing".": bufnum#".bufnum,'~'.expand("")) - else - " call Decho(" did not find a NetrwTreeListing buffer",'~'.expand("")) - let bufnum= -1 - endif - - elseif has_key(s:netrwbuf,s:NetrwFullPath(dirname)) - let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)] - " call Decho(" lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnum,'~'.expand("")) - if !bufexists(bufnum) - call remove(s:netrwbuf,s:NetrwFullPath(dirname)) - let bufnum= -1 - endif - - else - " call Decho(" lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."] not a key",'~'.expand("")) - let bufnum= -1 - endif - " call Decho(" bufnum#".bufnum,'~'.expand("")) - - " highjack the current buffer - " IF the buffer already has the desired name - " AND it is empty - let curbuf = bufname("%") - if curbuf == '.' - let curbuf = getcwd() - endif - " call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)") - " call Decho("deciding if netrw may highjack the current buffer#".bufnr("%")."<".curbuf.">",'~'.expand("")) - " call Decho("..dirname<".dirname."> IF dirname == bufname",'~'.expand("")) - " call Decho("..curbuf<".curbuf.">",'~'.expand("")) - " call Decho("..line($)=".line("$")." AND this is 1",'~'.expand("")) - " call Decho("..getline(%)<".getline("%")."> AND this line is empty",'~'.expand("")) - if dirname == curbuf && line("$") == 1 && getline("%") == "" - " call Dret("s:NetrwGetBuffer 0 : highjacking buffer#".bufnr("%")) - return 0 - else " DEBUG - " call Decho("..did NOT highjack buffer",'~'.expand("")) - endif - " Aug 14, 2021: was thinking about looking for a [No Name] buffer here and using it, but that might cause problems - - " get enew buffer and name it -or- re-use buffer {{{3 - if bufnum < 0 " get enew buffer and name it - " call Decho("--get enew buffer and name it (bufnum#".bufnum."<0 OR bufexists(".bufnum.")=".bufexists(bufnum)."==0)",'~'.expand("")) - call s:NetrwEnew(dirname) - " call Decho(" got enew buffer#".bufnr("%")." (altbuf<".expand("#").">)",'~'.expand("")) - " name the buffer - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - " Got enew buffer; transform into a NetrwTreeListing - " call Decho("--transform enew buffer#".bufnr("%")." into a NetrwTreeListing --",'~'.expand("")) - let w:netrw_treebufnr = bufnr("%") - call s:NetrwBufRename("NetrwTreeListing") - if g:netrw_use_noswf - setl nobl bt=nofile noswf - else - setl nobl bt=nofile - endif - nnoremap [[ :sil call TreeListMove('[[') - nnoremap ]] :sil call TreeListMove(']]') - nnoremap [] :sil call TreeListMove('[]') - nnoremap ][ :sil call TreeListMove('][') - " call Decho(" tree listing bufnr=".w:netrw_treebufnr,'~'.expand("")) - else - call s:NetrwBufRename(dirname) - " enter the new buffer into the s:netrwbuf dictionary - let s:netrwbuf[s:NetrwFullPath(dirname)]= bufnr("%") - " call Decho("update netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnr("%"),'~'.expand("")) - " call Decho("netrwbuf dictionary=".string(s:netrwbuf),'~'.expand("")) - endif - " call Decho(" named enew buffer#".bufnr("%")."<".bufname("%").">",'~'.expand("")) - - else " Re-use the buffer - " call Decho("--re-use buffer#".bufnum." (bufnum#".bufnum.">=0 AND bufexists(".bufnum.")=".bufexists(bufnum)."!=0)",'~'.expand("")) - " ignore all events - let eikeep= &ei - setl ei=all - - if &ft == "netrw" - " call Decho("buffer type is netrw; not using keepalt with b ".bufnum) - exe "sil! NetrwKeepj noswapfile b ".bufnum - " call Dredir("ls!","one") - else - " call Decho("buffer type is not netrw; using keepalt with b ".bufnum) - call s:NetrwEditBuf(bufnum) - " call Dredir("ls!","two") - endif - " call Decho(" line($)=".line("$"),'~'.expand("")) - if bufname("%") == '.' - call s:NetrwBufRename(getcwd()) - endif - - " restore ei - let &ei= eikeep - - if line("$") <= 1 && getline(1) == "" - " empty buffer - NetrwKeepj call s:NetrwListSettings(a:islocal) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but its empty, so refresh it") - return 0 - - elseif g:netrw_fastbrowse == 0 || (a:islocal && g:netrw_fastbrowse == 1) - " call Decho("g:netrw_fastbrowse=".g:netrw_fastbrowse." a:islocal=".a:islocal.": clear buffer",'~'.expand("")) - NetrwKeepj call s:NetrwListSettings(a:islocal) - sil NetrwKeepj %d _ - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but refreshing due to g:netrw_fastbrowse=".g:netrw_fastbrowse) - return 0 - - elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - " call Decho("--re-use tree listing--",'~'.expand("")) - " call Decho(" clear buffer<".expand("%")."> with :%d",'~'.expand("")) - setl ma - sil NetrwKeepj %d _ - NetrwKeepj call s:NetrwListSettings(a:islocal) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but treelist mode always needs a refresh") - return 0 - - else - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Dret("s:NetrwGetBuffer 1") - return 1 - endif - endif - - " do netrw settings: make this buffer not-a-file, modifiable, not line-numbered, etc {{{3 - " fastbrowse Local Remote Hiding a buffer implies it may be re-used (fast) - " slow 0 D D Deleting a buffer implies it will not be re-used (slow) - " med 1 D H - " fast 2 H H - " call Decho("--do netrw settings: make this buffer#".bufnr("%")." not-a-file, modifiable, not line-numbered, etc--",'~'.expand("")) - let fname= expand("%") - NetrwKeepj call s:NetrwListSettings(a:islocal) - call s:NetrwBufRename(fname) - - " delete all lines from buffer {{{3 - " call Decho("--delete all lines from buffer--",'~'.expand("")) - " call Decho(" clear buffer<".expand("%")."> with :%d",'~'.expand("")) - sil! keepalt NetrwKeepj %d _ - - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Dret("s:NetrwGetBuffer 0") - return 0 -endfun - -" --------------------------------------------------------------------- -" s:NetrwGetcwd: get the current directory. {{{2 -" Change backslashes to forward slashes, if any. -" If doesc is true, escape certain troublesome characters -fun! s:NetrwGetcwd(doesc) - " call Dfunc("NetrwGetcwd(doesc=".a:doesc.")") - let curdir= substitute(getcwd(),'\\','/','ge') - if curdir !~ '[\/]$' - let curdir= curdir.'/' - endif - if a:doesc - let curdir= fnameescape(curdir) - endif - " call Dret("NetrwGetcwd <".curdir.">") - return curdir -endfun - -" --------------------------------------------------------------------- -" s:NetrwGetWord: it gets the directory/file named under the cursor {{{2 -fun! s:NetrwGetWord() - " call Dfunc("s:NetrwGetWord() liststyle=".s:ShowStyle()." virtcol=".virtcol(".")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - let keepsol= &l:sol - setl nosol - - call s:UseBufWinVars() - - " insure that w:netrw_liststyle is set up - if !exists("w:netrw_liststyle") - if exists("g:netrw_liststyle") - let w:netrw_liststyle= g:netrw_liststyle - else - let w:netrw_liststyle= s:THINLIST - endif - " call Decho("w:netrw_liststyle=".w:netrw_liststyle,'~'.expand("")) - endif - - if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt - " Active Banner support - " call Decho("active banner handling",'~'.expand("")) - NetrwKeepj norm! 0 - let dirname= "./" - let curline= getline('.') - - if curline =~# '"\s*Sorted by\s' - NetrwKeepj norm! "_s - let s:netrw_skipbrowse= 1 - echo 'Pressing "s" also works' - - elseif curline =~# '"\s*Sort sequence:' - let s:netrw_skipbrowse= 1 - echo 'Press "S" to edit sorting sequence' - - elseif curline =~# '"\s*Quick Help:' - NetrwKeepj norm! ? - let s:netrw_skipbrowse= 1 - - elseif curline =~# '"\s*\%(Hiding\|Showing\):' - NetrwKeepj norm! a - let s:netrw_skipbrowse= 1 - echo 'Pressing "a" also works' - - elseif line("$") > w:netrw_bannercnt - exe 'sil NetrwKeepj '.w:netrw_bannercnt - endif - - elseif w:netrw_liststyle == s:THINLIST - " call Decho("thin column handling",'~'.expand("")) - NetrwKeepj norm! 0 - let dirname= substitute(getline('.'),'\t -->.*$','','') - - elseif w:netrw_liststyle == s:LONGLIST - " call Decho("long column handling",'~'.expand("")) - NetrwKeepj norm! 0 - let dirname= substitute(getline('.'),'^\(\%(\S\+ \)*\S\+\).\{-}$','\1','e') - - elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - " call Decho("treelist handling",'~'.expand("")) - let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e') - let dirname= substitute(dirname,'\t -->.*$','','') - - else - " call Decho("obtain word from wide listing",'~'.expand("")) - let dirname= getline('.') - - if !exists("b:netrw_cpf") - let b:netrw_cpf= 0 - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif' - call histdel("/",-1) - " "call Decho("computed cpf=".b:netrw_cpf,'~'.expand("")) - endif - - " call Decho("buf#".bufnr("%")."<".bufname("%").">",'~'.expand("")) - let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf - " call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart." bannercnt=".w:netrw_bannercnt,'~'.expand("")) - " call Decho("1: dirname<".dirname.">",'~'.expand("")) - if filestart == 0 - NetrwKeepj norm! 0ma - else - call cursor(line("."),filestart+1) - NetrwKeepj norm! ma - endif - - let dict={} - " save the unnamed register and register 0-9 and a - let dict.a=[getreg('a'), getregtype('a')] - for i in range(0, 9) - let dict[i] = [getreg(i), getregtype(i)] - endfor - let dict.unnamed = [getreg(''), getregtype('')] - - let eofname= filestart + b:netrw_cpf + 1 - if eofname <= col("$") - call cursor(line("."),filestart+b:netrw_cpf+1) - NetrwKeepj norm! "ay`a - else - NetrwKeepj norm! "ay$ - endif - - let dirname = @a - call s:RestoreRegister(dict) - - " call Decho("2: dirname<".dirname.">",'~'.expand("")) - let dirname= substitute(dirname,'\s\+$','','e') - " call Decho("3: dirname<".dirname.">",'~'.expand("")) - endif - - " symlinks are indicated by a trailing "@". Remove it before further processing. - let dirname= substitute(dirname,"@$","","") - - " executables are indicated by a trailing "*". Remove it before further processing. - let dirname= substitute(dirname,"\*$","","") - - let &l:sol= keepsol - - " call Dret("s:NetrwGetWord <".dirname.">") - return dirname -endfun - -" --------------------------------------------------------------------- -" s:NetrwListSettings: make standard settings for making a netrw listing {{{2 -" g:netrw_bufsettings will be used after the listing is produced. -" Called by s:NetrwGetBuffer() -fun! s:NetrwListSettings(islocal) - " call Dfunc("s:NetrwListSettings(islocal=".a:islocal.")") - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - let fname= bufname("%") - " " call Decho("setl bt=nofile nobl ma nonu nowrap noro nornu",'~'.expand("")) - " nobl noma nomod nonu noma nowrap ro nornu (std g:netrw_bufsettings) - setl bt=nofile nobl ma nonu nowrap noro nornu - call s:NetrwBufRename(fname) - if g:netrw_use_noswf - setl noswf - endif - " call Dredir("ls!","s:NetrwListSettings") - " call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand("")) - exe "setl ts=".(g:netrw_maxfilenamelen+1) - setl isk+=.,~,- - if g:netrw_fastbrowse > a:islocal - setl bh=hide - else - setl bh=delete - endif - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Dret("s:NetrwListSettings") -endfun - -" --------------------------------------------------------------------- -" s:NetrwListStyle: change list style (thin - long - wide - tree) {{{2 -" islocal=0: remote browsing -" =1: local browsing -fun! s:NetrwListStyle(islocal) - let ykeep = @@ - let fname = s:NetrwGetWord() - if !exists("w:netrw_liststyle")|let w:netrw_liststyle= g:netrw_liststyle|endif - let svpos = winsaveview() - let w:netrw_liststyle = (w:netrw_liststyle + 1) % s:MAXLIST - - " repoint t:netrw_lexbufnr if appropriate - if exists("t:netrw_lexbufnr") && bufnr("%") == t:netrw_lexbufnr - let repointlexbufnr= 1 - endif - - if w:netrw_liststyle == s:THINLIST - " use one column listing - let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') - - elseif w:netrw_liststyle == s:LONGLIST - " use long list - let g:netrw_list_cmd = g:netrw_list_cmd." -l" - - elseif w:netrw_liststyle == s:WIDELIST - " give wide list - let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') - - elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') - - else - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"bad value for g:netrw_liststyle (=".w:netrw_liststyle.")",46) - let g:netrw_liststyle = s:THINLIST - let w:netrw_liststyle = g:netrw_liststyle - let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') - endif - setl ma noro - - " clear buffer - this will cause NetrwBrowse/LocalBrowseCheck to do a refresh - sil! NetrwKeepj %d _ - " following prevents tree listing buffer from being marked "modified" - setl nomod - - " refresh the listing - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call s:NetrwCursor(0) - - " repoint t:netrw_lexbufnr if appropriate - if exists("repointlexbufnr") - let t:netrw_lexbufnr= bufnr("%") - endif - - " restore position; keep cursor on the filename - " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("")) - NetrwKeepj call winrestview(svpos) - let @@= ykeep - -endfun - -" --------------------------------------------------------------------- -" s:NetrwBannerCtrl: toggles the display of the banner {{{2 -fun! s:NetrwBannerCtrl(islocal) - let ykeep= @@ - " toggle the banner (enable/suppress) - let g:netrw_banner= !g:netrw_banner - - " refresh the listing - let svpos= winsaveview() - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - - " keep cursor on the filename - if g:netrw_banner && exists("w:netrw_bannercnt") && line(".") >= w:netrw_bannercnt - let fname= s:NetrwGetWord() - sil NetrwKeepj $ - let result= search('\%(^\%(|\+\s\)\=\|\s\{2,}\)\zs'.escape(fname,'.\[]*$^').'\%(\s\{2,}\|$\)','bc') - " " call Decho("search result=".result." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'N/A'),'~'.expand("")) - if result <= 0 && exists("w:netrw_bannercnt") - exe "NetrwKeepj ".w:netrw_bannercnt - endif - endif - let @@= ykeep - " call Dret("s:NetrwBannerCtrl : g:netrw_banner=".g:netrw_banner) -endfun - -" --------------------------------------------------------------------- -" s:NetrwBookmark: supports :NetrwMB[!] [file]s {{{2 -" -" No bang: enters files/directories into Netrw's bookmark system -" No argument and in netrw buffer: -" if there are marked files: bookmark marked files -" otherwise : bookmark file/directory under cursor -" No argument and not in netrw buffer: bookmarks current open file -" Has arguments: globs them individually and bookmarks them -" -" With bang: deletes files/directories from Netrw's bookmark system -fun! s:NetrwBookmark(del,...) - if a:0 == 0 - if &ft == "netrw" - let curbufnr = bufnr("%") - - if exists("s:netrwmarkfilelist_{curbufnr}") - " for every filename in the marked list - let svpos = winsaveview() - let islocal= expand("%") !~ '^\a\{3,}://' - for fname in s:netrwmarkfilelist_{curbufnr} - if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif - endfor - let curdir = exists("b:netrw_curdir")? b:netrw_curdir : getcwd() - call s:NetrwUnmarkList(curbufnr,curdir) - NetrwKeepj call s:NetrwRefresh(islocal,s:NetrwBrowseChgDir(islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - else - let fname= s:NetrwGetWord() - if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif - endif - - else - " bookmark currently open file - let fname= expand("%") - if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif - endif - - else - " bookmark specified files - " attempts to infer if working remote or local - " by deciding if the current file begins with an url - " Globbing cannot be done remotely. - let islocal= expand("%") !~ '^\a\{3,}://' - let i = 1 - while i <= a:0 - if islocal - if v:version > 704 || (v:version == 704 && has("patch656")) - let mbfiles= glob(fnameescape(a:{i}),0,1,1) - else - let mbfiles= glob(fnameescape(a:{i}),0,1) - endif - else - let mbfiles= [a:{i}] - endif - for mbfile in mbfiles - if a:del|call s:DeleteBookmark(mbfile)|else|call s:MakeBookmark(mbfile)|endif - endfor - let i= i + 1 - endwhile - endif - - " update the menu - call s:NetrwBookmarkMenu() -endfun - -" --------------------------------------------------------------------- -" s:NetrwBookmarkMenu: Uses menu priorities {{{2 -" .2.[cnt] for bookmarks, and -" .3.[cnt] for history -" (see s:NetrwMenu()) -fun! s:NetrwBookmarkMenu() - if !exists("s:netrw_menucnt") - return - endif - " call Dfunc("NetrwBookmarkMenu() histcnt=".g:netrw_dirhistcnt." menucnt=".s:netrw_menucnt) - - " the following test assures that gvim is running, has menus available, and has menus enabled. - if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu - if exists("g:NetrwTopLvlMenu") - " call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand("")) - exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks' - exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete' - endif - if !exists("s:netrw_initbookhist") - call s:NetrwBookHistRead() - endif - - " show bookmarked places - if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0 - let cnt= 1 - for bmd in g:netrw_bookmarklist - " call Decho('sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmark.'.bmd.' :e '.bmd,'~'.expand("")) - let bmd= escape(bmd,g:netrw_menu_escape) - - " show bookmarks for goto menu - exe 'sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks.'.bmd.' :e '.bmd."\" - - " show bookmarks for deletion menu - exe 'sil! menu '.g:NetrwMenuPriority.".8.2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete.'.bmd.' '.cnt."mB" - let cnt= cnt + 1 - endfor - - endif - - " show directory browsing history - if g:netrw_dirhistmax > 0 - let cnt = g:netrw_dirhistcnt - let first = 1 - let histcnt = 0 - while ( first || cnt != g:netrw_dirhistcnt ) - let histcnt = histcnt + 1 - let priority = g:netrw_dirhistcnt + histcnt - if exists("g:netrw_dirhist_{cnt}") - let histdir= escape(g:netrw_dirhist_{cnt},g:netrw_menu_escape) - " call Decho('sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.' :e '.histdir,'~'.expand("")) - exe 'sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.' :e '.histdir."\" - endif - let first = 0 - let cnt = ( cnt - 1 ) % g:netrw_dirhistmax - if cnt < 0 - let cnt= cnt + g:netrw_dirhistmax - endif - endwhile - endif - - endif - " call Dret("NetrwBookmarkMenu") -endfun - -" --------------------------------------------------------------------- -" s:NetrwBrowseChgDir: constructs a new directory based on the current {{{2 -" directory and a new directory name. Also, if the -" "new directory name" is actually a file, -" NetrwBrowseChgDir() edits the file. -" cursor=0: newdir is relative to b:netrw_curdir -" =1: newdir is relative to the path to the word under the cursor in -" tree view -fun! s:NetrwBrowseChgDir(islocal,newdir,cursor,...) - let ykeep= @@ - if !exists("b:netrw_curdir") - " Don't try to change-directory: this can happen, for example, when netrw#ErrorMsg has been called - " and the current window is the NetrwMessage window. - let @@= ykeep - return - endif - - " NetrwBrowseChgDir; save options and initialize {{{3 - call s:SavePosn(s:netrw_posn) - NetrwKeepj call s:NetrwOptionsSave("s:") - NetrwKeepj call s:NetrwOptionsSafe(a:islocal) - - let newdir = a:newdir - if a:cursor && exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop") - " dirname is the path to the word under the cursor - let dirname = s:NetrwTreePath(w:netrw_treetop) - " newdir resolves to a directory and points to a directory in dirname - " /tmp/test/folder_symlink/ -> /tmp/test/original_folder/ - if a:islocal && fnamemodify(dirname, ':t') == newdir && isdirectory(resolve(dirname)) && resolve(dirname) == resolve(newdir) - let dirname = fnamemodify(resolve(dirname), ':p:h:h') - let newdir = fnamemodify(resolve(newdir), ':t') - endif - " Remove trailing "/" - let dirname = substitute(dirname, "/$", "", "") - - " If the word under the cursor is a directory (except for ../), NetrwTreePath - " returns the full path, including the word under the cursor, remove it - if newdir =~ "/$" && newdir != "../" - let dirname = fnamemodify(dirname, ":h") - endif - else - let dirname = b:netrw_curdir - endif - if has("win32") - let dirname = substitute(dirname,'\\','/','ge') - endif - let dolockout = 0 - let dorestore = 1 - - " ignore s when done in the banner - if g:netrw_banner - if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt && line("$") >= w:netrw_bannercnt - if getline(".") =~# 'Quick Help' - let g:netrw_quickhelp= (g:netrw_quickhelp + 1)%len(s:QuickHelp) - setl ma noro nowrap - NetrwKeepj call setline(line('.'),'" Quick Help: :help '.s:QuickHelp[g:netrw_quickhelp]) - setl noma nomod nowrap - NetrwKeepj call s:NetrwOptionsRestore("s:") - endif - endif - endif - - " set up o/s-dependent directory recognition pattern - if has("amiga") - let dirpat= '[\/:]$' - else - let dirpat= '[\/]$' - endif - - if dirname !~ dirpat - " apparently vim is "recognizing" that it is in a directory and - " is removing the trailing "/". Bad idea, so let's put it back. - let dirname= dirname.'/' - endif - - if newdir !~ dirpat && !(a:islocal && isdirectory(s:NetrwFile(s:ComposePath(dirname,newdir)))) - " ------------------------------ - " NetrwBrowseChgDir: edit a file {{{3 - " ------------------------------ - - " save position for benefit of Rexplore - let s:rexposn_{bufnr("%")}= winsaveview() - - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") && newdir !~ '^\(/\|\a:\)' - if dirname =~ '/$' - let dirname= dirname.newdir - else - let dirname= dirname."/".newdir - endif - elseif newdir =~ '^\(/\|\a:\)' - let dirname= newdir - else - let dirname= s:ComposePath(dirname,newdir) - endif - " this lets netrw#BrowseX avoid the edit - if a:0 < 1 - NetrwKeepj call s:NetrwOptionsRestore("s:") - let curdir= b:netrw_curdir - if !exists("s:didsplit") - if type(g:netrw_browse_split) == 3 - " open file in server - " Note that g:netrw_browse_split is a List: [servername,tabnr,winnr] - call s:NetrwServerEdit(a:islocal,dirname) - return - - elseif g:netrw_browse_split == 1 - " horizontally splitting the window first - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize - exe "keepalt ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s" - if !&ea - keepalt wincmd _ - endif - call s:SetRexDir(a:islocal,curdir) - - elseif g:netrw_browse_split == 2 - " vertically splitting the window first - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize - exe "keepalt ".(g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s" - if !&ea - keepalt wincmd | - endif - call s:SetRexDir(a:islocal,curdir) - - elseif g:netrw_browse_split == 3 - " open file in new tab - keepalt tabnew - if !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - endif - call s:SetRexDir(a:islocal,curdir) - - elseif g:netrw_browse_split == 4 - " act like "P" (ie. open previous window) - if s:NetrwPrevWinOpen(2) == 3 - let @@= ykeep - return - endif - call s:SetRexDir(a:islocal,curdir) - - else - " handling a file, didn't split, so remove menu - call s:NetrwMenu(0) - " optional change to window - if g:netrw_chgwin >= 1 - if winnr("$")+1 == g:netrw_chgwin - " if g:netrw_chgwin is set to one more than the last window, then - " vertically split the last window to make that window available. - let curwin= winnr() - exe "NetrwKeepj keepalt ".winnr("$")."wincmd w" - vs - exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd ".curwin - endif - exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w" - endif - call s:SetRexDir(a:islocal,curdir) - endif - - endif - - " the point where netrw actually edits the (local) file - " if its local only: LocalBrowseCheck() doesn't edit a file, but NetrwBrowse() will - " use keepalt to support :e # to return to a directory listing - if !&mod - " if e the new file would fail due to &mod, then don't change any of the flags - let dolockout= 1 - endif - if a:islocal - " some like c-^ to return to the last edited file - " others like c-^ to return to the netrw buffer - " Apr 30, 2020: used to have e! here. That can cause loss of a modified file, - " so emit error E37 instead. - call s:NetrwEditFile("e","",dirname) - call s:NetrwCursor(1) - if &hidden || &bufhidden == "hide" - " file came from vim's hidden storage. Don't "restore" options with it. - let dorestore= 0 - endif - else - endif - - " handle g:Netrw_funcref -- call external-to-netrw functions - " This code will handle g:Netrw_funcref as an individual function reference - " or as a list of function references. It will ignore anything that's not - " a function reference. See :help Funcref for information about function references. - if exists("g:Netrw_funcref") - if type(g:Netrw_funcref) == 2 - NetrwKeepj call g:Netrw_funcref() - elseif type(g:Netrw_funcref) == 3 - for Fncref in g:Netrw_funcref - if type(Fncref) == 2 - NetrwKeepj call Fncref() - endif - endfor - endif - endif - endif - - elseif newdir =~ '^/' - " ---------------------------------------------------- - " NetrwBrowseChgDir: just go to the new directory spec {{{3 - " ---------------------------------------------------- - let dirname = newdir - NetrwKeepj call s:SetRexDir(a:islocal,dirname) - NetrwKeepj call s:NetrwOptionsRestore("s:") - norm! m` - - elseif newdir == './' - " --------------------------------------------- - " NetrwBrowseChgDir: refresh the directory list {{{3 - " --------------------------------------------- - NetrwKeepj call s:SetRexDir(a:islocal,dirname) - norm! m` - - elseif newdir == '../' - " -------------------------------------- - " NetrwBrowseChgDir: go up one directory {{{3 - " -------------------------------------- - - if w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") - " force a refresh - setl noro ma - NetrwKeepj %d _ - endif - - if has("amiga") - " amiga - if a:islocal - let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+$\)','\1','') - let dirname= substitute(dirname,'/$','','') - else - let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+/$\)','\1','') - endif - - elseif !g:netrw_cygwin && has("win32") - " windows - if a:islocal - let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','') - if dirname == "" - let dirname= '/' - endif - else - let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','') - endif - if dirname =~ '^\a:$' - let dirname= dirname.'/' - endif - - else - " unix or cygwin - if a:islocal - let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','') - if dirname == "" - let dirname= '/' - endif - else - let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','') - endif - endif - NetrwKeepj call s:SetRexDir(a:islocal,dirname) - norm! m` - - elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") - " -------------------------------------- - " NetrwBrowseChgDir: Handle Tree Listing {{{3 - " -------------------------------------- - " force a refresh (for TREELIST, NetrwTreeDir() will force the refresh) - setl noro ma - if !(exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")) - NetrwKeepj %d _ - endif - let treedir = s:NetrwTreeDir(a:islocal) - let s:treecurpos = winsaveview() - let haskey = 0 - - " search treedict for tree dir as-is - if has_key(w:netrw_treedict,treedir) - let haskey= 1 - else - endif - - " search treedict for treedir with a [/@] appended - if !haskey && treedir !~ '[/@]$' - if has_key(w:netrw_treedict,treedir."/") - let treedir= treedir."/" - let haskey = 1 - else - endif - endif - - " search treedict for treedir with any trailing / elided - if !haskey && treedir =~ '/$' - let treedir= substitute(treedir,'/$','','') - if has_key(w:netrw_treedict,treedir) - let haskey = 1 - else - endif - endif - - if haskey - " close tree listing for selected subdirectory - call remove(w:netrw_treedict,treedir) - let dirname= w:netrw_treetop - else - " go down one directory - let dirname= substitute(treedir,'/*$','/','') - endif - NetrwKeepj call s:SetRexDir(a:islocal,dirname) - let s:treeforceredraw = 1 - - else - " ---------------------------------------- - " NetrwBrowseChgDir: Go down one directory {{{3 - " ---------------------------------------- - let dirname = s:ComposePath(dirname,newdir) - NetrwKeepj call s:SetRexDir(a:islocal,dirname) - norm! m` - endif - - " -------------------------------------- - " NetrwBrowseChgDir: Restore and Cleanup {{{3 - " -------------------------------------- - if dorestore - " dorestore is zero'd when a local file was hidden or bufhidden; - " in such a case, we want to keep whatever settings it may have. - NetrwKeepj call s:NetrwOptionsRestore("s:") - endif - if dolockout && dorestore - if filewritable(dirname) - setl ma noro nomod - else - setl ma ro nomod - endif - endif - call s:RestorePosn(s:netrw_posn) - let @@= ykeep - - return dirname -endfun - -" --------------------------------------------------------------------- -" s:NetrwBrowseUpDir: implements the "-" mappings {{{2 -" for thin, long, and wide: cursor placed just after banner -" for tree, keeps cursor on current filename -fun! s:NetrwBrowseUpDir(islocal) - if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt-1 - " this test needed because occasionally this function seems to be incorrectly called - " when multiple leftmouse clicks are taken when atop the one line help in the banner. - " I'm allowing the very bottom line to permit a "-" exit so that one may escape empty - " directories. - return - endif - - norm! 0 - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") - let curline= getline(".") - let swwline= winline() - 1 - if exists("w:netrw_treetop") - let b:netrw_curdir= w:netrw_treetop - elseif exists("b:netrw_curdir") - let w:netrw_treetop= b:netrw_curdir - else - let w:netrw_treetop= getcwd() - let b:netrw_curdir = w:netrw_treetop - endif - let curfile = getline(".") - let curpath = s:NetrwTreePath(w:netrw_treetop) - if a:islocal - call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0)) - else - call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0)) - endif - if w:netrw_treetop == '/' - keepj call search('^\M'.curfile,"w") - elseif curfile == '../' - keepj call search('^\M'.curfile,"wb") - else - while 1 - keepj call search('^\M'.s:treedepthstring.curfile,"wb") - let treepath= s:NetrwTreePath(w:netrw_treetop) - if treepath == curpath - break - endif - endwhile - endif - - else - call s:SavePosn(s:netrw_posn) - if exists("b:netrw_curdir") - let curdir= b:netrw_curdir - else - let curdir= expand(getcwd()) - endif - if a:islocal - call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0)) - else - call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0)) - endif - call s:RestorePosn(s:netrw_posn) - let curdir= substitute(curdir,'^.*[\/]','','') - let curdir= '\<'. escape(curdir, '~'). '/' - call search(curdir,'wc') - endif -endfun - -func s:redir() - " set up redirection (avoids browser messages) - " by default if not set, g:netrw_suppress_gx_mesg is true - if get(g:, 'netrw_suppress_gx_mesg', 1) - if &srr =~# "%s" - return printf(&srr, has("win32") ? "nul" : "/dev/null") - else - return &srr .. (has("win32") ? "nul" : "/dev/null") - endif - endif - return '' -endfunc - -if has('unix') - if has('win32unix') - " Cygwin provides cygstart - if executable('cygstart') - fun! netrw#Launch(args) - exe 'silent ! cygstart --hide' a:args s:redir() | redraw! - endfun - elseif !empty($MSYSTEM) && executable('start') - " MSYS2/Git Bash comes by default without cygstart; see - " https://www.msys2.org/wiki/How-does-MSYS2-differ-from-Cygwin - " Instead it provides /usr/bin/start script running `cmd.exe //c start` - " Adding "" //b` sets void title, hides cmd window and blocks path conversion - " of /b to \b\ " by MSYS2; see https://www.msys2.org/docs/filesystem-paths/ - fun! netrw#Launch(args) - exe 'silent !start "" //b' a:args s:redir() | redraw! - endfun - else - " imitate /usr/bin/start script for other environments and hope for the best - fun! netrw#Launch(args) - exe 'silent !cmd //c start "" //b' a:args s:redir() | redraw! - endfun - endif - elseif exists('$WSL_DISTRO_NAME') " use cmd.exe to start GUI apps in WSL - fun! netrw#Launch(args) - let args = a:args - exe 'silent !' .. - \ ((args =~? '\v<\f+\.(exe|com|bat|cmd)>') ? - \ 'cmd.exe /c start /b ' .. args : - \ 'nohup ' .. args .. ' ' .. s:redir() .. ' &') - \ | redraw! - endfun - else - fun! netrw#Launch(args) - exe ':silent ! nohup' a:args s:redir() (has('gui_running') ? '' : '&') | redraw! - endfun - endif -elseif has('win32') - fun! netrw#Launch(args) - exe 'silent !' .. (&shell =~? '\' ? '' : 'cmd.exe /c') - \ 'start "" /b' a:args s:redir() | redraw! - endfun -else - fun! netrw#Launch(dummy) - echom 'No common launcher found' - endfun -endif - -" Git Bash -if has('win32unix') - " (cyg)start suffices - let s:os_viewer = '' -" Windows / WSL -elseif executable('explorer.exe') - let s:os_viewer = 'explorer.exe' -" Linux / BSD -elseif executable('xdg-open') - let s:os_viewer = 'xdg-open' -" MacOS -elseif executable('open') - let s:os_viewer = 'open' -endif - -fun! s:viewer() - " g:netrw_browsex_viewer could be a string of program + its arguments, - " test if first argument is executable - if exists('g:netrw_browsex_viewer') && executable(split(g:netrw_browsex_viewer)[0]) - " extract any viewing options. Assumes that they're set apart by spaces. - " call Decho("extract any viewing options from g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("")) - if g:netrw_browsex_viewer =~ '\s' - let viewer = substitute(g:netrw_browsex_viewer,'\s.*$','','') - let viewopt = substitute(g:netrw_browsex_viewer,'^\S\+\s*','','')." " - let oviewer = '' - let cnt = 1 - while !executable(viewer) && viewer != oviewer - let viewer = substitute(g:netrw_browsex_viewer,'^\(\(^\S\+\s\+\)\{'.cnt.'}\S\+\)\(.*\)$','\1','') - let viewopt = substitute(g:netrw_browsex_viewer,'^\(\(^\S\+\s\+\)\{'.cnt.'}\S\+\)\(.*\)$','\3','')." " - let cnt = cnt + 1 - let oviewer = viewer - " call Decho("!exe: viewer<".viewer."> viewopt<".viewopt.">",'~'.expand("")) - endwhile - else - let viewer = g:netrw_browsex_viewer - let viewopt = "" - endif - " call Decho("viewer<".viewer."> viewopt<".viewopt.">",'~'.expand("")) - return viewer .. ' ' .. viewopt - else - if !exists('s:os_viewer') - call netrw#ErrorMsg(s:ERROR,"No program to open this path found. See :help Open for more information.",106) - else - return s:os_viewer - endif - endif -endfun - -fun! netrw#Open(file) abort - call netrw#Launch(s:viewer() .. ' ' .. shellescape(a:file, 1)) -endfun - -if !exists('g:netrw_regex_url') - let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}' -endif - -" --------------------------------------------------------------------- -" netrw#BrowseX: (implements "x" and "gx") executes a special "viewer" script or program for the {{{2 -" given filename; typically this means given their extension. -" 0=local, 1=remote -fun! netrw#BrowseX(fname,remote) - if a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$' - " remote directory, not a webpage access, looks like an attempt to do a directory listing - norm! gf - endif - - if exists("g:netrw_browsex_viewer") && exists("g:netrw_browsex_support_remote") && !g:netrw_browsex_support_remote - let remote = a:remote - else - let remote = 0 - endif - - let ykeep = @@ - let screenposn = winsaveview() - - " need to save and restore aw setting as gx can invoke this function from non-netrw buffers - let awkeep = &aw - set noaw - - " special core dump handler - if a:fname =~ '/core\(\.\d\+\)\=$' - if exists("g:Netrw_corehandler") - if type(g:Netrw_corehandler) == 2 - " g:Netrw_corehandler is a function reference (see :help Funcref) - call g:Netrw_corehandler(s:NetrwFile(a:fname)) - elseif type(g:Netrw_corehandler) == 3 - " g:Netrw_corehandler is a List of function references (see :help Funcref) - for Fncref in g:Netrw_corehandler - if type(Fncref) == 2 - call Fncref(a:fname) - endif - endfor - endif - call winrestview(screenposn) - let @@= ykeep - let &aw= awkeep - return - endif - endif - - " set up the filename - " (lower case the extension, make a local copy of a remote file) - let exten= substitute(a:fname,'.*\.\(.\{-}\)','\1','e') - if has("win32") - let exten= substitute(exten,'^.*$','\L&\E','') - endif - if exten =~ "[\\/]" - let exten= "" - endif - - if remote == 1 - " create a local copy - setl bh=delete - call netrw#NetRead(3,a:fname) - " attempt to rename tempfile - let basename= substitute(a:fname,'^\(.*\)/\(.*\)\.\([^.]*\)$','\2','') - let newname = substitute(s:netrw_tmpfile,'^\(.*\)/\(.*\)\.\([^.]*\)$','\1/'.basename.'.\3','') - if s:netrw_tmpfile != newname && newname != "" - if rename(s:netrw_tmpfile,newname) == 0 - " renaming succeeded - let fname= newname - else - " renaming failed - let fname= s:netrw_tmpfile - endif - else - let fname= s:netrw_tmpfile - endif - else - let fname= a:fname - " special ~ handler for local - if fname =~ '^\~' && expand("$HOME") != "" - let fname= s:NetrwFile(substitute(fname,'^\~',expand("$HOME"),'')) - endif - endif - - " although shellescape(..., 1) is used in netrw#Open(), it's insufficient - call netrw#Open(escape(fname, '#%')) - - " cleanup: remove temporary file, - " delete current buffer if success with handler, - " return to prior buffer (directory listing) - " Feb 12, 2008: had to de-activate removal of - " temporary file because it wasn't getting seen. - " if remote == 1 && fname != a:fname - " call s:NetrwDelete(fname) - " endif - - if remote == 1 - setl bh=delete bt=nofile - if g:netrw_use_noswf - setl noswf - endif - exe "sil! NetrwKeepj norm! \" - endif - call winrestview(screenposn) - let @@ = ykeep - let &aw= awkeep -endfun - -" --------------------------------------------------------------------- -" netrw#GX: gets word under cursor for gx support {{{2 -" See also: netrw#BrowseXVis -" netrw#BrowseX -fun! netrw#GX() - " call Dfunc("netrw#GX()") - if &ft == "netrw" - let fname= s:NetrwGetWord() - else - let fname= exists("g:netrw_gx")? expand(g:netrw_gx) : s:GetURL() - endif - " call Dret("netrw#GX <".fname.">") - return fname -endfun - -fun! s:GetURL() abort - let URL = '' - if exists('*Netrw_get_URL_' .. &filetype) - let URL = call('Netrw_get_URL_' .. &filetype, []) - endif - if !empty(URL) | return URL | endif - " URLs end in letter, digit or forward slash - let URL = matchstr(expand(""), '\<' .. g:netrw_regex_url .. '\ze[^A-Za-z0-9/]*$') - if !empty(URL) | return URL | endif - - " Is it a file in the current work dir ... - let file = expand("") - if filereadable(file) | return file | endif - " ... or in that of the current buffer? - let path = fnamemodify(expand('%'), ':p') - if isdirectory(path) - let dir = path - elseif filereadable(path) - let dir = fnamemodify(path, ':h') - endif - if exists('dir') && filereadable(dir..'/'..file) | return dir..'/'..file | endif - - return '' -endf - -" --------------------------------------------------------------------- -" netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2 -fun! netrw#BrowseXVis() - let dict={} - let dict.a=[getreg('a'), getregtype('a')] - norm! gv"ay - let gxfile= @a - call s:RestoreRegister(dict) - call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile)) -endfun - -" --------------------------------------------------------------------- -" s:NetrwBufRename: renames a buffer without the side effect of retaining an unlisted buffer having the old name {{{2 -" Using the file command on a "[No Name]" buffer does not seem to cause the old "[No Name]" buffer -" to become an unlisted buffer, so in that case don't bwipe it. -fun! s:NetrwBufRename(newname) - " call Dfunc("s:NetrwBufRename(newname<".a:newname.">) buf(%)#".bufnr("%")."<".bufname(bufnr("%")).">") - " call Dredir("ls!","s:NetrwBufRename (before rename)") - let oldbufname= bufname(bufnr("%")) - " call Decho("buf#".bufnr("%").": oldbufname<".oldbufname.">",'~'.expand("")) - - if oldbufname != a:newname - " call Decho("do buffer rename: oldbufname<".oldbufname."> ≠ a:newname<".a:newname.">",'~'.expand("")) - let b:junk= 1 - " call Decho("rename buffer: sil! keepj keepalt file ".fnameescape(a:newname),'~'.expand("")) - exe 'sil! keepj keepalt file '.fnameescape(a:newname) - " call Dredir("ls!","s:NetrwBufRename (before bwipe)~".expand("")) - let oldbufnr= bufnr(oldbufname) - " call Decho("oldbufname<".oldbufname."> oldbufnr#".oldbufnr,'~'.expand("")) - " call Decho("bufnr(%)=".bufnr("%"),'~'.expand("")) - if oldbufname != "" && oldbufnr != -1 && oldbufnr != bufnr("%") - " call Decho("bwipe ".oldbufnr,'~'.expand("")) - exe "bwipe! ".oldbufnr - " else " Decho - " call Decho("did *not* bwipe buf#".oldbufnr,'~'.expand("")) - " call Decho("..reason: if oldbufname<".oldbufname."> is empty",'~'.expand(""))" - " call Decho("..reason: if oldbufnr#".oldbufnr." is -1",'~'.expand(""))" - " call Decho("..reason: if oldbufnr#".oldbufnr." != bufnr(%)#".bufnr("%"),'~'.expand(""))" - endif - " call Dredir("ls!","s:NetrwBufRename (after rename)") - " else " Decho - " call Decho("oldbufname<".oldbufname."> == a:newname: did *not* rename",'~'.expand("")) - endif - - " call Dret("s:NetrwBufRename : buf#".bufnr("%").": oldname<".oldbufname."> newname<".a:newname."> expand(%)<".expand("%").">") -endfun - -" --------------------------------------------------------------------- -" netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2 -fun! netrw#CheckIfRemote(...) - " call Dfunc("netrw#CheckIfRemote() a:0=".a:0) - if a:0 > 0 - let curfile= a:1 - else - let curfile= expand("%") - endif - " Ignore terminal buffers - if &buftype ==# 'terminal' - return 0 - endif - " call Decho("curfile<".curfile.">") - if curfile =~ '^\a\{3,}://' - " call Dret("netrw#CheckIfRemote 1") - return 1 - else - " call Dret("netrw#CheckIfRemote 0") - return 0 - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwChgPerm: (implements "gp") change file permission {{{2 -fun! s:NetrwChgPerm(islocal,curdir) - let ykeep = @@ - call inputsave() - let newperm= input("Enter new permission: ") - call inputrestore() - let chgperm= substitute(g:netrw_chgperm,'\',s:ShellEscape(expand("")),'') - let chgperm= substitute(chgperm,'\',s:ShellEscape(newperm),'') - call system(chgperm) - if v:shell_error != 0 - NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("")."> seems to have failed",75) - endif - if a:islocal - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - endif - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:CheckIfKde: checks if kdeinit is running {{{2 -" Returns 0: kdeinit not running -" 1: kdeinit is running -fun! s:CheckIfKde() - " call Dfunc("s:CheckIfKde()") - " seems kde systems often have gnome-open due to dependencies, even though - " gnome-open's subsidiary display tools are largely absent. Kde systems - " usually have "kdeinit" running, though... (tnx Mikolaj Machowski) - if !exists("s:haskdeinit") - if has("unix") && executable("ps") && !has("win32unix") - let s:haskdeinit= system("ps -e") =~ '\")) - endif - - " call Dret("s:CheckIfKde ".s:haskdeinit) - return s:haskdeinit -endfun - -" --------------------------------------------------------------------- -" s:NetrwClearExplore: clear explore variables (if any) {{{2 -fun! s:NetrwClearExplore() - " call Dfunc("s:NetrwClearExplore()") - 2match none - if exists("s:explore_match") |unlet s:explore_match |endif - if exists("s:explore_indx") |unlet s:explore_indx |endif - if exists("s:netrw_explore_prvdir") |unlet s:netrw_explore_prvdir |endif - if exists("s:dirstarstar") |unlet s:dirstarstar |endif - if exists("s:explore_prvdir") |unlet s:explore_prvdir |endif - if exists("w:netrw_explore_indx") |unlet w:netrw_explore_indx |endif - if exists("w:netrw_explore_listlen")|unlet w:netrw_explore_listlen|endif - if exists("w:netrw_explore_list") |unlet w:netrw_explore_list |endif - if exists("w:netrw_explore_bufnr") |unlet w:netrw_explore_bufnr |endif - " redraw! - " call Dret("s:NetrwClearExplore") -endfun - -" --------------------------------------------------------------------- -" s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2 -fun! s:NetrwEditBuf(bufnum) - " call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)") - if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw" - " call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)) - exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum) - else - " call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)) - exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum) - endif - " call Dret("s:NetrwEditBuf") -endfun - -" --------------------------------------------------------------------- -" s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2 -" NetrwKeepj [keepalt] -fun! s:NetrwEditFile(cmd,opt,fname) - " call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">) ft<".&ft.">") - if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw" - " call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)) - exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname) - else - " call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)) - if a:cmd =~# 'e\%[new]!' && !&hidden && getbufvar(bufname('%'), '&modified', 0) - call setbufvar(bufname('%'), '&bufhidden', 'hide') - endif - exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname) - endif - " call Dret("s:NetrwEditFile") -endfun - -" --------------------------------------------------------------------- -" s:NetrwExploreListUniq: {{{2 -fun! s:NetrwExploreListUniq(explist) - " this assumes that the list is already sorted - let newexplist= [] - for member in a:explist - if !exists("uniqmember") || member != uniqmember - let uniqmember = member - let newexplist = newexplist + [ member ] - endif - endfor - return newexplist -endfun - -" --------------------------------------------------------------------- -" s:NetrwForceChgDir: (gd support) Force treatment as a directory {{{2 -fun! s:NetrwForceChgDir(islocal,newdir) - let ykeep= @@ - if a:newdir !~ '/$' - " ok, looks like force is needed to get directory-style treatment - if a:newdir =~ '@$' - let newdir= substitute(a:newdir,'@$','/','') - elseif a:newdir =~ '[*=|\\]$' - let newdir= substitute(a:newdir,'.$','/','') - else - let newdir= a:newdir.'/' - endif - else - " should already be getting treatment as a directory - let newdir= a:newdir - endif - let newdir= s:NetrwBrowseChgDir(a:islocal,newdir,0) - call s:NetrwBrowse(a:islocal,newdir) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwGlob: does glob() if local, remote listing otherwise {{{2 -" direntry: this is the name of the directory. Will be fnameescape'd to prevent wildcard handling by glob() -" expr : this is the expression to follow the directory. Will use s:ComposePath() -" pare =1: remove the current directory from the resulting glob() filelist -" =0: leave the current directory in the resulting glob() filelist -fun! s:NetrwGlob(direntry,expr,pare) - " call Dfunc("s:NetrwGlob(direntry<".a:direntry."> expr<".a:expr."> pare=".a:pare.")") - if netrw#CheckIfRemote() - keepalt 1sp - keepalt enew - let keep_liststyle = w:netrw_liststyle - let w:netrw_liststyle = s:THINLIST - if s:NetrwRemoteListing() == 0 - keepj keepalt %s@/@@ - let filelist= getline(1,$) - q! - else - " remote listing error -- leave treedict unchanged - let filelist= w:netrw_treedict[a:direntry] - endif - let w:netrw_liststyle= keep_liststyle - else - let path= s:ComposePath(fnameescape(a:direntry), a:expr) - if has("win32") - " escape [ so it is not detected as wildcard character, see :h wildcard - let path= substitute(path, '[', '[[]', 'g') - endif - if v:version > 704 || (v:version == 704 && has("patch656")) - let filelist= glob(path,0,1,1) - else - let filelist= glob(path,0,1) - endif - if a:pare - let filelist= map(filelist,'substitute(v:val, "^.*/", "", "")') - endif - endif - return filelist -endfun - -" --------------------------------------------------------------------- -" s:NetrwForceFile: (gf support) Force treatment as a file {{{2 -fun! s:NetrwForceFile(islocal,newfile) - if a:newfile =~ '[/@*=|\\]$' - let newfile= substitute(a:newfile,'.$','','') - else - let newfile= a:newfile - endif - if a:islocal - call s:NetrwBrowseChgDir(a:islocal,newfile,0) - else - call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,newfile,0)) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwHide: this function is invoked by the "a" map for browsing {{{2 -" and switches the hiding mode. The actual hiding is done by -" s:NetrwListHide(). -" g:netrw_hide= 0: show all -" 1: show not-hidden files -" 2: show hidden files only -fun! s:NetrwHide(islocal) - let ykeep= @@ - let svpos= winsaveview() - - if exists("s:netrwmarkfilelist_{bufnr('%')}") - - " hide the files in the markfile list - for fname in s:netrwmarkfilelist_{bufnr("%")} - if match(g:netrw_list_hide,'\<'.fname.'\>') != -1 - " remove fname from hiding list - let g:netrw_list_hide= substitute(g:netrw_list_hide,'..\<'.escape(fname,g:netrw_fname_escape).'\>..','','') - let g:netrw_list_hide= substitute(g:netrw_list_hide,',,',',','g') - let g:netrw_list_hide= substitute(g:netrw_list_hide,'^,\|,$','','') - else - " append fname to hiding list - if exists("g:netrw_list_hide") && g:netrw_list_hide != "" - let g:netrw_list_hide= g:netrw_list_hide.',\<'.escape(fname,g:netrw_fname_escape).'\>' - else - let g:netrw_list_hide= '\<'.escape(fname,g:netrw_fname_escape).'\>' - endif - endif - endfor - NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) - let g:netrw_hide= 1 - - else - - " switch between show-all/show-not-hidden/show-hidden - let g:netrw_hide=(g:netrw_hide+1)%3 - exe "NetrwKeepj norm! 0" - if g:netrw_hide && g:netrw_list_hide == "" - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49) - let @@= ykeep - return - endif - endif - - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwHideEdit: allows user to edit the file/directory hiding list {{{2 -fun! s:NetrwHideEdit(islocal) - let ykeep= @@ - " save current cursor position - let svpos= winsaveview() - - " get new hiding list from user - call inputsave() - let newhide= input("Edit Hiding List: ",g:netrw_list_hide) - call inputrestore() - let g:netrw_list_hide= newhide - - " refresh the listing - sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./",0)) - - " restore cursor position - call winrestview(svpos) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwHidden: invoked by "gh" {{{2 -fun! s:NetrwHidden(islocal) - let ykeep= @@ - " save current position - let svpos = winsaveview() - - if g:netrw_list_hide =~ '\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+' - " remove .file pattern from hiding list - let g:netrw_list_hide= substitute(g:netrw_list_hide,'\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+','','') - elseif s:Strlen(g:netrw_list_hide) >= 1 - let g:netrw_list_hide= g:netrw_list_hide . ',\(^\|\s\s\)\zs\.\S\+' - else - let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+' - endif - if g:netrw_list_hide =~ '^,' - let g:netrw_list_hide= strpart(g:netrw_list_hide,1) - endif - - " refresh screen and return to saved position - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwHome: this function determines a "home" for saving bookmarks and history {{{2 -fun! s:NetrwHome() - if exists("g:netrw_home") - let home= expand(g:netrw_home) - else - let home = stdpath('data') - endif - " insure that the home directory exists - if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home)) - " call Decho("insure that the home<".home."> directory exists") - if exists("g:netrw_mkdir") - " call Decho("call system(".g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)).")") - call system(g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home))) - else - " call Decho("mkdir(".home.")") - call mkdir(home) - endif - endif - let g:netrw_home= home - return home -endfun - -" --------------------------------------------------------------------- -" s:NetrwLeftmouse: handles the when in a netrw browsing window {{{2 -fun! s:NetrwLeftmouse(islocal) - if exists("s:netrwdrag") - return - endif - if &ft != "netrw" - return - endif - - let ykeep= @@ - " check if the status bar was clicked on instead of a file/directory name - while getchar(0) != 0 - "clear the input stream - endwhile - call feedkeys("\") - let c = getchar() - let mouse_lnum = v:mouse_lnum - let wlastline = line('w$') - let lastline = line('$') - if mouse_lnum >= wlastline + 1 || v:mouse_win != winnr() - " appears to be a status bar leftmouse click - let @@= ykeep - return - endif - " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode - " Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar - " without this test when its disabled. - " May 26, 2014: edit file, :Lex, resize window -- causes refresh. Reinstated a modified test. See if problems develop. - if v:mouse_col > virtcol('.') - let @@= ykeep - return - endif - - if a:islocal - if exists("b:netrw_curdir") - NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1)) - endif - else - if exists("b:netrw_curdir") - NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) - endif - endif - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwCLeftmouse: used to select a file/directory for a target {{{2 -fun! s:NetrwCLeftmouse(islocal) - if &ft != "netrw" - return - endif - call s:NetrwMarkFileTgt(a:islocal) -endfun - -" --------------------------------------------------------------------- -" s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER (implements ){{{2 -" a:islocal=0 : not used, remote -" a:islocal=1 : not used, local -" a:islocal=2 : used, remote -" a:islocal=3 : used, local -fun! s:NetrwServerEdit(islocal,fname) - " call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)") - let islocal = a:islocal%2 " =0: remote =1: local - let ctrlr = a:islocal >= 2 " =0: not used =1: used - - if (islocal && isdirectory(s:NetrwFile(a:fname))) || (!islocal && a:fname =~ '/$') - " handle directories in the local window -- not in the remote vim server - " user must have closed the NETRWSERVER window. Treat as normal editing from netrw. - let g:netrw_browse_split= 0 - if exists("s:netrw_browse_split") && exists("s:netrw_browse_split_".winnr()) - let g:netrw_browse_split= s:netrw_browse_split_{winnr()} - unlet s:netrw_browse_split_{winnr()} - endif - call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0)) - return - endif - - if has("clientserver") && executable("gvim") - - if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3 - let srvrname = g:netrw_browse_split[0] - let tabnum = g:netrw_browse_split[1] - let winnum = g:netrw_browse_split[2] - - if serverlist() !~ '\<'.srvrname.'\>' - if !ctrlr - " user must have closed the server window and the user did not use , but - " used something like . - if exists("g:netrw_browse_split") - unlet g:netrw_browse_split - endif - let g:netrw_browse_split= 0 - if exists("s:netrw_browse_split_".winnr()) - let g:netrw_browse_split= s:netrw_browse_split_{winnr()} - endif - call s:NetrwBrowseChgDir(islocal,a:fname,0) - return - - elseif has("win32") && executable("start") - " start up remote netrw server under windows - call system("start gvim --servername ".srvrname) - - else - " start up remote netrw server under linux - call system("gvim --servername ".srvrname) - endif - endif - - call remote_send(srvrname,":tabn ".tabnum."\") - call remote_send(srvrname,":".winnum."wincmd w\") - call remote_send(srvrname,":e ".fnameescape(s:NetrwFile(a:fname))."\") - else - - if serverlist() !~ '\<'.g:netrw_servername.'\>' - - if !ctrlr - if exists("g:netrw_browse_split") - unlet g:netrw_browse_split - endif - let g:netrw_browse_split= 0 - call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0)) - return - - else - if has("win32") && executable("start") - " start up remote netrw server under windows - call system("start gvim --servername ".g:netrw_servername) - else - " start up remote netrw server under linux - call system("gvim --servername ".g:netrw_servername) - endif - endif - endif - - while 1 - try - call remote_send(g:netrw_servername,":e ".fnameescape(s:NetrwFile(a:fname))."\") - break - catch /^Vim\%((\a\+)\)\=:E241/ - sleep 200m - endtry - endwhile - - if exists("g:netrw_browse_split") - if type(g:netrw_browse_split) != 3 - let s:netrw_browse_split_{winnr()}= g:netrw_browse_split - endif - unlet g:netrw_browse_split - endif - let g:netrw_browse_split= [g:netrw_servername,1,1] - endif - - else - call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use ",98) - endif - -endfun - -" --------------------------------------------------------------------- -" s:NetrwSLeftmouse: marks the file under the cursor. May be dragged to select additional files {{{2 -fun! s:NetrwSLeftmouse(islocal) - if &ft != "netrw" - return - endif - " call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")") - - let s:ngw= s:NetrwGetWord() - call s:NetrwMarkFile(a:islocal,s:ngw) - - " call Dret("s:NetrwSLeftmouse") -endfun - -" --------------------------------------------------------------------- -" s:NetrwSLeftdrag: invoked via a shift-leftmouse and dragging {{{2 -" Used to mark multiple files. -fun! s:NetrwSLeftdrag(islocal) - " call Dfunc("s:NetrwSLeftdrag(islocal=".a:islocal.")") - if !exists("s:netrwdrag") - let s:netrwdrag = winnr() - if a:islocal - nno :call NetrwSLeftrelease(1) - else - nno :call NetrwSLeftrelease(0) - endif - endif - let ngw = s:NetrwGetWord() - if !exists("s:ngw") || s:ngw != ngw - call s:NetrwMarkFile(a:islocal,ngw) - endif - let s:ngw= ngw - " call Dret("s:NetrwSLeftdrag : s:netrwdrag=".s:netrwdrag." buf#".bufnr("%")) -endfun - -" --------------------------------------------------------------------- -" s:NetrwSLeftrelease: terminates shift-leftmouse dragging {{{2 -fun! s:NetrwSLeftrelease(islocal) - " call Dfunc("s:NetrwSLeftrelease(islocal=".a:islocal.") s:netrwdrag=".s:netrwdrag." buf#".bufnr("%")) - if exists("s:netrwdrag") - nunmap - let ngw = s:NetrwGetWord() - if !exists("s:ngw") || s:ngw != ngw - call s:NetrwMarkFile(a:islocal,ngw) - endif - if exists("s:ngw") - unlet s:ngw - endif - unlet s:netrwdrag - endif - " call Dret("s:NetrwSLeftrelease") -endfun - -" --------------------------------------------------------------------- -" s:NetrwListHide: uses [range]g~...~d to delete files that match {{{2 -" comma-separated patterns given in g:netrw_list_hide -fun! s:NetrwListHide() - " call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">") - " call Decho("initial: ".string(getline(w:netrw_bannercnt,'$'))) - let ykeep= @@ - - " find a character not in the "hide" string to use as a separator for :g and :v commands - " How-it-works: take the hiding command, convert it into a range. - " Duplicate characters don't matter. - " Remove all such characters from the '/~@#...890' string. - " Use the first character left as a separator character. - " call Decho("find a character not in the hide string to use as a separator",'~'.expand("")) - let listhide= g:netrw_list_hide - let sep = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1) - " call Decho("sep<".sep."> (sep not in hide string)",'~'.expand("")) - - while listhide != "" - if listhide =~ ',' - let hide = substitute(listhide,',.*$','','e') - let listhide = substitute(listhide,'^.\{-},\(.*\)$','\1','e') - else - let hide = listhide - let listhide = "" - endif - " call Decho("..extracted pattern from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand("")) - if g:netrw_sort_by =~ '^[ts]' - if hide =~ '^\^' - " call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand("")) - let hide= substitute(hide,'^\^','^\(\\d\\+/\)','') - elseif hide =~ '^\\(\^' - let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','') - endif - " call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand("")) - endif - - " Prune the list by hiding any files which match - " call Decho("..prune the list by hiding any files which ".((g:netrw_hide == 1)? "" : "don't")."match hide<".hide.">") - if g:netrw_hide == 1 - " call Decho("..hiding<".hide.">",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d' - elseif g:netrw_hide == 2 - " call Decho("..showing<".hide.">",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @' - endif - " call Decho("..result: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand("")) - endwhile - - if g:netrw_hide == 2 - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d' - " call Decho("..v KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e' - " call Decho("..g KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand("")) - endif - - " remove any blank lines that have somehow remained. - " This seems to happen under Windows. - exe 'sil! NetrwKeepj 1,$g@^\s*$@d' - - let @@= ykeep - " call Dret("s:NetrwListHide") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2 -" implements the "d" mapping. -fun! s:NetrwMakeDir(usrhost) - - let ykeep= @@ - " get name of new directory from user. A bare will skip. - " if its currently a directory, also request will be skipped, but with - " a message. - call inputsave() - let newdirname= input("Please give directory name: ") - call inputrestore() - - if newdirname == "" - let @@= ykeep - return - endif - - if a:usrhost == "" - - " Local mkdir: - " sanity checks - let fullnewdir= b:netrw_curdir.'/'.newdirname - if isdirectory(s:NetrwFile(fullnewdir)) - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24) - endif - let @@= ykeep - return - endif - if s:FileReadable(fullnewdir) - if !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25) - endif - let @@= ykeep - return - endif - - " requested new local directory is neither a pre-existing file or - " directory, so make it! - if exists("*mkdir") - if has("unix") - call mkdir(fullnewdir,"p",xor(0777, system("umask"))) - else - call mkdir(fullnewdir,"p") - endif - else - let netrw_origdir= s:NetrwGetcwd(1) - if s:NetrwLcd(b:netrw_curdir) - return - endif - call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(newdirname,1)) - if v:shell_error != 0 - let @@= ykeep - call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80) - return - endif - if !g:netrw_keepdir - if s:NetrwLcd(netrw_origdir) - return - endif - endif - endif - - if v:shell_error == 0 - " refresh listing - let svpos= winsaveview() - call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) - call winrestview(svpos) - elseif !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",26) - endif - - elseif !exists("b:netrw_method") || b:netrw_method == 4 - " Remote mkdir: using ssh - let mkdircmd = s:MakeSshCmd(g:netrw_mkdir_cmd) - let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname - call s:NetrwExe("sil! !".mkdircmd." ".s:ShellEscape(newdirname,1)) - if v:shell_error == 0 - " refresh listing - let svpos= winsaveview() - NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) - NetrwKeepj call winrestview(svpos) - elseif !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27) - endif - - elseif b:netrw_method == 2 - " Remote mkdir: using ftp+.netrc - let svpos= winsaveview() - if exists("b:netrw_fname") - let remotepath= b:netrw_fname - else - let remotepath= "" - endif - call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"') - NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) - NetrwKeepj call winrestview(svpos) - - elseif b:netrw_method == 3 - " Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc) - let svpos= winsaveview() - if exists("b:netrw_fname") - let remotepath= b:netrw_fname - else - let remotepath= "" - endif - call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"') - NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) - NetrwKeepj call winrestview(svpos) - endif - - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:TreeSqueezeDir: allows a shift-cr (gvim only) to squeeze the current tree-listing directory {{{2 -fun! s:TreeSqueezeDir(islocal) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") - " its a tree-listing style - let curdepth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e') - let stopline = (exists("w:netrw_bannercnt")? (w:netrw_bannercnt + 1) : 1) - let depth = strchars(substitute(curdepth,' ','','g')) - let srch = -1 - if depth >= 2 - NetrwKeepj norm! 0 - let curdepthm1= substitute(curdepth,'^'.s:treedepthstring,'','') - let srch = search('^'.curdepthm1.'\%('.s:treedepthstring.'\)\@!','bW',stopline) - elseif depth == 1 - NetrwKeepj norm! 0 - let treedepthchr= substitute(s:treedepthstring,' ','','') - let srch = search('^[^'.treedepthchr.']','bW',stopline) - endif - if srch > 0 - call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,s:NetrwGetWord(),1)) - exe srch - endif - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMaps: {{{2 -fun! s:NetrwMaps(islocal) - - " mouse maps: {{{3 - if g:netrw_mousemaps && g:netrw_retmap - " call Decho("set up Rexplore 2-leftmouse",'~'.expand("")) - if !hasmapto("NetrwReturn") - if maparg("<2-leftmouse>","n") == "" || maparg("<2-leftmouse>","n") =~ '^-$' - nmap <2-leftmouse> NetrwReturn - elseif maparg("","n") == "" - nmap NetrwReturn - endif - endif - nno NetrwReturn :Rexplore - endif - - " generate default maps {{{3 - if !hasmapto('NetrwHide') |nmap a NetrwHide_a|endif - if !hasmapto('NetrwBrowseUpDir') |nmap - NetrwBrowseUpDir|endif - if !hasmapto('NetrwOpenFile') |nmap % NetrwOpenFile|endif - if !hasmapto('NetrwBadd_cb') |nmap cb NetrwBadd_cb|endif - if !hasmapto('NetrwBadd_cB') |nmap cB NetrwBadd_cB|endif - if !hasmapto('NetrwLcd') |nmap cd NetrwLcd|endif - if !hasmapto('NetrwSetChgwin') |nmap C NetrwSetChgwin|endif - if !hasmapto('NetrwRefresh') |nmap NetrwRefresh|endif - if !hasmapto('NetrwLocalBrowseCheck') |nmap NetrwLocalBrowseCheck|endif - if !hasmapto('NetrwServerEdit') |nmap NetrwServerEdit|endif - if !hasmapto('NetrwMakeDir') |nmap d NetrwMakeDir|endif - if !hasmapto('NetrwBookHistHandler_gb')|nmap gb NetrwBookHistHandler_gb|endif - - if a:islocal - " local normal-mode maps {{{3 - nnoremap NetrwHide_a :call NetrwHide(1) - nnoremap NetrwBrowseUpDir :call NetrwBrowseUpDir(1) - nnoremap NetrwOpenFile :call NetrwOpenFile(1) - nnoremap NetrwBadd_cb :call NetrwBadd(1,0) - nnoremap NetrwBadd_cB :call NetrwBadd(1,1) - nnoremap NetrwLcd :call NetrwLcd(b:netrw_curdir) - nnoremap NetrwSetChgwin :call NetrwSetChgwin() - nnoremap NetrwLocalBrowseCheck :call netrw#LocalBrowseCheck(NetrwBrowseChgDir(1,NetrwGetWord(),1)) - nnoremap NetrwServerEdit :call NetrwServerEdit(3,NetrwGetWord()) - nnoremap NetrwMakeDir :call NetrwMakeDir("") - nnoremap NetrwBookHistHandler_gb :call NetrwBookHistHandler(1,b:netrw_curdir) - " --------------------------------------------------------------------- - nnoremap gd :call NetrwForceChgDir(1,NetrwGetWord()) - nnoremap gf :call NetrwForceFile(1,NetrwGetWord()) - nnoremap gh :call NetrwHidden(1) - nnoremap gn :call netrw#SetTreetop(0,NetrwGetWord()) - nnoremap gp :call NetrwChgPerm(1,b:netrw_curdir) - nnoremap I :call NetrwBannerCtrl(1) - nnoremap i :call NetrwListStyle(1) - nnoremap ma :call NetrwMarkFileArgList(1,0) - nnoremap mA :call NetrwMarkFileArgList(1,1) - nnoremap mb :call NetrwBookHistHandler(0,b:netrw_curdir) - nnoremap mB :call NetrwBookHistHandler(6,b:netrw_curdir) - nnoremap mc :call NetrwMarkFileCopy(1) - nnoremap md :call NetrwMarkFileDiff(1) - nnoremap me :call NetrwMarkFileEdit(1) - nnoremap mf :call NetrwMarkFile(1,NetrwGetWord()) - nnoremap mF :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir) - nnoremap mg :call NetrwMarkFileGrep(1) - nnoremap mh :call NetrwMarkHideSfx(1) - nnoremap mm :call NetrwMarkFileMove(1) - " nnoremap mp :call NetrwMarkFilePrint(1) - nnoremap mr :call NetrwMarkFileRegexp(1) - nnoremap ms :call NetrwMarkFileSource(1) - nnoremap mT :call NetrwMarkFileTag(1) - nnoremap mt :call NetrwMarkFileTgt(1) - nnoremap mu :call NetrwUnMarkFile(1) - nnoremap mv :call NetrwMarkFileVimCmd(1) - nnoremap mx :call NetrwMarkFileExe(1,0) - nnoremap mX :call NetrwMarkFileExe(1,1) - nnoremap mz :call NetrwMarkFileCompress(1) - nnoremap O :call NetrwObtain(1) - nnoremap o :call NetrwSplit(3) - nnoremap p :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1)) - nnoremap P :call NetrwPrevWinOpen(1) - nnoremap qb :call NetrwBookHistHandler(2,b:netrw_curdir) - nnoremap qf :call NetrwFileInfo(1,NetrwGetWord()) - nnoremap qF :call NetrwMarkFileQFEL(1,getqflist()) - nnoremap qL :call NetrwMarkFileQFEL(1,getloclist(v:count)) - nnoremap s :call NetrwSortStyle(1) - nnoremap S :call NetSortSequence(1) - nnoremap Tb :call NetrwSetTgt(1,'b',v:count1) - nnoremap t :call NetrwSplit(4) - nnoremap Th :call NetrwSetTgt(1,'h',v:count) - nnoremap u :call NetrwBookHistHandler(4,expand("%")) - nnoremap U :call NetrwBookHistHandler(5,expand("%")) - nnoremap v :call NetrwSplit(5) - nnoremap x :call netrw#BrowseX(NetrwBrowseChgDir(1,NetrwGetWord(),1,0),0)" - nnoremap X :call NetrwLocalExecute(expand(""))" - - nnoremap r :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwRefresh(1,NetrwBrowseChgDir(1,'./',0)) - if !hasmapto('NetrwHideEdit') - nmap NetrwHideEdit - endif - nnoremap NetrwHideEdit :call NetrwHideEdit(1) - if !hasmapto('NetrwRefresh') - nmap NetrwRefresh - endif - nnoremap NetrwRefresh :call NetrwRefresh(1,NetrwBrowseChgDir(1,(exists("w:netrw_liststyle") && exists("w:netrw_treetop") && w:netrw_liststyle == 3)? w:netrw_treetop : './',0)) - if s:didstarstar || !mapcheck("","n") - nnoremap :Nexplore - endif - if s:didstarstar || !mapcheck("","n") - nnoremap :Pexplore - endif - if !hasmapto('NetrwTreeSqueeze') - nmap NetrwTreeSqueeze - endif - nnoremap NetrwTreeSqueeze :call TreeSqueezeDir(1) - let mapsafecurdir = escape(b:netrw_curdir, s:netrw_map_escape) - if g:netrw_mousemaps == 1 - nmap NetrwLeftmouse - nmap NetrwCLeftmouse - nmap NetrwMiddlemouse - nmap NetrwSLeftmouse - nmap NetrwSLeftdrag - nmap <2-leftmouse> Netrw2Leftmouse - imap ILeftmouse - imap IMiddlemouse - nno NetrwLeftmouse :exec "norm! \leftmouse>"call NetrwLeftmouse(1) - nno NetrwCLeftmouse :exec "norm! \leftmouse>"call NetrwCLeftmouse(1) - nno NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(1) - nno NetrwSLeftmouse :exec "norm! \leftmouse>"call NetrwSLeftmouse(1) - nno NetrwSLeftdrag :exec "norm! \leftmouse>"call NetrwSLeftdrag(1) - nmap Netrw2Leftmouse - - exe 'nnoremap :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")' - exe 'vnoremap :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")' - endif - exe 'nnoremap :call NetrwLocalRm("'.mapsafecurdir.'")' - exe 'nnoremap D :call NetrwLocalRm("'.mapsafecurdir.'")' - exe 'nnoremap R :call NetrwLocalRename("'.mapsafecurdir.'")' - exe 'nnoremap d :call NetrwMakeDir("")' - exe 'vnoremap :call NetrwLocalRm("'.mapsafecurdir.'")' - exe 'vnoremap D :call NetrwLocalRm("'.mapsafecurdir.'")' - exe 'vnoremap R :call NetrwLocalRename("'.mapsafecurdir.'")' - nnoremap :he netrw-quickhelp - - " support user-specified maps - call netrw#UserMaps(1) - - else - " remote normal-mode maps {{{3 - call s:RemotePathAnalysis(b:netrw_curdir) - nnoremap NetrwHide_a :call NetrwHide(0) - nnoremap NetrwBrowseUpDir :call NetrwBrowseUpDir(0) - nnoremap NetrwOpenFile :call NetrwOpenFile(0) - nnoremap NetrwBadd_cb :call NetrwBadd(0,0) - nnoremap NetrwBadd_cB :call NetrwBadd(0,1) - nnoremap NetrwLcd :call NetrwLcd(b:netrw_curdir) - nnoremap NetrwSetChgwin :call NetrwSetChgwin() - nnoremap NetrwRefresh :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0)) - nnoremap NetrwLocalBrowseCheck :call NetrwBrowse(0,NetrwBrowseChgDir(0,NetrwGetWord(),1)) - nnoremap NetrwServerEdit :call NetrwServerEdit(2,NetrwGetWord()) - nnoremap NetrwBookHistHandler_gb :call NetrwBookHistHandler(1,b:netrw_curdir) - " --------------------------------------------------------------------- - nnoremap gd :call NetrwForceChgDir(0,NetrwGetWord()) - nnoremap gf :call NetrwForceFile(0,NetrwGetWord()) - nnoremap gh :call NetrwHidden(0) - nnoremap gp :call NetrwChgPerm(0,b:netrw_curdir) - nnoremap I :call NetrwBannerCtrl(1) - nnoremap i :call NetrwListStyle(0) - nnoremap ma :call NetrwMarkFileArgList(0,0) - nnoremap mA :call NetrwMarkFileArgList(0,1) - nnoremap mb :call NetrwBookHistHandler(0,b:netrw_curdir) - nnoremap mB :call NetrwBookHistHandler(6,b:netrw_curdir) - nnoremap mc :call NetrwMarkFileCopy(0) - nnoremap md :call NetrwMarkFileDiff(0) - nnoremap me :call NetrwMarkFileEdit(0) - nnoremap mf :call NetrwMarkFile(0,NetrwGetWord()) - nnoremap mF :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir) - nnoremap mg :call NetrwMarkFileGrep(0) - nnoremap mh :call NetrwMarkHideSfx(0) - nnoremap mm :call NetrwMarkFileMove(0) - " nnoremap mp :call NetrwMarkFilePrint(0) - nnoremap mr :call NetrwMarkFileRegexp(0) - nnoremap ms :call NetrwMarkFileSource(0) - nnoremap mT :call NetrwMarkFileTag(0) - nnoremap mt :call NetrwMarkFileTgt(0) - nnoremap mu :call NetrwUnMarkFile(0) - nnoremap mv :call NetrwMarkFileVimCmd(0) - nnoremap mx :call NetrwMarkFileExe(0,0) - nnoremap mX :call NetrwMarkFileExe(0,1) - nnoremap mz :call NetrwMarkFileCompress(0) - nnoremap O :call NetrwObtain(0) - nnoremap o :call NetrwSplit(0) - nnoremap p :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1)) - nnoremap P :call NetrwPrevWinOpen(0) - nnoremap qb :call NetrwBookHistHandler(2,b:netrw_curdir) - nnoremap qf :call NetrwFileInfo(0,NetrwGetWord()) - nnoremap qF :call NetrwMarkFileQFEL(0,getqflist()) - nnoremap qL :call NetrwMarkFileQFEL(0,getloclist(v:count)) - nnoremap r :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwBrowse(0,NetrwBrowseChgDir(0,'./',0)) - nnoremap s :call NetrwSortStyle(0) - nnoremap S :call NetSortSequence(0) - nnoremap Tb :call NetrwSetTgt(0,'b',v:count1) - nnoremap t :call NetrwSplit(1) - nnoremap Th :call NetrwSetTgt(0,'h',v:count) - nnoremap u :call NetrwBookHistHandler(4,b:netrw_curdir) - nnoremap U :call NetrwBookHistHandler(5,b:netrw_curdir) - nnoremap v :call NetrwSplit(2) - nnoremap x :call netrw#BrowseX(NetrwBrowseChgDir(0,NetrwGetWord(),1),1) - nmap gx x - if !hasmapto('NetrwHideEdit') - nmap NetrwHideEdit - endif - nnoremap NetrwHideEdit :call NetrwHideEdit(0) - if !hasmapto('NetrwRefresh') - nmap NetrwRefresh - endif - if !hasmapto('NetrwTreeSqueeze') - nmap NetrwTreeSqueeze - endif - nnoremap NetrwTreeSqueeze :call TreeSqueezeDir(0) - - let mapsafepath = escape(s:path, s:netrw_map_escape) - let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape) - - nnoremap NetrwRefresh :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0)) - if g:netrw_mousemaps == 1 - nmap NetrwLeftmouse - nno NetrwLeftmouse :exec "norm! \leftmouse>"call NetrwLeftmouse(0) - nmap NetrwCLeftmouse - nno NetrwCLeftmouse :exec "norm! \leftmouse>"call NetrwCLeftmouse(0) - nmap NetrwSLeftmouse - nno NetrwSLeftmouse :exec "norm! \leftmouse>"call NetrwSLeftmouse(0) - nmap NetrwSLeftdrag - nno NetrwSLeftdrag :exec "norm! \leftmouse>"call NetrwSLeftdrag(0) - nmap NetrwMiddlemouse - nno NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(0) - nmap <2-leftmouse> Netrw2Leftmouse - nmap Netrw2Leftmouse - - imap ILeftmouse - imap IMiddlemouse - imap ISLeftmouse - exe 'nnoremap :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' - exe 'vnoremap :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' - endif - exe 'nnoremap :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' - exe 'nnoremap d :call NetrwMakeDir("'.mapsafeusermach.'")' - exe 'nnoremap D :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' - exe 'nnoremap R :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")' - exe 'vnoremap :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' - exe 'vnoremap D :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' - exe 'vnoremap R :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")' - nnoremap :he netrw-quickhelp - - " support user-specified maps - call netrw#UserMaps(0) - endif " }}}3 -endfun - -" --------------------------------------------------------------------- -" s:NetrwCommands: set up commands {{{2 -" If -buffer, the command is only available from within netrw buffers -" Otherwise, the command is available from any window, so long as netrw -" has been used at least once in the session. -fun! s:NetrwCommands(islocal) - " call Dfunc("s:NetrwCommands(islocal=".a:islocal.")") - - com! -nargs=* -complete=file -bang NetrwMB call s:NetrwBookmark(0,) - com! -nargs=* NetrwC call s:NetrwSetChgwin() - com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"win#".winnr()." not a former netrw window",79)|endif - if a:islocal - com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(1,) - else - com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(0,) - endif - com! -buffer -nargs=? -complete=file MT call s:NetrwMarkTarget() - - " call Dret("s:NetrwCommands") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFiles: apply s:NetrwMarkFile() to named file(s) {{{2 -" glob()ing only works with local files -fun! s:NetrwMarkFiles(islocal,...) - " call Dfunc("s:NetrwMarkFiles(islocal=".a:islocal."...) a:0=".a:0) - let curdir = s:NetrwGetCurdir(a:islocal) - let i = 1 - while i <= a:0 - if a:islocal - if v:version > 704 || (v:version == 704 && has("patch656")) - let mffiles= glob(a:{i},0,1,1) - else - let mffiles= glob(a:{i},0,1) - endif - else - let mffiles= [a:{i}] - endif - " call Decho("mffiles".string(mffiles),'~'.expand("")) - for mffile in mffiles - " call Decho("mffile<".mffile.">",'~'.expand("")) - call s:NetrwMarkFile(a:islocal,mffile) - endfor - let i= i + 1 - endwhile - " call Dret("s:NetrwMarkFiles") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkTarget: implements :MT (mark target) {{{2 -fun! s:NetrwMarkTarget(...) - if a:0 == 0 || (a:0 == 1 && a:1 == "") - let curdir = s:NetrwGetCurdir(1) - let tgt = b:netrw_curdir - else - let curdir = s:NetrwGetCurdir((a:1 =~ '^\a\{3,}://')? 0 : 1) - let tgt = a:1 - endif - let s:netrwmftgt = tgt - let s:netrwmftgt_islocal = tgt !~ '^\a\{3,}://' - let curislocal = b:netrw_curdir !~ '^\a\{3,}://' - let svpos = winsaveview() - call s:NetrwRefresh(curislocal,s:NetrwBrowseChgDir(curislocal,'./',0)) - call winrestview(svpos) -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFile: (invoked by mf) This function is used to both {{{2 -" mark and unmark files. If a markfile list exists, -" then the rename and delete functions will use it instead -" of whatever may happen to be under the cursor at that -" moment. When the mouse and gui are available, -" shift-leftmouse may also be used to mark files. -" -" Creates two lists -" s:netrwmarkfilelist -- holds complete paths to all marked files -" s:netrwmarkfilelist_# -- holds list of marked files in current-buffer's directory (#==bufnr()) -" -" Creates a marked file match string -" s:netrwmarfilemtch_# -- used with 2match to display marked files -" -" Creates a buffer version of islocal -" b:netrw_islocal -fun! s:NetrwMarkFile(islocal,fname) - " call Dfunc("s:NetrwMarkFile(islocal=".a:islocal." fname<".a:fname.">)") - " call Decho("bufnr(%)=".bufnr("%").": ".bufname("%"),'~'.expand("")) - - " sanity check - if empty(a:fname) - " call Dret("s:NetrwMarkFile : empty fname") - return - endif - let curdir = s:NetrwGetCurdir(a:islocal) - - let ykeep = @@ - let curbufnr= bufnr("%") - let leader= '\%(^\|\s\)\zs' - if a:fname =~ '\a$' - let trailer = '\>[@=|\/\*]\=\ze\%( \|\t\|$\)' - else - let trailer = '[@=|\/\*]\=\ze\%( \|\t\|$\)' - endif - - if exists("s:netrwmarkfilelist_".curbufnr) - " markfile list pre-exists - " call Decho("case s:netrwmarkfilelist_".curbufnr." already exists",'~'.expand("")) - " call Decho("starting s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) - " call Decho("starting s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand("")) - let b:netrw_islocal= a:islocal - - if index(s:netrwmarkfilelist_{curbufnr},a:fname) == -1 - " append filename to buffer's markfilelist - " call Decho("append filename<".a:fname."> to local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) - call add(s:netrwmarkfilelist_{curbufnr},a:fname) - let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(a:fname,g:netrw_markfileesc).trailer - - else - " remove filename from buffer's markfilelist - " call Decho("remove filename<".a:fname."> from local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) - call filter(s:netrwmarkfilelist_{curbufnr},'v:val != a:fname') - if s:netrwmarkfilelist_{curbufnr} == [] - " local markfilelist is empty; remove it entirely - " call Decho("markfile list now empty",'~'.expand("")) - call s:NetrwUnmarkList(curbufnr,curdir) - else - " rebuild match list to display markings correctly - " call Decho("rebuild s:netrwmarkfilemtch_".curbufnr,'~'.expand("")) - let s:netrwmarkfilemtch_{curbufnr}= "" - let first = 1 - for fname in s:netrwmarkfilelist_{curbufnr} - if first - let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.leader.escape(fname,g:netrw_markfileesc).trailer - else - let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(fname,g:netrw_markfileesc).trailer - endif - let first= 0 - endfor - " call Decho("ending s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) - endif - endif - - else - " initialize new markfilelist - " call Decho("case: initialize new markfilelist",'~'.expand("")) - - " call Decho("add fname<".a:fname."> to new markfilelist_".curbufnr,'~'.expand("")) - let s:netrwmarkfilelist_{curbufnr}= [] - call add(s:netrwmarkfilelist_{curbufnr},substitute(a:fname,'[|@]$','','')) - " call Decho("ending s:netrwmarkfilelist_{curbufnr}<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) - - " build initial markfile matching pattern - if a:fname =~ '/$' - let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc) - else - let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc).trailer - endif - " call Decho("ending s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand("")) - endif - - " handle global markfilelist - if exists("s:netrwmarkfilelist") - let dname= s:ComposePath(b:netrw_curdir,a:fname) - if index(s:netrwmarkfilelist,dname) == -1 - " append new filename to global markfilelist - call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname)) - " call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand("")) - else - " remove new filename from global markfilelist - " call Decho("remove new filename from global s:markfilelist",'~'.expand("")) - " call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand("")) - call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"') - " call Decho("..ending s:netrwmarkfilelist <".string(s:netrwmarkfilelist).">",'~'.expand("")) - if s:netrwmarkfilelist == [] - " call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand("")) - unlet s:netrwmarkfilelist - endif - endif - else - " initialize new global-directory markfilelist - let s:netrwmarkfilelist= [] - call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname)) - " call Decho("init s:netrwmarkfilelist<".string(s:netrwmarkfilelist).">",'~'.expand("")) - endif - - " set up 2match'ing to netrwmarkfilemtch_# list - if has("syntax") && exists("g:syntax_on") && g:syntax_on - if exists("s:netrwmarkfilemtch_{curbufnr}") && s:netrwmarkfilemtch_{curbufnr} != "" - " " call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/",'~'.expand("")) - if exists("g:did_drchip_netrwlist_syntax") - exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/" - endif - else - " " call Decho("2match none",'~'.expand("")) - 2match none - endif - endif - let @@= ykeep - " call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand("")) - " call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist")."> (buf#".curbufnr."list)") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileArgList: ma: move the marked file list to the argument list (tomflist=0) {{{2 -" mA: move the argument list to marked file list (tomflist=1) -" Uses the global marked file list -fun! s:NetrwMarkFileArgList(islocal,tomflist) - let svpos = winsaveview() - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - if a:tomflist - " mA: move argument list to marked file list - while argc() - let fname= argv(0) - exe "argdel ".fnameescape(fname) - call s:NetrwMarkFile(a:islocal,fname) - endwhile - - else - " ma: move marked file list to argument list - if exists("s:netrwmarkfilelist") - - " for every filename in the marked list - for fname in s:netrwmarkfilelist - exe "argadd ".fnameescape(fname) - endfor " for every file in the marked list - - " unmark list and refresh - call s:NetrwUnmarkList(curbufnr,curdir) - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - endif - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileCompress: (invoked by mz) This function is used to {{{2 -" compress/decompress files using the programs -" in g:netrw_compress and g:netrw_uncompress, -" using g:netrw_compress_suffix to know which to -" do. By default: -" g:netrw_compress = "gzip" -" g:netrw_decompress = { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf", ".xz" : "unxz"} -fun! s:NetrwMarkFileCompress(islocal) - let svpos = winsaveview() - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - return - endif - - if exists("s:netrwmarkfilelist_{curbufnr}") && exists("g:netrw_compress") && exists("g:netrw_decompress") - - " for every filename in the marked list - for fname in s:netrwmarkfilelist_{curbufnr} - let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','') - if exists("g:netrw_decompress['".sfx."']") - " fname has a suffix indicating that its compressed; apply associated decompression routine - let exe= g:netrw_decompress[sfx] - let exe= netrw#WinPath(exe) - if a:islocal - if g:netrw_keepdir - let fname= s:ShellEscape(s:ComposePath(curdir,fname)) - endif - call system(exe." ".fname) - if v:shell_error - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50) - endif - else - let fname= s:ShellEscape(b:netrw_curdir.fname,1) - NetrwKeepj call s:RemoteSystem(exe." ".fname) - endif - - endif - unlet sfx - - if exists("exe") - unlet exe - elseif a:islocal - " fname not a compressed file, so compress it - call system(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,fname))) - if v:shell_error - call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_compress<".g:netrw_compress."> to something that works",104) - endif - else - " fname not a compressed file, so compress it - NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(fname)) - endif - endfor " for every file in the marked list - - call s:NetrwUnmarkList(curbufnr,curdir) - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileCopy: (invoked by mc) copy marked files to target {{{2 -" If no marked files, then set up directory as the -" target. Currently does not support copying entire -" directories. Uses the local-buffer marked file list. -" Returns 1=success (used by NetrwMarkFileMove()) -" 0=failure -fun! s:NetrwMarkFileCopy(islocal,...) - " call Dfunc("s:NetrwMarkFileCopy(islocal=".a:islocal.") target<".(exists("s:netrwmftgt")? s:netrwmftgt : '---')."> a:0=".a:0) - - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - if b:netrw_curdir !~ '/$' - if !exists("b:netrw_curdir") - let b:netrw_curdir= curdir - endif - let b:netrw_curdir= b:netrw_curdir."/" - endif - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - " call Dret("s:NetrwMarkFileCopy") - return - endif - " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) - - if !exists("s:netrwmftgt") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67) - " call Dret("s:NetrwMarkFileCopy 0") - return 0 - endif - " call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand("")) - - if a:islocal && s:netrwmftgt_islocal - " Copy marked files, local directory to local directory - " call Decho("copy from local to local",'~'.expand("")) - if !executable(g:netrw_localcopycmd) - call netrw#ErrorMsg(s:ERROR,"g:netrw_localcopycmd<".g:netrw_localcopycmd."> not executable on your system, aborting",91) - " call Dfunc("s:NetrwMarkFileMove : g:netrw_localcopycmd<".g:netrw_localcopycmd."> n/a!") - return - endif - - " copy marked files while within the same directory (ie. allow renaming) - if s:StripTrailingSlash(simplify(s:netrwmftgt)) == s:StripTrailingSlash(simplify(b:netrw_curdir)) - if len(s:netrwmarkfilelist_{bufnr('%')}) == 1 - " only one marked file - " call Decho("case: only one marked file",'~'.expand("")) - let args = s:ShellEscape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0]) - let oldname = s:netrwmarkfilelist_{bufnr('%')}[0] - elseif a:0 == 1 - " call Decho("case: handling one input argument",'~'.expand("")) - " this happens when the next case was used to recursively call s:NetrwMarkFileCopy() - let args = s:ShellEscape(b:netrw_curdir.a:1) - let oldname = a:1 - else - " copy multiple marked files inside the same directory - " call Decho("case: handling a multiple marked files",'~'.expand("")) - let s:recursive= 1 - for oldname in s:netrwmarkfilelist_{bufnr("%")} - let ret= s:NetrwMarkFileCopy(a:islocal,oldname) - if ret == 0 - break - endif - endfor - unlet s:recursive - call s:NetrwUnmarkList(curbufnr,curdir) - " call Dret("s:NetrwMarkFileCopy ".ret) - return ret - endif - - call inputsave() - let newname= input("Copy ".oldname." to : ",oldname,"file") - call inputrestore() - if newname == "" - " call Dret("s:NetrwMarkFileCopy 0") - return 0 - endif - let args= s:ShellEscape(oldname) - let tgt = s:ShellEscape(s:netrwmftgt.'/'.newname) - else - let args= join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"s:ShellEscape(b:netrw_curdir.\"/\".v:val)")) - let tgt = s:ShellEscape(s:netrwmftgt) - endif - if !g:netrw_cygwin && has("win32") - let args= substitute(args,'/','\\','g') - let tgt = substitute(tgt, '/','\\','g') - endif - if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif - if tgt =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif - if args =~ '//'|let args= substitute(args,'//','/','g')|endif - if tgt =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif - " call Decho("args <".args.">",'~'.expand("")) - " call Decho("tgt <".tgt.">",'~'.expand("")) - if isdirectory(s:NetrwFile(args)) - " call Decho("args<".args."> is a directory",'~'.expand("")) - let copycmd= g:netrw_localcopydircmd - " call Decho("using copydircmd<".copycmd.">",'~'.expand("")) - if !g:netrw_cygwin && has("win32") - " window's xcopy doesn't copy a directory to a target properly. Instead, it copies a directory's - " contents to a target. One must append the source directory name to the target to get xcopy to - " do the right thing. - let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','') - " call Decho("modified tgt for xcopy",'~'.expand("")) - endif - else - let copycmd= g:netrw_localcopycmd - endif - if g:netrw_localcopycmd =~ '\s' - let copycmd = substitute(copycmd,'\s.*$','','') - let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','') - let copycmd = netrw#WinPath(copycmd).copycmdargs - else - let copycmd = netrw#WinPath(copycmd) - endif - " call Decho("args <".args.">",'~'.expand("")) - " call Decho("tgt <".tgt.">",'~'.expand("")) - " call Decho("copycmd<".copycmd.">",'~'.expand("")) - " call Decho("system(".copycmd." '".args."' '".tgt."')",'~'.expand("")) - call system(copycmd.g:netrw_localcopycmdopt." '".args."' '".tgt."'") - if v:shell_error != 0 - if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir - call netrw#ErrorMsg(s:ERROR,"copy failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",101) - else - call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80) - endif - " call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".s:ShellEscape(s:netrwmftgt)) - return 0 - endif - - elseif a:islocal && !s:netrwmftgt_islocal - " Copy marked files, local directory to remote directory - " call Decho("copy from local to remote",'~'.expand("")) - NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt) - - elseif !a:islocal && s:netrwmftgt_islocal - " Copy marked files, remote directory to local directory - " call Decho("copy from remote to local",'~'.expand("")) - NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt) - - elseif !a:islocal && !s:netrwmftgt_islocal - " Copy marked files, remote directory to remote directory - " call Decho("copy from remote to remote",'~'.expand("")) - let curdir = getcwd() - let tmpdir = s:GetTempfile("") - if tmpdir !~ '/' - let tmpdir= curdir."/".tmpdir - endif - if exists("*mkdir") - call mkdir(tmpdir) - else - call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(tmpdir,1)) - if v:shell_error != 0 - call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80) - " call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.s:ShellEscape(tmpdir,1) ) - return - endif - endif - if isdirectory(s:NetrwFile(tmpdir)) - if s:NetrwLcd(tmpdir) - " call Dret("s:NetrwMarkFileCopy : lcd failure") - return - endif - NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir) - let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")') - NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt) - if getcwd() == tmpdir - for fname in s:netrwmarkfilelist_{bufnr('%')} - NetrwKeepj call s:NetrwDelete(fname) - endfor - if s:NetrwLcd(curdir) - " call Dret("s:NetrwMarkFileCopy : lcd failure") - return - endif - if delete(tmpdir,"d") - call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103) - endif - else - if s:NetrwLcd(curdir) - " call Dret("s:NetrwMarkFileCopy : lcd failure") - return - endif - endif - endif - endif - - " ------- - " cleanup - " ------- - " call Decho("cleanup",'~'.expand("")) - " remove markings from local buffer - call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer - " call Decho(" g:netrw_fastbrowse =".g:netrw_fastbrowse,'~'.expand("")) - " call Decho(" s:netrwmftgt =".s:netrwmftgt,'~'.expand("")) - " call Decho(" s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand("")) - " call Decho(" curdir =".curdir,'~'.expand("")) - " call Decho(" a:islocal =".a:islocal,'~'.expand("")) - " call Decho(" curbufnr =".curbufnr,'~'.expand("")) - if exists("s:recursive") - " call Decho(" s:recursive =".s:recursive,'~'.expand("")) - else - " call Decho(" s:recursive =n/a",'~'.expand("")) - endif - " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium) - if g:netrw_fastbrowse <= 1 - NetrwKeepj call s:LocalBrowseRefresh() - else - " refresh local and targets for fast browsing - if !exists("s:recursive") - " remove markings from local buffer - " call Decho(" remove markings from local buffer",'~'.expand("")) - NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir) - endif - - " refresh buffers - if s:netrwmftgt_islocal - " call Decho(" refresh s:netrwmftgt=".s:netrwmftgt,'~'.expand("")) - NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt) - endif - if a:islocal && s:netrwmftgt != curdir - " call Decho(" refresh curdir=".curdir,'~'.expand("")) - NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir) - endif - endif - - " call Dret("s:NetrwMarkFileCopy 1") - return 1 -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileDiff: (invoked by md) This function is used to {{{2 -" invoke vim's diff mode on the marked files. -" Either two or three files can be so handled. -" Uses the global marked file list. -fun! s:NetrwMarkFileDiff(islocal) - " call Dfunc("s:NetrwMarkFileDiff(islocal=".a:islocal.") b:netrw_curdir<".b:netrw_curdir.">") - let curbufnr= bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - " call Dret("s:NetrwMarkFileDiff") - return - endif - let curdir= s:NetrwGetCurdir(a:islocal) - " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) - - if exists("s:netrwmarkfilelist_{".curbufnr."}") - let cnt = 0 - for fname in s:netrwmarkfilelist - let cnt= cnt + 1 - if cnt == 1 - " call Decho("diffthis: fname<".fname.">",'~'.expand("")) - exe "NetrwKeepj e ".fnameescape(fname) - diffthis - elseif cnt == 2 || cnt == 3 - below vsplit - " call Decho("diffthis: ".fname,'~'.expand("")) - exe "NetrwKeepj e ".fnameescape(fname) - diffthis - else - break - endif - endfor - call s:NetrwUnmarkList(curbufnr,curdir) - endif - - " call Dret("s:NetrwMarkFileDiff") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileEdit: (invoked by me) put marked files on arg list and start editing them {{{2 -" Uses global markfilelist -fun! s:NetrwMarkFileEdit(islocal) - " call Dfunc("s:NetrwMarkFileEdit(islocal=".a:islocal.")") - - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - " call Dret("s:NetrwMarkFileEdit") - return - endif - " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) - - if exists("s:netrwmarkfilelist_{curbufnr}") - call s:SetRexDir(a:islocal,curdir) - let flist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)")) - " unmark markedfile list - " call s:NetrwUnmarkList(curbufnr,curdir) - call s:NetrwUnmarkAll() - " call Decho("exe sil args ".flist,'~'.expand("")) - exe "sil args ".flist - endif - echo "(use :bn, :bp to navigate files; :Rex to return)" - - " call Dret("s:NetrwMarkFileEdit") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileQFEL: convert a quickfix-error or location list into a marked file list {{{2 -fun! s:NetrwMarkFileQFEL(islocal,qfel) - " call Dfunc("s:NetrwMarkFileQFEL(islocal=".a:islocal.",qfel)") - call s:NetrwUnmarkAll() - let curbufnr= bufnr("%") - - if !empty(a:qfel) - for entry in a:qfel - let bufnmbr= entry["bufnr"] - " call Decho("bufname(".bufnmbr.")<".bufname(bufnmbr)."> line#".entry["lnum"]." text=".entry["text"],'~'.expand("")) - if !exists("s:netrwmarkfilelist_{curbufnr}") - " call Decho("case: no marked file list",'~'.expand("")) - call s:NetrwMarkFile(a:islocal,bufname(bufnmbr)) - elseif index(s:netrwmarkfilelist_{curbufnr},bufname(bufnmbr)) == -1 - " s:NetrwMarkFile will remove duplicate entries from the marked file list. - " So, this test lets two or more hits on the same pattern to be ignored. - " call Decho("case: ".bufname(bufnmbr)." not currently in marked file list",'~'.expand("")) - call s:NetrwMarkFile(a:islocal,bufname(bufnmbr)) - else - " call Decho("case: ".bufname(bufnmbr)." already in marked file list",'~'.expand("")) - endif - endfor - echo "(use me to edit marked files)" - else - call netrw#ErrorMsg(s:WARNING,"can't convert quickfix error list; its empty!",92) - endif - - " call Dret("s:NetrwMarkFileQFEL") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2 -" mx enbloc=0: Uses the local marked-file list, applies command to each file individually -" mX enbloc=1: Uses the global marked-file list, applies command to entire list -fun! s:NetrwMarkFileExe(islocal,enbloc) - let svpos = winsaveview() - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - if a:enbloc == 0 - " individually apply command to files, one at a time - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - return - endif - - if exists("s:netrwmarkfilelist_{curbufnr}") - " get the command - call inputsave() - let cmd= input("Enter command: ","","file") - call inputrestore() - if cmd == "" - return - endif - - " apply command to marked files, individually. Substitute: filename -> % - " If no %, then append a space and the filename to the command - for fname in s:netrwmarkfilelist_{curbufnr} - if a:islocal - if g:netrw_keepdir - let fname= s:ShellEscape(netrw#WinPath(s:ComposePath(curdir,fname))) - endif - else - let fname= s:ShellEscape(netrw#WinPath(b:netrw_curdir.fname)) - endif - if cmd =~ '%' - let xcmd= substitute(cmd,'%',fname,'g') - else - let xcmd= cmd.' '.fname - endif - if a:islocal - let ret= system(xcmd) - else - let ret= s:RemoteSystem(xcmd) - endif - if v:shell_error < 0 - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54) - break - else - if ret !=# '' - echo "\n" - " skip trailing new line - echo ret[0:-2] - else - echo ret - endif - endif - endfor - - " unmark marked file list - call s:NetrwUnmarkList(curbufnr,curdir) - - " refresh the listing - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - else - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) - endif - - else " apply command to global list of files, en bloc - - call inputsave() - let cmd= input("Enter command: ","","file") - call inputrestore() - if cmd == "" - return - endif - if cmd =~ '%' - let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' '),'g') - else - let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' ') - endif - if a:islocal - call system(cmd) - if v:shell_error < 0 - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54) - endif - else - let ret= s:RemoteSystem(cmd) - endif - call s:NetrwUnmarkAll() - - " refresh the listing - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix -" as the marked file(s) (toggles suffix presence) -" Uses the local marked file list. -fun! s:NetrwMarkHideSfx(islocal) - let svpos = winsaveview() - let curbufnr = bufnr("%") - - " s:netrwmarkfilelist_{curbufnr}: the List of marked files - if exists("s:netrwmarkfilelist_{curbufnr}") - - for fname in s:netrwmarkfilelist_{curbufnr} - " construct suffix pattern - if fname =~ '\.' - let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','') - else - let sfxpat= '^\%(\%(\.\)\@!.\)*$' - endif - " determine if its in the hiding list or not - let inhidelist= 0 - if g:netrw_list_hide != "" - let itemnum = 0 - let hidelist= split(g:netrw_list_hide,',') - for hidepat in hidelist - if sfxpat == hidepat - let inhidelist= 1 - break - endif - let itemnum= itemnum + 1 - endfor - endif - if inhidelist - " remove sfxpat from list - call remove(hidelist,itemnum) - let g:netrw_list_hide= join(hidelist,",") - elseif g:netrw_list_hide != "" - " append sfxpat to non-empty list - let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat - else - " set hiding list to sfxpat - let g:netrw_list_hide= sfxpat - endif - endfor - - " refresh the listing - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - else - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2 -" Uses the local marked-file list. -fun! s:NetrwMarkFileVimCmd(islocal) - let svpos = winsaveview() - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - return - endif - - if exists("s:netrwmarkfilelist_{curbufnr}") - " get the command - call inputsave() - let cmd= input("Enter vim command: ","","file") - call inputrestore() - if cmd == "" - return - endif - - " apply command to marked files. Substitute: filename -> % - " If no %, then append a space and the filename to the command - for fname in s:netrwmarkfilelist_{curbufnr} - if a:islocal - 1split - exe "sil! NetrwKeepj keepalt e ".fnameescape(fname) - exe cmd - exe "sil! keepalt wq!" - else - echo "sorry, \"mv\" not supported yet for remote files" - endif - endfor - - " unmark marked file list - call s:NetrwUnmarkList(curbufnr,curdir) - - " refresh the listing - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - else - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix -" as the marked file(s) (toggles suffix presence) -" Uses the local marked file list. -fun! s:NetrwMarkHideSfx(islocal) - let svpos = winsaveview() - let curbufnr = bufnr("%") - - " s:netrwmarkfilelist_{curbufnr}: the List of marked files - if exists("s:netrwmarkfilelist_{curbufnr}") - - for fname in s:netrwmarkfilelist_{curbufnr} - " construct suffix pattern - if fname =~ '\.' - let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','') - else - let sfxpat= '^\%(\%(\.\)\@!.\)*$' - endif - " determine if its in the hiding list or not - let inhidelist= 0 - if g:netrw_list_hide != "" - let itemnum = 0 - let hidelist= split(g:netrw_list_hide,',') - for hidepat in hidelist - if sfxpat == hidepat - let inhidelist= 1 - break - endif - let itemnum= itemnum + 1 - endfor - endif - if inhidelist - " remove sfxpat from list - call remove(hidelist,itemnum) - let g:netrw_list_hide= join(hidelist,",") - elseif g:netrw_list_hide != "" - " append sfxpat to non-empty list - let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat - else - " set hiding list to sfxpat - let g:netrw_list_hide= sfxpat - endif - endfor - - " refresh the listing - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - else - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileGrep: (invoked by mg) This function applies vimgrep to marked files {{{2 -" Uses the global markfilelist -fun! s:NetrwMarkFileGrep(islocal) - " call Dfunc("s:NetrwMarkFileGrep(islocal=".a:islocal.")") - let svpos = winsaveview() - " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) - let curbufnr = bufnr("%") - let curdir = s:NetrwGetCurdir(a:islocal) - - if exists("s:netrwmarkfilelist") - " call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand("")) - let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)")) - " call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand(""))" - call s:NetrwUnmarkAll() - else - " call Decho('no marked files, using "*"','~'.expand("")) - let netrwmarkfilelist= "*" - endif - - " ask user for pattern - " call Decho("ask user for search pattern",'~'.expand("")) - call inputsave() - let pat= input("Enter pattern: ","") - call inputrestore() - let patbang = "" - if pat =~ '^!' - let patbang = "!" - let pat = strpart(pat,2) - endif - if pat =~ '^\i' - let pat = escape(pat,'/') - let pat = '/'.pat.'/' - else - let nonisi = pat[0] - endif - - " use vimgrep for both local and remote - " call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist,'~'.expand("")) - try - exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist - catch /^Vim\%((\a\+)\)\=:E480/ - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76) - " call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">") - return - endtry - echo "(use :cn, :cp to navigate, :Rex to return)" - - 2match none - " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("")) - NetrwKeepj call winrestview(svpos) - - if exists("nonisi") - " original, user-supplied pattern did not begin with a character from isident - " call Decho("looking for trailing nonisi<".nonisi."> followed by a j, gj, or jg",'~'.expand("")) - if pat =~# nonisi.'j$\|'.nonisi.'gj$\|'.nonisi.'jg$' - call s:NetrwMarkFileQFEL(a:islocal,getqflist()) - endif - endif - - " call Dret("s:NetrwMarkFileGrep") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileMove: (invoked by mm) execute arbitrary command on marked files, one at a time {{{2 -" uses the global marked file list -" s:netrwmfloc= 0: target directory is remote -" = 1: target directory is local -fun! s:NetrwMarkFileMove(islocal) - " call Dfunc("s:NetrwMarkFileMove(islocal=".a:islocal.")") - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - " call Dret("s:NetrwMarkFileMove") - return - endif - " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) - - if !exists("s:netrwmftgt") - NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67) - " call Dret("s:NetrwMarkFileCopy 0") - return 0 - endif - " call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand("")) - - if a:islocal && s:netrwmftgt_islocal - " move: local -> local - " call Decho("move from local to local",'~'.expand("")) - " call Decho("local to local move",'~'.expand("")) - if !executable(g:netrw_localmovecmd) - call netrw#ErrorMsg(s:ERROR,"g:netrw_localmovecmd<".g:netrw_localmovecmd."> not executable on your system, aborting",90) - " call Dfunc("s:NetrwMarkFileMove : g:netrw_localmovecmd<".g:netrw_localmovecmd."> n/a!") - return - endif - let tgt = s:ShellEscape(s:netrwmftgt) - " call Decho("tgt<".tgt.">",'~'.expand("")) - if !g:netrw_cygwin && has("win32") - let tgt= substitute(tgt, '/','\\','g') - " call Decho("windows exception: tgt<".tgt.">",'~'.expand("")) - if g:netrw_localmovecmd =~ '\s' - let movecmd = substitute(g:netrw_localmovecmd,'\s.*$','','') - let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','') - let movecmd = netrw#WinPath(movecmd).movecmdargs - " call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand("")) - else - let movecmd = netrw#WinPath(g:netrw_localmovecmd) - " call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand("")) - endif - else - let movecmd = netrw#WinPath(g:netrw_localmovecmd) - " call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand("")) - endif - for fname in s:netrwmarkfilelist_{bufnr("%")} - if g:netrw_keepdir - " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1 - let fname= b:netrw_curdir."/".fname - endif - if !g:netrw_cygwin && has("win32") - let fname= substitute(fname,'/','\\','g') - endif - " call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand("")) - let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt) - if v:shell_error != 0 - if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir - call netrw#ErrorMsg(s:ERROR,"move failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",100) - else - call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localmovecmd<".g:netrw_localmovecmd.">; it doesn't work!",54) - endif - break - endif - endfor - - elseif a:islocal && !s:netrwmftgt_islocal - " move: local -> remote - " call Decho("move from local to remote",'~'.expand("")) - " call Decho("copy",'~'.expand("")) - let mflist= s:netrwmarkfilelist_{bufnr("%")} - NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) - " call Decho("remove",'~'.expand("")) - for fname in mflist - let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') - let ok = s:NetrwLocalRmFile(b:netrw_curdir,barefname,1) - endfor - unlet mflist - - elseif !a:islocal && s:netrwmftgt_islocal - " move: remote -> local - " call Decho("move from remote to local",'~'.expand("")) - " call Decho("copy",'~'.expand("")) - let mflist= s:netrwmarkfilelist_{bufnr("%")} - NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) - " call Decho("remove",'~'.expand("")) - for fname in mflist - let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') - let ok = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1) - endfor - unlet mflist - - elseif !a:islocal && !s:netrwmftgt_islocal - " move: remote -> remote - " call Decho("move from remote to remote",'~'.expand("")) - " call Decho("copy",'~'.expand("")) - let mflist= s:netrwmarkfilelist_{bufnr("%")} - NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) - " call Decho("remove",'~'.expand("")) - for fname in mflist - let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') - let ok = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1) - endfor - unlet mflist - endif - - " ------- - " cleanup - " ------- - " call Decho("cleanup",'~'.expand("")) - - " remove markings from local buffer - call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer - - " refresh buffers - if !s:netrwmftgt_islocal - " call Decho("refresh netrwmftgt<".s:netrwmftgt.">",'~'.expand("")) - NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt) - endif - if a:islocal - " call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">",'~'.expand("")) - NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir) - endif - if g:netrw_fastbrowse <= 1 - " call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh",'~'.expand("")) - NetrwKeepj call s:LocalBrowseRefresh() - endif - - " call Dret("s:NetrwMarkFileMove") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2 -" using the hardcopy command. Local marked-file list only. -fun! s:NetrwMarkFilePrint(islocal) - " call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")") - let curbufnr= bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - " call Dret("s:NetrwMarkFilePrint") - return - endif - " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) - let curdir= s:NetrwGetCurdir(a:islocal) - - if exists("s:netrwmarkfilelist_{curbufnr}") - let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr} - call s:NetrwUnmarkList(curbufnr,curdir) - for fname in netrwmarkfilelist - if a:islocal - if g:netrw_keepdir - let fname= s:ComposePath(curdir,fname) - endif - else - let fname= curdir.fname - endif - 1split - " the autocmds will handle both local and remote files - " call Decho("exe sil e ".escape(fname,' '),'~'.expand("")) - exe "sil NetrwKeepj e ".fnameescape(fname) - " call Decho("hardcopy",'~'.expand("")) - hardcopy - q - endfor - 2match none - endif - " call Dret("s:NetrwMarkFilePrint") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2 -" files when given a regexp (for which a prompt is -" issued) (matches to name of files). -fun! s:NetrwMarkFileRegexp(islocal) - " call Dfunc("s:NetrwMarkFileRegexp(islocal=".a:islocal.")") - - " get the regular expression - call inputsave() - let regexp= input("Enter regexp: ","","file") - call inputrestore() - - if a:islocal - let curdir= s:NetrwGetCurdir(a:islocal) - " call Decho("curdir<".fnameescape(curdir).">") - " get the matching list of files using local glob() - " call Decho("handle local regexp",'~'.expand("")) - let dirname = escape(b:netrw_curdir,g:netrw_glob_escape) - if v:version > 704 || (v:version == 704 && has("patch656")) - let filelist= glob(s:ComposePath(dirname,regexp),0,1,1) - else - let files = glob(s:ComposePath(dirname,regexp),0,0) - let filelist= split(files,"\n") - endif - " call Decho("files<".string(filelist).">",'~'.expand("")) - - " mark the list of files - for fname in filelist - if fname =~ '^'.fnameescape(curdir) - " call Decho("fname<".substitute(fname,'^'.fnameescape(curdir).'/','','').">",'~'.expand("")) - NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^'.fnameescape(curdir).'/','','')) - else - " call Decho("fname<".fname.">",'~'.expand("")) - NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','','')) - endif - endfor - - else - " call Decho("handle remote regexp",'~'.expand("")) - - " convert displayed listing into a filelist - let eikeep = &ei - let areg = @a - sil NetrwKeepj %y a - setl ei=all ma - " call Decho("setl ei=all ma",'~'.expand("")) - 1split - NetrwKeepj call s:NetrwEnew() - NetrwKeepj call s:NetrwOptionsSafe(a:islocal) - sil NetrwKeepj norm! "ap - NetrwKeepj 2 - let bannercnt= search('^" =====','W') - exe "sil NetrwKeepj 1,".bannercnt."d" - setl bt=nofile - if g:netrw_liststyle == s:LONGLIST - sil NetrwKeepj %s/\s\{2,}\S.*$//e - call histdel("/",-1) - elseif g:netrw_liststyle == s:WIDELIST - sil NetrwKeepj %s/\s\{2,}/\r/ge - call histdel("/",-1) - elseif g:netrw_liststyle == s:TREELIST - exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e' - sil! NetrwKeepj g/^ .*$/d - call histdel("/",-1) - call histdel("/",-1) - endif - " convert regexp into the more usual glob-style format - let regexp= substitute(regexp,'\*','.*','g') - " call Decho("regexp<".regexp.">",'~'.expand("")) - exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d" - call histdel("/",-1) - let filelist= getline(1,line("$")) - q! - for filename in filelist - NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','','')) - endfor - unlet filelist - let @a = areg - let &ei = eikeep - endif - echo " (use me to edit marked files)" - - " call Dret("s:NetrwMarkFileRegexp") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileSource: (invoked by ms) This function sources marked files {{{2 -" Uses the local marked file list. -fun! s:NetrwMarkFileSource(islocal) - " call Dfunc("s:NetrwMarkFileSource(islocal=".a:islocal.")") - let curbufnr= bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - " call Dret("s:NetrwMarkFileSource") - return - endif - " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) - let curdir= s:NetrwGetCurdir(a:islocal) - - if exists("s:netrwmarkfilelist_{curbufnr}") - let netrwmarkfilelist = s:netrwmarkfilelist_{bufnr("%")} - call s:NetrwUnmarkList(curbufnr,curdir) - for fname in netrwmarkfilelist - if a:islocal - if g:netrw_keepdir - let fname= s:ComposePath(curdir,fname) - endif - else - let fname= curdir.fname - endif - " the autocmds will handle sourcing both local and remote files - " call Decho("exe so ".fnameescape(fname),'~'.expand("")) - exe "so ".fnameescape(fname) - endfor - 2match none - endif - " call Dret("s:NetrwMarkFileSource") -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileTag: (invoked by mT) This function applies g:netrw_ctags to marked files {{{2 -" Uses the global markfilelist -fun! s:NetrwMarkFileTag(islocal) - let svpos = winsaveview() - let curdir = s:NetrwGetCurdir(a:islocal) - let curbufnr = bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) - return - endif - - if exists("s:netrwmarkfilelist") - let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "s:ShellEscape(v:val,".!a:islocal.")")) - call s:NetrwUnmarkAll() - - if a:islocal - - call system(g:netrw_ctags." ".netrwmarkfilelist) - if v:shell_error - call netrw#ErrorMsg(s:ERROR,"g:netrw_ctags<".g:netrw_ctags."> is not executable!",51) - endif - - else - let cmd = s:RemoteSystem(g:netrw_ctags." ".netrwmarkfilelist) - call netrw#Obtain(a:islocal,"tags") - let curdir= b:netrw_curdir - 1split - NetrwKeepj e tags - let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','') - exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e' - call histdel("/",-1) - wq! - endif - 2match none - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - call winrestview(svpos) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwMarkFileTgt: (invoked by mt) This function sets up a marked file target {{{2 -" Sets up two variables, -" s:netrwmftgt : holds the target directory -" s:netrwmftgt_islocal : 0=target directory is remote -" 1=target directory is local -fun! s:NetrwMarkFileTgt(islocal) - let svpos = winsaveview() - let curdir = s:NetrwGetCurdir(a:islocal) - let hadtgt = exists("s:netrwmftgt") - if !exists("w:netrw_bannercnt") - let w:netrw_bannercnt= b:netrw_bannercnt - endif - - " set up target - if line(".") < w:netrw_bannercnt - " if cursor in banner region, use b:netrw_curdir for the target unless its already the target - if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") && s:netrwmftgt == b:netrw_curdir - unlet s:netrwmftgt s:netrwmftgt_islocal - if g:netrw_fastbrowse <= 1 - call s:LocalBrowseRefresh() - endif - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - call winrestview(svpos) - return - else - let s:netrwmftgt= b:netrw_curdir - endif - - else - " get word under cursor. - " * If directory, use it for the target. - " * If file, use b:netrw_curdir for the target - let curword= s:NetrwGetWord() - let tgtdir = s:ComposePath(curdir,curword) - if a:islocal && isdirectory(s:NetrwFile(tgtdir)) - let s:netrwmftgt = tgtdir - elseif !a:islocal && tgtdir =~ '/$' - let s:netrwmftgt = tgtdir - else - let s:netrwmftgt = curdir - endif - endif - if a:islocal - " simplify the target (eg. /abc/def/../ghi -> /abc/ghi) - let s:netrwmftgt= simplify(s:netrwmftgt) - endif - if g:netrw_cygwin - let s:netrwmftgt= substitute(system("cygpath ".s:ShellEscape(s:netrwmftgt)),'\n$','','') - let s:netrwmftgt= substitute(s:netrwmftgt,'\n$','','') - endif - let s:netrwmftgt_islocal= a:islocal - - " need to do refresh so that the banner will be updated - " s:LocalBrowseRefresh handles all local-browsing buffers when not fast browsing - if g:netrw_fastbrowse <= 1 - call s:LocalBrowseRefresh() - endif - " call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,w:netrw_treetop,0)) - else - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - endif - call winrestview(svpos) - if !hadtgt - sil! NetrwKeepj norm! j - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwGetCurdir: gets current directory and sets up b:netrw_curdir if necessary {{{2 -fun! s:NetrwGetCurdir(islocal) - " call Dfunc("s:NetrwGetCurdir(islocal=".a:islocal.")") - - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - let b:netrw_curdir = s:NetrwTreePath(w:netrw_treetop) - " call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used s:NetrwTreeDir)",'~'.expand("")) - elseif !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - " call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used getcwd)",'~'.expand("")) - endif - - " call Decho("b:netrw_curdir<".b:netrw_curdir."> ".((b:netrw_curdir !~ '\<\a\{3,}://')? "does not match" : "matches")." url pattern",'~'.expand("")) - if b:netrw_curdir !~ '\<\a\{3,}://' - let curdir= b:netrw_curdir - " call Decho("g:netrw_keepdir=".g:netrw_keepdir,'~'.expand("")) - if g:netrw_keepdir == 0 - call s:NetrwLcd(curdir) - endif - endif - - " call Dret("s:NetrwGetCurdir <".curdir.">") - return b:netrw_curdir -endfun - -" --------------------------------------------------------------------- -" s:NetrwOpenFile: query user for a filename and open it {{{2 -fun! s:NetrwOpenFile(islocal) - " call Dfunc("s:NetrwOpenFile(islocal=".a:islocal.")") - let ykeep= @@ - call inputsave() - let fname= input("Enter filename: ") - call inputrestore() - " call Decho("(s:NetrwOpenFile) fname<".fname.">",'~'.expand("")) - - " determine if Lexplore is in use - if exists("t:netrw_lexbufnr") - " check if t:netrw_lexbufnr refers to a netrw window - " call Decho("(s:netrwOpenFile) ..t:netrw_lexbufnr=".t:netrw_lexbufnr,'~'.expand("")) - let lexwinnr = bufwinnr(t:netrw_lexbufnr) - if lexwinnr != -1 && exists("g:netrw_chgwin") && g:netrw_chgwin != -1 - " call Decho("(s:netrwOpenFile) ..Lexplore in use",'~'.expand("")) - exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w" - exe "NetrwKeepj e ".fnameescape(fname) - let @@= ykeep - " call Dret("s:NetrwOpenFile : creating a file with Lexplore mode") - endif - endif - - " Does the filename contain a path? - if fname !~ '[/\\]' - if exists("b:netrw_curdir") - if exists("g:netrw_quiet") - let netrw_quiet_keep = g:netrw_quiet - endif - let g:netrw_quiet = 1 - " save position for benefit of Rexplore - let s:rexposn_{bufnr("%")}= winsaveview() - " call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand("")) - if b:netrw_curdir =~ '/$' - exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname) - else - exe "e ".fnameescape(b:netrw_curdir."/".fname) - endif - if exists("netrw_quiet_keep") - let g:netrw_quiet= netrw_quiet_keep - else - unlet g:netrw_quiet - endif - endif - else - exe "NetrwKeepj e ".fnameescape(fname) - endif - let @@= ykeep - " call Dret("s:NetrwOpenFile") -endfun - -" --------------------------------------------------------------------- -" netrw#Shrink: shrinks/expands a netrw or Lexplorer window {{{2 -" For the mapping to this function be made via -" netrwPlugin, you'll need to have had -" g:netrw_usetab set to non-zero. -fun! netrw#Shrink() - " call Dfunc("netrw#Shrink() ft<".&ft."> winwidth=".winwidth(0)." lexbuf#".((exists("t:netrw_lexbufnr"))? t:netrw_lexbufnr : 'n/a')) - let curwin = winnr() - let wiwkeep = &wiw - set wiw=1 - - if &ft == "netrw" - if winwidth(0) > g:netrw_wiw - let t:netrw_winwidth= winwidth(0) - exe "vert resize ".g:netrw_wiw - wincmd l - if winnr() == curwin - wincmd h - endif - " call Decho("vert resize 0",'~'.expand("")) - else - exe "vert resize ".t:netrw_winwidth - " call Decho("vert resize ".t:netrw_winwidth,'~'.expand("")) - endif - - elseif exists("t:netrw_lexbufnr") - exe bufwinnr(t:netrw_lexbufnr)."wincmd w" - if winwidth(bufwinnr(t:netrw_lexbufnr)) > g:netrw_wiw - let t:netrw_winwidth= winwidth(0) - exe "vert resize ".g:netrw_wiw - wincmd l - if winnr() == curwin - wincmd h - endif - " call Decho("vert resize 0",'~'.expand("")) - elseif winwidth(bufwinnr(t:netrw_lexbufnr)) >= 0 - exe "vert resize ".t:netrw_winwidth - " call Decho("vert resize ".t:netrw_winwidth,'~'.expand("")) - else - call netrw#Lexplore(0,0) - endif - - else - call netrw#Lexplore(0,0) - endif - let wiw= wiwkeep - - " call Dret("netrw#Shrink") -endfun - -" --------------------------------------------------------------------- -" s:NetSortSequence: allows user to edit the sorting sequence {{{2 -fun! s:NetSortSequence(islocal) - let ykeep= @@ - let svpos= winsaveview() - call inputsave() - let newsortseq= input("Edit Sorting Sequence: ",g:netrw_sort_sequence) - call inputrestore() - - " refresh the listing - let g:netrw_sort_sequence= newsortseq - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwUnmarkList: delete local marked file list and remove their contents from the global marked-file list {{{2 -" User access provided by the mapping. (see :help netrw-mF) -" Used by many MarkFile functions. -fun! s:NetrwUnmarkList(curbufnr,curdir) - " call Dfunc("s:NetrwUnmarkList(curbufnr=".a:curbufnr." curdir<".a:curdir.">)") - - " remove all files in local marked-file list from global list - if exists("s:netrwmarkfilelist") - for mfile in s:netrwmarkfilelist_{a:curbufnr} - let dfile = s:ComposePath(a:curdir,mfile) " prepend directory to mfile - let idx = index(s:netrwmarkfilelist,dfile) " get index in list of dfile - call remove(s:netrwmarkfilelist,idx) " remove from global list - endfor - if s:netrwmarkfilelist == [] - unlet s:netrwmarkfilelist - endif - - " getting rid of the local marked-file lists is easy - unlet s:netrwmarkfilelist_{a:curbufnr} - endif - if exists("s:netrwmarkfilemtch_{a:curbufnr}") - unlet s:netrwmarkfilemtch_{a:curbufnr} - endif - 2match none - " call Dret("s:NetrwUnmarkList") -endfun - -" --------------------------------------------------------------------- -" s:NetrwUnmarkAll: remove the global marked file list and all local ones {{{2 -fun! s:NetrwUnmarkAll() - " call Dfunc("s:NetrwUnmarkAll()") - if exists("s:netrwmarkfilelist") - unlet s:netrwmarkfilelist - endif - sil call s:NetrwUnmarkAll2() - 2match none - " call Dret("s:NetrwUnmarkAll") -endfun - -" --------------------------------------------------------------------- -" s:NetrwUnmarkAll2: unmark all files from all buffers {{{2 -fun! s:NetrwUnmarkAll2() - " call Dfunc("s:NetrwUnmarkAll2()") - redir => netrwmarkfilelist_let - let - redir END - let netrwmarkfilelist_list= split(netrwmarkfilelist_let,'\n') " convert let string into a let list - call filter(netrwmarkfilelist_list,"v:val =~ '^s:netrwmarkfilelist_'") " retain only those vars that start as s:netrwmarkfilelist_ - call map(netrwmarkfilelist_list,"substitute(v:val,'\\s.*$','','')") " remove what the entries are equal to - for flist in netrwmarkfilelist_list - let curbufnr= substitute(flist,'s:netrwmarkfilelist_','','') - unlet s:netrwmarkfilelist_{curbufnr} - unlet s:netrwmarkfilemtch_{curbufnr} - endfor - " call Dret("s:NetrwUnmarkAll2") -endfun - -" --------------------------------------------------------------------- -" s:NetrwUnMarkFile: called via mu map; unmarks *all* marked files, both global and buffer-local {{{2 -" -" Marked files are in two types of lists: -" s:netrwmarkfilelist -- holds complete paths to all marked files -" s:netrwmarkfilelist_# -- holds list of marked files in current-buffer's directory (#==bufnr()) -" -" Marked files suitable for use with 2match are in: -" s:netrwmarkfilemtch_# -- used with 2match to display marked files -fun! s:NetrwUnMarkFile(islocal) - let svpos = winsaveview() - let curbufnr = bufnr("%") - - " unmark marked file list - " (although I expect s:NetrwUpload() to do it, I'm just making sure) - if exists("s:netrwmarkfilelist") - " " call Decho("unlet'ing: s:netrwmarkfilelist",'~'.expand("")) - unlet s:netrwmarkfilelist - endif - - let ibuf= 1 - while ibuf < bufnr("$") - if exists("s:netrwmarkfilelist_".ibuf) - unlet s:netrwmarkfilelist_{ibuf} - unlet s:netrwmarkfilemtch_{ibuf} - endif - let ibuf = ibuf + 1 - endwhile - 2match none - - " call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - call winrestview(svpos) -endfun - -" --------------------------------------------------------------------- -" s:NetrwMenu: generates the menu for gvim and netrw {{{2 -fun! s:NetrwMenu(domenu) - - if !exists("g:NetrwMenuPriority") - let g:NetrwMenuPriority= 80 - endif - - if has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu - " call Dfunc("NetrwMenu(domenu=".a:domenu.")") - - if !exists("s:netrw_menu_enabled") && a:domenu - " call Decho("initialize menu",'~'.expand("")) - let s:netrw_menu_enabled= 1 - exe 'sil! menu '.g:NetrwMenuPriority.'.1 '.g:NetrwTopLvlMenu.'Help ' - exe 'sil! menu '.g:NetrwMenuPriority.'.5 '.g:NetrwTopLvlMenu.'-Sep1- :' - exe 'sil! menu '.g:NetrwMenuPriority.'.6 '.g:NetrwTopLvlMenu.'Go\ Up\ Directory- -' - exe 'sil! menu '.g:NetrwMenuPriority.'.7 '.g:NetrwTopLvlMenu.'Apply\ Special\ Viewerx x' - if g:netrw_dirhistmax > 0 - exe 'sil! menu '.g:NetrwMenuPriority.'.8.1 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Current\ Directorymb mb' - exe 'sil! menu '.g:NetrwMenuPriority.'.8.4 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Prev\ Dir\ (History)u u' - exe 'sil! menu '.g:NetrwMenuPriority.'.8.5 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Next\ Dir\ (History)U U' - exe 'sil! menu '.g:NetrwMenuPriority.'.8.6 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Listqb qb' - else - exe 'sil! menu '.g:NetrwMenuPriority.'.8 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History :echo "(disabled)"'."\" - endif - exe 'sil! menu '.g:NetrwMenuPriority.'.9.1 '.g:NetrwTopLvlMenu.'Browsing\ Control.Horizontal\ Splito o' - exe 'sil! menu '.g:NetrwMenuPriority.'.9.2 '.g:NetrwTopLvlMenu.'Browsing\ Control.Vertical\ Splitv v' - exe 'sil! menu '.g:NetrwMenuPriority.'.9.3 '.g:NetrwTopLvlMenu.'Browsing\ Control.New\ Tabt t' - exe 'sil! menu '.g:NetrwMenuPriority.'.9.4 '.g:NetrwTopLvlMenu.'Browsing\ Control.Previewp p' - exe 'sil! menu '.g:NetrwMenuPriority.'.9.5 '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ File\ Hiding\ List'." \'" - exe 'sil! menu '.g:NetrwMenuPriority.'.9.6 '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ Sorting\ SequenceS S' - exe 'sil! menu '.g:NetrwMenuPriority.'.9.7 '.g:NetrwTopLvlMenu.'Browsing\ Control.Quick\ Hide/Unhide\ Dot\ Files'."gh gh" - exe 'sil! menu '.g:NetrwMenuPriority.'.9.8 '.g:NetrwTopLvlMenu.'Browsing\ Control.Refresh\ Listing'." \" - exe 'sil! menu '.g:NetrwMenuPriority.'.9.9 '.g:NetrwTopLvlMenu.'Browsing\ Control.Settings/Options:NetrwSettings '.":NetrwSettings\" - exe 'sil! menu '.g:NetrwMenuPriority.'.10 '.g:NetrwTopLvlMenu.'Delete\ File/DirectoryD D' - exe 'sil! menu '.g:NetrwMenuPriority.'.11.1 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Create\ New\ File% %' - exe 'sil! menu '.g:NetrwMenuPriority.'.11.1 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Current\ Window '."\" - exe 'sil! menu '.g:NetrwMenuPriority.'.11.2 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directoryp p' - exe 'sil! menu '.g:NetrwMenuPriority.'.11.3 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ WindowP P' - exe 'sil! menu '.g:NetrwMenuPriority.'.11.4 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Windowo o' - exe 'sil! menu '.g:NetrwMenuPriority.'.11.5 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tabt t' - exe 'sil! menu '.g:NetrwMenuPriority.'.11.5 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Windowv v' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.1 '.g:NetrwTopLvlMenu.'Explore.Directory\ Name :Explore ' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.2 '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only):Explore\ */ :Explore */' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.2 '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (+subdirs):Explore\ **/ :Explore **/' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.3 '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (curdir\ only):Explore\ *// :Explore *//' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.4 '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (+subdirs):Explore\ **// :Explore **//' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.4 '.g:NetrwTopLvlMenu.'Explore.Next\ Match:Nexplore :Nexplore' - exe 'sil! menu '.g:NetrwMenuPriority.'.12.4 '.g:NetrwTopLvlMenu.'Explore.Prev\ Match:Pexplore :Pexplore' - exe 'sil! menu '.g:NetrwMenuPriority.'.13 '.g:NetrwTopLvlMenu.'Make\ Subdirectoryd d' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.1 '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Filemf mf' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.2 '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Files\ by\ Regexpmr mr' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.3 '.g:NetrwTopLvlMenu.'Marked\ Files.Hide-Show-List\ Controla a' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.4 '.g:NetrwTopLvlMenu.'Marked\ Files.Copy\ To\ Targetmc mc' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.5 '.g:NetrwTopLvlMenu.'Marked\ Files.DeleteD D' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.6 '.g:NetrwTopLvlMenu.'Marked\ Files.Diffmd md' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.7 '.g:NetrwTopLvlMenu.'Marked\ Files.Editme me' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.8 '.g:NetrwTopLvlMenu.'Marked\ Files.Exe\ Cmdmx mx' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.9 '.g:NetrwTopLvlMenu.'Marked\ Files.Move\ To\ Targetmm mm' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.10 '.g:NetrwTopLvlMenu.'Marked\ Files.ObtainO O' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.11 '.g:NetrwTopLvlMenu.'Marked\ Files.Printmp mp' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.12 '.g:NetrwTopLvlMenu.'Marked\ Files.ReplaceR R' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.13 '.g:NetrwTopLvlMenu.'Marked\ Files.Set\ Targetmt mt' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.14 '.g:NetrwTopLvlMenu.'Marked\ Files.TagmT mT' - exe 'sil! menu '.g:NetrwMenuPriority.'.14.15 '.g:NetrwTopLvlMenu.'Marked\ Files.Zip/Unzip/Compress/Uncompressmz mz' - exe 'sil! menu '.g:NetrwMenuPriority.'.15 '.g:NetrwTopLvlMenu.'Obtain\ FileO O' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.thini :let w:netrw_liststyle=0' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.longi :let w:netrw_liststyle=1' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.widei :let w:netrw_liststyle=2' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.treei :let w:netrw_liststyle=3' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.1 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Show\ Alla :let g:netrw_hide=0' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.3 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Normala :let g:netrw_hide=1' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.2 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Hidden\ Onlya :let g:netrw_hide=2' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.3 '.g:NetrwTopLvlMenu.'Style.Reverse\ Sorting\ Order'."r r" - exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.1 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Names :let g:netrw_sort_by="name"' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.2 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Times :let g:netrw_sort_by="time"' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Sizes :let g:netrw_sort_by="size"' - exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Extens :let g:netrw_sort_by="exten"' - exe 'sil! menu '.g:NetrwMenuPriority.'.17 '.g:NetrwTopLvlMenu.'Rename\ File/DirectoryR R' - exe 'sil! menu '.g:NetrwMenuPriority.'.18 '.g:NetrwTopLvlMenu.'Set\ Current\ Directoryc c' - let s:netrw_menucnt= 28 - call s:NetrwBookmarkMenu() " provide some history! uses priorities 2,3, reserves 4, 8.2.x - call s:NetrwTgtMenu() " let bookmarks and history be easy targets - - elseif !a:domenu - let s:netrwcnt = 0 - let curwin = winnr() - windo if getline(2) =~# "Netrw" | let s:netrwcnt= s:netrwcnt + 1 | endif - exe curwin."wincmd w" - - if s:netrwcnt <= 1 - " call Decho("clear menus",'~'.expand("")) - exe 'sil! unmenu '.g:NetrwTopLvlMenu - " call Decho('exe sil! unmenu '.g:NetrwTopLvlMenu.'*','~'.expand("")) - sil! unlet s:netrw_menu_enabled - endif - endif - " call Dret("NetrwMenu") - return - endif - -endfun - -" --------------------------------------------------------------------- -" s:NetrwObtain: obtain file under cursor or from markfile list {{{2 -" Used by the O maps (as NetrwObtain()) -fun! s:NetrwObtain(islocal) - " call Dfunc("NetrwObtain(islocal=".a:islocal.")") - - let ykeep= @@ - if exists("s:netrwmarkfilelist_{bufnr('%')}") - let islocal= s:netrwmarkfilelist_{bufnr('%')}[1] !~ '^\a\{3,}://' - call netrw#Obtain(islocal,s:netrwmarkfilelist_{bufnr('%')}) - call s:NetrwUnmarkList(bufnr('%'),b:netrw_curdir) - else - call netrw#Obtain(a:islocal,s:NetrwGetWord()) - endif - let @@= ykeep - - " call Dret("NetrwObtain") -endfun - -" --------------------------------------------------------------------- -" s:NetrwPrevWinOpen: open file/directory in previous window. {{{2 -" If there's only one window, then the window will first be split. -" Returns: -" choice = 0 : didn't have to choose -" choice = 1 : saved modified file in window first -" choice = 2 : didn't save modified file, opened window -" choice = 3 : cancel open -fun! s:NetrwPrevWinOpen(islocal) - let ykeep= @@ - " grab a copy of the b:netrw_curdir to pass it along to newly split windows - let curdir = b:netrw_curdir - - " get last window number and the word currently under the cursor - let origwin = winnr() - let lastwinnr = winnr("$") - let curword = s:NetrwGetWord() - let choice = 0 - let s:prevwinopen= 1 " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen) - let s:treedir = s:NetrwTreeDir(a:islocal) - let curdir = s:treedir - - let didsplit = 0 - if lastwinnr == 1 - " if only one window, open a new one first - " g:netrw_preview=0: preview window shown in a horizontally split window - " g:netrw_preview=1: preview window shown in a vertically split window - if g:netrw_preview - " vertically split preview window - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize - exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s" - else - " horizontally split preview window - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize - exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s" - endif - let didsplit = 1 - - else - NetrwKeepj call s:SaveBufVars() - let eikeep= &ei - setl ei=all - wincmd p - - if exists("s:lexplore_win") && s:lexplore_win == winnr() - " whoops -- user trying to open file in the Lexplore window. - " Use Lexplore's opening-file window instead. - " exe g:netrw_chgwin."wincmd w" - wincmd p - call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) - endif - - " prevwinnr: the window number of the "prev" window - " prevbufnr: the buffer number of the buffer in the "prev" window - " bnrcnt : the qty of windows open on the "prev" buffer - let prevwinnr = winnr() - let prevbufnr = bufnr("%") - let prevbufname = bufname("%") - let prevmod = &mod - let bnrcnt = 0 - NetrwKeepj call s:RestoreBufVars() - - " if the previous window's buffer has been changed (ie. its modified flag is set), - " and it doesn't appear in any other extant window, then ask the - " user if s/he wants to abandon modifications therein. - if prevmod - windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif - exe prevwinnr."wincmd w" - - if bnrcnt == 1 && &hidden == 0 - " only one copy of the modified buffer in a window, and - " hidden not set, so overwriting will lose the modified file. Ask first... - let choice = confirm("Save modified buffer<".prevbufname."> first?","&Yes\n&No\n&Cancel") - let &ei= eikeep - - if choice == 1 - " Yes -- write file & then browse - let v:errmsg= "" - sil w - if v:errmsg != "" - call netrw#ErrorMsg(s:ERROR,"unable to write <".(exists("prevbufname")? prevbufname : 'n/a').">!",30) - exe origwin."wincmd w" - let &ei = eikeep - let @@ = ykeep - return choice - endif - - elseif choice == 2 - " No -- don't worry about changed file, just browse anyway - echomsg "**note** changes to ".prevbufname." abandoned" - - else - " Cancel -- don't do this - exe origwin."wincmd w" - let &ei= eikeep - let @@ = ykeep - return choice - endif - endif - endif - let &ei= eikeep - endif - - " restore b:netrw_curdir (window split/enew may have lost it) - let b:netrw_curdir= curdir - if a:islocal < 2 - if a:islocal - call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(a:islocal,curword,0)) - else - call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,curword,0)) - endif - endif - let @@= ykeep - return choice -endfun - -" --------------------------------------------------------------------- -" s:NetrwUpload: load fname to tgt (used by NetrwMarkFileCopy()) {{{2 -" Always assumed to be local -> remote -" call s:NetrwUpload(filename, target) -" call s:NetrwUpload(filename, target, fromdirectory) -fun! s:NetrwUpload(fname,tgt,...) - " call Dfunc("s:NetrwUpload(fname<".((type(a:fname) == 1)? a:fname : string(a:fname))."> tgt<".a:tgt.">) a:0=".a:0) - - if a:tgt =~ '^\a\{3,}://' - let tgtdir= substitute(a:tgt,'^\a\{3,}://[^/]\+/\(.\{-}\)$','\1','') - else - let tgtdir= substitute(a:tgt,'^\(.*\)/[^/]*$','\1','') - endif - " call Decho("tgtdir<".tgtdir.">",'~'.expand("")) - - if a:0 > 0 - let fromdir= a:1 - else - let fromdir= getcwd() - endif - " call Decho("fromdir<".fromdir.">",'~'.expand("")) - - if type(a:fname) == 1 - " handle uploading a single file using NetWrite - " call Decho("handle uploading a single file via NetWrite",'~'.expand("")) - 1split - " call Decho("exe e ".fnameescape(s:NetrwFile(a:fname)),'~'.expand("")) - exe "NetrwKeepj e ".fnameescape(s:NetrwFile(a:fname)) - " call Decho("now locally editing<".expand("%").">, has ".line("$")." lines",'~'.expand("")) - if a:tgt =~ '/$' - let wfname= substitute(a:fname,'^.*/','','') - " call Decho("exe w! ".fnameescape(wfname),'~'.expand("")) - exe "w! ".fnameescape(a:tgt.wfname) - else - " call Decho("writing local->remote: exe w ".fnameescape(a:tgt),'~'.expand("")) - exe "w ".fnameescape(a:tgt) - " call Decho("done writing local->remote",'~'.expand("")) - endif - q! - - elseif type(a:fname) == 3 - " handle uploading a list of files via scp - " call Decho("handle uploading a list of files via scp",'~'.expand("")) - let curdir= getcwd() - if a:tgt =~ '^scp:' - if s:NetrwLcd(fromdir) - " call Dret("s:NetrwUpload : lcd failure") - return - endif - let filelist= deepcopy(s:netrwmarkfilelist_{bufnr('%')}) - let args = join(map(filelist,"s:ShellEscape(v:val, 1)")) - if exists("g:netrw_port") && g:netrw_port != "" - let useport= " ".g:netrw_scpport." ".g:netrw_port - else - let useport= "" - endif - let machine = substitute(a:tgt,'^scp://\([^/:]\+\).*$','\1','') - let tgt = substitute(a:tgt,'^scp://[^/]\+/\(.*\)$','\1','') - call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".args." ".s:ShellEscape(machine.":".tgt,1)) - if s:NetrwLcd(curdir) - " call Dret("s:NetrwUpload : lcd failure") - return - endif - - elseif a:tgt =~ '^ftp:' - call s:NetrwMethod(a:tgt) - - if b:netrw_method == 2 - " handle uploading a list of files via ftp+.netrc - let netrw_fname = b:netrw_fname - sil NetrwKeepj new - " call Decho("filter input window#".winnr(),'~'.expand("")) - - NetrwKeepj put =g:netrw_ftpmode - " call Decho("filter input: ".getline('$'),'~'.expand("")) - - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - - if tgtdir == "" - let tgtdir= '/' - endif - NetrwKeepj call setline(line("$")+1,'cd "'.tgtdir.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - - for fname in a:fname - NetrwKeepj call setline(line("$")+1,'put "'.s:NetrwFile(fname).'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endfor - - if exists("g:netrw_port") && g:netrw_port != "" - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) - else - " call Decho("filter input window#".winnr(),'~'.expand("")) - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) - endif - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - sil NetrwKeepj g/Local directory now/d - call histdel("/",-1) - if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' - call netrw#ErrorMsg(s:ERROR,getline(1),14) - else - bw!|q - endif - - elseif b:netrw_method == 3 - " upload with ftp + machine, id, passwd, and fname (ie. no .netrc) - let netrw_fname= b:netrw_fname - NetrwKeepj call s:SaveBufVars()|sil NetrwKeepj new|NetrwKeepj call s:RestoreBufVars() - let tmpbufnr= bufnr("%") - setl ff=unix - - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - " call Decho("filter input: ".getline('$'),'~'.expand("")) - else - NetrwKeepj put ='open '.g:netrw_machine - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("g:netrw_uid") && g:netrw_uid != "" - if exists("g:netrw_ftp") && g:netrw_ftp == 1 - NetrwKeepj put =g:netrw_uid - " call Decho("filter input: ".getline('$'),'~'.expand("")) - if exists("s:netrw_passwd") - NetrwKeepj call setline(line("$")+1,'"'.s:netrw_passwd.'"') - endif - " call Decho("filter input: ".getline('$'),'~'.expand("")) - elseif exists("s:netrw_passwd") - NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - endif - - NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - - if exists("b:netrw_fname") && b:netrw_fname != "" - NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endif - - for fname in a:fname - NetrwKeepj call setline(line("$")+1,'put "'.fname.'"') - " call Decho("filter input: ".getline('$'),'~'.expand("")) - endfor - - " perform ftp: - " -i : turns off interactive prompting from ftp - " -n unix : DON'T use <.netrc>, even though it exists - " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1G"_dd - call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) - " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) - sil NetrwKeepj g/Local directory now/d - call histdel("/",-1) - if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' - let debugkeep= &debug - setl debug=msg - call netrw#ErrorMsg(s:ERROR,getline(1),15) - let &debug = debugkeep - let mod = 1 - else - bw!|q - endif - elseif !exists("b:netrw_method") || b:netrw_method < 0 - " call Dret("s:#NetrwUpload : unsupported method") - return - endif - else - call netrw#ErrorMsg(s:ERROR,"can't obtain files with protocol from<".a:tgt.">",63) - endif - endif - - " call Dret("s:NetrwUpload") -endfun - -" --------------------------------------------------------------------- -" s:NetrwPreview: supports netrw's "p" map {{{2 -fun! s:NetrwPreview(path) range - " call Dfunc("NetrwPreview(path<".a:path.">)") - " call Decho("g:netrw_alto =".(exists("g:netrw_alto")? g:netrw_alto : 'n/a'),'~'.expand("")) - " call Decho("g:netrw_preview=".(exists("g:netrw_preview")? g:netrw_preview : 'n/a'),'~'.expand("")) - let ykeep= @@ - NetrwKeepj call s:NetrwOptionsSave("s:") - if a:path !~ '^\*\{1,2}/' && a:path !~ '^\a\{3,}://' - NetrwKeepj call s:NetrwOptionsSafe(1) - else - NetrwKeepj call s:NetrwOptionsSafe(0) - endif - if has("quickfix") - " call Decho("has quickfix",'~'.expand("")) - if !isdirectory(s:NetrwFile(a:path)) - " call Decho("good; not previewing a directory",'~'.expand("")) - if g:netrw_preview - " vertical split - let pvhkeep = &pvh - let winsz = (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize - let &pvh = winwidth(0) - winsz - " call Decho("g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily) g:netrw_winsize=".g:netrw_winsize,'~'.expand("")) - else - " horizontal split - let pvhkeep = &pvh - let winsz = (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize - let &pvh = winheight(0) - winsz - " call Decho("!g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily) g:netrw_winsize=".g:netrw_winsize,'~'.expand("")) - endif - " g:netrw_preview g:netrw_alto - " 1 : vert 1: top -- preview window is vertically split off and on the left - " 1 : vert 0: bot -- preview window is vertically split off and on the right - " 0 : 1: top -- preview window is horizontally split off and on the top - " 0 : 0: bot -- preview window is horizontally split off and on the bottom - " - " Note that the file being previewed is already known to not be a directory, hence we can avoid doing a LocalBrowseCheck() check via - " the BufEnter event set up in netrwPlugin.vim - " call Decho("exe ".(g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path),'~'.expand("")) - let eikeep = &ei - set ei=BufEnter - exe (g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path) - let &ei= eikeep - " call Decho("winnr($)=".winnr("$"),'~'.expand("")) - if exists("pvhkeep") - let &pvh= pvhkeep - endif - elseif !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, cannot preview a directory such as <".a:path.">",38) - endif - elseif !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, to preview your vim needs the quickfix feature compiled in",39) - endif - NetrwKeepj call s:NetrwOptionsRestore("s:") - let @@= ykeep - " call Dret("NetrwPreview") -endfun - -" --------------------------------------------------------------------- -" s:NetrwRefresh: {{{2 -fun! s:NetrwRefresh(islocal,dirname) - " call Dfunc("s:NetrwRefresh(islocal<".a:islocal.">,dirname=".a:dirname.") g:netrw_hide=".g:netrw_hide." g:netrw_sort_direction=".g:netrw_sort_direction) - " at the current time (Mar 19, 2007) all calls to NetrwRefresh() call NetrwBrowseChgDir() first. - setl ma noro - " call Decho("setl ma noro",'~'.expand("")) - " call Decho("clear buffer<".expand("%")."> with :%d",'~'.expand("")) - let ykeep = @@ - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - if !exists("w:netrw_treetop") - if exists("b:netrw_curdir") - let w:netrw_treetop= b:netrw_curdir - else - let w:netrw_treetop= getcwd() - endif - endif - NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop) - endif - - " save the cursor position before refresh. - let screenposn = winsaveview() - " call Decho("saving posn to screenposn<".string(screenposn).">",'~'.expand("")) - - " call Decho("win#".winnr().": ".winheight(0)."x".winwidth(0)." curfile<".expand("%").">",'~'.expand("")) - " call Decho("clearing buffer prior to refresh",'~'.expand("")) - sil! NetrwKeepj %d _ - if a:islocal - NetrwKeepj call netrw#LocalBrowseCheck(a:dirname) - else - NetrwKeepj call s:NetrwBrowse(a:islocal,a:dirname) - endif - - " restore position - " call Decho("restoring posn to screenposn<".string(screenposn).">",'~'.expand("")) - NetrwKeepj call winrestview(screenposn) - - " restore file marks - if has("syntax") && exists("g:syntax_on") && g:syntax_on - if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != "" - " " call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/",'~'.expand("")) - exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/" - else - " " call Decho("2match none (bufnr(%)=".bufnr("%")."<".bufname("%").">)",'~'.expand("")) - 2match none - endif - endif - - " restore - let @@= ykeep - " call Dret("s:NetrwRefresh") -endfun - -" --------------------------------------------------------------------- -" s:NetrwRefreshDir: refreshes a directory by name {{{2 -" Called by NetrwMarkFileCopy() -" Interfaces to s:NetrwRefresh() and s:LocalBrowseRefresh() -fun! s:NetrwRefreshDir(islocal,dirname) - if g:netrw_fastbrowse == 0 - " slowest mode (keep buffers refreshed, local or remote) - let tgtwin= bufwinnr(a:dirname) - - if tgtwin > 0 - " tgtwin is being displayed, so refresh it - let curwin= winnr() - exe tgtwin."wincmd w" - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - exe curwin."wincmd w" - - elseif bufnr(a:dirname) > 0 - let bn= bufnr(a:dirname) - exe "sil keepj bd ".bn - endif - - elseif g:netrw_fastbrowse <= 1 - NetrwKeepj call s:LocalBrowseRefresh() - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwSetChgwin: set g:netrw_chgwin; a will use the specified -" window number to do its editing in. -" Supports [count]C where the count, if present, is used to specify -" a window to use for editing via the mapping. -fun! s:NetrwSetChgwin(...) - " call Dfunc("s:NetrwSetChgwin() v:count=".v:count) - if a:0 > 0 - " call Decho("a:1<".a:1.">",'~'.expand("")) - if a:1 == "" " :NetrwC win# - let g:netrw_chgwin= winnr() - else " :NetrwC - let g:netrw_chgwin= a:1 - endif - elseif v:count > 0 " [count]C - let g:netrw_chgwin= v:count - else " C - let g:netrw_chgwin= winnr() - endif - echo "editing window now set to window#".g:netrw_chgwin - " call Dret("s:NetrwSetChgwin : g:netrw_chgwin=".g:netrw_chgwin) -endfun - -" --------------------------------------------------------------------- -" s:NetrwSetSort: sets up the sort based on the g:netrw_sort_sequence {{{2 -" What this function does is to compute a priority for the patterns -" in the g:netrw_sort_sequence. It applies a substitute to any -" "files" that satisfy each pattern, putting the priority / in -" front. An "*" pattern handles the default priority. -fun! s:NetrwSetSort() - " call Dfunc("SetSort() bannercnt=".w:netrw_bannercnt) - let ykeep= @@ - if w:netrw_liststyle == s:LONGLIST - let seqlist = substitute(g:netrw_sort_sequence,'\$','\\%(\t\\|\$\\)','ge') - else - let seqlist = g:netrw_sort_sequence - endif - " sanity check -- insure that * appears somewhere - if seqlist == "" - let seqlist= '*' - elseif seqlist !~ '\*' - let seqlist= seqlist.',*' - endif - let priority = 1 - while seqlist != "" - if seqlist =~ ',' - let seq = substitute(seqlist,',.*$','','e') - let seqlist = substitute(seqlist,'^.\{-},\(.*\)$','\1','e') - else - let seq = seqlist - let seqlist = "" - endif - if priority < 10 - let spriority= "00".priority.g:netrw_sepchr - elseif priority < 100 - let spriority= "0".priority.g:netrw_sepchr - else - let spriority= priority.g:netrw_sepchr - endif - " call Decho("priority=".priority." spriority<".spriority."> seq<".seq."> seqlist<".seqlist.">",'~'.expand("")) - - " sanity check - if w:netrw_bannercnt > line("$") - " apparently no files were left after a Hiding pattern was used - " call Dret("SetSort : no files left after hiding") - return - endif - if seq == '*' - let starpriority= spriority - else - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/'.seq.'/s/^/'.spriority.'/' - call histdel("/",-1) - " sometimes multiple sorting patterns will match the same file or directory. - " The following substitute is intended to remove the excess matches. - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^\d\{3}'.g:netrw_sepchr.'\d\{3}\//s/^\d\{3}'.g:netrw_sepchr.'\(\d\{3}\/\).\@=/\1/e' - NetrwKeepj call histdel("/",-1) - endif - let priority = priority + 1 - endwhile - if exists("starpriority") - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v/^\d\{3}'.g:netrw_sepchr.'/s/^/'.starpriority.'/e' - NetrwKeepj call histdel("/",-1) - endif - - " Following line associated with priority -- items that satisfy a priority - " pattern get prefixed by ###/ which permits easy sorting by priority. - " Sometimes files can satisfy multiple priority patterns -- only the latest - " priority pattern needs to be retained. So, at this point, these excess - " priority prefixes need to be removed, but not directories that happen to - " be just digits themselves. - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{3}'.g:netrw_sepchr.'\)\%(\d\{3}'.g:netrw_sepchr.'\)\+\ze./\1/e' - NetrwKeepj call histdel("/",-1) - let @@= ykeep - - " call Dret("SetSort") -endfun - -" --------------------------------------------------------------------- -" s:NetrwSetTgt: sets the target to the specified choice index {{{2 -" Implements [count]Tb (bookhist) -" [count]Th (bookhist) -" See :help netrw-qb for how to make the choice. -fun! s:NetrwSetTgt(islocal,bookhist,choice) - " call Dfunc("s:NetrwSetTgt(islocal=".a:islocal." bookhist<".a:bookhist."> choice#".a:choice.")") - - if a:bookhist == 'b' - " supports choosing a bookmark as a target using a qb-generated list - let choice= a:choice - 1 - if exists("g:netrw_bookmarklist[".choice."]") - call netrw#MakeTgt(g:netrw_bookmarklist[choice]) - else - echomsg "Sorry, bookmark#".a:choice." doesn't exist!" - endif - - elseif a:bookhist == 'h' - " supports choosing a history stack entry as a target using a qb-generated list - let choice= (a:choice % g:netrw_dirhistmax) + 1 - if exists("g:netrw_dirhist_".choice) - let histentry = g:netrw_dirhist_{choice} - call netrw#MakeTgt(histentry) - else - echomsg "Sorry, history#".a:choice." not available!" - endif - endif - - " refresh the display - if !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - endif - call s:NetrwRefresh(a:islocal,b:netrw_curdir) - - " call Dret("s:NetrwSetTgt") -endfun - -" ===================================================================== -" s:NetrwSortStyle: change sorting style (name - time - size - exten) and refresh display {{{2 -fun! s:NetrwSortStyle(islocal) - NetrwKeepj call s:NetrwSaveWordPosn() - let svpos= winsaveview() - - let g:netrw_sort_by= (g:netrw_sort_by =~# '^n')? 'time' : (g:netrw_sort_by =~# '^t')? 'size' : (g:netrw_sort_by =~# '^siz')? 'exten' : 'name' - NetrwKeepj norm! 0 - NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - NetrwKeepj call winrestview(svpos) -endfun - -" --------------------------------------------------------------------- -" s:NetrwSplit: mode {{{2 -" =0 : net and o -" =1 : net and t -" =2 : net and v -" =3 : local and o -" =4 : local and t -" =5 : local and v -fun! s:NetrwSplit(mode) - - let ykeep= @@ - call s:SaveWinVars() - - if a:mode == 0 - " remote and o - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize - if winsz == 0|let winsz= ""|endif - exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s" - let s:didsplit= 1 - NetrwKeepj call s:RestoreWinVars() - NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) - unlet s:didsplit - - elseif a:mode == 1 - " remote and t - let newdir = s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1) - tabnew - let s:didsplit= 1 - NetrwKeepj call s:RestoreWinVars() - NetrwKeepj call s:NetrwBrowse(0,newdir) - unlet s:didsplit - - elseif a:mode == 2 - " remote and v - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize - if winsz == 0|let winsz= ""|endif - exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v" - let s:didsplit= 1 - NetrwKeepj call s:RestoreWinVars() - NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) - unlet s:didsplit - - elseif a:mode == 3 - " local and o - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize - if winsz == 0|let winsz= ""|endif - exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s" - let s:didsplit= 1 - NetrwKeepj call s:RestoreWinVars() - NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1)) - unlet s:didsplit - - elseif a:mode == 4 - " local and t - let cursorword = s:NetrwGetWord() - let eikeep = &ei - let netrw_winnr = winnr() - let netrw_line = line(".") - let netrw_col = virtcol(".") - NetrwKeepj norm! H0 - let netrw_hline = line(".") - setl ei=all - exe "NetrwKeepj norm! ".netrw_hline."G0z\" - exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\" - let &ei = eikeep - let netrw_curdir = s:NetrwTreeDir(0) - tabnew - let b:netrw_curdir = netrw_curdir - let s:didsplit = 1 - NetrwKeepj call s:RestoreWinVars() - NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,cursorword,0)) - if &ft == "netrw" - setl ei=all - exe "NetrwKeepj norm! ".netrw_hline."G0z\" - exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\" - let &ei= eikeep - endif - unlet s:didsplit - - elseif a:mode == 5 - " local and v - let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize - if winsz == 0|let winsz= ""|endif - exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v" - let s:didsplit= 1 - NetrwKeepj call s:RestoreWinVars() - NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1)) - unlet s:didsplit - - else - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"(NetrwSplit) unsupported mode=".a:mode,45) - endif - - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwTgtMenu: {{{2 -fun! s:NetrwTgtMenu() - if !exists("s:netrw_menucnt") - return - endif - " call Dfunc("s:NetrwTgtMenu()") - - " the following test assures that gvim is running, has menus available, and has menus enabled. - if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu - if exists("g:NetrwTopLvlMenu") - " call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand("")) - exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Targets' - endif - if !exists("s:netrw_initbookhist") - call s:NetrwBookHistRead() - endif - - " try to cull duplicate entries - let tgtdict={} - - " target bookmarked places - if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0 - " call Decho("installing bookmarks as easy targets",'~'.expand("")) - let cnt= 1 - for bmd in g:netrw_bookmarklist - if has_key(tgtdict,bmd) - let cnt= cnt + 1 - continue - endif - let tgtdict[bmd]= cnt - let ebmd= escape(bmd,g:netrw_menu_escape) - " show bookmarks for goto menu - " call Decho("menu: Targets: ".bmd,'~'.expand("")) - exe 'sil! menu '.g:NetrwMenuPriority.".19.1.".cnt." ".g:NetrwTopLvlMenu.'Targets.'.ebmd." :call netrw#MakeTgt('".bmd."')\" - let cnt= cnt + 1 - endfor - endif - - " target directory browsing history - if exists("g:netrw_dirhistmax") && g:netrw_dirhistmax > 0 - " call Decho("installing history as easy targets (histmax=".g:netrw_dirhistmax.")",'~'.expand("")) - let histcnt = 1 - while histcnt <= g:netrw_dirhistmax - let priority = g:netrw_dirhistcnt + histcnt - if exists("g:netrw_dirhist_{histcnt}") - let histentry = g:netrw_dirhist_{histcnt} - if has_key(tgtdict,histentry) - let histcnt = histcnt + 1 - continue - endif - let tgtdict[histentry] = histcnt - let ehistentry = escape(histentry,g:netrw_menu_escape) - " call Decho("menu: Targets: ".histentry,'~'.expand("")) - exe 'sil! menu '.g:NetrwMenuPriority.".19.2.".priority." ".g:NetrwTopLvlMenu.'Targets.'.ehistentry." :call netrw#MakeTgt('".histentry."')\" - endif - let histcnt = histcnt + 1 - endwhile - endif - endif - " call Dret("s:NetrwTgtMenu") -endfun - -" --------------------------------------------------------------------- -" s:NetrwTreeDir: determine tree directory given current cursor position {{{2 -" (full path directory with trailing slash returned) -fun! s:NetrwTreeDir(islocal) - - if exists("s:treedir") && exists("s:prevwinopen") - " s:NetrwPrevWinOpen opens a "previous" window -- and thus needs to and does call s:NetrwTreeDir early - let treedir= s:treedir - unlet s:treedir - unlet s:prevwinopen - return treedir - endif - if exists("s:prevwinopen") - unlet s:prevwinopen - endif - - if !exists("b:netrw_curdir") || b:netrw_curdir == "" - let b:netrw_curdir= getcwd() - endif - let treedir = b:netrw_curdir - let s:treecurpos= winsaveview() - - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - - " extract tree directory if on a line specifying a subdirectory (ie. ends with "/") - let curline= substitute(getline('.'),"\t -->.*$",'','') - if curline =~ '/$' - let treedir= substitute(getline('.'),'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e') - elseif curline =~ '@$' - let potentialdir= resolve(s:NetrwTreePath(w:netrw_treetop)) - else - let treedir= "" - endif - - " detect user attempting to close treeroot - if curline !~ '^'.s:treedepthstring && getline('.') != '..' - " now force a refresh - sil! NetrwKeepj %d _ - return b:netrw_curdir - endif - - " COMBAK: a symbolic link may point anywhere -- so it will be used to start a new treetop - " if a:islocal && curline =~ '@$' && isdirectory(s:NetrwFile(potentialdir)) - " let newdir = w:netrw_treetop.'/'.potentialdir - if a:islocal && curline =~ '@$' - if isdirectory(s:NetrwFile(potentialdir)) - let treedir = potentialdir - let w:netrw_treetop = treedir - endif - else - let potentialdir= s:NetrwFile(substitute(curline,'^'.s:treedepthstring.'\+ \(.*\)@$','\1','')) - let treedir = s:NetrwTreePath(w:netrw_treetop) - endif - endif - - " sanity maintenance: keep those //s away... - let treedir= substitute(treedir,'//$','/','') - return treedir -endfun - -" --------------------------------------------------------------------- -" s:NetrwTreeDisplay: recursive tree display {{{2 -fun! s:NetrwTreeDisplay(dir,depth) - " ensure that there are no folds - setl nofen - - " install ../ and shortdir - if a:depth == "" - call setline(line("$")+1,'../') - endif - if a:dir =~ '^\a\{3,}://' - if a:dir == w:netrw_treetop - let shortdir= a:dir - else - let shortdir= substitute(a:dir,'^.*/\([^/]\+\)/$','\1/','e') - endif - call setline(line("$")+1,a:depth.shortdir) - else - let shortdir= substitute(a:dir,'^.*/','','e') - call setline(line("$")+1,a:depth.shortdir.'/') - endif - " append a / to dir if its missing one - let dir= a:dir - - " display subtrees (if any) - let depth= s:treedepthstring.a:depth - - " implement g:netrw_hide for tree listings (uses g:netrw_list_hide) - if g:netrw_hide == 1 - " hide given patterns - let listhide= split(g:netrw_list_hide,',') - for pat in listhide - call filter(w:netrw_treedict[dir],'v:val !~ "'.escape(pat,'\\').'"') - endfor - - elseif g:netrw_hide == 2 - " show given patterns (only) - let listhide= split(g:netrw_list_hide,',') - let entries=[] - for entry in w:netrw_treedict[dir] - for pat in listhide - if entry =~ pat - call add(entries,entry) - break - endif - endfor - endfor - let w:netrw_treedict[dir]= entries - endif - if depth != "" - " always remove "." and ".." entries when there's depth - call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\.$"') - call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\./$"') - call filter(w:netrw_treedict[dir],'v:val !~ "\\.$"') - call filter(w:netrw_treedict[dir],'v:val !~ "\\./$"') - endif - - for entry in w:netrw_treedict[dir] - if dir =~ '/$' - let direntry= substitute(dir.entry,'[@/]$','','e') - else - let direntry= substitute(dir.'/'.entry,'[@/]$','','e') - endif - if entry =~ '/$' && has_key(w:netrw_treedict,direntry) - NetrwKeepj call s:NetrwTreeDisplay(direntry,depth) - elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/') - NetrwKeepj call s:NetrwTreeDisplay(direntry.'/',depth) - elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@') - NetrwKeepj call s:NetrwTreeDisplay(direntry.'@',depth) - else - sil! NetrwKeepj call setline(line("$")+1,depth.entry) - endif - endfor -endfun - -" --------------------------------------------------------------------- -" s:NetrwRefreshTreeDict: updates the contents information for a tree (w:netrw_treedict) {{{2 -fun! s:NetrwRefreshTreeDict(dir) - if !exists("w:netrw_treedict") - return - endif - - for entry in w:netrw_treedict[a:dir] - let direntry= substitute(a:dir.'/'.entry,'[@/]$','','e') - - if entry =~ '/$' && has_key(w:netrw_treedict,direntry) - NetrwKeepj call s:NetrwRefreshTreeDict(direntry) - let filelist = s:NetrwLocalListingList(direntry,0) - let w:netrw_treedict[direntry] = sort(filelist) - - elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/') - NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/') - let filelist = s:NetrwLocalListingList(direntry.'/',0) - let w:netrw_treedict[direntry] = sort(filelist) - - elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@') - NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/') - let liststar = s:NetrwGlob(direntry.'/','*',1) - let listdotstar= s:NetrwGlob(direntry.'/','.*',1) - - else - endif - endfor -endfun - -" --------------------------------------------------------------------- -" s:NetrwTreeListing: displays tree listing from treetop on down, using NetrwTreeDisplay() {{{2 -" Called by s:PerformListing() -fun! s:NetrwTreeListing(dirname) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - - " update the treetop - if !exists("w:netrw_treetop") - let w:netrw_treetop= a:dirname - let s:netrw_treetop= w:netrw_treetop - " use \V in case the directory contains specials chars like '$' or '~' - elseif (w:netrw_treetop =~ ('^'.'\V'.a:dirname) && s:Strlen(a:dirname) < s:Strlen(w:netrw_treetop)) - \ || a:dirname !~ ('^'.'\V'.w:netrw_treetop) - let w:netrw_treetop= a:dirname - let s:netrw_treetop= w:netrw_treetop - endif - if exists("w:netrw_treetop") - let s:netrw_treetop= w:netrw_treetop - else - let w:netrw_treetop= getcwd() - let s:netrw_treetop= w:netrw_treetop - endif - - if !exists("w:netrw_treedict") - " insure that we have a treedict, albeit empty - let w:netrw_treedict= {} - endif - - " update the dictionary for the current directory - exe "sil! NetrwKeepj keepp ".w:netrw_bannercnt.',$g@^\.\.\=/$@d _' - let w:netrw_treedict[a:dirname]= getline(w:netrw_bannercnt,line("$")) - exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _" - - " if past banner, record word - if exists("w:netrw_bannercnt") && line(".") > w:netrw_bannercnt - let fname= expand("") - else - let fname= "" - endif - - " display from treetop on down - NetrwKeepj call s:NetrwTreeDisplay(w:netrw_treetop,"") - - " remove any blank line remaining as line#1 (happens in treelisting mode with banner suppressed) - while getline(1) =~ '^\s*$' && byte2line(1) > 0 - 1d - endwhile - - exe "setl ".g:netrw_bufsettings - - return - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwTreePath: returns path to current file/directory in tree listing {{{2 -" Normally, treetop is w:netrw_treetop, but a -" user of the function ( netrw#SetTreetop() ) -" wipes that out prior to calling this function -fun! s:NetrwTreePath(treetop) - " call Dfunc("s:NetrwTreePath(treetop<".a:treetop.">) line#".line(".")."<".getline(".").">") - if line(".") < w:netrw_bannercnt + 2 - let treedir= a:treetop - if treedir !~ '/$' - let treedir= treedir.'/' - endif - " call Dret("s:NetrwTreePath ".treedir." : line#".line(".")." ≤ ".(w:netrw_bannercnt+2)) - return treedir - endif - - let svpos = winsaveview() - " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) - let depth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e') - " call Decho("depth<".depth."> 1st subst",'~'.expand("")) - let depth = substitute(depth,'^'.s:treedepthstring,'','') - " call Decho("depth<".depth."> 2nd subst (first depth removed)",'~'.expand("")) - let curline= getline('.') - " call Decho("curline<".curline.'>','~'.expand("")) - if curline =~ '/$' - " call Decho("extract tree directory from current line",'~'.expand("")) - let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e') - " call Decho("treedir<".treedir.">",'~'.expand("")) - elseif curline =~ '@\s\+-->' - " call Decho("extract tree directory using symbolic link",'~'.expand("")) - let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e') - let treedir= substitute(treedir,'@\s\+-->.*$','','e') - " call Decho("treedir<".treedir.">",'~'.expand("")) - else - " call Decho("do not extract tree directory from current line and set treedir to empty",'~'.expand("")) - let treedir= "" - endif - " construct treedir by searching backwards at correct depth - " call Decho("construct treedir by searching backwards for correct depth",'~'.expand("")) - " call Decho("initial treedir<".treedir."> depth<".depth.">",'~'.expand("")) - while depth != "" && search('^'.depth.'[^'.s:treedepthstring.'].\{-}/$','bW') - let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e') - let treedir= dirname.treedir - let depth = substitute(depth,'^'.s:treedepthstring,'','') - " call Decho("constructing treedir<".treedir.">: dirname<".dirname."> while depth<".depth.">",'~'.expand("")) - endwhile - " call Decho("treedir#1<".treedir.">",'~'.expand("")) - if a:treetop =~ '/$' - let treedir= a:treetop.treedir - else - let treedir= a:treetop.'/'.treedir - endif - " call Decho("treedir#2<".treedir.">",'~'.expand("")) - let treedir= substitute(treedir,'//$','/','') - " call Decho("treedir#3<".treedir.">",'~'.expand("")) - " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))" - call winrestview(svpos) - " call Dret("s:NetrwTreePath <".treedir.">") - return treedir -endfun - -" --------------------------------------------------------------------- -" s:NetrwWideListing: {{{2 -fun! s:NetrwWideListing() - - if w:netrw_liststyle == s:WIDELIST - " call Dfunc("NetrwWideListing() w:netrw_liststyle=".w:netrw_liststyle.' fo='.&fo.' l:fo='.&l:fo) - " look for longest filename (cpf=characters per filename) - " cpf: characters per filename - " fpl: filenames per line - " fpc: filenames per column - setl ma noro - let dict={} - " save the unnamed register and register 0-9 and a - let dict.a=[getreg('a'), getregtype('a')] - for i in range(0, 9) - let dict[i] = [getreg(i), getregtype(i)] - endfor - let dict.unnamed = [getreg(''), getregtype('')] - " call Decho("setl ma noro",'~'.expand("")) - let b:netrw_cpf= 0 - if line("$") >= w:netrw_bannercnt - " determine the maximum filename size; use that to set cpf - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif' - NetrwKeepj call histdel("/",-1) - else - " restore stored registers - call s:RestoreRegister(dict) - " call Dret("NetrwWideListing") - return - endif - " allow for two spaces to separate columns - let b:netrw_cpf= b:netrw_cpf + 2 - " call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand("")) - - " determine qty files per line (fpl) - let w:netrw_fpl= winwidth(0)/b:netrw_cpf - if w:netrw_fpl <= 0 - let w:netrw_fpl= 1 - endif - " call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl,'~'.expand("")) - - " make wide display - " fpc: files per column of wide listing - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'S",submatch(0)),"\\")/' - NetrwKeepj call histdel("/",-1) - let fpc = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl - let newcolstart = w:netrw_bannercnt + fpc - let newcolend = newcolstart + fpc - 1 - " call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand("")) - if !has('nvim') && has("clipboard") && g:netrw_clipboard - " call Decho("(s:NetrwWideListing) save @* and @+",'~'.expand("")) - sil! let keepregstar = @* - sil! let keepregplus = @+ - endif - while line("$") >= newcolstart - if newcolend > line("$") | let newcolend= line("$") | endif - let newcolqty= newcolend - newcolstart - exe newcolstart - " COMBAK: both of the visual-mode using lines below are problematic vis-a-vis @* - if newcolqty == 0 - exe "sil! NetrwKeepj norm! 0\$h\"ax".w:netrw_bannercnt."G$\"ap" - else - exe "sil! NetrwKeepj norm! 0\".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap' - endif - exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _' - exe 'sil! NetrwKeepj '.w:netrw_bannercnt - endwhile - if !has('nvim') && has("clipboard") - " call Decho("(s:NetrwWideListing) restore @* and @+",'~'.expand("")) - if @* != keepregstar | sil! let @* = keepregstar | endif - if @+ != keepregplus | sil! let @+ = keepregplus | endif - endif - exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e' - NetrwKeepj call histdel("/",-1) - exe 'nno w :call search(''^.\\|\s\s\zs\S'',''W'')'."\" - exe 'nno b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\" - " call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand("")) - exe "setl ".g:netrw_bufsettings - call s:RestoreRegister(dict) - " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - " call Dret("NetrwWideListing") - return - else - if hasmapto("w","n") - sil! nunmap w - endif - if hasmapto("b","n") - sil! nunmap b - endif - endif -endfun - -" --------------------------------------------------------------------- -" s:PerformListing: {{{2 -fun! s:PerformListing(islocal) - " call Dfunc("s:PerformListing(islocal=".a:islocal.")") - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) - " call Decho("settings: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (enter)"." ei<".&ei.">",'~'.expand("")) - sil! NetrwKeepj %d _ - " call DechoBuf(bufnr("%")) - - " set up syntax highlighting {{{3 - " call Decho("--set up syntax highlighting (ie. setl ft=netrw)",'~'.expand("")) - sil! setl ft=netrw - - NetrwKeepj call s:NetrwOptionsSafe(a:islocal) - setl noro ma - " call Decho("setl noro ma bh=".&bh,'~'.expand("")) - - " if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1 " Decho - " call Decho("Processing your browsing request...",'~'.expand("")) - " endif " Decho - - " call Decho('w:netrw_liststyle='.(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand("")) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") - " force a refresh for tree listings - " call Decho("force refresh for treelisting: clear buffer<".expand("%")."> with :%d",'~'.expand("")) - sil! NetrwKeepj %d _ - endif - - " save current directory on directory history list - NetrwKeepj call s:NetrwBookHistHandler(3,b:netrw_curdir) - - " Set up the banner {{{3 - if g:netrw_banner - " call Decho("--set up banner",'~'.expand("")) - NetrwKeepj call setline(1,'" ============================================================================') - if exists("g:netrw_pchk") - " this undocumented option allows pchk to run with different versions of netrw without causing spurious - " failure detections. - NetrwKeepj call setline(2,'" Netrw Directory Listing') - else - NetrwKeepj call setline(2,'" Netrw Directory Listing (netrw '.g:loaded_netrw.')') - endif - if exists("g:netrw_pchk") - let curdir= substitute(b:netrw_curdir,expand("$HOME"),'~','') - else - let curdir= b:netrw_curdir - endif - if exists("g:netrw_bannerbackslash") && g:netrw_bannerbackslash - NetrwKeepj call setline(3,'" '.substitute(curdir,'/','\\','g')) - else - NetrwKeepj call setline(3,'" '.curdir) - endif - let w:netrw_bannercnt= 3 - NetrwKeepj exe "sil! NetrwKeepj ".w:netrw_bannercnt - else - " call Decho("--no banner",'~'.expand("")) - NetrwKeepj 1 - let w:netrw_bannercnt= 1 - endif - " call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." win#".winnr(),'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) - - " construct sortby string: [name|time|size|exten] [reversed] - let sortby= g:netrw_sort_by - if g:netrw_sort_direction =~# "^r" - let sortby= sortby." reversed" - endif - - " Sorted by... {{{3 - if g:netrw_banner - " call Decho("--handle specified sorting: g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand("")) - if g:netrw_sort_by =~# "^n" - " call Decho("directories will be sorted by name",'~'.expand("")) - " sorted by name (also includes the sorting sequence in the banner) - NetrwKeepj put ='\" Sorted by '.sortby - NetrwKeepj put ='\" Sort sequence: '.g:netrw_sort_sequence - let w:netrw_bannercnt= w:netrw_bannercnt + 2 - else - " call Decho("directories will be sorted by size or time",'~'.expand("")) - " sorted by time, size, exten - NetrwKeepj put ='\" Sorted by '.sortby - let w:netrw_bannercnt= w:netrw_bannercnt + 1 - endif - exe "sil! NetrwKeepj ".w:netrw_bannercnt - " else " Decho - " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - endif - - " show copy/move target, if any {{{3 - if g:netrw_banner - if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") - " call Decho("--show copy/move target<".s:netrwmftgt.">",'~'.expand("")) - NetrwKeepj put ='' - if s:netrwmftgt_islocal - sil! NetrwKeepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (local)') - else - sil! NetrwKeepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (remote)') - endif - let w:netrw_bannercnt= w:netrw_bannercnt + 1 - else - " call Decho("s:netrwmftgt does not exist, don't make Copy/Move Tgt",'~'.expand("")) - endif - exe "sil! NetrwKeepj ".w:netrw_bannercnt - endif - - " Hiding... -or- Showing... {{{3 - if g:netrw_banner - " call Decho("--handle hiding/showing in banner (g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">)",'~'.expand("")) - if g:netrw_list_hide != "" && g:netrw_hide - if g:netrw_hide == 1 - NetrwKeepj put ='\" Hiding: '.g:netrw_list_hide - else - NetrwKeepj put ='\" Showing: '.g:netrw_list_hide - endif - let w:netrw_bannercnt= w:netrw_bannercnt + 1 - endif - exe "NetrwKeepj ".w:netrw_bannercnt - - " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - let quickhelp = g:netrw_quickhelp%len(s:QuickHelp) - " call Decho("quickhelp =".quickhelp,'~'.expand("")) - NetrwKeepj put ='\" Quick Help: :help '.s:QuickHelp[quickhelp] - " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - NetrwKeepj put ='\" ==============================================================================' - let w:netrw_bannercnt= w:netrw_bannercnt + 2 - " else " Decho - " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - endif - - " bannercnt should index the line just after the banner - if g:netrw_banner - let w:netrw_bannercnt= w:netrw_bannercnt + 1 - exe "sil! NetrwKeepj ".w:netrw_bannercnt - " call Decho("--w:netrw_bannercnt=".w:netrw_bannercnt." (should index line just after banner) line($)=".line("$"),'~'.expand("")) - " else " Decho - " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - endif - - " get list of files - " call Decho("--Get list of files - islocal=".a:islocal,'~'.expand("")) - if a:islocal - NetrwKeepj call s:LocalListing() - else " remote - NetrwKeepj let badresult= s:NetrwRemoteListing() - if badresult - " call Decho("w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a')." win#".winnr()." buf#".bufnr("%")."<".bufname("%").">",'~'.expand("")) - " call Dret("s:PerformListing : error detected by NetrwRemoteListing") - return - endif - endif - - " manipulate the directory listing (hide, sort) {{{3 - if !exists("w:netrw_bannercnt") - let w:netrw_bannercnt= 0 - endif - " call Decho("--manipulate directory listing (hide, sort)",'~'.expand("")) - " call Decho("g:netrw_banner=".g:netrw_banner." w:netrw_bannercnt=".w:netrw_bannercnt." (banner complete)",'~'.expand("")) - " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - - if !g:netrw_banner || line("$") >= w:netrw_bannercnt - " call Decho("manipulate directory listing (support hide)",'~'.expand("")) - " call Decho("g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">",'~'.expand("")) - if g:netrw_hide && g:netrw_list_hide != "" - NetrwKeepj call s:NetrwListHide() - endif - if !g:netrw_banner || line("$") >= w:netrw_bannercnt - " call Decho("manipulate directory listing (sort) : g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand("")) - - if g:netrw_sort_by =~# "^n" - " sort by name - " call Decho("sort by name",'~'.expand("")) - NetrwKeepj call s:NetrwSetSort() - - if !g:netrw_banner || w:netrw_bannercnt < line("$") - " call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - if g:netrw_sort_direction =~# 'n' - " name: sort by name of file - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options - else - " reverse direction sorting - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options - endif - endif - - " remove priority pattern prefix - " call Decho("remove priority pattern prefix",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e' - NetrwKeepj call histdel("/",-1) - - elseif g:netrw_sort_by =~# "^ext" - " exten: sort by extension - " The histdel(...,-1) calls remove the last search from the search history - " call Decho("sort by extension",'~'.expand("")) - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g+/+s/^/001'.g:netrw_sepchr.'/' - NetrwKeepj call histdel("/",-1) - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+[./]+s/^/002'.g:netrw_sepchr.'/' - NetrwKeepj call histdel("/",-1) - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+['.g:netrw_sepchr.'/]+s/^\(.*\.\)\(.\{-\}\)$/\2'.g:netrw_sepchr.'&/e' - NetrwKeepj call histdel("/",-1) - if !g:netrw_banner || w:netrw_bannercnt < line("$") - " call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - if g:netrw_sort_direction =~# 'n' - " normal direction sorting - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options - else - " reverse direction sorting - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options - endif - endif - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^.\{-}'.g:netrw_sepchr.'//e' - NetrwKeepj call histdel("/",-1) - - elseif a:islocal - if !g:netrw_banner || w:netrw_bannercnt < line("$") - " call Decho("g:netrw_sort_direction=".g:netrw_sort_direction,'~'.expand("")) - if g:netrw_sort_direction =~# 'n' - " call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort','~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options - else - " call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort!','~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options - endif - " call Decho("remove leading digits/ (sorting) information from listing",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e' - NetrwKeepj call histdel("/",-1) - endif - endif - - elseif g:netrw_sort_direction =~# 'r' - " call Decho('(s:PerformListing) reverse the sorted listing','~'.expand("")) - if !g:netrw_banner || w:netrw_bannercnt < line('$') - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g/^/m '.w:netrw_bannercnt - call histdel("/",-1) - endif - endif - endif - " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) - - " convert to wide/tree listing {{{3 - " call Decho("--modify display if wide/tree listing style",'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#1)",'~'.expand("")) - NetrwKeepj call s:NetrwWideListing() - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#2)",'~'.expand("")) - NetrwKeepj call s:NetrwTreeListing(b:netrw_curdir) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#3)",'~'.expand("")) - - " resolve symbolic links if local and (thin or tree) - if a:islocal && (w:netrw_liststyle == s:THINLIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST)) - " call Decho("--resolve symbolic links if local and thin|tree",'~'.expand("")) - sil! keepp g/@$/call s:ShowLink() - endif - - if exists("w:netrw_bannercnt") && (line("$") >= w:netrw_bannercnt || !g:netrw_banner) - " place cursor on the top-left corner of the file listing - " call Decho("--place cursor on top-left corner of file listing",'~'.expand("")) - exe 'sil! '.w:netrw_bannercnt - sil! NetrwKeepj norm! 0 - " call Decho(" tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) - else - " call Decho("--did NOT place cursor on top-left corner",'~'.expand("")) - " call Decho(" w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'),'~'.expand("")) - " call Decho(" line($)=".line("$"),'~'.expand("")) - " call Decho(" g:netrw_banner=".(exists("g:netrw_banner")? g:netrw_banner : 'n/a'),'~'.expand("")) - endif - - " record previous current directory - let w:netrw_prvdir= b:netrw_curdir - " call Decho("--record netrw_prvdir<".w:netrw_prvdir.">",'~'.expand("")) - - " save certain window-oriented variables into buffer-oriented variables {{{3 - " call Decho("--save some window-oriented variables into buffer oriented variables",'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#4)",'~'.expand("")) - NetrwKeepj call s:SetBufWinVars() - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#5)",'~'.expand("")) - NetrwKeepj call s:NetrwOptionsRestore("w:") - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#6)",'~'.expand("")) - - " set display to netrw display settings - " call Decho("--set display to netrw display settings (".g:netrw_bufsettings.")",'~'.expand("")) - exe "setl ".g:netrw_bufsettings - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#7)",'~'.expand("")) - if g:netrw_liststyle == s:LONGLIST - " call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand("")) - exe "setl ts=".(g:netrw_maxfilenamelen+1) - endif - " call Decho("PerformListing buffer:",'~'.expand("")) - " call DechoBuf(bufnr("%")) - - if exists("s:treecurpos") - " call Decho("s:treecurpos exists; restore posn",'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#8)",'~'.expand("")) - " call Decho("restoring posn to s:treecurpos<".string(s:treecurpos).">",'~'.expand("")) - NetrwKeepj call winrestview(s:treecurpos) - unlet s:treecurpos - endif - - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (return)",'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) - " call Dret("s:PerformListing : curpos<".string(getpos(".")).">") -endfun - -" --------------------------------------------------------------------- -" s:SetupNetrwStatusLine: {{{2 -fun! s:SetupNetrwStatusLine(statline) - " call Dfunc("SetupNetrwStatusLine(statline<".a:statline.">)") - - if !exists("s:netrw_setup_statline") - let s:netrw_setup_statline= 1 - " call Decho("do first-time status line setup",'~'.expand("")) - - if !exists("s:netrw_users_stl") - let s:netrw_users_stl= &stl - endif - if !exists("s:netrw_users_ls") - let s:netrw_users_ls= &laststatus - endif - - " set up User9 highlighting as needed - let dict={} - let dict.a=[getreg('a'), getregtype('a')] - redir @a - try - hi User9 - catch /^Vim\%((\a\{3,})\)\=:E411/ - if &bg == "dark" - hi User9 ctermfg=yellow ctermbg=blue guifg=yellow guibg=blue - else - hi User9 ctermbg=yellow ctermfg=blue guibg=yellow guifg=blue - endif - endtry - redir END - call s:RestoreRegister(dict) - endif - - " set up status line (may use User9 highlighting) - " insure that windows have a statusline - " make sure statusline is displayed - let &l:stl=a:statline - setl laststatus=2 - " call Decho("stl=".&stl,'~'.expand("")) - redraw - - " call Dret("SetupNetrwStatusLine : stl=".&stl) -endfun - -" ========================================= -" Remote Directory Browsing Support: {{{1 -" ========================================= - -" --------------------------------------------------------------------- -" s:NetrwRemoteFtpCmd: unfortunately, not all ftp servers honor options for ls {{{2 -" This function assumes that a long listing will be received. Size, time, -" and reverse sorts will be requested of the server but not otherwise -" enforced here. -fun! s:NetrwRemoteFtpCmd(path,listcmd) - " call Dfunc("NetrwRemoteFtpCmd(path<".a:path."> listcmd<".a:listcmd.">) w:netrw_method=".(exists("w:netrw_method")? w:netrw_method : (exists("b:netrw_method")? b:netrw_method : "???"))) - " call Decho("line($)=".line("$")." win#".winnr()." w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand("")) - " sanity check: {{{3 - if !exists("w:netrw_method") - if exists("b:netrw_method") - let w:netrw_method= b:netrw_method - else - call netrw#ErrorMsg(2,"(s:NetrwRemoteFtpCmd) internal netrw error",93) - " call Dret("NetrwRemoteFtpCmd") - return - endif - endif - - " WinXX ftp uses unix style input, so set ff to unix " {{{3 - let ffkeep= &ff - setl ma ff=unix noro - " call Decho("setl ma ff=unix noro",'~'.expand("")) - - " clear off any older non-banner lines " {{{3 - " note that w:netrw_bannercnt indexes the line after the banner - " call Decho('exe sil! NetrwKeepj '.w:netrw_bannercnt.",$d _ (clear off old non-banner lines)",'~'.expand("")) - exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _" - - "......................................... - if w:netrw_method == 2 || w:netrw_method == 5 " {{{3 - " ftp + <.netrc>: Method #2 - if a:path != "" - NetrwKeepj put ='cd \"'.a:path.'\"' - endif - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - NetrwKeepj call setline(line("$")+1,a:listcmd) - " exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))' - if exists("g:netrw_port") && g:netrw_port != "" - " call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1),'~'.expand("")) - exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1) - else - " call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1),'~'.expand("")) - exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1) - endif - - "......................................... - elseif w:netrw_method == 3 " {{{3 - " ftp + machine,id,passwd,filename: Method #3 - setl ff=unix - if exists("g:netrw_port") && g:netrw_port != "" - NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port - else - NetrwKeepj put ='open '.g:netrw_machine - endif - - " handle userid and password - let host= substitute(g:netrw_machine,'\..*$','','') - " call Decho("host<".host.">",'~'.expand("")) - if exists("s:netrw_hup") && exists("s:netrw_hup[host]") - call NetUserPass("ftp:".host) - endif - if exists("g:netrw_uid") && g:netrw_uid != "" - if exists("g:netrw_ftp") && g:netrw_ftp == 1 - NetrwKeepj put =g:netrw_uid - if exists("s:netrw_passwd") && s:netrw_passwd != "" - NetrwKeepj put ='\"'.s:netrw_passwd.'\"' - endif - elseif exists("s:netrw_passwd") - NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' - endif - endif - - if a:path != "" - NetrwKeepj put ='cd \"'.a:path.'\"' - endif - if exists("g:netrw_ftpextracmd") - NetrwKeepj put =g:netrw_ftpextracmd - " call Decho("filter input: ".getline('.'),'~'.expand("")) - endif - NetrwKeepj call setline(line("$")+1,a:listcmd) - - " perform ftp: - " -i : turns off interactive prompting from ftp - " -n unix : DON'T use <.netrc>, even though it exists - " -n win32: quit being obnoxious about password - if exists("w:netrw_bannercnt") - " exe w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))' - call s:NetrwExe(s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) - " else " Decho - " call Decho("WARNING: w:netrw_bannercnt doesn't exist!",'~'.expand("")) - " g/^./call Decho("SKIPPING ftp#".line(".").": ".getline("."),'~'.expand("")) - endif - - "......................................... - elseif w:netrw_method == 9 " {{{3 - " sftp username@machine: Method #9 - " s:netrw_sftp_cmd - setl ff=unix - - " restore settings - let &l:ff= ffkeep - " call Dret("NetrwRemoteFtpCmd") - return - - "......................................... - else " {{{3 - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . bufname("%") . ">",23) - endif - - " cleanup for Windows " {{{3 - if has("win32") - sil! NetrwKeepj %s/\r$//e - NetrwKeepj call histdel("/",-1) - endif - if a:listcmd == "dir" - " infer directory/link based on the file permission string - sil! NetrwKeepj g/d\%([-r][-w][-x]\)\{3}/NetrwKeepj s@$@/@e - sil! NetrwKeepj g/l\%([-r][-w][-x]\)\{3}/NetrwKeepj s/$/@/e - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST) - exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/^\%(\S\+\s\+\)\{8}//e' - NetrwKeepj call histdel("/",-1) - endif - endif - - " ftp's listing doesn't seem to include ./ or ../ " {{{3 - if !search('^\.\/$\|\s\.\/$','wn') - exe 'NetrwKeepj '.w:netrw_bannercnt - NetrwKeepj put ='./' - endif - if !search('^\.\.\/$\|\s\.\.\/$','wn') - exe 'NetrwKeepj '.w:netrw_bannercnt - NetrwKeepj put ='../' - endif - - " restore settings " {{{3 - let &l:ff= ffkeep - " call Dret("NetrwRemoteFtpCmd") -endfun - -" --------------------------------------------------------------------- -" s:NetrwRemoteListing: {{{2 -fun! s:NetrwRemoteListing() - " call Dfunc("s:NetrwRemoteListing() b:netrw_curdir<".b:netrw_curdir.">) win#".winnr()) - - if !exists("w:netrw_bannercnt") && exists("s:bannercnt") - let w:netrw_bannercnt= s:bannercnt - endif - if !exists("w:netrw_bannercnt") && exists("b:bannercnt") - let w:netrw_bannercnt= b:bannercnt - endif - - call s:RemotePathAnalysis(b:netrw_curdir) - - " sanity check: - if exists("b:netrw_method") && b:netrw_method =~ '[235]' - " call Decho("b:netrw_method=".b:netrw_method,'~'.expand("")) - if !executable("ftp") - " call Decho("ftp is not executable",'~'.expand("")) - if !exists("g:netrw_quiet") - call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ftp",18) - endif - call s:NetrwOptionsRestore("w:") - " call Dret("s:NetrwRemoteListing -1") - return -1 - endif - - elseif !exists("g:netrw_list_cmd") || g:netrw_list_cmd == '' - " call Decho("g:netrw_list_cmd<",(exists("g:netrw_list_cmd")? 'n/a' : "-empty-").">",'~'.expand("")) - if !exists("g:netrw_quiet") - if g:netrw_list_cmd == "" - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your g:netrw_list_cmd is empty; perhaps ".g:netrw_ssh_cmd." is not executable on your system",47) - else - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ".g:netrw_list_cmd,19) - endif - endif - - NetrwKeepj call s:NetrwOptionsRestore("w:") - " call Dret("s:NetrwRemoteListing -1") - return -1 - endif " (remote handling sanity check) - " call Decho("passed remote listing sanity checks",'~'.expand("")) - - if exists("b:netrw_method") - " call Decho("setting w:netrw_method to b:netrw_method<".b:netrw_method.">",'~'.expand("")) - let w:netrw_method= b:netrw_method - endif - - if s:method == "ftp" - " use ftp to get remote file listing {{{3 - " call Decho("use ftp to get remote file listing",'~'.expand("")) - let s:method = "ftp" - let listcmd = g:netrw_ftp_list_cmd - if g:netrw_sort_by =~# '^t' - let listcmd= g:netrw_ftp_timelist_cmd - elseif g:netrw_sort_by =~# '^s' - let listcmd= g:netrw_ftp_sizelist_cmd - endif - " call Decho("listcmd<".listcmd."> (using g:netrw_ftp_list_cmd)",'~'.expand("")) - call s:NetrwRemoteFtpCmd(s:path,listcmd) - " exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("raw listing: ".getline("."),''~''.expand(""))' - - " report on missing file or directory messages - if search('[Nn]o such file or directory\|Failed to change directory') - let mesg= getline(".") - if exists("w:netrw_bannercnt") - setl ma - exe w:netrw_bannercnt.",$d _" - setl noma - endif - NetrwKeepj call s:NetrwOptionsRestore("w:") - call netrw#ErrorMsg(s:WARNING,mesg,96) - " call Dret("s:NetrwRemoteListing : -1") - return -1 - endif - - if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST) - " shorten the listing - " call Decho("generate short listing",'~'.expand("")) - exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt - - " cleanup - if g:netrw_ftp_browse_reject != "" - exe "sil! keepalt NetrwKeepj g/".g:netrw_ftp_browse_reject."/NetrwKeepj d" - NetrwKeepj call histdel("/",-1) - endif - sil! NetrwKeepj %s/\r$//e - NetrwKeepj call histdel("/",-1) - - " if there's no ../ listed, then put ../ in - let line1= line(".") - exe "sil! NetrwKeepj ".w:netrw_bannercnt - let line2= search('\.\.\/\%(\s\|$\)','cnW') - " call Decho("search(".'\.\.\/\%(\s\|$\)'."','cnW')=".line2." w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand("")) - if line2 == 0 - " call Decho("netrw is putting ../ into listing",'~'.expand("")) - sil! NetrwKeepj put='../' - endif - exe "sil! NetrwKeepj ".line1 - sil! NetrwKeepj norm! 0 - - " call Decho("line1=".line1." line2=".line2." line(.)=".line("."),'~'.expand("")) - if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup - " call Decho("M$ ftp cleanup",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+//' - NetrwKeepj call histdel("/",-1) - else " normal ftp cleanup - " call Decho("normal ftp cleanup",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2/e' - exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*/$#/#e' - exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*$#/#e' - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - endif - endif - - else - " use ssh to get remote file listing {{{3 - " call Decho("use ssh to get remote file listing: s:path<".s:path.">",'~'.expand("")) - let listcmd= s:MakeSshCmd(g:netrw_list_cmd) - " call Decho("listcmd<".listcmd."> (using g:netrw_list_cmd)",'~'.expand("")) - if g:netrw_scp_cmd =~ '^pscp' - " call Decho("1: exe r! ".s:ShellEscape(listcmd.s:path, 1),'~'.expand("")) - exe "NetrwKeepj r! ".listcmd.s:ShellEscape(s:path, 1) - " remove rubbish and adjust listing format of 'pscp' to 'ssh ls -FLa' like - sil! NetrwKeepj g/^Listing directory/NetrwKeepj d - sil! NetrwKeepj g/^d[-rwx][-rwx][-rwx]/NetrwKeepj s+$+/+e - sil! NetrwKeepj g/^l[-rwx][-rwx][-rwx]/NetrwKeepj s+$+@+e - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - if g:netrw_liststyle != s:LONGLIST - sil! NetrwKeepj g/^[dlsp-][-rwx][-rwx][-rwx]/NetrwKeepj s/^.*\s\(\S\+\)$/\1/e - NetrwKeepj call histdel("/",-1) - endif - else - if s:path == "" - " call Decho("2: exe r! ".listcmd,'~'.expand("")) - exe "NetrwKeepj keepalt r! ".listcmd - else - " call Decho("3: exe r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1),'~'.expand("")) - exe "NetrwKeepj keepalt r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1) - " call Decho("listcmd<".listcmd."> path<".s:path.">",'~'.expand("")) - endif - endif - - " cleanup - if g:netrw_ssh_browse_reject != "" - " call Decho("cleanup: exe sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d",'~'.expand("")) - exe "sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d" - NetrwKeepj call histdel("/",-1) - endif - endif - - if w:netrw_liststyle == s:LONGLIST - " do a long listing; these substitutions need to be done prior to sorting {{{3 - " call Decho("fix long listing:",'~'.expand("")) - - if s:method == "ftp" - " cleanup - exe "sil! NetrwKeepj ".w:netrw_bannercnt - while getline('.') =~# g:netrw_ftp_browse_reject - sil! NetrwKeepj d - endwhile - " if there's no ../ listed, then put ../ in - let line1= line(".") - sil! NetrwKeepj 1 - sil! NetrwKeepj call search('^\.\.\/\%(\s\|$\)','W') - let line2= line(".") - if line2 == 0 - if b:netrw_curdir != '/' - exe 'sil! NetrwKeepj '.w:netrw_bannercnt."put='../'" - endif - endif - exe "sil! NetrwKeepj ".line1 - sil! NetrwKeepj norm! 0 - endif - - if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup - " call Decho("M$ ftp site listing cleanup",'~'.expand("")) - exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+\)\(\w.*\)$/\2\t\1/' - elseif exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") - " call Decho("normal ftp site listing cleanup: bannercnt=".w:netrw_bannercnt." line($)=".line("$"),'~'.expand("")) - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/ -> .*$//e' - exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2 \t\1/e' - exe 'sil NetrwKeepj '.w:netrw_bannercnt - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - NetrwKeepj call histdel("/",-1) - endif - endif - - " if exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") " Decho - " exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("listing: ".getline("."),''~''.expand(""))' - " endif " Decho - - " call Dret("s:NetrwRemoteListing 0") - return 0 -endfun - -" --------------------------------------------------------------------- -" s:NetrwRemoteRm: remove/delete a remote file or directory {{{2 -fun! s:NetrwRemoteRm(usrhost,path) range - let svpos= winsaveview() - - let all= 0 - if exists("s:netrwmarkfilelist_{bufnr('%')}") - " remove all marked files - for fname in s:netrwmarkfilelist_{bufnr("%")} - let ok= s:NetrwRemoteRmFile(a:path,fname,all) - if ok =~# 'q\%[uit]' - break - elseif ok =~# 'a\%[ll]' - let all= 1 - endif - endfor - call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) - - else - " remove files specified by range - - " preparation for removing multiple files/directories - let keepsol = &l:sol - setl nosol - let ctr = a:firstline - - " remove multiple files and directories - while ctr <= a:lastline - exe "NetrwKeepj ".ctr - let ok= s:NetrwRemoteRmFile(a:path,s:NetrwGetWord(),all) - if ok =~# 'q\%[uit]' - break - elseif ok =~# 'a\%[ll]' - let all= 1 - endif - let ctr= ctr + 1 - endwhile - let &l:sol = keepsol - endif - - " refresh the (remote) directory listing - NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) - NetrwKeepj call winrestview(svpos) -endfun - -" --------------------------------------------------------------------- -" s:NetrwRemoteRmFile: {{{2 -fun! s:NetrwRemoteRmFile(path,rmfile,all) - " call Dfunc("s:NetrwRemoteRmFile(path<".a:path."> rmfile<".a:rmfile.">) all=".a:all) - - let all= a:all - let ok = "" - - if a:rmfile !~ '^"' && (a:rmfile =~ '@$' || a:rmfile !~ '[\/]$') - " attempt to remove file - " call Decho("attempt to remove file (all=".all.")",'~'.expand("")) - if !all - echohl Statement - " call Decho("case all=0:",'~'.expand("")) - call inputsave() - let ok= input("Confirm deletion of file<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") - call inputrestore() - echohl NONE - if ok == "" - let ok="no" - endif - let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') - if ok =~# 'a\%[ll]' - let all= 1 - endif - endif - - if all || ok =~# 'y\%[es]' || ok == "" - " call Decho("case all=".all." or ok<".ok.">".(exists("w:netrw_method")? ': netrw_method='.w:netrw_method : ""),'~'.expand("")) - if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) - " call Decho("case ftp:",'~'.expand("")) - let path= a:path - if path =~ '^\a\{3,}://' - let path= substitute(path,'^\a\{3,}://[^/]\+/','','') - endif - sil! NetrwKeepj .,$d _ - call s:NetrwRemoteFtpCmd(path,"delete ".'"'.a:rmfile.'"') - else - " call Decho("case ssh: g:netrw_rm_cmd<".g:netrw_rm_cmd.">",'~'.expand("")) - let netrw_rm_cmd= s:MakeSshCmd(g:netrw_rm_cmd) - " call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand("")) - if !exists("b:netrw_curdir") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53) - let ok="q" - else - let remotedir= substitute(b:netrw_curdir,'^.\{-}//[^/]\+/\(.*\)$','\1','') - " call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand("")) - " call Decho("remotedir<".remotedir.">",'~'.expand("")) - " call Decho("rmfile<".a:rmfile.">",'~'.expand("")) - if remotedir != "" - let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(remotedir.a:rmfile)) - else - let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(a:rmfile)) - endif - " call Decho("call system(".netrw_rm_cmd.")",'~'.expand("")) - let ret= system(netrw_rm_cmd) - if v:shell_error != 0 - if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir - call netrw#ErrorMsg(s:ERROR,"remove failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",102) - else - call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60) - endif - elseif ret != 0 - call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60) - endif - " call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand("")) - endif - endif - elseif ok =~# 'q\%[uit]' - " call Decho("ok==".ok,'~'.expand("")) - endif - - else - " attempt to remove directory - " call Decho("attempt to remove directory",'~'.expand("")) - if !all - call inputsave() - let ok= input("Confirm deletion of directory<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") - call inputrestore() - if ok == "" - let ok="no" - endif - let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') - if ok =~# 'a\%[ll]' - let all= 1 - endif - endif - - if all || ok =~# 'y\%[es]' || ok == "" - if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) - NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rmdir ".a:rmfile) - else - let rmfile = substitute(a:path.a:rmfile,'/$','','') - let netrw_rmdir_cmd = s:MakeSshCmd(netrw#WinPath(g:netrw_rmdir_cmd)).' '.s:ShellEscape(netrw#WinPath(rmfile)) - " call Decho("attempt to remove dir: system(".netrw_rmdir_cmd.")",'~'.expand("")) - let ret= system(netrw_rmdir_cmd) - " call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand("")) - - if v:shell_error != 0 - " call Decho("v:shell_error not 0",'~'.expand("")) - let netrw_rmf_cmd= s:MakeSshCmd(netrw#WinPath(g:netrw_rmf_cmd)).' '.s:ShellEscape(netrw#WinPath(substitute(rmfile,'[\/]$','','e'))) - " call Decho("2nd attempt to remove dir: system(".netrw_rmf_cmd.")",'~'.expand("")) - let ret= system(netrw_rmf_cmd) - " call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand("")) - - if v:shell_error != 0 && !exists("g:netrw_quiet") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",22) - endif - endif - endif - - elseif ok =~# 'q\%[uit]' - " call Decho("ok==".ok,'~'.expand("")) - endif - endif - - " call Dret("s:NetrwRemoteRmFile ".ok) - return ok -endfun - -" --------------------------------------------------------------------- -" s:NetrwRemoteRename: rename a remote file or directory {{{2 -fun! s:NetrwRemoteRename(usrhost,path) range - - " preparation for removing multiple files/directories - let svpos = winsaveview() - " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) - let ctr = a:firstline - let rename_cmd = s:MakeSshCmd(g:netrw_rename_cmd) - - " rename files given by the markfilelist - if exists("s:netrwmarkfilelist_{bufnr('%')}") - for oldname in s:netrwmarkfilelist_{bufnr("%")} - if exists("subfrom") - let newname= substitute(oldname,subfrom,subto,'') - else - call inputsave() - let newname= input("Moving ".oldname." to : ",oldname) - call inputrestore() - if newname =~ '^s/' - let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','') - let subto = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','') - let newname = substitute(oldname,subfrom,subto,'') - endif - endif - - if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) - NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname) - else - let oldname= s:ShellEscape(a:path.oldname) - let newname= s:ShellEscape(a:path.newname) - let ret = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname) - endif - - endfor - call s:NetrwUnMarkFile(1) - - else - - " attempt to rename files/directories - let keepsol= &l:sol - setl nosol - while ctr <= a:lastline - exe "NetrwKeepj ".ctr - - let oldname= s:NetrwGetWord() - - call inputsave() - let newname= input("Moving ".oldname." to : ",oldname) - call inputrestore() - - if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) - call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname) - else - let oldname= s:ShellEscape(a:path.oldname) - let newname= s:ShellEscape(a:path.newname) - let ret = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname) - endif - - let ctr= ctr + 1 - endwhile - let &l:sol= keepsol - endif - - " refresh the directory - NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) - NetrwKeepj call winrestview(svpos) -endfun - -" ========================================== -" Local Directory Browsing Support: {{{1 -" ========================================== - -" --------------------------------------------------------------------- -" netrw#FileUrlEdit: handles editing file://* files {{{2 -" Should accept: file://localhost/etc/fstab -" file:///etc/fstab -" file:///c:/WINDOWS/clock.avi -" file:///c|/WINDOWS/clock.avi -" file://localhost/c:/WINDOWS/clock.avi -" file://localhost/c|/WINDOWS/clock.avi -" file://c:/foo.txt -" file:///c:/foo.txt -" and %XX (where X is [0-9a-fA-F] is converted into a character with the given hexadecimal value -fun! netrw#FileUrlEdit(fname) - " call Dfunc("netrw#FileUrlEdit(fname<".a:fname.">)") - let fname = a:fname - if fname =~ '^file://localhost/' - " call Decho('converting file://localhost/ -to- file:///','~'.expand("")) - let fname= substitute(fname,'^file://localhost/','file:///','') - " call Decho("fname<".fname.">",'~'.expand("")) - endif - if has("win32") - if fname =~ '^file:///\=\a[|:]/' - " call Decho('converting file:///\a|/ -to- file://\a:/','~'.expand("")) - let fname = substitute(fname,'^file:///\=\(\a\)[|:]/','file://\1:/','') - " call Decho("fname<".fname.">",'~'.expand("")) - endif - endif - let fname2396 = netrw#RFC2396(fname) - let fname2396e= fnameescape(fname2396) - let plainfname= substitute(fname2396,'file://\(.*\)','\1',"") - if has("win32") - " call Decho("windows exception for plainfname",'~'.expand("")) - if plainfname =~ '^/\+\a:' - " call Decho('removing leading "/"s','~'.expand("")) - let plainfname= substitute(plainfname,'^/\+\(\a:\)','\1','') - endif - endif - - " call Decho("fname2396<".fname2396.">",'~'.expand("")) - " call Decho("plainfname<".plainfname.">",'~'.expand("")) - exe "sil doau BufReadPre ".fname2396e - exe 'NetrwKeepj keepalt edit '.plainfname - exe 'sil! NetrwKeepj keepalt bdelete '.fnameescape(a:fname) - - " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - " call Dret("netrw#FileUrlEdit") - exe "sil doau BufReadPost ".fname2396e -endfun - -" --------------------------------------------------------------------- -" netrw#LocalBrowseCheck: {{{2 -fun! netrw#LocalBrowseCheck(dirname) - " This function is called by netrwPlugin.vim's s:LocalBrowseCheck(), s:NetrwRexplore(), - " and by when atop a listed file/directory (via a buffer-local map) - " - " unfortunate interaction -- split window debugging can't be used here, must use - " D-echoRemOn or D-echoTabOn as the BufEnter event triggers - " another call to LocalBrowseCheck() when attempts to write - " to the DBG buffer are made. - " - " The &ft == "netrw" test was installed because the BufEnter event - " would hit when re-entering netrw windows, creating unexpected - " refreshes (and would do so in the middle of NetrwSaveOptions(), too) - " call Dfunc("netrw#LocalBrowseCheck(dirname<".a:dirname.">)") - " call Decho("isdir<".a:dirname."> =".isdirectory(s:NetrwFile(a:dirname)).((exists("s:treeforceredraw")? " treeforceredraw" : "")).'~'.expand("")) - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " getting E930: Cannot use :redir inside execute - "" call Dredir("ls!","netrw#LocalBrowseCheck") - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Decho("current buffer#".bufnr("%")."<".bufname("%")."> ft=".&ft,'~'.expand("")) - - let ykeep= @@ - if isdirectory(s:NetrwFile(a:dirname)) - " call Decho("is-directory ft<".&ft."> b:netrw_curdir<".(exists("b:netrw_curdir")? b:netrw_curdir : " doesn't exist")."> dirname<".a:dirname.">"." line($)=".line("$")." ft<".&ft."> g:netrw_fastbrowse=".g:netrw_fastbrowse,'~'.expand("")) - - if &ft != "netrw" || (exists("b:netrw_curdir") && b:netrw_curdir != a:dirname) || g:netrw_fastbrowse <= 1 - " call Decho("case 1 : ft=".&ft,'~'.expand("")) - " call Decho("s:rexposn_".bufnr("%")."<".bufname("%")."> ".(exists("s:rexposn_".bufnr("%"))? "exists" : "does not exist"),'~'.expand("")) - sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname) - - elseif &ft == "netrw" && line("$") == 1 - " call Decho("case 2 (ft≡netrw && line($)≡1)",'~'.expand("")) - sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname) - - elseif exists("s:treeforceredraw") - " call Decho("case 3 (treeforceredraw)",'~'.expand("")) - unlet s:treeforceredraw - sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname) - endif - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " call Dret("netrw#LocalBrowseCheck") - return - endif - - " The following code wipes out currently unused netrw buffers - " IF g:netrw_fastbrowse is zero (ie. slow browsing selected) - " AND IF the listing style is not a tree listing - if exists("g:netrw_fastbrowse") && g:netrw_fastbrowse == 0 && g:netrw_liststyle != s:TREELIST - " call Decho("wiping out currently unused netrw buffers",'~'.expand("")) - let ibuf = 1 - let buflast = bufnr("$") - while ibuf <= buflast - if bufwinnr(ibuf) == -1 && isdirectory(s:NetrwFile(bufname(ibuf))) - exe "sil! keepj keepalt ".ibuf."bw!" - endif - let ibuf= ibuf + 1 - endwhile - endif - let @@= ykeep - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) - " not a directory, ignore it - " call Dret("netrw#LocalBrowseCheck : not a directory, ignoring it; dirname<".a:dirname.">") -endfun - -" --------------------------------------------------------------------- -" s:LocalBrowseRefresh: this function is called after a user has {{{2 -" performed any shell command. The idea is to cause all local-browsing -" buffers to be refreshed after a user has executed some shell command, -" on the chance that s/he removed/created a file/directory with it. -fun! s:LocalBrowseRefresh() - " determine which buffers currently reside in a tab - if !exists("s:netrw_browselist") - return - endif - if !exists("w:netrw_bannercnt") - return - endif - if !empty(getcmdwintype()) - " cannot move away from cmdline window, see :h E11 - return - endif - if exists("s:netrw_events") && s:netrw_events == 1 - " s:LocalFastBrowser gets called (indirectly) from a - let s:netrw_events= 2 - return - endif - let itab = 1 - let buftablist = [] - let ykeep = @@ - while itab <= tabpagenr("$") - let buftablist = buftablist + tabpagebuflist() - let itab = itab + 1 - sil! tabn - endwhile - " GO through all buffers on netrw_browselist (ie. just local-netrw buffers): - " | refresh any netrw window - " | wipe out any non-displaying netrw buffer - let curwinid = win_getid(winnr()) - let ibl = 0 - for ibuf in s:netrw_browselist - if bufwinnr(ibuf) == -1 && index(buftablist,ibuf) == -1 - " wipe out any non-displaying netrw buffer - " (ibuf not shown in a current window AND - " ibuf not in any tab) - exe "sil! keepj bd ".fnameescape(ibuf) - call remove(s:netrw_browselist,ibl) - continue - elseif index(tabpagebuflist(),ibuf) != -1 - " refresh any netrw buffer - exe bufwinnr(ibuf)."wincmd w" - if getline(".") =~# 'Quick Help' - " decrement g:netrw_quickhelp to prevent refresh from changing g:netrw_quickhelp - " (counteracts s:NetrwBrowseChgDir()'s incrementing) - let g:netrw_quickhelp= g:netrw_quickhelp - 1 - endif - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop) - endif - NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) - endif - let ibl= ibl + 1 - endfor - call win_gotoid(curwinid) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:LocalFastBrowser: handles setting up/taking down fast browsing for the local browser {{{2 -" -" g:netrw_ Directory Is -" fastbrowse Local Remote -" slow 0 D D D=Deleting a buffer implies it will not be re-used (slow) -" med 1 D H H=Hiding a buffer implies it may be re-used (fast) -" fast 2 H H -" -" Deleting a buffer means that it will be re-loaded when examined, hence "slow". -" Hiding a buffer means that it will be re-used when examined, hence "fast". -" (re-using a buffer may not be as accurate) -" -" s:netrw_events : doesn't exist, s:LocalFastBrowser() will install autocmds with medium-speed or fast browsing -" =1: autocmds installed, but ignore next FocusGained event to avoid initial double-refresh of listing. -" BufEnter may be first event, then a FocusGained event. Ignore the first FocusGained event. -" If :Explore used: it sets s:netrw_events to 2, so no FocusGained events are ignored. -" =2: autocmds installed (doesn't ignore any FocusGained events) -fun! s:LocalFastBrowser() - - " initialize browselist, a list of buffer numbers that the local browser has used - if !exists("s:netrw_browselist") - let s:netrw_browselist= [] - endif - - " append current buffer to fastbrowse list - if empty(s:netrw_browselist) || bufnr("%") > s:netrw_browselist[-1] - call add(s:netrw_browselist,bufnr("%")) - endif - - " enable autocmd events to handle refreshing/removing local browser buffers - " If local browse buffer is currently showing: refresh it - " If local browse buffer is currently hidden : wipe it - " g:netrw_fastbrowse=0 : slow speed, never re-use directory listing - " =1 : medium speed, re-use directory listing for remote only - " =2 : fast speed, always re-use directory listing when possible - if g:netrw_fastbrowse <= 1 && !exists("#ShellCmdPost") && !exists("s:netrw_events") - let s:netrw_events= 1 - augroup AuNetrwEvent - au! - if has("win32") - au ShellCmdPost * call s:LocalBrowseRefresh() - else - au ShellCmdPost,FocusGained * call s:LocalBrowseRefresh() - endif - augroup END - - " user must have changed fastbrowse to its fast setting, so remove - " the associated autocmd events - elseif g:netrw_fastbrowse > 1 && exists("#ShellCmdPost") && exists("s:netrw_events") - unlet s:netrw_events - augroup AuNetrwEvent - au! - augroup END - augroup! AuNetrwEvent - endif -endfun - -fun! s:NetrwLocalListingList(dirname,setmaxfilenamelen) - " get the list of files contained in the current directory - let dirname = a:dirname - let dirnamelen = strlen(dirname) - let filelist = s:NetrwGlob(dirname,"*",0) - let filelist = filelist + s:NetrwGlob(dirname,".*",0) - - if g:netrw_cygwin == 0 && has("win32") - elseif index(filelist,'..') == -1 && dirname !~ '/' - " include ../ in the glob() entry if its missing - let filelist= filelist+[s:ComposePath(dirname,"../")] - endif - - if a:setmaxfilenamelen && get(g:, 'netrw_dynamic_maxfilenamelen', 0) - let filelistcopy = map(deepcopy(filelist),'fnamemodify(v:val, ":t")') - let g:netrw_maxfilenamelen = max(map(filelistcopy,'len(v:val)')) + 1 - endif - - let resultfilelist = [] - for filename in filelist - - if getftype(filename) == "link" - " indicate a symbolic link - let pfile= filename."@" - - elseif getftype(filename) == "socket" - " indicate a socket - let pfile= filename."=" - - elseif getftype(filename) == "fifo" - " indicate a fifo - let pfile= filename."|" - - elseif isdirectory(s:NetrwFile(filename)) - " indicate a directory - let pfile= filename."/" - - elseif exists("b:netrw_curdir") && b:netrw_curdir !~ '^.*://' && !isdirectory(s:NetrwFile(filename)) - if has("win32") - if filename =~ '\.[eE][xX][eE]$' || filename =~ '\.[cC][oO][mM]$' || filename =~ '\.[bB][aA][tT]$' - " indicate an executable - let pfile= filename."*" - else - " normal file - let pfile= filename - endif - elseif executable(filename) - " indicate an executable - let pfile= filename."*" - else - " normal file - let pfile= filename - endif - - else - " normal file - let pfile= filename - endif - - if pfile =~ '//$' - let pfile= substitute(pfile,'//$','/','e') - endif - let pfile= strpart(pfile,dirnamelen) - let pfile= substitute(pfile,'^[/\\]','','e') - - if w:netrw_liststyle == s:LONGLIST - let longfile = printf("%-".g:netrw_maxfilenamelen."S",pfile) - let sz = getfsize(filename) - let szlen = 15 - (strdisplaywidth(longfile) - g:netrw_maxfilenamelen) - let szlen = (szlen > 0) ? szlen : 0 - - if g:netrw_sizestyle =~# "[hH]" - let sz= s:NetrwHumanReadable(sz) - endif - let fsz = printf("%".szlen."S",sz) - let pfile= longfile." ".fsz." ".strftime(g:netrw_timefmt,getftime(filename)) - endif - - if g:netrw_sort_by =~# "^t" - " sort by time (handles time up to 1 quintillion seconds, US) - " Decorate listing by prepending a timestamp/ . Sorting will then be done based on time. - let t = getftime(filename) - let ft = printf("%018d",t) - let ftpfile= ft.'/'.pfile - let resultfilelist += [ftpfile] - - elseif g:netrw_sort_by =~ "^s" - " sort by size (handles file sizes up to 1 quintillion bytes, US) - let sz = getfsize(filename) - let fsz = printf("%018d",sz) - let fszpfile= fsz.'/'.pfile - let resultfilelist += [fszpfile] - - else - " sort by name - let resultfilelist += [pfile] - endif - endfor - - return resultfilelist -endfun - -" --------------------------------------------------------------------- -" s:LocalListing: does the job of "ls" for local directories {{{2 -fun! s:LocalListing() - - let filelist = s:NetrwLocalListingList(b:netrw_curdir, 1) - for filename in filelist - sil! NetrwKeepj put =filename - endfor - - " cleanup any windows mess at end-of-line - sil! NetrwKeepj g/^$/d - sil! NetrwKeepj %s/\r$//e - call histdel("/",-1) - exe "setl ts=".(g:netrw_maxfilenamelen+1) -endfun - -" --------------------------------------------------------------------- -" s:NetrwLocalExecute: uses system() to execute command under cursor ("X" command support) {{{2 -fun! s:NetrwLocalExecute(cmd) - " call Dfunc("s:NetrwLocalExecute(cmd<".a:cmd.">)") - let ykeep= @@ - " sanity check - if !executable(a:cmd) - call netrw#ErrorMsg(s:ERROR,"the file<".a:cmd."> is not executable!",89) - let @@= ykeep - " call Dret("s:NetrwLocalExecute") - return - endif - - let optargs= input(":!".a:cmd,"","file") - " call Decho("optargs<".optargs.">",'~'.expand("")) - let result= system(a:cmd.optargs) - " call Decho("result,'~'.expand("")) - - " strip any ansi escape sequences off - let result = substitute(result,"\e\\[[0-9;]*m","","g") - - " show user the result(s) - echomsg result - let @@= ykeep - - " call Dret("s:NetrwLocalExecute") -endfun - -" --------------------------------------------------------------------- -" s:NetrwLocalRename: rename a local file or directory {{{2 -fun! s:NetrwLocalRename(path) range - - if !exists("w:netrw_bannercnt") - let w:netrw_bannercnt= b:netrw_bannercnt - endif - - " preparation for removing multiple files/directories - let ykeep = @@ - let ctr = a:firstline - let svpos = winsaveview() - let all = 0 - - " rename files given by the markfilelist - if exists("s:netrwmarkfilelist_{bufnr('%')}") - for oldname in s:netrwmarkfilelist_{bufnr("%")} - if exists("subfrom") - let newname= substitute(oldname,subfrom,subto,'') - else - call inputsave() - let newname= input("Moving ".oldname." to : ",oldname,"file") - call inputrestore() - if newname =~ '' - " two ctrl-x's : ignore all of string preceding the ctrl-x's - let newname = substitute(newname,'^.*','','') - elseif newname =~ '' - " one ctrl-x : ignore portion of string preceding ctrl-x but after last / - let newname = substitute(newname,'[^/]*','','') - endif - if newname =~ '^s/' - let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','') - let subto = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','') - let newname = substitute(oldname,subfrom,subto,'') - endif - endif - if !all && filereadable(newname) - call inputsave() - let response= input("File<".newname."> already exists; do you want to overwrite it? (y/all/n) ") - call inputrestore() - if response == "all" - let all= 1 - elseif response != "y" && response != "yes" - " refresh the directory - NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) - NetrwKeepj call winrestview(svpos) - let @@= ykeep - return - endif - endif - call rename(oldname,newname) - endfor - call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) - - else - - " attempt to rename files/directories - while ctr <= a:lastline - exe "NetrwKeepj ".ctr - - " sanity checks - if line(".") < w:netrw_bannercnt - let ctr= ctr + 1 - continue - endif - let curword= s:NetrwGetWord() - if curword == "./" || curword == "../" - let ctr= ctr + 1 - continue - endif - - NetrwKeepj norm! 0 - let oldname= s:ComposePath(a:path,curword) - - call inputsave() - let newname= input("Moving ".oldname." to : ",substitute(oldname,'/*$','','e')) - call inputrestore() - - call rename(oldname,newname) - let ctr= ctr + 1 - endwhile - endif - - " refresh the directory - NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) - NetrwKeepj call winrestview(svpos) - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwLocalRm: {{{2 -fun! s:NetrwLocalRm(path) range - if !exists("w:netrw_bannercnt") - let w:netrw_bannercnt= b:netrw_bannercnt - endif - - " preparation for removing multiple files/directories - let ykeep = @@ - let ret = 0 - let all = 0 - let svpos = winsaveview() - - if exists("s:netrwmarkfilelist_{bufnr('%')}") - " remove all marked files - for fname in s:netrwmarkfilelist_{bufnr("%")} - let ok= s:NetrwLocalRmFile(a:path,fname,all) - if ok =~# 'q\%[uit]' || ok == "no" - break - elseif ok =~# '^a\%[ll]$' - let all= 1 - endif - endfor - call s:NetrwUnMarkFile(1) - - else - " remove (multiple) files and directories - - let keepsol= &l:sol - setl nosol - let ctr = a:firstline - while ctr <= a:lastline - exe "NetrwKeepj ".ctr - - " sanity checks - if line(".") < w:netrw_bannercnt - let ctr= ctr + 1 - continue - endif - let curword= s:NetrwGetWord() - if curword == "./" || curword == "../" - let ctr= ctr + 1 - continue - endif - let ok= s:NetrwLocalRmFile(a:path,curword,all) - if ok =~# 'q\%[uit]' || ok == "no" - break - elseif ok =~# '^a\%[ll]$' - let all= 1 - endif - let ctr= ctr + 1 - endwhile - let &l:sol= keepsol - endif - - " refresh the directory - if bufname("%") != "NetrwMessage" - NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) - NetrwKeepj call winrestview(svpos) - endif - let @@= ykeep -endfun - -" --------------------------------------------------------------------- -" s:NetrwLocalRmFile: remove file fname given the path {{{2 -" Give confirmation prompt unless all==1 -fun! s:NetrwLocalRmFile(path,fname,all) - " call Dfunc("s:NetrwLocalRmFile(path<".a:path."> fname<".a:fname."> all=".a:all) - - let all= a:all - let ok = "" - NetrwKeepj norm! 0 - let rmfile= s:NetrwFile(s:ComposePath(a:path,escape(a:fname, '\\'))) - " call Decho("rmfile<".rmfile.">",'~'.expand("")) - - if rmfile !~ '^"' && (rmfile =~ '@$' || rmfile !~ '[\/]$') - " attempt to remove file - " call Decho("attempt to remove file<".rmfile.">",'~'.expand("")) - if !all - echohl Statement - call inputsave() - let ok= input("Confirm deletion of file <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") - call inputrestore() - echohl NONE - if ok == "" - let ok="no" - endif - " call Decho("response: ok<".ok.">",'~'.expand("")) - let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') - " call Decho("response: ok<".ok."> (after sub)",'~'.expand("")) - if ok =~# '^a\%[ll]$' - let all= 1 - endif - endif - - if all || ok =~# '^y\%[es]$' || ok == "" - let ret= s:NetrwDelete(rmfile) - " call Decho("errcode=".v:shell_error." ret=".ret,'~'.expand("")) - endif - - else - " attempt to remove directory - if !all - echohl Statement - call inputsave() - let ok= input("Confirm *recursive* deletion of directory <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") - call inputrestore() - let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') - if ok == "" - let ok="no" - endif - if ok =~# '^a\%[ll]$' - let all= 1 - endif - endif - let rmfile= substitute(rmfile,'[\/]$','','e') - - if all || ok =~# '^y\%[es]$' || ok == "" - if delete(rmfile,"rf") - call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103) - endif - endif - endif - - " call Dret("s:NetrwLocalRmFile ".ok) - return ok -endfun - -" ===================================================================== -" Support Functions: {{{1 - -" --------------------------------------------------------------------- -" netrw#Access: intended to provide access to variable values for netrw's test suite {{{2 -" 0: marked file list of current buffer -" 1: marked file target -fun! netrw#Access(ilist) - if a:ilist == 0 - if exists("s:netrwmarkfilelist_".bufnr('%')) - return s:netrwmarkfilelist_{bufnr('%')} - else - return "no-list-buf#".bufnr('%') - endif - elseif a:ilist == 1 - return s:netrwmftgt - endif -endfun - -" --------------------------------------------------------------------- -" netrw#Call: allows user-specified mappings to call internal netrw functions {{{2 -fun! netrw#Call(funcname,...) - return call("s:".a:funcname,a:000) -endfun - -" --------------------------------------------------------------------- -" netrw#Expose: allows UserMaps and pchk to look at otherwise script-local variables {{{2 -" I expect this function to be used in -" :PChkAssert netrw#Expose("netrwmarkfilelist") -" for example. -fun! netrw#Expose(varname) - " call Dfunc("netrw#Expose(varname<".a:varname.">)") - if exists("s:".a:varname) - exe "let retval= s:".a:varname - " call Decho("retval=".retval,'~'.expand("")) - if exists("g:netrw_pchk") - " call Decho("type(g:netrw_pchk=".g:netrw_pchk.")=".type(retval),'~'.expand("")) - if type(retval) == 3 - let retval = copy(retval) - let i = 0 - while i < len(retval) - let retval[i]= substitute(retval[i],expand("$HOME"),'~','') - let i = i + 1 - endwhile - endif - " call Dret("netrw#Expose ".string(retval)),'~'.expand("")) - return string(retval) - else - " call Decho("g:netrw_pchk doesn't exist",'~'.expand("")) - endif - else - " call Decho("s:".a:varname." doesn't exist",'~'.expand("")) - let retval= "n/a" - endif - - " call Dret("netrw#Expose ".string(retval)) - return retval -endfun - -" --------------------------------------------------------------------- -" netrw#Modify: allows UserMaps to set (modify) script-local variables {{{2 -fun! netrw#Modify(varname,newvalue) - " call Dfunc("netrw#Modify(varname<".a:varname.">,newvalue<".string(a:newvalue).">)") - exe "let s:".a:varname."= ".string(a:newvalue) - " call Dret("netrw#Modify") -endfun - -" --------------------------------------------------------------------- -" netrw#RFC2396: converts %xx into characters {{{2 -fun! netrw#RFC2396(fname) - " call Dfunc("netrw#RFC2396(fname<".a:fname.">)") - let fname = escape(substitute(a:fname,'%\(\x\x\)','\=printf("%c","0x".submatch(1))','ge')," \t") - " call Dret("netrw#RFC2396 ".fname) - return fname -endfun - -" --------------------------------------------------------------------- -" netrw#UserMaps: supports user-specified maps {{{2 -" see :help function() -" -" g:Netrw_UserMaps is a List with members such as: -" [[keymap sequence, function reference],...] -" -" The referenced function may return a string, -" refresh : refresh the display -" -other- : this string will be executed -" or it may return a List of strings. -" -" Each keymap-sequence will be set up with a nnoremap -" to invoke netrw#UserMaps(a:islocal). -" Related functions: -" netrw#Expose(varname) -- see s:varname variables -" netrw#Modify(varname,newvalue) -- modify value of s:varname variable -" netrw#Call(funcname,...) -- call internal netrw function with optional arguments -fun! netrw#UserMaps(islocal) - " call Dfunc("netrw#UserMaps(islocal=".a:islocal.")") - " call Decho("g:Netrw_UserMaps ".(exists("g:Netrw_UserMaps")? "exists" : "does NOT exist"),'~'.expand("")) - - " set up usermaplist - if exists("g:Netrw_UserMaps") && type(g:Netrw_UserMaps) == 3 - " call Decho("g:Netrw_UserMaps has type 3",'~'.expand("")) - for umap in g:Netrw_UserMaps - " call Decho("type(umap[0]<".string(umap[0]).">)=".type(umap[0])." (should be 1=string)",'~'.expand("")) - " call Decho("type(umap[1])=".type(umap[1])." (should be 1=string)",'~'.expand("")) - " if umap[0] is a string and umap[1] is a string holding a function name - if type(umap[0]) == 1 && type(umap[1]) == 1 - " call Decho("nno ".umap[0]." :call s:UserMaps(".a:islocal.",".string(umap[1]).")",'~'.expand("")) - exe "nno ".umap[0]." :call UserMaps(".a:islocal.",'".umap[1]."')" - else - call netrw#ErrorMsg(s:WARNING,"ignoring usermap <".string(umap[0])."> -- not a [string,funcref] entry",99) - endif - endfor - endif - " call Dret("netrw#UserMaps") -endfun - -" --------------------------------------------------------------------- -" netrw#WinPath: tries to insure that the path is windows-acceptable, whether cygwin is used or not {{{2 -fun! netrw#WinPath(path) - " call Dfunc("netrw#WinPath(path<".a:path.">)") - if (!g:netrw_cygwin || &shell !~ '\%(\\|\\)\%(\.exe\)\=$') && has("win32") - " remove cygdrive prefix, if present - let path = substitute(a:path,g:netrw_cygdrive.'/\(.\)','\1:','') - " remove trailing slash (Win95) - let path = substitute(path, '\(\\\|/\)$', '', 'g') - " remove escaped spaces - let path = substitute(path, '\ ', ' ', 'g') - " convert slashes to backslashes - let path = substitute(path, '/', '\', 'g') - else - let path= a:path - endif - " call Dret("netrw#WinPath <".path.">") - return path -endfun - -" --------------------------------------------------------------------- -" s:StripTrailingSlash: removes trailing slashes from a path {{{2 -fun! s:StripTrailingSlash(path) - " remove trailing slash - return substitute(a:path, '[/\\]$', '', 'g') -endfun - -" --------------------------------------------------------------------- -" s:NetrwBadd: adds marked files to buffer list or vice versa {{{2 -" cb : bl2mf=0 add marked files to buffer list -" cB : bl2mf=1 use bufferlist to mark files -" (mnemonic: cb = copy (marked files) to buffer list) -fun! s:NetrwBadd(islocal,bl2mf) - " " call Dfunc("s:NetrwBadd(islocal=".a:islocal." mf2bl=".mf2bl.")") - if a:bl2mf - " cB: add buffer list to marked files - redir => bufl - ls - redir END - let bufl = map(split(bufl,"\n"),'substitute(v:val,''^.\{-}"\(.*\)".\{-}$'',''\1'','''')') - for fname in bufl - call s:NetrwMarkFile(a:islocal,fname) - endfor - else - " cb: add marked files to buffer list - for fname in s:netrwmarkfilelist_{bufnr("%")} - " " call Decho("badd ".fname,'~'.expand("")) - exe "badd ".fnameescape(fname) - endfor - let curbufnr = bufnr("%") - let curdir = s:NetrwGetCurdir(a:islocal) - call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer - endif - " call Dret("s:NetrwBadd") -endfun - -" --------------------------------------------------------------------- -" s:ComposePath: Appends a new part to a path taking different systems into consideration {{{2 -fun! s:ComposePath(base,subdir) - " call Dfunc("s:ComposePath(base<".a:base."> subdir<".a:subdir.">)") - - if has("amiga") - " call Decho("amiga",'~'.expand("")) - let ec = a:base[s:Strlen(a:base)-1] - if ec != '/' && ec != ':' - let ret = a:base."/" . a:subdir - else - let ret = a:base.a:subdir - endif - - " COMBAK: test on windows with changing to root directory: :e C:/ - elseif a:subdir =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32") - " call Decho("windows",'~'.expand("")) - let ret= a:subdir - - elseif a:base =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32") - " call Decho("windows",'~'.expand("")) - if a:base =~ '[/\\]$' - let ret= a:base.a:subdir - else - let ret= a:base.'/'.a:subdir - endif - - elseif a:base =~ '^\a\{3,}://' - " call Decho("remote linux/macos",'~'.expand("")) - let urlbase = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\1','') - let curpath = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\2','') - if a:subdir == '../' - if curpath =~ '[^/]/[^/]\+/$' - let curpath= substitute(curpath,'[^/]\+/$','','') - else - let curpath="" - endif - let ret= urlbase.curpath - else - let ret= urlbase.curpath.a:subdir - endif - " call Decho("urlbase<".urlbase.">",'~'.expand("")) - " call Decho("curpath<".curpath.">",'~'.expand("")) - " call Decho("ret<".ret.">",'~'.expand("")) - - else - " call Decho("local linux/macos",'~'.expand("")) - let ret = substitute(a:base."/".a:subdir,"//","/","g") - if a:base =~ '^//' - " keeping initial '//' for the benefit of network share listing support - let ret= '/'.ret - endif - let ret= simplify(ret) - endif - - " call Dret("s:ComposePath ".ret) - return ret -endfun - -" --------------------------------------------------------------------- -" s:DeleteBookmark: deletes a file/directory from Netrw's bookmark system {{{2 -" Related Functions: s:MakeBookmark() s:NetrwBookHistHandler() s:NetrwBookmark() -fun! s:DeleteBookmark(fname) - " call Dfunc("s:DeleteBookmark(fname<".a:fname.">)") - call s:MergeBookmarks() - - if exists("g:netrw_bookmarklist") - let indx= index(g:netrw_bookmarklist,a:fname) - if indx == -1 - let indx= 0 - while indx < len(g:netrw_bookmarklist) - if g:netrw_bookmarklist[indx] =~ a:fname - call remove(g:netrw_bookmarklist,indx) - let indx= indx - 1 - endif - let indx= indx + 1 - endwhile - else - " remove exact match - call remove(g:netrw_bookmarklist,indx) - endif - endif - - " call Dret("s:DeleteBookmark") -endfun - -" --------------------------------------------------------------------- -" s:FileReadable: o/s independent filereadable {{{2 -fun! s:FileReadable(fname) - " call Dfunc("s:FileReadable(fname<".a:fname.">)") - - if g:netrw_cygwin - let ret= filereadable(s:NetrwFile(substitute(a:fname,g:netrw_cygdrive.'/\(.\)','\1:/',''))) - else - let ret= filereadable(s:NetrwFile(a:fname)) - endif - - " call Dret("s:FileReadable ".ret) - return ret -endfun - -" --------------------------------------------------------------------- -" s:GetTempfile: gets a tempname that'll work for various o/s's {{{2 -" Places correct suffix on end of temporary filename, -" using the suffix provided with fname -fun! s:GetTempfile(fname) - " call Dfunc("s:GetTempfile(fname<".a:fname.">)") - - if !exists("b:netrw_tmpfile") - " get a brand new temporary filename - let tmpfile= tempname() - " call Decho("tmpfile<".tmpfile."> : from tempname()",'~'.expand("")) - - let tmpfile= substitute(tmpfile,'\','/','ge') - " call Decho("tmpfile<".tmpfile."> : chgd any \\ -> /",'~'.expand("")) - - " sanity check -- does the temporary file's directory exist? - if !isdirectory(s:NetrwFile(substitute(tmpfile,'[^/]\+$','','e'))) - " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your <".substitute(tmpfile,'[^/]\+$','','e')."> directory is missing!",2) - " call Dret("s:GetTempfile getcwd<".getcwd().">") - return "" - endif - - " let netrw#NetSource() know about the tmpfile - let s:netrw_tmpfile= tmpfile " used by netrw#NetSource() and netrw#BrowseX() - " call Decho("tmpfile<".tmpfile."> s:netrw_tmpfile<".s:netrw_tmpfile.">",'~'.expand("")) - - " o/s dependencies - if g:netrw_cygwin != 0 - let tmpfile = substitute(tmpfile,'^\(\a\):',g:netrw_cygdrive.'/\1','e') - elseif has("win32") - if !exists("+shellslash") || !&ssl - let tmpfile = substitute(tmpfile,'/','\','g') - endif - else - let tmpfile = tmpfile - endif - let b:netrw_tmpfile= tmpfile - " call Decho("o/s dependent fixed tempname<".tmpfile.">",'~'.expand("")) - else - " re-use temporary filename - let tmpfile= b:netrw_tmpfile - " call Decho("tmpfile<".tmpfile."> re-using",'~'.expand("")) - endif - - " use fname's suffix for the temporary file - if a:fname != "" - if a:fname =~ '\.[^./]\+$' - " call Decho("using fname<".a:fname.">'s suffix",'~'.expand("")) - if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$' - let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') - elseif a:fname =~ '.txz$' - let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') - else - let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') - endif - " call Decho("suffix<".suffix.">",'~'.expand("")) - let tmpfile= substitute(tmpfile,'\.tmp$','','e') - " call Decho("chgd tmpfile<".tmpfile."> (removed any .tmp suffix)",'~'.expand("")) - let tmpfile .= suffix - " call Decho("chgd tmpfile<".tmpfile."> (added ".suffix." suffix) netrw_fname<".b:netrw_fname.">",'~'.expand("")) - let s:netrw_tmpfile= tmpfile " supports netrw#NetSource() - endif - endif - - " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - " call Dret("s:GetTempfile <".tmpfile.">") - return tmpfile -endfun - -" --------------------------------------------------------------------- -" s:MakeSshCmd: transforms input command using USEPORT HOSTNAME into {{{2 -" a correct command for use with a system() call -fun! s:MakeSshCmd(sshcmd) - " call Dfunc("s:MakeSshCmd(sshcmd<".a:sshcmd.">) user<".s:user."> machine<".s:machine.">") - if s:user == "" - let sshcmd = substitute(a:sshcmd,'\',s:machine,'') - else - let sshcmd = substitute(a:sshcmd,'\',s:user."@".s:machine,'') - endif - if exists("g:netrw_port") && g:netrw_port != "" - let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'') - elseif exists("s:port") && s:port != "" - let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'') - else - let sshcmd= substitute(sshcmd,"USEPORT ",'','') - endif - " call Dret("s:MakeSshCmd <".sshcmd.">") - return sshcmd -endfun - -" --------------------------------------------------------------------- -" s:MakeBookmark: enters a bookmark into Netrw's bookmark system {{{2 -fun! s:MakeBookmark(fname) - " call Dfunc("s:MakeBookmark(fname<".a:fname.">)") - - if !exists("g:netrw_bookmarklist") - let g:netrw_bookmarklist= [] - endif - - if index(g:netrw_bookmarklist,a:fname) == -1 - " curdir not currently in g:netrw_bookmarklist, so include it - if isdirectory(s:NetrwFile(a:fname)) && a:fname !~ '/$' - call add(g:netrw_bookmarklist,a:fname.'/') - elseif a:fname !~ '/' - call add(g:netrw_bookmarklist,getcwd()."/".a:fname) - else - call add(g:netrw_bookmarklist,a:fname) - endif - call sort(g:netrw_bookmarklist) - endif - - " call Dret("s:MakeBookmark") -endfun - -" --------------------------------------------------------------------- -" s:MergeBookmarks: merge current bookmarks with saved bookmarks {{{2 -fun! s:MergeBookmarks() - " call Dfunc("s:MergeBookmarks() : merge current bookmarks into .netrwbook") - " get bookmarks from .netrwbook file - let savefile= s:NetrwHome()."/.netrwbook" - if filereadable(s:NetrwFile(savefile)) - " call Decho("merge bookmarks (active and file)",'~'.expand("")) - NetrwKeepj call s:NetrwBookHistSave() - " call Decho("bookmark delete savefile<".savefile.">",'~'.expand("")) - NetrwKeepj call delete(savefile) - endif - " call Dret("s:MergeBookmarks") -endfun - -" --------------------------------------------------------------------- -" s:NetrwBMShow: {{{2 -fun! s:NetrwBMShow() - " call Dfunc("s:NetrwBMShow()") - redir => bmshowraw - menu - redir END - let bmshowlist = split(bmshowraw,'\n') - if bmshowlist != [] - let bmshowfuncs= filter(bmshowlist,'v:val =~# "\\d\\+_BMShow()"') - if bmshowfuncs != [] - let bmshowfunc = substitute(bmshowfuncs[0],'^.*:\(call.*BMShow()\).*$','\1','') - if bmshowfunc =~# '^call.*BMShow()' - exe "sil! NetrwKeepj ".bmshowfunc - endif - endif - endif - " call Dret("s:NetrwBMShow : bmshowfunc<".(exists("bmshowfunc")? bmshowfunc : 'n/a').">") -endfun - -" --------------------------------------------------------------------- -" s:NetrwCursor: responsible for setting cursorline/cursorcolumn based upon g:netrw_cursor {{{2 -fun! s:NetrwCursor(editfile) - if !exists("w:netrw_liststyle") - let w:netrw_liststyle= g:netrw_liststyle - endif - " call Dfunc("s:NetrwCursor() ft<".&ft."> liststyle=".w:netrw_liststyle." g:netrw_cursor=".g:netrw_cursor." s:netrw_usercuc=".s:netrw_usercuc." s:netrw_usercul=".s:netrw_usercul) - - " call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul) - - if &ft != "netrw" - " if the current window isn't a netrw directory listing window, then use user cursorline/column - " settings. Affects when netrw is used to read/write a file using scp/ftp/etc. - " call Decho("case ft!=netrw: use user cul,cuc",'~'.expand("")) - - elseif g:netrw_cursor == 8 - if w:netrw_liststyle == s:WIDELIST - setl cursorline - setl cursorcolumn - else - setl cursorline - endif - elseif g:netrw_cursor == 7 - setl cursorline - elseif g:netrw_cursor == 6 - if w:netrw_liststyle == s:WIDELIST - setl cursorline - endif - elseif g:netrw_cursor == 4 - " all styles: cursorline, cursorcolumn - " call Decho("case g:netrw_cursor==4: setl cul cuc",'~'.expand("")) - setl cursorline - setl cursorcolumn - - elseif g:netrw_cursor == 3 - " thin-long-tree: cursorline, user's cursorcolumn - " wide : cursorline, cursorcolumn - if w:netrw_liststyle == s:WIDELIST - " call Decho("case g:netrw_cursor==3 and wide: setl cul cuc",'~'.expand("")) - setl cursorline - setl cursorcolumn - else - " call Decho("case g:netrw_cursor==3 and not wide: setl cul (use user's cuc)",'~'.expand("")) - setl cursorline - endif - - elseif g:netrw_cursor == 2 - " thin-long-tree: cursorline, user's cursorcolumn - " wide : cursorline, user's cursorcolumn - " call Decho("case g:netrw_cursor==2: setl cuc (use user's cul)",'~'.expand("")) - setl cursorline - - elseif g:netrw_cursor == 1 - " thin-long-tree: user's cursorline, user's cursorcolumn - " wide : cursorline, user's cursorcolumn - if w:netrw_liststyle == s:WIDELIST - " call Decho("case g:netrw_cursor==2 and wide: setl cul (use user's cuc)",'~'.expand("")) - setl cursorline - else - " call Decho("case g:netrw_cursor==2 and not wide: (use user's cul,cuc)",'~'.expand("")) - endif - - else - " all styles: user's cursorline, user's cursorcolumn - " call Decho("default: (use user's cul,cuc)",'~'.expand("")) - let &l:cursorline = s:netrw_usercul - let &l:cursorcolumn = s:netrw_usercuc - endif - - " call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul) - " call Dret("s:NetrwCursor : l:cursorline=".&l:cursorline." l:cursorcolumn=".&l:cursorcolumn) -endfun - -" --------------------------------------------------------------------- -" s:RestoreCursorline: restores cursorline/cursorcolumn to original user settings {{{2 -fun! s:RestoreCursorline() - " call Dfunc("s:RestoreCursorline() currently, cul=".&l:cursorline." cuc=".&l:cursorcolumn." win#".winnr()." buf#".bufnr("%")) - if exists("s:netrw_usercul") - let &l:cursorline = s:netrw_usercul - endif - if exists("s:netrw_usercuc") - let &l:cursorcolumn = s:netrw_usercuc - endif - " call Decho("(s:RestoreCursorline) COMBAK: cuc=".&l:cuc." cul=".&l:cul) - " call Dret("s:RestoreCursorline : restored cul=".&l:cursorline." cuc=".&l:cursorcolumn) -endfun - -" s:RestoreRegister: restores all registers given in the dict {{{2 -fun! s:RestoreRegister(dict) - for [key, val] in items(a:dict) - if key == 'unnamed' - let key = '' - endif - call setreg(key, val[0], val[1]) - endfor -endfun - -" --------------------------------------------------------------------- -" s:NetrwDelete: Deletes a file. {{{2 -" Uses Steve Hall's idea to insure that Windows paths stay -" acceptable. No effect on Unix paths. -" Examples of use: let result= s:NetrwDelete(path) -fun! s:NetrwDelete(path) - " call Dfunc("s:NetrwDelete(path<".a:path.">)") - - let path = netrw#WinPath(a:path) - if !g:netrw_cygwin && has("win32") - if exists("+shellslash") - let sskeep= &shellslash - setl noshellslash - let result = delete(path) - let &shellslash = sskeep - else - " call Decho("exe let result= ".a:cmd."('".path."')",'~'.expand("")) - let result= delete(path) - endif - else - " call Decho("let result= delete(".path.")",'~'.expand("")) - let result= delete(path) - endif - if result < 0 - NetrwKeepj call netrw#ErrorMsg(s:WARNING,"delete(".path.") failed!",71) - endif - - " call Dret("s:NetrwDelete ".result) - return result -endfun - -" --------------------------------------------------------------------- -" s:NetrwBufRemover: removes a buffer that: {{{2s -" has buffer-id > 1 -" is unlisted -" is unnamed -" does not appear in any window -fun! s:NetrwBufRemover(bufid) - " call Dfunc("s:NetrwBufRemover(".a:bufid.")") - " call Decho("buf#".a:bufid." ".((a:bufid > 1)? ">" : "≯")." must be >1 for removal","~".expand("")) - " call Decho("buf#".a:bufid." is ".(buflisted(a:bufid)? "listed" : "unlisted"),"~".expand("")) - " call Decho("buf#".a:bufid." has name <".bufname(a:bufid).">","~".expand("")) - " call Decho("buf#".a:bufid." has winid#".bufwinid(a:bufid),"~".expand("")) - - 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("")) - exe "sil! bd! ".a:bufid - endif - - " call Dret("s:NetrwBufRemover") -endfun - -" --------------------------------------------------------------------- -" s:NetrwEnew: opens a new buffer, passes netrw buffer variables through {{{2 -fun! s:NetrwEnew(...) - " call Dfunc("s:NetrwEnew() a:0=".a:0." win#".winnr()." winnr($)=".winnr("$")." bufnr($)=".bufnr("$")." expand(%)<".expand("%").">") - " call Decho("curdir<".((a:0>0)? a:1 : "")."> buf#".bufnr("%")."<".bufname("%").">",'~'.expand("")) - - " Clean out the last buffer: - " Check if the last buffer has # > 1, is unlisted, is unnamed, and does not appear in a window - " If so, delete it. - call s:NetrwBufRemover(bufnr("$")) - - " grab a function-local-variable copy of buffer variables - " call Decho("make function-local copy of netrw variables",'~'.expand("")) - if exists("b:netrw_bannercnt") |let netrw_bannercnt = b:netrw_bannercnt |endif - if exists("b:netrw_browser_active") |let netrw_browser_active = b:netrw_browser_active |endif - if exists("b:netrw_cpf") |let netrw_cpf = b:netrw_cpf |endif - if exists("b:netrw_curdir") |let netrw_curdir = b:netrw_curdir |endif - if exists("b:netrw_explore_bufnr") |let netrw_explore_bufnr = b:netrw_explore_bufnr |endif - if exists("b:netrw_explore_indx") |let netrw_explore_indx = b:netrw_explore_indx |endif - if exists("b:netrw_explore_line") |let netrw_explore_line = b:netrw_explore_line |endif - if exists("b:netrw_explore_list") |let netrw_explore_list = b:netrw_explore_list |endif - if exists("b:netrw_explore_listlen")|let netrw_explore_listlen = b:netrw_explore_listlen|endif - if exists("b:netrw_explore_mtchcnt")|let netrw_explore_mtchcnt = b:netrw_explore_mtchcnt|endif - if exists("b:netrw_fname") |let netrw_fname = b:netrw_fname |endif - if exists("b:netrw_lastfile") |let netrw_lastfile = b:netrw_lastfile |endif - if exists("b:netrw_liststyle") |let netrw_liststyle = b:netrw_liststyle |endif - if exists("b:netrw_method") |let netrw_method = b:netrw_method |endif - if exists("b:netrw_option") |let netrw_option = b:netrw_option |endif - if exists("b:netrw_prvdir") |let netrw_prvdir = b:netrw_prvdir |endif - - NetrwKeepj call s:NetrwOptionsRestore("w:") - " call Decho("generate a buffer with NetrwKeepj enew!",'~'.expand("")) - " when tree listing uses file TreeListing... a new buffer is made. - " Want the old buffer to be unlisted. - " COMBAK: this causes a problem, see P43 - " setl nobl - let netrw_keepdiff= &l:diff - call s:NetrwEditFile("enew!","","") - let &l:diff= netrw_keepdiff - " call Decho("bufnr($)=".bufnr("$")."<".bufname(bufnr("$"))."> winnr($)=".winnr("$"),'~'.expand("")) - NetrwKeepj call s:NetrwOptionsSave("w:") - - " copy function-local-variables to buffer variable equivalents - " call Decho("copy function-local variables back to buffer netrw variables",'~'.expand("")) - if exists("netrw_bannercnt") |let b:netrw_bannercnt = netrw_bannercnt |endif - if exists("netrw_browser_active") |let b:netrw_browser_active = netrw_browser_active |endif - if exists("netrw_cpf") |let b:netrw_cpf = netrw_cpf |endif - if exists("netrw_curdir") |let b:netrw_curdir = netrw_curdir |endif - if exists("netrw_explore_bufnr") |let b:netrw_explore_bufnr = netrw_explore_bufnr |endif - if exists("netrw_explore_indx") |let b:netrw_explore_indx = netrw_explore_indx |endif - if exists("netrw_explore_line") |let b:netrw_explore_line = netrw_explore_line |endif - if exists("netrw_explore_list") |let b:netrw_explore_list = netrw_explore_list |endif - if exists("netrw_explore_listlen")|let b:netrw_explore_listlen = netrw_explore_listlen|endif - if exists("netrw_explore_mtchcnt")|let b:netrw_explore_mtchcnt = netrw_explore_mtchcnt|endif - if exists("netrw_fname") |let b:netrw_fname = netrw_fname |endif - if exists("netrw_lastfile") |let b:netrw_lastfile = netrw_lastfile |endif - if exists("netrw_liststyle") |let b:netrw_liststyle = netrw_liststyle |endif - if exists("netrw_method") |let b:netrw_method = netrw_method |endif - if exists("netrw_option") |let b:netrw_option = netrw_option |endif - if exists("netrw_prvdir") |let b:netrw_prvdir = netrw_prvdir |endif - - if a:0 > 0 - let b:netrw_curdir= a:1 - if b:netrw_curdir =~ '/$' - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST - setl nobl - file NetrwTreeListing - setl nobl bt=nowrite bh=hide - nno [ :sil call TreeListMove('[') - nno ] :sil call TreeListMove(']') - else - call s:NetrwBufRename(b:netrw_curdir) - endif - endif - endif - if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on") - let &l:bexpr = "netrw#BalloonHelp()" - endif - - " call Dret("s:NetrwEnew : buf#".bufnr("%")."<".bufname("%")."> expand(%)<".expand("%")."> expand(#)<".expand("#")."> bh=".&bh." win#".winnr()." winnr($)#".winnr("$")) -endfun - -" --------------------------------------------------------------------- -" s:NetrwExe: executes a string using "!" {{{2 -fun! s:NetrwExe(cmd) - if has("win32") && exepath(&shell) !~? '\v[\/]?(cmd|pwsh|powershell)(\.exe)?$' && !g:netrw_cygwin - let savedShell=[&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] - set shell& shellcmdflag& shellxquote& shellxescape& - set shellquote& shellpipe& shellredir& shellslash& - try - exe a:cmd - finally - let [&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] = savedShell - endtry - else - exe a:cmd - endif - if v:shell_error - call netrw#ErrorMsg(s:WARNING,"shell signalled an error",106) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwInsureWinVars: insure that a netrw buffer has its w: variables in spite of a wincmd v or s {{{2 -fun! s:NetrwInsureWinVars() - if !exists("w:netrw_liststyle") - " call Dfunc("s:NetrwInsureWinVars() win#".winnr()) - let curbuf = bufnr("%") - let curwin = winnr() - let iwin = 1 - while iwin <= winnr("$") - exe iwin."wincmd w" - if winnr() != curwin && bufnr("%") == curbuf && exists("w:netrw_liststyle") - " looks like ctrl-w_s or ctrl-w_v was used to split a netrw buffer - let winvars= w: - break - endif - let iwin= iwin + 1 - endwhile - exe "keepalt ".curwin."wincmd w" - if exists("winvars") - " call Decho("copying w#".iwin." window variables to w#".curwin,'~'.expand("")) - for k in keys(winvars) - let w:{k}= winvars[k] - endfor - endif - " call Dret("s:NetrwInsureWinVars win#".winnr()) - endif -endfun - -" --------------------------------------------------------------------- -" s:NetrwLcd: handles changing the (local) directory {{{2 -" Returns: 0=success -" -1=failed -fun! s:NetrwLcd(newdir) - " call Dfunc("s:NetrwLcd(newdir<".a:newdir.">)") - " call Decho("changing local directory",'~'.expand("")) - - let err472= 0 - try - exe 'NetrwKeepj sil lcd '.fnameescape(a:newdir) - catch /^Vim\%((\a\+)\)\=:E344/ - " Vim's lcd fails with E344 when attempting to go above the 'root' of a Windows share. - " Therefore, detect if a Windows share is present, and if E344 occurs, just settle at - " 'root' (ie. '\'). The share name may start with either backslashes ('\\Foo') or - " forward slashes ('//Foo'), depending on whether backslashes have been converted to - " forward slashes by earlier code; so check for both. - if has("win32") && !g:netrw_cygwin - if a:newdir =~ '^\\\\\w\+' || a:newdir =~ '^//\w\+' - let dirname = '\' - exe 'NetrwKeepj sil lcd '.fnameescape(dirname) - endif - endif - catch /^Vim\%((\a\+)\)\=:E472/ - let err472= 1 - endtry - - if err472 - call netrw#ErrorMsg(s:ERROR,"unable to change directory to <".a:newdir."> (permissions?)",61) - if exists("w:netrw_prvdir") - let a:newdir= w:netrw_prvdir - else - call s:NetrwOptionsRestore("w:") - " call Decho("setl noma nomod nowrap",'~'.expand("")) - exe "setl ".g:netrw_bufsettings - " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) - let a:newdir= dirname - endif - " call Dret("s:NetrwBrowse -1 : reusing buffer#".(exists("bufnum")? bufnum : 'N/A')."<".dirname."> getcwd<".getcwd().">") - return -1 - endif - - " call Decho("getcwd <".getcwd().">") - " call Decho("b:netrw_curdir<".b:netrw_curdir.">") - " call Dret("s:NetrwLcd 0") - return 0 -endfun - -" ------------------------------------------------------------------------ -" s:NetrwSaveWordPosn: used to keep cursor on same word after refresh, {{{2 -" changed sorting, etc. Also see s:NetrwRestoreWordPosn(). -fun! s:NetrwSaveWordPosn() - " call Dfunc("NetrwSaveWordPosn()") - let s:netrw_saveword= '^'.fnameescape(getline('.')).'$' - " call Dret("NetrwSaveWordPosn : saveword<".s:netrw_saveword.">") -endfun - -" --------------------------------------------------------------------- -" s:NetrwHumanReadable: takes a number and makes it "human readable" {{{2 -" 1000 -> 1K, 1000000 -> 1M, 1000000000 -> 1G -fun! s:NetrwHumanReadable(sz) - " call Dfunc("s:NetrwHumanReadable(sz=".a:sz.") type=".type(a:sz)." style=".g:netrw_sizestyle ) - - if g:netrw_sizestyle == 'h' - if a:sz >= 1000000000 - let sz = printf("%.1f",a:sz/1000000000.0)."g" - elseif a:sz >= 10000000 - let sz = printf("%d",a:sz/1000000)."m" - elseif a:sz >= 1000000 - let sz = printf("%.1f",a:sz/1000000.0)."m" - elseif a:sz >= 10000 - let sz = printf("%d",a:sz/1000)."k" - elseif a:sz >= 1000 - let sz = printf("%.1f",a:sz/1000.0)."k" - else - let sz= a:sz - endif - - elseif g:netrw_sizestyle == 'H' - if a:sz >= 1073741824 - let sz = printf("%.1f",a:sz/1073741824.0)."G" - elseif a:sz >= 10485760 - let sz = printf("%d",a:sz/1048576)."M" - elseif a:sz >= 1048576 - let sz = printf("%.1f",a:sz/1048576.0)."M" - elseif a:sz >= 10240 - let sz = printf("%d",a:sz/1024)."K" - elseif a:sz >= 1024 - let sz = printf("%.1f",a:sz/1024.0)."K" - else - let sz= a:sz - endif - - else - let sz= a:sz - endif - - " call Dret("s:NetrwHumanReadable ".sz) - return sz -endfun - -" --------------------------------------------------------------------- -" s:NetrwRestoreWordPosn: used to keep cursor on same word after refresh, {{{2 -" changed sorting, etc. Also see s:NetrwSaveWordPosn(). -fun! s:NetrwRestoreWordPosn() - " call Dfunc("NetrwRestoreWordPosn()") - sil! call search(s:netrw_saveword,'w') - " call Dret("NetrwRestoreWordPosn") -endfun - -" --------------------------------------------------------------------- -" s:RestoreBufVars: {{{2 -fun! s:RestoreBufVars() - " call Dfunc("s:RestoreBufVars()") - - if exists("s:netrw_curdir") |let b:netrw_curdir = s:netrw_curdir |endif - if exists("s:netrw_lastfile") |let b:netrw_lastfile = s:netrw_lastfile |endif - if exists("s:netrw_method") |let b:netrw_method = s:netrw_method |endif - if exists("s:netrw_fname") |let b:netrw_fname = s:netrw_fname |endif - if exists("s:netrw_machine") |let b:netrw_machine = s:netrw_machine |endif - if exists("s:netrw_browser_active")|let b:netrw_browser_active = s:netrw_browser_active|endif - - " call Dret("s:RestoreBufVars") -endfun - -" --------------------------------------------------------------------- -" s:RemotePathAnalysis: {{{2 -fun! s:RemotePathAnalysis(dirname) - " call Dfunc("s:RemotePathAnalysis(a:dirname<".a:dirname.">)") - - " method :// user @ machine :port /path - let dirpat = '^\(\w\{-}\)://\(\(\w\+\)@\)\=\([^/:#]\+\)\%([:#]\(\d\+\)\)\=/\(.*\)$' - let s:method = substitute(a:dirname,dirpat,'\1','') - let s:user = substitute(a:dirname,dirpat,'\3','') - let s:machine = substitute(a:dirname,dirpat,'\4','') - let s:port = substitute(a:dirname,dirpat,'\5','') - let s:path = substitute(a:dirname,dirpat,'\6','') - let s:fname = substitute(s:path,'^.*/\ze.','','') - if s:machine =~ '@' - let dirpat = '^\(.*\)@\(.\{-}\)$' - let s:user = s:user.'@'.substitute(s:machine,dirpat,'\1','') - let s:machine = substitute(s:machine,dirpat,'\2','') - endif - - " call Decho("set up s:method <".s:method .">",'~'.expand("")) - " call Decho("set up s:user <".s:user .">",'~'.expand("")) - " call Decho("set up s:machine<".s:machine.">",'~'.expand("")) - " call Decho("set up s:port <".s:port.">",'~'.expand("")) - " call Decho("set up s:path <".s:path .">",'~'.expand("")) - " call Decho("set up s:fname <".s:fname .">",'~'.expand("")) - - " call Dret("s:RemotePathAnalysis") -endfun - -" --------------------------------------------------------------------- -" s:RemoteSystem: runs a command on a remote host using ssh {{{2 -" Returns status -" Runs system() on -" [cd REMOTEDIRPATH;] a:cmd -" Note that it doesn't do s:ShellEscape(a:cmd)! -fun! s:RemoteSystem(cmd) - " call Dfunc("s:RemoteSystem(cmd<".a:cmd.">)") - if !executable(g:netrw_ssh_cmd) - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"g:netrw_ssh_cmd<".g:netrw_ssh_cmd."> is not executable!",52) - elseif !exists("b:netrw_curdir") - NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53) - else - let cmd = s:MakeSshCmd(g:netrw_ssh_cmd." USEPORT HOSTNAME") - let remotedir= substitute(b:netrw_curdir,'^.*//[^/]\+/\(.*\)$','\1','') - if remotedir != "" - let cmd= cmd.' cd '.s:ShellEscape(remotedir).";" - else - let cmd= cmd.' ' - endif - let cmd= cmd.a:cmd - " call Decho("call system(".cmd.")",'~'.expand("")) - let ret= system(cmd) - endif - " call Dret("s:RemoteSystem ".ret) - return ret -endfun - -" --------------------------------------------------------------------- -" s:RestoreWinVars: (used by Explore() and NetrwSplit()) {{{2 -fun! s:RestoreWinVars() - " call Dfunc("s:RestoreWinVars()") - if exists("s:bannercnt") |let w:netrw_bannercnt = s:bannercnt |unlet s:bannercnt |endif - if exists("s:col") |let w:netrw_col = s:col |unlet s:col |endif - if exists("s:curdir") |let w:netrw_curdir = s:curdir |unlet s:curdir |endif - if exists("s:explore_bufnr") |let w:netrw_explore_bufnr = s:explore_bufnr |unlet s:explore_bufnr |endif - if exists("s:explore_indx") |let w:netrw_explore_indx = s:explore_indx |unlet s:explore_indx |endif - if exists("s:explore_line") |let w:netrw_explore_line = s:explore_line |unlet s:explore_line |endif - if exists("s:explore_listlen")|let w:netrw_explore_listlen = s:explore_listlen|unlet s:explore_listlen|endif - if exists("s:explore_list") |let w:netrw_explore_list = s:explore_list |unlet s:explore_list |endif - if exists("s:explore_mtchcnt")|let w:netrw_explore_mtchcnt = s:explore_mtchcnt|unlet s:explore_mtchcnt|endif - if exists("s:fpl") |let w:netrw_fpl = s:fpl |unlet s:fpl |endif - if exists("s:hline") |let w:netrw_hline = s:hline |unlet s:hline |endif - if exists("s:line") |let w:netrw_line = s:line |unlet s:line |endif - if exists("s:liststyle") |let w:netrw_liststyle = s:liststyle |unlet s:liststyle |endif - if exists("s:method") |let w:netrw_method = s:method |unlet s:method |endif - if exists("s:prvdir") |let w:netrw_prvdir = s:prvdir |unlet s:prvdir |endif - if exists("s:treedict") |let w:netrw_treedict = s:treedict |unlet s:treedict |endif - if exists("s:treetop") |let w:netrw_treetop = s:treetop |unlet s:treetop |endif - if exists("s:winnr") |let w:netrw_winnr = s:winnr |unlet s:winnr |endif - " call Dret("s:RestoreWinVars") -endfun - -" --------------------------------------------------------------------- -" s:Rexplore: implements returning from a buffer to a netrw directory {{{2 -" -" s:SetRexDir() sets up <2-leftmouse> maps (if g:netrw_retmap -" is true) and a command, :Rexplore, which call this function. -" -" s:netrw_posn is set up by s:NetrwBrowseChgDir() -" -" s:rexposn_BUFNR used to save/restore cursor position -fun! s:NetrwRexplore(islocal,dirname) - if exists("s:netrwdrag") - return - endif - " call Dfunc("s:NetrwRexplore() w:netrw_rexlocal=".w:netrw_rexlocal." w:netrw_rexdir<".w:netrw_rexdir."> win#".winnr()) - " call Decho("currently in bufname<".bufname("%").">",'~'.expand("")) - " call Decho("ft=".&ft." win#".winnr()." w:netrw_rexfile<".(exists("w:netrw_rexfile")? w:netrw_rexfile : 'n/a').">",'~'.expand("")) - - if &ft == "netrw" && exists("w:netrw_rexfile") && w:netrw_rexfile != "" - " a :Rex while in a netrw buffer means: edit the file in w:netrw_rexfile - " call Decho("in netrw buffer, will edit file<".w:netrw_rexfile.">",'~'.expand("")) - exe "NetrwKeepj e ".w:netrw_rexfile - unlet w:netrw_rexfile - " call Dret("s:NetrwRexplore returning from netrw to buf#".bufnr("%")."<".bufname("%")."> (ft=".&ft.")") - return - " else " Decho - " call Decho("treating as not-netrw-buffer: ft=".&ft.((&ft == "netrw")? " == netrw" : "!= netrw"),'~'.expand("")) - " call Decho("treating as not-netrw-buffer: w:netrw_rexfile<".((exists("w:netrw_rexfile"))? w:netrw_rexfile : 'n/a').">",'~'.expand("")) - endif - - " --------------------------- - " :Rex issued while in a file - " --------------------------- - - " record current file so :Rex can return to it from netrw - let w:netrw_rexfile= expand("%") - " call Decho("set w:netrw_rexfile<".w:netrw_rexfile."> (win#".winnr().")",'~'.expand("")) - - if !exists("w:netrw_rexlocal") - " call Dret("s:NetrwRexplore w:netrw_rexlocal doesn't exist (".&ft." win#".winnr().")") - return - endif - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - if w:netrw_rexlocal - NetrwKeepj call netrw#LocalBrowseCheck(w:netrw_rexdir) - else - NetrwKeepj call s:NetrwBrowse(0,w:netrw_rexdir) - endif - if exists("s:initbeval") - setl beval - endif - if exists("s:rexposn_".bufnr("%")) - " call Decho("restore posn, then unlet s:rexposn_".bufnr('%')."<".bufname("%").">",'~'.expand("")) - " restore position in directory listing - " call Decho("restoring posn to s:rexposn_".bufnr('%')."<".string(s:rexposn_{bufnr('%')}).">",'~'.expand("")) - NetrwKeepj call winrestview(s:rexposn_{bufnr('%')}) - if exists("s:rexposn_".bufnr('%')) - unlet s:rexposn_{bufnr('%')} - endif - else - " call Decho("s:rexposn_".bufnr('%')."<".bufname("%")."> doesn't exist",'~'.expand("")) - endif - - if has("syntax") && exists("g:syntax_on") && g:syntax_on - if exists("s:explore_match") - exe "2match netrwMarkFile /".s:explore_match."/" - endif - endif - - " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) - " call Dret("s:NetrwRexplore : ft=".&ft) -endfun - -" --------------------------------------------------------------------- -" s:SaveBufVars: save selected b: variables to s: variables {{{2 -" use s:RestoreBufVars() to restore b: variables from s: variables -fun! s:SaveBufVars() - " call Dfunc("s:SaveBufVars() buf#".bufnr("%")) - - if exists("b:netrw_curdir") |let s:netrw_curdir = b:netrw_curdir |endif - if exists("b:netrw_lastfile") |let s:netrw_lastfile = b:netrw_lastfile |endif - if exists("b:netrw_method") |let s:netrw_method = b:netrw_method |endif - if exists("b:netrw_fname") |let s:netrw_fname = b:netrw_fname |endif - if exists("b:netrw_machine") |let s:netrw_machine = b:netrw_machine |endif - if exists("b:netrw_browser_active")|let s:netrw_browser_active = b:netrw_browser_active|endif - - " call Dret("s:SaveBufVars") -endfun - -" --------------------------------------------------------------------- -" s:SavePosn: saves position associated with current buffer into a dictionary {{{2 -fun! s:SavePosn(posndict) - " call Dfunc("s:SavePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">") - - if !exists("a:posndict[bufnr('%')]") - let a:posndict[bufnr("%")]= [] - endif - " call Decho("before push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) - call add(a:posndict[bufnr("%")],winsaveview()) - " call Decho("after push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) - - " call Dret("s:SavePosn posndict") - return a:posndict -endfun - -" --------------------------------------------------------------------- -" s:RestorePosn: restores position associated with current buffer using dictionary {{{2 -fun! s:RestorePosn(posndict) - " call Dfunc("s:RestorePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">") - if exists("a:posndict") - if has_key(a:posndict,bufnr("%")) - " call Decho("before pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) - let posnlen= len(a:posndict[bufnr("%")]) - if posnlen > 0 - let posnlen= posnlen - 1 - " call Decho("restoring posn posndict[".bufnr("%")."][".posnlen."]=".string(a:posndict[bufnr("%")][posnlen]),'~'.expand("")) - call winrestview(a:posndict[bufnr("%")][posnlen]) - call remove(a:posndict[bufnr("%")],posnlen) - " call Decho("after pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) - endif - endif - endif - " call Dret("s:RestorePosn") -endfun - -" --------------------------------------------------------------------- -" s:SaveWinVars: (used by Explore() and NetrwSplit()) {{{2 -fun! s:SaveWinVars() - " call Dfunc("s:SaveWinVars() win#".winnr()) - if exists("w:netrw_bannercnt") |let s:bannercnt = w:netrw_bannercnt |endif - if exists("w:netrw_col") |let s:col = w:netrw_col |endif - if exists("w:netrw_curdir") |let s:curdir = w:netrw_curdir |endif - if exists("w:netrw_explore_bufnr") |let s:explore_bufnr = w:netrw_explore_bufnr |endif - if exists("w:netrw_explore_indx") |let s:explore_indx = w:netrw_explore_indx |endif - if exists("w:netrw_explore_line") |let s:explore_line = w:netrw_explore_line |endif - if exists("w:netrw_explore_listlen")|let s:explore_listlen = w:netrw_explore_listlen|endif - if exists("w:netrw_explore_list") |let s:explore_list = w:netrw_explore_list |endif - if exists("w:netrw_explore_mtchcnt")|let s:explore_mtchcnt = w:netrw_explore_mtchcnt|endif - if exists("w:netrw_fpl") |let s:fpl = w:netrw_fpl |endif - if exists("w:netrw_hline") |let s:hline = w:netrw_hline |endif - if exists("w:netrw_line") |let s:line = w:netrw_line |endif - if exists("w:netrw_liststyle") |let s:liststyle = w:netrw_liststyle |endif - if exists("w:netrw_method") |let s:method = w:netrw_method |endif - if exists("w:netrw_prvdir") |let s:prvdir = w:netrw_prvdir |endif - if exists("w:netrw_treedict") |let s:treedict = w:netrw_treedict |endif - if exists("w:netrw_treetop") |let s:treetop = w:netrw_treetop |endif - if exists("w:netrw_winnr") |let s:winnr = w:netrw_winnr |endif - " call Dret("s:SaveWinVars") -endfun - -" --------------------------------------------------------------------- -" s:SetBufWinVars: (used by NetrwBrowse() and LocalBrowseCheck()) {{{2 -" To allow separate windows to have their own activities, such as -" Explore **/pattern, several variables have been made window-oriented. -" However, when the user splits a browser window (ex: ctrl-w s), these -" variables are not inherited by the new window. SetBufWinVars() and -" UseBufWinVars() get around that. -fun! s:SetBufWinVars() - " call Dfunc("s:SetBufWinVars() win#".winnr()) - if exists("w:netrw_liststyle") |let b:netrw_liststyle = w:netrw_liststyle |endif - if exists("w:netrw_bannercnt") |let b:netrw_bannercnt = w:netrw_bannercnt |endif - if exists("w:netrw_method") |let b:netrw_method = w:netrw_method |endif - if exists("w:netrw_prvdir") |let b:netrw_prvdir = w:netrw_prvdir |endif - if exists("w:netrw_explore_indx") |let b:netrw_explore_indx = w:netrw_explore_indx |endif - if exists("w:netrw_explore_listlen")|let b:netrw_explore_listlen= w:netrw_explore_listlen|endif - if exists("w:netrw_explore_mtchcnt")|let b:netrw_explore_mtchcnt= w:netrw_explore_mtchcnt|endif - if exists("w:netrw_explore_bufnr") |let b:netrw_explore_bufnr = w:netrw_explore_bufnr |endif - if exists("w:netrw_explore_line") |let b:netrw_explore_line = w:netrw_explore_line |endif - if exists("w:netrw_explore_list") |let b:netrw_explore_list = w:netrw_explore_list |endif - " call Dret("s:SetBufWinVars") -endfun - -" --------------------------------------------------------------------- -" s:SetRexDir: set directory for :Rexplore {{{2 -fun! s:SetRexDir(islocal,dirname) - " call Dfunc("s:SetRexDir(islocal=".a:islocal." dirname<".a:dirname.">) win#".winnr()) - let w:netrw_rexdir = a:dirname - let w:netrw_rexlocal = a:islocal - let s:rexposn_{bufnr("%")} = winsaveview() - " call Decho("setting w:netrw_rexdir =".w:netrw_rexdir,'~'.expand("")) - " call Decho("setting w:netrw_rexlocal=".w:netrw_rexlocal,'~'.expand("")) - " call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand("")) - " call Decho("setting s:rexposn_".bufnr("%")."<".bufname("%")."> to ".string(winsaveview()),'~'.expand("")) - " call Dret("s:SetRexDir : win#".winnr()." ".(a:islocal? "local" : "remote")." dir: ".a:dirname) -endfun - -" --------------------------------------------------------------------- -" s:ShowLink: used to modify thin and tree listings to show links {{{2 -fun! s:ShowLink() - if exists("b:netrw_curdir") - keepp :norm! $?\a - "call histdel("/",-1) - if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop") - let basedir = s:NetrwTreePath(w:netrw_treetop) - else - let basedir = b:netrw_curdir.'/' - endif - let fname = basedir.s:NetrwGetWord() - let resname = resolve(fname) - if resname =~ '^\M'.basedir - let dirlen = strlen(basedir) - let resname = strpart(resname,dirlen) - endif - let modline = getline(".")."\t --> ".resname - setl noro ma - call setline(".",modline) - setl ro noma nomod - endif -endfun - -" --------------------------------------------------------------------- -" s:ShowStyle: {{{2 -fun! s:ShowStyle() - if !exists("w:netrw_liststyle") - let liststyle= g:netrw_liststyle - else - let liststyle= w:netrw_liststyle - endif - if liststyle == s:THINLIST - return s:THINLIST.":thin" - elseif liststyle == s:LONGLIST - return s:LONGLIST.":long" - elseif liststyle == s:WIDELIST - return s:WIDELIST.":wide" - elseif liststyle == s:TREELIST - return s:TREELIST.":tree" - else - return 'n/a' - endif -endfun - -" --------------------------------------------------------------------- -" s:Strlen: this function returns the length of a string, even if its using multi-byte characters. {{{2 -" Solution from Nicolai Weibull, vim docs (:help strlen()), -" Tony Mechelynck, and my own invention. -fun! s:Strlen(x) - " "" call Dfunc("s:Strlen(x<".a:x."> g:Align_xstrlen=".g:Align_xstrlen.")") - - if v:version >= 703 && exists("*strdisplaywidth") - let ret= strdisplaywidth(a:x) - - elseif type(g:Align_xstrlen) == 1 - " allow user to specify a function to compute the string length (ie. let g:Align_xstrlen="mystrlenfunc") - exe "let ret= ".g:Align_xstrlen."('".substitute(a:x,"'","''","g")."')" - - elseif g:Align_xstrlen == 1 - " number of codepoints (Latin a + combining circumflex is two codepoints) - " (comment from TM, solution from NW) - let ret= strlen(substitute(a:x,'.','c','g')) - - elseif g:Align_xstrlen == 2 - " number of spacing codepoints (Latin a + combining circumflex is one spacing - " codepoint; a hard tab is one; wide and narrow CJK are one each; etc.) - " (comment from TM, solution from TM) - let ret=strlen(substitute(a:x, '.\Z', 'x', 'g')) - - elseif g:Align_xstrlen == 3 - " virtual length (counting, for instance, tabs as anything between 1 and - " 'tabstop', wide CJK as 2 rather than 1, Arabic alif as zero when immediately - " preceded by lam, one otherwise, etc.) - " (comment from TM, solution from me) - let modkeep= &l:mod - exe "norm! o\" - call setline(line("."),a:x) - let ret= virtcol("$") - 1 - d - NetrwKeepj norm! k - let &l:mod= modkeep - - else - " at least give a decent default - let ret= strlen(a:x) - endif - " "" call Dret("s:Strlen ".ret) - return ret -endfun - -" --------------------------------------------------------------------- -" s:ShellEscape: shellescape(), or special windows handling {{{2 -fun! s:ShellEscape(s, ...) - if has('win32') && $SHELL == '' && &shellslash - return printf('"%s"', substitute(a:s, '"', '""', 'g')) - endif - let f = a:0 > 0 ? a:1 : 0 - return shellescape(a:s, f) -endfun - -" --------------------------------------------------------------------- -" s:TreeListMove: supports [[, ]], [], and ][ in tree mode {{{2 -fun! s:TreeListMove(dir) - " call Dfunc("s:TreeListMove(dir<".a:dir.">)") - let curline = getline('.') - let prvline = (line(".") > 1)? getline(line(".")-1) : '' - let nxtline = (line(".") < line("$"))? getline(line(".")+1) : '' - let curindent = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e') - let indentm1 = substitute(curindent,'^'.s:treedepthstring,'','') - let treedepthchr = substitute(s:treedepthstring,' ','','g') - let stopline = exists("w:netrw_bannercnt")? w:netrw_bannercnt : 1 - " call Decho("prvline <".prvline."> #".(line(".")-1), '~'.expand("")) - " call Decho("curline <".curline."> #".line(".") , '~'.expand("")) - " call Decho("nxtline <".nxtline."> #".(line(".")+1), '~'.expand("")) - " call Decho("curindent<".curindent.">" , '~'.expand("")) - " call Decho("indentm1 <".indentm1.">" , '~'.expand("")) - " COMBAK : need to handle when on a directory - " COMBAK : need to handle ]] and ][. In general, needs work!!! - if curline !~ '/$' - if a:dir == '[[' && prvline != '' - NetrwKeepj norm! 0 - let nl = search('^'.indentm1.'\%('.s:treedepthstring.'\)\@!','bWe',stopline) " search backwards - " call Decho("regfile srch back: ".nl,'~'.expand("")) - elseif a:dir == '[]' && nxtline != '' - NetrwKeepj norm! 0 - " call Decho('srchpat<'.'^\%('.curindent.'\)\@!'.'>','~'.expand("")) - let nl = search('^\%('.curindent.'\)\@!','We') " search forwards - if nl != 0 - NetrwKeepj norm! k - else - NetrwKeepj norm! G - endif - " call Decho("regfile srch fwd: ".nl,'~'.expand("")) - endif - endif - - " call Dret("s:TreeListMove") -endfun - -" --------------------------------------------------------------------- -" s:UpdateBuffersMenu: does emenu Buffers.Refresh (but due to locale, the menu item may not be called that) {{{2 -" The Buffers.Refresh menu calls s:BMShow(); unfortunately, that means that that function -" can't be called except via emenu. But due to locale, that menu line may not be called -" Buffers.Refresh; hence, s:NetrwBMShow() utilizes a "cheat" to call that function anyway. -fun! s:UpdateBuffersMenu() - " call Dfunc("s:UpdateBuffersMenu()") - if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu - try - sil emenu Buffers.Refresh\ menu - catch /^Vim\%((\a\+)\)\=:E/ - let v:errmsg= "" - sil NetrwKeepj call s:NetrwBMShow() - endtry - endif - " call Dret("s:UpdateBuffersMenu") -endfun - -" --------------------------------------------------------------------- -" s:UseBufWinVars: (used by NetrwBrowse() and LocalBrowseCheck() {{{2 -" Matching function to s:SetBufWinVars() -fun! s:UseBufWinVars() - " call Dfunc("s:UseBufWinVars()") - if exists("b:netrw_liststyle") && !exists("w:netrw_liststyle") |let w:netrw_liststyle = b:netrw_liststyle |endif - if exists("b:netrw_bannercnt") && !exists("w:netrw_bannercnt") |let w:netrw_bannercnt = b:netrw_bannercnt |endif - if exists("b:netrw_method") && !exists("w:netrw_method") |let w:netrw_method = b:netrw_method |endif - if exists("b:netrw_prvdir") && !exists("w:netrw_prvdir") |let w:netrw_prvdir = b:netrw_prvdir |endif - if exists("b:netrw_explore_indx") && !exists("w:netrw_explore_indx") |let w:netrw_explore_indx = b:netrw_explore_indx |endif - if exists("b:netrw_explore_listlen") && !exists("w:netrw_explore_listlen")|let w:netrw_explore_listlen = b:netrw_explore_listlen|endif - if exists("b:netrw_explore_mtchcnt") && !exists("w:netrw_explore_mtchcnt")|let w:netrw_explore_mtchcnt = b:netrw_explore_mtchcnt|endif - if exists("b:netrw_explore_bufnr") && !exists("w:netrw_explore_bufnr") |let w:netrw_explore_bufnr = b:netrw_explore_bufnr |endif - if exists("b:netrw_explore_line") && !exists("w:netrw_explore_line") |let w:netrw_explore_line = b:netrw_explore_line |endif - if exists("b:netrw_explore_list") && !exists("w:netrw_explore_list") |let w:netrw_explore_list = b:netrw_explore_list |endif - " call Dret("s:UseBufWinVars") -endfun - -" --------------------------------------------------------------------- -" s:UserMaps: supports user-defined UserMaps {{{2 -" * calls a user-supplied funcref(islocal,curdir) -" * interprets result -" See netrw#UserMaps() -fun! s:UserMaps(islocal,funcname) - if !exists("b:netrw_curdir") - let b:netrw_curdir= getcwd() - endif - let Funcref = function(a:funcname) - let result = Funcref(a:islocal) - - if type(result) == 1 - " if result from user's funcref is a string... - if result == "refresh" - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - elseif result != "" - exe result - endif - - elseif type(result) == 3 - " if result from user's funcref is a List... - for action in result - if action == "refresh" - call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) - elseif action != "" - exe action - endif - endfor - endif -endfun - -" ========================== -" Settings Restoration: {{{1 -" ========================== -let &cpo= s:keepcpo -unlet s:keepcpo - -" =============== -" Modelines: {{{1 -" =============== -" vim:ts=8 sts=2 sw=2 et fdm=marker diff --git a/runtime/autoload/netrwFileHandlers.vim b/runtime/autoload/netrwFileHandlers.vim deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/runtime/autoload/netrwSettings.vim b/runtime/autoload/netrwSettings.vim deleted file mode 100644 index 3452602272..0000000000 --- a/runtime/autoload/netrwSettings.vim +++ /dev/null @@ -1,249 +0,0 @@ -" netrwSettings.vim: makes netrw settings simpler -" Date: Nov 15, 2021 -" Maintainer: This runtime file is looking for a new maintainer. -" Former Maintainer: Charles E Campbell -" Version: 18 -" Last Change: -" 2024 May 08 by Vim Project: cleanup legacy Win9X checks -" Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{1 -" Permission is hereby granted to use and distribute this code, -" with or without modifications, provided that this copyright -" notice is copied with it. Like anything else that's free, -" netrwSettings.vim is provided *as is* and comes with no -" warranty of any kind, either expressed or implied. By using -" this plugin, you agree that in no event will the copyright -" holder be liable for any damages resulting from the use -" of this software. -" -" Mat 4:23 (WEB) Jesus went about in all Galilee, teaching in their {{{1 -" synagogues, preaching the gospel of the kingdom, and healing -" every disease and every sickness among the people. -" Load Once: {{{1 -if exists("g:loaded_netrwSettings") || &cp - finish -endif -let g:loaded_netrwSettings = "v18" -if v:version < 700 - echohl WarningMsg - echo "***warning*** this version of netrwSettings needs vim 7.0" - echohl Normal - finish -endif - -" --------------------------------------------------------------------- -" NetrwSettings: {{{1 -fun! netrwSettings#NetrwSettings() - " this call is here largely just to insure that netrw has been loaded - call netrw#WinPath("") - if !exists("g:loaded_netrw") - echohl WarningMsg | echomsg "***sorry*** netrw needs to be loaded prior to using NetrwSettings" | echohl None - return - endif - - above wincmd s - enew - setlocal noswapfile bh=wipe - set ft=vim - file Netrw\ Settings - - " these variables have the following default effects when they don't - " exist (ie. have not been set by the user in his/her .vimrc) - if !exists("g:netrw_liststyle") - let g:netrw_liststyle= 0 - let g:netrw_list_cmd= "ssh HOSTNAME ls -FLa" - endif - if !exists("g:netrw_silent") - let g:netrw_silent= 0 - endif - if !exists("g:netrw_use_nt_rcp") - let g:netrw_use_nt_rcp= 0 - endif - if !exists("g:netrw_ftp") - let g:netrw_ftp= 0 - endif - if !exists("g:netrw_ignorenetrc") - let g:netrw_ignorenetrc= 0 - endif - - put ='+ ---------------------------------------------' - put ='+ NetrwSettings: by Charles E. Campbell' - put ='+ Press with cursor atop any line for help' - put ='+ ---------------------------------------------' - let s:netrw_settings_stop= line(".") - - put ='' - put ='+ Netrw Protocol Commands' - put = 'let g:netrw_dav_cmd = '.g:netrw_dav_cmd - put = 'let g:netrw_fetch_cmd = '.g:netrw_fetch_cmd - put = 'let g:netrw_ftp_cmd = '.g:netrw_ftp_cmd - put = 'let g:netrw_http_cmd = '.g:netrw_http_cmd - put = 'let g:netrw_rcp_cmd = '.g:netrw_rcp_cmd - put = 'let g:netrw_rsync_cmd = '.g:netrw_rsync_cmd - put = 'let g:netrw_scp_cmd = '.g:netrw_scp_cmd - put = 'let g:netrw_sftp_cmd = '.g:netrw_sftp_cmd - put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd - let s:netrw_protocol_stop= line(".") - put = '' - - put ='+Netrw Transfer Control' - put = 'let g:netrw_cygwin = '.g:netrw_cygwin - put = 'let g:netrw_ftp = '.g:netrw_ftp - put = 'let g:netrw_ftpmode = '.g:netrw_ftpmode - put = 'let g:netrw_ignorenetrc = '.g:netrw_ignorenetrc - put = 'let g:netrw_sshport = '.g:netrw_sshport - put = 'let g:netrw_silent = '.g:netrw_silent - put = 'let g:netrw_use_nt_rcp = '.g:netrw_use_nt_rcp - let s:netrw_xfer_stop= line(".") - put ='' - put ='+ Netrw Messages' - put ='let g:netrw_use_errorwindow = '.g:netrw_use_errorwindow - - put = '' - put ='+ Netrw Browser Control' - if exists("g:netrw_altfile") - put = 'let g:netrw_altfile = '.g:netrw_altfile - else - put = 'let g:netrw_altfile = 0' - endif - put = 'let g:netrw_alto = '.g:netrw_alto - put = 'let g:netrw_altv = '.g:netrw_altv - put = 'let g:netrw_banner = '.g:netrw_banner - if exists("g:netrw_bannerbackslash") - put = 'let g:netrw_bannerbackslash = '.g:netrw_bannerbackslash - else - put = '\" let g:netrw_bannerbackslash = (not defined)' - endif - put = 'let g:netrw_browse_split = '.g:netrw_browse_split - if exists("g:netrw_browsex_viewer") - put = 'let g:netrw_browsex_viewer = '.g:netrw_browsex_viewer - else - put = '\" let g:netrw_browsex_viewer = (not defined)' - endif - put = 'let g:netrw_compress = '.g:netrw_compress - if exists("g:Netrw_corehandler") - put = 'let g:Netrw_corehandler = '.g:Netrw_corehandler - else - put = '\" let g:Netrw_corehandler = (not defined)' - endif - put = 'let g:netrw_ctags = '.g:netrw_ctags - put = 'let g:netrw_cursor = '.g:netrw_cursor - let decompressline= line("$") - put = 'let g:netrw_decompress = '.string(g:netrw_decompress) - if exists("g:netrw_dynamic_maxfilenamelen") - put = 'let g:netrw_dynamic_maxfilenamelen='.g:netrw_dynamic_maxfilenamelen - else - put = '\" let g:netrw_dynamic_maxfilenamelen= (not defined)' - endif - put = 'let g:netrw_dirhistmax = '.g:netrw_dirhistmax - put = 'let g:netrw_errorlvl = '.g:netrw_errorlvl - put = 'let g:netrw_fastbrowse = '.g:netrw_fastbrowse - let fnameescline= line("$") - put = 'let g:netrw_fname_escape = '.string(g:netrw_fname_escape) - put = 'let g:netrw_ftp_browse_reject = '.g:netrw_ftp_browse_reject - put = 'let g:netrw_ftp_list_cmd = '.g:netrw_ftp_list_cmd - put = 'let g:netrw_ftp_sizelist_cmd = '.g:netrw_ftp_sizelist_cmd - put = 'let g:netrw_ftp_timelist_cmd = '.g:netrw_ftp_timelist_cmd - let globescline= line("$") - put = 'let g:netrw_glob_escape = '.string(g:netrw_glob_escape) - put = 'let g:netrw_hide = '.g:netrw_hide - if exists("g:netrw_home") - put = 'let g:netrw_home = '.g:netrw_home - else - put = '\" let g:netrw_home = (not defined)' - endif - put = 'let g:netrw_keepdir = '.g:netrw_keepdir - put = 'let g:netrw_list_cmd = '.g:netrw_list_cmd - put = 'let g:netrw_list_hide = '.g:netrw_list_hide - put = 'let g:netrw_liststyle = '.g:netrw_liststyle - put = 'let g:netrw_localcopycmd = '.g:netrw_localcopycmd - put = 'let g:netrw_localcopycmdopt = '.g:netrw_localcopycmdopt - put = 'let g:netrw_localmkdir = '.g:netrw_localmkdir - put = 'let g:netrw_localmkdiropt = '.g:netrw_localmkdiropt - put = 'let g:netrw_localmovecmd = '.g:netrw_localmovecmd - put = 'let g:netrw_localmovecmdopt = '.g:netrw_localmovecmdopt - put = 'let g:netrw_maxfilenamelen = '.g:netrw_maxfilenamelen - put = 'let g:netrw_menu = '.g:netrw_menu - put = 'let g:netrw_mousemaps = '.g:netrw_mousemaps - put = 'let g:netrw_mkdir_cmd = '.g:netrw_mkdir_cmd - if exists("g:netrw_nobeval") - put = 'let g:netrw_nobeval = '.g:netrw_nobeval - else - put = '\" let g:netrw_nobeval = (not defined)' - endif - put = 'let g:netrw_remote_mkdir = '.g:netrw_remote_mkdir - put = 'let g:netrw_preview = '.g:netrw_preview - put = 'let g:netrw_rename_cmd = '.g:netrw_rename_cmd - put = 'let g:netrw_retmap = '.g:netrw_retmap - put = 'let g:netrw_rm_cmd = '.g:netrw_rm_cmd - put = 'let g:netrw_rmdir_cmd = '.g:netrw_rmdir_cmd - put = 'let g:netrw_rmf_cmd = '.g:netrw_rmf_cmd - put = 'let g:netrw_sort_by = '.g:netrw_sort_by - put = 'let g:netrw_sort_direction = '.g:netrw_sort_direction - put = 'let g:netrw_sort_options = '.g:netrw_sort_options - put = 'let g:netrw_sort_sequence = '.g:netrw_sort_sequence - put = 'let g:netrw_servername = '.g:netrw_servername - put = 'let g:netrw_special_syntax = '.g:netrw_special_syntax - put = 'let g:netrw_ssh_browse_reject = '.g:netrw_ssh_browse_reject - put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd - put = 'let g:netrw_scpport = '.g:netrw_scpport - put = 'let g:netrw_sepchr = '.g:netrw_sepchr - put = 'let g:netrw_sshport = '.g:netrw_sshport - put = 'let g:netrw_timefmt = '.g:netrw_timefmt - let tmpfileescline= line("$") - put ='let g:netrw_tmpfile_escape...' - put = 'let g:netrw_use_noswf = '.g:netrw_use_noswf - put = 'let g:netrw_xstrlen = '.g:netrw_xstrlen - put = 'let g:netrw_winsize = '.g:netrw_winsize - - put ='' - put ='+ For help, place cursor on line and press ' - - 1d - silent %s/^+/"/e - res 99 - silent %s/= \([^0-9].*\)$/= '\1'/e - silent %s/= $/= ''/e - 1 - - call setline(decompressline,"let g:netrw_decompress = ".substitute(string(g:netrw_decompress),"^'\\(.*\\)'$",'\1','')) - call setline(fnameescline, "let g:netrw_fname_escape = '".escape(g:netrw_fname_escape,"'")."'") - call setline(globescline, "let g:netrw_glob_escape = '".escape(g:netrw_glob_escape,"'")."'") - call setline(tmpfileescline,"let g:netrw_tmpfile_escape = '".escape(g:netrw_tmpfile_escape,"'")."'") - - set nomod - - nmap :call NetrwSettingHelp() - nnoremap :call NetrwSettingHelp() - let tmpfile= tempname() - exe 'au BufWriteCmd Netrw\ Settings silent w! '.tmpfile.'|so '.tmpfile.'|call delete("'.tmpfile.'")|set nomod' -endfun - -" --------------------------------------------------------------------- -" NetrwSettingHelp: {{{2 -fun! NetrwSettingHelp() -" call Dfunc("NetrwSettingHelp()") - let curline = getline(".") - if curline =~ '=' - let varhelp = substitute(curline,'^\s*let ','','e') - let varhelp = substitute(varhelp,'\s*=.*$','','e') -" call Decho("trying help ".varhelp) - try - exe "he ".varhelp - catch /^Vim\%((\a\+)\)\=:E149/ - echo "***sorry*** no help available for <".varhelp.">" - endtry - elseif line(".") < s:netrw_settings_stop - he netrw-settings - elseif line(".") < s:netrw_protocol_stop - he netrw-externapp - elseif line(".") < s:netrw_xfer_stop - he netrw-variables - else - he netrw-browse-var - endif -" call Dret("NetrwSettingHelp") -endfun - -" --------------------------------------------------------------------- -" Modelines: {{{1 -" vim:ts=8 fdm=marker diff --git a/runtime/autoload/netrw_gitignore.vim b/runtime/autoload/netrw_gitignore.vim deleted file mode 100644 index 1b55e2488a..0000000000 --- a/runtime/autoload/netrw_gitignore.vim +++ /dev/null @@ -1,22 +0,0 @@ -" netrw_gitignore#Hide: gitignore-based hiding -" Function returns a string of comma separated patterns convenient for -" assignment to `g:netrw_list_hide` option. -" Function can take additional filenames as arguments, example: -" netrw_gitignore#Hide('custom_gitignore1', 'custom_gitignore2') -" -" Usage examples: -" let g:netrw_list_hide = netrw_gitignore#Hide() -" let g:netrw_list_hide = netrw_gitignore#Hide() . 'more,hide,patterns' -" -" Copyright: Copyright (C) 2013 Bruno Sutic {{{1 -" Permission is hereby granted to use and distribute this code, -" with or without modifications, provided that this copyright -" notice is copied with it. Like anything else that's free, -" netrw_gitignore.vim is provided *as is* and comes with no -" warranty of any kind, either expressed or implied. By using -" this plugin, you agree that in no event will the copyright -" holder be liable for any damages resulting from the use -" of this software. -function! netrw_gitignore#Hide(...) - return substitute(substitute(system('git ls-files --other --ignored --exclude-standard --directory'), '\n', ',', 'g'), ',$', '', '') -endfunction diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt deleted file mode 100644 index 01e6b5fa6a..0000000000 --- a/runtime/doc/pi_netrw.txt +++ /dev/null @@ -1,4397 +0,0 @@ -*pi_netrw.txt* Nvim - - ------------------------------------------------ - NETRW REFERENCE MANUAL by Charles E. Campbell - ------------------------------------------------ -Author: Charles E. Campbell - (remove NOSPAM from Campbell's email first) - -Copyright: Copyright (C) 2017 Charles E Campbell *netrw-copyright* - The VIM LICENSE applies to the files in this package, including - netrw.vim, pi_netrw.txt, netrwFileHandlers.vim, netrwSettings.vim, and - syntax/netrw.vim. Like anything else that's free, netrw.vim and its - associated files are provided *as is* and comes with no warranty of - any kind, either expressed or implied. No guarantees of - merchantability. No guarantees of suitability for any purpose. By - using this plugin, you agree that in no event will the copyright - holder be liable for any damages resulting from the use of this - software. Use at your own risk! - - *netrw* - *dav* *ftp* *netrw-file* *rcp* *scp* - *davs* *http* *netrw.vim* *rsync* *sftp* - *fetch* *network* - -============================================================================== -1. Contents *netrw-contents* {{{1 - -1. Contents..............................................|netrw-contents| -2. Starting With Netrw...................................|netrw-start| -3. Netrw Reference.......................................|netrw-ref| - EXTERNAL APPLICATIONS AND PROTOCOLS.................|netrw-externapp| - READING.............................................|netrw-read| - WRITING.............................................|netrw-write| - SOURCING............................................|netrw-source| - DIRECTORY LISTING...................................|netrw-dirlist| - CHANGING THE USERID AND PASSWORD....................|netrw-chgup| - VARIABLES AND SETTINGS..............................|netrw-variables| - PATHS...............................................|netrw-path| -4. Network-Oriented File Transfer........................|netrw-xfer| - NETRC...............................................|netrw-netrc| - PASSWORD............................................|netrw-passwd| -5. Activation............................................|netrw-activate| -6. Transparent Remote File Editing.......................|netrw-transparent| -7. Ex Commands...........................................|netrw-ex| -8. Variables and Options.................................|netrw-variables| -9. Browsing..............................................|netrw-browse| - Introduction To Browsing............................|netrw-intro-browse| - Quick Reference: Maps...............................|netrw-browse-maps| - Quick Reference: Commands...........................|netrw-browse-cmds| - Banner Display......................................|netrw-I| - Bookmarking A Directory.............................|netrw-mb| - Browsing............................................|netrw-cr| - Squeezing the Current Tree-Listing Directory........|netrw-s-cr| - Browsing With A Horizontally Split Window...........|netrw-o| - Browsing With A New Tab.............................|netrw-t| - Browsing With A Vertically Split Window.............|netrw-v| - Change Listing Style (thin wide long tree)..........|netrw-i| - Changing To A Bookmarked Directory..................|netrw-gb| - Quick hide/unhide of dot-files......................|netrw-gh| - Changing local-only File Permission.................|netrw-gp| - Changing To A Predecessor Directory.................|netrw-u| - Changing To A Successor Directory...................|netrw-U| - Customizing Browsing With A Special Handler.........|netrw-x| - Deleting Bookmarks..................................|netrw-mB| - Deleting Files Or Directories.......................|netrw-D| - Directory Exploring Commands........................|netrw-explore| - Exploring With Stars and Patterns...................|netrw-star| - Displaying Information About File...................|netrw-qf| - Edit File Or Directory Hiding List..................|netrw-ctrl-h| - Editing The Sorting Sequence........................|netrw-S| - Forcing treatment as a file or directory............|netrw-gd| |netrw-gf| - Going Up............................................|netrw--| - Hiding Files Or Directories.........................|netrw-a| - Improving Browsing..................................|netrw-ssh-hack| - Listing Bookmarks And History.......................|netrw-qb| - Making A New Directory..............................|netrw-d| - Making The Browsing Directory The Current Directory.|netrw-cd| - Marking Files.......................................|netrw-mf| - Unmarking Files.....................................|netrw-mF| - Marking Files By Location List......................|netrw-qL| - Marking Files By QuickFix List......................|netrw-qF| - Marking Files By Regular Expression.................|netrw-mr| - Marked Files: Arbitrary Shell Command...............|netrw-mx| - Marked Files: Arbitrary Shell Command, En Bloc......|netrw-mX| - Marked Files: Arbitrary Vim Command.................|netrw-mv| - Marked Files: Argument List.........................|netrw-ma| |netrw-mA| - Marked Files: Buffer List...........................|netrw-cb| |netrw-cB| - Marked Files: Compression And Decompression.........|netrw-mz| - Marked Files: Copying...............................|netrw-mc| - Marked Files: Diff..................................|netrw-md| - Marked Files: Editing...............................|netrw-me| - Marked Files: Grep..................................|netrw-mg| - Marked Files: Hiding and Unhiding by Suffix.........|netrw-mh| - Marked Files: Moving................................|netrw-mm| - Marked Files: Sourcing..............................|netrw-ms| - Marked Files: Setting the Target Directory..........|netrw-mt| - Marked Files: Tagging...............................|netrw-mT| - Marked Files: Target Directory Using Bookmarks......|netrw-Tb| - Marked Files: Target Directory Using History........|netrw-Th| - Marked Files: Unmarking.............................|netrw-mu| - Netrw Browser Variables.............................|netrw-browser-var| - Netrw Browsing And Option Incompatibilities.........|netrw-incompatible| - Netrw Settings Window...............................|netrw-settings-window| - Obtaining A File....................................|netrw-O| - Preview Window......................................|netrw-p| - Previous Window.....................................|netrw-P| - Refreshing The Listing..............................|netrw-ctrl-l| - Reversing Sorting Order.............................|netrw-r| - Renaming Files Or Directories.......................|netrw-R| - Selecting Sorting Style.............................|netrw-s| - Setting Editing Window..............................|netrw-C| -10. Problems and Fixes....................................|netrw-problems| -11. Debugging Netrw Itself................................|netrw-debug| -12. History...............................................|netrw-history| -13. Todo..................................................|netrw-todo| -14. Credits...............................................|netrw-credits| - -============================================================================== -2. Starting With Netrw *netrw-start* {{{1 - -Netrw makes reading files, writing files, browsing over a network, and -local browsing easy! First, make sure that you have plugins enabled, so -you'll need to have at least the following in your <.vimrc>: -(or see |netrw-activate|) > - - set nocp " 'compatible' is not set - filetype plugin on " plugins are enabled -< -(see |'cp'| and |:filetype-plugin-on|) - -Netrw supports "transparent" editing of files on other machines using urls -(see |netrw-transparent|). As an example of this, let's assume you have an -account on some other machine; if you can use scp, try: > - - vim scp://hostname/path/to/file -< -Want to make ssh/scp easier to use? Check out |netrw-ssh-hack|! - -So, what if you have ftp, not ssh/scp? That's easy, too; try > - - vim ftp://hostname/path/to/file -< -Want to make ftp simpler to use? See if your ftp supports a file called -<.netrc> -- typically it goes in your home directory, has read/write -permissions for only the user to read (ie. not group, world, other, etc), -and has lines resembling > - - machine HOSTNAME login USERID password "PASSWORD" - machine HOSTNAME login USERID password "PASSWORD" - ... - default login USERID password "PASSWORD" -< -Windows' ftp doesn't support .netrc; however, one may have in one's .vimrc: > - - let g:netrw_ftp_cmd= 'c:\Windows\System32\ftp -s:C:\Users\MyUserName\MACHINE' -< -Netrw will substitute the host's machine name for "MACHINE" from the URL it is -attempting to open, and so one may specify > - userid - password -for each site in a separate file: c:\Users\MyUserName\MachineName. - -Now about browsing -- when you just want to look around before editing a -file. For browsing on your current host, just "edit" a directory: > - - vim . - vim /home/userid/path -< -For browsing on a remote host, "edit" a directory (but make sure that -the directory name is followed by a "/"): > - - vim scp://hostname/ - vim ftp://hostname/path/to/dir/ -< -See |netrw-browse| for more! - -There are more protocols supported by netrw than just scp and ftp, too: see the -next section, |netrw-externapp|, on how to use these external applications with -netrw and vim. - -PREVENTING LOADING *netrw-noload* - -If you want to use plugins, but for some reason don't wish to use netrw, then -you need to avoid loading both the plugin and the autoload portions of netrw. -You may do so by placing the following two lines in your <.vimrc>: > - - :let g:loaded_netrw = 1 - :let g:loaded_netrwPlugin = 1 -< - -============================================================================== -3. Netrw Reference *netrw-ref* {{{1 - - Netrw supports several protocols in addition to scp and ftp as mentioned - in |netrw-start|. These include dav, fetch, http,... well, just look - at the list in |netrw-externapp|. Each protocol is associated with a - variable which holds the default command supporting that protocol. - -EXTERNAL APPLICATIONS AND PROTOCOLS *netrw-externapp* {{{2 - - Protocol Variable Default Value - -------- ---------------- ------------- - dav: *g:netrw_dav_cmd* = "cadaver" if cadaver is executable - dav: g:netrw_dav_cmd = "curl -o" elseif curl is available - fetch: *g:netrw_fetch_cmd* = "fetch -o" if fetch is available - ftp: *g:netrw_ftp_cmd* = "ftp" - http: *g:netrw_http_cmd* = "elinks" if elinks is available - http: g:netrw_http_cmd = "links" elseif links is available - http: g:netrw_http_cmd = "curl" elseif curl is available - http: g:netrw_http_cmd = "wget" elseif wget is available - http: g:netrw_http_cmd = "fetch" elseif fetch is available - http: *g:netrw_http_put_cmd* = "curl -T" - rcp: *g:netrw_rcp_cmd* = "rcp" - rsync: *g:netrw_rsync_cmd* = "rsync" (see |g:netrw_rsync_sep|) - scp: *g:netrw_scp_cmd* = "scp -q" - sftp: *g:netrw_sftp_cmd* = "sftp" - file: *g:netrw_file_cmd* = "elinks" or "links" - - *g:netrw_http_xcmd* : the option string for http://... protocols are - specified via this variable and may be independently overridden. By - default, the option arguments for the http-handling commands are: > - - elinks : "-source >" - links : "-dump >" - curl : "-L -o" - wget : "-q -O" - fetch : "-o" -< - For example, if your system has elinks, and you'd rather see the - page using an attempt at rendering the text, you may wish to have > - let g:netrw_http_xcmd= "-dump >" -< in your .vimrc. - - g:netrw_http_put_cmd: this option specifies both the executable and - any needed options. This command does a PUT operation to the url. - - -READING *netrw-read* *netrw-nread* {{{2 - - Generally, one may just use the URL notation with a normal editing - command, such as > - - :e ftp://[user@]machine/path -< - Netrw also provides the Nread command: - - :Nread ? give help - :Nread "machine:path" uses rcp - :Nread "machine path" uses ftp w/ <.netrc> - :Nread "machine id password path" uses ftp - :Nread "dav://machine[:port]/path" uses cadaver - :Nread "fetch://[user@]machine/path" uses fetch - :Nread "ftp://[user@]machine[[:#]port]/path" uses ftp w/ <.netrc> - :Nread "http://[user@]machine/path" uses http uses wget - :Nread "rcp://[user@]machine/path" uses rcp - :Nread "rsync://[user@]machine[:port]/path" uses rsync - :Nread "scp://[user@]machine[[:#]port]/path" uses scp - :Nread "sftp://[user@]machine/path" uses sftp - -WRITING *netrw-write* *netrw-nwrite* {{{2 - - One may just use the URL notation with a normal file writing - command, such as > - - :w ftp://[user@]machine/path -< - Netrw also provides the Nwrite command: - - :Nwrite ? give help - :Nwrite "machine:path" uses rcp - :Nwrite "machine path" uses ftp w/ <.netrc> - :Nwrite "machine id password path" uses ftp - :Nwrite "dav://machine[:port]/path" uses cadaver - :Nwrite "ftp://[user@]machine[[:#]port]/path" uses ftp w/ <.netrc> - :Nwrite "rcp://[user@]machine/path" uses rcp - :Nwrite "rsync://[user@]machine[:port]/path" uses rsync - :Nwrite "scp://[user@]machine[[:#]port]/path" uses scp - :Nwrite "sftp://[user@]machine/path" uses sftp - http: not supported! - -SOURCING *netrw-source* {{{2 - - One may just use the URL notation with the normal file sourcing - command, such as > - - :so ftp://[user@]machine/path -< - Netrw also provides the Nsource command: - - :Nsource ? give help - :Nsource "dav://machine[:port]/path" uses cadaver - :Nsource "fetch://[user@]machine/path" uses fetch - :Nsource "ftp://[user@]machine[[:#]port]/path" uses ftp w/ <.netrc> - :Nsource "http://[user@]machine/path" uses http uses wget - :Nsource "rcp://[user@]machine/path" uses rcp - :Nsource "rsync://[user@]machine[:port]/path" uses rsync - :Nsource "scp://[user@]machine[[:#]port]/path" uses scp - :Nsource "sftp://[user@]machine/path" uses sftp - -DIRECTORY LISTING *netrw-trailingslash* *netrw-dirlist* {{{2 - - One may browse a directory to get a listing by simply attempting to - edit the directory: > - - :e scp://[user]@hostname/path/ - :e ftp://[user]@hostname/path/ -< - For remote directory listings (ie. those using scp or ftp), that - trailing "/" is necessary (the slash tells netrw to treat the argument - as a directory to browse instead of as a file to download). - - The Nread command may also be used to accomplish this (again, that - trailing slash is necessary): > - - :Nread [protocol]://[user]@hostname/path/ -< - *netrw-login* *netrw-password* -CHANGING USERID AND PASSWORD *netrw-chgup* *netrw-userpass* {{{2 - - Attempts to use ftp will prompt you for a user-id and a password. - These will be saved in global variables |g:netrw_uid| and - |s:netrw_passwd|; subsequent use of ftp will re-use those two strings, - thereby simplifying use of ftp. However, if you need to use a - different user id and/or password, you'll want to call |NetUserPass()| - first. To work around the need to enter passwords, check if your ftp - supports a <.netrc> file in your home directory. Also see - |netrw-passwd| (and if you're using ssh/scp hoping to figure out how - to not need to use passwords for scp, look at |netrw-ssh-hack|). - - :NetUserPass [uid [password]] -- prompts as needed - :call NetUserPass() -- prompts for uid and password - :call NetUserPass("uid") -- prompts for password - :call NetUserPass("uid","password") -- sets global uid and password - -(Related topics: |ftp| |netrw-userpass| |netrw-start|) - -NETRW VARIABLES AND SETTINGS *netrw-variables* {{{2 - (Also see: - |netrw-browser-var| : netrw browser option variables - |netrw-protocol| : file transfer protocol option variables - |netrw-settings| : additional file transfer options - |netrw-browser-options| : these options affect browsing directories - ) - -Netrw provides a lot of variables which allow you to customize netrw to your -preferences. One way to look at them is via the command :NetrwSettings (see -|netrw-settings|) which will display your current netrw settings. Most such -settings are described below, in |netrw-browser-options|, and in -|netrw-externapp|: - - *b:netrw_lastfile* last file Network-read/written retained on a - per-buffer basis (supports plain :Nw ) - - *g:netrw_bufsettings* the settings that netrw buffers have - (default) noma nomod nonu nowrap ro nobl - - *g:netrw_chgwin* specifies a window number where subsequent file edits - will take place. (also see |netrw-C|) - (default) -1 - - *g:Netrw_funcref* specifies a function (or functions) to be called when - netrw edits a file. The file is first edited, and - then the function reference (|Funcref|) is called. - This variable may also hold a |List| of Funcrefs. - (default) not defined. (the capital in g:Netrw... - is required by its holding a function reference) -> - Example: place in .vimrc; affects all file opening - fun! MyFuncRef() - endfun - let g:Netrw_funcref= function("MyFuncRef") - -< - *g:Netrw_UserMaps* specifies a function or |List| of functions which can - be used to set up user-specified maps and functionality. - See |netrw-usermaps| - - *g:netrw_ftp* if it doesn't exist, use default ftp - =0 use default ftp (uid password) - =1 use alternate ftp method (user uid password) - If you're having trouble with ftp, try changing the - value of this variable to see if the alternate ftp - method works for your setup. - - *g:netrw_ftp_options* Chosen by default, these options are supposed to - turn interactive prompting off and to restrain ftp - from attempting auto-login upon initial connection. - However, it appears that not all ftp implementations - support this (ex. ncftp). - ="-i -n" - - *g:netrw_ftpextracmd* default: doesn't exist - If this variable exists, then any string it contains - will be placed into the commands set to your ftp - client. As an example: - ="passive" - - *g:netrw_ftpmode* ="binary" (default) - ="ascii" - - *g:netrw_ignorenetrc* =0 (default for linux, cygwin) - =1 If you have a <.netrc> file but it doesn't work and - you want it ignored, then set this variable as - shown. (default for Windows + cmd.exe) - - *g:netrw_menu* =0 disable netrw's menu - =1 (default) netrw's menu enabled - - *g:netrw_nogx* if this variable exists, then the "gx" map will not - be available (see |netrw-gx|) - - *g:netrw_uid* (ftp) user-id, retained on a per-vim-session basis - *s:netrw_passwd* (ftp) password, retained on a per-vim-session basis - - *g:netrw_preview* =0 (default) preview window shown in a horizontally - split window - =1 preview window shown in a vertically split window. - Also affects the "previous window" (see |netrw-P|) - in the same way. - The |g:netrw_alto| variable may be used to provide - additional splitting control: - g:netrw_preview g:netrw_alto result - 0 0 |:aboveleft| - 0 1 |:belowright| - 1 0 |:topleft| - 1 1 |:botright| - To control sizing, see |g:netrw_winsize| - - *g:netrw_scpport* = "-P" : option to use to set port for scp - *g:netrw_sshport* = "-p" : option to use to set port for ssh - - *g:netrw_sepchr* =\0xff - =\0x01 for enc == euc-jp (and perhaps it should be for - others, too, please let me know) - Separates priority codes from filenames internally. - See |netrw-p12|. - - *g:netrw_silent* =0 : transfers done normally - =1 : transfers done silently - - *g:netrw_use_errorwindow* =2: messages from netrw will use a popup window - Move the mouse and pause to remove the popup window. - =1 : messages from netrw will use a separate one - line window. This window provides reliable - delivery of messages. - =0 : (default) messages from netrw will use echoerr ; - messages don't always seem to show up this - way, but one doesn't have to quit the window. - - *g:netrw_cygwin* =1 assume scp under windows is from cygwin. Also - permits network browsing to use ls with time and - size sorting (default if windows) - =0 assume Windows' scp accepts windows-style paths - Network browsing uses dir instead of ls - This option is ignored if you're using unix - - *g:netrw_use_nt_rcp* =0 don't use the rcp of WinNT, Win2000 and WinXP - =1 use WinNT's rcp in binary mode (default) - -PATHS *netrw-path* {{{2 - -Paths to files are generally user-directory relative for most protocols. -It is possible that some protocol will make paths relative to some -associated directory, however. -> - example: vim scp://user@host/somefile - example: vim scp://user@host/subdir1/subdir2/somefile -< -where "somefile" is in the "user"'s home directory. If you wish to get a -file using root-relative paths, use the full path: -> - example: vim scp://user@host//somefile - example: vim scp://user@host//subdir1/subdir2/somefile -< - -============================================================================== -4. Network-Oriented File Transfer *netrw-xfer* {{{1 - -Network-oriented file transfer under Vim is implemented by a vim script -() using plugin techniques. It currently supports both reading and -writing across networks using rcp, scp, ftp or ftp+<.netrc>, scp, fetch, -dav/cadaver, rsync, or sftp. - -http is currently supported read-only via use of wget or fetch. - - is a standard plugin which acts as glue between Vim and the -various file transfer programs. It uses autocommand events (BufReadCmd, -FileReadCmd, BufWriteCmd) to intercept reads/writes with url-like filenames. > - - ex. vim ftp://hostname/path/to/file -< -The characters preceding the colon specify the protocol to use; in the -example, it's ftp. The script then formulates a command or a -series of commands (typically ftp) which it issues to an external program -(ftp, scp, etc) which does the actual file transfer/protocol. Files are read -from/written to a temporary file (under Unix/Linux, /tmp/...) which the - script will clean up. - -Now, a word about Jan Minář's "FTP User Name and Password Disclosure"; first, -ftp is not a secure protocol. User names and passwords are transmitted "in -the clear" over the internet; any snooper tool can pick these up; this is not -a netrw thing, this is a ftp thing. If you're concerned about this, please -try to use scp or sftp instead. - -Netrw re-uses the user id and password during the same vim session and so long -as the remote hostname remains the same. - -Jan seems to be a bit confused about how netrw handles ftp; normally multiple -commands are performed in a "ftp session", and he seems to feel that the -uid/password should only be retained over one ftp session. However, netrw -does every ftp operation in a separate "ftp session"; so remembering the -uid/password for just one "ftp session" would be the same as not remembering -the uid/password at all. IMHO this would rapidly grow tiresome as one -browsed remote directories, for example. - -On the other hand, thanks go to Jan M. for pointing out the many -vulnerabilities that netrw (and vim itself) had had in handling "crafted" -filenames. The |shellescape()| and |fnameescape()| functions were written in -response by Bram Moolenaar to handle these sort of problems, and netrw has -been modified to use them. Still, my advice is, if the "filename" looks like -a vim command that you aren't comfortable with having executed, don't open it. - - *netrw-putty* *netrw-pscp* *netrw-psftp* -One may modify any protocol's implementing external application by setting a -variable (ex. scp uses the variable g:netrw_scp_cmd, which is defaulted to -"scp -q"). As an example, consider using PuTTY: > - - let g:netrw_scp_cmd = '"c:\Program Files\PuTTY\pscp.exe" -q -batch' - let g:netrw_sftp_cmd= '"c:\Program Files\PuTTY\psftp.exe"' -< -(note: it has been reported that windows 7 with putty v0.6's "-batch" option - doesn't work, so its best to leave it off for that system) - -See |netrw-p8| for more about putty, pscp, psftp, etc. - -Ftp, an old protocol, seems to be blessed by numerous implementations. -Unfortunately, some implementations are noisy (ie., add junk to the end of the -file). Thus, concerned users may decide to write a NetReadFixup() function -that will clean up after reading with their ftp. Some Unix systems (ie., -FreeBSD) provide a utility called "fetch" which uses the ftp protocol but is -not noisy and more convenient, actually, for to use. -Consequently, if "fetch" is available (ie. executable), it may be preferable -to use it for ftp://... based transfers. - -For rcp, scp, sftp, and http, one may use network-oriented file transfers -transparently; ie. -> - vim rcp://[user@]machine/path - vim scp://[user@]machine/path -< -If your ftp supports <.netrc>, then it too can be transparently used -if the needed triad of machine name, user id, and password are present in -that file. Your ftp must be able to use the <.netrc> file on its own, however. -> - vim ftp://[user@]machine[[:#]portnumber]/path -< -Windows provides an ftp (typically c:\Windows\System32\ftp.exe) which uses -an option, -s:filename (filename can and probably should be a full path) -which contains ftp commands which will be automatically run whenever ftp -starts. You may use this feature to enter a user and password for one site: > - userid - password -< *netrw-windows-netrc* *netrw-windows-s* -If |g:netrw_ftp_cmd| contains -s:[path/]MACHINE, then (on Windows machines -only) netrw will substitute the current machine name requested for ftp -connections for MACHINE. Hence one can have multiple machine.ftp files -containing login and password for ftp. Example: > - - let g:netrw_ftp_cmd= 'c:\Windows\System32\ftp -s:C:\Users\Myself\MACHINE' - vim ftp://myhost.somewhere.net/ - -will use a file > - - C:\Users\Myself\myhost.ftp -< -Often, ftp will need to query the user for the userid and password. -The latter will be done "silently"; ie. asterisks will show up instead of -the actually-typed-in password. Netrw will retain the userid and password -for subsequent read/writes from the most recent transfer so subsequent -transfers (read/write) to or from that machine will take place without -additional prompting. - - *netrw-urls* - +=================================+============================+============+ - | Reading | Writing | Uses | - +=================================+============================+============+ - | DAV: | | | - | dav://host/path | | cadaver | - | :Nread dav://host/path | :Nwrite dav://host/path | cadaver | - +---------------------------------+----------------------------+------------+ - | DAV + SSL: | | | - | davs://host/path | | cadaver | - | :Nread davs://host/path | :Nwrite davs://host/path | cadaver | - +---------------------------------+----------------------------+------------+ - | FETCH: | | | - | fetch://[user@]host/path | | | - | fetch://[user@]host:http/path | Not Available | fetch | - | :Nread fetch://[user@]host/path| | | - +---------------------------------+----------------------------+------------+ - | FILE: | | | - | file:///* | file:///* | | - | file://localhost/* | file://localhost/* | | - +---------------------------------+----------------------------+------------+ - | FTP: (*3) | (*3) | | - | ftp://[user@]host/path | ftp://[user@]host/path | ftp (*2) | - | :Nread ftp://host/path | :Nwrite ftp://host/path | ftp+.netrc | - | :Nread host path | :Nwrite host path | ftp+.netrc | - | :Nread host uid pass path | :Nwrite host uid pass path | ftp | - +---------------------------------+----------------------------+------------+ - | HTTP: wget is executable: (*4) | | | - | http://[user@]host/path | Not Available | wget | - +---------------------------------+----------------------------+------------+ - | HTTP: fetch is executable (*4) | | | - | http://[user@]host/path | Not Available | fetch | - +---------------------------------+----------------------------+------------+ - | RCP: | | | - | rcp://[user@]host/path | rcp://[user@]host/path | rcp | - +---------------------------------+----------------------------+------------+ - | RSYNC: | | | - | rsync://[user@]host/path | rsync://[user@]host/path | rsync | - | :Nread rsync://host/path | :Nwrite rsync://host/path | rsync | - | :Nread rcp://host/path | :Nwrite rcp://host/path | rcp | - +---------------------------------+----------------------------+------------+ - | SCP: | | | - | scp://[user@]host/path | scp://[user@]host/path | scp | - | :Nread scp://host/path | :Nwrite scp://host/path | scp (*1) | - +---------------------------------+----------------------------+------------+ - | SFTP: | | | - | sftp://[user@]host/path | sftp://[user@]host/path | sftp | - | :Nread sftp://host/path | :Nwrite sftp://host/path | sftp (*1) | - +=================================+============================+============+ - - (*1) For an absolute path use scp://machine//path. - - (*2) if <.netrc> is present, it is assumed that it will - work with your ftp client. Otherwise the script will - prompt for user-id and password. - - (*3) for ftp, "machine" may be machine#port or machine:port - if a different port is needed than the standard ftp port - - (*4) for http:..., if wget is available it will be used. Otherwise, - if fetch is available it will be used. - -Both the :Nread and the :Nwrite ex-commands can accept multiple filenames. - - -NETRC *netrw-netrc* - -The <.netrc> file, typically located in your home directory, contains lines -therein which map a hostname (machine name) to the user id and password you -prefer to use with it. - -The typical syntax for lines in a <.netrc> file is given as shown below. -Ftp under Unix usually supports <.netrc>; ftp under Windows usually doesn't. -> - machine {full machine name} login {user-id} password "{password}" - default login {user-id} password "{password}" - -Your ftp client must handle the use of <.netrc> on its own, but if the -<.netrc> file exists, an ftp transfer will not ask for the user-id or -password. - - Note: - Since this file contains passwords, make very sure nobody else can - read this file! Most programs will refuse to use a .netrc that is - readable for others. Don't forget that the system administrator can - still read the file! Ie. for Linux/Unix: chmod 600 .netrc - -Even though Windows' ftp clients typically do not support .netrc, netrw has -a work-around: see |netrw-windows-s|. - - -PASSWORD *netrw-passwd* - -The script attempts to get passwords for ftp invisibly using |inputsecret()|, -a built-in Vim function. See |netrw-userpass| for how to change the password -after one has set it. - -Unfortunately there doesn't appear to be a way for netrw to feed a password to -scp. Thus every transfer via scp will require re-entry of the password. -However, |netrw-ssh-hack| can help with this problem. - - -============================================================================== -5. Activation *netrw-activate* {{{1 - -Network-oriented file transfers are available by default whenever Vim's -|'nocompatible'| mode is enabled. Netrw's script files reside in your -system's plugin, autoload, and syntax directories; just the -plugin/netrwPlugin.vim script is sourced automatically whenever you bring up -vim. The main script in autoload/netrw.vim is only loaded when you actually -use netrw. I suggest that, at a minimum, you have at least the following in -your <.vimrc> customization file: > - - set nocp - if version >= 600 - filetype plugin indent on - endif -< -By also including the following lines in your .vimrc, one may have netrw -immediately activate when using [g]vim without any filenames, showing the -current directory: > - - " Augroup VimStartup: - augroup VimStartup - au! - au VimEnter * if expand("%") == "" | e . | endif - augroup END -< - -============================================================================== -6. Transparent Remote File Editing *netrw-transparent* {{{1 - -Transparent file transfers occur whenever a regular file read or write -(invoked via an |:autocmd| for |BufReadCmd|, |BufWriteCmd|, or |SourceCmd| -events) is made. Thus one may read, write, or source files across networks -just as easily as if they were local files! > - - vim ftp://[user@]machine/path - ... - :wq - -See |netrw-activate| for more on how to encourage your vim to use plugins -such as netrw. - -For password-free use of scp:, see |netrw-ssh-hack|. - - -============================================================================== -7. Ex Commands *netrw-ex* {{{1 - -The usual read/write commands are supported. There are also a few -additional commands available. Often you won't need to use Nwrite or -Nread as shown in |netrw-transparent| (ie. simply use > - :e URL - :r URL - :w URL -instead, as appropriate) -- see |netrw-urls|. In the explanations -below, a {netfile} is a URL to a remote file. - - *:Nwrite* *:Nw* -:[range]Nw[rite] Write the specified lines to the current - file as specified in b:netrw_lastfile. - (related: |netrw-nwrite|) - -:[range]Nw[rite] {netfile} [{netfile}]... - Write the specified lines to the {netfile}. - - *:Nread* *:Nr* -:Nr[ead] Read the lines from the file specified in b:netrw_lastfile - into the current buffer. (related: |netrw-nread|) - -:Nr[ead] {netfile} {netfile}... - Read the {netfile} after the current line. - - *:Nsource* *:Ns* -:Ns[ource] {netfile} - Source the {netfile}. - To start up vim using a remote .vimrc, one may use - the following (all on one line) (tnx to Antoine Mechelynck) > - vim -u NORC -N - --cmd "runtime plugin/netrwPlugin.vim" - --cmd "source scp://HOSTNAME/.vimrc" -< (related: |netrw-source|) - -:call NetUserPass() *NetUserPass()* - If g:netrw_uid and s:netrw_passwd don't exist, - this function will query the user for them. - (related: |netrw-userpass|) - -:call NetUserPass("userid") - This call will set the g:netrw_uid and, if - the password doesn't exist, will query the user for it. - (related: |netrw-userpass|) - -:call NetUserPass("userid","passwd") - This call will set both the g:netrw_uid and s:netrw_passwd. - The user-id and password are used by ftp transfers. One may - effectively remove the user-id and password by using empty - strings (ie. ""). - (related: |netrw-userpass|) - -:NetrwSettings This command is described in |netrw-settings| -- used to - display netrw settings and change netrw behavior. - - -============================================================================== -8. Variables and Options *netrw-var* *netrw-settings* {{{1 - -(also see: |netrw-options| |netrw-variables| |netrw-protocol| - |netrw-browser-settings| |netrw-browser-options| ) - -The script provides several variables which act as options to -affect 's file transfer behavior. These variables typically may be -set in the user's <.vimrc> file: (see also |netrw-settings| |netrw-protocol|) - *netrw-options* -> - ------------- - Netrw Options - ------------- - Option Meaning - -------------- ----------------------------------------------- -< - b:netrw_col Holds current cursor position (during NetWrite) - g:netrw_cygwin =1 assume scp under windows is from cygwin - (default/windows) - =0 assume scp under windows accepts windows - style paths (default/else) - g:netrw_ftp =0 use default ftp (uid password) - g:netrw_ftpmode ="binary" (default) - ="ascii" (your choice) - g:netrw_ignorenetrc =1 (default) - if you have a <.netrc> file but you don't - want it used, then set this variable. Its - mere existence is enough to cause <.netrc> - to be ignored. - b:netrw_lastfile Holds latest method/machine/path. - b:netrw_line Holds current line number (during NetWrite) - g:netrw_silent =0 transfers done normally - =1 transfers done silently - g:netrw_uid Holds current user-id for ftp. - g:netrw_use_nt_rcp =0 don't use WinNT/2K/XP's rcp (default) - =1 use WinNT/2K/XP's rcp, binary mode - ----------------------------------------------------------------------- -< - *netrw-internal-variables* -The script will also make use of the following variables internally, albeit -temporarily. -> - ------------------- - Temporary Variables - ------------------- - Variable Meaning - -------- ------------------------------------ -< - b:netrw_method Index indicating rcp/ftp+.netrc/ftp - w:netrw_method (same as b:netrw_method) - g:netrw_machine Holds machine name parsed from input - b:netrw_fname Holds filename being accessed > - ------------------------------------------------------------ -< - *netrw-protocol* - -Netrw supports a number of protocols. These protocols are invoked using the -variables listed below, and may be modified by the user. -> - ------------------------ - Protocol Control Options - ------------------------ - Option Type Setting Meaning - --------- -------- -------------- --------------------------- -< netrw_ftp variable =doesn't exist userid set by "user userid" - =0 userid set by "user userid" - =1 userid set by "userid" - NetReadFixup function =doesn't exist no change - =exists Allows user to have files - read via ftp automatically - transformed however they wish - by NetReadFixup() - g:netrw_dav_cmd var ="cadaver" if cadaver is executable - g:netrw_dav_cmd var ="curl -o" elseif curl is executable - g:netrw_fetch_cmd var ="fetch -o" if fetch is available - g:netrw_ftp_cmd var ="ftp" - g:netrw_http_cmd var ="fetch -o" if fetch is available - g:netrw_http_cmd var ="wget -O" else if wget is available - g:netrw_http_put_cmd var ="curl -T" - |g:netrw_list_cmd| var ="ssh USEPORT HOSTNAME ls -Fa" - g:netrw_rcp_cmd var ="rcp" - g:netrw_rsync_cmd var ="rsync" - *g:netrw_rsync_sep* var ="/" used to separate the hostname - from the file spec - g:netrw_scp_cmd var ="scp -q" - g:netrw_sftp_cmd var ="sftp" > - ------------------------------------------------------------------------- -< - *netrw-ftp* - -The g:netrw_..._cmd options (|g:netrw_ftp_cmd| and |g:netrw_sftp_cmd|) -specify the external program to use handle the ftp protocol. They may -include command line options (such as -p for passive mode). Example: > - - let g:netrw_ftp_cmd= "ftp -p" -< -Browsing is supported by using the |g:netrw_list_cmd|; the substring -"HOSTNAME" will be changed via substitution with whatever the current request -is for a hostname. - -Two options (|g:netrw_ftp| and |netrw-fixup|) both help with certain ftp's -that give trouble . In order to best understand how to use these options if -ftp is giving you troubles, a bit of discussion is provided on how netrw does -ftp reads. - -For ftp, netrw typically builds up lines of one of the following formats in a -temporary file: -> - IF g:netrw_ftp !exists or is not 1 IF g:netrw_ftp exists and is 1 - ---------------------------------- ------------------------------ -< - open machine [port] open machine [port] - user userid password userid password - [g:netrw_ftpmode] password - [g:netrw_ftpextracmd] [g:netrw_ftpmode] - get filename tempfile [g:netrw_extracmd] - get filename tempfile > - --------------------------------------------------------------------- -< -The |g:netrw_ftpmode| and |g:netrw_ftpextracmd| are optional. - -Netrw then executes the lines above by use of a filter: -> - :%! {g:netrw_ftp_cmd} -i [-n] -< -where - g:netrw_ftp_cmd is usually "ftp", - -i tells ftp not to be interactive - -n means don't use netrc and is used for Method #3 (ftp w/o <.netrc>) - -If <.netrc> exists it will be used to avoid having to query the user for -userid and password. The transferred file is put into a temporary file. -The temporary file is then read into the main editing session window that -requested it and the temporary file deleted. - -If your ftp doesn't accept the "user" command and immediately just demands a -userid, then try putting "let netrw_ftp=1" in your <.vimrc>. - - *netrw-cadaver* -To handle the SSL certificate dialog for untrusted servers, one may pull -down the certificate and place it into /usr/ssl/cert.pem. This operation -renders the server treatment as "trusted". - - *netrw-fixup* *netreadfixup* -If your ftp for whatever reason generates unwanted lines (such as AUTH -messages) you may write a NetReadFixup() function: -> - function! NetReadFixup(method,line1,line2) - " a:line1: first new line in current file - " a:line2: last new line in current file - if a:method == 1 "rcp - elseif a:method == 2 "ftp + <.netrc> - elseif a:method == 3 "ftp + machine,uid,password,filename - elseif a:method == 4 "scp - elseif a:method == 5 "http/wget - elseif a:method == 6 "dav/cadaver - elseif a:method == 7 "rsync - elseif a:method == 8 "fetch - elseif a:method == 9 "sftp - else " complain - endif - endfunction -> -The NetReadFixup() function will be called if it exists and thus allows you to -customize your reading process. - -(Related topics: |ftp| |netrw-userpass| |netrw-start|) - -============================================================================== -9. Browsing *netrw-browsing* *netrw-browse* *netrw-help* {{{1 - *netrw-browser* *netrw-dir* *netrw-list* - -INTRODUCTION TO BROWSING *netrw-intro-browse* {{{2 - (Quick References: |netrw-quickmaps| |netrw-quickcoms|) - -Netrw supports the browsing of directories on your local system and on remote -hosts; browsing includes listing files and directories, entering directories, -editing files therein, deleting files/directories, making new directories, -moving (renaming) files and directories, copying files and directories, etc. -One may mark files and execute any system command on them! The Netrw browser -generally implements the previous explorer's maps and commands for remote -directories, although details (such as pertinent global variable names) -necessarily differ. To browse a directory, simply "edit" it! > - - vim /your/directory/ - vim . - vim c:\your\directory\ -< -(Related topics: |netrw-cr| |netrw-o| |netrw-p| |netrw-P| |netrw-t| - |netrw-mf| |netrw-mx| |netrw-D| |netrw-R| |netrw-v| ) - -The Netrw remote file and directory browser handles two protocols: ssh and -ftp. The protocol in the url, if it is ftp, will cause netrw also to use ftp -in its remote browsing. Specifying any other protocol will cause it to be -used for file transfers; but the ssh protocol will be used to do remote -browsing. - -To use Netrw's remote directory browser, simply attempt to read a "file" with -a trailing slash and it will be interpreted as a request to list a directory: -> - vim [protocol]://[user@]hostname/path/ -< -where [protocol] is typically scp or ftp. As an example, try: > - - vim ftp://ftp.home.vim.org/pub/vim/ -< -For local directories, the trailing slash is not required. Again, because it's -easy to miss: to browse remote directories, the URL must terminate with a -slash! - -If you'd like to avoid entering the password repeatedly for remote directory -listings with ssh or scp, see |netrw-ssh-hack|. To avoid password entry with -ftp, see |netrw-netrc| (if your ftp supports it). - -There are several things you can do to affect the browser's display of files: - - * To change the listing style, press the "i" key (|netrw-i|). - Currently there are four styles: thin, long, wide, and tree. - To make that change "permanent", see |g:netrw_liststyle|. - - * To hide files (don't want to see those xyz~ files anymore?) see - |netrw-ctrl-h|. - - * Press s to sort files by name, time, or size. - -See |netrw-browse-cmds| for all the things you can do with netrw! - - *netrw-getftype* *netrw-filigree* *netrw-ftype* -The |getftype()| function is used to append a bit of filigree to indicate -filetype to locally listed files: - - directory : / - executable : * - fifo : | - links : @ - sockets : = - -The filigree also affects the |g:netrw_sort_sequence|. - - -QUICK HELP *netrw-quickhelp* {{{2 - (Use ctrl-] to select a topic)~ - Intro to Browsing...............................|netrw-intro-browse| - Quick Reference: Maps.........................|netrw-quickmap| - Quick Reference: Commands.....................|netrw-browse-cmds| - Hiding - Edit hiding list..............................|netrw-ctrl-h| - Hiding Files or Directories...................|netrw-a| - Hiding/Unhiding by suffix.....................|netrw-mh| - Hiding dot-files.............................|netrw-gh| - Listing Style - Select listing style (thin/long/wide/tree)....|netrw-i| - Associated setting variable...................|g:netrw_liststyle| - Shell command used to perform listing.........|g:netrw_list_cmd| - Quick file info...............................|netrw-qf| - Sorted by - Select sorting style (name/time/size).........|netrw-s| - Editing the sorting sequence..................|netrw-S| - Sorting options...............................|g:netrw_sort_options| - Associated setting variable...................|g:netrw_sort_sequence| - Reverse sorting order.........................|netrw-r| - - - *netrw-quickmap* *netrw-quickmaps* -QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2 -> - --- ----------------- ---- - Map Quick Explanation Link - --- ----------------- ---- -< Causes Netrw to issue help - Netrw will enter the directory or read the file |netrw-cr| - Netrw will attempt to remove the file/directory |netrw-del| - Edit file hiding list |netrw-ctrl-h| - Causes Netrw to refresh the directory listing |netrw-ctrl-l| - Browse using a gvim server |netrw-ctrl-r| - Shrink/expand a netrw/explore window |netrw-c-tab| - - Makes Netrw go up one directory |netrw--| - a Cycles between normal display, |netrw-a| - hiding (suppress display of files matching g:netrw_list_hide) - and showing (display only files which match g:netrw_list_hide) - cd Make browsing directory the current directory |netrw-cd| - C Setting the editing window |netrw-C| - d Make a directory |netrw-d| - D Attempt to remove the file(s)/directory(ies) |netrw-D| - gb Go to previous bookmarked directory |netrw-gb| - gd Force treatment as directory |netrw-gd| - gf Force treatment as file |netrw-gf| - gh Quick hide/unhide of dot-files |netrw-gh| - gn Make top of tree the directory below the cursor |netrw-gn| - gp Change local-only file permissions |netrw-gp| - i Cycle between thin, long, wide, and tree listings |netrw-i| - I Toggle the displaying of the banner |netrw-I| - mb Bookmark current directory |netrw-mb| - mc Copy marked files to marked-file target directory |netrw-mc| - md Apply diff to marked files (up to 3) |netrw-md| - me Place marked files on arg list and edit them |netrw-me| - mf Mark a file |netrw-mf| - mF Unmark files |netrw-mF| - mg Apply vimgrep to marked files |netrw-mg| - mh Toggle marked file suffices' presence on hiding list |netrw-mh| - mm Move marked files to marked-file target directory |netrw-mm| - mr Mark files using a shell-style |regexp| |netrw-mr| - mt Current browsing directory becomes markfile target |netrw-mt| - mT Apply ctags to marked files |netrw-mT| - mu Unmark all marked files |netrw-mu| - mv Apply arbitrary vim command to marked files |netrw-mv| - mx Apply arbitrary shell command to marked files |netrw-mx| - mX Apply arbitrary shell command to marked files en bloc|netrw-mX| - mz Compress/decompress marked files |netrw-mz| - o Enter the file/directory under the cursor in a new |netrw-o| - browser window. A horizontal split is used. - O Obtain a file specified by cursor |netrw-O| - p Preview the file |netrw-p| - P Browse in the previously used window |netrw-P| - qb List bookmarked directories and history |netrw-qb| - qf Display information on file |netrw-qf| - qF Mark files using a quickfix list |netrw-qF| - qL Mark files using a |location-list| |netrw-qL| - r Reverse sorting order |netrw-r| - R Rename the designated file(s)/directory(ies) |netrw-R| - s Select sorting style: by name, time, or file size |netrw-s| - S Specify suffix priority for name-sorting |netrw-S| - t Enter the file/directory under the cursor in a new tab|netrw-t| - u Change to recently-visited directory |netrw-u| - U Change to subsequently-visited directory |netrw-U| - v Enter the file/directory under the cursor in a new |netrw-v| - browser window. A vertical split is used. - x View file with an associated program |netrw-x| - X Execute filename under cursor via |system()| |netrw-X| - - % Open a new file in netrw's current directory |netrw-%| - - *netrw-mouse* *netrw-leftmouse* *netrw-middlemouse* *netrw-rightmouse* - (gvim only) selects word under mouse as if a - had been pressed (ie. edit file, change directory) - (gvim only) same as P selecting word under mouse; - see |netrw-P| - (gvim only) delete file/directory using word under - mouse - <2-leftmouse> (gvim only) when: - * in a netrw-selected file, AND - * |g:netrw_retmap| == 1 AND - * the user doesn't already have a <2-leftmouse> - mapping defined before netrw is autoloaded, - then a double clicked leftmouse button will return - to the netrw browser window. See |g:netrw_retmap|. - (gvim only) like mf, will mark files. Dragging - the shifted leftmouse will mark multiple files. - (see |netrw-mf|) - - (to disable mouse buttons while browsing: |g:netrw_mousemaps|) - - *netrw-quickcom* *netrw-quickcoms* -QUICK REFERENCE: COMMANDS *netrw-explore-cmds* *netrw-browse-cmds* {{{2 - :NetrwClean[!]............................................|netrw-clean| - :NetrwSettings............................................|netrw-settings| - :Ntree....................................................|netrw-ntree| - :Explore[!] [dir] Explore directory of current file......|netrw-explore| - :Hexplore[!] [dir] Horizontal Split & Explore.............|netrw-explore| - :Lexplore[!] [dir] Left Explorer Toggle...................|netrw-explore| - :Nexplore[!] [dir] Vertical Split & Explore...............|netrw-explore| - :Pexplore[!] [dir] Vertical Split & Explore...............|netrw-explore| - :Rexplore Return to Explorer.....................|netrw-explore| - :Sexplore[!] [dir] Split & Explore directory .............|netrw-explore| - :Texplore[!] [dir] Tab & Explore..........................|netrw-explore| - :Vexplore[!] [dir] Vertical Split & Explore...............|netrw-explore| - - -BANNER DISPLAY *netrw-I* - -One may toggle the displaying of the banner by pressing "I". - -Also See: |g:netrw_banner| - - -BOOKMARKING A DIRECTORY *netrw-mb* *netrw-bookmark* *netrw-bookmarks* {{{2 - -One may easily "bookmark" the currently browsed directory by using > - - mb -< - *.netrwbook* -Bookmarks are retained in between sessions of vim in a file called .netrwbook -as a |List|, which is typically stored in the first directory on the user's -'runtimepath'; entries are kept in sorted order. - -If there are marked files and/or directories, mb will add them to the bookmark -list. - - *netrw-:NetrwMB* -Additionally, one may use :NetrwMB to bookmark files or directories. > - - :NetrwMB[!] [files/directories] - -< No bang: enters files/directories into Netrw's bookmark system - - No argument and in netrw buffer: - if there are marked files : bookmark marked files - otherwise : bookmark file/directory under cursor - No argument and not in netrw buffer: bookmarks current open file - Has arguments : |glob()|s each arg and bookmarks them - - With bang: deletes files/directories from Netrw's bookmark system - -The :NetrwMB command is available outside of netrw buffers (once netrw has been -invoked in the session). - -The file ".netrwbook" holds bookmarks when netrw (and vim) is not active. By -default, its stored on the first directory on the user's |'runtimepath'|. - -Related Topics: - |netrw-gb| how to return (go) to a bookmark - |netrw-mB| how to delete bookmarks - |netrw-qb| how to list bookmarks - |g:netrw_home| controls where .netrwbook is kept - - -BROWSING *netrw-enter* *netrw-cr* {{{2 - -Browsing is simple: move the cursor onto a file or directory of interest. -Hitting the (the return key) will select the file or directory. -Directories will themselves be listed, and files will be opened using the -protocol given in the original read request. - - CAVEAT: There are four forms of listing (see |netrw-i|). Netrw assumes that - two or more spaces delimit filenames and directory names for the long and - wide listing formats. Thus, if your filename or directory name has two or - more sequential spaces embedded in it, or any trailing spaces, then you'll - need to use the "thin" format to select it. - -The |g:netrw_browse_split| option, which is zero by default, may be used to -cause the opening of files to be done in a new window or tab instead of the -default. When the option is one or two, the splitting will be taken -horizontally or vertically, respectively. When the option is set to three, a - will cause the file to appear in a new tab. - - -When using the gui (gvim), one may select a file by pressing the -button. In addition, if - - * |g:netrw_retmap| == 1 AND (its default value is 0) - * in a netrw-selected file, AND - * the user doesn't already have a <2-leftmouse> mapping defined before - netrw is loaded - -then a doubly-clicked leftmouse button will return to the netrw browser -window. - -Netrw attempts to speed up browsing, especially for remote browsing where one -may have to enter passwords, by keeping and re-using previously obtained -directory listing buffers. The |g:netrw_fastbrowse| variable is used to -control this behavior; one may have slow browsing (no buffer re-use), medium -speed browsing (re-use directory buffer listings only for remote directories), -and fast browsing (re-use directory buffer listings as often as possible). -The price for such re-use is that when changes are made (such as new files -are introduced into a directory), the listing may become out-of-date. One may -always refresh directory listing buffers by pressing ctrl-L (see -|netrw-ctrl-l|). - - *netrw-s-cr* -Squeezing the Current Tree-Listing Directory~ - -When the tree listing style is enabled (see |netrw-i|) and one is using -gvim, then the mapping may be used to squeeze (close) the -directory currently containing the cursor. - -Otherwise, one may remap a key combination of one's own choice to get -this effect: > - - nmap YOURKEYCOMBO NetrwTreeSqueeze -< -Put this line in $HOME/ftplugin/netrw/netrw.vim; it needs to be generated -for netrw buffers only. - -Related topics: - |netrw-ctrl-r| |netrw-o| |netrw-p| - |netrw-P| |netrw-t| |netrw-v| -Associated setting variables: - |g:netrw_browse_split| |g:netrw_fastbrowse| - |g:netrw_ftp_list_cmd| |g:netrw_ftp_sizelist_cmd| - |g:netrw_ftp_timelist_cmd| |g:netrw_ssh_browse_reject| - |g:netrw_ssh_cmd| |g:netrw_use_noswf| - - -BROWSING WITH A HORIZONTALLY SPLIT WINDOW *netrw-o* *netrw-horiz* {{{2 - -Normally one enters a file or directory using the . However, the "o" map -allows one to open a new window to hold the new directory listing or file. A -horizontal split is used. (for vertical splitting, see |netrw-v|) - -Normally, the o key splits the window horizontally with the new window and -cursor at the top. - -Associated setting variables: |g:netrw_alto| |g:netrw_winsize| - -Related topics: - |netrw-ctrl-r| |netrw-o| |netrw-p| - |netrw-P| |netrw-t| |netrw-v| -Associated setting variables: - |g:netrw_alto| control above/below splitting - |g:netrw_winsize| control initial sizing - -BROWSING WITH A NEW TAB *netrw-t* {{{2 - -Normally one enters a file or directory using the . The "t" map -allows one to open a new window holding the new directory listing or file in -a new tab. - -If you'd like to have the new listing in a background tab, use |gT|. - -Related topics: - |netrw-ctrl-r| |netrw-o| |netrw-p| - |netrw-P| |netrw-t| |netrw-v| -Associated setting variables: - |g:netrw_winsize| control initial sizing - -BROWSING WITH A VERTICALLY SPLIT WINDOW *netrw-v* {{{2 - -Normally one enters a file or directory using the . However, the "v" map -allows one to open a new window to hold the new directory listing or file. A -vertical split is used. (for horizontal splitting, see |netrw-o|) - -Normally, the v key splits the window vertically with the new window and -cursor at the left. - -There is only one tree listing buffer; using "v" on a displayed subdirectory -will split the screen, but the same buffer will be shown twice. - -Related topics: - |netrw-ctrl-r| |netrw-o| |netrw-p| - |netrw-P| |netrw-t| |netrw-v| -Associated setting variables: - |g:netrw_altv| control right/left splitting - |g:netrw_winsize| control initial sizing - - -BROWSING USING A GVIM SERVER *netrw-ctrl-r* {{{2 - -One may keep a browsing gvim separate from the gvim being used to edit. -Use the map on a file (not a directory) in the netrw browser, and it -will use a gvim server (see |g:netrw_servername|). Subsequent use of -(see |netrw-cr|) will re-use that server for editing files. - -Related topics: - |netrw-ctrl-r| |netrw-o| |netrw-p| - |netrw-P| |netrw-t| |netrw-v| -Associated setting variables: - |g:netrw_servername| : sets name of server - |g:netrw_browse_split| : controls how will open files - - -CHANGE LISTING STYLE (THIN LONG WIDE TREE) *netrw-i* {{{2 - -The "i" map cycles between the thin, long, wide, and tree listing formats. - -The thin listing format gives just the files' and directories' names. - -The long listing is either based on the "ls" command via ssh for remote -directories or displays the filename, file size (in bytes), and the time and -date of last modification for local directories. With the long listing -format, netrw is not able to recognize filenames which have trailing spaces. -Use the thin listing format for such files. - -The wide listing format uses two or more contiguous spaces to delineate -filenames; when using that format, netrw won't be able to recognize or use -filenames which have two or more contiguous spaces embedded in the name or any -trailing spaces. The thin listing format will, however, work with such files. -The wide listing format is the most compact. - -The tree listing format has a top directory followed by files and directories -preceded by one or more "|"s, which indicate the directory depth. One may -open and close directories by pressing the key while atop the directory -name. - -One may make a preferred listing style your default; see |g:netrw_liststyle|. -As an example, by putting the following line in your .vimrc, > - let g:netrw_liststyle= 3 -the tree style will become your default listing style. - -One typical way to use the netrw tree display is to: > - - vim . - (use i until a tree display shows) - navigate to a file - v (edit as desired in vertically split window) - ctrl-w h (to return to the netrw listing) - P (edit newly selected file in the previous window) - ctrl-w h (to return to the netrw listing) - P (edit newly selected file in the previous window) - ...etc... -< -Associated setting variables: |g:netrw_liststyle| |g:netrw_maxfilenamelen| - |g:netrw_timefmt| |g:netrw_list_cmd| - -CHANGE FILE PERMISSION *netrw-gp* {{{2 - -"gp" will ask you for a new permission for the file named under the cursor. -Currently, this only works for local files. - -Associated setting variables: |g:netrw_chgperm| - - -CHANGING TO A BOOKMARKED DIRECTORY *netrw-gb* {{{2 - -To change directory back to a bookmarked directory, use - - {cnt}gb - -Any count may be used to reference any of the bookmarks. -Note that |netrw-qb| shows both bookmarks and history; to go -to a location stored in the history see |netrw-u| and |netrw-U|. - -Related Topics: - |netrw-mB| how to delete bookmarks - |netrw-mb| how to make a bookmark - |netrw-qb| how to list bookmarks - - -CHANGING TO A PREDECESSOR DIRECTORY *netrw-u* *netrw-updir* {{{2 - -Every time you change to a new directory (new for the current session), netrw -will save the directory in a recently-visited directory history list (unless -|g:netrw_dirhistmax| is zero; by default, it holds ten entries). With the "u" -map, one can change to an earlier directory (predecessor). To do the -opposite, see |netrw-U|. - -The "u" map also accepts counts to go back in the history several slots. For -your convenience, qb (see |netrw-qb|) lists the history number which may be -used in that count. - - *.netrwhist* -See |g:netrw_dirhistmax| for how to control the quantity of history stack -slots. The file ".netrwhist" holds history when netrw (and vim) is not -active. By default, its stored on the first directory on the user's -|'runtimepath'|. - -Related Topics: - |netrw-U| changing to a successor directory - |g:netrw_home| controls where .netrwhist is kept - - -CHANGING TO A SUCCESSOR DIRECTORY *netrw-U* *netrw-downdir* {{{2 - -With the "U" map, one can change to a later directory (successor). -This map is the opposite of the "u" map. (see |netrw-u|) Use the -qb map to list both the bookmarks and history. (see |netrw-qb|) - -The "U" map also accepts counts to go forward in the history several slots. - -See |g:netrw_dirhistmax| for how to control the quantity of history stack -slots. - - -CHANGING TREE TOP *netrw-ntree* *:Ntree* *netrw-gn* {{{2 - -One may specify a new tree top for tree listings using > - - :Ntree [dirname] - -Without a "dirname", the current line is used (and any leading depth -information is elided). -With a "dirname", the specified directory name is used. - -The "gn" map will take the word below the cursor and use that for -changing the top of the tree listing. - - -NETRW CLEAN *netrw-clean* *:NetrwClean* {{{2 - -With :NetrwClean one may easily remove netrw from one's home directory; -more precisely, from the first directory on your |'runtimepath'|. - -With :NetrwClean!, netrw will attempt to remove netrw from all directories on -your |'runtimepath'|. Of course, you have to have write/delete permissions -correct to do this. - -With either form of the command, netrw will first ask for confirmation -that the removal is in fact what you want to do. If netrw doesn't have -permission to remove a file, it will issue an error message. - -CUSTOMIZING BROWSING WITH A SPECIAL HANDLER *netrw-x* *netrw-handler* {{{2 - -Certain files, such as html, gif, jpeg, (word/office) doc, etc, files, are -best seen with a special handler (ie. a tool provided with your computer's -operating system). Netrw allows one to invoke such special handlers by: - - * hitting gx with the cursor atop the file path or alternatively x - in a netrw buffer; the former can be disabled by defining the - |g:netrw_nogx| variable - * when in command line, typing :Open , see |:Open| below. - -One may also use visual mode (see |visual-start|) to select the text that the -special handler will use. Normally gx checks for a close-by URL or file name -to pick up the text under the cursor; one may change what |expand()| uses via the -|g:netrw_gx| variable (options include "", ""). Note that -expand("") depends on the |'isfname'| setting. Alternatively, one may -select the text to be used by gx by making a visual selection (see -|visual-block|) and then pressing gx. - -The selection function can be adapted for each filetype by adding a function -`Netrw_get_URL_`, where is given by the 'filetype'. -The function should return the URL or file name to be used by gx, and will -fall back to the default behavior if it returns an empty string. -For example, special handlers for links Markdown and HTML are - -" make gx work on concealed links regardless of exact cursor position: > - - function Netrw_get_URL_markdown() - " markdown URL such as [link text](http://ya.ru 'yandex search') - try - let save_view = winsaveview() - if searchpair('\[.\{-}\](', '', ')\zs', 'cbW', '', line('.')) > 0 - return matchstr(getline('.')[col('.')-1:], - \ '\[.\{-}\](\zs' .. g:netrw_regex_url .. '\ze\(\s\+.\{-}\)\?)') - endif - return '' - finally - call winrestview(save_view) - endtry - endfunction - - function Netrw_get_URL_html() - " HTML URL such as Python is here - " - try - let save_view = winsaveview() - if searchpair('\|/>\)\zs', 'cbW', '', line('.')) > 0 - return matchstr(getline('.')[col('.') - 1 : ], - \ 'href=["'.."'"..']\?\zs\S\{-}\ze["'.."'"..']\?/\?>') - endif - return '' - finally - call winrestview(save_view) - endtry - endfunction -< -Other than a file path, the text under the cursor may be a URL. Netrw uses -by default the following regular expression to determine if the text under the -cursor is a URL: -> - :let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}' -< -Associated setting variables: - |g:netrw_gx| control how gx picks up the text under the cursor - |g:netrw_nogx| prevent gx map while editing - |g:netrw_suppress_gx_mesg| controls gx's suppression of browser messages - -OPENING FILES AND LAUNCHING APPS *netrw-gx* *:Open* *:Launch* {{{2 - -Netrw determines which special handler by the following method: - - * if |g:netrw_browsex_viewer| exists, then it will be used to attempt to - view files. - If the viewer you wish to use does not support handling of a remote URL - directory, set |g:netrw_browsex_support_remote| to 0. - * otherwise: - - * for Windows : explorer.exe is used - * for Mac OS X : open is used. - * for Linux : xdg-open is used. - -To open a path (or URL) by the appropriate handler, type > - - :Open -< -No escaping, neither for the shell nor for Vim's command-line, is needed. - -To launch a specific application , often being > - - :Launch . - -Since can be arbitrarily complex, in particular contain many file -paths, the escaping is left to the user. - -If you disabled the netrw plugin by setting g:loaded_netrwPlugin (see -|netrw-noload|), then you can use > - - :call netrw#Launch(' ') - :call netrw#Open('') -< - *netrw-curdir* -DELETING BOOKMARKS *netrw-mB* {{{2 - -To delete a bookmark, use > - - {cnt}mB - -If there are marked files, then mB will remove them from the -bookmark list. - -Alternatively, one may use :NetrwMB! (see |netrw-:NetrwMB|). > - - :NetrwMB! [files/directories] - -Related Topics: - |netrw-gb| how to return (go) to a bookmark - |netrw-mb| how to make a bookmark - |netrw-qb| how to list bookmarks - - -DELETING FILES OR DIRECTORIES *netrw-delete* *netrw-D* *netrw-del* {{{2 - -If files have not been marked with |netrw-mf|: (local marked file list) - - Deleting/removing files and directories involves moving the cursor to the - file/directory to be deleted and pressing "D". Directories must be empty - first before they can be successfully removed. If the directory is a - softlink to a directory, then netrw will make two requests to remove the - directory before succeeding. Netrw will ask for confirmation before doing - the removal(s). You may select a range of lines with the "V" command - (visual selection), and then pressing "D". - -If files have been marked with |netrw-mf|: (local marked file list) - - Marked files (and empty directories) will be deleted; again, you'll be - asked to confirm the deletion before it actually takes place. - -A further approach is to delete files which match a pattern. - - * use :MF pattern (see |netrw-:MF|); then press "D". - - * use mr (see |netrw-mr|) which will prompt you for pattern. - This will cause the matching files to be marked. Then, - press "D". - -Please note that only empty directories may be deleted with the "D" mapping. -Regular files are deleted with |delete()|, too. - -The |g:netrw_rm_cmd|, |g:netrw_rmf_cmd|, and |g:netrw_rmdir_cmd| variables are -used to control the attempts to remove remote files and directories. The -g:netrw_rm_cmd is used with files, and its default value is: - - g:netrw_rm_cmd: ssh HOSTNAME rm - -The g:netrw_rmdir_cmd variable is used to support the removal of directories. -Its default value is: - - |g:netrw_rmdir_cmd|: ssh HOSTNAME rmdir - -If removing a directory fails with g:netrw_rmdir_cmd, netrw then will attempt -to remove it again using the g:netrw_rmf_cmd variable. Its default value is: - - |g:netrw_rmf_cmd|: ssh HOSTNAME rm -f - -Related topics: |netrw-d| -Associated setting variable: |g:netrw_rm_cmd| |g:netrw_ssh_cmd| - - -*netrw-explore* *netrw-hexplore* *netrw-nexplore* *netrw-pexplore* -*netrw-rexplore* *netrw-sexplore* *netrw-texplore* *netrw-vexplore* *netrw-lexplore* -DIRECTORY EXPLORATION COMMANDS {{{2 - - :[N]Explore[!] [dir]... Explore directory of current file *:Explore* - :[N]Hexplore[!] [dir]... Horizontal Split & Explore *:Hexplore* - :[N]Lexplore[!] [dir]... Left Explorer Toggle *:Lexplore* - :[N]Sexplore[!] [dir]... Split&Explore current file's directory *:Sexplore* - :[N]Vexplore[!] [dir]... Vertical Split & Explore *:Vexplore* - :Texplore [dir]... Tab & Explore *:Texplore* - :Rexplore ... Return to/from Explorer *:Rexplore* - - Used with :Explore **/pattern : (also see |netrw-starstar|) - :Nexplore............. go to next matching file *:Nexplore* - :Pexplore............. go to previous matching file *:Pexplore* - - *netrw-:Explore* -:Explore will open the local-directory browser on the current file's - directory (or on directory [dir] if specified). The window will be - split only if the file has been modified and |'hidden'| is not set, - otherwise the browsing window will take over that window. Normally - the splitting is taken horizontally. - Also see: |netrw-:Rexplore| -:Explore! is like :Explore, but will use vertical splitting. - - *netrw-:Hexplore* -:Hexplore [dir] does an :Explore with |:belowright| horizontal splitting. -:Hexplore! [dir] does an :Explore with |:aboveleft| horizontal splitting. - - *netrw-:Lexplore* -:[N]Lexplore [dir] toggles a full height Explorer window on the left hand side - of the current tab. It will open a netrw window on the current - directory if [dir] is omitted; a :Lexplore [dir] will show the - specified directory in the left-hand side browser display no matter - from which window the command is issued. - - By default, :Lexplore will change an uninitialized |g:netrw_chgwin| - to 2; edits will thus preferentially be made in window#2. - - The [N] specifies a |g:netrw_winsize| just for the new :Lexplore - window. That means that - if [N] < 0 : use |N| columns for the Lexplore window - if [N] = 0 : a normal split is made - if [N] > 0 : use N% of the current window will be used for the - new window - - Those who like this method often also like tree style displays; - see |g:netrw_liststyle|. - -:[N]Lexplore! [dir] is similar to :Lexplore, except that the full-height - Explorer window will open on the right hand side and an - uninitialized |g:netrw_chgwin| will be set to 1 (eg. edits will - preferentially occur in the leftmost window). - - Also see: |netrw-C| |g:netrw_browse_split| |g:netrw_wiw| - |netrw-p| |netrw-P| |g:netrw_chgwin| - |netrw-c-tab| |g:netrw_winsize| - - *netrw-:Sexplore* -:[N]Sexplore will always split the window before invoking the local-directory - browser. As with Explore, the splitting is normally done - horizontally. -:[N]Sexplore! [dir] is like :Sexplore, but the splitting will be done vertically. - - *netrw-:Texplore* -:Texplore [dir] does a |:tabnew| before generating the browser window - - *netrw-:Vexplore* -:[N]Vexplore [dir] does an :Explore with |:leftabove| vertical splitting. -:[N]Vexplore! [dir] does an :Explore with |:rightbelow| vertical splitting. - -The optional parameters are: - - [N]: This parameter will override |g:netrw_winsize| to specify the quantity of - rows and/or columns the new explorer window should have. - Otherwise, the |g:netrw_winsize| variable, if it has been specified by the - user, is used to control the quantity of rows and/or columns new - explorer windows should have. - - [dir]: By default, these explorer commands use the current file's directory. - However, one may explicitly provide a directory (path) to use instead; - ie. > - - :Explore /some/path -< - *netrw-:Rexplore* -:Rexplore This command is a little different from the other Explore commands - as it doesn't necessarily open an Explorer window. - - Return to Explorer~ - When one edits a file using netrw which can occur, for example, - when pressing while the cursor is atop a filename in a netrw - browser window, a :Rexplore issued while editing that file will - return the display to that of the last netrw browser display in - that window. - - Return from Explorer~ - Conversely, when one is editing a directory, issuing a :Rexplore - will return to editing the file that was last edited in that - window. - - The <2-leftmouse> map (which is only available under gvim and - cooperative terms) does the same as :Rexplore. - -Also see: |g:netrw_alto| |g:netrw_altv| |g:netrw_winsize| - - -*netrw-star* *netrw-starpat* *netrw-starstar* *netrw-starstarpat* *netrw-grep* -EXPLORING WITH STARS AND PATTERNS {{{2 - -When Explore, Sexplore, Hexplore, or Vexplore are used with one of the -following four patterns Explore generates a list of files which satisfy the -request for the local file system. These exploration patterns will not work -with remote file browsing. - - */filepat files in current directory which satisfy filepat - **/filepat files in current directory or below which satisfy the - file pattern - *//pattern files in the current directory which contain the - pattern (vimgrep is used) - **//pattern files in the current directory or below which contain - the pattern (vimgrep is used) -< -The cursor will be placed on the first file in the list. One may then -continue to go to subsequent files on that list via |:Nexplore| or to -preceding files on that list with |:Pexplore|. Explore will update the -directory and place the cursor appropriately. - -A plain > - :Explore -will clear the explore list. - -If your console or gui produces recognizable shift-up or shift-down sequences, -then you'll likely find using shift-downarrow and shift-uparrow convenient. -They're mapped by netrw as follows: - - == Nexplore, and - == Pexplore. - -As an example, consider -> - :Explore */*.c - :Nexplore - :Nexplore - :Pexplore -< -The status line will show, on the right hand side of the status line, a -message like "Match 3 of 20". - -Associated setting variables: - |g:netrw_keepdir| |g:netrw_browse_split| - |g:netrw_fastbrowse| |g:netrw_ftp_browse_reject| - |g:netrw_ftp_list_cmd| |g:netrw_ftp_sizelist_cmd| - |g:netrw_ftp_timelist_cmd| |g:netrw_list_cmd| - |g:netrw_liststyle| - - -DISPLAYING INFORMATION ABOUT FILE *netrw-qf* {{{2 - -With the cursor atop a filename, pressing "qf" will reveal the file's size -and last modification timestamp. Currently this capability is only available -for local files. - - -EDIT FILE OR DIRECTORY HIDING LIST *netrw-ctrl-h* *netrw-edithide* {{{2 - -The "" map brings up a requestor allowing the user to change the -file/directory hiding list contained in |g:netrw_list_hide|. The hiding list -consists of one or more patterns delimited by commas. Files and/or -directories satisfying these patterns will either be hidden (ie. not shown) or -be the only ones displayed (see |netrw-a|). - -The "gh" mapping (see |netrw-gh|) quickly alternates between the usual -hiding list and the hiding of files or directories that begin with ".". - -As an example, > - let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+' -Effectively, this makes the effect of a |netrw-gh| command the initial setting. -What it means: - - \(^\|\s\s\) : if the line begins with the following, -or- - two consecutive spaces are encountered - \zs : start the hiding match now - \. : if it now begins with a dot - \S\+ : and is followed by one or more non-whitespace - characters - -Associated setting variables: |g:netrw_hide| |g:netrw_list_hide| -Associated topics: |netrw-a| |netrw-gh| |netrw-mh| - - *netrw-sort-sequence* -EDITING THE SORTING SEQUENCE *netrw-S* *netrw-sortsequence* {{{2 - -When "Sorted by" is name, one may specify priority via the sorting sequence -(g:netrw_sort_sequence). The sorting sequence typically prioritizes the -name-listing by suffix, although any pattern will do. Patterns are delimited -by commas. The default sorting sequence is (all one line): - -For Unix: > - '[\/]$,\ - '[\/]$,\.[a-np-z]$,\.h$,\.c$,\.cpp$,*,\.o$,\.obj$,\.info$, - \.swp$,\.bak$,\~$' -< -The lone * is where all filenames not covered by one of the other patterns -will end up. One may change the sorting sequence by modifying the -g:netrw_sort_sequence variable (either manually or in your <.vimrc>) or by -using the "S" map. - -Related topics: |netrw-s| |netrw-S| -Associated setting variables: |g:netrw_sort_sequence| |g:netrw_sort_options| - - -EXECUTING FILE UNDER CURSOR VIA SYSTEM() *netrw-X* {{{2 - -Pressing X while the cursor is atop an executable file will yield a prompt -using the filename asking for any arguments. Upon pressing a [return], netrw -will then call |system()| with that command and arguments. The result will be -displayed by |:echomsg|, and so |:messages| will repeat display of the result. -Ansi escape sequences will be stripped out. - -See |cmdline-window| for directions for more on how to edit the arguments. - - -FORCING TREATMENT AS A FILE OR DIRECTORY *netrw-gd* *netrw-gf* {{{2 - -Remote symbolic links (ie. those listed via ssh or ftp) are problematic -in that it is difficult to tell whether they link to a file or to a -directory. - -To force treatment as a file: use > - gf -< -To force treatment as a directory: use > - gd -< - -GOING UP *netrw--* {{{2 - -To go up a directory, press "-" or press the when atop the ../ directory -entry in the listing. - -Netrw will use the command in |g:netrw_list_cmd| to perform the directory -listing operation after changing HOSTNAME to the host specified by the -user-prpvided url. By default netrw provides the command as: > - - ssh HOSTNAME ls -FLa -< -where the HOSTNAME becomes the [user@]hostname as requested by the attempt to -read. Naturally, the user may override this command with whatever is -preferred. The NetList function which implements remote browsing -expects that directories will be flagged by a trailing slash. - - -HIDING FILES OR DIRECTORIES *netrw-a* *netrw-hiding* {{{2 - -Netrw's browsing facility allows one to use the hiding list in one of three -ways: ignore it, hide files which match, and show only those files which -match. - -If no files have been marked via |netrw-mf|: - -The "a" map allows the user to cycle through the three hiding modes. - -The |g:netrw_list_hide| variable holds a comma delimited list of patterns -based on regular expressions (ex. ^.*\.obj$,^\.) which specify the hiding list. -(also see |netrw-ctrl-h|) To set the hiding list, use the map. As an -example, to hide files which begin with a ".", one may use the map to -set the hiding list to '^\..*' (or one may put let g:netrw_list_hide= '^\..*' -in one's <.vimrc>). One may then use the "a" key to show all files, hide -matching files, or to show only the matching files. - - Example: \.[ch]$ - This hiding list command will hide/show all *.c and *.h files. - - Example: \.c$,\.h$ - This hiding list command will also hide/show all *.c and *.h - files. - -Don't forget to use the "a" map to select the mode (normal/hiding/show) you -want! - -If files have been marked using |netrw-mf|, then this command will: - - if showing all files or non-hidden files: - modify the g:netrw_list_hide list by appending the marked files to it - and showing only non-hidden files. - - else if showing hidden files only: - modify the g:netrw_list_hide list by removing the marked files from it - and showing only non-hidden files. - endif - - *netrw-gh* *netrw-hide* -As a quick shortcut, one may press > - gh -to toggle between hiding files which begin with a period (dot) and not hiding -them. - -Associated setting variables: |g:netrw_list_hide| |g:netrw_hide| -Associated topics: |netrw-a| |netrw-ctrl-h| |netrw-mh| - - *netrw-gitignore* -Netrw provides a helper function 'netrw_gitignore#Hide()' that, when used with -|g:netrw_list_hide| automatically hides all git-ignored files. - -'netrw_gitignore#Hide' searches for patterns in the following files: > - - './.gitignore' - './.git/info/exclude' - global gitignore file: `git config --global core.excludesfile` - system gitignore file: `git config --system core.excludesfile` -< -Files that do not exist, are ignored. -Git-ignore patterns are taken from existing files, and converted to patterns for -hiding files. For example, if you had '*.log' in your '.gitignore' file, it -would be converted to '.*\.log'. - -To use this function, simply assign its output to |g:netrw_list_hide| option. > - - Example: let g:netrw_list_hide= netrw_gitignore#Hide() - Git-ignored files are hidden in Netrw. - - Example: let g:netrw_list_hide= netrw_gitignore#Hide('my_gitignore_file') - Function can take additional files with git-ignore patterns. - - Example: let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' - Combining 'netrw_gitignore#Hide' with custom patterns. -< - -IMPROVING BROWSING *netrw-listhack* *netrw-ssh-hack* {{{2 - -Especially with the remote directory browser, constantly entering the password -is tedious. - -For Linux/Unix systems, the book "Linux Server Hacks - 100 industrial strength -tips & tools" by Rob Flickenger (O'Reilly, ISBN 0-596-00461-3) gives a tip -for setting up no-password ssh and scp and discusses associated security -issues. It used to be available at http://hacks.oreilly.com/pub/h/66 , -but apparently that address is now being redirected to some "hackzine". -I'll attempt a summary based on that article and on a communication from -Ben Schmidt: - - 1. Generate a public/private key pair on the local machine - (ssh client): > - ssh-keygen -t rsa - (saving the file in ~/.ssh/id_rsa as prompted) -< - 2. Just hit the when asked for passphrase (twice) for no - passphrase. If you do use a passphrase, you will also need to use - ssh-agent so you only have to type the passphrase once per session. - If you don't use a passphrase, simply logging onto your local - computer or getting access to the keyfile in any way will suffice - to access any ssh servers which have that key authorized for login. - - 3. This creates two files: > - ~/.ssh/id_rsa - ~/.ssh/id_rsa.pub -< - 4. On the target machine (ssh server): > - cd - mkdir -p .ssh - chmod 0700 .ssh -< - 5. On your local machine (ssh client): (one line) > - ssh {serverhostname} - cat '>>' '~/.ssh/authorized_keys2' < ~/.ssh/id_rsa.pub -< - or, for OpenSSH, (one line) > - ssh {serverhostname} - cat '>>' '~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub -< -You can test it out with > - ssh {serverhostname} -and you should be log onto the server machine without further need to type -anything. - -If you decided to use a passphrase, do: > - ssh-agent $SHELL - ssh-add - ssh {serverhostname} -You will be prompted for your key passphrase when you use ssh-add, but not -subsequently when you use ssh. For use with vim, you can use > - ssh-agent vim -and, when next within vim, use > - :!ssh-add -Alternatively, you can apply ssh-agent to the terminal you're planning on -running vim in: > - ssh-agent xterm & -and do ssh-add whenever you need. - -For Windows, folks on the vim mailing list have mentioned that Pageant helps -with avoiding the constant need to enter the password. - -Kingston Fung wrote about another way to avoid constantly needing to enter -passwords: - - In order to avoid the need to type in the password for scp each time, you - provide a hack in the docs to set up a non password ssh account. I found a - better way to do that: I can use a regular ssh account which uses a - password to access the material without the need to key-in the password - each time. It's good for security and convenience. I tried ssh public key - authorization + ssh-agent, implementing this, and it works! - - - Ssh hints: - - Thomer Gil has provided a hint on how to speed up netrw+ssh: - http://thomer.com/howtos/netrw_ssh.html - - Alex Young has several hints on speeding ssh up: - http://usevim.com/2012/03/16/editing-remote-files/ - - -LISTING BOOKMARKS AND HISTORY *netrw-qb* *netrw-listbookmark* {{{2 - -Pressing "qb" (query bookmarks) will list both the bookmarked directories and -directory traversal history. - -Related Topics: - |netrw-gb| how to return (go) to a bookmark - |netrw-mb| how to make a bookmark - |netrw-mB| how to delete bookmarks - |netrw-u| change to a predecessor directory via the history stack - |netrw-U| change to a successor directory via the history stack - -MAKING A NEW DIRECTORY *netrw-d* {{{2 - -With the "d" map one may make a new directory either remotely (which depends -on the global variable g:netrw_mkdir_cmd) or locally (which depends on the -global variable g:netrw_localmkdir). Netrw will issue a request for the new -directory's name. A bare at that point will abort the making of the -directory. Attempts to make a local directory that already exists (as either -a file or a directory) will be detected, reported on, and ignored. - -Related topics: |netrw-D| -Associated setting variables: |g:netrw_localmkdir| |g:netrw_mkdir_cmd| - |g:netrw_remote_mkdir| |netrw-%| - - -MAKING THE BROWSING DIRECTORY THE CURRENT DIRECTORY *netrw-cd* {{{2 - -By default, |g:netrw_keepdir| is 1. This setting means that the current -directory will not track the browsing directory. (done for backwards -compatibility with v6's file explorer). - -Setting g:netrw_keepdir to 0 tells netrw to make vim's current directory -track netrw's browsing directory. - -However, given the default setting for g:netrw_keepdir of 1 where netrw -maintains its own separate notion of the current directory, in order to make -the two directories the same, use the "cd" map (type cd). That map will -set Vim's notion of the current directory to netrw's current browsing -directory. - -|netrw-cd| : This map's name was changed from "c" to cd (see |netrw-cd|). - This change was done to allow for |netrw-cb| and |netrw-cB| maps. - -Associated setting variable: |g:netrw_keepdir| - -MARKING FILES *netrw-:MF* *netrw-mf* {{{2 - (also see |netrw-mr|) - -Netrw provides several ways to mark files: - - * One may mark files with the cursor atop a filename and - then pressing "mf". - - * With gvim, in addition one may mark files with - . (see |netrw-mouse|) - - * One may use the :MF command, which takes a list of - files (for local directories, the list may include - wildcards -- see |glob()|) > - - :MF *.c -< - (Note that :MF uses || to break the line - at spaces) - - * Mark files using the |argument-list| (|netrw-mA|) - - * Mark files based upon a |location-list| (|netrw-qL|) - - * Mark files based upon the quickfix list (|netrw-qF|) - (|quickfix-error-lists|) - -The following netrw maps make use of marked files: - - |netrw-a| Hide marked files/directories - |netrw-D| Delete marked files/directories - |netrw-ma| Move marked files' names to |arglist| - |netrw-mA| Move |arglist| filenames to marked file list - |netrw-mb| Append marked files to bookmarks - |netrw-mB| Delete marked files from bookmarks - |netrw-mc| Copy marked files to target - |netrw-md| Apply vimdiff to marked files - |netrw-me| Edit marked files - |netrw-mF| Unmark marked files - |netrw-mg| Apply vimgrep to marked files - |netrw-mm| Move marked files to target - |netrw-ms| Netrw will source marked files - |netrw-mt| Set target for |netrw-mm| and |netrw-mc| - |netrw-mT| Generate tags using marked files - |netrw-mv| Apply vim command to marked files - |netrw-mx| Apply shell command to marked files - |netrw-mX| Apply shell command to marked files, en bloc - |netrw-mz| Compress/Decompress marked files - |netrw-O| Obtain marked files - |netrw-R| Rename marked files - -One may unmark files one at a time the same way one marks them; ie. place -the cursor atop a marked file and press "mf". This process also works -with using gvim. One may unmark all files by pressing -"mu" (see |netrw-mu|). - -Marked files are highlighted using the "netrwMarkFile" highlighting group, -which by default is linked to "Identifier" (see Identifier under -|group-name|). You may change the highlighting group by putting something -like > - - highlight clear netrwMarkFile - hi link netrwMarkFile ..whatever.. -< -into $HOME/.vim/after/syntax/netrw.vim . - -If the mouse is enabled and works with your vim, you may use to -mark one or more files. You may mark multiple files by dragging the shifted -leftmouse. (see |netrw-mouse|) - - *markfilelist* *global_markfilelist* *local_markfilelist* -All marked files are entered onto the global marked file list; there is only -one such list. In addition, every netrw buffer also has its own buffer-local -marked file list; since netrw buffers are associated with specific -directories, this means that each directory has its own local marked file -list. The various commands which operate on marked files use one or the other -of the marked file lists. - -Known Problem: if one is using tree mode (|g:netrw_liststyle|) and several -directories have files with the same name, then marking such a file will -result in all such files being highlighted as if they were all marked. The -|markfilelist|, however, will only have the selected file in it. This problem -is unlikely to be fixed. - - -UNMARKING FILES *netrw-mF* {{{2 - (also see |netrw-mf|, |netrw-mu|) - -The "mF" command will unmark all files in the current buffer. One may also use -mf (|netrw-mf|) on a specific, already marked, file to unmark just that file. - -MARKING FILES BY LOCATION LIST *netrw-qL* {{{2 - (also see |netrw-mf|) - -One may convert |location-list|s into a marked file list using "qL". -You may then proceed with commands such as me (|netrw-me|) to edit them. - - -MARKING FILES BY QUICKFIX LIST *netrw-qF* {{{2 - (also see |netrw-mf|) - -One may convert |quickfix-error-lists| into a marked file list using "qF". -You may then proceed with commands such as me (|netrw-me|) to edit them. -Quickfix error lists are generated, for example, by calls to |:vimgrep|. - - -MARKING FILES BY REGULAR EXPRESSION *netrw-mr* {{{2 - (also see |netrw-mf|) - -One may also mark files by pressing "mr"; netrw will then issue a prompt, -"Enter regexp: ". You may then enter a shell-style regular expression such -as *.c$ (see |glob()|). For remote systems, glob() doesn't work -- so netrw -converts "*" into ".*" (see |regexp|) and marks files based on that. In the -future I may make it possible to use |regexp|s instead of glob()-style -expressions (yet-another-option). - -See |cmdline-window| for directions on more on how to edit the regular -expression. - - -MARKED FILES, ARBITRARY VIM COMMAND *netrw-mv* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the local marked-file list) - -The "mv" map causes netrw to execute an arbitrary vim command on each file on -the local marked file list, individually: - - * 1split - * sil! keepalt e file - * run vim command - * sil! keepalt wq! - -A prompt, "Enter vim command: ", will be issued to elicit the vim command you -wish used. See |cmdline-window| for directions for more on how to edit the -command. - - -MARKED FILES, ARBITRARY SHELL COMMAND *netrw-mx* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the local marked-file list) - -Upon activation of the "mx" map, netrw will query the user for some (external) -command to be applied to all marked files. All "%"s in the command will be -substituted with the name of each marked file in turn. If no "%"s are in the -command, then the command will be followed by a space and a marked filename. - -Example: - (mark files) - mx - Enter command: cat - - The result is a series of shell commands: - cat 'file1' - cat 'file2' - ... - - -MARKED FILES, ARBITRARY SHELL COMMAND, EN BLOC *netrw-mX* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked-file list) - -Upon activation of the 'mX' map, netrw will query the user for some (external) -command to be applied to all marked files on the global marked file list. The -"en bloc" means that one command will be executed on all the files at once: > - - command files - -This approach is useful, for example, to select files and make a tarball: > - - (mark files) - mX - Enter command: tar cf mynewtarball.tar -< -The command that will be run with this example: - - tar cf mynewtarball.tar 'file1' 'file2' ... - - -MARKED FILES: ARGUMENT LIST *netrw-ma* *netrw-mA* - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked-file list) - -Using ma, one moves filenames from the marked file list to the argument list. -Using mA, one moves filenames from the argument list to the marked file list. - -See Also: |netrw-cb| |netrw-cB| |netrw-qF| |argument-list| |:args| - - -MARKED FILES: BUFFER LIST *netrw-cb* *netrw-cB* - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked-file list) - -Using cb, one moves filenames from the marked file list to the buffer list. -Using cB, one copies filenames from the buffer list to the marked file list. - -See Also: |netrw-ma| |netrw-mA| |netrw-qF| |buffer-list| |:buffers| - - -MARKED FILES: COMPRESSION AND DECOMPRESSION *netrw-mz* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the local marked file list) - -If any marked files are compressed, then "mz" will decompress them. -If any marked files are decompressed, then "mz" will compress them -using the command specified by |g:netrw_compress|; by default, -that's "gzip". - -For decompression, netrw uses a |Dictionary| of suffices and their -associated decompressing utilities; see |g:netrw_decompress|. - -Remember that one can mark multiple files by regular expression -(see |netrw-mr|); this is particularly useful to facilitate compressing and -decompressing a large number of files. - -Associated setting variables: |g:netrw_compress| |g:netrw_decompress| - -MARKED FILES: COPYING *netrw-mc* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (Uses the global marked file list) - -Select a target directory with mt (|netrw-mt|). Then change directory, -select file(s) (see |netrw-mf|), and press "mc". The copy is done -from the current window (where one does the mf) to the target. - -If one does not have a target directory set with |netrw-mt|, then netrw -will query you for a directory to copy to. - -One may also copy directories and their contents (local only) to a target -directory. - -Associated setting variables: - |g:netrw_localcopycmd| |g:netrw_localcopycmdopt| - |g:netrw_localcopydircmd| |g:netrw_localcopydircmdopt| - |g:netrw_ssh_cmd| - -MARKED FILES: DIFF *netrw-md* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked file list) - -Use vimdiff to visualize difference between selected files (two or -three may be selected for this). Uses the global marked file list. - -MARKED FILES: EDITING *netrw-me* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked file list) - -The "me" command will place the marked files on the |arglist| and commence -editing them. One may return the to explorer window with |:Rexplore|. -(use |:n| and |:p| to edit next and previous files in the arglist) - -MARKED FILES: GREP *netrw-mg* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked file list) - -The "mg" command will apply |:vimgrep| to the marked files. -The command will ask for the requested pattern; one may then enter: > - - /pattern/[g][j] - ! /pattern/[g][j] - pattern -< -With /pattern/, editing will start with the first item on the |quickfix| list -that vimgrep sets up (see |:copen|, |:cnext|, |:cprevious|, |:cclose|). The |:vimgrep| -command is in use, so without 'g' each line is added to quickfix list only -once; with 'g' every match is included. - -With /pattern/j, "mg" will winnow the current marked file list to just those -marked files also possessing the specified pattern. Thus, one may use > - - mr ...file-pattern... - mg /pattern/j -< -to have a marked file list satisfying the file-pattern but also restricted to -files containing some desired pattern. - - -MARKED FILES: HIDING AND UNHIDING BY SUFFIX *netrw-mh* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the local marked file list) - -The "mh" command extracts the suffices of the marked files and toggles their -presence on the hiding list. Please note that marking the same suffix -this way multiple times will result in the suffix's presence being toggled -for each file (so an even quantity of marked files having the same suffix -is the same as not having bothered to select them at all). - -Related topics: |netrw-a| |g:netrw_list_hide| - -MARKED FILES: MOVING *netrw-mm* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked file list) - - WARNING: moving files is more dangerous than copying them. - A file being moved is first copied and then deleted; if the - copy operation fails and the delete succeeds, you will lose - the file. Either try things out with unimportant files - first or do the copy and then delete yourself using mc and D. - Use at your own risk! - -Select a target directory with mt (|netrw-mt|). Then change directory, -select file(s) (see |netrw-mf|), and press "mm". The move is done -from the current window (where one does the mf) to the target. - -Associated setting variable: |g:netrw_localmovecmd| |g:netrw_ssh_cmd| - -MARKED FILES: SOURCING *netrw-ms* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the local marked file list) - -With "ms", netrw will source the marked files (using vim's |:source| command) - - -MARKED FILES: SETTING THE TARGET DIRECTORY *netrw-mt* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - -Set the marked file copy/move-to target (see |netrw-mc| and |netrw-mm|): - - * If the cursor is atop a file name, then the netrw window's currently - displayed directory is used for the copy/move-to target. - - * Also, if the cursor is in the banner, then the netrw window's currently - displayed directory is used for the copy/move-to target. - Unless the target already is the current directory. In which case, - typing "mf" clears the target. - - * However, if the cursor is atop a directory name, then that directory is - used for the copy/move-to target - - * One may use the :MT [directory] command to set the target *netrw-:MT* - This command uses ||, so spaces in the directory name are - permitted without escaping. - - * With mouse-enabled vim or with gvim, one may select a target by using - - -There is only one copy/move-to target at a time in a vim session; ie. the -target is a script variable (see |s:var|) and is shared between all netrw -windows (in an instance of vim). - -When using menus and gvim, netrw provides a "Targets" entry which allows one -to pick a target from the list of bookmarks and history. - -Related topics: - Marking Files......................................|netrw-mf| - Marking Files by Regular Expression................|netrw-mr| - Marked Files: Target Directory Using Bookmarks.....|netrw-Tb| - Marked Files: Target Directory Using History.......|netrw-Th| - - -MARKED FILES: TAGGING *netrw-mT* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the global marked file list) - -The "mT" mapping will apply the command in |g:netrw_ctags| (by default, it is -"ctags") to marked files. For remote browsing, in order to create a tags file -netrw will use ssh (see |g:netrw_ssh_cmd|), and so ssh must be available for -this to work on remote systems. For your local system, see |ctags| on how to -get a version. I myself use hdrtags, currently available at -http://www.drchip.org/astronaut/src/index.html , and have > - - let g:netrw_ctags= "hdrtag" -< -in my <.vimrc>. - -When a remote set of files are tagged, the resulting tags file is "obtained"; -ie. a copy is transferred to the local system's directory. The now local tags -file is then modified so that one may use it through the network. The -modification made concerns the names of the files in the tags; each filename is -preceded by the netrw-compatible URL used to obtain it. When one subsequently -uses one of the go to tag actions (|tags|), the URL will be used by netrw to -edit the desired file and go to the tag. - -Associated setting variables: |g:netrw_ctags| |g:netrw_ssh_cmd| - -MARKED FILES: TARGET DIRECTORY USING BOOKMARKS *netrw-Tb* {{{2 - -Sets the marked file copy/move-to target. - -The |netrw-qb| map will give you a list of bookmarks (and history). -One may choose one of the bookmarks to become your marked file -target by using [count]Tb (default count: 1). - -Related topics: - Copying files to target............................|netrw-mc| - Listing Bookmarks and History......................|netrw-qb| - Marked Files: Setting The Target Directory.........|netrw-mt| - Marked Files: Target Directory Using History.......|netrw-Th| - Marking Files......................................|netrw-mf| - Marking Files by Regular Expression................|netrw-mr| - Moving files to target.............................|netrw-mm| - - -MARKED FILES: TARGET DIRECTORY USING HISTORY *netrw-Th* {{{2 - -Sets the marked file copy/move-to target. - -The |netrw-qb| map will give you a list of history (and bookmarks). -One may choose one of the history entries to become your marked file -target by using [count]Th (default count: 0; ie. the current directory). - -Related topics: - Copying files to target............................|netrw-mc| - Listing Bookmarks and History......................|netrw-qb| - Marked Files: Setting The Target Directory.........|netrw-mt| - Marked Files: Target Directory Using Bookmarks.....|netrw-Tb| - Marking Files......................................|netrw-mf| - Marking Files by Regular Expression................|netrw-mr| - Moving files to target.............................|netrw-mm| - - -MARKED FILES: UNMARKING *netrw-mu* {{{2 - (See |netrw-mf|, |netrw-mF|) - -The "mu" mapping will unmark all currently marked files. This command differs -from "mF" as the latter only unmarks files in the current directory whereas -"mu" will unmark global and all buffer-local marked files. -(see |netrw-mF|) - - - *netrw-browser-settings* -NETRW BROWSER VARIABLES *netrw-browser-options* *netrw-browser-var* {{{2 - -(if you're interested in the netrw file transfer settings, see |netrw-options| - and |netrw-protocol|) - -The browser provides settings in the form of variables which -you may modify; by placing these settings in your <.vimrc>, you may customize -your browsing preferences. (see also: |netrw-settings|) -> - --- ----------- - Var Explanation - --- ----------- -< *g:netrw_altfile* some like |CTRL-^| to return to the last - edited file. Choose that by setting this - parameter to 1. - Others like |CTRL-^| to return to the - netrw browsing buffer. Choose that by setting - this parameter to 0. - default: =0 - - *g:netrw_alto* change from above splitting to below splitting - by setting this variable (see |netrw-o|) - default: =&sb (see |'sb'|) - - *g:netrw_altv* change from left splitting to right splitting - by setting this variable (see |netrw-v|) - default: =&spr (see |'spr'|) - - *g:netrw_banner* enable/suppress the banner - =0: suppress the banner - =1: banner is enabled (default) - - *g:netrw_bannerbackslash* if this variable exists and is not zero, the - banner will be displayed with backslashes - rather than forward slashes. - - *g:netrw_browse_split* when browsing, will open the file by: - =0: re-using the same window (default) - =1: horizontally splitting the window first - =2: vertically splitting the window first - =3: open file in new tab - =4: act like "P" (ie. open previous window) - Note that |g:netrw_preview| may be used - to get vertical splitting instead of - horizontal splitting. - =[servername,tab-number,window-number] - Given a |List| such as this, a remote server - named by the "servername" will be used for - editing. It will also use the specified tab - and window numbers to perform editing - (see |clientserver|, |netrw-ctrl-r|) - This option does not affect the production of - |:Lexplore| windows. - - Related topics: - |g:netrw_alto| |g:netrw_altv| - |netrw-C| |netrw-cr| - |netrw-ctrl-r| - - *g:netrw_browsex_viewer* specify user's preference for a viewer: > - "kfmclient exec" - "gnome-open" -< - *g:netrw_browsex_support_remote* - specify if the specified viewer supports a - remote URL. (see |netrw-handler|). - - *g:netrw_chgperm* Unix/Linux: "chmod PERM FILENAME" - Windows: "cacls FILENAME /e /p PERM" - Used to change access permission for a file. - - *g:netrw_clipboard* =1 - By default, netrw will attempt to insure that - the clipboard's values will remain unchanged. - However, some users report that they have - speed problems with this; consequently, this - option, when set to zero, lets such users - prevent netrw from saving and restoring the - clipboard (the latter is done only as needed). - That means that if the clipboard is changed - (inadvertently) by normal netrw operation that - it will not be restored to its prior state. - - *g:netrw_compress* ="gzip" - Will compress marked files with this - command - - *g:Netrw_corehandler* Allows one to specify something additional - to do when handling files via netrw's - browser's "x" command (see |netrw-x|). If - present, g:Netrw_corehandler specifies - either one or more function references - (see |Funcref|). (the capital g:Netrw... - is required its holding a function reference) - - - *g:netrw_ctags* ="ctags" - The default external program used to create - tags - - *g:netrw_cursor* = 2 (default) - This option controls the use of the - |'cursorline'| (cul) and |'cursorcolumn'| - (cuc) settings by netrw: - - Value Thin-Long-Tree Wide - =0 u-cul u-cuc u-cul u-cuc - =1 u-cul u-cuc cul u-cuc - =2 cul u-cuc cul u-cuc - =3 cul u-cuc cul cuc - =4 cul cuc cul cuc - =5 U-cul U-cuc U-cul U-cuc - =6 U-cul U-cuc cul U-cuc - =7 cul U-cuc cul U-cuc - =8 cul U-cuc cul cuc - - Where - u-cul : user's |'cursorline'| initial setting used - u-cuc : user's |'cursorcolumn'| initial setting used - U-cul : user's |'cursorline'| current setting used - U-cuc : user's |'cursorcolumn'| current setting used - cul : |'cursorline'| will be locally set - cuc : |'cursorcolumn'| will be locally set - - The "initial setting" means the values of - the |'cuc'| and |'cul'| settings in effect when - netrw last saw |g:netrw_cursor| >= 5 or when - netrw was initially run. - - *g:netrw_decompress* = { ".lz4": "lz4 -d", - ".lzo": "lzop -d", - ".lz": "lzip -dk", - ".7z": "7za x", - ".001": "7za x", - ".tar.bz": "tar -xvjf", - ".tar.bz2": "tar -xvjf", - ".tbz": "tar -xvjf", - ".tbz2": "tar -xvjf", - ".tar.gz": "tar -xvzf", - ".tgz": "tar -xvzf", - ".tar.zst": "tar --use-compress-program=unzstd -xvf", - ".tzst": "tar --use-compress-program=unzstd -xvf", - ".tar": "tar -xvf", - ".zip": "unzip", - ".bz": "bunzip2 -k", - ".bz2": "bunzip2 -k", - ".gz": "gunzip -k", - ".lzma": "unlzma -T0 -k", - ".xz": "unxz -T0 -k", - ".zst": "zstd -T0 -d", - ".Z": "uncompress -k", - ".rar": "unrar x -ad", - ".tar.lzma": "tar --lzma -xvf", - ".tlz": "tar --lzma -xvf", - ".tar.xz": "tar -xvJf", - ".txz": "tar -xvJf"} - - A dictionary mapping suffices to - decompression programs. - - *g:netrw_dirhistmax* =10: controls maximum quantity of past - history. May be zero to suppress - history. - (related: |netrw-qb| |netrw-u| |netrw-U|) - - *g:netrw_dynamic_maxfilenamelen* =32: enables dynamic determination of - |g:netrw_maxfilenamelen|, which affects - local file long listing. - - *g:netrw_errorlvl* =0: error levels greater than or equal to - this are permitted to be displayed - 0: notes - 1: warnings - 2: errors - - *g:netrw_fastbrowse* =0: slow speed directory browsing; - never re-uses directory listings; - always obtains directory listings. - =1: medium speed directory browsing; - re-use directory listings only - when remote directory browsing. - (default value) - =2: fast directory browsing; - only obtains directory listings when the - directory hasn't been seen before - (or |netrw-ctrl-l| is used). - - Fast browsing retains old directory listing - buffers so that they don't need to be - re-acquired. This feature is especially - important for remote browsing. However, if - a file is introduced or deleted into or from - such directories, the old directory buffer - becomes out-of-date. One may always refresh - such a directory listing with |netrw-ctrl-l|. - This option gives the user the choice of - trading off accuracy (ie. up-to-date listing) - versus speed. - - *g:netrw_ffkeep* (default: doesn't exist) - If this variable exists and is zero, then - netrw will not do a save and restore for - |'fileformat'|. - - *g:netrw_fname_escape* =' ?&;%' - Used on filenames before remote reading/writing - - *g:netrw_ftp_browse_reject* ftp can produce a number of errors and warnings - that can show up as "directories" and "files" - in the listing. This pattern is used to - remove such embedded messages. By default its - value is: - '^total\s\+\d\+$\| - ^Trying\s\+\d\+.*$\| - ^KERBEROS_V\d rejected\| - ^Security extensions not\| - No such file\| - : connect to address [0-9a-fA-F:]* - : No route to host$' - - *g:netrw_ftp_list_cmd* options for passing along to ftp for directory - listing. Defaults: - unix or g:netrw_cygwin set: : "ls -lF" - otherwise "dir" - - - *g:netrw_ftp_sizelist_cmd* options for passing along to ftp for directory - listing, sorted by size of file. - Defaults: - unix or g:netrw_cygwin set: : "ls -slF" - otherwise "dir" - - *g:netrw_ftp_timelist_cmd* options for passing along to ftp for directory - listing, sorted by time of last modification. - Defaults: - unix or g:netrw_cygwin set: : "ls -tlF" - otherwise "dir" - - *g:netrw_glob_escape* ='[]*?`{~$' (unix) - ='[]*?`{$' (windows - These characters in directory names are - escaped before applying glob() - - *g:netrw_gx* ="" - This option controls how gx (|netrw-gx|) picks - up the text under the cursor. See |expand()| - for possibilities. - - *g:netrw_hide* Controlled by the "a" map (see |netrw-a|) - =0 : show all - =1 : show not-hidden files - =2 : show hidden files only - default: =1 - - *g:netrw_home* The home directory for where bookmarks and - history are saved (as .netrwbook and - .netrwhist). - Netrw uses |expand()| on the string. - default: stdpath("data") (see |stdpath()|) - - *g:netrw_keepdir* =1 (default) keep current directory immune from - the browsing directory. - =0 keep the current directory the same as the - browsing directory. - The current browsing directory is contained in - b:netrw_curdir (also see |netrw-cd|) - - *g:netrw_keepj* ="keepj" (default) netrw attempts to keep the - |:jumps| table unaffected. - ="" netrw will not use |:keepjumps| with - exceptions only for the - saving/restoration of position. - - *g:netrw_list_cmd* command for listing remote directories - default: (if ssh is executable) - "ssh HOSTNAME ls -FLa" - - *g:netrw_list_cmd_options* If this variable exists, then its contents are - appended to the g:netrw_list_cmd. For - example, use "2>/dev/null" to get rid of banner - messages on unix systems. - - - *g:netrw_liststyle* Set the default listing style: - = 0: thin listing (one file per line) - = 1: long listing (one file per line with time - stamp information and file size) - = 2: wide listing (multiple files in columns) - = 3: tree style listing - - *g:netrw_list_hide* comma-separated pattern list for hiding files - Patterns are regular expressions (see |regexp|) - There's some special support for git-ignore - files: you may add the output from the helper - function 'netrw_gitignore#Hide() automatically - hiding all gitignored files. - For more details see |netrw-gitignore|. - default: "" - - Examples: > - let g:netrw_list_hide= '.*\.swp$' - let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' -< - *g:netrw_localcopycmd* ="cp" Linux/Unix/MacOS/Cygwin - =expand("$COMSPEC") Windows - Copies marked files (|netrw-mf|) to target - directory (|netrw-mt|, |netrw-mc|) - - *g:netrw_localcopycmdopt* ='' Linux/Unix/MacOS/Cygwin - =' \c copy' Windows - Options for the |g:netrw_localcopycmd| - - *g:netrw_localcopydircmd* ="cp" Linux/Unix/MacOS/Cygwin - =expand("$COMSPEC") Windows - Copies directories to target directory. - (|netrw-mc|, |netrw-mt|) - - *g:netrw_localcopydircmdopt* =" -R" Linux/Unix/MacOS/Cygwin - =" /c xcopy /e /c /h/ /i /k" Windows - Options for |g:netrw_localcopydircmd| - - *g:netrw_localmkdir* ="mkdir" Linux/Unix/MacOS/Cygwin - =expand("$COMSPEC") Windows - command for making a local directory - - *g:netrw_localmkdiropt* ="" Linux/Unix/MacOS/Cygwin - =" /c mkdir" Windows - Options for |g:netrw_localmkdir| - - *g:netrw_localmovecmd* ="mv" Linux/Unix/MacOS/Cygwin - =expand("$COMSPEC") Windows - Moves marked files (|netrw-mf|) to target - directory (|netrw-mt|, |netrw-mm|) - - *g:netrw_localmovecmdopt* ="" Linux/Unix/MacOS/Cygwin - =" /c move" Windows - Options for |g:netrw_localmovecmd| - - *g:netrw_maxfilenamelen* =32 by default, selected so as to make long - listings fit on 80 column displays. - If your screen is wider, and you have file - or directory names longer than 32 bytes, - you may set this option to keep listings - columnar. - - *g:netrw_mkdir_cmd* command for making a remote directory - via ssh (also see |g:netrw_remote_mkdir|) - default: "ssh USEPORT HOSTNAME mkdir" - - *g:netrw_mousemaps* =1 (default) enables mouse buttons while - browsing to: - leftmouse : open file/directory - shift-leftmouse : mark file - middlemouse : same as P - rightmouse : remove file/directory - =0: disables mouse maps - - *g:netrw_nobeval* doesn't exist (default) - If this variable exists, then balloon - evaluation will be suppressed - (see |'ballooneval'|) - - *g:netrw_sizestyle* not defined: actual bytes (default) - ="b" : actual bytes (default) - ="h" : human-readable (ex. 5k, 4m, 3g) - uses 1000 base - ="H" : human-readable (ex. 5K, 4M, 3G) - uses 1024 base - The long listing (|netrw-i|) and query-file - maps (|netrw-qf|) will display file size - using the specified style. - - *g:netrw_usetab* if this variable exists and is non-zero, then - the map supporting shrinking/expanding a - Lexplore or netrw window will be enabled. - (see |netrw-c-tab|) - - *g:netrw_remote_mkdir* command for making a remote directory - via ftp (also see |g:netrw_mkdir_cmd|) - default: "mkdir" - - *g:netrw_retmap* if it exists and is set to one, then: - * if in a netrw-selected file, AND - * no normal-mode <2-leftmouse> mapping exists, - then the <2-leftmouse> will be mapped for easy - return to the netrw browser window. - example: click once to select and open a file, - double-click to return. - - Note that one may instead choose to: - * let g:netrw_retmap= 1, AND - * nmap YourChoice NetrwReturn - and have another mapping instead of - <2-leftmouse> to invoke the return. - - You may also use the |:Rexplore| command to do - the same thing. - - default: =0 - - *g:netrw_rm_cmd* command for removing remote files - default: "ssh USEPORT HOSTNAME rm" - - *g:netrw_rmdir_cmd* command for removing remote directories - default: "ssh USEPORT HOSTNAME rmdir" - - *g:netrw_rmf_cmd* command for removing remote softlinks - default: "ssh USEPORT HOSTNAME rm -f" - - *g:netrw_servername* use this variable to provide a name for - |netrw-ctrl-r| to use for its server. - default: "NETRWSERVER" - - *g:netrw_sort_by* sort by "name", "time", "size", or - "exten". - default: "name" - - *g:netrw_sort_direction* sorting direction: "normal" or "reverse" - default: "normal" - - *g:netrw_sort_options* sorting is done using |:sort|; this - variable's value is appended to the - sort command. Thus one may ignore case, - for example, with the following in your - .vimrc: > - let g:netrw_sort_options="i" -< default: "" - - *g:netrw_sort_sequence* when sorting by name, first sort by the - comma-separated pattern sequence. Note that - any filigree added to indicate filetypes - should be accounted for in your pattern. - default: '[\/]$,*,\.bak$,\.o$,\.h$, - \.info$,\.swp$,\.obj$' - - *g:netrw_special_syntax* If true, then certain files will be shown - using special syntax in the browser: - - netrwBak : *.bak - netrwCompress: *.gz *.bz2 *.Z *.zip - netrwCoreDump: core.\d\+ - netrwData : *.dat - netrwDoc : *.doc,*.txt,*.pdf, - *.pdf,*.docx - netrwHdr : *.h - netrwLex : *.l *.lex - netrwLib : *.a *.so *.lib *.dll - netrwMakefile: [mM]akefile *.mak - netrwObj : *.o *.obj - netrwPix : *.bmp,*.fit,*.fits,*.gif, - *.jpg,*.jpeg,*.pcx,*.ppc - *.pgm,*.png,*.psd,*.rgb - *.tif,*.xbm,*.xcf - netrwTags : tags ANmenu ANtags - netrwTilde : * - netrwTmp : tmp* *tmp - netrwYacc : *.y - - In addition, those groups mentioned in - |'suffixes'| are also added to the special - file highlighting group. - These syntax highlighting groups are linked - to netrwGray or Folded by default - (see |hl-Folded|), but one may put lines like > - hi link netrwCompress Visual -< into one's <.vimrc> to use one's own - preferences. Alternatively, one may - put such specifications into > - .vim/after/syntax/netrw.vim. -< The netrwGray highlighting is set up by - netrw when > - * netrwGray has not been previously - defined - * the gui is running -< As an example, I myself use a dark-background - colorscheme with the following in - .vim/after/syntax/netrw.vim: > - - hi netrwCompress term=NONE cterm=NONE gui=NONE ctermfg=10 guifg=green ctermbg=0 guibg=black - hi netrwData term=NONE cterm=NONE gui=NONE ctermfg=9 guifg=blue ctermbg=0 guibg=black - hi netrwHdr term=NONE cterm=NONE,italic gui=NONE guifg=SeaGreen1 - hi netrwLex term=NONE cterm=NONE,italic gui=NONE guifg=SeaGreen1 - hi netrwYacc term=NONE cterm=NONE,italic gui=NONE guifg=SeaGreen1 - hi netrwLib term=NONE cterm=NONE gui=NONE ctermfg=14 guifg=yellow - hi netrwObj term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red - hi netrwTilde term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red - hi netrwTmp term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red - hi netrwTags term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red - hi netrwDoc term=NONE cterm=NONE gui=NONE ctermfg=220 ctermbg=27 guifg=yellow2 guibg=Blue3 - hi netrwSymLink term=NONE cterm=NONE gui=NONE ctermfg=220 ctermbg=27 guifg=grey60 -< - *g:netrw_ssh_browse_reject* ssh can sometimes produce unwanted lines, - messages, banners, and whatnot that one doesn't - want masquerading as "directories" and "files". - Use this pattern to remove such embedded - messages. By default its value is: - '^total\s\+\d\+$' - - *g:netrw_ssh_cmd* One may specify an executable command - to use instead of ssh for remote actions - such as listing, file removal, etc. - default: ssh - - *g:netrw_suppress_gx_mesg* =1 : browsers sometimes produce messages - which are normally unwanted intermixed - with the page. - However, when using links, for example, - those messages are what the browser produces. - By setting this option to 0, netrw will not - suppress browser messages. - - *g:netrw_tmpfile_escape* =' &;' - escape() is applied to all temporary files - to escape these characters. - - *g:netrw_timefmt* specify format string to vim's strftime(). - The default, "%c", is "the preferred date - and time representation for the current - locale" according to my manpage entry for - strftime(); however, not all are satisfied - with it. Some alternatives: - "%a %d %b %Y %T", - " %a %Y-%m-%d %I-%M-%S %p" - default: "%c" - - *g:netrw_use_noswf* netrw normally avoids writing swapfiles - for browser buffers. However, under some - systems this apparently is causing nasty - ml_get errors to appear; if you're getting - ml_get errors, try putting - let g:netrw_use_noswf= 0 - in your .vimrc. - default: 1 - - *g:netrw_winsize* specify initial size of new windows made with - "o" (see |netrw-o|), "v" (see |netrw-v|), - |:Hexplore| or |:Vexplore|. The g:netrw_winsize - is an integer describing the percentage of the - current netrw buffer's window to be used for - the new window. - If g:netrw_winsize is less than zero, then - the absolute value of g:netrw_winsize will be - used to specify the quantity of lines or - columns for the new window. - If g:netrw_winsize is zero, then a normal - split will be made (ie. |'equalalways'| will - take effect, for example). - default: 50 (for 50%) - - *g:netrw_wiw* =1 specifies the minimum window width to use - when shrinking a netrw/Lexplore window - (see |netrw-c-tab|). - - *g:netrw_xstrlen* Controls how netrw computes string lengths, - including multi-byte characters' string - length. (thanks to N Weibull, T Mechelynck) - =0: uses Vim's built-in strlen() - =1: number of codepoints (Latin a + combining - circumflex is two codepoints) (DEFAULT) - =2: number of spacing codepoints (Latin a + - combining circumflex is one spacing - codepoint; a hard tab is one; wide and - narrow CJK are one each; etc.) - =3: virtual length (counting tabs as anything - between 1 and |'tabstop'|, wide CJK as 2 - rather than 1, Arabic alif as zero when - immediately preceded by lam, one - otherwise, etc) - - *g:NetrwTopLvlMenu* This variable specifies the top level - menu name; by default, it's "Netrw.". If - you wish to change this, do so in your - .vimrc. - -NETRW BROWSING AND OPTION INCOMPATIBILITIES *netrw-incompatible* {{{2 - -Netrw has been designed to handle user options by saving them, setting the -options to something that's compatible with netrw's needs, and then restoring -them. However, the autochdir option: > - :set acd -is problematic. Autochdir sets the current directory to that containing the -file you edit; this apparently also applies to directories. In other words, -autochdir sets the current directory to that containing the "file" (even if -that "file" is itself a directory). - -NETRW SETTINGS WINDOW *netrw-settings-window* {{{2 - -With the NetrwSettings.vim plugin, > - :NetrwSettings -will bring up a window with the many variables that netrw uses for its -settings. You may change any of their values; when you save the file, the -settings therein will be used. One may also press "?" on any of the lines for -help on what each of the variables do. - -(also see: |netrw-browser-var| |netrw-protocol| |netrw-variables|) - - -============================================================================== -OBTAINING A FILE *netrw-obtain* *netrw-O* {{{2 - -If there are no marked files: - - When browsing a remote directory, one may obtain a file under the cursor - (ie. get a copy on your local machine, but not edit it) by pressing the O - key. - -If there are marked files: - - The marked files will be obtained (ie. a copy will be transferred to your - local machine, but not set up for editing). - -Only ftp and scp are supported for this operation (but since these two are -available for browsing, that shouldn't be a problem). The status bar will -then show, on its right hand side, a message like "Obtaining filename". The -statusline will be restored after the transfer is complete. - -Netrw can also "obtain" a file using the local browser. Netrw's display -of a directory is not necessarily the same as Vim's "current directory", -unless |g:netrw_keepdir| is set to 0 in the user's <.vimrc>. One may select -a file using the local browser (by putting the cursor on it) and pressing -"O" will then "obtain" the file; ie. copy it to Vim's current directory. - -Related topics: - * To see what the current directory is, use |:pwd| - * To make the currently browsed directory the current directory, see - |netrw-cd| - * To automatically make the currently browsed directory the current - directory, see |g:netrw_keepdir|. - - *netrw-newfile* *netrw-createfile* -OPEN A NEW FILE IN NETRW'S CURRENT DIRECTORY *netrw-%* {{{2 - -To open a new file in netrw's current directory, press "%". This map -will query the user for a new filename; an empty file by that name will -be placed in the netrw's current directory (ie. b:netrw_curdir). - -If Lexplore (|netrw-:Lexplore|) is in use, the new file will be generated -in the |g:netrw_chgwin| window. - -Related topics: |netrw-d| - - -PREVIEW WINDOW *netrw-p* *netrw-preview* {{{2 - -One may use a preview window by using the "p" key when the cursor is atop the -desired filename to be previewed. The display will then split to show both -the browser (where the cursor will remain) and the file (see |:pedit|). By -default, the split will be taken horizontally; one may use vertical splitting -if one has set |g:netrw_preview| first. - -An interesting set of netrw settings is: > - - let g:netrw_preview = 1 - let g:netrw_liststyle = 3 - let g:netrw_winsize = 30 - -These will: - - 1. Make vertical splitting the default for previewing files - 2. Make the default listing style "tree" - 3. When a vertical preview window is opened, the directory listing - will use only 30% of the columns available; the rest of the window - is used for the preview window. - - Related: if you like this idea, you may also find :Lexplore - (|netrw-:Lexplore|) or |g:netrw_chgwin| of interest - -Also see: |g:netrw_chgwin| |netrw-P| |'previewwindow'| |CTRL-W_z| |:pclose| - - -PREVIOUS WINDOW *netrw-P* *netrw-prvwin* {{{2 - -To edit a file or directory under the cursor in the previously used (last -accessed) window (see :he |CTRL-W_p|), press a "P". If there's only one -window, then the one window will be horizontally split (by default). - -If there's more than one window, the previous window will be re-used on -the selected file/directory. If the previous window's associated buffer -has been modified, and there's only one window with that buffer, then -the user will be asked if they wish to save the buffer first (yes, no, or -cancel). - -Related Actions |netrw-cr| |netrw-o| |netrw-t| |netrw-v| -Associated setting variables: - |g:netrw_alto| control above/below splitting - |g:netrw_altv| control right/left splitting - |g:netrw_preview| control horizontal vs vertical splitting - |g:netrw_winsize| control initial sizing - -Also see: |g:netrw_chgwin| |netrw-p| - - -REFRESHING THE LISTING *netrw-refresh* *netrw-ctrl-l* *netrw-ctrl_l* {{{2 - -To refresh either a local or remote directory listing, press ctrl-l () or -hit the when atop the ./ directory entry in the listing. One may also -refresh a local directory by using ":e .". - - -REVERSING SORTING ORDER *netrw-r* *netrw-reverse* {{{2 - -One may toggle between normal and reverse sorting order by pressing the -"r" key. - -Related topics: |netrw-s| -Associated setting variable: |g:netrw_sort_direction| - - -RENAMING FILES OR DIRECTORIES *netrw-move* *netrw-rename* *netrw-R* {{{2 - -If there are no marked files: (see |netrw-mf|) - - Renaming files and directories involves moving the cursor to the - file/directory to be moved (renamed) and pressing "R". You will then be - queried for what you want the file/directory to be renamed to. You may - select a range of lines with the "V" command (visual selection), and then - press "R"; you will be queried for each file as to what you want it - renamed to. - -If there are marked files: (see |netrw-mf|) - - Marked files will be renamed (moved). You will be queried as above in - order to specify where you want the file/directory to be moved. - - If you answer a renaming query with a "s/frompattern/topattern/", then - subsequent files on the marked file list will be renamed by taking each - name, applying that substitute, and renaming each file to the result. - As an example : > - - mr [query: reply with *.c] - R [query: reply with s/^\(.*\)\.c$/\1.cpp/] -< - This example will mark all "*.c" files and then rename them to "*.cpp" - files. Netrw will protect you from overwriting local files without - confirmation, but not remote ones. - - The ctrl-X character has special meaning for renaming files: > - - : a single ctrl-x tells netrw to ignore the portion of the response - lying between the last '/' and the ctrl-x. - - : a pair of contiguous ctrl-x's tells netrw to ignore any - portion of the string preceding the double ctrl-x's. -< - WARNING: ~ - - Note that moving files is a dangerous operation; copies are safer. That's - because a "move" for remote files is actually a copy + delete -- and if - the copy fails and the delete succeeds you may lose the file. - Use at your own risk. - -The *g:netrw_rename_cmd* variable is used to implement remote renaming. By -default its value is: > - - ssh HOSTNAME mv -< -One may rename a block of files and directories by selecting them with -V (|linewise-visual|) when using thin style. - -See |cmdline-editing| for more on how to edit the command line; in particular, -you'll find (initiates cmdline window editing) and (uses the -command line under the cursor) useful in conjunction with the R command. - - -SELECTING SORTING STYLE *netrw-s* *netrw-sort* {{{2 - -One may select the sorting style by name, time, or (file) size. The "s" map -allows one to circulate amongst the three choices; the directory listing will -automatically be refreshed to reflect the selected style. - -Related topics: |netrw-r| |netrw-S| -Associated setting variables: |g:netrw_sort_by| |g:netrw_sort_sequence| - - -SETTING EDITING WINDOW *netrw-editwindow* *netrw-C* *netrw-:NetrwC* {{{2 - -One may select a netrw window for editing with the "C" mapping, using the -:NetrwC [win#] command, or by setting |g:netrw_chgwin| to the selected window -number. Subsequent selection of a file to edit (|netrw-cr|) will use that -window. - - * C : by itself, will select the current window holding a netrw buffer - for subsequent editing via |netrw-cr|. The C mapping is only available - while in netrw buffers. - - * [count]C : the count will be used as the window number to be used - for subsequent editing via |netrw-cr|. - - * :NetrwC will set |g:netrw_chgwin| to the current window - - * :NetrwC win# will set |g:netrw_chgwin| to the specified window - number - -Using > - let g:netrw_chgwin= -1 -will restore the default editing behavior -(ie. subsequent editing will use the current window). - -Related topics: |netrw-cr| |g:netrw_browse_split| -Associated setting variables: |g:netrw_chgwin| - - -SHRINKING OR EXPANDING A NETRW OR LEXPLORE WINDOW *netrw-c-tab* {{{2 - -The key will toggle a netrw or |:Lexplore| window's width, -but only if |g:netrw_usetab| exists and is non-zero (and, of course, -only if your terminal supports differentiating from a plain -). - - * If the current window is a netrw window, toggle its width - (between |g:netrw_wiw| and its original width) - - * Else if there is a |:Lexplore| window in the current tab, toggle - its width - - * Else bring up a |:Lexplore| window - -If |g:netrw_usetab| exists and is zero, or if there is a pre-existing mapping -for , then the will not be mapped. One may map something other -than a , too: (but you'll still need to have had |g:netrw_usetab| set). > - - nmap (whatever) NetrwShrink -< -Related topics: |:Lexplore| -Associated setting variable: |g:netrw_usetab| - - -USER SPECIFIED MAPS *netrw-usermaps* {{{1 - -One may make customized user maps. Specify a variable, |g:Netrw_UserMaps|, -to hold a |List| of lists of keymap strings and function names: > - - [["keymap-sequence","ExampleUserMapFunc"],...] -< -When netrw is setting up maps for a netrw buffer, if |g:Netrw_UserMaps| -exists, then the internal function netrw#UserMaps(islocal) is called. -This function goes through all the entries in the |g:Netrw_UserMaps| list: - - * sets up maps: > - nno KEYMAP-SEQUENCE - :call s:UserMaps(islocal,"ExampleUserMapFunc") -< * refreshes if result from that function call is the string - "refresh" - * if the result string is not "", then that string will be - executed (:exe result) - * if the result is a List, then the above two actions on results - will be taken for every string in the result List - -The user function is passed one argument; it resembles > - - fun! ExampleUserMapFunc(islocal) -< -where a:islocal is 1 if its a local-directory system call or 0 when -remote-directory system call. - - *netrw-call* *netrw-expose* *netrw-modify* -Use netrw#Expose("varname") to access netrw-internal (script-local) - variables. -Use netrw#Modify("varname",newvalue) to change netrw-internal variables. -Use netrw#Call("funcname"[,args]) to call a netrw-internal function with - specified arguments. - -Example: Get a copy of netrw's marked file list: > - - let netrwmarkfilelist= netrw#Expose("netrwmarkfilelist") -< -Example: Modify the value of netrw's marked file list: > - - call netrw#Modify("netrwmarkfilelist",[]) -< -Example: Clear netrw's marked file list via a mapping on gu > - " ExampleUserMap: {{{2 - fun! ExampleUserMap(islocal) - call netrw#Modify("netrwmarkfilelist",[]) - call netrw#Modify('netrwmarkfilemtch_{bufnr("%")}',"") - let retval= ["refresh"] - return retval - endfun - let g:Netrw_UserMaps= [["gu","ExampleUserMap"]] -< - -10. Problems and Fixes *netrw-problems* {{{1 - - (This section is likely to grow as I get feedback) - (also see |netrw-debug|) - *netrw-p1* - P1. I use Windows, and my network browsing with ftp doesn't sort by {{{2 - time or size! -or- The remote system is a Windows server; why - don't I get sorts by time or size? - - Windows' ftp has a minimal support for ls (ie. it doesn't - accept sorting options). It doesn't support the -F which - gives an explanatory character (ABC/ for "ABC is a directory"). - Netrw then uses "dir" to get both its thin and long listings. - If you think your ftp does support a full-up ls, put the - following into your <.vimrc>: > - - let g:netrw_ftp_list_cmd = "ls -lF" - let g:netrw_ftp_timelist_cmd= "ls -tlF" - let g:netrw_ftp_sizelist_cmd= "ls -slF" -< - Alternatively, if you have cygwin on your Windows box, put - into your <.vimrc>: > - - let g:netrw_cygwin= 1 -< - This problem also occurs when the remote system is Windows. - In this situation, the various g:netrw_ftp_[time|size]list_cmds - are as shown above, but the remote system will not correctly - modify its listing behavior. - - - *netrw-p2* - P2. I tried rcp://user@host/ (or protocol other than ftp) and netrw {{{2 - used ssh! That wasn't what I asked for... - - Netrw has two methods for browsing remote directories: ssh - and ftp. Unless you specify ftp specifically, ssh is used. - When it comes time to do download a file (not just a directory - listing), netrw will use the given protocol to do so. - - *netrw-p3* - P3. I would like long listings to be the default. {{{2 - - Put the following statement into your |vimrc|: > - - let g:netrw_liststyle= 1 -< - Check out |netrw-browser-var| for more customizations that - you can set. - - *netrw-p4* - P4. My times come up oddly in local browsing {{{2 - - Does your system's strftime() accept the "%c" to yield dates - such as "Sun Apr 27 11:49:23 1997"? If not, do a - "man strftime" and find out what option should be used. Then - put it into your |vimrc|: > - - let g:netrw_timefmt= "%X" (where X is the option) -< - *netrw-p5* - P5. I want my current directory to track my browsing. {{{2 - How do I do that? - - Put the following line in your |vimrc|: -> - let g:netrw_keepdir= 0 -< - *netrw-p6* - P6. I use Chinese (or other non-ascii) characters in my filenames, {{{2 - and netrw (Explore, Sexplore, Hexplore, etc) doesn't display them! - - (taken from an answer provided by Wu Yongwei on the vim - mailing list) - I now see the problem. Your code page is not 936, right? Vim - seems only able to open files with names that are valid in the - current code page, as are many other applications that do not - use the Unicode version of Windows APIs. This is an OS-related - issue. You should not have such problems when the system - locale uses UTF-8, such as modern Linux distros. - - (...it is one more reason to recommend that people use utf-8!) - - *netrw-p7* - P7. I'm getting "ssh is not executable on your system" -- what do I {{{2 - do? - - (Dudley Fox) Most people I know use putty for windows ssh. It - is a free ssh/telnet application. You can read more about it - here: - - http://www.chiark.greenend.org.uk/~sgtatham/putty/ Also: - - (Marlin Unruh) This program also works for me. It's a single - executable, so he/she can copy it into the Windows\System32 - folder and create a shortcut to it. - - (Dudley Fox) You might also wish to consider plink, as it - sounds most similar to what you are looking for. plink is an - application in the putty suite. - - http://the.earth.li/~sgtatham/putty/0.58/htmldoc/Chapter7.html#plink - - (Vissale Neang) Maybe you can try OpenSSH for windows, which - can be obtained from: - - http://sshwindows.sourceforge.net/ - - It doesn't need the full Cygwin package. - - (Antoine Mechelynck) For individual Unix-like programs needed - for work in a native-Windows environment, I recommend getting - them from the GnuWin32 project on sourceforge if it has them: - - http://gnuwin32.sourceforge.net/ - - Unlike Cygwin, which sets up a Unix-like virtual machine on - top of Windows, GnuWin32 is a rewrite of Unix utilities with - Windows system calls, and its programs works quite well in the - cmd.exe "Dos box". - - (dave) Download WinSCP and use that to connect to the server. - In Preferences > Editors, set gvim as your editor: - - - Click "Add..." - - Set External Editor (adjust path as needed, include - the quotes and !.! at the end): - "c:\Program Files\Vim\vim82\gvim.exe" !.! - - Check that the filetype in the box below is - {asterisk}.{asterisk} (all files), or whatever types - you want (cec: change {asterisk} to * ; I had to - write it that way because otherwise the helptags - system thinks it's a tag) - - Make sure it's at the top of the listbox (click it, - then click "Up" if it's not) - If using the Norton Commander style, you just have to hit - to edit a file in a local copy of gvim. - - (Vit Gottwald) How to generate public/private key and save - public key it on server: > - http://www.chiark.greenend.org.uk/~sgtatham/putty/0.60/htmldoc/Chapter8.html#pubkey-gettingready - (8.3 Getting ready for public key authentication) -< - How to use a private key with "pscp": > - - http://www.chiark.greenend.org.uk/~sgtatham/putty/0.60/htmldoc/Chapter5.html - (5.2.4 Using public key authentication with PSCP) -< - (Ben Schmidt) I find the ssh included with cwRsync is - brilliant, and install cwRsync or cwRsyncServer on most - Windows systems I come across these days. I guess COPSSH, - packed by the same person, is probably even better for use as - just ssh on Windows, and probably includes sftp, etc. which I - suspect the cwRsync doesn't, though it might - - (cec) To make proper use of these suggestions above, you will - need to modify the following user-settable variables in your - .vimrc: - - |g:netrw_ssh_cmd| |g:netrw_list_cmd| |g:netrw_mkdir_cmd| - |g:netrw_rm_cmd| |g:netrw_rmdir_cmd| |g:netrw_rmf_cmd| - - The first one (|g:netrw_ssh_cmd|) is the most important; most - of the others will use the string in g:netrw_ssh_cmd by - default. - - *netrw-p8* *netrw-ml_get* - P8. I'm browsing, changing directory, and bang! ml_get errors {{{2 - appear and I have to kill vim. Any way around this? - - Normally netrw attempts to avoid writing swapfiles for - its temporary directory buffers. However, on some systems - this attempt appears to be causing ml_get errors to - appear. Please try setting |g:netrw_use_noswf| to 0 - in your <.vimrc>: > - let g:netrw_use_noswf= 0 -< - *netrw-p9* - P9. I'm being pestered with "[something] is a directory" and {{{2 - "Press ENTER or type command to continue" prompts... - - The "[something] is a directory" prompt is issued by Vim, - not by netrw, and there appears to be no way to work around - it. Coupled with the default cmdheight of 1, this message - causes the "Press ENTER..." prompt. So: read |hit-enter|; - I also suggest that you set your |'cmdheight'| to 2 (or more) in - your <.vimrc> file. - - *netrw-p10* - P10. I want to have two windows; a thin one on the left and my {{{2 - editing window on the right. How may I accomplish this? - - You probably want netrw running as in a side window. If so, you - will likely find that ":[N]Lexplore" does what you want. The - optional "[N]" allows you to select the quantity of columns you - wish the |:Lexplore|r window to start with (see |g:netrw_winsize| - for how this parameter works). - - Previous solution: - - * Put the following line in your <.vimrc>: - let g:netrw_altv = 1 - * Edit the current directory: :e . - * Select some file, press v - * Resize the windows as you wish (see |CTRL-W_<| and - |CTRL-W_>|). If you're using gvim, you can drag - the separating bar with your mouse. - * When you want a new file, use ctrl-w h to go back to the - netrw browser, select a file, then press P (see |CTRL-W_h| - and |netrw-P|). If you're using gvim, you can press - in the browser window and then press the - to select the file. - - - *netrw-p11* - P11. My directory isn't sorting correctly, or unwanted letters are {{{2 - appearing in the listed filenames, or things aren't lining - up properly in the wide listing, ... - - This may be due to an encoding problem. I myself usually use - utf-8, but really only use ascii (ie. bytes from 32-126). - Multibyte encodings use two (or more) bytes per character. - You may need to change |g:netrw_sepchr| and/or |g:netrw_xstrlen|. - - *netrw-p12* - P12. I'm a Windows + putty + ssh user, and when I attempt to {{{2 - browse, the directories are missing trailing "/"s so netrw treats - them as file transfers instead of as attempts to browse - subdirectories. How may I fix this? - - (mikeyao) If you want to use vim via ssh and putty under Windows, - try combining the use of pscp/psftp with plink. pscp/psftp will - be used to connect and plink will be used to execute commands on - the server, for example: list files and directory using 'ls'. - - These are the settings I use to do this: -> - " list files, it's the key setting, if you haven't set, - " you will get a blank buffer - let g:netrw_list_cmd = "plink HOSTNAME ls -Fa" - " if you haven't add putty directory in system path, you should - " specify scp/sftp command. For examples: - "let g:netrw_sftp_cmd = "d:\\dev\\putty\\PSFTP.exe" - "let g:netrw_scp_cmd = "d:\\dev\\putty\\PSCP.exe" -< - *netrw-p13* - P13. I would like to speed up writes using Nwrite and scp/ssh {{{2 - style connections. How? (Thomer M. Gil) - - Try using ssh's ControlMaster and ControlPath (see the ssh_config - man page) to share multiple ssh connections over a single network - connection. That cuts out the cryptographic handshake on each - file write, sometimes speeding it up by an order of magnitude. - (see http://thomer.com/howtos/netrw_ssh.html) - (included by permission) - - Add the following to your ~/.ssh/config: > - - # you change "*" to the hostname you care about - Host * - ControlMaster auto - ControlPath /tmp/%r@%h:%p - -< Then create an ssh connection to the host and leave it running: > - - ssh -N host.domain.com - -< Now remotely open a file with Vim's Netrw and enjoy the - zippiness: > - - vim scp://host.domain.com//home/user/.bashrc -< - *netrw-p14* - P14. How may I use a double-click instead of netrw's usual single {{{2 - click to open a file or directory? (Ben Fritz) - - First, disable netrw's mapping with > - let g:netrw_mousemaps= 0 -< and then create a netrw buffer only mapping in - $HOME/.vim/after/ftplugin/netrw.vim: > - nmap <2-leftmouse> -< Note that setting g:netrw_mousemaps to zero will turn off - all netrw's mouse mappings, not just the one. - (see |g:netrw_mousemaps|) - - *netrw-p15* - P15. When editing remote files (ex. :e ftp://hostname/path/file), {{{2 - under Windows I get an |E303| message complaining that its unable - to open a swap file. - - (romainl) It looks like you are starting Vim from a protected - directory. Start netrw from your $HOME or other writable - directory. - - *netrw-p16* - P16. Netrw is closing buffers on its own. {{{2 - What steps will reproduce the problem? - 1. :Explore, navigate directories, open a file - 2. :Explore, open another file - 3. Buffer opened in step 1 will be closed. o - What is the expected output? What do you see instead? - I expect both buffers to exist, but only the last one does. - - (Lance) Problem is caused by "set autochdir" in .vimrc. - (drchip) I am able to duplicate this problem with |'acd'| set. - It appears that the buffers are not exactly closed; - a ":ls!" will show them (although ":ls" does not). - - *netrw-P17* - P17. How to locally edit a file that's only available via {{{2 - another server accessible via ssh? - See http://stackoverflow.com/questions/12469645/ - "Using Vim to Remotely Edit A File on ServerB Only - Accessible From ServerA" - - *netrw-P18* - P18. How do I get numbering on in directory listings? {{{2 - With |g:netrw_bufsettings|, you can control netrw's buffer - settings; try putting > - let g:netrw_bufsettings="noma nomod nu nobl nowrap ro nornu" -< in your .vimrc. If you'd like to have relative numbering - instead, try > - let g:netrw_bufsettings="noma nomod nonu nobl nowrap ro rnu" -< - *netrw-P19* - P19. How may I have gvim start up showing a directory listing? {{{2 - Try putting the following code snippet into your .vimrc: > - augroup VimStartup - au! - au VimEnter * if expand("%") == "" && argc() == 0 && - \ (v:servername =~ 'GVIM\d*' || v:servername == "") - \ | e . | endif - augroup END -< You may use Lexplore instead of "e" if you're so inclined. - This snippet assumes that you have client-server enabled - (ie. a "huge" vim version). - - *netrw-P20* - P20. I've made a directory (or file) with an accented character, {{{2 - but netrw isn't letting me enter that directory/read that file: - - Its likely that the shell or o/s is using a different encoding - than you have vim (netrw) using. A patch to vim supporting - "systemencoding" may address this issue in the future; for - now, just have netrw use the proper encoding. For example: > - - au FileType netrw set enc=latin1 -< - *netrw-P21* - P21. I get an error message when I try to copy or move a file: {{{2 -> - **error** (netrw) tried using g:netrw_localcopycmd; it doesn't work! -< - What's wrong? - - Netrw uses several system level commands to do things (see - - |g:netrw_localcopycmd|, |g:netrw_localmovecmd|, - |g:netrw_mkdir_cmd|). - - You may need to adjust the default commands for one or more of - these commands by setting them properly in your .vimrc. Another - source of difficulty is that these commands use vim's local - directory, which may not be the same as the browsing directory - shown by netrw (see |g:netrw_keepdir|). - - -============================================================================== -11. Debugging Netrw Itself *netrw-debug* {{{1 - -Step 1: check that the problem you've encountered hasn't already been resolved -by obtaining a copy of the latest (often developmental) netrw at: - - http://www.drchip.org/astronaut/vim/index.html#NETRW - -The script is typically installed on systems as something like: -> - /usr/local/share/vim/vim8x/plugin/netrwPlugin.vim - /usr/local/share/vim/vim8x/autoload/netrw.vim - (see output of :echo &rtp) -< -which is loaded automatically at startup (assuming :set nocp). If you -installed a new netrw, then it will be located at > - - $HOME/.vim/plugin/netrwPlugin.vim - $HOME/.vim/autoload/netrw.vim -< -Step 2: assuming that you've installed the latest version of netrw, -check that your problem is really due to netrw. Create a file -called netrw.vimrc with the following contents: > - - set nocp - so $HOME/.vim/plugin/netrwPlugin.vim -< -Then run netrw as follows: > - - vim -u netrw.vimrc --noplugins -i NONE [some path here] -< -Perform whatever netrw commands you need to, and check that the problem is -still present. This procedure sidesteps any issues due to personal .vimrc -settings, .viminfo file, and other plugins. If the problem does not appear, -then you need to determine which setting in your .vimrc is causing the -conflict with netrw or which plugin(s) is/are involved. - -Step 3: If the problem still is present, then get a debugging trace from -netrw: - - 1. Get the script, available as: - - http://www.drchip.org/astronaut/vim/index.html#DECHO - or - http://vim.sourceforge.net/scripts/script.php?script_id=120 - - Decho.vim is provided as a "vimball". You - should edit the Decho.vba.gz file and source it in: > - - vim Decho.vba.gz - :so % - :q -< - 2. To turn on debug tracing in netrw, then edit the - file by typing: > - - vim netrw.vim - :DechoOn - :wq -< - To restore to normal non-debugging behavior, re-edit - and type > - - vim netrw.vim - :DechoOff - :wq -< - This command, provided by , will comment out all - Decho-debugging statements (Dfunc(), Dret(), Decho(), Dredir()). - - 3. Then bring up vim and attempt to evoke the problem by doing a - transfer or doing some browsing. A set of messages should appear - concerning the steps that took in attempting to - read/write your file over the network in a separate tab or - server vim window. - - Change the netrw.vimrc file to include the Decho plugin: > - - set nocp - so $HOME/.vim/plugin/Decho.vim - so $HOME/.vim/plugin/netrwPlugin.vim -< - You should continue to run vim with > - - vim -u netrw.vimrc --noplugins -i NONE [some path here] -< - to avoid entanglements with options and other plugins. - - To save the file: under linux, the output will be in a separate - remote server window; in it, just save the file with > - - :w! DBG - -< Under a vim that doesn't support clientserver, your debugging - output will appear in another tab: > - - :tabnext - :set bt= - :w! DBG -< - Furthermore, it'd be helpful if you would type > - - :Dsep - -< where is the command you're about to type next, - thereby making it easier to associate which part of the - debugging trace is due to which command. - - Please send that information to 's maintainer along - with the o/s you're using and the vim version that you're using - (see |:version|) (remove the embedded NOSPAM first) > - - NcampObell@SdrPchip.AorgM-NOSPAM -< -============================================================================== -12. History *netrw-history* {{{1 - - v172: Sep 02, 2021 * (Bram Moolenaar) Changed "l:go" to "go" - * (Bram Moolenaar) no need for "b" in - netrw-safe guioptions - Nov 15, 2021 * removed netrw_localrm and netrw_localrmdir - references - Aug 18, 2022 * (Miguel Barro) improving compatibility with - powershell - v171: Oct 09, 2020 * included code in s:NetrwOptionsSafe() - to allow |'bh'| to be set to delete when - rather than hide when g:netrw_fastbrowse - was zero. - * Installed |g:netrw_clipboard| setting - * Installed option bypass for |'guioptions'| - a/A settings - * Changed popup_beval() to popup_atcursor() - in netrw#ErrorMsg (lacygoill). Apparently - popup_beval doesn't reliably close the - popup when the mouse is moved. - * VimEnter() now using win_execute to examine - buffers for an attempt to open a directory. - Avoids issues with popups/terminal from - command line. (lacygoill) - Jun 28, 2021 * (zeertzjq) provided a patch for use of - xmap,xno instead of vmap,vno in - netrwPlugin.vim. Avoids entanglement with - select mode. - Jul 14, 2021 * Fixed problem addressed by tst976; opening - a file using tree mode, going up a - directory, and opening a file there was - opening the file in the wrong directory. - Jul 28, 2021 * (Ingo Karkat) provided a patch fixing an - E488 error with netrwPlugin.vim - (occurred for vim versions < 8.02) - v170: Mar 11, 2020 * (reported by Reiner Herrmann) netrw+tree - would not hide with the ^\..* pattern - correctly. - * (Marcin Szamotulski) NetrwOptionRestore - did not restore options correctly that - had a single quote in the option string. - Apr 13, 2020 * implemented error handling via popup - windows (see popup_beval()) - Apr 30, 2020 * (reported by Manatsu Takahashi) while - using Lexplore, a modified file could - be overwritten. Sol'n: will not overwrite, - but will emit an |E37| (although one cannot - add an ! to override) - Jun 07, 2020 * (reported by Jo Totland) repeatedly invoking - :Lexplore and quitting it left unused - hidden buffers. Netrw will now set netrw - buffers created by :Lexplore to |'bh'|=wipe. - v169: Dec 20, 2019 * (reported by amkarthik) that netrw's x - (|netrw-x|) would throw an error when - attempting to open a local directory. - v168: Dec 12, 2019 * scp timeout error message not reported, - hopefully now fixed (Shane Xb Qian) - v167: Nov 29, 2019 * netrw does a save&restore on @* and @+. - That causes problems with the clipboard. - Now restores occurs only if @* or @+ have - been changed. - * netrw will change @* or @+ less often. - Never if I happen to have caught all the - operations that modify the unnamed - register (which also writes @*). - * Modified hiding behavior so that "s" - will not ignore hiding. - v166: Nov 06, 2019 * Removed a space from a nmap for "-" - * Numerous debugging statement changes - v163: Dec 05, 2017 * (Cristi Balan) reported that a setting ('sel') - was left changed - * (Holger Mitschke) reported a problem with - saving and restoring history. Fixed. - * Hopefully I fixed a nasty bug that caused a - file rename to wipe out a buffer that it - should not have wiped out. - * (Holger Mitschke) amended this help file - with additional |g:netrw_special_syntax| - items - * Prioritized wget over curl for - g:netrw_http_cmd - v162: Sep 19, 2016 * (haya14busa) pointed out two syntax errors - with a patch; these are now fixed. - Oct 26, 2016 * I started using mate-terminal and found that - x and gx (|netrw-x| and |netrw-gx|) were no - longer working. Fixed (using atril when - $DESKTOP_SESSION is "mate"). - Nov 04, 2016 * (Martin Vuille) pointed out that @+ was - being restored with keepregstar rather than - keepregplus. - Nov 09, 2016 * Broke apart the command from the options, - mostly for Windows. Introduced new netrw - settings: |g:netrw_localcopycmdopt| - |g:netrw_localcopydircmdopt| - |g:netrw_localmkdiropt| - |g:netrw_localmovecmdopt| - Nov 21, 2016 * (mattn) provided a patch for preview; swapped - winwidth() with winheight() - Nov 22, 2016 * (glacambre) reported that files containing - spaces weren't being obtained properly via - scp. Fix: apparently using single quotes - such as with "file name" wasn't enough; the - spaces inside the quotes also had to be - escaped (ie. "file\ name"). - * Also fixed obtain (|netrw-O|) to be able to - obtain files with spaces in their names - Dec 20, 2016 * (xc1427) Reported that using "I" (|netrw-I|) - when atop "Hiding" in the banner also caused - the active-banner hiding control to occur - Jan 03, 2017 * (Enno Nagel) reported that attempting to - apply netrw to a directory that was without - read permission caused a syntax error. - Jan 13, 2017 * (Ingo Karkat) provided a patch which makes - using netrw#Call() better. Now returns - value of internal routines return, for example. - Jan 13, 2017 * (Ingo Karkat) changed netrw#FileUrlRead to - use |:edit| instead of |:read|. I also - changed the routine name to netrw#FileUrlEdit. - Jan 16, 2017 * (Sayem) reported a problem where :Lexplore - could generate a new listing buffer and - window instead of toggling the netrw display. - Unfortunately, the directions for eliciting - the problem weren't complete, so I may or - may not have fixed that issue. - Feb 06, 2017 * Implemented cb and cB. Changed "c" to "cd". - (see |netrw-cb|, |netrw-cB|, and |netrw-cd|) - Mar 21, 2017 * previously, netrw would specify (safe) settings - even when the setting was already safe for - netrw. Netrw now attempts to leave such - already-netrw-safe settings alone. - (affects s:NetrwOptionRestore() and - s:NetrwSafeOptions(); also introduced - s:NetrwRestoreSetting()) - Jun 26, 2017 * (Christian Brabandt) provided a patch to - allow curl to follow redirects (ie. -L - option) - Jun 26, 2017 * (Callum Howard) reported a problem with - :Lexpore not removing the Lexplore window - after a change-directory - Aug 30, 2017 * (Ingo Karkat) one cannot switch to the - previously edited file (e.g. with CTRL-^) - after editing a file:// URL. Patch to - have a "keepalt" included. - Oct 17, 2017 * (Adam Faryna) reported that gn (|netrw-gn|) - did not work on directories in the current - tree - v157: Apr 20, 2016 * (Nicola) had set up a "nmap ..." with - a function that returned a 0 while silently - invoking a shell command. The shell command - activated a ShellCmdPost event which in turn - called s:LocalBrowseRefresh(). That looks - over all netrw buffers for changes needing - refreshes. However, inside a |:map-|, - tab and window changes are disallowed. Fixed. - (affects netrw's s:LocalBrowseRefresh()) - * g:netrw_localrmdir not used any more, but - the relevant patch that causes |delete()| to - take over was #1107 (not #1109). - * |expand()| is now used on |g:netrw_home|; - consequently, g:netrw_home may now use - environment variables - * s:NetrwLeftmouse and s:NetrwCLeftmouse will - return without doing anything if invoked - when inside a non-netrw window - Jun 15, 2016 * gx now calls netrw#GX() which returns - the word under the cursor. The new - wrinkle: if one is in a netrw buffer, - then netrw's s:NetrwGetWord(). - Jun 22, 2016 * Netrw was executing all its associated - Filetype commands silently; I'm going - to try doing that "noisily" and see if - folks have a problem with that. - Aug 12, 2016 * Changed order of tool selection for - handling http://... viewing. - (Nikolay Aleksandrovich Pavlov) - Aug 21, 2016 * Included hiding/showing/all for tree - listings - * Fixed refresh (^L) for tree listings - v156: Feb 18, 2016 * Changed =~ to =~# where appropriate - Feb 23, 2016 * s:ComposePath(base,subdir) now uses - fnameescape() on the base portion - Mar 01, 2016 * (gt_macki) reported where :Explore would - make file unlisted. Fixed (tst943) - Apr 04, 2016 * (reported by John Little) netrw normally - suppresses browser messages, but sometimes - those "messages" are what is wanted. - See |g:netrw_suppress_gx_mesg| - Apr 06, 2016 * (reported by Carlos Pita) deleting a remote - file was giving an error message. Fixed. - Apr 08, 2016 * (Charles Cooper) had a problem with an - undefined b:netrw_curdir. He also provided - a fix. - Apr 20, 2016 * Changed s:NetrwGetBuffer(); now uses - dictionaries. Also fixed the "No Name" - buffer problem. - v155: Oct 29, 2015 * (Timur Fayzrakhmanov) reported that netrw's - mapping of ctrl-l was not allowing refresh of - other windows when it was done in a netrw - window. - Nov 05, 2015 * Improved s:TreeSqueezeDir() to use search() - instead of a loop - * NetrwBrowse() will return line to - w:netrw_bannercnt if cursor ended up in - banner - Nov 16, 2015 * Added a NetrwTreeSqueeze (|netrw-s-cr|) - Nov 17, 2015 * Commented out imaps -- perhaps someone can - tell me how they're useful and should be - retained? - Nov 20, 2015 * Added |netrw-ma| and |netrw-mA| support - Nov 20, 2015 * gx (|netrw-gx|) on a URL downloaded the - file in addition to simply bringing up the - URL in a browser. Fixed. - Nov 23, 2015 * Added |g:netrw_sizestyle| support - Nov 27, 2015 * Inserted a lot of s into various netrw - maps. - Jan 05, 2016 * |netrw-qL| implemented to mark files based - upon |location-list|s; similar to |netrw-qF|. - Jan 19, 2016 * using - call delete(directoryname,"d") - - instead of using g:netrw_localrmdir if - v7.4 + patch#1107 is available - Jan 28, 2016 * changed to using |winsaveview()| and - |winrestview()| - Jan 28, 2016 * s:NetrwTreePath() now does a save and - restore of view - Feb 08, 2016 * Fixed a tree-listing problem with remote - directories - v154: Feb 26, 2015 * (Yuri Kanivetsky) reported a situation where - a file was not treated properly as a file - due to g:netrw_keepdir == 1 - Mar 25, 2015 * (requested by Ben Friz) one may now sort by - extension - Mar 28, 2015 * (requested by Matt Brooks) netrw has a lot - of buffer-local mappings; however, some - plugins (such as vim-surround) set up - conflicting mappings that cause vim to wait. - The "" modifier has been included - with most of netrw's mappings to avoid that - delay. - Jun 26, 2015 * |netrw-gn| mapping implemented - * :Ntree NotADir resulted in having - the tree listing expand in the error messages - window. Fixed. - Jun 29, 2015 * Attempting to delete a file remotely caused - an error with "keepsol" mentioned; fixed. - Jul 08, 2015 * Several changes to keep the |:jumps| table - correct when working with - |g:netrw_fastbrowse| set to 2 - * wide listing with accented characters fixed - (using %-S instead of %-s with a |printf()| - Jul 13, 2015 * (Daniel Hahler) CheckIfKde() could be true - but kfmclient not installed. Changed order - in netrw#BrowseX(): checks if kde and - kfmclient, then will use xdg-open on a unix - system (if xdg-open is executable) - Aug 11, 2015 * (McDonnell) tree listing mode wouldn't - select a file in a open subdirectory. - * (McDonnell) when multiple subdirectories - were concurrently open in tree listing - mode, a ctrl-L wouldn't refresh properly. - * The netrw:target menu showed duplicate - entries - Oct 13, 2015 * (mattn) provided an exception to handle - windows with shellslash set but no shell - Oct 23, 2015 * if g:netrw_usetab and now used - to control whether NetrwShrink is used - (see |netrw-c-tab|) - v153: May 13, 2014 * added another |g:netrw_ffkeep| usage {{{2 - May 14, 2014 * changed s:PerformListing() so that it - always sets ft=netrw for netrw buffers - (ie. even when syntax highlighting is - off, not available, etc) - May 16, 2014 * introduced the |netrw-ctrl-r| functionality - May 17, 2014 * introduced the |netrw-:NetrwMB| functionality - * mb and mB (|netrw-mb|, |netrw-mB|) will - add/remove marked files from bookmark list - May 20, 2014 * (Enno Nagel) reported that :Lex - wasn't working. Fixed. - May 26, 2014 * restored test to prevent leftmouse window - resizing from causing refresh. - (see s:NetrwLeftmouse()) - * fixed problem where a refresh caused cursor - to go just under the banner instead of - staying put - May 28, 2014 * (László Bimba) provided a patch for opening - the |:Lexplore| window 100% high, optionally - on the right, and will work with remote - files. - May 29, 2014 * implemented :NetrwC (see |netrw-:NetrwC|) - Jun 01, 2014 * Removed some "silent"s from commands used - to implemented scp://... and pscp://... - directory listing. Permits request for - password to appear. - Jun 05, 2014 * (Enno Nagel) reported that user maps "/" - caused problems with "b" and "w", which - are mapped (for wide listings only) to - skip over files rather than just words. - Jun 10, 2014 * |g:netrw_gx| introduced to allow users to - override default "" with the gx - (|netrw-gx|) map - Jun 11, 2014 * gx (|netrw-gx|), with |'autowrite'| set, - will write modified files. s:NetrwBrowseX() - will now save, turn off, and restore the - |'autowrite'| setting. - Jun 13, 2014 * added visual map for gx use - Jun 15, 2014 * (Enno Nagel) reported that with having hls - set and wide listing style in use, that the - b and w maps caused unwanted highlighting. - Jul 05, 2014 * |netrw-mv| and |netrw-mX| commands included - Jul 09, 2014 * |g:netrw_keepj| included, allowing optional - keepj - Jul 09, 2014 * fixing bugs due to previous update - Jul 21, 2014 * (Bruno Sutic) provided an updated - netrw_gitignore.vim - Jul 30, 2014 * (Yavuz Yetim) reported that editing two - remote files of the same name caused the - second instance to have a "temporary" - name. Fixed: now they use the same buffer. - Sep 18, 2014 * (Yasuhiro Matsumoto) provided a patch which - allows scp and windows local paths to work. - Oct 07, 2014 * gx (see |netrw-gx|) when atop a directory, - will now do |gf| instead - Nov 06, 2014 * For cygwin: cygstart will be available for - netrw#BrowseX() to use if its executable. - Nov 07, 2014 * Began support for file://... urls. Will use - |g:netrw_file_cmd| (typically elinks or links) - Dec 02, 2014 * began work on having mc (|netrw-mc|) copy - directories. Works for linux machines, - cygwin+vim, but not for windows+gvim. - Dec 02, 2014 * in tree mode, netrw was not opening - directories via symbolic links. - Dec 02, 2014 * added resolved link information to - thin and tree modes - Dec 30, 2014 * (issue#231) |:ls| was not showing - remote-file buffers reliably. Fixed. - v152: Apr 08, 2014 * uses the |'noswapfile'| option (requires {{{2 - vim 7.4 with patch 213) - * (Enno Nagel) turn |'rnu'| off in netrw - buffers. - * (Quinn Strahl) suggested that netrw - allow regular window splitting to occur, - thereby allowing |'equalalways'| to take - effect. - * (qingtian zhao) normally, netrw will - save and restore the |'fileformat'|; - however, sometimes that isn't wanted - Apr 14, 2014 * whenever netrw marks a buffer as ro, - it will also mark it as nomod. - Apr 16, 2014 * sftp protocol now supported by - netrw#Obtain(); this means that one - may use "mc" to copy a remote file - to a local file using sftp, and that - the |netrw-O| command can obtain remote - files via sftp. - * added [count]C support (see |netrw-C|) - Apr 18, 2014 * when |g:netrw_chgwin| is one more than - the last window, then vertically split - the last window and use it as the - chgwin window. - May 09, 2014 * SavePosn was "saving filename under cursor" - from a non-netrw window when using :Rex. - v151: Jan 22, 2014 * extended :Rexplore to return to buffer {{{2 - prior to Explore or editing a directory - * (Ken Takata) netrw gave error when - clipboard was disabled. Sol'n: Placed - several if has("clipboard") tests in. - * Fixed ftp://X@Y@Z// problem; X@Y now - part of user id, and only Z is part of - hostname. - * (A Loumiotis) reported that completion - using a directory name containing spaces - did not work. Fixed with a retry in - netrw#Explore() which removes the - backslashes vim inserted. - Feb 26, 2014 * :Rexplore now records the current file - using w:netrw_rexfile when returning via - |:Rexplore| - Mar 08, 2014 * (David Kotchan) provided some patches - allowing netrw to work properly with - windows shares. - * Multiple one-liner help messages available - by pressing while atop the "Quick - Help" line - * worked on ShellCmdPost, FocusGained event - handling. - * |:Lexplore| path: will be used to update - a left-side netrw browsing directory. - Mar 12, 2014 * |netrw-s-cr|: use to close - tree directory implemented - Mar 13, 2014 * (Tony Mechylynck) reported that using - the browser with ftp on a directory, - and selecting a gzipped txt file, that - an E19 occurred (which was issued by - gzip.vim). Fixed. - Mar 14, 2014 * Implemented :MF and :MT (see |netrw-:MF| - and |netrw-:MT|, respectively) - Mar 17, 2014 * |:Ntree| [dir] wasn't working properly; fixed - Mar 18, 2014 * Changed all uses of set to setl - Mar 18, 2014 * Commented the netrw_btkeep line in - s:NetrwOptionSave(); the effect is that - netrw buffers will remain as |'bt'|=nofile. - This should prevent swapfiles being created - for netrw buffers. - Mar 20, 2014 * Changed all uses of lcd to use s:NetrwLcd() - instead. Consistent error handling results - and it also handles Window's shares - * Fixed |netrw-d| command when applied with ftp - * https: support included for netrw#NetRead() - v150: Jul 12, 2013 * removed a "keepalt" to allow ":e #" to {{{2 - return to the netrw directory listing - Jul 13, 2013 * (Jonas Diemer) suggested changing - a to . - Jul 21, 2013 * (Yuri Kanivetsky) reported that netrw's - use of mkdir did not produce directories - following the user's umask. - Aug 27, 2013 * introduced |g:netrw_altfile| option - Sep 05, 2013 * s:Strlen() now uses |strdisplaywidth()| - when available, by default - Sep 12, 2013 * (Selyano Baldo) reported that netrw wasn't - opening some directories properly from the - command line. - Nov 09, 2013 * |:Lexplore| introduced - * (Ondrej Platek) reported an issue with - netrw's trees (P15). Fixed. - * (Jorge Solis) reported that "t" in - tree mode caused netrw to forget its - line position. - Dec 05, 2013 * Added file marking - (see |netrw-mf|) - Dec 05, 2013 * (Yasuhiro Matsumoto) Explore should use - strlen() instead s:Strlen() when handling - multibyte chars with strpart() - (ie. strpart() is byte oriented, not - display-width oriented). - Dec 09, 2013 * (Ken Takata) Provided a patch; File sizes - and a portion of timestamps were wrongly - highlighted with the directory color when - setting `:let g:netrw_liststyle=1` on Windows. - * (Paul Domaskis) noted that sometimes - cursorline was activating in non-netrw - windows. All but one setting of cursorline - was done via setl; there was one that was - overlooked. Fixed. - Dec 24, 2013 * (esquifit) asked that netrw allow the - /cygdrive prefix be a user-alterable - parameter. - Jan 02, 2014 * Fixed a problem with netrw-based balloon - evaluation (ie. netrw#NetrwBalloonHelp() - not having been loaded error messages) - Jan 03, 2014 * Fixed a problem with tree listings - * New command installed: |:Ntree| - Jan 06, 2014 * (Ivan Brennan) reported a problem with - |netrw-P|. Fixed. - Jan 06, 2014 * Fixed a problem with |netrw-P| when the - modified file was to be abandoned. - Jan 15, 2014 * (Matteo Cavalleri) reported that when the - banner is suppressed and tree listing is - used, a blank line was left at the top of - the display. Fixed. - Jan 20, 2014 * (Gideon Go) reported that, in tree listing - style, with a previous window open, that - the wrong directory was being used to open - a file. Fixed. (P21) - v149: Apr 18, 2013 * in wide listing format, now have maps for {{{2 - w and b to move to next/previous file - Apr 26, 2013 * one may now copy files in the same - directory; netrw will issue requests for - what names the files should be copied under - Apr 29, 2013 * Trying Benzinger's problem again. Seems - that commenting out the BufEnter and - installing VimEnter (only) works. Weird - problem! (tree listing, vim -O Dir1 Dir2) - May 01, 2013 * :Explore ftp://... wasn't working. Fixed. - May 02, 2013 * introduced |g:netrw_bannerbackslash| as - requested by Paul Domaskis. - Jul 03, 2013 * Explore now avoids splitting when a buffer - will be hidden. - v148: Apr 16, 2013 * changed Netrw's Style menu to allow direct {{{2 - choice of listing style, hiding style, and - sorting style - -============================================================================== -13. Todo *netrw-todo* {{{1 - -07/29/09 : banner :|g:netrw_banner| can be used to suppress the - suppression banner. This feature is new and experimental, - so its in the process of being debugged. -09/04/09 : "gp" : See if it can be made to work for remote systems. - : See if it can be made to work with marked files. - -============================================================================== -14. Credits *netrw-credits* {{{1 - - Vim editor by Bram Moolenaar (Thanks, Bram!) - dav support by C Campbell - fetch support by Bram Moolenaar and C Campbell - ftp support by C Campbell - http support by Bram Moolenaar - rcp - rsync support by C Campbell (suggested by Erik Warendorph) - scp support by raf - sftp support by C Campbell - - inputsecret(), BufReadCmd, BufWriteCmd contributed by C Campbell - - Jérôme Augé -- also using new buffer method with ftp+.netrc - Bram Moolenaar -- obviously vim itself, :e and v:cmdarg use, - fetch,... - Yasuhiro Matsumoto -- pointing out undo+0r problem and a solution - Erik Warendorph -- for several suggestions (g:netrw_..._cmd - variables, rsync etc) - Doug Claar -- modifications to test for success with ftp - operation - -============================================================================== -Modelines: {{{1 -vim:tw=78:ts=8:ft=help:noet:norl:fdm=marker diff --git a/runtime/pack/dist/opt/netrw/LICENSE.txt b/runtime/pack/dist/opt/netrw/LICENSE.txt new file mode 100644 index 0000000000..702c2386ac --- /dev/null +++ b/runtime/pack/dist/opt/netrw/LICENSE.txt @@ -0,0 +1,16 @@ +Unless otherwise stated, all files in this directory are distributed under the +Zero-Clause BSD license. + +Zero-Clause BSD +=============== + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/runtime/pack/dist/opt/netrw/README.md b/runtime/pack/dist/opt/netrw/README.md new file mode 100644 index 0000000000..ecd97f1e9a --- /dev/null +++ b/runtime/pack/dist/opt/netrw/README.md @@ -0,0 +1,544 @@ +# Netrw.vim + +netrw.vim plugin from vim (upstream repository) + +The upstream maintained netrw plugin. The original has been created and +maintained by Charles E Campbell and maintained by the vim project until +v9.1.0988. + +Every major version a snapshot from here will be sent to the main [Vim][1] +upstream for distribution with Vim. + +# License + +To see License informations see the LICENSE.txt file included in this +repository. + +# Credits + +Below are stated the contribution made in the past to netrw. + +Changes made to `autoload/netrw.vim`: +- 2023 Nov 21 by Vim Project: ignore wildignore when expanding $COMSPEC (v173a) +- 2023 Nov 22 by Vim Project: fix handling of very long filename on longlist style (v173a) +- 2024 Feb 19 by Vim Project: (announce adoption) +- 2024 Feb 29 by Vim Project: handle symlinks in tree mode correctly +- 2024 Apr 03 by Vim Project: detect filetypes for remote edited files +- 2024 May 08 by Vim Project: cleanup legacy Win9X checks +- 2024 May 09 by Vim Project: remove hard-coded private.ppk +- 2024 May 10 by Vim Project: recursively delete directories by default +- 2024 May 13 by Vim Project: prefer scp over pscp +- 2024 Jun 04 by Vim Project: set bufhidden if buffer changed, nohidden is set and buffer shall be switched (#14915) +- 2024 Jun 13 by Vim Project: glob() on Windows fails when a directory name contains [] (#14952) +- 2024 Jun 23 by Vim Project: save ad restore registers when liststyle = WIDELIST (#15077, #15114) +- 2024 Jul 22 by Vim Project: avoid endless recursion (#15318) +- 2024 Jul 23 by Vim Project: escape filename before trying to delete it (#15330) +- 2024 Jul 30 by Vim Project: handle mark-copy to same target directory (#12112) +- 2024 Aug 02 by Vim Project: honor g:netrw_alt{o,v} for :{S,H,V}explore (#15417) +- 2024 Aug 15 by Vim Project: style changes, prevent E121 (#15501) +- 2024 Aug 22 by Vim Project: fix mf-selection highlight (#15551) +- 2024 Aug 22 by Vim Project: adjust echo output of mx command (#15550) +- 2024 Sep 15 by Vim Project: more strict confirmation dialog (#15680) +- 2024 Sep 19 by Vim Project: mf-selection highlight uses wrong pattern (#15700) +- 2024 Sep 21 by Vim Project: remove extraneous closing bracket (#15718) +- 2024 Oct 21 by Vim Project: remove netrwFileHandlers (#15895) +- 2024 Oct 27 by Vim Project: clean up gx mapping (#15721) +- 2024 Oct 30 by Vim Project: fix filetype detection for remote files (#15961) +- 2024 Oct 30 by Vim Project: fix x mapping on cygwin (#13687) +- 2024 Oct 31 by Vim Project: add netrw#Launch() and netrw#Open() (#15962) +- 2024 Oct 31 by Vim Project: fix E874 when browsing remote dir (#15964) +- 2024 Nov 07 by Vim Project: use keeppatterns to prevent polluting the search history +- 2024 Nov 07 by Vim Project: fix a few issues with netrw tree listing (#15996) +- 2024 Nov 10 by Vim Project: directory symlink not resolved in tree view (#16020) +- 2024 Nov 14 by Vim Project: small fixes to netrw#BrowseX (#16056) +- 2024 Nov 23 by Vim Project: update decompress defaults (#16104) +- 2024 Nov 23 by Vim Project: fix powershell escaping issues (#16094) +- 2024 Dec 04 by Vim Project: do not detach for gvim (#16168) +- 2024 Dec 08 by Vim Project: check the first arg of netrw_browsex_viewer for being executable (#16185) +- 2024 Dec 12 by Vim Project: do not pollute the search history (#16206) +- 2024 Dec 19 by Vim Project: change style (#16248) +- 2024 Dec 20 by Vim Project: change style continued (#16266), fix escaping of # in :Open command (#16265) + +General changes made to netrw: + +``` + v172: Sep 02, 2021 * (Bram Moolenaar) Changed "l:go" to "go" + * (Bram Moolenaar) no need for "b" in + netrw-safe guioptions + Nov 15, 2021 * removed netrw_localrm and netrw_localrmdir + references + Aug 18, 2022 * (Miguel Barro) improving compatibility with + powershell + v171: Oct 09, 2020 * included code in s:NetrwOptionsSafe() + to allow |'bh'| to be set to delete when + rather than hide when g:netrw_fastbrowse + was zero. + * Installed |g:netrw_clipboard| setting + * Installed option bypass for |'guioptions'| + a/A settings + * Changed popup_beval() to |popup_atcursor()| + in netrw#ErrorMsg (lacygoill). Apparently + popup_beval doesn't reliably close the + popup when the mouse is moved. + * VimEnter() now using win_execute to examine + buffers for an attempt to open a directory. + Avoids issues with popups/terminal from + command line. (lacygoill) + Jun 28, 2021 * (zeertzjq) provided a patch for use of + xmap,xno instead of vmap,vno in + netrwPlugin.vim. Avoids entanglement with + select mode. + Jul 14, 2021 * Fixed problem addressed by tst976; opening + a file using tree mode, going up a + directory, and opening a file there was + opening the file in the wrong directory. + Jul 28, 2021 * (Ingo Karkat) provided a patch fixing an + E488 error with netrwPlugin.vim + (occurred for vim versions < 8.02) + v170: Mar 11, 2020 * (reported by Reiner Herrmann) netrw+tree + would not hide with the ^\..* pattern + correctly. + * (Marcin Szamotulski) NetrwOptionRestore + did not restore options correctly that + had a single quote in the option string. + Apr 13, 2020 * implemented error handling via popup + windows (see |popup_beval()|) + Apr 30, 2020 * (reported by Manatsu Takahashi) while + using Lexplore, a modified file could + be overwritten. Sol'n: will not overwrite, + but will emit an |E37| (although one cannot + add an ! to override) + Jun 07, 2020 * (reported by Jo Totland) repeatedly invoking + :Lexplore and quitting it left unused + hidden buffers. Netrw will now set netrw + buffers created by :Lexplore to |'bh'|=wipe. + v169: Dec 20, 2019 * (reported by amkarthik) that netrw's x + (|netrw-x|) would throw an error when + attempting to open a local directory. + v168: Dec 12, 2019 * scp timeout error message not reported, + hopefully now fixed (Shane Xb Qian) + v167: Nov 29, 2019 * netrw does a save&restore on @* and @+. + That causes problems with the clipboard. + Now restores occurs only if @* or @+ have + been changed. + * netrw will change @* or @+ less often. + Never if I happen to have caught all the + operations that modify the unnamed + register (which also writes @*). + * Modified hiding behavior so that "s" + will not ignore hiding. + v166: Nov 06, 2019 * Removed a space from a nmap for "-" + * Numerous debugging statement changes + v163: Dec 05, 2017 * (Cristi Balan) reported that a setting ('sel') + was left changed + * (Holger Mitschke) reported a problem with + saving and restoring history. Fixed. + * Hopefully I fixed a nasty bug that caused a + file rename to wipe out a buffer that it + should not have wiped out. + * (Holger Mitschke) amended this help file + with additional |g:netrw_special_syntax| + items + * Prioritized wget over curl for + g:netrw_http_cmd + v162: Sep 19, 2016 * (haya14busa) pointed out two syntax errors + with a patch; these are now fixed. + Oct 26, 2016 * I started using mate-terminal and found that + x and gx (|netrw-x| and |netrw-gx|) were no + longer working. Fixed (using atril when + $DESKTOP_SESSION is "mate"). + Nov 04, 2016 * (Martin Vuille) pointed out that @+ was + being restored with keepregstar rather than + keepregplus. + Nov 09, 2016 * Broke apart the command from the options, + mostly for Windows. Introduced new netrw + settings: |g:netrw_localcopycmdopt| + |g:netrw_localcopydircmdopt| + |g:netrw_localmkdiropt| + |g:netrw_localmovecmdopt| + Nov 21, 2016 * (mattn) provided a patch for preview; swapped + winwidth() with winheight() + Nov 22, 2016 * (glacambre) reported that files containing + spaces weren't being obtained properly via + scp. Fix: apparently using single quotes + such as with 'file name' wasn't enough; the + spaces inside the quotes also had to be + escaped (ie. 'file\ name'). + * Also fixed obtain (|netrw-O|) to be able to + obtain files with spaces in their names + Dec 20, 2016 * (xc1427) Reported that using "I" (|netrw-I|) + when atop "Hiding" in the banner also caused + the active-banner hiding control to occur + Jan 03, 2017 * (Enno Nagel) reported that attempting to + apply netrw to a directory that was without + read permission caused a syntax error. + Jan 13, 2017 * (Ingo Karkat) provided a patch which makes + using netrw#Call() better. Now returns + value of internal routines return, for example. + Jan 13, 2017 * (Ingo Karkat) changed netrw#FileUrlRead to + use |:edit| instead of |:read|. I also + changed the routine name to netrw#FileUrlEdit. + Jan 16, 2017 * (Sayem) reported a problem where :Lexplore + could generate a new listing buffer and + window instead of toggling the netrw display. + Unfortunately, the directions for eliciting + the problem weren't complete, so I may or + may not have fixed that issue. + Feb 06, 2017 * Implemented cb and cB. Changed "c" to "cd". + (see |netrw-cb|, |netrw-cB|, and |netrw-cd|) + Mar 21, 2017 * previously, netrw would specify (safe) settings + even when the setting was already safe for + netrw. Netrw now attempts to leave such + already-netrw-safe settings alone. + (affects s:NetrwOptionRestore() and + s:NetrwSafeOptions(); also introduced + s:NetrwRestoreSetting()) + Jun 26, 2017 * (Christian Brabandt) provided a patch to + allow curl to follow redirects (ie. -L + option) + Jun 26, 2017 * (Callum Howard) reported a problem with + :Lexpore not removing the Lexplore window + after a change-directory + Aug 30, 2017 * (Ingo Karkat) one cannot switch to the + previously edited file (e.g. with CTRL-^) + after editing a file:// URL. Patch to + have a "keepalt" included. + Oct 17, 2017 * (Adam Faryna) reported that gn (|netrw-gn|) + did not work on directories in the current + tree + v157: Apr 20, 2016 * (Nicola) had set up a "nmap ..." with + a function that returned a 0 while silently + invoking a shell command. The shell command + activated a ShellCmdPost event which in turn + called s:LocalBrowseRefresh(). That looks + over all netrw buffers for changes needing + refreshes. However, inside a |:map-|, + tab and window changes are disallowed. Fixed. + (affects netrw's s:LocalBrowseRefresh()) + * g:netrw_localrmdir not used any more, but + the relevant patch that causes |delete()| to + take over was #1107 (not #1109). + * |expand()| is now used on |g:netrw_home|; + consequently, g:netrw_home may now use + environment variables + * s:NetrwLeftmouse and s:NetrwCLeftmouse will + return without doing anything if invoked + when inside a non-netrw window + Jun 15, 2016 * gx now calls netrw#GX() which returns + the word under the cursor. The new + wrinkle: if one is in a netrw buffer, + then netrw's s:NetrwGetWord(). + Jun 22, 2016 * Netrw was executing all its associated + Filetype commands silently; I'm going + to try doing that "noisily" and see if + folks have a problem with that. + Aug 12, 2016 * Changed order of tool selection for + handling http://... viewing. + (Nikolay Aleksandrovich Pavlov) + Aug 21, 2016 * Included hiding/showing/all for tree + listings + * Fixed refresh (^L) for tree listings + v156: Feb 18, 2016 * Changed =~ to =~# where appropriate + Feb 23, 2016 * s:ComposePath(base,subdir) now uses + fnameescape() on the base portion + Mar 01, 2016 * (gt_macki) reported where :Explore would + make file unlisted. Fixed (tst943) + Apr 04, 2016 * (reported by John Little) netrw normally + suppresses browser messages, but sometimes + those "messages" are what is wanted. + See |g:netrw_suppress_gx_mesg| + Apr 06, 2016 * (reported by Carlos Pita) deleting a remote + file was giving an error message. Fixed. + Apr 08, 2016 * (Charles Cooper) had a problem with an + undefined b:netrw_curdir. He also provided + a fix. + Apr 20, 2016 * Changed s:NetrwGetBuffer(); now uses + dictionaries. Also fixed the "No Name" + buffer problem. + v155: Oct 29, 2015 * (Timur Fayzrakhmanov) reported that netrw's + mapping of ctrl-l was not allowing refresh of + other windows when it was done in a netrw + window. + Nov 05, 2015 * Improved s:TreeSqueezeDir() to use search() + instead of a loop + * NetrwBrowse() will return line to + w:netrw_bannercnt if cursor ended up in + banner + Nov 16, 2015 * Added a NetrwTreeSqueeze (|netrw-s-cr|) + Nov 17, 2015 * Commented out imaps -- perhaps someone can + tell me how they're useful and should be + retained? + Nov 20, 2015 * Added |netrw-ma| and |netrw-mA| support + Nov 20, 2015 * gx (|netrw-gx|) on a URL downloaded the + file in addition to simply bringing up the + URL in a browser. Fixed. + Nov 23, 2015 * Added |g:netrw_sizestyle| support + Nov 27, 2015 * Inserted a lot of s into various netrw + maps. + Jan 05, 2016 * |netrw-qL| implemented to mark files based + upon |location-list|s; similar to |netrw-qF|. + Jan 19, 2016 * using - call delete(directoryname,"d") - + instead of using g:netrw_localrmdir if + v7.4 + patch#1107 is available + Jan 28, 2016 * changed to using |winsaveview()| and + |winrestview()| + Jan 28, 2016 * s:NetrwTreePath() now does a save and + restore of view + Feb 08, 2016 * Fixed a tree-listing problem with remote + directories + v154: Feb 26, 2015 * (Yuri Kanivetsky) reported a situation where + a file was not treated properly as a file + due to g:netrw_keepdir == 1 + Mar 25, 2015 * (requested by Ben Friz) one may now sort by + extension + Mar 28, 2015 * (requested by Matt Brooks) netrw has a lot + of buffer-local mappings; however, some + plugins (such as vim-surround) set up + conflicting mappings that cause vim to wait. + The "" modifier has been included + with most of netrw's mappings to avoid that + delay. + Jun 26, 2015 * |netrw-gn| mapping implemented + * :Ntree NotADir resulted in having + the tree listing expand in the error messages + window. Fixed. + Jun 29, 2015 * Attempting to delete a file remotely caused + an error with "keepsol" mentioned; fixed. + Jul 08, 2015 * Several changes to keep the |:jumps| table + correct when working with + |g:netrw_fastbrowse| set to 2 + * wide listing with accented characters fixed + (using %-S instead of %-s with a |printf()| + Jul 13, 2015 * (Daniel Hahler) CheckIfKde() could be true + but kfmclient not installed. Changed order + in netrw#BrowseX(): checks if kde and + kfmclient, then will use xdg-open on a unix + system (if xdg-open is executable) + Aug 11, 2015 * (McDonnell) tree listing mode wouldn't + select a file in a open subdirectory. + * (McDonnell) when multiple subdirectories + were concurrently open in tree listing + mode, a ctrl-L wouldn't refresh properly. + * The netrw:target menu showed duplicate + entries + Oct 13, 2015 * (mattn) provided an exception to handle + windows with shellslash set but no shell + Oct 23, 2015 * if g:netrw_usetab and now used + to control whether NetrwShrink is used + (see |netrw-c-tab|) + v153: May 13, 2014 * added another |g:netrw_ffkeep| usage {{{2 + May 14, 2014 * changed s:PerformListing() so that it + always sets ft=netrw for netrw buffers + (ie. even when syntax highlighting is + off, not available, etc) + May 16, 2014 * introduced the |netrw-ctrl-r| functionality + May 17, 2014 * introduced the |netrw-:NetrwMB| functionality + * mb and mB (|netrw-mb|, |netrw-mB|) will + add/remove marked files from bookmark list + May 20, 2014 * (Enno Nagel) reported that :Lex + wasn't working. Fixed. + May 26, 2014 * restored test to prevent leftmouse window + resizing from causing refresh. + (see s:NetrwLeftmouse()) + * fixed problem where a refresh caused cursor + to go just under the banner instead of + staying put + May 28, 2014 * (László Bimba) provided a patch for opening + the |:Lexplore| window 100% high, optionally + on the right, and will work with remote + files. + May 29, 2014 * implemented :NetrwC (see |netrw-:NetrwC|) + Jun 01, 2014 * Removed some "silent"s from commands used + to implemented scp://... and pscp://... + directory listing. Permits request for + password to appear. + Jun 05, 2014 * (Enno Nagel) reported that user maps "/" + caused problems with "b" and "w", which + are mapped (for wide listings only) to + skip over files rather than just words. + Jun 10, 2014 * |g:netrw_gx| introduced to allow users to + override default "" with the gx + (|netrw-gx|) map + Jun 11, 2014 * gx (|netrw-gx|), with |'autowrite'| set, + will write modified files. s:NetrwBrowseX() + will now save, turn off, and restore the + |'autowrite'| setting. + Jun 13, 2014 * added visual map for gx use + Jun 15, 2014 * (Enno Nagel) reported that with having hls + set and wide listing style in use, that the + b and w maps caused unwanted highlighting. + Jul 05, 2014 * |netrw-mv| and |netrw-mX| commands included + Jul 09, 2014 * |g:netrw_keepj| included, allowing optional + keepj + Jul 09, 2014 * fixing bugs due to previous update + Jul 21, 2014 * (Bruno Sutic) provided an updated + netrw_gitignore.vim + Jul 30, 2014 * (Yavuz Yetim) reported that editing two + remote files of the same name caused the + second instance to have a "temporary" + name. Fixed: now they use the same buffer. + Sep 18, 2014 * (Yasuhiro Matsumoto) provided a patch which + allows scp and windows local paths to work. + Oct 07, 2014 * gx (see |netrw-gx|) when atop a directory, + will now do |gf| instead + Nov 06, 2014 * For cygwin: cygstart will be available for + netrw#BrowseX() to use if its executable. + Nov 07, 2014 * Began support for file://... urls. Will use + |g:netrw_file_cmd| (typically elinks or links) + Dec 02, 2014 * began work on having mc (|netrw-mc|) copy + directories. Works for linux machines, + cygwin+vim, but not for windows+gvim. + Dec 02, 2014 * in tree mode, netrw was not opening + directories via symbolic links. + Dec 02, 2014 * added resolved link information to + thin and tree modes + Dec 30, 2014 * (issue#231) |:ls| was not showing + remote-file buffers reliably. Fixed. + v152: Apr 08, 2014 * uses the |'noswapfile'| option (requires {{{2 + vim 7.4 with patch 213) + * (Enno Nagel) turn |'rnu'| off in netrw + buffers. + * (Quinn Strahl) suggested that netrw + allow regular window splitting to occur, + thereby allowing |'equalalways'| to take + effect. + * (qingtian zhao) normally, netrw will + save and restore the |'fileformat'|; + however, sometimes that isn't wanted + Apr 14, 2014 * whenever netrw marks a buffer as ro, + it will also mark it as nomod. + Apr 16, 2014 * sftp protocol now supported by + netrw#Obtain(); this means that one + may use "mc" to copy a remote file + to a local file using sftp, and that + the |netrw-O| command can obtain remote + files via sftp. + * added [count]C support (see |netrw-C|) + Apr 18, 2014 * when |g:netrw_chgwin| is one more than + the last window, then vertically split + the last window and use it as the + chgwin window. + May 09, 2014 * SavePosn was "saving filename under cursor" + from a non-netrw window when using :Rex. + v151: Jan 22, 2014 * extended :Rexplore to return to buffer {{{2 + prior to Explore or editing a directory + * (Ken Takata) netrw gave error when + clipboard was disabled. Sol'n: Placed + several if has("clipboard") tests in. + * Fixed ftp://X@Y@Z// problem; X@Y now + part of user id, and only Z is part of + hostname. + * (A Loumiotis) reported that completion + using a directory name containing spaces + did not work. Fixed with a retry in + netrw#Explore() which removes the + backslashes vim inserted. + Feb 26, 2014 * :Rexplore now records the current file + using w:netrw_rexfile when returning via + |:Rexplore| + Mar 08, 2014 * (David Kotchan) provided some patches + allowing netrw to work properly with + windows shares. + * Multiple one-liner help messages available + by pressing while atop the "Quick + Help" line + * worked on ShellCmdPost, FocusGained event + handling. + * |:Lexplore| path: will be used to update + a left-side netrw browsing directory. + Mar 12, 2014 * |netrw-s-cr|: use to close + tree directory implemented + Mar 13, 2014 * (Tony Mechylynck) reported that using + the browser with ftp on a directory, + and selecting a gzipped txt file, that + an E19 occurred (which was issued by + gzip.vim). Fixed. + Mar 14, 2014 * Implemented :MF and :MT (see |netrw-:MF| + and |netrw-:MT|, respectively) + Mar 17, 2014 * |:Ntree| [dir] wasn't working properly; fixed + Mar 18, 2014 * Changed all uses of set to setl + Mar 18, 2014 * Commented the netrw_btkeep line in + s:NetrwOptionSave(); the effect is that + netrw buffers will remain as |'bt'|=nofile. + This should prevent swapfiles being created + for netrw buffers. + Mar 20, 2014 * Changed all uses of lcd to use s:NetrwLcd() + instead. Consistent error handling results + and it also handles Window's shares + * Fixed |netrw-d| command when applied with ftp + * https: support included for netrw#NetRead() + v150: Jul 12, 2013 * removed a "keepalt" to allow ":e #" to {{{2 + return to the netrw directory listing + Jul 13, 2013 * (Jonas Diemer) suggested changing + a to . + Jul 21, 2013 * (Yuri Kanivetsky) reported that netrw's + use of mkdir did not produce directories + following the user's umask. + Aug 27, 2013 * introduced |g:netrw_altfile| option + Sep 05, 2013 * s:Strlen() now uses |strdisplaywidth()| + when available, by default + Sep 12, 2013 * (Selyano Baldo) reported that netrw wasn't + opening some directories properly from the + command line. + Nov 09, 2013 * |:Lexplore| introduced + * (Ondrej Platek) reported an issue with + netrw's trees (P15). Fixed. + * (Jorge Solis) reported that "t" in + tree mode caused netrw to forget its + line position. + Dec 05, 2013 * Added file marking + (see |netrw-mf|) + Dec 05, 2013 * (Yasuhiro Matsumoto) Explore should use + strlen() instead s:Strlen() when handling + multibyte chars with strpart() + (ie. strpart() is byte oriented, not + display-width oriented). + Dec 09, 2013 * (Ken Takata) Provided a patch; File sizes + and a portion of timestamps were wrongly + highlighted with the directory color when + setting `:let g:netrw_liststyle=1` on Windows. + * (Paul Domaskis) noted that sometimes + cursorline was activating in non-netrw + windows. All but one setting of cursorline + was done via setl; there was one that was + overlooked. Fixed. + Dec 24, 2013 * (esquifit) asked that netrw allow the + /cygdrive prefix be a user-alterable + parameter. + Jan 02, 2014 * Fixed a problem with netrw-based ballon + evaluation (ie. netrw#NetrwBaloonHelp() + not having been loaded error messages) + Jan 03, 2014 * Fixed a problem with tree listings + * New command installed: |:Ntree| + Jan 06, 2014 * (Ivan Brennan) reported a problem with + |netrw-P|. Fixed. + Jan 06, 2014 * Fixed a problem with |netrw-P| when the + modified file was to be abandoned. + Jan 15, 2014 * (Matteo Cavalleri) reported that when the + banner is suppressed and tree listing is + used, a blank line was left at the top of + the display. Fixed. + Jan 20, 2014 * (Gideon Go) reported that, in tree listing + style, with a previous window open, that + the wrong directory was being used to open + a file. Fixed. (P21) + v149: Apr 18, 2013 * in wide listing format, now have maps for {{{2 + w and b to move to next/previous file + Apr 26, 2013 * one may now copy files in the same + directory; netrw will issue requests for + what names the files should be copied under + Apr 29, 2013 * Trying Benzinger's problem again. Seems + that commenting out the BufEnter and + installing VimEnter (only) works. Weird + problem! (tree listing, vim -O Dir1 Dir2) + May 01, 2013 * :Explore ftp://... wasn't working. Fixed. + May 02, 2013 * introduced |g:netrw_bannerbackslash| as + requested by Paul Domaskis. + Jul 03, 2013 * Explore now avoids splitting when a buffer + will be hidden. + v148: Apr 16, 2013 * changed Netrw's Style menu to allow direct {{{2 + choice of listing style, hiding style, and + sorting style +``` + +[1]: https://github.com/vim/vim diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw.vim b/runtime/pack/dist/opt/netrw/autoload/netrw.vim new file mode 100644 index 0000000000..d3f60bb8dc --- /dev/null +++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim @@ -0,0 +1,11927 @@ +" Maintainer: Luca Saccarola +" Former Maintainer: Charles E Campbell +" Upstream: +" Copyright: Copyright (C) 2016 Charles E. Campbell {{{1 +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" netrw.vim, netrwPlugin.vim, and netrwSettings.vim are provided +" *as is* and come with no warranty of any kind, either +" expressed or implied. By using this plugin, you agree that +" in no event will the copyright holder be liable for any damages +" resulting from the use of this software. +" +" Note: the code here was started in 1999 under a much earlier version of vim. The directory browsing +" code was written using vim v6, which did not have Lists (Lists were first offered with vim-v7). + +" Load Once: {{{1 +if &cp || exists("g:loaded_netrw") + finish +endif + +" Check that vim has patches that netrw requires. +" Patches needed for v7.4: 1557, and 213. +" (netrw will benefit from vim's having patch#656, too) +let s:needspatches=[1557,213] +if exists("s:needspatches") + for ptch in s:needspatches + if v:version < 704 || (v:version == 704 && !has("patch".ptch)) + if !exists("s:needpatch{ptch}") + unsilent echomsg "***sorry*** this version of netrw requires vim v7.4 with patch#".ptch + endif + let s:needpatch{ptch}= 1 + finish + endif + endfor +endif + +let g:loaded_netrw = "v174" + +let s:keepcpo= &cpo +setl cpo&vim +"DechoFuncName 1 +"DechoRemOn +"call Decho("doing autoload/netrw.vim version ".g:loaded_netrw,'~'.expand("")) + +" ====================== +" Netrw Variables: {{{1 +" ====================== + +" --------------------------------------------------------------------- +" netrw#ErrorMsg: {{{2 +" 0=note = s:NOTE +" 1=warning = s:WARNING +" 2=error = s:ERROR +" Usage: netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,"some message",error-number) +" netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,["message1","message2",...],error-number) +" (this function can optionally take a list of messages) +" Dec 2, 2019 : max errnum currently is 106 +fun! netrw#ErrorMsg(level,msg,errnum) + " call Dfunc("netrw#ErrorMsg(level=".a:level." msg<".a:msg."> errnum=".a:errnum.") g:netrw_use_errorwindow=".g:netrw_use_errorwindow) + + if a:level < g:netrw_errorlvl + " call Dret("netrw#ErrorMsg : suppressing level=".a:level." since g:netrw_errorlvl=".g:netrw_errorlvl) + return + endif + + if a:level == 1 + let level= "**warning** (netrw) " + elseif a:level == 2 + let level= "**error** (netrw) " + else + let level= "**note** (netrw) " + endif + " call Decho("level=".level,'~'.expand("")) + + if g:netrw_use_errorwindow == 2 && exists("*popup_atcursor") + " use popup window + if type(a:msg) == 3 + let msg = [level]+a:msg + else + let msg= level.a:msg + endif + let s:popuperr_id = popup_atcursor(msg,{}) + let s:popuperr_text= "" + elseif g:netrw_use_errorwindow + " (default) netrw creates a one-line window to show error/warning + " messages (reliably displayed) + + " record current window number + let s:winBeforeErr= winnr() + " call Decho("s:winBeforeErr=".s:winBeforeErr,'~'.expand("")) + + " getting messages out reliably is just plain difficult! + " This attempt splits the current window, creating a one line window. + if bufexists("NetrwMessage") && bufwinnr("NetrwMessage") > 0 + " call Decho("write to NetrwMessage buffer",'~'.expand("")) + exe bufwinnr("NetrwMessage")."wincmd w" + " call Decho("setl ma noro",'~'.expand("")) + setl ma noro + if type(a:msg) == 3 + for msg in a:msg + NetrwKeepj call setline(line("$")+1,level.msg) + endfor + else + NetrwKeepj call setline(line("$")+1,level.a:msg) + endif + NetrwKeepj $ + else + " call Decho("create a NetrwMessage buffer window",'~'.expand("")) + bo 1split + sil! call s:NetrwEnew() + sil! NetrwKeepj call s:NetrwOptionsSafe(1) + setl bt=nofile + NetrwKeepj file NetrwMessage + " call Decho("setl ma noro",'~'.expand("")) + setl ma noro + if type(a:msg) == 3 + for msg in a:msg + NetrwKeepj call setline(line("$")+1,level.msg) + endfor + else + NetrwKeepj call setline(line("$"),level.a:msg) + endif + NetrwKeepj $ + endif + " call Decho("wrote msg<".level.a:msg."> to NetrwMessage win#".winnr(),'~'.expand("")) + if &fo !~ '[ta]' + syn clear + syn match netrwMesgNote "^\*\*note\*\*" + syn match netrwMesgWarning "^\*\*warning\*\*" + syn match netrwMesgError "^\*\*error\*\*" + hi link netrwMesgWarning WarningMsg + hi link netrwMesgError Error + endif + " call Decho("setl noma ro bh=wipe",'~'.expand("")) + setl ro nomod noma bh=wipe + + else + " (optional) netrw will show messages using echomsg. Even if the + " message doesn't appear, at least it'll be recallable via :messages + " redraw! + if a:level == s:WARNING + echohl WarningMsg + elseif a:level == s:ERROR + echohl Error + endif + + if type(a:msg) == 3 + for msg in a:msg + unsilent echomsg level.msg + endfor + else + unsilent echomsg level.a:msg + endif + + " call Decho("echomsg ***netrw*** ".a:msg,'~'.expand("")) + echohl None + endif + + " call Dret("netrw#ErrorMsg") +endfun + +" --------------------------------------------------------------------- +" s:NetrwInit: initializes variables if they haven't been defined {{{2 +" Loosely, varname = value. +fun s:NetrwInit(varname,value) + " call Decho("varname<".a:varname."> value=".a:value,'~'.expand("")) + if !exists(a:varname) + if type(a:value) == 0 + exe "let ".a:varname."=".a:value + elseif type(a:value) == 1 && a:value =~ '^[{[]' + exe "let ".a:varname."=".a:value + elseif type(a:value) == 1 + exe "let ".a:varname."="."'".a:value."'" + else + exe "let ".a:varname."=".a:value + endif + endif +endfun + +" --------------------------------------------------------------------- +" Netrw Constants: {{{2 +call s:NetrwInit("g:netrw_dirhistcnt",0) +if !exists("s:LONGLIST") + call s:NetrwInit("s:THINLIST",0) + call s:NetrwInit("s:LONGLIST",1) + call s:NetrwInit("s:WIDELIST",2) + call s:NetrwInit("s:TREELIST",3) + call s:NetrwInit("s:MAXLIST" ,4) +endif + +let s:NOTE = 0 +let s:WARNING = 1 +let s:ERROR = 2 +call s:NetrwInit("g:netrw_errorlvl", s:NOTE) + +" --------------------------------------------------------------------- +" Default option values: {{{2 +let g:netrw_localcopycmdopt = "" +let g:netrw_localcopydircmdopt = "" +let g:netrw_localmkdiropt = "" +let g:netrw_localmovecmdopt = "" + +" --------------------------------------------------------------------- +" Default values for netrw's global protocol variables {{{2 +if !exists("g:netrw_use_errorwindow") + let g:netrw_use_errorwindow = 0 +endif + +if !exists("g:netrw_dav_cmd") + if executable("cadaver") + let g:netrw_dav_cmd = "cadaver" + elseif executable("curl") + let g:netrw_dav_cmd = "curl" + else + let g:netrw_dav_cmd = "" + endif +endif +if !exists("g:netrw_fetch_cmd") + if executable("fetch") + let g:netrw_fetch_cmd = "fetch -o" + else + let g:netrw_fetch_cmd = "" + endif +endif +if !exists("g:netrw_file_cmd") + if executable("elinks") + call s:NetrwInit("g:netrw_file_cmd","elinks") + elseif executable("links") + call s:NetrwInit("g:netrw_file_cmd","links") + endif +endif +if !exists("g:netrw_ftp_cmd") + let g:netrw_ftp_cmd = "ftp" +endif +let s:netrw_ftp_cmd= g:netrw_ftp_cmd +if !exists("g:netrw_ftp_options") + let g:netrw_ftp_options= "-i -n" +endif +if !exists("g:netrw_http_cmd") + if executable("wget") + let g:netrw_http_cmd = "wget" + call s:NetrwInit("g:netrw_http_xcmd","-q -O") + elseif executable("curl") + let g:netrw_http_cmd = "curl" + call s:NetrwInit("g:netrw_http_xcmd","-L -o") + elseif executable("elinks") + let g:netrw_http_cmd = "elinks" + call s:NetrwInit("g:netrw_http_xcmd","-source >") + elseif executable("fetch") + let g:netrw_http_cmd = "fetch" + call s:NetrwInit("g:netrw_http_xcmd","-o") + elseif executable("links") + let g:netrw_http_cmd = "links" + call s:NetrwInit("g:netrw_http_xcmd","-http.extra-header ".shellescape("Accept-Encoding: identity", 1)." -source >") + else + let g:netrw_http_cmd = "" + endif +endif +call s:NetrwInit("g:netrw_http_put_cmd","curl -T") +call s:NetrwInit("g:netrw_keepj","keepj") +call s:NetrwInit("g:netrw_rcp_cmd" , "rcp") +call s:NetrwInit("g:netrw_rsync_cmd", "rsync") +call s:NetrwInit("g:netrw_rsync_sep", "/") +if !exists("g:netrw_scp_cmd") + if executable("scp") + call s:NetrwInit("g:netrw_scp_cmd" , "scp -q") + elseif executable("pscp") + call s:NetrwInit("g:netrw_scp_cmd", 'pscp -q') + else + call s:NetrwInit("g:netrw_scp_cmd" , "scp -q") + endif +endif +call s:NetrwInit("g:netrw_sftp_cmd" , "sftp") +call s:NetrwInit("g:netrw_ssh_cmd" , "ssh") + +if has("win32") + \ && exists("g:netrw_use_nt_rcp") + \ && g:netrw_use_nt_rcp + \ && executable( $SystemRoot .'/system32/rcp.exe') + let s:netrw_has_nt_rcp = 1 + let s:netrw_rcpmode = '-b' +else + let s:netrw_has_nt_rcp = 0 + let s:netrw_rcpmode = '' +endif + +" --------------------------------------------------------------------- +" Default values for netrw's global variables {{{2 +" Cygwin Detection ------- {{{3 +if !exists("g:netrw_cygwin") + if has("win32unix") && &shell =~ '\%(\\|\\)\%(\.exe\)\=$' + let g:netrw_cygwin= 1 + else + let g:netrw_cygwin= 0 + endif +endif +" Default values - a-c ---------- {{{3 +call s:NetrwInit("g:netrw_alto" , &sb) +call s:NetrwInit("g:netrw_altv" , &spr) +call s:NetrwInit("g:netrw_banner" , 1) +call s:NetrwInit("g:netrw_browse_split", 0) +call s:NetrwInit("g:netrw_bufsettings" , "noma nomod nonu nobl nowrap ro nornu") +call s:NetrwInit("g:netrw_chgwin" , -1) +call s:NetrwInit("g:netrw_clipboard" , 1) +call s:NetrwInit("g:netrw_compress" , "gzip") +call s:NetrwInit("g:netrw_ctags" , "ctags") +if exists("g:netrw_cursorline") && !exists("g:netrw_cursor") + call netrw#ErrorMsg(s:NOTE,'g:netrw_cursorline is deprecated; use g:netrw_cursor instead',77) + let g:netrw_cursor= g:netrw_cursorline +endif +call s:NetrwInit("g:netrw_cursor" , 2) +let s:netrw_usercul = &cursorline +let s:netrw_usercuc = &cursorcolumn +"call Decho("(netrw) COMBAK: cuc=".&l:cuc." cul=".&l:cul." initialization of s:netrw_cu[cl]") +call s:NetrwInit("g:netrw_cygdrive","/cygdrive") +" Default values - d-g ---------- {{{3 +call s:NetrwInit("s:didstarstar",0) +call s:NetrwInit("g:netrw_dirhistcnt" , 0) +let s:xz_opt = has('unix') ? "XZ_OPT=-T0" : + \ (has("win32") && &shell =~? '\vcmd(\.exe)?$' ? + \ "setx XZ_OPT=-T0 &&" : "") +call s:NetrwInit("g:netrw_decompress ", "{" + \ .."'.lz4': 'lz4 -d'," + \ .."'.lzo': 'lzop -d'," + \ .."'.lz': 'lzip -dk'," + \ .."'.7z': '7za x'," + \ .."'.001': '7za x'," + \ .."'.zip': 'unzip'," + \ .."'.bz': 'bunzip2 -k'," + \ .."'.bz2': 'bunzip2 -k'," + \ .."'.gz': 'gunzip -k'," + \ .."'.lzma': 'unlzma -T0 -k'," + \ .."'.xz': 'unxz -T0 -k'," + \ .."'.zst': 'zstd -T0 -d'," + \ .."'.Z': 'uncompress -k'," + \ .."'.tar': 'tar -xvf'," + \ .."'.tar.bz': 'tar -xvjf'," + \ .."'.tar.bz2': 'tar -xvjf'," + \ .."'.tbz': 'tar -xvjf'," + \ .."'.tbz2': 'tar -xvjf'," + \ .."'.tar.gz': 'tar -xvzf'," + \ .."'.tgz': 'tar -xvzf'," + \ .."'.tar.lzma': '"..s:xz_opt.." tar -xvf --lzma'," + \ .."'.tlz': '"..s:xz_opt.." tar -xvf --lzma'," + \ .."'.tar.xz': '"..s:xz_opt.." tar -xvfJ'," + \ .."'.txz': '"..s:xz_opt.." tar -xvfJ'," + \ .."'.tar.zst': '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd'," + \ .."'.tzst': '"..s:xz_opt.." tar -xvf --use-compress-program=unzstd'," + \ .."'.rar': '"..(executable("unrar")?"unrar x -ad":"rar x -ad").."'" + \ .."}") +unlet s:xz_opt +call s:NetrwInit("g:netrw_dirhistmax" , 10) +call s:NetrwInit("g:netrw_fastbrowse" , 1) +call s:NetrwInit("g:netrw_ftp_browse_reject", '^total\s\+\d\+$\|^Trying\s\+\d\+.*$\|^KERBEROS_V\d rejected\|^Security extensions not\|No such file\|: connect to address [0-9a-fA-F:]*: No route to host$') +if !exists("g:netrw_ftp_list_cmd") + if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin) + let g:netrw_ftp_list_cmd = "ls -lF" + let g:netrw_ftp_timelist_cmd = "ls -tlF" + let g:netrw_ftp_sizelist_cmd = "ls -slF" + else + let g:netrw_ftp_list_cmd = "dir" + let g:netrw_ftp_timelist_cmd = "dir" + let g:netrw_ftp_sizelist_cmd = "dir" + endif +endif +call s:NetrwInit("g:netrw_ftpmode",'binary') +" Default values - h-lh ---------- {{{3 +call s:NetrwInit("g:netrw_hide",1) +if !exists("g:netrw_ignorenetrc") + if &shell =~ '\c\<\%(cmd\|4nt\)\.exe$' + let g:netrw_ignorenetrc= 1 + else + let g:netrw_ignorenetrc= 0 + endif +endif +call s:NetrwInit("g:netrw_keepdir",1) +if !exists("g:netrw_list_cmd") + if g:netrw_scp_cmd =~ '^pscp' && executable("pscp") + if exists("g:netrw_list_cmd_options") + let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME: ".g:netrw_list_cmd_options + else + let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME:" + endif + elseif executable(g:netrw_ssh_cmd) + " provide a scp-based default listing command + if exists("g:netrw_list_cmd_options") + let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa ".g:netrw_list_cmd_options + else + let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa" + endif + else + " call Decho(g:netrw_ssh_cmd." is not executable",'~'.expand("")) + let g:netrw_list_cmd= "" + endif +endif +call s:NetrwInit("g:netrw_list_hide","") +" Default values - lh-lz ---------- {{{3 +if exists("g:netrw_local_copycmd") + let g:netrw_localcopycmd= g:netrw_local_copycmd + call netrw#ErrorMsg(s:NOTE,"g:netrw_local_copycmd is deprecated in favor of g:netrw_localcopycmd",84) +endif +if !exists("g:netrw_localcmdshell") + let g:netrw_localcmdshell= "" +endif +if !exists("g:netrw_localcopycmd") + if has("win32") + if g:netrw_cygwin + let g:netrw_localcopycmd= "cp" + else + let g:netrw_localcopycmd = expand("$COMSPEC", v:true) + let g:netrw_localcopycmdopt= " /c copy" + endif + elseif has("unix") || has("macunix") + let g:netrw_localcopycmd= "cp" + else + let g:netrw_localcopycmd= "" + endif +endif +if !exists("g:netrw_localcopydircmd") + if has("win32") + if g:netrw_cygwin + let g:netrw_localcopydircmd = "cp" + let g:netrw_localcopydircmdopt= " -R" + else + let g:netrw_localcopydircmd = expand("$COMSPEC", v:true) + let g:netrw_localcopydircmdopt= " /c xcopy /e /c /h /i /k" + endif + elseif has("unix") + let g:netrw_localcopydircmd = "cp" + let g:netrw_localcopydircmdopt= " -R" + elseif has("macunix") + let g:netrw_localcopydircmd = "cp" + let g:netrw_localcopydircmdopt= " -R" + else + let g:netrw_localcopydircmd= "" + endif +endif +if exists("g:netrw_local_mkdir") + let g:netrw_localmkdir= g:netrw_local_mkdir + call netrw#ErrorMsg(s:NOTE,"g:netrw_local_mkdir is deprecated in favor of g:netrw_localmkdir",87) +endif +if has("win32") + if g:netrw_cygwin + call s:NetrwInit("g:netrw_localmkdir","mkdir") + else + let g:netrw_localmkdir = expand("$COMSPEC", v:true) + let g:netrw_localmkdiropt= " /c mkdir" + endif +else + call s:NetrwInit("g:netrw_localmkdir","mkdir") +endif +call s:NetrwInit("g:netrw_remote_mkdir","mkdir") +if exists("g:netrw_local_movecmd") + let g:netrw_localmovecmd= g:netrw_local_movecmd + call netrw#ErrorMsg(s:NOTE,"g:netrw_local_movecmd is deprecated in favor of g:netrw_localmovecmd",88) +endif +if !exists("g:netrw_localmovecmd") + if has("win32") + if g:netrw_cygwin + let g:netrw_localmovecmd= "mv" + else + let g:netrw_localmovecmd = expand("$COMSPEC", v:true) + let g:netrw_localmovecmdopt= " /c move" + endif + elseif has("unix") || has("macunix") + let g:netrw_localmovecmd= "mv" + else + let g:netrw_localmovecmd= "" + endif +endif +" following serves as an example for how to insert a version&patch specific test +"if v:version < 704 || (v:version == 704 && !has("patch1107")) +"endif +call s:NetrwInit("g:netrw_liststyle" , s:THINLIST) +" sanity checks +if g:netrw_liststyle < 0 || g:netrw_liststyle >= s:MAXLIST + let g:netrw_liststyle= s:THINLIST +endif +if g:netrw_liststyle == s:LONGLIST && g:netrw_scp_cmd !~ '^pscp' + let g:netrw_list_cmd= g:netrw_list_cmd." -l" +endif +" Default values - m-r ---------- {{{3 +call s:NetrwInit("g:netrw_markfileesc" , '*./[\~') +call s:NetrwInit("g:netrw_maxfilenamelen", 32) +call s:NetrwInit("g:netrw_menu" , 1) +call s:NetrwInit("g:netrw_mkdir_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME mkdir") +call s:NetrwInit("g:netrw_mousemaps" , (exists("+mouse") && &mouse =~# '[anh]')) +call s:NetrwInit("g:netrw_retmap" , 0) +if has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin) + call s:NetrwInit("g:netrw_chgperm" , "chmod PERM FILENAME") +elseif has("win32") + call s:NetrwInit("g:netrw_chgperm" , "cacls FILENAME /e /p PERM") +else + call s:NetrwInit("g:netrw_chgperm" , "chmod PERM FILENAME") +endif +call s:NetrwInit("g:netrw_preview" , 0) +call s:NetrwInit("g:netrw_scpport" , "-P") +call s:NetrwInit("g:netrw_servername" , "NETRWSERVER") +call s:NetrwInit("g:netrw_sshport" , "-p") +call s:NetrwInit("g:netrw_rename_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME mv") +call s:NetrwInit("g:netrw_rm_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rm") +call s:NetrwInit("g:netrw_rmdir_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rmdir") +call s:NetrwInit("g:netrw_rmf_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rm -f ") +" Default values - q-s ---------- {{{3 +call s:NetrwInit("g:netrw_quickhelp",0) +let s:QuickHelp= ["-:go up dir D:delete R:rename s:sort-by x:special", + \ "(create new) %:file d:directory", + \ "(windows split&open) o:horz v:vert p:preview", + \ "i:style qf:file info O:obtain r:reverse", + \ "(marks) mf:mark file mt:set target mm:move mc:copy", + \ "(bookmarks) mb:make mB:delete qb:list gb:go to", + \ "(history) qb:list u:go up U:go down", + \ "(targets) mt:target Tb:use bookmark Th:use history"] +" g:netrw_sepchr: picking a character that doesn't appear in filenames that can be used to separate priority from filename +call s:NetrwInit("g:netrw_sepchr" , (&enc == "euc-jp")? "\" : "\") +if !exists("g:netrw_keepj") || g:netrw_keepj == "keepj" + call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil keepj " : "keepj ") +else + call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil " : " ") +endif +call s:NetrwInit("g:netrw_sort_by" , "name") " alternatives: date , size +call s:NetrwInit("g:netrw_sort_options" , "") +call s:NetrwInit("g:netrw_sort_direction", "normal") " alternative: reverse (z y x ...) +if !exists("g:netrw_sort_sequence") + if has("unix") + let g:netrw_sort_sequence= '[\/]$,\,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$' + else + let g:netrw_sort_sequence= '[\/]$,\.h$,\.c$,\.cpp$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$' + endif +endif +call s:NetrwInit("g:netrw_special_syntax" , 0) +call s:NetrwInit("g:netrw_ssh_browse_reject", '^total\s\+\d\+$') +call s:NetrwInit("g:netrw_use_noswf" , 1) +call s:NetrwInit("g:netrw_sizestyle" ,"b") +" Default values - t-w ---------- {{{3 +call s:NetrwInit("g:netrw_timefmt","%c") +if !exists("g:netrw_xstrlen") + if exists("g:Align_xstrlen") + let g:netrw_xstrlen= g:Align_xstrlen + elseif exists("g:drawit_xstrlen") + let g:netrw_xstrlen= g:drawit_xstrlen + elseif &enc == "latin1" || !has("multi_byte") + let g:netrw_xstrlen= 0 + else + let g:netrw_xstrlen= 1 + endif +endif +call s:NetrwInit("g:NetrwTopLvlMenu","Netrw.") +call s:NetrwInit("g:netrw_winsize",50) +call s:NetrwInit("g:netrw_wiw",1) +if g:netrw_winsize > 100|let g:netrw_winsize= 100|endif +" --------------------------------------------------------------------- +" Default values for netrw's script variables: {{{2 +call s:NetrwInit("g:netrw_fname_escape",' ?&;%') +if has("win32") + call s:NetrwInit("g:netrw_glob_escape",'*?`{[]$') +else + call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\') +endif +call s:NetrwInit("g:netrw_menu_escape",'.&? \') +call s:NetrwInit("g:netrw_tmpfile_escape",' &;') +call s:NetrwInit("s:netrw_map_escape","<|\n\r\\\\"") +if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4') + let s:treedepthstring= "│ " +else + let s:treedepthstring= "| " +endif +call s:NetrwInit("s:netrw_posn",'{}') + +" BufEnter event ignored by decho when following variable is true +" Has a side effect that doau BufReadPost doesn't work, so +" files read by network transfer aren't appropriately highlighted. +"let g:decho_bufenter = 1 "Decho + +" ====================== +" Netrw Initialization: {{{1 +" ====================== +if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on") + " call Decho("installed beval events",'~'.expand("")) + let &l:bexpr = "netrw#BalloonHelp()" + " call Decho("&l:bexpr<".&l:bexpr."> buf#".bufnr()) + au FileType netrw setl beval + au WinLeave * if &ft == "netrw" && exists("s:initbeval")|let &beval= s:initbeval|endif + au VimEnter * let s:initbeval= &beval + "else " Decho + " if v:version < 700 | call Decho("did not install beval events: v:version=".v:version." < 700","~".expand("")) | endif + " if !has("balloon_eval") | call Decho("did not install beval events: does not have balloon_eval","~".expand("")) | endif + " if exists("s:initbeval") | call Decho("did not install beval events: s:initbeval exists","~".expand("")) | endif + " if exists("g:netrw_nobeval") | call Decho("did not install beval events: g:netrw_nobeval exists","~".expand("")) | endif + " if !has("syntax") | call Decho("did not install beval events: does not have syntax highlighting","~".expand("")) | endif + " if exists("g:syntax_on") | call Decho("did not install beval events: g:syntax_on exists","~".expand("")) | endif +endif +au WinEnter * if &ft == "netrw"|call s:NetrwInsureWinVars()|endif + +if g:netrw_keepj =~# "keepj" + com! -nargs=* NetrwKeepj keepj +else + let g:netrw_keepj= "" + com! -nargs=* NetrwKeepj +endif + +" ============================== +" Netrw Utility Functions: {{{1 +" ============================== + +" --------------------------------------------------------------------- +" netrw#BalloonHelp: {{{2 +if v:version >= 700 && has("balloon_eval") && has("syntax") && exists("g:syntax_on") && !exists("g:netrw_nobeval") + " call Decho("loading netrw#BalloonHelp()",'~'.expand("")) + fun! netrw#BalloonHelp() + if &ft != "netrw" + return "" + endif + if exists("s:popuperr_id") && popup_getpos(s:popuperr_id) != {} + " popup error window is still showing + " s:pouperr_id and s:popuperr_text are set up in netrw#ErrorMsg() + if exists("s:popuperr_text") && s:popuperr_text != "" && v:beval_text != s:popuperr_text + " text under mouse hasn't changed; only close window when it changes + call popup_close(s:popuperr_id) + unlet s:popuperr_text + else + let s:popuperr_text= v:beval_text + endif + let mesg= "" + elseif !exists("w:netrw_bannercnt") || v:beval_lnum >= w:netrw_bannercnt || (exists("g:netrw_nobeval") && g:netrw_nobeval) + let mesg= "" + elseif v:beval_text == "Netrw" || v:beval_text == "Directory" || v:beval_text == "Listing" + let mesg = "i: thin-long-wide-tree gh: quick hide/unhide of dot-files qf: quick file info %:open new file" + elseif getline(v:beval_lnum) =~ '^"\s*/' + let mesg = ": edit/enter o: edit/enter in horiz window t: edit/enter in new tab v:edit/enter in vert window" + elseif v:beval_text == "Sorted" || v:beval_text == "by" + let mesg = 's: sort by name, time, file size, extension r: reverse sorting order mt: mark target' + elseif v:beval_text == "Sort" || v:beval_text == "sequence" + let mesg = "S: edit sorting sequence" + elseif v:beval_text == "Hiding" || v:beval_text == "Showing" + let mesg = "a: hiding-showing-all ctrl-h: editing hiding list mh: hide/show by suffix" + elseif v:beval_text == "Quick" || v:beval_text == "Help" + let mesg = "Help: press " + elseif v:beval_text == "Copy/Move" || v:beval_text == "Tgt" + let mesg = "mt: mark target mc: copy marked file to target mm: move marked file to target" + else + let mesg= "" + endif + return mesg + endfun + "else " Decho + " if v:version < 700 |call Decho("did not load netrw#BalloonHelp(): vim version ".v:version." < 700 -","~".expand(""))|endif + " if !has("balloon_eval") |call Decho("did not load netrw#BalloonHelp(): does not have balloon eval","~".expand("")) |endif + " if !has("syntax") |call Decho("did not load netrw#BalloonHelp(): syntax disabled","~".expand("")) |endif + " if !exists("g:syntax_on") |call Decho("did not load netrw#BalloonHelp(): g:syntax_on n/a","~".expand("")) |endif + " if exists("g:netrw_nobeval") |call Decho("did not load netrw#BalloonHelp(): g:netrw_nobeval exists","~".expand("")) |endif +endif + +" ------------------------------------------------------------------------ +" netrw#Explore: launch the local browser in the directory of the current file {{{2 +" indx: == -1: Nexplore +" == -2: Pexplore +" == +: this is overloaded: +" * If Nexplore/Pexplore is in use, then this refers to the +" indx'th item in the w:netrw_explore_list[] of items which +" matched the */pattern **/pattern *//pattern **//pattern +" * If Hexplore or Vexplore, then this will override +" g:netrw_winsize to specify the qty of rows or columns the +" newly split window should have. +" dosplit==0: the window will be split iff the current file has been modified and hidden not set +" dosplit==1: the window will be split before running the local browser +" style == 0: Explore style == 1: Explore! +" == 2: Hexplore style == 3: Hexplore! +" == 4: Vexplore style == 5: Vexplore! +" == 6: Texplore +fun! netrw#Explore(indx,dosplit,style,...) + if !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + endif + + " record current file for Rexplore's benefit + if &ft != "netrw" + let w:netrw_rexfile= expand("%:p") + endif + + " record current directory + let curdir = simplify(b:netrw_curdir) + let curfiledir = substitute(expand("%:p"),'^\(.*[/\\]\)[^/\\]*$','\1','e') + if !exists("g:netrw_cygwin") && has("win32") + let curdir= substitute(curdir,'\','/','g') + endif + + " using completion, directories with spaces in their names (thanks, Bill Gates, for a truly dumb idea) + " will end up with backslashes here. Solution: strip off backslashes that precede white space and + " try Explore again. + if a:0 > 0 + if a:1 =~ "\\\s" && !filereadable(s:NetrwFile(a:1)) && !isdirectory(s:NetrwFile(a:1)) + let a1 = substitute(a:1, '\\\(\s\)', '\1', 'g') + if a1 != a:1 + call netrw#Explore(a:indx, a:dosplit, a:style, a1) + return + endif + endif + endif + + " save registers + if !has('nvim') && has("clipboard") && g:netrw_clipboard + " call Decho("(netrw#Explore) save @* and @+",'~'.expand("")) + sil! let keepregstar = @* + sil! let keepregplus = @+ + endif + sil! let keepregslash= @/ + + " if dosplit + " -or- file has been modified AND file not hidden when abandoned + " -or- Texplore used + if a:dosplit || (&modified && &hidden == 0 && &bufhidden != "hide") || a:style == 6 + call s:SaveWinVars() + let winsz= g:netrw_winsize + if a:indx > 0 + let winsz= a:indx + endif + + if a:style == 0 " Explore, Sexplore + let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz + if winsz == 0|let winsz= ""|endif + exe "noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s" + + elseif a:style == 1 " Explore!, Sexplore! + let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz + if winsz == 0|let winsz= ""|endif + exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v" + + elseif a:style == 2 " Hexplore + let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz + if winsz == 0|let winsz= ""|endif + exe "keepalt noswapfile ".(g:netrw_alto ? "below " : "above ").winsz."wincmd s" + + elseif a:style == 3 " Hexplore! + let winsz= (winsz > 0)? (winsz*winheight(0))/100 : -winsz + if winsz == 0|let winsz= ""|endif + exe "keepalt noswapfile ".(!g:netrw_alto ? "below " : "above ").winsz."wincmd s" + + elseif a:style == 4 " Vexplore + let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz + if winsz == 0|let winsz= ""|endif + exe "keepalt noswapfile ".(g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v" + + elseif a:style == 5 " Vexplore! + let winsz= (winsz > 0)? (winsz*winwidth(0))/100 : -winsz + if winsz == 0|let winsz= ""|endif + exe "keepalt noswapfile ".(!g:netrw_altv ? "rightbelow " : "leftabove ").winsz."wincmd v" + + elseif a:style == 6 " Texplore + call s:SaveBufVars() + exe "keepalt tabnew ".fnameescape(curdir) + call s:RestoreBufVars() + endif + call s:RestoreWinVars() + endif + NetrwKeepj norm! 0 + + if a:0 > 0 + if a:1 =~ '^\~' && (has("unix") || (exists("g:netrw_cygwin") && g:netrw_cygwin)) + let dirname= simplify(substitute(a:1,'\~',expand("$HOME"),'')) + elseif a:1 == '.' + let dirname= simplify(exists("b:netrw_curdir")? b:netrw_curdir : getcwd()) + if dirname !~ '/$' + let dirname= dirname."/" + endif + elseif a:1 =~ '\$' + let dirname= simplify(expand(a:1)) + elseif a:1 !~ '^\*\{1,2}/' && a:1 !~ '^\a\{3,}://' + let dirname= simplify(a:1) + else + let dirname= a:1 + endif + else + " clear explore + call s:NetrwClearExplore() + return + endif + + if dirname =~ '\.\./\=$' + let dirname= simplify(fnamemodify(dirname,':p:h')) + elseif dirname =~ '\.\.' || dirname == '.' + let dirname= simplify(fnamemodify(dirname,':p')) + endif + + if dirname =~ '^\*//' + " starpat=1: Explore *//pattern (current directory only search for files containing pattern) + let pattern= substitute(dirname,'^\*//\(.*\)$','\1','') + let starpat= 1 + if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif + + elseif dirname =~ '^\*\*//' + " starpat=2: Explore **//pattern (recursive descent search for files containing pattern) + let pattern= substitute(dirname,'^\*\*//','','') + let starpat= 2 + + elseif dirname =~ '/\*\*/' + " handle .../**/.../filepat + let prefixdir= substitute(dirname,'^\(.\{-}\)\*\*.*$','\1','') + if prefixdir =~ '^/' || (prefixdir =~ '^\a:/' && has("win32")) + let b:netrw_curdir = prefixdir + else + let b:netrw_curdir= getcwd().'/'.prefixdir + endif + let dirname= substitute(dirname,'^.\{-}\(\*\*/.*\)$','\1','') + let starpat= 4 + + elseif dirname =~ '^\*/' + " case starpat=3: Explore */filepat (search in current directory for filenames matching filepat) + let starpat= 3 + + elseif dirname=~ '^\*\*/' + " starpat=4: Explore **/filepat (recursive descent search for filenames matching filepat) + let starpat= 4 + + else + let starpat= 0 + endif + + if starpat == 0 && a:indx >= 0 + " [Explore Hexplore Vexplore Sexplore] [dirname] + if dirname == "" + let dirname= curfiledir + endif + if dirname =~# '^scp://' || dirname =~ '^ftp://' + call netrw#Nread(2,dirname) + else + if dirname == "" + let dirname= getcwd() + elseif has("win32") && !g:netrw_cygwin + " Windows : check for a drive specifier, or else for a remote share name ('\\Foo' or '//Foo', + " depending on whether backslashes have been converted to forward slashes by earlier code). + if dirname !~ '^[a-zA-Z]:' && dirname !~ '^\\\\\w\+' && dirname !~ '^//\w\+' + let dirname= b:netrw_curdir."/".dirname + endif + elseif dirname !~ '^/' + let dirname= b:netrw_curdir."/".dirname + endif + call netrw#LocalBrowseCheck(dirname) + endif + if exists("w:netrw_bannercnt") + " done to handle P08-Ingelrest. :Explore will _Always_ go to the line just after the banner. + " If one wants to return the same place in the netrw window, use :Rex instead. + exe w:netrw_bannercnt + endif + + + " starpat=1: Explore *//pattern (current directory only search for files containing pattern) + " starpat=2: Explore **//pattern (recursive descent search for files containing pattern) + " starpat=3: Explore */filepat (search in current directory for filenames matching filepat) + " starpat=4: Explore **/filepat (recursive descent search for filenames matching filepat) + elseif a:indx <= 0 + " Nexplore, Pexplore, Explore: handle starpat + if !mapcheck("","n") && !mapcheck("","n") && exists("b:netrw_curdir") + let s:didstarstar= 1 + nnoremap :Pexplore + nnoremap :Nexplore + endif + + if has("path_extra") + if !exists("w:netrw_explore_indx") + let w:netrw_explore_indx= 0 + endif + + let indx = a:indx + + if indx == -1 + " Nexplore + if !exists("w:netrw_explore_list") " sanity check + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or improperly; see help for netrw-starstar",40) + if !has('nvim') && has("clipboard") && g:netrw_clipboard + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + sil! let @/ = keepregslash + return + endif + let indx= w:netrw_explore_indx + if indx < 0 | let indx= 0 | endif + if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif + let curfile= w:netrw_explore_list[indx] + while indx < w:netrw_explore_listlen && curfile == w:netrw_explore_list[indx] + let indx= indx + 1 + endwhile + if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif + + elseif indx == -2 + " Pexplore + if !exists("w:netrw_explore_list") " sanity check + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or improperly; see help for netrw-starstar",41) + if !has('nvim') && has("clipboard") && g:netrw_clipboard + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + sil! let @/ = keepregslash + return + endif + let indx= w:netrw_explore_indx + if indx < 0 | let indx= 0 | endif + if indx >= w:netrw_explore_listlen | let indx= w:netrw_explore_listlen - 1 | endif + let curfile= w:netrw_explore_list[indx] + while indx >= 0 && curfile == w:netrw_explore_list[indx] + let indx= indx - 1 + endwhile + if indx < 0 | let indx= 0 | endif + + else + " Explore -- initialize + " build list of files to Explore with Nexplore/Pexplore + NetrwKeepj keepalt call s:NetrwClearExplore() + let w:netrw_explore_indx= 0 + if !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + endif + + " switch on starpat to build the w:netrw_explore_list of files + if starpat == 1 + " starpat=1: Explore *//pattern (current directory only search for files containing pattern) + try + exe "NetrwKeepj noautocmd vimgrep /".pattern."/gj ".fnameescape(b:netrw_curdir)."/*" + catch /^Vim\%((\a\+)\)\=:E480/ + keepalt call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pattern.">",76) + return + endtry + let w:netrw_explore_list = s:NetrwExploreListUniq(map(getqflist(),'bufname(v:val.bufnr)')) + if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif + + elseif starpat == 2 + " starpat=2: Explore **//pattern (recursive descent search for files containing pattern) + try + exe "sil NetrwKeepj noautocmd keepalt vimgrep /".pattern."/gj "."**/*" + catch /^Vim\%((\a\+)\)\=:E480/ + keepalt call netrw#ErrorMsg(s:WARNING,'no files matched pattern<'.pattern.'>',45) + if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif + if !has('nvim') && has("clipboard") && g:netrw_clipboard + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + sil! let @/ = keepregslash + return + endtry + let s:netrw_curdir = b:netrw_curdir + let w:netrw_explore_list = getqflist() + let w:netrw_explore_list = s:NetrwExploreListUniq(map(w:netrw_explore_list,'s:netrw_curdir."/".bufname(v:val.bufnr)')) + if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif + + elseif starpat == 3 + " starpat=3: Explore */filepat (search in current directory for filenames matching filepat) + let filepat= substitute(dirname,'^\*/','','') + let filepat= substitute(filepat,'^[%#<]','\\&','') + let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".filepat),'\n')) + if &hls | let keepregslash= s:ExplorePatHls(filepat) | endif + + elseif starpat == 4 + " starpat=4: Explore **/filepat (recursive descent search for filenames matching filepat) + let w:netrw_explore_list= s:NetrwExploreListUniq(split(expand(b:netrw_curdir."/".dirname),'\n')) + if &hls | let keepregslash= s:ExplorePatHls(dirname) | endif + endif " switch on starpat to build w:netrw_explore_list + + let w:netrw_explore_listlen = len(w:netrw_explore_list) + + if w:netrw_explore_listlen == 0 || (w:netrw_explore_listlen == 1 && w:netrw_explore_list[0] =~ '\*\*\/') + keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no files matched",42) + if !has('nvim') && has("clipboard") && g:netrw_clipboard + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + sil! let @/ = keepregslash + return + endif + endif " if indx ... endif + + " NetrwStatusLine support - for exploring support + let w:netrw_explore_indx= indx + + " wrap the indx around, but issue a note + if indx >= w:netrw_explore_listlen || indx < 0 + let indx = (indx < 0)? ( w:netrw_explore_listlen - 1 ) : 0 + let w:netrw_explore_indx= indx + keepalt NetrwKeepj call netrw#ErrorMsg(s:NOTE,"no more files match Explore pattern",43) + endif + + exe "let dirfile= w:netrw_explore_list[".indx."]" + let newdir= substitute(dirfile,'/[^/]*$','','e') + + call netrw#LocalBrowseCheck(newdir) + if !exists("w:netrw_liststyle") + let w:netrw_liststyle= g:netrw_liststyle + endif + if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:LONGLIST + keepalt NetrwKeepj call search('^'.substitute(dirfile,"^.*/","","").'\>',"W") + else + keepalt NetrwKeepj call search('\<'.substitute(dirfile,"^.*/","","").'\>',"w") + endif + let w:netrw_explore_mtchcnt = indx + 1 + let w:netrw_explore_bufnr = bufnr("%") + let w:netrw_explore_line = line(".") + keepalt NetrwKeepj call s:SetupNetrwStatusLine('%f %h%m%r%=%9*%{NetrwStatusLine()}') + + else + if !exists("g:netrw_quiet") + keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44) + endif + if !has('nvim') && has("clipboard") && g:netrw_clipboard + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + sil! let @/ = keepregslash + return + endif + + else + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && dirname =~ '/' + sil! unlet w:netrw_treedict + sil! unlet w:netrw_treetop + endif + let newdir= dirname + if !exists("b:netrw_curdir") + NetrwKeepj call netrw#LocalBrowseCheck(getcwd()) + else + NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,newdir,0)) + endif + endif + + " visual display of **/ **// */ Exploration files + if exists("w:netrw_explore_indx") && exists("b:netrw_curdir") + if !exists("s:explore_prvdir") || s:explore_prvdir != b:netrw_curdir + " only update match list when current directory isn't the same as before + let s:explore_prvdir = b:netrw_curdir + let s:explore_match = "" + let dirlen = strlen(b:netrw_curdir) + if b:netrw_curdir !~ '/$' + let dirlen= dirlen + 1 + endif + let prvfname= "" + for fname in w:netrw_explore_list + if fname =~ '^'.b:netrw_curdir + if s:explore_match == "" + let s:explore_match= '\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>' + else + let s:explore_match= s:explore_match.'\|\<'.escape(strpart(fname,dirlen),g:netrw_markfileesc).'\>' + endif + elseif fname !~ '^/' && fname != prvfname + if s:explore_match == "" + let s:explore_match= '\<'.escape(fname,g:netrw_markfileesc).'\>' + else + let s:explore_match= s:explore_match.'\|\<'.escape(fname,g:netrw_markfileesc).'\>' + endif + endif + let prvfname= fname + endfor + if has("syntax") && exists("g:syntax_on") && g:syntax_on + exe "2match netrwMarkFile /".s:explore_match."/" + endif + endif + echo "==Pexplore ==Nexplore" + else + 2match none + if exists("s:explore_match") | unlet s:explore_match | endif + if exists("s:explore_prvdir") | unlet s:explore_prvdir | endif + endif + + " since Explore may be used to initialize netrw's browser, + " there's no danger of a late FocusGained event on initialization. + " Consequently, set s:netrw_events to 2. + let s:netrw_events= 2 + if !has('nvim') && has("clipboard") && g:netrw_clipboard + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + sil! let @/ = keepregslash +endfun + +" --------------------------------------------------------------------- +" netrw#Lexplore: toggle Explorer window, keeping it on the left of the current tab {{{2 +" Uses g:netrw_chgwin : specifies the window where Lexplore files are to be opened +" t:netrw_lexposn : winsaveview() output (used on Lexplore window) +" t:netrw_lexbufnr: the buffer number of the Lexplore buffer (internal to this function) +" s:lexplore_win : window number of Lexplore window (serves to indicate which window is a Lexplore window) +" w:lexplore_buf : buffer number of Lexplore window (serves to indicate which window is a Lexplore window) +fun! netrw#Lexplore(count,rightside,...) + " call Dfunc("netrw#Lexplore(count=".a:count." rightside=".a:rightside.",...) a:0=".a:0." ft=".&ft) + let curwin= winnr() + + if a:0 > 0 && a:1 != "" + " if a netrw window is already on the left-side of the tab + " and a directory has been specified, explore with that + " directory. + let a1 = expand(a:1) + exe "1wincmd w" + if &ft == "netrw" + exe "Explore ".fnameescape(a1) + exe curwin."wincmd w" + let s:lexplore_win= curwin + let w:lexplore_buf= bufnr("%") + if exists("t:netrw_lexposn") + unlet t:netrw_lexposn + endif + return + endif + exe curwin."wincmd w" + else + let a1= "" + endif + + if exists("t:netrw_lexbufnr") + " check if t:netrw_lexbufnr refers to a netrw window + let lexwinnr = bufwinnr(t:netrw_lexbufnr) + else + let lexwinnr= 0 + endif + + if lexwinnr > 0 + " close down netrw explorer window + exe lexwinnr."wincmd w" + let g:netrw_winsize = -winwidth(0) + let t:netrw_lexposn = winsaveview() + close + if lexwinnr < curwin + let curwin= curwin - 1 + endif + if lexwinnr != curwin + exe curwin."wincmd w" + endif + unlet t:netrw_lexbufnr + + else + " open netrw explorer window + exe "1wincmd w" + let keep_altv = g:netrw_altv + let g:netrw_altv = 0 + if a:count != 0 + let netrw_winsize = g:netrw_winsize + let g:netrw_winsize = a:count + endif + let curfile= expand("%") + exe (a:rightside? "botright" : "topleft")." vertical ".((g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize) . " new" + if a:0 > 0 && a1 != "" + call netrw#Explore(0,0,0,a1) + exe "Explore ".fnameescape(a1) + elseif curfile =~ '^\a\{3,}://' + call netrw#Explore(0,0,0,substitute(curfile,'[^/\\]*$','','')) + else + call netrw#Explore(0,0,0,".") + endif + if a:count != 0 + let g:netrw_winsize = netrw_winsize + endif + setlocal winfixwidth + let g:netrw_altv = keep_altv + let t:netrw_lexbufnr = bufnr("%") + " done to prevent build-up of hidden buffers due to quitting and re-invocation of :Lexplore. + " Since the intended use of :Lexplore is to have an always-present explorer window, the extra + " effort to prevent mis-use of :Lex is warranted. + set bh=wipe + if exists("t:netrw_lexposn") + call winrestview(t:netrw_lexposn) + unlet t:netrw_lexposn + endif + endif + + " set up default window for editing via + if exists("g:netrw_chgwin") && g:netrw_chgwin == -1 + if a:rightside + let g:netrw_chgwin= 1 + else + let g:netrw_chgwin= 2 + endif + endif + +endfun + +" --------------------------------------------------------------------- +" netrw#Clean: remove netrw {{{2 +" supports :NetrwClean -- remove netrw from first directory on runtimepath +" :NetrwClean! -- remove netrw from all directories on runtimepath +fun! netrw#Clean(sys) + " call Dfunc("netrw#Clean(sys=".a:sys.")") + + if a:sys + let choice= confirm("Remove personal and system copies of netrw?","&Yes\n&No") + else + let choice= confirm("Remove personal copy of netrw?","&Yes\n&No") + endif + " call Decho("choice=".choice,'~'.expand("")) + let diddel= 0 + let diddir= "" + + if choice == 1 + for dir in split(&rtp,',') + if filereadable(dir."/plugin/netrwPlugin.vim") + " call Decho("removing netrw-related files from ".dir,'~'.expand("")) + if s:NetrwDelete(dir."/plugin/netrwPlugin.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/plugin/netrwPlugin.vim",55) |endif + if s:NetrwDelete(dir."/autoload/netrwFileHandlers.vim")|call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwFileHandlers.vim",55)|endif + if s:NetrwDelete(dir."/autoload/netrwSettings.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrwSettings.vim",55) |endif + if s:NetrwDelete(dir."/autoload/netrw.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/autoload/netrw.vim",55) |endif + if s:NetrwDelete(dir."/syntax/netrw.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrw.vim",55) |endif + if s:NetrwDelete(dir."/syntax/netrwlist.vim") |call netrw#ErrorMsg(1,"unable to remove ".dir."/syntax/netrwlist.vim",55) |endif + let diddir= dir + let diddel= diddel + 1 + if !a:sys|break|endif + endif + endfor + endif + + echohl WarningMsg + if diddel == 0 + echomsg "netrw is either not installed or not removable" + elseif diddel == 1 + echomsg "removed one copy of netrw from <".diddir.">" + else + echomsg "removed ".diddel." copies of netrw" + endif + echohl None + + " call Dret("netrw#Clean") +endfun + +" --------------------------------------------------------------------- +" netrw#MakeTgt: make a target out of the directory name provided {{{2 +fun! netrw#MakeTgt(dname) + " call Dfunc("netrw#MakeTgt(dname<".a:dname.">)") + " simplify the target (eg. /abc/def/../ghi -> /abc/ghi) + let svpos = winsaveview() + " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) + let s:netrwmftgt_islocal= (a:dname !~ '^\a\{3,}://') + " call Decho("s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand("")) + if s:netrwmftgt_islocal + let netrwmftgt= simplify(a:dname) + else + let netrwmftgt= a:dname + endif + if exists("s:netrwmftgt") && netrwmftgt == s:netrwmftgt + " re-selected target, so just clear it + unlet s:netrwmftgt s:netrwmftgt_islocal + else + let s:netrwmftgt= netrwmftgt + endif + if g:netrw_fastbrowse <= 1 + call s:NetrwRefresh((b:netrw_curdir !~ '\a\{3,}://'),b:netrw_curdir) + endif + " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))" + call winrestview(svpos) + " call Dret("netrw#MakeTgt") +endfun + +" --------------------------------------------------------------------- +" netrw#Obtain: {{{2 +" netrw#Obtain(islocal,fname[,tgtdirectory]) +" islocal=0 obtain from remote source +" =1 obtain from local source +" fname : a filename or a list of filenames +" tgtdir : optional place where files are to go (not present, uses getcwd()) +fun! netrw#Obtain(islocal,fname,...) + " call Dfunc("netrw#Obtain(islocal=".a:islocal." fname<".((type(a:fname) == 1)? a:fname : string(a:fname)).">) a:0=".a:0) + " NetrwStatusLine support - for obtaining support + + if type(a:fname) == 1 + let fnamelist= [ a:fname ] + elseif type(a:fname) == 3 + let fnamelist= a:fname + else + call netrw#ErrorMsg(s:ERROR,"attempting to use NetrwObtain on something not a filename or a list",62) + " call Dret("netrw#Obtain") + return + endif + " call Decho("fnamelist<".string(fnamelist).">",'~'.expand("")) + if a:0 > 0 + let tgtdir= a:1 + else + let tgtdir= getcwd() + endif + " call Decho("tgtdir<".tgtdir.">",'~'.expand("")) + + if exists("b:netrw_islocal") && b:netrw_islocal + " obtain a file from local b:netrw_curdir to (local) tgtdir + " call Decho("obtain a file from local ".b:netrw_curdir." to ".tgtdir,'~'.expand("")) + if exists("b:netrw_curdir") && getcwd() != b:netrw_curdir + let topath= s:ComposePath(tgtdir,"") + if has("win32") + " transfer files one at time + " call Decho("transfer files one at a time",'~'.expand("")) + for fname in fnamelist + " call Decho("system(".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath).")",'~'.expand("")) + call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".s:ShellEscape(fname)." ".s:ShellEscape(topath)) + if v:shell_error != 0 + call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80) + " call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".s:ShellEscape(fname)." ".s:ShellEscape(topath)) + return + endif + endfor + else + " transfer files with one command + " call Decho("transfer files with one command",'~'.expand("")) + let filelist= join(map(deepcopy(fnamelist),"s:ShellEscape(v:val)")) + " call Decho("system(".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath).")",'~'.expand("")) + call system(g:netrw_localcopycmd.g:netrw_localcopycmdopt." ".filelist." ".s:ShellEscape(topath)) + if v:shell_error != 0 + call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localcopycmd<".g:netrw_localcopycmd."> to something that works",80) + " call Dret("s:NetrwObtain 0 : failed: ".g:netrw_localcopycmd." ".filelist." ".s:ShellEscape(topath)) + return + endif + endif + elseif !exists("b:netrw_curdir") + call netrw#ErrorMsg(s:ERROR,"local browsing directory doesn't exist!",36) + else + call netrw#ErrorMsg(s:WARNING,"local browsing directory and current directory are identical",37) + endif + + else + " obtain files from remote b:netrw_curdir to local tgtdir + " call Decho("obtain a file from remote ".b:netrw_curdir." to ".tgtdir,'~'.expand("")) + if type(a:fname) == 1 + call s:SetupNetrwStatusLine('%f %h%m%r%=%9*Obtaining '.a:fname) + endif + call s:NetrwMethod(b:netrw_curdir) + + if b:netrw_method == 4 + " obtain file using scp + " call Decho("obtain via scp (method#4)",'~'.expand("")) + if exists("g:netrw_port") && g:netrw_port != "" + let useport= " ".g:netrw_scpport." ".g:netrw_port + else + let useport= "" + endif + if b:netrw_fname =~ '/' + let path= substitute(b:netrw_fname,'^\(.*/\).\{-}$','\1','') + else + let path= "" + endif + let filelist= join(map(deepcopy(fnamelist),'escape(s:ShellEscape(g:netrw_machine.":".path.v:val,1)," ")')) + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".filelist." ".s:ShellEscape(tgtdir,1)) + + elseif b:netrw_method == 2 + " obtain file using ftp + .netrc + " call Decho("obtain via ftp+.netrc (method #2)",'~'.expand("")) + call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars() + let tmpbufnr= bufnr("%") + setl ff=unix + if exists("g:netrw_ftpmode") && g:netrw_ftpmode != "" + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("b:netrw_fname") && b:netrw_fname != "" + call setline(line("$")+1,'cd "'.b:netrw_fname.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + for fname in fnamelist + call setline(line("$")+1,'get "'.fname.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endfor + if exists("g:netrw_port") && g:netrw_port != "" + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) + else + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) + endif + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' + let debugkeep= &debug + setl debug=msg + call netrw#ErrorMsg(s:ERROR,getline(1),4) + let &debug= debugkeep + endif + + elseif b:netrw_method == 3 + " obtain with ftp + machine, id, passwd, and fname (ie. no .netrc) + " call Decho("obtain via ftp+mipf (method #3)",'~'.expand("")) + call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars() + let tmpbufnr= bufnr("%") + setl ff=unix + + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + " call Decho("filter input: ".getline('$'),'~'.expand("")) + else + NetrwKeepj put ='open '.g:netrw_machine + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("g:netrw_uid") && g:netrw_uid != "" + if exists("g:netrw_ftp") && g:netrw_ftp == 1 + NetrwKeepj put =g:netrw_uid + " call Decho("filter input: ".getline('$'),'~'.expand("")) + if exists("s:netrw_passwd") && s:netrw_passwd != "" + NetrwKeepj put ='\"'.s:netrw_passwd.'\"' + endif + " call Decho("filter input: ".getline('$'),'~'.expand("")) + elseif exists("s:netrw_passwd") + NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + endif + + if exists("g:netrw_ftpmode") && g:netrw_ftpmode != "" + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("b:netrw_fname") && b:netrw_fname != "" + NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + for fname in fnamelist + NetrwKeepj call setline(line("$")+1,'get "'.fname.'"') + endfor + " call Decho("filter input: ".getline('$'),'~'.expand("")) + + " perform ftp: + " -i : turns off interactive prompting from ftp + " -n unix : DON'T use <.netrc>, even though it exists + " -n win32: quit being obnoxious about password + " Note: using "_dd to delete to the black hole register; avoids messing up @@ + NetrwKeepj norm! 1G"_dd + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + if getline(1) !~ "^$" + " call Decho("error<".getline(1).">",'~'.expand("")) + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),5) + endif + endif + + elseif b:netrw_method == 9 + " obtain file using sftp + " call Decho("obtain via sftp (method #9)",'~'.expand("")) + if a:fname =~ '/' + let localfile= substitute(a:fname,'^.*/','','') + else + let localfile= a:fname + endif + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1).s:ShellEscape(localfile)." ".s:ShellEscape(tgtdir)) + + elseif !exists("b:netrw_method") || b:netrw_method < 0 + " probably a badly formed url; protocol not recognized + " call Dret("netrw#Obtain : unsupported method") + return + + else + " protocol recognized but not supported for Obtain (yet?) + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"current protocol not supported for obtaining file",97) + endif + " call Dret("netrw#Obtain : current protocol not supported for obtaining file") + return + endif + + " restore status line + if type(a:fname) == 1 && exists("s:netrw_users_stl") + NetrwKeepj call s:SetupNetrwStatusLine(s:netrw_users_stl) + endif + + endif + + " cleanup + if exists("tmpbufnr") + if bufnr("%") != tmpbufnr + exe tmpbufnr."bw!" + else + q! + endif + endif + + " call Dret("netrw#Obtain") +endfun + +" --------------------------------------------------------------------- +" netrw#Nread: save position, call netrw#NetRead(), and restore position {{{2 +fun! netrw#Nread(mode,fname) + " call Dfunc("netrw#Nread(mode=".a:mode." fname<".a:fname.">)") + let svpos= winsaveview() + " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) + call netrw#NetRead(a:mode,a:fname) + " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("")) + call winrestview(svpos) + + if exists("w:netrw_liststyle") && w:netrw_liststyle != s:TREELIST + if exists("w:netrw_bannercnt") + " start with cursor just after the banner + exe w:netrw_bannercnt + endif + endif + " call Dret("netrw#Nread") +endfun + +" ------------------------------------------------------------------------ +" s:NetrwOptionsSave: save options prior to setting to "netrw-buffer-standard" form {{{2 +" Options get restored by s:NetrwOptionsRestore() +" +" Option handling: +" * save user's options (s:NetrwOptionsSave) +" * set netrw-safe options (s:NetrwOptionsSafe) +" - change an option only when user option != safe option (s:netrwSetSafeSetting) +" * restore user's options (s:netrwOPtionsRestore) +" - restore a user option when != safe option (s:NetrwRestoreSetting) +" vt: (variable type) normally its either "w:" or "s:" +fun! s:NetrwOptionsSave(vt) + " call Dfunc("s:NetrwOptionsSave(vt<".a:vt.">) win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%")).">"." winnr($)=".winnr("$")." mod=".&mod." ma=".&ma) + " call Decho(a:vt."netrw_optionsave".(exists("{a:vt}netrw_optionsave")? ("=".{a:vt}netrw_optionsave) : " doesn't exist"),'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt." hid=".&hid,'~'.expand("")) + " call Decho("(s:NetrwOptionsSave) lines=".&lines) + + if !exists("{a:vt}netrw_optionsave") + let {a:vt}netrw_optionsave= 1 + else + " call Dret("s:NetrwOptionsSave : options already saved") + return + endif + " call Decho("prior to save: fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." diff=".&l:diff,'~'.expand("")) + + " Save current settings and current directory + " call Decho("saving current settings and current directory",'~'.expand("")) + let s:yykeep = @@ + if exists("&l:acd")|let {a:vt}netrw_acdkeep = &l:acd|endif + let {a:vt}netrw_aikeep = &l:ai + let {a:vt}netrw_awkeep = &l:aw + let {a:vt}netrw_bhkeep = &l:bh + let {a:vt}netrw_blkeep = &l:bl + let {a:vt}netrw_btkeep = &l:bt + let {a:vt}netrw_bombkeep = &l:bomb + let {a:vt}netrw_cedit = &cedit + let {a:vt}netrw_cikeep = &l:ci + let {a:vt}netrw_cinkeep = &l:cin + let {a:vt}netrw_cinokeep = &l:cino + let {a:vt}netrw_comkeep = &l:com + let {a:vt}netrw_cpokeep = &l:cpo + let {a:vt}netrw_cuckeep = &l:cuc + let {a:vt}netrw_culkeep = &l:cul + " call Decho("(s:NetrwOptionsSave) COMBAK: cuc=".&l:cuc." cul=".&l:cul) + let {a:vt}netrw_diffkeep = &l:diff + let {a:vt}netrw_fenkeep = &l:fen + if !exists("g:netrw_ffkeep") || g:netrw_ffkeep + let {a:vt}netrw_ffkeep = &l:ff + endif + let {a:vt}netrw_fokeep = &l:fo " formatoptions + let {a:vt}netrw_gdkeep = &l:gd " gdefault + let {a:vt}netrw_gokeep = &go " guioptions + let {a:vt}netrw_hidkeep = &l:hidden + let {a:vt}netrw_imkeep = &l:im + let {a:vt}netrw_iskkeep = &l:isk + let {a:vt}netrw_lines = &lines + let {a:vt}netrw_lskeep = &l:ls + let {a:vt}netrw_makeep = &l:ma + let {a:vt}netrw_magickeep = &l:magic + let {a:vt}netrw_modkeep = &l:mod + let {a:vt}netrw_nukeep = &l:nu + let {a:vt}netrw_rnukeep = &l:rnu + let {a:vt}netrw_repkeep = &l:report + let {a:vt}netrw_rokeep = &l:ro + let {a:vt}netrw_selkeep = &l:sel + let {a:vt}netrw_spellkeep = &l:spell + if !g:netrw_use_noswf + let {a:vt}netrw_swfkeep = &l:swf + endif + let {a:vt}netrw_tskeep = &l:ts + let {a:vt}netrw_twkeep = &l:tw " textwidth + let {a:vt}netrw_wigkeep = &l:wig " wildignore + let {a:vt}netrw_wrapkeep = &l:wrap + let {a:vt}netrw_writekeep = &l:write + + " save a few selected netrw-related variables + " call Decho("saving a few selected netrw-related variables",'~'.expand("")) + if g:netrw_keepdir + let {a:vt}netrw_dirkeep = getcwd() + " call Decho("saving to ".a:vt."netrw_dirkeep<".{a:vt}netrw_dirkeep.">",'~'.expand("")) + endif + if !has('nvim') && has("clipboard") && g:netrw_clipboard + sil! let {a:vt}netrw_starkeep = @* + sil! let {a:vt}netrw_pluskeep = @+ + endif + sil! let {a:vt}netrw_slashkeep= @/ + + " call Decho("(s:NetrwOptionsSave) lines=".&lines) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("")) + " call Dret("s:NetrwOptionsSave : tab#".tabpagenr()." win#".winnr()) +endfun + +" --------------------------------------------------------------------- +" s:NetrwOptionsSafe: sets options to help netrw do its job {{{2 +" Use s:NetrwSaveOptions() to save user settings +" Use s:NetrwOptionsRestore() to restore user settings +fun! s:NetrwOptionsSafe(islocal) + " call Dfunc("s:NetrwOptionsSafe(islocal=".a:islocal.") win#".winnr()." buf#".bufnr("%")."<".bufname(bufnr("%"))."> winnr($)=".winnr("$")) + " call Decho("win#".winnr()."'s ft=".&ft,'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + if exists("+acd") | call s:NetrwSetSafeSetting("&l:acd",0)|endif + call s:NetrwSetSafeSetting("&l:ai",0) + call s:NetrwSetSafeSetting("&l:aw",0) + call s:NetrwSetSafeSetting("&l:bl",0) + call s:NetrwSetSafeSetting("&l:bomb",0) + if a:islocal + call s:NetrwSetSafeSetting("&l:bt","nofile") + else + call s:NetrwSetSafeSetting("&l:bt","acwrite") + endif + call s:NetrwSetSafeSetting("&l:ci",0) + call s:NetrwSetSafeSetting("&l:cin",0) + if g:netrw_fastbrowse > a:islocal + call s:NetrwSetSafeSetting("&l:bh","hide") + else + call s:NetrwSetSafeSetting("&l:bh","delete") + endif + call s:NetrwSetSafeSetting("&l:cino","") + call s:NetrwSetSafeSetting("&l:com","") + if &cpo =~ 'a' | call s:NetrwSetSafeSetting("&cpo",substitute(&cpo,'a','','g')) | endif + if &cpo =~ 'A' | call s:NetrwSetSafeSetting("&cpo",substitute(&cpo,'A','','g')) | endif + setl fo=nroql2 + if &go =~ 'a' | set go-=a | endif + if &go =~ 'A' | set go-=A | endif + if &go =~ 'P' | set go-=P | endif + call s:NetrwSetSafeSetting("&l:hid",0) + call s:NetrwSetSafeSetting("&l:im",0) + setl isk+=@ isk+=* isk+=/ + call s:NetrwSetSafeSetting("&l:magic",1) + if g:netrw_use_noswf + call s:NetrwSetSafeSetting("swf",0) + endif + call s:NetrwSetSafeSetting("&l:report",10000) + call s:NetrwSetSafeSetting("&l:sel","inclusive") + call s:NetrwSetSafeSetting("&l:spell",0) + call s:NetrwSetSafeSetting("&l:tw",0) + call s:NetrwSetSafeSetting("&l:wig","") + setl cedit& + + " set up cuc and cul based on g:netrw_cursor and listing style + " COMBAK -- cuc cul related + call s:NetrwCursor(0) + + " allow the user to override safe options + " call Decho("ft<".&ft."> ei=".&ei,'~'.expand("")) + if &ft == "netrw" + " call Decho("do any netrw FileType autocmds (doau FileType netrw)",'~'.expand("")) + keepalt NetrwKeepj doau FileType netrw + endif + + " call Decho("fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." bh=".&l:bh." bt<".&bt.">",'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Dret("s:NetrwOptionsSafe") +endfun + +" --------------------------------------------------------------------- +" s:NetrwOptionsRestore: restore options (based on prior s:NetrwOptionsSave) {{{2 +fun! s:NetrwOptionsRestore(vt) + if !exists("{a:vt}netrw_optionsave") + " filereadable() returns zero for remote files (e.g. scp://user@localhost//etc/fstab) + " Note: @ may not be in 'isfname', so '^\w\+://\f\+/' may not match + if filereadable(expand("%")) || expand("%") =~# '^\w\+://\f\+' + filetype detect + else + setl ft=netrw + endif + return + endif + unlet {a:vt}netrw_optionsave + + if exists("+acd") + if exists("{a:vt}netrw_acdkeep") + let curdir = getcwd() + let &l:acd = {a:vt}netrw_acdkeep + unlet {a:vt}netrw_acdkeep + if &l:acd + call s:NetrwLcd(curdir) + endif + endif + endif + call s:NetrwRestoreSetting(a:vt."netrw_aikeep","&l:ai") + call s:NetrwRestoreSetting(a:vt."netrw_awkeep","&l:aw") + call s:NetrwRestoreSetting(a:vt."netrw_blkeep","&l:bl") + call s:NetrwRestoreSetting(a:vt."netrw_btkeep","&l:bt") + call s:NetrwRestoreSetting(a:vt."netrw_bombkeep","&l:bomb") + call s:NetrwRestoreSetting(a:vt."netrw_cedit","&cedit") + call s:NetrwRestoreSetting(a:vt."netrw_cikeep","&l:ci") + call s:NetrwRestoreSetting(a:vt."netrw_cinkeep","&l:cin") + call s:NetrwRestoreSetting(a:vt."netrw_cinokeep","&l:cino") + call s:NetrwRestoreSetting(a:vt."netrw_comkeep","&l:com") + call s:NetrwRestoreSetting(a:vt."netrw_cpokeep","&l:cpo") + call s:NetrwRestoreSetting(a:vt."netrw_diffkeep","&l:diff") + call s:NetrwRestoreSetting(a:vt."netrw_fenkeep","&l:fen") + if exists("g:netrw_ffkeep") && g:netrw_ffkeep + call s:NetrwRestoreSetting(a:vt."netrw_ffkeep")","&l:ff") + endif + call s:NetrwRestoreSetting(a:vt."netrw_fokeep" ,"&l:fo") + call s:NetrwRestoreSetting(a:vt."netrw_gdkeep" ,"&l:gd") + call s:NetrwRestoreSetting(a:vt."netrw_gokeep" ,"&go") + call s:NetrwRestoreSetting(a:vt."netrw_hidkeep" ,"&l:hidden") + call s:NetrwRestoreSetting(a:vt."netrw_imkeep" ,"&l:im") + call s:NetrwRestoreSetting(a:vt."netrw_iskkeep" ,"&l:isk") + call s:NetrwRestoreSetting(a:vt."netrw_lines" ,"&lines") + call s:NetrwRestoreSetting(a:vt."netrw_lskeep" ,"&l:ls") + call s:NetrwRestoreSetting(a:vt."netrw_makeep" ,"&l:ma") + call s:NetrwRestoreSetting(a:vt."netrw_magickeep","&l:magic") + call s:NetrwRestoreSetting(a:vt."netrw_modkeep" ,"&l:mod") + call s:NetrwRestoreSetting(a:vt."netrw_nukeep" ,"&l:nu") + call s:NetrwRestoreSetting(a:vt."netrw_rnukeep" ,"&l:rnu") + call s:NetrwRestoreSetting(a:vt."netrw_repkeep" ,"&l:report") + call s:NetrwRestoreSetting(a:vt."netrw_rokeep" ,"&l:ro") + call s:NetrwRestoreSetting(a:vt."netrw_selkeep" ,"&l:sel") + call s:NetrwRestoreSetting(a:vt."netrw_spellkeep","&l:spell") + call s:NetrwRestoreSetting(a:vt."netrw_twkeep" ,"&l:tw") + call s:NetrwRestoreSetting(a:vt."netrw_wigkeep" ,"&l:wig") + call s:NetrwRestoreSetting(a:vt."netrw_wrapkeep" ,"&l:wrap") + call s:NetrwRestoreSetting(a:vt."netrw_writekeep","&l:write") + call s:NetrwRestoreSetting("s:yykeep","@@") + " former problem: start with liststyle=0; press : result, following line resets l:ts. + " Fixed; in s:PerformListing, when w:netrw_liststyle is s:LONGLIST, will use a printf to pad filename with spaces + " rather than by appending a tab which previously was using "&ts" to set the desired spacing. (Sep 28, 2018) + call s:NetrwRestoreSetting(a:vt."netrw_tskeep","&l:ts") + + if exists("{a:vt}netrw_swfkeep") + if &directory == "" + " user hasn't specified a swapfile directory; + " netrw will temporarily set the swapfile directory + " to the current directory as returned by getcwd(). + let &l:directory= getcwd() + sil! let &l:swf = {a:vt}netrw_swfkeep + setl directory= + unlet {a:vt}netrw_swfkeep + elseif &l:swf != {a:vt}netrw_swfkeep + if !g:netrw_use_noswf + " following line causes a Press ENTER in windows -- can't seem to work around it!!! + sil! let &l:swf= {a:vt}netrw_swfkeep + endif + unlet {a:vt}netrw_swfkeep + endif + endif + if exists("{a:vt}netrw_dirkeep") && isdirectory(s:NetrwFile({a:vt}netrw_dirkeep)) && g:netrw_keepdir + let dirkeep = substitute({a:vt}netrw_dirkeep,'\\','/','g') + if exists("{a:vt}netrw_dirkeep") + call s:NetrwLcd(dirkeep) + unlet {a:vt}netrw_dirkeep + endif + endif + if !has('nvim') && has("clipboard") && g:netrw_clipboard + call s:NetrwRestoreSetting(a:vt."netrw_starkeep","@*") + call s:NetrwRestoreSetting(a:vt."netrw_pluskeep","@+") + endif + call s:NetrwRestoreSetting(a:vt."netrw_slashkeep","@/") + + " Moved the filetype detect here from NetrwGetFile() because remote files + " were having their filetype detect-generated settings overwritten by + " NetrwOptionRestore. + if &ft != "netrw" + filetype detect + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwSetSafeSetting: sets an option to a safe setting {{{2 +" but only when the options' value and the safe setting differ +" Doing this means that netrw will not come up as having changed a +" setting last when it really didn't actually change it. +" +" Called from s:NetrwOptionsSafe +" ex. call s:NetrwSetSafeSetting("&l:sel","inclusive") +fun! s:NetrwSetSafeSetting(setting,safesetting) + " call Dfunc("s:NetrwSetSafeSetting(setting<".a:setting."> safesetting<".a:safesetting.">)") + + if a:setting =~ '^&' + " call Decho("fyi: a:setting starts with &") + exe "let settingval= ".a:setting + " call Decho("fyi: settingval<".settingval.">") + + if settingval != a:safesetting + " call Decho("set setting<".a:setting."> to option value<".a:safesetting.">") + if type(a:safesetting) == 0 + exe "let ".a:setting."=".a:safesetting + elseif type(a:safesetting) == 1 + exe "let ".a:setting."= '".a:safesetting."'" + else + call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:setting." with a safesetting of type#".type(a:safesetting),105) + endif + endif + endif + + " call Dret("s:NetrwSetSafeSetting") +endfun + +" ------------------------------------------------------------------------ +" s:NetrwRestoreSetting: restores specified setting using associated keepvar, {{{2 +" but only if the setting value differs from the associated keepvar. +" Doing this means that netrw will not come up as having changed a +" setting last when it really didn't actually change it. +" +" Used by s:NetrwOptionsRestore() to restore each netrw-sensitive setting +" keepvars are set up by s:NetrwOptionsSave +fun! s:NetrwRestoreSetting(keepvar,setting) + """ call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)") + + " typically called from s:NetrwOptionsRestore + " call s:NetrwRestoreSettings(keep-option-variable-name,'associated-option') + " ex. call s:NetrwRestoreSetting(a:vt."netrw_selkeep","&l:sel") + " Restores option (but only if different) from a:keepvar + if exists(a:keepvar) + exe "let keepvarval= ".a:keepvar + exe "let setting= ".a:setting + + "" call Decho("fyi: a:keepvar<".a:keepvar."> exists") + "" call Decho("fyi: keepvarval=".keepvarval) + "" call Decho("fyi: a:setting<".a:setting."> setting<".setting.">") + + if setting != keepvarval + "" call Decho("restore setting<".a:setting."> (currently=".setting.") to keepvarval<".keepvarval.">") + if type(a:setting) == 0 + exe "let ".a:setting."= ".keepvarval + elseif type(a:setting) == 1 + exe "let ".a:setting."= '".substitute(keepvarval,"'","''","g")."'" + else + call netrw#ErrorMsg(s:ERROR,"(s:NetrwRestoreSetting) doesn't know how to restore ".a:keepvar." with a setting of type#".type(a:setting),105) + endif + endif + + exe "unlet ".a:keepvar + endif + + "" call Dret("s:NetrwRestoreSetting") +endfun + +" --------------------------------------------------------------------- +" NetrwStatusLine: {{{2 +fun! NetrwStatusLine() + + " vvv NetrwStatusLine() debugging vvv + " let g:stlmsg="" + " if !exists("w:netrw_explore_bufnr") + " let g:stlmsg="!X" + " elseif w:netrw_explore_bufnr != bufnr("%") + " let g:stlmsg="explore_bufnr!=".bufnr("%") + " endif + " if !exists("w:netrw_explore_line") + " let g:stlmsg=" !X" + " elseif w:netrw_explore_line != line(".") + " let g:stlmsg=" explore_line!={line(.)<".line(".").">" + " endif + " if !exists("w:netrw_explore_list") + " let g:stlmsg=" !X" + " endif + " ^^^ NetrwStatusLine() debugging ^^^ + + if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list") + " restore user's status line + let &l:stl = s:netrw_users_stl + let &laststatus = s:netrw_users_ls + if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif + if exists("w:netrw_explore_line") |unlet w:netrw_explore_line |endif + return "" + else + return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen + endif +endfun + +" =============================== +" Netrw Transfer Functions: {{{1 +" =============================== + +" ------------------------------------------------------------------------ +" netrw#NetRead: responsible for reading a file over the net {{{2 +" mode: =0 read remote file and insert before current line +" =1 read remote file and insert after current line +" =2 replace with remote file +" =3 obtain file, but leave in temporary format +fun! netrw#NetRead(mode,...) + " call Dfunc("netrw#NetRead(mode=".a:mode.",...) a:0=".a:0." ".g:loaded_netrw.((a:0 > 0)? " a:1<".a:1.">" : "")) + + " NetRead: save options {{{3 + call s:NetrwOptionsSave("w:") + call s:NetrwOptionsSafe(0) + call s:RestoreCursorline() + " NetrwSafeOptions sets a buffer up for a netrw listing, which includes buflisting off. + " However, this setting is not wanted for a remote editing session. The buffer should be "nofile", still. + setl bl + " call Decho("buf#".bufnr("%")."<".bufname("%")."> bl=".&bl." bt=".&bt." bh=".&bh,'~'.expand("")) + + " NetRead: interpret mode into a readcmd {{{3 + if a:mode == 0 " read remote file before current line + let readcmd = "0r" + elseif a:mode == 1 " read file after current line + let readcmd = "r" + elseif a:mode == 2 " replace with remote file + let readcmd = "%r" + elseif a:mode == 3 " skip read of file (leave as temporary) + let readcmd = "t" + else + exe a:mode + let readcmd = "r" + endif + let ichoice = (a:0 == 0)? 0 : 1 + " call Decho("readcmd<".readcmd."> ichoice=".ichoice,'~'.expand("")) + + " NetRead: get temporary filename {{{3 + let tmpfile= s:GetTempfile("") + if tmpfile == "" + " call Dret("netrw#NetRead : unable to get a tempfile!") + return + endif + + while ichoice <= a:0 + + " attempt to repeat with previous host-file-etc + if exists("b:netrw_lastfile") && a:0 == 0 + " call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand("")) + let choice = b:netrw_lastfile + let ichoice= ichoice + 1 + + else + exe "let choice= a:" . ichoice + " call Decho("no lastfile: choice<" . choice . ">",'~'.expand("")) + + if match(choice,"?") == 0 + " give help + echomsg 'NetRead Usage:' + echomsg ':Nread machine:path uses rcp' + echomsg ':Nread "machine path" uses ftp with <.netrc>' + echomsg ':Nread "machine id password path" uses ftp' + echomsg ':Nread dav://machine[:port]/path uses cadaver' + echomsg ':Nread fetch://machine/path uses fetch' + echomsg ':Nread ftp://[user@]machine[:port]/path uses ftp autodetects <.netrc>' + echomsg ':Nread http://[user@]machine/path uses http wget' + echomsg ':Nread file:///path uses elinks' + echomsg ':Nread https://[user@]machine/path uses http wget' + echomsg ':Nread rcp://[user@]machine/path uses rcp' + echomsg ':Nread rsync://machine[:port]/path uses rsync' + echomsg ':Nread scp://[user@]machine[[:#]port]/path uses scp' + echomsg ':Nread sftp://[user@]machine[[:#]port]/path uses sftp' + sleep 4 + break + + elseif match(choice,'^"') != -1 + " Reconstruct Choice if choice starts with '"' + " call Decho("reconstructing choice",'~'.expand("")) + if match(choice,'"$') != -1 + " case "..." + let choice= strpart(choice,1,strlen(choice)-2) + else + " case "... ... ..." + let choice = strpart(choice,1,strlen(choice)-1) + let wholechoice = "" + + while match(choice,'"$') == -1 + let wholechoice = wholechoice . " " . choice + let ichoice = ichoice + 1 + if ichoice > a:0 + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",3) + endif + " call Dret("netrw#NetRead :2 getcwd<".getcwd().">") + return + endif + let choice= a:{ichoice} + endwhile + let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1) + endif + endif + endif + + " call Decho("choice<" . choice . ">",'~'.expand("")) + let ichoice= ichoice + 1 + + " NetRead: Determine method of read (ftp, rcp, etc) {{{3 + call s:NetrwMethod(choice) + if !exists("b:netrw_method") || b:netrw_method < 0 + " call Dret("netrw#NetRead : unsupported method") + return + endif + let tmpfile= s:GetTempfile(b:netrw_fname) " apply correct suffix + + " Check whether or not NetrwBrowse() should be handling this request + " call Decho("checking if NetrwBrowse() should handle choice<".choice."> with netrw_list_cmd<".g:netrw_list_cmd.">",'~'.expand("")) + if choice =~ "^.*[\/]$" && b:netrw_method != 5 && choice !~ '^https\=://' + " call Decho("yes, choice matches '^.*[\/]$'",'~'.expand("")) + NetrwKeepj call s:NetrwBrowse(0,choice) + " call Dret("netrw#NetRead :3 getcwd<".getcwd().">") + return + endif + + " ============ + " NetRead: Perform Protocol-Based Read {{{3 + " =========================== + if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1 + echo "(netrw) Processing your read request..." + endif + + "......................................... + " NetRead: (rcp) NetRead Method #1 {{{3 + if b:netrw_method == 1 " read with rcp + " call Decho("read via rcp (method #1)",'~'.expand("")) + " ER: nothing done with g:netrw_uid yet? + " ER: on Win2K" rcp machine[.user]:file tmpfile + " ER: when machine contains '.' adding .user is required (use $USERNAME) + " ER: the tmpfile is full path: rcp sees C:\... as host C + if s:netrw_has_nt_rcp == 1 + if exists("g:netrw_uid") && ( g:netrw_uid != "" ) + let uid_machine = g:netrw_machine .'.'. g:netrw_uid + else + " Any way needed it machine contains a '.' + let uid_machine = g:netrw_machine .'.'. $USERNAME + endif + else + if exists("g:netrw_uid") && ( g:netrw_uid != "" ) + let uid_machine = g:netrw_uid .'@'. g:netrw_machine + else + let uid_machine = g:netrw_machine + endif + endif + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1)) + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (ftp + <.netrc>) NetRead Method #2 {{{3 + elseif b:netrw_method == 2 " read with ftp + <.netrc> + " call Decho("read via ftp+.netrc (method #2)",'~'.expand("")) + let netrw_fname= b:netrw_fname + NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars() + let filtbuf= bufnr("%") + setl ff=unix + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline(line("$")),'~'.expand("")) + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline(line("$")),'~'.expand("")) + endif + call setline(line("$")+1,'get "'.netrw_fname.'" '.tmpfile) + " call Decho("filter input: ".getline(line("$")),'~'.expand("")) + if exists("g:netrw_port") && g:netrw_port != "" + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) + else + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) + endif + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' + let debugkeep = &debug + setl debug=msg + NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),4) + let &debug = debugkeep + endif + call s:SaveBufVars() + keepj bd! + if bufname("%") == "" && getline("$") == "" && line('$') == 1 + " needed when one sources a file in a nolbl setting window via ftp + q! + endif + call s:RestoreBufVars() + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (ftp + machine,id,passwd,filename) NetRead Method #3 {{{3 + elseif b:netrw_method == 3 " read with ftp + machine, id, passwd, and fname + " Construct execution string (four lines) which will be passed through filter + " call Decho("read via ftp+mipf (method #3)",'~'.expand("")) + let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape) + NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars() + let filtbuf= bufnr("%") + setl ff=unix + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + " call Decho("filter input: ".getline('.'),'~'.expand("")) + else + NetrwKeepj put ='open '.g:netrw_machine + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + + if exists("g:netrw_uid") && g:netrw_uid != "" + if exists("g:netrw_ftp") && g:netrw_ftp == 1 + NetrwKeepj put =g:netrw_uid + " call Decho("filter input: ".getline('.'),'~'.expand("")) + if exists("s:netrw_passwd") + NetrwKeepj put ='\"'.s:netrw_passwd.'\"' + endif + " call Decho("filter input: ".getline('.'),'~'.expand("")) + elseif exists("s:netrw_passwd") + NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + endif + + if exists("g:netrw_ftpmode") && g:netrw_ftpmode != "" + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + NetrwKeepj put ='get \"'.netrw_fname.'\" '.tmpfile + " call Decho("filter input: ".getline('.'),'~'.expand("")) + + " perform ftp: + " -i : turns off interactive prompting from ftp + " -n unix : DON'T use <.netrc>, even though it exists + " -n win32: quit being obnoxious about password + NetrwKeepj norm! 1G"_dd + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + if getline(1) !~ "^$" + " call Decho("error<".getline(1).">",'~'.expand("")) + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,getline(1),5) + endif + endif + call s:SaveBufVars()|keepj bd!|call s:RestoreBufVars() + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (scp) NetRead Method #4 {{{3 + elseif b:netrw_method == 4 " read with scp + " call Decho("read via scp (method #4)",'~'.expand("")) + if exists("g:netrw_port") && g:netrw_port != "" + let useport= " ".g:netrw_scpport." ".g:netrw_port + else + let useport= "" + endif + " 'C' in 'C:\path\to\file' is handled as hostname on windows. + " This is workaround to avoid mis-handle windows local-path: + if g:netrw_scp_cmd =~ '^scp' && has("win32") + let tmpfile_get = substitute(tr(tmpfile, '\', '/'), '^\(\a\):[/\\]\(.*\)$', '/\1/\2', '') + else + let tmpfile_get = tmpfile + endif + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".escape(s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1),' ')." ".s:ShellEscape(tmpfile_get,1)) + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (http) NetRead Method #5 (wget) {{{3 + elseif b:netrw_method == 5 + " call Decho("read via http (method #5)",'~'.expand("")) + if g:netrw_http_cmd == "" + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,"neither the wget nor the fetch command is available",6) + endif + " call Dret("netrw#NetRead :4 getcwd<".getcwd().">") + return + endif + + if match(b:netrw_fname,"#") == -1 || exists("g:netrw_http_xcmd") + " using g:netrw_http_cmd (usually elinks, links, curl, wget, or fetch) + " call Decho('using '.g:netrw_http_cmd.' (# not in b:netrw_fname<'.b:netrw_fname.">)",'~'.expand("")) + if exists("g:netrw_http_xcmd") + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".s:ShellEscape(tmpfile,1)) + else + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)) + endif + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + + else + " wget/curl/fetch plus a jump to an in-page marker (ie. http://abc/def.html#aMarker) + " call Decho("wget/curl plus jump (# in b:netrw_fname<".b:netrw_fname.">)",'~'.expand("")) + let netrw_html= substitute(b:netrw_fname,"#.*$","","") + let netrw_tag = substitute(b:netrw_fname,"^.*#","","") + " call Decho("netrw_html<".netrw_html.">",'~'.expand("")) + " call Decho("netrw_tag <".netrw_tag.">",'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(b:netrw_http."://".g:netrw_machine.netrw_html,1)) + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + " call Decho('<\s*a\s*name=\s*"'.netrw_tag.'"/','~'.expand("")) + exe 'NetrwKeepj norm! 1G/<\s*a\s*name=\s*"'.netrw_tag.'"/'."\" + endif + let b:netrw_lastfile = choice + " call Decho("setl ro",'~'.expand("")) + setl ro nomod + + "......................................... + " NetRead: (dav) NetRead Method #6 {{{3 + elseif b:netrw_method == 6 + " call Decho("read via cadaver (method #6)",'~'.expand("")) + + if !executable(g:netrw_dav_cmd) + call netrw#ErrorMsg(s:ERROR,g:netrw_dav_cmd." is not executable",73) + " call Dret("netrw#NetRead : ".g:netrw_dav_cmd." not executable") + return + endif + if g:netrw_dav_cmd =~ "curl" + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_dav_cmd." ".s:ShellEscape("dav://".g:netrw_machine.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1)) + else + " Construct execution string (four lines) which will be passed through filter + let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape) + new + setl ff=unix + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + else + NetrwKeepj put ='open '.g:netrw_machine + endif + if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != "" + NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd + endif + NetrwKeepj put ='get '.netrw_fname.' '.tmpfile + NetrwKeepj put ='quit' + + " perform cadaver operation: + NetrwKeepj norm! 1G"_dd + call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd) + keepj bd! + endif + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (rsync) NetRead Method #7 {{{3 + elseif b:netrw_method == 7 + " call Decho("read via rsync (method #7)",'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)." ".s:ShellEscape(tmpfile,1)) + let result = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (fetch) NetRead Method #8 {{{3 + " fetch://[user@]host[:http]/path + elseif b:netrw_method == 8 + " call Decho("read via fetch (method #8)",'~'.expand("")) + if g:netrw_fetch_cmd == "" + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"fetch command not available",7) + endif + " call Dret("NetRead") + return + endif + if exists("g:netrw_option") && g:netrw_option =~ ":https\=" + let netrw_option= "http" + else + let netrw_option= "ftp" + endif + " call Decho("read via fetch for ".netrw_option,'~'.expand("")) + + if exists("g:netrw_uid") && g:netrw_uid != "" && exists("s:netrw_passwd") && s:netrw_passwd != "" + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1)) + else + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1)) + endif + + let result = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + " call Decho("setl ro",'~'.expand("")) + setl ro nomod + + "......................................... + " NetRead: (sftp) NetRead Method #9 {{{3 + elseif b:netrw_method == 9 + " call Decho("read via sftp (method #9)",'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile) + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: (file) NetRead Method #10 {{{3 + elseif b:netrw_method == 10 && exists("g:netrw_file_cmd") + " " call Decho("read via ".b:netrw_file_cmd." (method #10)",'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_file_cmd." ".s:ShellEscape(b:netrw_fname,1)." ".tmpfile) + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice + + "......................................... + " NetRead: Complain {{{3 + else + call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",8) + endif + endwhile + + " NetRead: cleanup {{{3 + if exists("b:netrw_method") + " call Decho("cleanup b:netrw_method and b:netrw_fname",'~'.expand("")) + unlet b:netrw_method + unlet b:netrw_fname + endif + if s:FileReadable(tmpfile) && tmpfile !~ '.tar.bz2$' && tmpfile !~ '.tar.gz$' && tmpfile !~ '.zip' && tmpfile !~ '.tar' && readcmd != 't' && tmpfile !~ '.tar.xz$' && tmpfile !~ '.txz' + " call Decho("cleanup by deleting tmpfile<".tmpfile.">",'~'.expand("")) + NetrwKeepj call s:NetrwDelete(tmpfile) + endif + NetrwKeepj call s:NetrwOptionsRestore("w:") + + " call Dret("netrw#NetRead :5 getcwd<".getcwd().">") +endfun + +" ------------------------------------------------------------------------ +" netrw#NetWrite: responsible for writing a file over the net {{{2 +fun! netrw#NetWrite(...) range + " call Dfunc("netrw#NetWrite(a:0=".a:0.") ".g:loaded_netrw) + + " NetWrite: option handling {{{3 + let mod= 0 + call s:NetrwOptionsSave("w:") + call s:NetrwOptionsSafe(0) + + " NetWrite: Get Temporary Filename {{{3 + let tmpfile= s:GetTempfile("") + if tmpfile == "" + " call Dret("netrw#NetWrite : unable to get a tempfile!") + return + endif + + if a:0 == 0 + let ichoice = 0 + else + let ichoice = 1 + endif + + let curbufname= expand("%") + " call Decho("curbufname<".curbufname.">",'~'.expand("")) + if &binary + " For binary writes, always write entire file. + " (line numbers don't really make sense for that). + " Also supports the writing of tar and zip files. + " call Decho("(write entire file) sil exe w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand("")) + exe "sil NetrwKeepj w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile) + elseif g:netrw_cygwin + " write (selected portion of) file to temporary + let cygtmpfile= substitute(tmpfile,g:netrw_cygdrive.'/\(.\)','\1:','') + " call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile),'~'.expand("")) + exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile) + else + " write (selected portion of) file to temporary + " call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile),'~'.expand("")) + exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile) + endif + + if curbufname == "" + " when the file is [No Name], and one attempts to Nwrite it, the buffer takes + " on the temporary file's name. Deletion of the temporary file during + " cleanup then causes an error message. + 0file! + endif + + " NetWrite: while choice loop: {{{3 + while ichoice <= a:0 + + " Process arguments: {{{4 + " attempt to repeat with previous host-file-etc + if exists("b:netrw_lastfile") && a:0 == 0 + " call Decho("using b:netrw_lastfile<" . b:netrw_lastfile . ">",'~'.expand("")) + let choice = b:netrw_lastfile + let ichoice= ichoice + 1 + else + exe "let choice= a:" . ichoice + + " Reconstruct Choice when choice starts with '"' + if match(choice,"?") == 0 + echomsg 'NetWrite Usage:"' + echomsg ':Nwrite machine:path uses rcp' + echomsg ':Nwrite "machine path" uses ftp with <.netrc>' + echomsg ':Nwrite "machine id password path" uses ftp' + echomsg ':Nwrite dav://[user@]machine/path uses cadaver' + echomsg ':Nwrite fetch://[user@]machine/path uses fetch' + echomsg ':Nwrite ftp://machine[#port]/path uses ftp (autodetects <.netrc>)' + echomsg ':Nwrite rcp://machine/path uses rcp' + echomsg ':Nwrite rsync://[user@]machine/path uses rsync' + echomsg ':Nwrite scp://[user@]machine[[:#]port]/path uses scp' + echomsg ':Nwrite sftp://[user@]machine/path uses sftp' + sleep 4 + break + + elseif match(choice,"^\"") != -1 + if match(choice,"\"$") != -1 + " case "..." + let choice=strpart(choice,1,strlen(choice)-2) + else + " case "... ... ..." + let choice = strpart(choice,1,strlen(choice)-1) + let wholechoice = "" + + while match(choice,"\"$") == -1 + let wholechoice= wholechoice . " " . choice + let ichoice = ichoice + 1 + if choice > a:0 + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,"Unbalanced string in filename '". wholechoice ."'",13) + endif + " call Dret("netrw#NetWrite") + return + endif + let choice= a:{ichoice} + endwhile + let choice= strpart(wholechoice,1,strlen(wholechoice)-1) . " " . strpart(choice,0,strlen(choice)-1) + endif + endif + endif + let ichoice= ichoice + 1 + " call Decho("choice<" . choice . "> ichoice=".ichoice,'~'.expand("")) + + " Determine method of write (ftp, rcp, etc) {{{4 + NetrwKeepj call s:NetrwMethod(choice) + if !exists("b:netrw_method") || b:netrw_method < 0 + " call Dfunc("netrw#NetWrite : unsupported method") + return + endif + + " ============= + " NetWrite: Perform Protocol-Based Write {{{3 + " ============================ + if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1 + echo "(netrw) Processing your write request..." + " call Decho("Processing your write request...",'~'.expand("")) + endif + + "......................................... + " NetWrite: (rcp) NetWrite Method #1 {{{3 + if b:netrw_method == 1 + " call Decho("write via rcp (method #1)",'~'.expand("")) + if s:netrw_has_nt_rcp == 1 + if exists("g:netrw_uid") && ( g:netrw_uid != "" ) + let uid_machine = g:netrw_machine .'.'. g:netrw_uid + else + let uid_machine = g:netrw_machine .'.'. $USERNAME + endif + else + if exists("g:netrw_uid") && ( g:netrw_uid != "" ) + let uid_machine = g:netrw_uid .'@'. g:netrw_machine + else + let uid_machine = g:netrw_machine + endif + endif + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(uid_machine.":".b:netrw_fname,1)) + let b:netrw_lastfile = choice + + "......................................... + " NetWrite: (ftp + <.netrc>) NetWrite Method #2 {{{3 + elseif b:netrw_method == 2 + " call Decho("write via ftp+.netrc (method #2)",'~'.expand("")) + let netrw_fname = b:netrw_fname + + " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead + let bhkeep = &l:bh + let curbuf = bufnr("%") + setl bh=hide + keepj keepalt enew + + " call Decho("filter input window#".winnr(),'~'.expand("")) + setl ff=unix + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline('$'),'~'.expand("")) + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline("$"),'~'.expand("")) + endif + NetrwKeepj call setline(line("$")+1,'put "'.tmpfile.'" "'.netrw_fname.'"') + " call Decho("filter input: ".getline("$"),'~'.expand("")) + if exists("g:netrw_port") && g:netrw_port != "" + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) + else + " call Decho("filter input window#".winnr(),'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) + endif + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + if getline(1) !~ "^$" + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),14) + endif + let mod=1 + endif + + " remove enew buffer (quietly) + let filtbuf= bufnr("%") + exe curbuf."b!" + let &l:bh = bhkeep + exe filtbuf."bw!" + + let b:netrw_lastfile = choice + + "......................................... + " NetWrite: (ftp + machine, id, passwd, filename) NetWrite Method #3 {{{3 + elseif b:netrw_method == 3 + " Construct execution string (three or more lines) which will be passed through filter + " call Decho("read via ftp+mipf (method #3)",'~'.expand("")) + let netrw_fname = b:netrw_fname + let bhkeep = &l:bh + + " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead + let curbuf = bufnr("%") + setl bh=hide + keepj keepalt enew + setl ff=unix + + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + " call Decho("filter input: ".getline('.'),'~'.expand("")) + else + NetrwKeepj put ='open '.g:netrw_machine + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + if exists("g:netrw_uid") && g:netrw_uid != "" + if exists("g:netrw_ftp") && g:netrw_ftp == 1 + NetrwKeepj put =g:netrw_uid + " call Decho("filter input: ".getline('.'),'~'.expand("")) + if exists("s:netrw_passwd") && s:netrw_passwd != "" + NetrwKeepj put ='\"'.s:netrw_passwd.'\"' + endif + " call Decho("filter input: ".getline('.'),'~'.expand("")) + elseif exists("s:netrw_passwd") && s:netrw_passwd != "" + NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + endif + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline('$'),'~'.expand("")) + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline("$"),'~'.expand("")) + endif + NetrwKeepj put ='put \"'.tmpfile.'\" \"'.netrw_fname.'\"' + " call Decho("filter input: ".getline('.'),'~'.expand("")) + " save choice/id/password for future use + let b:netrw_lastfile = choice + + " perform ftp: + " -i : turns off interactive prompting from ftp + " -n unix : DON'T use <.netrc>, even though it exists + " -n win32: quit being obnoxious about password + NetrwKeepj norm! 1G"_dd + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + if getline(1) !~ "^$" + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,getline(1),15) + endif + let mod=1 + endif + + " remove enew buffer (quietly) + let filtbuf= bufnr("%") + exe curbuf."b!" + let &l:bh= bhkeep + exe filtbuf."bw!" + + "......................................... + " NetWrite: (scp) NetWrite Method #4 {{{3 + elseif b:netrw_method == 4 + " call Decho("write via scp (method #4)",'~'.expand("")) + if exists("g:netrw_port") && g:netrw_port != "" + let useport= " ".g:netrw_scpport." ".fnameescape(g:netrw_port) + else + let useport= "" + endif + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.":".b:netrw_fname,1)) + let b:netrw_lastfile = choice + + "......................................... + " NetWrite: (http) NetWrite Method #5 {{{3 + elseif b:netrw_method == 5 + " call Decho("write via http (method #5)",'~'.expand("")) + let curl= substitute(g:netrw_http_put_cmd,'\s\+.*$',"","") + if executable(curl) + let url= g:netrw_choice + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(url,1) ) + elseif !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,"can't write to http using <".g:netrw_http_put_cmd.">",16) + endif + + "......................................... + " NetWrite: (dav) NetWrite Method #6 (cadaver) {{{3 + elseif b:netrw_method == 6 + " call Decho("write via cadaver (method #6)",'~'.expand("")) + + " Construct execution string (four lines) which will be passed through filter + let netrw_fname = escape(b:netrw_fname,g:netrw_fname_escape) + let bhkeep = &l:bh + + " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead + let curbuf = bufnr("%") + setl bh=hide + keepj keepalt enew + + setl ff=unix + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + else + NetrwKeepj put ='open '.g:netrw_machine + endif + if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != "" + NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd + endif + NetrwKeepj put ='put '.tmpfile.' '.netrw_fname + + " perform cadaver operation: + NetrwKeepj norm! 1G"_dd + call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd) + + " remove enew buffer (quietly) + let filtbuf= bufnr("%") + exe curbuf."b!" + let &l:bh = bhkeep + exe filtbuf."bw!" + + let b:netrw_lastfile = choice + + "......................................... + " NetWrite: (rsync) NetWrite Method #7 {{{3 + elseif b:netrw_method == 7 + " call Decho("write via rsync (method #7)",'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".s:ShellEscape(tmpfile,1)." ".s:ShellEscape(g:netrw_machine.g:netrw_rsync_sep.b:netrw_fname,1)) + let b:netrw_lastfile = choice + + "......................................... + " NetWrite: (sftp) NetWrite Method #9 {{{3 + elseif b:netrw_method == 9 + " call Decho("write via sftp (method #9)",'~'.expand("")) + let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape) + if exists("g:netrw_uid") && ( g:netrw_uid != "" ) + let uid_machine = g:netrw_uid .'@'. g:netrw_machine + else + let uid_machine = g:netrw_machine + endif + + " formerly just a "new...bd!", that changed the window sizes when equalalways. Using enew workaround instead + let bhkeep = &l:bh + let curbuf = bufnr("%") + setl bh=hide + keepj keepalt enew + + setl ff=unix + call setline(1,'put "'.escape(tmpfile,'\').'" '.netrw_fname) + " call Decho("filter input: ".getline('.'),'~'.expand("")) + let sftpcmd= substitute(g:netrw_sftp_cmd,"%TEMPFILE%",escape(tmpfile,'\'),"g") + call s:NetrwExe(s:netrw_silentxfer."%!".sftpcmd.' '.s:ShellEscape(uid_machine,1)) + let filtbuf= bufnr("%") + exe curbuf."b!" + let &l:bh = bhkeep + exe filtbuf."bw!" + let b:netrw_lastfile = choice + + "......................................... + " NetWrite: Complain {{{3 + else + call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",17) + let leavemod= 1 + endif + endwhile + + " NetWrite: Cleanup: {{{3 + " call Decho("cleanup",'~'.expand("")) + if s:FileReadable(tmpfile) + " call Decho("tmpfile<".tmpfile."> readable, will now delete it",'~'.expand("")) + call s:NetrwDelete(tmpfile) + endif + call s:NetrwOptionsRestore("w:") + + if a:firstline == 1 && a:lastline == line("$") + " restore modifiability; usually equivalent to set nomod + let &l:mod= mod + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + elseif !exists("leavemod") + " indicate that the buffer has not been modified since last written + " call Decho("set nomod",'~'.expand("")) + setl nomod + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + endif + + " call Dret("netrw#NetWrite") +endfun + +" --------------------------------------------------------------------- +" netrw#NetSource: source a remotely hosted vim script {{{2 +" uses NetRead to get a copy of the file into a temporarily file, +" then sources that file, +" then removes that file. +fun! netrw#NetSource(...) + " call Dfunc("netrw#NetSource() a:0=".a:0) + if a:0 > 0 && a:1 == '?' + " give help + echomsg 'NetSource Usage:' + echomsg ':Nsource dav://machine[:port]/path uses cadaver' + echomsg ':Nsource fetch://machine/path uses fetch' + echomsg ':Nsource ftp://[user@]machine[:port]/path uses ftp autodetects <.netrc>' + echomsg ':Nsource http[s]://[user@]machine/path uses http wget' + echomsg ':Nsource rcp://[user@]machine/path uses rcp' + echomsg ':Nsource rsync://machine[:port]/path uses rsync' + echomsg ':Nsource scp://[user@]machine[[:#]port]/path uses scp' + echomsg ':Nsource sftp://[user@]machine[[:#]port]/path uses sftp' + sleep 4 + else + let i= 1 + while i <= a:0 + call netrw#NetRead(3,a:{i}) + " call Decho("s:netread_tmpfile<".s:netrw_tmpfile.">",'~'.expand("")) + if s:FileReadable(s:netrw_tmpfile) + " call Decho("exe so ".fnameescape(s:netrw_tmpfile),'~'.expand("")) + exe "so ".fnameescape(s:netrw_tmpfile) + " call Decho("delete(".s:netrw_tmpfile.")",'~'.expand("")) + if delete(s:netrw_tmpfile) + call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".s:netrw_tmpfile.">!",103) + endif + unlet s:netrw_tmpfile + else + call netrw#ErrorMsg(s:ERROR,"unable to source <".a:{i}.">!",48) + endif + let i= i + 1 + endwhile + endif + " call Dret("netrw#NetSource") +endfun + +" --------------------------------------------------------------------- +" netrw#SetTreetop: resets the tree top to the current directory/specified directory {{{2 +" (implements the :Ntree command) +fun! netrw#SetTreetop(iscmd,...) + + " iscmd==0: netrw#SetTreetop called using gn mapping + " iscmd==1: netrw#SetTreetop called using :Ntree from the command line + " clear out the current tree + if exists("w:netrw_treetop") + let inittreetop= w:netrw_treetop + unlet w:netrw_treetop + endif + if exists("w:netrw_treedict") + unlet w:netrw_treedict + endif + + if (a:iscmd == 0 || a:1 == "") && exists("inittreetop") + let treedir = s:NetrwTreePath(inittreetop) + else + if isdirectory(s:NetrwFile(a:1)) + let treedir = a:1 + let s:netrw_treetop = treedir + elseif exists("b:netrw_curdir") && (isdirectory(s:NetrwFile(b:netrw_curdir."/".a:1)) || a:1 =~ '^\a\{3,}://') + let treedir = b:netrw_curdir."/".a:1 + let s:netrw_treetop = treedir + else + " normally the cursor is left in the message window. + " However, here this results in the directory being listed in the message window, which is not wanted. + let netrwbuf= bufnr("%") + call netrw#ErrorMsg(s:ERROR,"sorry, ".a:1." doesn't seem to be a directory!",95) + exe bufwinnr(netrwbuf)."wincmd w" + let treedir = "." + let s:netrw_treetop = getcwd() + endif + endif + + " determine if treedir is remote or local + let islocal= expand("%") !~ '^\a\{3,}://' + + " browse the resulting directory + if islocal + call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(islocal,treedir,0)) + else + call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,treedir,0)) + endif + +endfun + +" =========================================== +" s:NetrwGetFile: Function to read temporary file "tfile" with command "readcmd". {{{2 +" readcmd == %r : replace buffer with newly read file +" == 0r : read file at top of buffer +" == r : read file after current line +" == t : leave file in temporary form (ie. don't read into buffer) +fun! s:NetrwGetFile(readcmd, tfile, method) + " call Dfunc("NetrwGetFile(readcmd<".a:readcmd.">,tfile<".a:tfile."> method<".a:method.">)") + + " readcmd=='t': simply do nothing + if a:readcmd == 't' + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + " call Dret("NetrwGetFile : skip read of tfile<".a:tfile.">") + return + endif + + " get name of remote filename (ie. url and all) + let rfile= bufname("%") + " call Decho("rfile<".rfile.">",'~'.expand("")) + + if exists("*NetReadFixup") + " for the use of NetReadFixup (not otherwise used internally) + let line2= line("$") + endif + + if a:readcmd[0] == '%' + " get file into buffer + " call Decho("get file into buffer",'~'.expand("")) + + " rename the current buffer to the temp file (ie. tfile) + if g:netrw_cygwin + let tfile= substitute(a:tfile,g:netrw_cygdrive.'/\(.\)','\1:','') + else + let tfile= a:tfile + endif + call s:NetrwBufRename(tfile) + + " edit temporary file (ie. read the temporary file in) + if rfile =~ '\.zip$' + " call Decho("handling remote zip file with zip#Browse(tfile<".tfile.">)",'~'.expand("")) + call zip#Browse(tfile) + elseif rfile =~ '\.tar$' + " call Decho("handling remote tar file with tar#Browse(tfile<".tfile.">)",'~'.expand("")) + call tar#Browse(tfile) + elseif rfile =~ '\.tar\.gz$' + " call Decho("handling remote gzip-compressed tar file",'~'.expand("")) + call tar#Browse(tfile) + elseif rfile =~ '\.tar\.bz2$' + " call Decho("handling remote bz2-compressed tar file",'~'.expand("")) + call tar#Browse(tfile) + elseif rfile =~ '\.tar\.xz$' + " call Decho("handling remote xz-compressed tar file",'~'.expand("")) + call tar#Browse(tfile) + elseif rfile =~ '\.txz$' + " call Decho("handling remote xz-compressed tar file (.txz)",'~'.expand("")) + call tar#Browse(tfile) + else + " call Decho("edit temporary file",'~'.expand("")) + NetrwKeepj e! + endif + + " rename buffer back to remote filename + call s:NetrwBufRename(rfile) + + " Jan 19, 2022: COMBAK -- bram problem with https://github.com/vim/vim/pull/9554.diff filetype + " Detect filetype of local version of remote file. + " Note that isk must not include a "/" for scripts.vim + " to process this detection correctly. + " call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand("")) + " call Decho("..did_filetype()=".did_filetype()) + " setl ft= + " call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">") + let iskkeep= &isk + setl isk-=/ + filetype detect + " call Decho("..local filetype<".&ft."> for buf#".bufnr()."<".bufname().">") + let &l:isk= iskkeep + " call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)") + let line1 = 1 + let line2 = line("$") + + elseif !&ma + " attempting to read a file after the current line in the file, but the buffer is not modifiable + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"attempt to read<".a:tfile."> into a non-modifiable buffer!",94) + " call Dret("NetrwGetFile : attempt to read<".a:tfile."> into a non-modifiable buffer!") + return + + elseif s:FileReadable(a:tfile) + " read file after current line + " call Decho("read file<".a:tfile."> after current line",'~'.expand("")) + let curline = line(".") + let lastline= line("$") + " call Decho("exe<".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)."> line#".curline,'~'.expand("")) + exe "NetrwKeepj ".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile) + let line1= curline + 1 + let line2= line("$") - lastline + 1 + + else + " not readable + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + " call Decho("tfile<".a:tfile."> not readable",'~'.expand("")) + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"file <".a:tfile."> not readable",9) + " call Dret("NetrwGetFile : tfile<".a:tfile."> not readable") + return + endif + + " User-provided (ie. optional) fix-it-up command + if exists("*NetReadFixup") + " call Decho("calling NetReadFixup(method<".a:method."> line1=".line1." line2=".line2.")",'~'.expand("")) + NetrwKeepj call NetReadFixup(a:method, line1, line2) + " else " Decho + " call Decho("NetReadFixup() not called, doesn't exist (line1=".line1." line2=".line2.")",'~'.expand("")) + endif + + if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu + " update the Buffers menu + NetrwKeepj call s:UpdateBuffersMenu() + endif + + " call Decho("readcmd<".a:readcmd."> cmdarg<".v:cmdarg."> tfile<".a:tfile."> readable=".s:FileReadable(a:tfile),'~'.expand("")) + + " make sure file is being displayed + " redraw! + + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + " call Dret("NetrwGetFile") +endfun + +" ------------------------------------------------------------------------ +" s:NetrwMethod: determine method of transfer {{{2 +" Input: +" choice = url [protocol:]//[userid@]hostname[:port]/[path-to-file] +" Output: +" b:netrw_method= 1: rcp +" 2: ftp + <.netrc> +" 3: ftp + machine, id, password, and [path]filename +" 4: scp +" 5: http[s] (wget) +" 6: dav +" 7: rsync +" 8: fetch +" 9: sftp +" 10: file +" g:netrw_machine= hostname +" b:netrw_fname = filename +" g:netrw_port = optional port number (for ftp) +" g:netrw_choice = copy of input url (choice) +fun! s:NetrwMethod(choice) + " call Dfunc("s:NetrwMethod(a:choice<".a:choice.">)") + + " sanity check: choice should have at least three slashes in it + if strlen(substitute(a:choice,'[^/]','','g')) < 3 + call netrw#ErrorMsg(s:ERROR,"not a netrw-style url; netrw uses protocol://[user@]hostname[:port]/[path])",78) + let b:netrw_method = -1 + " call Dret("s:NetrwMethod : incorrect url format<".a:choice.">") + return + endif + + " record current g:netrw_machine, if any + " curmachine used if protocol == ftp and no .netrc + if exists("g:netrw_machine") + let curmachine= g:netrw_machine + " call Decho("curmachine<".curmachine.">",'~'.expand("")) + else + let curmachine= "N O T A HOST" + endif + if exists("g:netrw_port") + let netrw_port= g:netrw_port + endif + + " insure that netrw_ftp_cmd starts off every method determination + " with the current g:netrw_ftp_cmd + let s:netrw_ftp_cmd= g:netrw_ftp_cmd + + " initialization + let b:netrw_method = 0 + let g:netrw_machine = "" + let b:netrw_fname = "" + let g:netrw_port = "" + let g:netrw_choice = a:choice + + " Patterns: + " mipf : a:machine a:id password filename Use ftp + " mf : a:machine filename Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd + " ftpurm : ftp://[user@]host[[#:]port]/filename Use ftp + <.netrc> or g:netrw_uid s:netrw_passwd + " rcpurm : rcp://[user@]host/filename Use rcp + " rcphf : [user@]host:filename Use rcp + " scpurm : scp://[user@]host[[#:]port]/filename Use scp + " httpurm : http[s]://[user@]host/filename Use wget + " davurm : dav[s]://host[:port]/path Use cadaver/curl + " rsyncurm : rsync://host[:port]/path Use rsync + " fetchurm : fetch://[user@]host[:http]/filename Use fetch (defaults to ftp, override for http) + " sftpurm : sftp://[user@]host/filename Use scp + " fileurm : file://[user@]host/filename Use elinks or links + let mipf = '^\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)$' + let mf = '^\(\S\+\)\s\+\(\S\+\)$' + let ftpurm = '^ftp://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\([#:]\d\+\)\=/\(.*\)$' + let rcpurm = '^rcp://\%(\([^/]*\)@\)\=\([^/]\{-}\)/\(.*\)$' + let rcphf = '^\(\(\h\w*\)@\)\=\(\h\w*\):\([^@]\+\)$' + let scpurm = '^scp://\([^/#:]\+\)\%([#:]\(\d\+\)\)\=/\(.*\)$' + let httpurm = '^https\=://\([^/]\{-}\)\(/.*\)\=$' + let davurm = '^davs\=://\([^/]\+\)/\(.*/\)\([-_.~[:alnum:]]\+\)$' + let rsyncurm = '^rsync://\([^/]\{-}\)/\(.*\)\=$' + let fetchurm = '^fetch://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\(:http\)\=/\(.*\)$' + let sftpurm = '^sftp://\([^/]\{-}\)/\(.*\)\=$' + let fileurm = '^file\=://\(.*\)$' + + " call Decho("determine method:",'~'.expand("")) + " Determine Method + " Method#1: rcp://user@hostname/...path-to-file {{{3 + if match(a:choice,rcpurm) == 0 + " call Decho("rcp://...",'~'.expand("")) + let b:netrw_method = 1 + let userid = substitute(a:choice,rcpurm,'\1',"") + let g:netrw_machine = substitute(a:choice,rcpurm,'\2',"") + let b:netrw_fname = substitute(a:choice,rcpurm,'\3',"") + if userid != "" + let g:netrw_uid= userid + endif + + " Method#4: scp://user@hostname/...path-to-file {{{3 + elseif match(a:choice,scpurm) == 0 + " call Decho("scp://...",'~'.expand("")) + let b:netrw_method = 4 + let g:netrw_machine = substitute(a:choice,scpurm,'\1',"") + let g:netrw_port = substitute(a:choice,scpurm,'\2',"") + let b:netrw_fname = substitute(a:choice,scpurm,'\3',"") + + " Method#5: http[s]://user@hostname/...path-to-file {{{3 + elseif match(a:choice,httpurm) == 0 + " call Decho("http[s]://...",'~'.expand("")) + let b:netrw_method = 5 + let g:netrw_machine= substitute(a:choice,httpurm,'\1',"") + let b:netrw_fname = substitute(a:choice,httpurm,'\2',"") + let b:netrw_http = (a:choice =~ '^https:')? "https" : "http" + + " Method#6: dav://hostname[:port]/..path-to-file.. {{{3 + elseif match(a:choice,davurm) == 0 + " call Decho("dav://...",'~'.expand("")) + let b:netrw_method= 6 + if a:choice =~ 'davs:' + let g:netrw_machine= 'https://'.substitute(a:choice,davurm,'\1/\2',"") + else + let g:netrw_machine= 'http://'.substitute(a:choice,davurm,'\1/\2',"") + endif + let b:netrw_fname = substitute(a:choice,davurm,'\3',"") + + " Method#7: rsync://user@hostname/...path-to-file {{{3 + elseif match(a:choice,rsyncurm) == 0 + " call Decho("rsync://...",'~'.expand("")) + let b:netrw_method = 7 + let g:netrw_machine= substitute(a:choice,rsyncurm,'\1',"") + let b:netrw_fname = substitute(a:choice,rsyncurm,'\2',"") + + " Methods 2,3: ftp://[user@]hostname[[:#]port]/...path-to-file {{{3 + elseif match(a:choice,ftpurm) == 0 + " call Decho("ftp://...",'~'.expand("")) + let userid = substitute(a:choice,ftpurm,'\2',"") + let g:netrw_machine= substitute(a:choice,ftpurm,'\3',"") + let g:netrw_port = substitute(a:choice,ftpurm,'\4',"") + let b:netrw_fname = substitute(a:choice,ftpurm,'\5',"") + " call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand("")) + if userid != "" + let g:netrw_uid= userid + endif + + if curmachine != g:netrw_machine + if exists("s:netrw_hup[".g:netrw_machine."]") + call NetUserPass("ftp:".g:netrw_machine) + elseif exists("s:netrw_passwd") + " if there's a change in hostname, require password re-entry + unlet s:netrw_passwd + endif + if exists("netrw_port") + unlet netrw_port + endif + endif + + if exists("g:netrw_uid") && exists("s:netrw_passwd") + let b:netrw_method = 3 + else + let host= substitute(g:netrw_machine,'\..*$','','') + if exists("s:netrw_hup[host]") + call NetUserPass("ftp:".host) + + elseif has("win32") && s:netrw_ftp_cmd =~# '-[sS]:' + " call Decho("has -s: : s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand("")) + " call Decho(" g:netrw_ftp_cmd<".g:netrw_ftp_cmd.">",'~'.expand("")) + if g:netrw_ftp_cmd =~# '-[sS]:\S*MACHINE\>' + let s:netrw_ftp_cmd= substitute(g:netrw_ftp_cmd,'\',g:netrw_machine,'') + " call Decho("s:netrw_ftp_cmd<".s:netrw_ftp_cmd.">",'~'.expand("")) + endif + let b:netrw_method= 2 + elseif s:FileReadable(expand("$HOME/.netrc")) && !g:netrw_ignorenetrc + " call Decho("using <".expand("$HOME/.netrc")."> (readable)",'~'.expand("")) + let b:netrw_method= 2 + else + if !exists("g:netrw_uid") || g:netrw_uid == "" + call NetUserPass() + elseif !exists("s:netrw_passwd") || s:netrw_passwd == "" + call NetUserPass(g:netrw_uid) + " else just use current g:netrw_uid and s:netrw_passwd + endif + let b:netrw_method= 3 + endif + endif + + " Method#8: fetch {{{3 + elseif match(a:choice,fetchurm) == 0 + " call Decho("fetch://...",'~'.expand("")) + let b:netrw_method = 8 + let g:netrw_userid = substitute(a:choice,fetchurm,'\2',"") + let g:netrw_machine= substitute(a:choice,fetchurm,'\3',"") + let b:netrw_option = substitute(a:choice,fetchurm,'\4',"") + let b:netrw_fname = substitute(a:choice,fetchurm,'\5',"") + + " Method#3: Issue an ftp : "machine id password [path/]filename" {{{3 + elseif match(a:choice,mipf) == 0 + " call Decho("(ftp) host id pass file",'~'.expand("")) + let b:netrw_method = 3 + let g:netrw_machine = substitute(a:choice,mipf,'\1',"") + let g:netrw_uid = substitute(a:choice,mipf,'\2',"") + let s:netrw_passwd = substitute(a:choice,mipf,'\3',"") + let b:netrw_fname = substitute(a:choice,mipf,'\4',"") + call NetUserPass(g:netrw_machine,g:netrw_uid,s:netrw_passwd) + + " Method#3: Issue an ftp: "hostname [path/]filename" {{{3 + elseif match(a:choice,mf) == 0 + " call Decho("(ftp) host file",'~'.expand("")) + if exists("g:netrw_uid") && exists("s:netrw_passwd") + let b:netrw_method = 3 + let g:netrw_machine = substitute(a:choice,mf,'\1',"") + let b:netrw_fname = substitute(a:choice,mf,'\2',"") + + elseif s:FileReadable(expand("$HOME/.netrc")) + let b:netrw_method = 2 + let g:netrw_machine = substitute(a:choice,mf,'\1',"") + let b:netrw_fname = substitute(a:choice,mf,'\2',"") + endif + + " Method#9: sftp://user@hostname/...path-to-file {{{3 + elseif match(a:choice,sftpurm) == 0 + " call Decho("sftp://...",'~'.expand("")) + let b:netrw_method = 9 + let g:netrw_machine= substitute(a:choice,sftpurm,'\1',"") + let b:netrw_fname = substitute(a:choice,sftpurm,'\2',"") + + " Method#1: Issue an rcp: hostname:filename" (this one should be last) {{{3 + elseif match(a:choice,rcphf) == 0 + " call Decho("(rcp) [user@]host:file) rcphf<".rcphf.">",'~'.expand("")) + let b:netrw_method = 1 + let userid = substitute(a:choice,rcphf,'\2',"") + let g:netrw_machine = substitute(a:choice,rcphf,'\3',"") + let b:netrw_fname = substitute(a:choice,rcphf,'\4',"") + " call Decho('\1<'.substitute(a:choice,rcphf,'\1',"").">",'~'.expand("")) + " call Decho('\2<'.substitute(a:choice,rcphf,'\2',"").">",'~'.expand("")) + " call Decho('\3<'.substitute(a:choice,rcphf,'\3',"").">",'~'.expand("")) + " call Decho('\4<'.substitute(a:choice,rcphf,'\4',"").">",'~'.expand("")) + if userid != "" + let g:netrw_uid= userid + endif + + " Method#10: file://user@hostname/...path-to-file {{{3 + elseif match(a:choice,fileurm) == 0 && exists("g:netrw_file_cmd") + " call Decho("http[s]://...",'~'.expand("")) + let b:netrw_method = 10 + let b:netrw_fname = substitute(a:choice,fileurm,'\1',"") + " call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">",'~'.expand("")) + + " Cannot Determine Method {{{3 + else + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:WARNING,"cannot determine method (format: protocol://[user@]hostname[:port]/[path])",45) + endif + let b:netrw_method = -1 + endif + "}}}3 + + if g:netrw_port != "" + " remove any leading [:#] from port number + let g:netrw_port = substitute(g:netrw_port,'[#:]\+','','') + elseif exists("netrw_port") + " retain port number as implicit for subsequent ftp operations + let g:netrw_port= netrw_port + endif + + " call Decho("a:choice <".a:choice.">",'~'.expand("")) + " call Decho("b:netrw_method <".b:netrw_method.">",'~'.expand("")) + " call Decho("g:netrw_machine<".g:netrw_machine.">",'~'.expand("")) + " call Decho("g:netrw_port <".g:netrw_port.">",'~'.expand("")) + " if exists("g:netrw_uid") "Decho + " call Decho("g:netrw_uid <".g:netrw_uid.">",'~'.expand("")) + " endif "Decho + " if exists("s:netrw_passwd") "Decho + " call Decho("s:netrw_passwd <".s:netrw_passwd.">",'~'.expand("")) + " endif "Decho + " call Decho("b:netrw_fname <".b:netrw_fname.">",'~'.expand("")) + " call Dret("s:NetrwMethod : b:netrw_method=".b:netrw_method." g:netrw_port=".g:netrw_port) +endfun + +" --------------------------------------------------------------------- +" NetUserPass: set username and password for subsequent ftp transfer {{{2 +" Usage: :call NetUserPass() -- will prompt for userid and password +" :call NetUserPass("uid") -- will prompt for password +" :call NetUserPass("uid","password") -- sets global userid and password +" :call NetUserPass("ftp:host") -- looks up userid and password using hup dictionary +" :call NetUserPass("host","uid","password") -- sets hup dictionary with host, userid, password +fun! NetUserPass(...) + + " call Dfunc("NetUserPass() a:0=".a:0) + + if !exists('s:netrw_hup') + let s:netrw_hup= {} + endif + + if a:0 == 0 + " case: no input arguments + + " change host and username if not previously entered; get new password + if !exists("g:netrw_machine") + let g:netrw_machine= input('Enter hostname: ') + endif + if !exists("g:netrw_uid") || g:netrw_uid == "" + " get username (user-id) via prompt + let g:netrw_uid= input('Enter username: ') + endif + " get password via prompting + let s:netrw_passwd= inputsecret("Enter Password: ") + + " set up hup database + let host = substitute(g:netrw_machine,'\..*$','','') + if !exists('s:netrw_hup[host]') + let s:netrw_hup[host]= {} + endif + let s:netrw_hup[host].uid = g:netrw_uid + let s:netrw_hup[host].passwd = s:netrw_passwd + + elseif a:0 == 1 + " case: one input argument + + if a:1 =~ '^ftp:' + " get host from ftp:... url + " access userid and password from hup (host-user-passwd) dictionary + " call Decho("case a:0=1: a:1<".a:1."> (get host from ftp:... url)",'~'.expand("")) + let host = substitute(a:1,'^ftp:','','') + let host = substitute(host,'\..*','','') + if exists("s:netrw_hup[host]") + let g:netrw_uid = s:netrw_hup[host].uid + let s:netrw_passwd = s:netrw_hup[host].passwd + " call Decho("get s:netrw_hup[".host."].uid <".s:netrw_hup[host].uid.">",'~'.expand("")) + " call Decho("get s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand("")) + else + let g:netrw_uid = input("Enter UserId: ") + let s:netrw_passwd = inputsecret("Enter Password: ") + endif + + else + " case: one input argument, not an url. Using it as a new user-id. + " call Decho("case a:0=1: a:1<".a:1."> (get host from input argument, not an url)",'~'.expand("")) + if exists("g:netrw_machine") + if g:netrw_machine =~ '[0-9.]\+' + let host= g:netrw_machine + else + let host= substitute(g:netrw_machine,'\..*$','','') + endif + else + let g:netrw_machine= input('Enter hostname: ') + endif + let g:netrw_uid = a:1 + " call Decho("set g:netrw_uid= <".g:netrw_uid.">",'~'.expand("")) + if exists("g:netrw_passwd") + " ask for password if one not previously entered + let s:netrw_passwd= g:netrw_passwd + else + let s:netrw_passwd = inputsecret("Enter Password: ") + endif + endif + + " call Decho("host<".host.">",'~'.expand("")) + if exists("host") + if !exists('s:netrw_hup[host]') + let s:netrw_hup[host]= {} + endif + let s:netrw_hup[host].uid = g:netrw_uid + let s:netrw_hup[host].passwd = s:netrw_passwd + endif + + elseif a:0 == 2 + let g:netrw_uid = a:1 + let s:netrw_passwd = a:2 + + elseif a:0 == 3 + " enter hostname, user-id, and password into the hup dictionary + let host = substitute(a:1,'^\a\+:','','') + let host = substitute(host,'\..*$','','') + if !exists('s:netrw_hup[host]') + let s:netrw_hup[host]= {} + endif + let s:netrw_hup[host].uid = a:2 + let s:netrw_hup[host].passwd = a:3 + let g:netrw_uid = s:netrw_hup[host].uid + let s:netrw_passwd = s:netrw_hup[host].passwd + " call Decho("set s:netrw_hup[".host."].uid <".s:netrw_hup[host].uid.">",'~'.expand("")) + " call Decho("set s:netrw_hup[".host."].passwd<".s:netrw_hup[host].passwd.">",'~'.expand("")) + endif + + " call Dret("NetUserPass : uid<".g:netrw_uid."> passwd<".s:netrw_passwd.">") +endfun + +" ================================= +" Shared Browsing Support: {{{1 +" ================================= + +" --------------------------------------------------------------------- +" s:ExplorePatHls: converts an Explore pattern into a regular expression search pattern {{{2 +fun! s:ExplorePatHls(pattern) + " call Dfunc("s:ExplorePatHls(pattern<".a:pattern.">)") + let repat= substitute(a:pattern,'^**/\{1,2}','','') + " call Decho("repat<".repat.">",'~'.expand("")) + let repat= escape(repat,'][.\') + " call Decho("repat<".repat.">",'~'.expand("")) + let repat= '\<'.substitute(repat,'\*','\\(\\S\\+ \\)*\\S\\+','g').'\>' + " call Dret("s:ExplorePatHls repat<".repat.">") + return repat +endfun + +" --------------------------------------------------------------------- +" s:NetrwBookHistHandler: {{{2 +" 0: (user: ) bookmark current directory +" 1: (user: ) change to the bookmarked directory +" 2: (user: ) list bookmarks +" 3: (browsing) records current directory history +" 4: (user: ) go up (previous) directory, using history +" 5: (user: ) go down (next) directory, using history +" 6: (user: ) delete bookmark +fun! s:NetrwBookHistHandler(chg,curdir) + " call Dfunc("s:NetrwBookHistHandler(chg=".a:chg." curdir<".a:curdir.">) cnt=".v:count." histcnt=".g:netrw_dirhistcnt." histmax=".g:netrw_dirhistmax) + if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0 + " " call Dret("s:NetrwBookHistHandler - suppressed due to g:netrw_dirhistmax") + return + endif + + let ykeep = @@ + let curbufnr = bufnr("%") + + if a:chg == 0 + " bookmark the current directory + " call Decho("(user: ) bookmark the current directory",'~'.expand("")) + if exists("s:netrwmarkfilelist_{curbufnr}") + call s:NetrwBookmark(0) + echo "bookmarked marked files" + else + call s:MakeBookmark(a: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("")) + if exists("g:netrw_bookmarklist[v:count-1]") + " call Decho("(user: <".v:count."gb>) bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("")) + exe "NetrwKeepj e ".fnameescape(g:netrw_bookmarklist[v:count-1]) + else + echomsg "Sorry, bookmark#".v:count." doesn't exist!" + endif + + elseif a:chg == 2 + " redraw! + let didwork= 0 + " list user's bookmarks + " call Decho("(user: ) list user's bookmarks",'~'.expand("")) + if exists("g:netrw_bookmarklist") + " call Decho('list '.len(g:netrw_bookmarklist).' bookmarks','~'.expand("")) + let cnt= 1 + for bmd in g:netrw_bookmarklist + " call Decho("Netrw Bookmark#".cnt.": ".g:netrw_bookmarklist[cnt-1],'~'.expand("")) + echo printf("Netrw Bookmark#%-2d: %s",cnt,g:netrw_bookmarklist[cnt-1]) + let didwork = 1 + let cnt = cnt + 1 + endfor + endif + + " list directory history + " Note: history is saved only when PerformListing is done; + " ie. when netrw can re-use a netrw buffer, the current directory is not saved in the history. + let cnt = g:netrw_dirhistcnt + let first = 1 + let histcnt = 0 + if g:netrw_dirhistmax > 0 + while ( first || cnt != g:netrw_dirhistcnt ) + " call Decho("first=".first." cnt=".cnt." dirhistcnt=".g:netrw_dirhistcnt,'~'.expand("")) + if exists("g:netrw_dirhist_{cnt}") + " call Decho("Netrw History#".histcnt.": ".g:netrw_dirhist_{cnt},'~'.expand("")) + echo printf("Netrw History#%-2d: %s",histcnt,g:netrw_dirhist_{cnt}) + let didwork= 1 + endif + let histcnt = histcnt + 1 + let first = 0 + let cnt = ( cnt - 1 ) % g:netrw_dirhistmax + if cnt < 0 + let cnt= cnt + g:netrw_dirhistmax + endif + endwhile + else + let g:netrw_dirhistcnt= 0 + endif + if didwork + call inputsave()|call input("Press to continue")|call inputrestore() + endif + + elseif a:chg == 3 + " saves most recently visited directories (when they differ) + " call Decho("(browsing) record curdir history",'~'.expand("")) + if !exists("g:netrw_dirhistcnt") || !exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") || g:netrw_dirhist_{g:netrw_dirhistcnt} != a:curdir + if g:netrw_dirhistmax > 0 + let g:netrw_dirhistcnt = ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax + let g:netrw_dirhist_{g:netrw_dirhistcnt} = a:curdir + endif + " call Decho("save dirhist#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand("")) + endif + + elseif a:chg == 4 + " u: change to the previous directory stored on the history list + " call Decho("(user: ) chg to prev dir from history",'~'.expand("")) + if g:netrw_dirhistmax > 0 + let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - v:count1 ) % g:netrw_dirhistmax + if g:netrw_dirhistcnt < 0 + let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax + endif + else + let g:netrw_dirhistcnt= 0 + endif + if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") + " call Decho("changedir u#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand("")) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir") + setl ma noro + " call Decho("setl ma noro",'~'.expand("")) + sil! NetrwKeepj %d _ + setl nomod + " call Decho("setl nomod",'~'.expand("")) + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + endif + " call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand("")) + exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}) + else + if g:netrw_dirhistmax > 0 + let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + v:count1 ) % g:netrw_dirhistmax + else + let g:netrw_dirhistcnt= 0 + endif + echo "Sorry, no predecessor directory exists yet" + endif + + elseif a:chg == 5 + " U: change to the subsequent directory stored on the history list + " call Decho("(user: ) chg to next dir from history",'~'.expand("")) + if g:netrw_dirhistmax > 0 + let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt + 1 ) % g:netrw_dirhistmax + if exists("g:netrw_dirhist_{g:netrw_dirhistcnt}") + " call Decho("changedir U#".g:netrw_dirhistcnt."<".g:netrw_dirhist_{g:netrw_dirhistcnt}.">",'~'.expand("")) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir") + " call Decho("setl ma noro",'~'.expand("")) + setl ma noro + sil! NetrwKeepj %d _ + " call Decho("removed all lines from buffer (%d)",'~'.expand("")) + " call Decho("setl nomod",'~'.expand("")) + setl nomod + " call Decho("(set nomod) ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + endif + " call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}),'~'.expand("")) + exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhistcnt}) + else + let g:netrw_dirhistcnt= ( g:netrw_dirhistcnt - 1 ) % g:netrw_dirhistmax + if g:netrw_dirhistcnt < 0 + let g:netrw_dirhistcnt= g:netrw_dirhistcnt + g:netrw_dirhistmax + endif + echo "Sorry, no successor directory exists yet" + endif + else + let g:netrw_dirhistcnt= 0 + echo "Sorry, no successor directory exists yet (g:netrw_dirhistmax is ".g:netrw_dirhistmax.")" + endif + + elseif a:chg == 6 + " call Decho("(user: ) delete bookmark'd directory",'~'.expand("")) + if exists("s:netrwmarkfilelist_{curbufnr}") + call s:NetrwBookmark(1) + echo "removed marked files from bookmarks" + else + " delete the v:count'th bookmark + let iremove = v:count + let dremove = g:netrw_bookmarklist[iremove - 1] + " call Decho("delete bookmark#".iremove."<".g:netrw_bookmarklist[iremove - 1].">",'~'.expand("")) + call s:MergeBookmarks() + " call Decho("remove g:netrw_bookmarklist[".(iremove-1)."]<".g:netrw_bookmarklist[(iremove-1)].">",'~'.expand("")) + NetrwKeepj call remove(g:netrw_bookmarklist,iremove-1) + echo "removed ".dremove." from g:netrw_bookmarklist" + " call Decho("g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("")) + endif + " call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("")) + + try + call s:NetrwBookHistSave() + catch + endtry + endif + call s:NetrwBookmarkMenu() + call s:NetrwTgtMenu() + let @@= ykeep + " call Dret("s:NetrwBookHistHandler") +endfun + +" --------------------------------------------------------------------- +" s:NetrwBookHistRead: this function reads bookmarks and history {{{2 +" Will source the history file (.netrwhist) only if the g:netrw_disthistmax is > 0. +" Sister function: s:NetrwBookHistSave() +fun! s:NetrwBookHistRead() + " call Dfunc("s:NetrwBookHistRead()") + if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0 + " call Dret("s:NetrwBookHistRead - nothing read (suppressed due to dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a").")") + return + endif + let ykeep= @@ + + " read bookmarks + if !exists("s:netrw_initbookhist") + let home = s:NetrwHome() + let savefile= home."/.netrwbook" + if filereadable(s:NetrwFile(savefile)) + " call Decho("sourcing .netrwbook",'~'.expand("")) + exe "keepalt NetrwKeepj so ".savefile + endif + + " read history + if g:netrw_dirhistmax > 0 + let savefile= home."/.netrwhist" + if filereadable(s:NetrwFile(savefile)) + " call Decho("sourcing .netrwhist",'~'.expand("")) + exe "keepalt NetrwKeepj so ".savefile + endif + let s:netrw_initbookhist= 1 + au VimLeave * call s:NetrwBookHistSave() + endif + endif + + let @@= ykeep + " call Decho("dirhistmax=".(exists("g:netrw_dirhistmax")? g:netrw_dirhistmax : "n/a"),'~'.expand("")) + " call Decho("dirhistcnt=".(exists("g:netrw_dirhistcnt")? g:netrw_dirhistcnt : "n/a"),'~'.expand("")) + " call Dret("s:NetrwBookHistRead") +endfun + +" --------------------------------------------------------------------- +" s:NetrwBookHistSave: this function saves bookmarks and history to files {{{2 +" Sister function: s:NetrwBookHistRead() +" I used to do this via viminfo but that appears to +" be unreliable for long-term storage +" If g:netrw_dirhistmax is <= 0, no history or bookmarks +" will be saved. +" (s:NetrwBookHistHandler(3,...) used to record history) +fun! s:NetrwBookHistSave() + " call Dfunc("s:NetrwBookHistSave() dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt) + if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0 + " call Dret("s:NetrwBookHistSave : nothing saved (dirhistmax=".g:netrw_dirhistmax.")") + return + endif + + let savefile= s:NetrwHome()."/.netrwhist" + " call Decho("savefile<".savefile.">",'~'.expand("")) + 1split + + " setting up a new buffer which will become .netrwhist + call s:NetrwEnew() + " call Decho("case g:netrw_use_noswf=".g:netrw_use_noswf.(exists("+acd")? " +acd" : " -acd"),'~'.expand("")) + if g:netrw_use_noswf + setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000 noswf + else + setl cino= com= cpo-=a cpo-=A fo=nroql2 tw=0 report=10000 + endif + setl nocin noai noci magic nospell nohid wig= noaw + setl ma noro write + if exists("+acd") | setl noacd | endif + sil! NetrwKeepj keepalt %d _ + + " rename enew'd file: .netrwhist -- no attempt to merge + " record dirhistmax and current dirhistcnt + " save history + " call Decho("saving history: dirhistmax=".g:netrw_dirhistmax." dirhistcnt=".g:netrw_dirhistcnt." lastline=".line("$"),'~'.expand("")) + sil! keepalt file .netrwhist + call setline(1,"let g:netrw_dirhistmax =".g:netrw_dirhistmax) + call setline(2,"let g:netrw_dirhistcnt =".g:netrw_dirhistcnt) + if g:netrw_dirhistmax > 0 + let lastline = line("$") + let cnt = g:netrw_dirhistcnt + let first = 1 + while ( first || cnt != g:netrw_dirhistcnt ) + let lastline= lastline + 1 + if exists("g:netrw_dirhist_{cnt}") + call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'") + " call Decho("..".lastline.'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'",'~'.expand("")) + endif + let first = 0 + let cnt = ( cnt - 1 ) % g:netrw_dirhistmax + if cnt < 0 + let cnt= cnt + g:netrw_dirhistmax + endif + endwhile + exe "sil! w! ".savefile + " call Decho("exe sil! w! ".savefile,'~'.expand("")) + endif + + " save bookmarks + sil NetrwKeepj %d _ + if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] + " call Decho("saving bookmarks",'~'.expand("")) + " merge and write .netrwbook + let savefile= s:NetrwHome()."/.netrwbook" + + if filereadable(s:NetrwFile(savefile)) + let booklist= deepcopy(g:netrw_bookmarklist) + exe "sil NetrwKeepj keepalt so ".savefile + for bdm in booklist + if index(g:netrw_bookmarklist,bdm) == -1 + call add(g:netrw_bookmarklist,bdm) + endif + endfor + call sort(g:netrw_bookmarklist) + endif + + " construct and save .netrwbook + call setline(1,"let g:netrw_bookmarklist= ".string(g:netrw_bookmarklist)) + exe "sil! w! ".savefile + " call Decho("exe sil! w! ".savefile,'~'.expand("")) + endif + + " cleanup -- remove buffer used to construct history + let bgone= bufnr("%") + q! + exe "keepalt ".bgone."bwipe!" + + " call Dret("s:NetrwBookHistSave") +endfun + +" --------------------------------------------------------------------- +" s:NetrwBrowse: This function uses the command in g:netrw_list_cmd to provide a {{{2 +" list of the contents of a local or remote directory. It is assumed that the +" g:netrw_list_cmd has a string, USEPORT HOSTNAME, that needs to be substituted +" with the requested remote hostname first. +" Often called via: Explore/e dirname/etc -> netrw#LocalBrowseCheck() -> s:NetrwBrowse() +fun! s:NetrwBrowse(islocal,dirname) + if !exists("w:netrw_liststyle")|let w:netrw_liststyle= g:netrw_liststyle|endif + + " save alternate-file's filename if w:netrw_rexlocal doesn't exist + " This is useful when one edits a local file, then :e ., then :Rex + if a:islocal && !exists("w:netrw_rexfile") && bufname("#") != "" + let w:netrw_rexfile= bufname("#") + endif + + " s:NetrwBrowse : initialize history {{{3 + if !exists("s:netrw_initbookhist") + NetrwKeepj call s:NetrwBookHistRead() + endif + + " s:NetrwBrowse : simplify the dirname (especially for ".."s in dirnames) {{{3 + if a:dirname !~ '^\a\{3,}://' + let dirname= simplify(a:dirname) + else + let dirname= a:dirname + endif + + " repoint t:netrw_lexbufnr if appropriate + if exists("t:netrw_lexbufnr") && bufnr("%") == t:netrw_lexbufnr + let repointlexbufnr= 1 + endif + + " s:NetrwBrowse : sanity checks: {{{3 + if exists("s:netrw_skipbrowse") + unlet s:netrw_skipbrowse + return + endif + if !exists("*shellescape") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing shellescape()",69) + return + endif + if !exists("*fnameescape") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing fnameescape()",70) + return + endif + + " s:NetrwBrowse : save options: {{{3 + call s:NetrwOptionsSave("w:") + + " s:NetrwBrowse : re-instate any marked files {{{3 + if has("syntax") && exists("g:syntax_on") && g:syntax_on + if exists("s:netrwmarkfilelist_{bufnr('%')}") + exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/" + endif + endif + + if a:islocal && exists("w:netrw_acdkeep") && w:netrw_acdkeep + " s:NetrwBrowse : set up "safe" options for local directory/file {{{3 + if s:NetrwLcd(dirname) + return + endif + + elseif !a:islocal && dirname !~ '[\/]$' && dirname !~ '^"' + " s:NetrwBrowse : remote regular file handler {{{3 + if bufname(dirname) != "" + exe "NetrwKeepj b ".bufname(dirname) + else + " attempt transfer of remote regular file + + " remove any filetype indicator from end of dirname, except for the + " "this is a directory" indicator (/). + " There shouldn't be one of those here, anyway. + let path= substitute(dirname,'[*=@|]\r\=$','','e') + call s:RemotePathAnalysis(dirname) + + " s:NetrwBrowse : remote-read the requested file into current buffer {{{3 + call s:NetrwEnew(dirname) + call s:NetrwOptionsSafe(a:islocal) + setl ma noro + let b:netrw_curdir = dirname + let url = s:method."://".((s:user == "")? "" : s:user."@").s:machine.(s:port ? ":".s:port : "")."/".s:path + call s:NetrwBufRename(url) + exe "sil! NetrwKeepj keepalt doau BufReadPre ".fnameescape(s:fname) + sil call netrw#NetRead(2,url) + " netrw.vim and tar.vim have already handled decompression of the tarball; avoiding gzip.vim error + if s:path =~ '.bz2' + exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.bz2$','','')) + elseif s:path =~ '.gz' + exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.gz$','','')) + elseif s:path =~ '.gz' + exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.txz$','','')) + else + exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(s:fname) + endif + endif + + " s:NetrwBrowse : save certain window-oriented variables into buffer-oriented variables {{{3 + call s:SetBufWinVars() + call s:NetrwOptionsRestore("w:") + setl ma nomod noro + return + endif + + " use buffer-oriented WinVars if buffer variables exist but associated window variables don't {{{3 + call s:UseBufWinVars() + + " set up some variables {{{3 + let b:netrw_browser_active = 1 + let dirname = dirname + let s:last_sort_by = g:netrw_sort_by + + " set up menu {{{3 + NetrwKeepj call s:NetrwMenu(1) + + " get/set-up buffer {{{3 + let svpos = winsaveview() + + " NetrwGetBuffer might change buffers but s:rexposn_X was set for the + " previous buffer + let prevbufnr = bufnr('%') + let reusing= s:NetrwGetBuffer(a:islocal,dirname) + if exists("s:rexposn_".prevbufnr) + let s:rexposn_{bufnr('%')} = s:rexposn_{prevbufnr} + endif + + " maintain markfile highlighting + if has("syntax") && exists("g:syntax_on") && g:syntax_on + if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != "" + exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/" + else + 2match none + endif + endif + if reusing && line("$") > 1 + call s:NetrwOptionsRestore("w:") + setl noma nomod nowrap + return + endif + + " set b:netrw_curdir to the new directory name {{{3 + let b:netrw_curdir= dirname + if b:netrw_curdir =~ '[/\\]$' + let b:netrw_curdir= substitute(b:netrw_curdir,'[/\\]$','','e') + endif + if b:netrw_curdir =~ '\a:$' && has("win32") + let b:netrw_curdir= b:netrw_curdir."/" + endif + if b:netrw_curdir == '' + if has("amiga") + " On the Amiga, the empty string connotes the current directory + let b:netrw_curdir= getcwd() + else + " under unix, when the root directory is encountered, the result + " from the preceding substitute is an empty string. + let b:netrw_curdir= '/' + endif + endif + if !a:islocal && b:netrw_curdir !~ '/$' + let b:netrw_curdir= b:netrw_curdir.'/' + endif + + " ------------ + " (local only) {{{3 + " ------------ + if a:islocal + " Set up ShellCmdPost handling. Append current buffer to browselist + call s:LocalFastBrowser() + + " handle g:netrw_keepdir: set vim's current directory to netrw's notion of the current directory {{{3 + if !g:netrw_keepdir + if !exists("&l:acd") || !&l:acd + if s:NetrwLcd(b:netrw_curdir) + return + endif + endif + endif + + " -------------------------------- + " remote handling: {{{3 + " -------------------------------- + else + + " analyze dirname and g:netrw_list_cmd {{{3 + if dirname =~# "^NetrwTreeListing\>" + let dirname= b:netrw_curdir + elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir") + let dirname= substitute(b:netrw_curdir,'\\','/','g') + if dirname !~ '/$' + let dirname= dirname.'/' + endif + let b:netrw_curdir = dirname + else + let dirname = substitute(dirname,'\\','/','g') + endif + + let dirpat = '^\(\w\{-}\)://\(\w\+@\)\=\([^/]\+\)/\(.*\)$' + if dirname !~ dirpat + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw doesn't understand your dirname<".dirname.">",20) + endif + NetrwKeepj call s:NetrwOptionsRestore("w:") + setl noma nomod nowrap + return + endif + let b:netrw_curdir= dirname + endif " (additional remote handling) + + " ------------------------------- + " Perform Directory Listing: {{{3 + " ------------------------------- + NetrwKeepj call s:NetrwMaps(a:islocal) + NetrwKeepj call s:NetrwCommands(a:islocal) + NetrwKeepj call s:PerformListing(a:islocal) + + " restore option(s) + call s:NetrwOptionsRestore("w:") + + " If there is a rexposn: restore position with rexposn + " Otherwise : set rexposn + if exists("s:rexposn_".bufnr("%")) + NetrwKeepj call winrestview(s:rexposn_{bufnr('%')}) + if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt + NetrwKeepj exe w:netrw_bannercnt + endif + else + NetrwKeepj call s:SetRexDir(a:islocal,b:netrw_curdir) + endif + if v:version >= 700 && has("balloon_eval") && &beval == 0 && &l:bexpr == "" && !exists("g:netrw_nobeval") + let &l:bexpr= "netrw#BalloonHelp()" + setl beval + endif + + " repoint t:netrw_lexbufnr if appropriate + if exists("repointlexbufnr") + let t:netrw_lexbufnr= bufnr("%") + endif + + " restore position + if reusing + call winrestview(svpos) + endif + + " The s:LocalBrowseRefresh() function is called by an autocmd + " installed by s:LocalFastBrowser() when g:netrw_fastbrowse <= 1 (ie. slow or medium speed). + " However, s:NetrwBrowse() causes the FocusGained event to fire the first time. + return +endfun + +" --------------------------------------------------------------------- +" s:NetrwFile: because of g:netrw_keepdir, isdirectory(), type(), etc may or {{{2 +" may not apply correctly; ie. netrw's idea of the current directory may +" differ from vim's. This function insures that netrw's idea of the current +" directory is used. +" Returns a path to the file specified by a:fname +fun! s:NetrwFile(fname) + " "" call Dfunc("s:NetrwFile(fname<".a:fname.">) win#".winnr()) + " "" call Decho("g:netrw_keepdir =".(exists("g:netrw_keepdir")? g:netrw_keepdir : 'n/a'),'~'.expand("")) + " "" call Decho("g:netrw_cygwin =".(exists("g:netrw_cygwin")? g:netrw_cygwin : 'n/a'),'~'.expand("")) + " "" call Decho("g:netrw_liststyle=".(exists("g:netrw_liststyle")? g:netrw_liststyle : 'n/a'),'~'.expand("")) + " "" call Decho("w:netrw_liststyle=".(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand("")) + + " clean up any leading treedepthstring + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + let fname= substitute(a:fname,'^'.s:treedepthstring.'\+','','') + " "" call Decho("clean up any leading treedepthstring: fname<".fname.">",'~'.expand("")) + else + let fname= a:fname + endif + + if g:netrw_keepdir + " vim's idea of the current directory possibly may differ from netrw's + if !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + endif + + if !exists("g:netrw_cygwin") && has("win32") + if fname =~ '^\' || fname =~ '^\a:\' + " windows, but full path given + let ret= fname + " "" call Decho("windows+full path: isdirectory(".fname.")",'~'.expand("")) + else + " windows, relative path given + let ret= s:ComposePath(b:netrw_curdir,fname) + " "" call Decho("windows+rltv path: isdirectory(".fname.")",'~'.expand("")) + endif + + elseif fname =~ '^/' + " not windows, full path given + let ret= fname + " "" call Decho("unix+full path: isdirectory(".fname.")",'~'.expand("")) + else + " not windows, relative path given + let ret= s:ComposePath(b:netrw_curdir,fname) + " "" call Decho("unix+rltv path: isdirectory(".fname.")",'~'.expand("")) + endif + else + " vim and netrw agree on the current directory + let ret= fname + " "" call Decho("vim and netrw agree on current directory (g:netrw_keepdir=".g:netrw_keepdir.")",'~'.expand("")) + " "" call Decho("vim directory: ".getcwd(),'~'.expand("")) + " "" call Decho("netrw directory: ".(exists("b:netrw_curdir")? b:netrw_curdir : 'n/a'),'~'.expand("")) + endif + + " "" call Dret("s:NetrwFile ".ret) + return ret +endfun + +" --------------------------------------------------------------------- +" s:NetrwFileInfo: supports qf (query for file information) {{{2 +fun! s:NetrwFileInfo(islocal,fname) + " call Dfunc("s:NetrwFileInfo(islocal=".a:islocal." fname<".a:fname.">) b:netrw_curdir<".b:netrw_curdir.">") + let ykeep= @@ + if a:islocal + let lsopt= "-lsad" + if g:netrw_sizestyle =~# 'H' + let lsopt= "-lsadh" + elseif g:netrw_sizestyle =~# 'h' + let lsopt= "-lsadh --si" + endif + " call Decho("(s:NetrwFileInfo) lsopt<".lsopt.">") + if (has("unix") || has("macunix")) && executable("/bin/ls") + + if getline(".") == "../" + echo system("/bin/ls ".lsopt." ".s:ShellEscape("..")) + " call Decho("#1: echo system(/bin/ls -lsad ".s:ShellEscape(..).")",'~'.expand("")) + + elseif w:netrw_liststyle == s:TREELIST && getline(".") !~ '^'.s:treedepthstring + echo system("/bin/ls ".lsopt." ".s:ShellEscape(b:netrw_curdir)) + " call Decho("#2: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir).")",'~'.expand("")) + + elseif exists("b:netrw_curdir") + echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,a:fname))) + " call Decho("#3: echo system(/bin/ls -lsad ".s:ShellEscape(b:netrw_curdir.a:fname).")",'~'.expand("")) + + else + " call Decho('using ls '.a:fname." using cwd<".getcwd().">",'~'.expand("")) + echo system("/bin/ls ".lsopt." ".s:ShellEscape(s:NetrwFile(a:fname))) + " call Decho("#5: echo system(/bin/ls -lsad ".s:ShellEscape(a:fname).")",'~'.expand("")) + endif + else + " use vim functions to return information about file below cursor + " call Decho("using vim functions to query for file info",'~'.expand("")) + if !isdirectory(s:NetrwFile(a:fname)) && !filereadable(s:NetrwFile(a:fname)) && a:fname =~ '[*@/]' + let fname= substitute(a:fname,".$","","") + else + let fname= a:fname + endif + let t = getftime(s:NetrwFile(fname)) + let sz = getfsize(s:NetrwFile(fname)) + if g:netrw_sizestyle =~# "[hH]" + let sz= s:NetrwHumanReadable(sz) + endif + echo a:fname.": ".sz." ".strftime(g:netrw_timefmt,getftime(s:NetrwFile(fname))) + " call Decho("fname.": ".sz." ".strftime(g:netrw_timefmt,getftime(fname)),'~'.expand("")) + endif + else + echo "sorry, \"qf\" not supported yet for remote files" + endif + let @@= ykeep + " call Dret("s:NetrwFileInfo") +endfun + +" --------------------------------------------------------------------- +" s:NetrwFullPath: returns the full path to a directory and/or file {{{2 +fun! s:NetrwFullPath(filename) + " " call Dfunc("s:NetrwFullPath(filename<".a:filename.">)") + let filename= a:filename + if filename !~ '^/' + let filename= resolve(getcwd().'/'.filename) + endif + if filename != "/" && filename =~ '/$' + let filename= substitute(filename,'/$','','') + endif + " " call Dret("s:NetrwFullPath <".filename.">") + return filename +endfun + +" --------------------------------------------------------------------- +" s:NetrwGetBuffer: [get a new|find an old netrw] buffer for a netrw listing {{{2 +" returns 0=cleared buffer +" 1=re-used buffer (buffer not cleared) +" Nov 09, 2020: tst952 shows that when user does :set hidden that NetrwGetBuffer will come up with a [No Name] buffer (hid fix) +fun! s:NetrwGetBuffer(islocal,dirname) + " call Dfunc("s:NetrwGetBuffer(islocal=".a:islocal." dirname<".a:dirname.">) liststyle=".g:netrw_liststyle) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." hid=".&hid,'~'.expand("")) + " call Decho("netrwbuf dictionary=".(exists("s:netrwbuf")? string(s:netrwbuf) : 'n/a'),'~'.expand("")) + " call Dredir("ls!","s:NetrwGetBuffer") + let dirname= a:dirname + + " re-use buffer if possible {{{3 + " call Decho("--re-use a buffer if possible--",'~'.expand("")) + if !exists("s:netrwbuf") + " call Decho(" s:netrwbuf initialized to {}",'~'.expand("")) + let s:netrwbuf= {} + endif + " call Decho(" s:netrwbuf =".string(s:netrwbuf),'~'.expand("")) + " call Decho(" w:netrw_liststyle =".(exists("w:netrw_liststyle")? w:netrw_liststyle : "n/a"),'~'.expand("")) + + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + let bufnum = -1 + + if !empty(s:netrwbuf) && has_key(s:netrwbuf,s:NetrwFullPath(dirname)) + if has_key(s:netrwbuf,"NetrwTreeListing") + let bufnum= s:netrwbuf["NetrwTreeListing"] + else + let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)] + endif + " call Decho(" NetrwTreeListing: bufnum#".bufnum,'~'.expand("")) + if !bufexists(bufnum) + call remove(s:netrwbuf,"NetrwTreeListing") + let bufnum= -1 + endif + elseif bufnr("NetrwTreeListing") != -1 + let bufnum= bufnr("NetrwTreeListing") + " call Decho(" NetrwTreeListing".": bufnum#".bufnum,'~'.expand("")) + else + " call Decho(" did not find a NetrwTreeListing buffer",'~'.expand("")) + let bufnum= -1 + endif + + elseif has_key(s:netrwbuf,s:NetrwFullPath(dirname)) + let bufnum= s:netrwbuf[s:NetrwFullPath(dirname)] + " call Decho(" lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnum,'~'.expand("")) + if !bufexists(bufnum) + call remove(s:netrwbuf,s:NetrwFullPath(dirname)) + let bufnum= -1 + endif + + else + " call Decho(" lookup netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."] not a key",'~'.expand("")) + let bufnum= -1 + endif + " call Decho(" bufnum#".bufnum,'~'.expand("")) + + " highjack the current buffer + " IF the buffer already has the desired name + " AND it is empty + let curbuf = bufname("%") + if curbuf == '.' + let curbuf = getcwd() + endif + " call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)") + " call Decho("deciding if netrw may highjack the current buffer#".bufnr("%")."<".curbuf.">",'~'.expand("")) + " call Decho("..dirname<".dirname."> IF dirname == bufname",'~'.expand("")) + " call Decho("..curbuf<".curbuf.">",'~'.expand("")) + " call Decho("..line($)=".line("$")." AND this is 1",'~'.expand("")) + " call Decho("..getline(%)<".getline("%")."> AND this line is empty",'~'.expand("")) + if dirname == curbuf && line("$") == 1 && getline("%") == "" + " call Dret("s:NetrwGetBuffer 0 : highjacking buffer#".bufnr("%")) + return 0 + else " DEBUG + " call Decho("..did NOT highjack buffer",'~'.expand("")) + endif + " Aug 14, 2021: was thinking about looking for a [No Name] buffer here and using it, but that might cause problems + + " get enew buffer and name it -or- re-use buffer {{{3 + if bufnum < 0 " get enew buffer and name it + " call Decho("--get enew buffer and name it (bufnum#".bufnum."<0 OR bufexists(".bufnum.")=".bufexists(bufnum)."==0)",'~'.expand("")) + call s:NetrwEnew(dirname) + " call Decho(" got enew buffer#".bufnr("%")." (altbuf<".expand("#").">)",'~'.expand("")) + " name the buffer + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + " Got enew buffer; transform into a NetrwTreeListing + " call Decho("--transform enew buffer#".bufnr("%")." into a NetrwTreeListing --",'~'.expand("")) + let w:netrw_treebufnr = bufnr("%") + call s:NetrwBufRename("NetrwTreeListing") + if g:netrw_use_noswf + setl nobl bt=nofile noswf + else + setl nobl bt=nofile + endif + nnoremap [[ :sil call TreeListMove('[[') + nnoremap ]] :sil call TreeListMove(']]') + nnoremap [] :sil call TreeListMove('[]') + nnoremap ][ :sil call TreeListMove('][') + " call Decho(" tree listing bufnr=".w:netrw_treebufnr,'~'.expand("")) + else + call s:NetrwBufRename(dirname) + " enter the new buffer into the s:netrwbuf dictionary + let s:netrwbuf[s:NetrwFullPath(dirname)]= bufnr("%") + " call Decho("update netrwbuf dictionary: s:netrwbuf[".s:NetrwFullPath(dirname)."]=".bufnr("%"),'~'.expand("")) + " call Decho("netrwbuf dictionary=".string(s:netrwbuf),'~'.expand("")) + endif + " call Decho(" named enew buffer#".bufnr("%")."<".bufname("%").">",'~'.expand("")) + + else " Re-use the buffer + " call Decho("--re-use buffer#".bufnum." (bufnum#".bufnum.">=0 AND bufexists(".bufnum.")=".bufexists(bufnum)."!=0)",'~'.expand("")) + " ignore all events + let eikeep= &ei + setl ei=all + + if &ft == "netrw" + " call Decho("buffer type is netrw; not using keepalt with b ".bufnum) + exe "sil! NetrwKeepj noswapfile b ".bufnum + " call Dredir("ls!","one") + else + " call Decho("buffer type is not netrw; using keepalt with b ".bufnum) + call s:NetrwEditBuf(bufnum) + " call Dredir("ls!","two") + endif + " call Decho(" line($)=".line("$"),'~'.expand("")) + if bufname("%") == '.' + call s:NetrwBufRename(getcwd()) + endif + + " restore ei + let &ei= eikeep + + if line("$") <= 1 && getline(1) == "" + " empty buffer + NetrwKeepj call s:NetrwListSettings(a:islocal) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but its empty, so refresh it") + return 0 + + elseif g:netrw_fastbrowse == 0 || (a:islocal && g:netrw_fastbrowse == 1) + " call Decho("g:netrw_fastbrowse=".g:netrw_fastbrowse." a:islocal=".a:islocal.": clear buffer",'~'.expand("")) + NetrwKeepj call s:NetrwListSettings(a:islocal) + sil NetrwKeepj %d _ + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but refreshing due to g:netrw_fastbrowse=".g:netrw_fastbrowse) + return 0 + + elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + " call Decho("--re-use tree listing--",'~'.expand("")) + " call Decho(" clear buffer<".expand("%")."> with :%d",'~'.expand("")) + setl ma + sil NetrwKeepj %d _ + NetrwKeepj call s:NetrwListSettings(a:islocal) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Dret("s:NetrwGetBuffer 0 : re-using buffer#".bufnr("%").", but treelist mode always needs a refresh") + return 0 + + else + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Dret("s:NetrwGetBuffer 1") + return 1 + endif + endif + + " do netrw settings: make this buffer not-a-file, modifiable, not line-numbered, etc {{{3 + " fastbrowse Local Remote Hiding a buffer implies it may be re-used (fast) + " slow 0 D D Deleting a buffer implies it will not be re-used (slow) + " med 1 D H + " fast 2 H H + " call Decho("--do netrw settings: make this buffer#".bufnr("%")." not-a-file, modifiable, not line-numbered, etc--",'~'.expand("")) + let fname= expand("%") + NetrwKeepj call s:NetrwListSettings(a:islocal) + call s:NetrwBufRename(fname) + + " delete all lines from buffer {{{3 + " call Decho("--delete all lines from buffer--",'~'.expand("")) + " call Decho(" clear buffer<".expand("%")."> with :%d",'~'.expand("")) + sil! keepalt NetrwKeepj %d _ + + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Dret("s:NetrwGetBuffer 0") + return 0 +endfun + +" --------------------------------------------------------------------- +" s:NetrwGetcwd: get the current directory. {{{2 +" Change backslashes to forward slashes, if any. +" If doesc is true, escape certain troublesome characters +fun! s:NetrwGetcwd(doesc) + " call Dfunc("NetrwGetcwd(doesc=".a:doesc.")") + let curdir= substitute(getcwd(),'\\','/','ge') + if curdir !~ '[\/]$' + let curdir= curdir.'/' + endif + if a:doesc + let curdir= fnameescape(curdir) + endif + " call Dret("NetrwGetcwd <".curdir.">") + return curdir +endfun + +" --------------------------------------------------------------------- +" s:NetrwGetWord: it gets the directory/file named under the cursor {{{2 +fun! s:NetrwGetWord() + " call Dfunc("s:NetrwGetWord() liststyle=".s:ShowStyle()." virtcol=".virtcol(".")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + let keepsol= &l:sol + setl nosol + + call s:UseBufWinVars() + + " insure that w:netrw_liststyle is set up + if !exists("w:netrw_liststyle") + if exists("g:netrw_liststyle") + let w:netrw_liststyle= g:netrw_liststyle + else + let w:netrw_liststyle= s:THINLIST + endif + " call Decho("w:netrw_liststyle=".w:netrw_liststyle,'~'.expand("")) + endif + + if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt + " Active Banner support + " call Decho("active banner handling",'~'.expand("")) + NetrwKeepj norm! 0 + let dirname= "./" + let curline= getline('.') + + if curline =~# '"\s*Sorted by\s' + NetrwKeepj norm! "_s + let s:netrw_skipbrowse= 1 + echo 'Pressing "s" also works' + + elseif curline =~# '"\s*Sort sequence:' + let s:netrw_skipbrowse= 1 + echo 'Press "S" to edit sorting sequence' + + elseif curline =~# '"\s*Quick Help:' + NetrwKeepj norm! ? + let s:netrw_skipbrowse= 1 + + elseif curline =~# '"\s*\%(Hiding\|Showing\):' + NetrwKeepj norm! a + let s:netrw_skipbrowse= 1 + echo 'Pressing "a" also works' + + elseif line("$") > w:netrw_bannercnt + exe 'sil NetrwKeepj '.w:netrw_bannercnt + endif + + elseif w:netrw_liststyle == s:THINLIST + " call Decho("thin column handling",'~'.expand("")) + NetrwKeepj norm! 0 + let dirname= substitute(getline('.'),'\t -->.*$','','') + + elseif w:netrw_liststyle == s:LONGLIST + " call Decho("long column handling",'~'.expand("")) + NetrwKeepj norm! 0 + let dirname= substitute(getline('.'),'^\(\%(\S\+ \)*\S\+\).\{-}$','\1','e') + + elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + " call Decho("treelist handling",'~'.expand("")) + let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e') + let dirname= substitute(dirname,'\t -->.*$','','') + + else + " call Decho("obtain word from wide listing",'~'.expand("")) + let dirname= getline('.') + + if !exists("b:netrw_cpf") + let b:netrw_cpf= 0 + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif' + call histdel("/",-1) + " "call Decho("computed cpf=".b:netrw_cpf,'~'.expand("")) + endif + + " call Decho("buf#".bufnr("%")."<".bufname("%").">",'~'.expand("")) + let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf + " call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart." bannercnt=".w:netrw_bannercnt,'~'.expand("")) + " call Decho("1: dirname<".dirname.">",'~'.expand("")) + if filestart == 0 + NetrwKeepj norm! 0ma + else + call cursor(line("."),filestart+1) + NetrwKeepj norm! ma + endif + + let dict={} + " save the unnamed register and register 0-9 and a + let dict.a=[getreg('a'), getregtype('a')] + for i in range(0, 9) + let dict[i] = [getreg(i), getregtype(i)] + endfor + let dict.unnamed = [getreg(''), getregtype('')] + + let eofname= filestart + b:netrw_cpf + 1 + if eofname <= col("$") + call cursor(line("."),filestart+b:netrw_cpf+1) + NetrwKeepj norm! "ay`a + else + NetrwKeepj norm! "ay$ + endif + + let dirname = @a + call s:RestoreRegister(dict) + + " call Decho("2: dirname<".dirname.">",'~'.expand("")) + let dirname= substitute(dirname,'\s\+$','','e') + " call Decho("3: dirname<".dirname.">",'~'.expand("")) + endif + + " symlinks are indicated by a trailing "@". Remove it before further processing. + let dirname= substitute(dirname,"@$","","") + + " executables are indicated by a trailing "*". Remove it before further processing. + let dirname= substitute(dirname,"\*$","","") + + let &l:sol= keepsol + + " call Dret("s:NetrwGetWord <".dirname.">") + return dirname +endfun + +" --------------------------------------------------------------------- +" s:NetrwListSettings: make standard settings for making a netrw listing {{{2 +" g:netrw_bufsettings will be used after the listing is produced. +" Called by s:NetrwGetBuffer() +fun! s:NetrwListSettings(islocal) + " call Dfunc("s:NetrwListSettings(islocal=".a:islocal.")") + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + let fname= bufname("%") + " " call Decho("setl bt=nofile nobl ma nonu nowrap noro nornu",'~'.expand("")) + " nobl noma nomod nonu noma nowrap ro nornu (std g:netrw_bufsettings) + setl bt=nofile nobl ma nonu nowrap noro nornu + call s:NetrwBufRename(fname) + if g:netrw_use_noswf + setl noswf + endif + " call Dredir("ls!","s:NetrwListSettings") + " call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand("")) + exe "setl ts=".(g:netrw_maxfilenamelen+1) + setl isk+=.,~,- + if g:netrw_fastbrowse > a:islocal + setl bh=hide + else + setl bh=delete + endif + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Dret("s:NetrwListSettings") +endfun + +" --------------------------------------------------------------------- +" s:NetrwListStyle: change list style (thin - long - wide - tree) {{{2 +" islocal=0: remote browsing +" =1: local browsing +fun! s:NetrwListStyle(islocal) + let ykeep = @@ + let fname = s:NetrwGetWord() + if !exists("w:netrw_liststyle")|let w:netrw_liststyle= g:netrw_liststyle|endif + let svpos = winsaveview() + let w:netrw_liststyle = (w:netrw_liststyle + 1) % s:MAXLIST + + " repoint t:netrw_lexbufnr if appropriate + if exists("t:netrw_lexbufnr") && bufnr("%") == t:netrw_lexbufnr + let repointlexbufnr= 1 + endif + + if w:netrw_liststyle == s:THINLIST + " use one column listing + let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') + + elseif w:netrw_liststyle == s:LONGLIST + " use long list + let g:netrw_list_cmd = g:netrw_list_cmd." -l" + + elseif w:netrw_liststyle == s:WIDELIST + " give wide list + let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') + + elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') + + else + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"bad value for g:netrw_liststyle (=".w:netrw_liststyle.")",46) + let g:netrw_liststyle = s:THINLIST + let w:netrw_liststyle = g:netrw_liststyle + let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge') + endif + setl ma noro + + " clear buffer - this will cause NetrwBrowse/LocalBrowseCheck to do a refresh + sil! NetrwKeepj %d _ + " following prevents tree listing buffer from being marked "modified" + setl nomod + + " refresh the listing + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call s:NetrwCursor(0) + + " repoint t:netrw_lexbufnr if appropriate + if exists("repointlexbufnr") + let t:netrw_lexbufnr= bufnr("%") + endif + + " restore position; keep cursor on the filename + " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("")) + NetrwKeepj call winrestview(svpos) + let @@= ykeep + +endfun + +" --------------------------------------------------------------------- +" s:NetrwBannerCtrl: toggles the display of the banner {{{2 +fun! s:NetrwBannerCtrl(islocal) + let ykeep= @@ + " toggle the banner (enable/suppress) + let g:netrw_banner= !g:netrw_banner + + " refresh the listing + let svpos= winsaveview() + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + + " keep cursor on the filename + if g:netrw_banner && exists("w:netrw_bannercnt") && line(".") >= w:netrw_bannercnt + let fname= s:NetrwGetWord() + sil NetrwKeepj $ + let result= search('\%(^\%(|\+\s\)\=\|\s\{2,}\)\zs'.escape(fname,'.\[]*$^').'\%(\s\{2,}\|$\)','bc') + " " call Decho("search result=".result." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'N/A'),'~'.expand("")) + if result <= 0 && exists("w:netrw_bannercnt") + exe "NetrwKeepj ".w:netrw_bannercnt + endif + endif + let @@= ykeep + " call Dret("s:NetrwBannerCtrl : g:netrw_banner=".g:netrw_banner) +endfun + +" --------------------------------------------------------------------- +" s:NetrwBookmark: supports :NetrwMB[!] [file]s {{{2 +" +" No bang: enters files/directories into Netrw's bookmark system +" No argument and in netrw buffer: +" if there are marked files: bookmark marked files +" otherwise : bookmark file/directory under cursor +" No argument and not in netrw buffer: bookmarks current open file +" Has arguments: globs them individually and bookmarks them +" +" With bang: deletes files/directories from Netrw's bookmark system +fun! s:NetrwBookmark(del,...) + if a:0 == 0 + if &ft == "netrw" + let curbufnr = bufnr("%") + + if exists("s:netrwmarkfilelist_{curbufnr}") + " for every filename in the marked list + let svpos = winsaveview() + let islocal= expand("%") !~ '^\a\{3,}://' + for fname in s:netrwmarkfilelist_{curbufnr} + if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif + endfor + let curdir = exists("b:netrw_curdir")? b:netrw_curdir : getcwd() + call s:NetrwUnmarkList(curbufnr,curdir) + NetrwKeepj call s:NetrwRefresh(islocal,s:NetrwBrowseChgDir(islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + else + let fname= s:NetrwGetWord() + if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif + endif + + else + " bookmark currently open file + let fname= expand("%") + if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif + endif + + else + " bookmark specified files + " attempts to infer if working remote or local + " by deciding if the current file begins with an url + " Globbing cannot be done remotely. + let islocal= expand("%") !~ '^\a\{3,}://' + let i = 1 + while i <= a:0 + if islocal + if v:version > 704 || (v:version == 704 && has("patch656")) + let mbfiles= glob(fnameescape(a:{i}),0,1,1) + else + let mbfiles= glob(fnameescape(a:{i}),0,1) + endif + else + let mbfiles= [a:{i}] + endif + for mbfile in mbfiles + if a:del|call s:DeleteBookmark(mbfile)|else|call s:MakeBookmark(mbfile)|endif + endfor + let i= i + 1 + endwhile + endif + + " update the menu + call s:NetrwBookmarkMenu() +endfun + +" --------------------------------------------------------------------- +" s:NetrwBookmarkMenu: Uses menu priorities {{{2 +" .2.[cnt] for bookmarks, and +" .3.[cnt] for history +" (see s:NetrwMenu()) +fun! s:NetrwBookmarkMenu() + if !exists("s:netrw_menucnt") + return + endif + " call Dfunc("NetrwBookmarkMenu() histcnt=".g:netrw_dirhistcnt." menucnt=".s:netrw_menucnt) + + " the following test assures that gvim is running, has menus available, and has menus enabled. + if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu + if exists("g:NetrwTopLvlMenu") + " call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand("")) + exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks' + exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete' + endif + if !exists("s:netrw_initbookhist") + call s:NetrwBookHistRead() + endif + + " show bookmarked places + if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0 + let cnt= 1 + for bmd in g:netrw_bookmarklist + " call Decho('sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmark.'.bmd.' :e '.bmd,'~'.expand("")) + let bmd= escape(bmd,g:netrw_menu_escape) + + " show bookmarks for goto menu + exe 'sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks.'.bmd.' :e '.bmd."\" + + " show bookmarks for deletion menu + exe 'sil! menu '.g:NetrwMenuPriority.".8.2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete.'.bmd.' '.cnt."mB" + let cnt= cnt + 1 + endfor + + endif + + " show directory browsing history + if g:netrw_dirhistmax > 0 + let cnt = g:netrw_dirhistcnt + let first = 1 + let histcnt = 0 + while ( first || cnt != g:netrw_dirhistcnt ) + let histcnt = histcnt + 1 + let priority = g:netrw_dirhistcnt + histcnt + if exists("g:netrw_dirhist_{cnt}") + let histdir= escape(g:netrw_dirhist_{cnt},g:netrw_menu_escape) + " call Decho('sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.' :e '.histdir,'~'.expand("")) + exe 'sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.' :e '.histdir."\" + endif + let first = 0 + let cnt = ( cnt - 1 ) % g:netrw_dirhistmax + if cnt < 0 + let cnt= cnt + g:netrw_dirhistmax + endif + endwhile + endif + + endif + " call Dret("NetrwBookmarkMenu") +endfun + +" --------------------------------------------------------------------- +" s:NetrwBrowseChgDir: constructs a new directory based on the current {{{2 +" directory and a new directory name. Also, if the +" "new directory name" is actually a file, +" NetrwBrowseChgDir() edits the file. +" cursor=0: newdir is relative to b:netrw_curdir +" =1: newdir is relative to the path to the word under the cursor in +" tree view +fun! s:NetrwBrowseChgDir(islocal,newdir,cursor,...) + let ykeep= @@ + if !exists("b:netrw_curdir") + " Don't try to change-directory: this can happen, for example, when netrw#ErrorMsg has been called + " and the current window is the NetrwMessage window. + let @@= ykeep + return + endif + + " NetrwBrowseChgDir; save options and initialize {{{3 + call s:SavePosn(s:netrw_posn) + NetrwKeepj call s:NetrwOptionsSave("s:") + NetrwKeepj call s:NetrwOptionsSafe(a:islocal) + + let newdir = a:newdir + if a:cursor && exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop") + " dirname is the path to the word under the cursor + let dirname = s:NetrwTreePath(w:netrw_treetop) + " newdir resolves to a directory and points to a directory in dirname + " /tmp/test/folder_symlink/ -> /tmp/test/original_folder/ + if a:islocal && fnamemodify(dirname, ':t') == newdir && isdirectory(resolve(dirname)) && resolve(dirname) == resolve(newdir) + let dirname = fnamemodify(resolve(dirname), ':p:h:h') + let newdir = fnamemodify(resolve(newdir), ':t') + endif + " Remove trailing "/" + let dirname = substitute(dirname, "/$", "", "") + + " If the word under the cursor is a directory (except for ../), NetrwTreePath + " returns the full path, including the word under the cursor, remove it + if newdir =~ "/$" && newdir != "../" + let dirname = fnamemodify(dirname, ":h") + endif + else + let dirname = b:netrw_curdir + endif + if has("win32") + let dirname = substitute(dirname,'\\','/','ge') + endif + let dolockout = 0 + let dorestore = 1 + + " ignore s when done in the banner + if g:netrw_banner + if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt && line("$") >= w:netrw_bannercnt + if getline(".") =~# 'Quick Help' + let g:netrw_quickhelp= (g:netrw_quickhelp + 1)%len(s:QuickHelp) + setl ma noro nowrap + NetrwKeepj call setline(line('.'),'" Quick Help: :help '.s:QuickHelp[g:netrw_quickhelp]) + setl noma nomod nowrap + NetrwKeepj call s:NetrwOptionsRestore("s:") + endif + endif + endif + + " set up o/s-dependent directory recognition pattern + if has("amiga") + let dirpat= '[\/:]$' + else + let dirpat= '[\/]$' + endif + + if dirname !~ dirpat + " apparently vim is "recognizing" that it is in a directory and + " is removing the trailing "/". Bad idea, so let's put it back. + let dirname= dirname.'/' + endif + + if newdir !~ dirpat && !(a:islocal && isdirectory(s:NetrwFile(s:ComposePath(dirname,newdir)))) + " ------------------------------ + " NetrwBrowseChgDir: edit a file {{{3 + " ------------------------------ + + " save position for benefit of Rexplore + let s:rexposn_{bufnr("%")}= winsaveview() + + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") && newdir !~ '^\(/\|\a:\)' + if dirname =~ '/$' + let dirname= dirname.newdir + else + let dirname= dirname."/".newdir + endif + elseif newdir =~ '^\(/\|\a:\)' + let dirname= newdir + else + let dirname= s:ComposePath(dirname,newdir) + endif + " this lets netrw#BrowseX avoid the edit + if a:0 < 1 + NetrwKeepj call s:NetrwOptionsRestore("s:") + let curdir= b:netrw_curdir + if !exists("s:didsplit") + if type(g:netrw_browse_split) == 3 + " open file in server + " Note that g:netrw_browse_split is a List: [servername,tabnr,winnr] + call s:NetrwServerEdit(a:islocal,dirname) + return + + elseif g:netrw_browse_split == 1 + " horizontally splitting the window first + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize + exe "keepalt ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s" + if !&ea + keepalt wincmd _ + endif + call s:SetRexDir(a:islocal,curdir) + + elseif g:netrw_browse_split == 2 + " vertically splitting the window first + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize + exe "keepalt ".(g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s" + if !&ea + keepalt wincmd | + endif + call s:SetRexDir(a:islocal,curdir) + + elseif g:netrw_browse_split == 3 + " open file in new tab + keepalt tabnew + if !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + endif + call s:SetRexDir(a:islocal,curdir) + + elseif g:netrw_browse_split == 4 + " act like "P" (ie. open previous window) + if s:NetrwPrevWinOpen(2) == 3 + let @@= ykeep + return + endif + call s:SetRexDir(a:islocal,curdir) + + else + " handling a file, didn't split, so remove menu + call s:NetrwMenu(0) + " optional change to window + if g:netrw_chgwin >= 1 + if winnr("$")+1 == g:netrw_chgwin + " if g:netrw_chgwin is set to one more than the last window, then + " vertically split the last window to make that window available. + let curwin= winnr() + exe "NetrwKeepj keepalt ".winnr("$")."wincmd w" + vs + exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd ".curwin + endif + exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w" + endif + call s:SetRexDir(a:islocal,curdir) + endif + + endif + + " the point where netrw actually edits the (local) file + " if its local only: LocalBrowseCheck() doesn't edit a file, but NetrwBrowse() will + " use keepalt to support :e # to return to a directory listing + if !&mod + " if e the new file would fail due to &mod, then don't change any of the flags + let dolockout= 1 + endif + if a:islocal + " some like c-^ to return to the last edited file + " others like c-^ to return to the netrw buffer + " Apr 30, 2020: used to have e! here. That can cause loss of a modified file, + " so emit error E37 instead. + call s:NetrwEditFile("e","",dirname) + call s:NetrwCursor(1) + if &hidden || &bufhidden == "hide" + " file came from vim's hidden storage. Don't "restore" options with it. + let dorestore= 0 + endif + else + endif + + " handle g:Netrw_funcref -- call external-to-netrw functions + " This code will handle g:Netrw_funcref as an individual function reference + " or as a list of function references. It will ignore anything that's not + " a function reference. See :help Funcref for information about function references. + if exists("g:Netrw_funcref") + if type(g:Netrw_funcref) == 2 + NetrwKeepj call g:Netrw_funcref() + elseif type(g:Netrw_funcref) == 3 + for Fncref in g:Netrw_funcref + if type(Fncref) == 2 + NetrwKeepj call Fncref() + endif + endfor + endif + endif + endif + + elseif newdir =~ '^/' + " ---------------------------------------------------- + " NetrwBrowseChgDir: just go to the new directory spec {{{3 + " ---------------------------------------------------- + let dirname = newdir + NetrwKeepj call s:SetRexDir(a:islocal,dirname) + NetrwKeepj call s:NetrwOptionsRestore("s:") + norm! m` + + elseif newdir == './' + " --------------------------------------------- + " NetrwBrowseChgDir: refresh the directory list {{{3 + " --------------------------------------------- + NetrwKeepj call s:SetRexDir(a:islocal,dirname) + norm! m` + + elseif newdir == '../' + " -------------------------------------- + " NetrwBrowseChgDir: go up one directory {{{3 + " -------------------------------------- + + if w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") + " force a refresh + setl noro ma + NetrwKeepj %d _ + endif + + if has("amiga") + " amiga + if a:islocal + let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+$\)','\1','') + let dirname= substitute(dirname,'/$','','') + else + let dirname= substitute(dirname,'^\(.*[/:]\)\([^/]\+/$\)','\1','') + endif + + elseif !g:netrw_cygwin && has("win32") + " windows + if a:islocal + let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','') + if dirname == "" + let dirname= '/' + endif + else + let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','') + endif + if dirname =~ '^\a:$' + let dirname= dirname.'/' + endif + + else + " unix or cygwin + if a:islocal + let dirname= substitute(dirname,'^\(.*\)/\([^/]\+\)/$','\1','') + if dirname == "" + let dirname= '/' + endif + else + let dirname= substitute(dirname,'^\(\a\{3,}://.\{-}/\{1,2}\)\(.\{-}\)\([^/]\+\)/$','\1\2','') + endif + endif + NetrwKeepj call s:SetRexDir(a:islocal,dirname) + norm! m` + + elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") + " -------------------------------------- + " NetrwBrowseChgDir: Handle Tree Listing {{{3 + " -------------------------------------- + " force a refresh (for TREELIST, NetrwTreeDir() will force the refresh) + setl noro ma + if !(exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")) + NetrwKeepj %d _ + endif + let treedir = s:NetrwTreeDir(a:islocal) + let s:treecurpos = winsaveview() + let haskey = 0 + + " search treedict for tree dir as-is + if has_key(w:netrw_treedict,treedir) + let haskey= 1 + else + endif + + " search treedict for treedir with a [/@] appended + if !haskey && treedir !~ '[/@]$' + if has_key(w:netrw_treedict,treedir."/") + let treedir= treedir."/" + let haskey = 1 + else + endif + endif + + " search treedict for treedir with any trailing / elided + if !haskey && treedir =~ '/$' + let treedir= substitute(treedir,'/$','','') + if has_key(w:netrw_treedict,treedir) + let haskey = 1 + else + endif + endif + + if haskey + " close tree listing for selected subdirectory + call remove(w:netrw_treedict,treedir) + let dirname= w:netrw_treetop + else + " go down one directory + let dirname= substitute(treedir,'/*$','/','') + endif + NetrwKeepj call s:SetRexDir(a:islocal,dirname) + let s:treeforceredraw = 1 + + else + " ---------------------------------------- + " NetrwBrowseChgDir: Go down one directory {{{3 + " ---------------------------------------- + let dirname = s:ComposePath(dirname,newdir) + NetrwKeepj call s:SetRexDir(a:islocal,dirname) + norm! m` + endif + + " -------------------------------------- + " NetrwBrowseChgDir: Restore and Cleanup {{{3 + " -------------------------------------- + if dorestore + " dorestore is zero'd when a local file was hidden or bufhidden; + " in such a case, we want to keep whatever settings it may have. + NetrwKeepj call s:NetrwOptionsRestore("s:") + endif + if dolockout && dorestore + if filewritable(dirname) + setl ma noro nomod + else + setl ma ro nomod + endif + endif + call s:RestorePosn(s:netrw_posn) + let @@= ykeep + + return dirname +endfun + +" --------------------------------------------------------------------- +" s:NetrwBrowseUpDir: implements the "-" mappings {{{2 +" for thin, long, and wide: cursor placed just after banner +" for tree, keeps cursor on current filename +fun! s:NetrwBrowseUpDir(islocal) + if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt-1 + " this test needed because occasionally this function seems to be incorrectly called + " when multiple leftmouse clicks are taken when atop the one line help in the banner. + " I'm allowing the very bottom line to permit a "-" exit so that one may escape empty + " directories. + return + endif + + norm! 0 + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") + let curline= getline(".") + let swwline= winline() - 1 + if exists("w:netrw_treetop") + let b:netrw_curdir= w:netrw_treetop + elseif exists("b:netrw_curdir") + let w:netrw_treetop= b:netrw_curdir + else + let w:netrw_treetop= getcwd() + let b:netrw_curdir = w:netrw_treetop + endif + let curfile = getline(".") + let curpath = s:NetrwTreePath(w:netrw_treetop) + if a:islocal + call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0)) + else + call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0)) + endif + if w:netrw_treetop == '/' + keepj call search('^\M'.curfile,"w") + elseif curfile == '../' + keepj call search('^\M'.curfile,"wb") + else + while 1 + keepj call search('^\M'.s:treedepthstring.curfile,"wb") + let treepath= s:NetrwTreePath(w:netrw_treetop) + if treepath == curpath + break + endif + endwhile + endif + + else + call s:SavePosn(s:netrw_posn) + if exists("b:netrw_curdir") + let curdir= b:netrw_curdir + else + let curdir= expand(getcwd()) + endif + if a:islocal + call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../',0)) + else + call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,'../',0)) + endif + call s:RestorePosn(s:netrw_posn) + let curdir= substitute(curdir,'^.*[\/]','','') + let curdir= '\<'. escape(curdir, '~'). '/' + call search(curdir,'wc') + endif +endfun + +func s:redir() + " set up redirection (avoids browser messages) + " by default if not set, g:netrw_suppress_gx_mesg is true + if get(g:, 'netrw_suppress_gx_mesg', 1) + if &srr =~# "%s" + return printf(&srr, has("win32") ? "nul" : "/dev/null") + else + return &srr .. (has("win32") ? "nul" : "/dev/null") + endif + endif + return '' +endfunc + +if has('unix') + if has('win32unix') + " Cygwin provides cygstart + if executable('cygstart') + fun! netrw#Launch(args) + exe 'silent ! cygstart --hide' a:args s:redir() | redraw! + endfun + elseif !empty($MSYSTEM) && executable('start') + " MSYS2/Git Bash comes by default without cygstart; see + " https://www.msys2.org/wiki/How-does-MSYS2-differ-from-Cygwin + " Instead it provides /usr/bin/start script running `cmd.exe //c start` + " Adding "" //b` sets void title, hides cmd window and blocks path conversion + " of /b to \b\ " by MSYS2; see https://www.msys2.org/docs/filesystem-paths/ + fun! netrw#Launch(args) + exe 'silent !start "" //b' a:args s:redir() | redraw! + endfun + else + " imitate /usr/bin/start script for other environments and hope for the best + fun! netrw#Launch(args) + exe 'silent !cmd //c start "" //b' a:args s:redir() | redraw! + endfun + endif + elseif exists('$WSL_DISTRO_NAME') " use cmd.exe to start GUI apps in WSL + fun! netrw#Launch(args) + let args = a:args + exe 'silent !' .. + \ ((args =~? '\v<\f+\.(exe|com|bat|cmd)>') ? + \ 'cmd.exe /c start /b ' .. args : + \ 'nohup ' .. args .. ' ' .. s:redir() .. ' &') + \ | redraw! + endfun + else + fun! netrw#Launch(args) + exe ':silent ! nohup' a:args s:redir() (has('gui_running') ? '' : '&') | redraw! + endfun + endif +elseif has('win32') + fun! netrw#Launch(args) + exe 'silent !' .. (&shell =~? '\' ? '' : 'cmd.exe /c') + \ 'start "" /b' a:args s:redir() | redraw! + endfun +else + fun! netrw#Launch(dummy) + echom 'No common launcher found' + endfun +endif + +" Git Bash +if has('win32unix') + " (cyg)start suffices + let s:os_viewer = '' +" Windows / WSL +elseif executable('explorer.exe') + let s:os_viewer = 'explorer.exe' +" Linux / BSD +elseif executable('xdg-open') + let s:os_viewer = 'xdg-open' +" MacOS +elseif executable('open') + let s:os_viewer = 'open' +endif + +fun! s:viewer() + " g:netrw_browsex_viewer could be a string of program + its arguments, + " test if first argument is executable + if exists('g:netrw_browsex_viewer') && executable(split(g:netrw_browsex_viewer)[0]) + " extract any viewing options. Assumes that they're set apart by spaces. + " call Decho("extract any viewing options from g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("")) + if g:netrw_browsex_viewer =~ '\s' + let viewer = substitute(g:netrw_browsex_viewer,'\s.*$','','') + let viewopt = substitute(g:netrw_browsex_viewer,'^\S\+\s*','','')." " + let oviewer = '' + let cnt = 1 + while !executable(viewer) && viewer != oviewer + let viewer = substitute(g:netrw_browsex_viewer,'^\(\(^\S\+\s\+\)\{'.cnt.'}\S\+\)\(.*\)$','\1','') + let viewopt = substitute(g:netrw_browsex_viewer,'^\(\(^\S\+\s\+\)\{'.cnt.'}\S\+\)\(.*\)$','\3','')." " + let cnt = cnt + 1 + let oviewer = viewer + " call Decho("!exe: viewer<".viewer."> viewopt<".viewopt.">",'~'.expand("")) + endwhile + else + let viewer = g:netrw_browsex_viewer + let viewopt = "" + endif + " call Decho("viewer<".viewer."> viewopt<".viewopt.">",'~'.expand("")) + return viewer .. ' ' .. viewopt + else + if !exists('s:os_viewer') + call netrw#ErrorMsg(s:ERROR,"No program to open this path found. See :help Open for more information.",106) + else + return s:os_viewer + endif + endif +endfun + +fun! netrw#Open(file) abort + call netrw#Launch(s:viewer() .. ' ' .. shellescape(a:file, 1)) +endfun + +if !exists('g:netrw_regex_url') + let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}' +endif + +" --------------------------------------------------------------------- +" netrw#BrowseX: (implements "x" and "gx") executes a special "viewer" script or program for the {{{2 +" given filename; typically this means given their extension. +" 0=local, 1=remote +fun! netrw#BrowseX(fname,remote) + if a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$' + " remote directory, not a webpage access, looks like an attempt to do a directory listing + norm! gf + endif + + if exists("g:netrw_browsex_viewer") && exists("g:netrw_browsex_support_remote") && !g:netrw_browsex_support_remote + let remote = a:remote + else + let remote = 0 + endif + + let ykeep = @@ + let screenposn = winsaveview() + + " need to save and restore aw setting as gx can invoke this function from non-netrw buffers + let awkeep = &aw + set noaw + + " special core dump handler + if a:fname =~ '/core\(\.\d\+\)\=$' + if exists("g:Netrw_corehandler") + if type(g:Netrw_corehandler) == 2 + " g:Netrw_corehandler is a function reference (see :help Funcref) + call g:Netrw_corehandler(s:NetrwFile(a:fname)) + elseif type(g:Netrw_corehandler) == 3 + " g:Netrw_corehandler is a List of function references (see :help Funcref) + for Fncref in g:Netrw_corehandler + if type(Fncref) == 2 + call Fncref(a:fname) + endif + endfor + endif + call winrestview(screenposn) + let @@= ykeep + let &aw= awkeep + return + endif + endif + + " set up the filename + " (lower case the extension, make a local copy of a remote file) + let exten= substitute(a:fname,'.*\.\(.\{-}\)','\1','e') + if has("win32") + let exten= substitute(exten,'^.*$','\L&\E','') + endif + if exten =~ "[\\/]" + let exten= "" + endif + + if remote == 1 + " create a local copy + setl bh=delete + call netrw#NetRead(3,a:fname) + " attempt to rename tempfile + let basename= substitute(a:fname,'^\(.*\)/\(.*\)\.\([^.]*\)$','\2','') + let newname = substitute(s:netrw_tmpfile,'^\(.*\)/\(.*\)\.\([^.]*\)$','\1/'.basename.'.\3','') + if s:netrw_tmpfile != newname && newname != "" + if rename(s:netrw_tmpfile,newname) == 0 + " renaming succeeded + let fname= newname + else + " renaming failed + let fname= s:netrw_tmpfile + endif + else + let fname= s:netrw_tmpfile + endif + else + let fname= a:fname + " special ~ handler for local + if fname =~ '^\~' && expand("$HOME") != "" + let fname= s:NetrwFile(substitute(fname,'^\~',expand("$HOME"),'')) + endif + endif + + " although shellescape(..., 1) is used in netrw#Open(), it's insufficient + call netrw#Open(escape(fname, '#%')) + + " cleanup: remove temporary file, + " delete current buffer if success with handler, + " return to prior buffer (directory listing) + " Feb 12, 2008: had to de-activate removal of + " temporary file because it wasn't getting seen. + " if remote == 1 && fname != a:fname + " call s:NetrwDelete(fname) + " endif + + if remote == 1 + setl bh=delete bt=nofile + if g:netrw_use_noswf + setl noswf + endif + exe "sil! NetrwKeepj norm! \" + endif + call winrestview(screenposn) + let @@ = ykeep + let &aw= awkeep +endfun + +" --------------------------------------------------------------------- +" netrw#GX: gets word under cursor for gx support {{{2 +" See also: netrw#BrowseXVis +" netrw#BrowseX +fun! netrw#GX() + " call Dfunc("netrw#GX()") + if &ft == "netrw" + let fname= s:NetrwGetWord() + else + let fname= exists("g:netrw_gx")? expand(g:netrw_gx) : s:GetURL() + endif + " call Dret("netrw#GX <".fname.">") + return fname +endfun + +fun! s:GetURL() abort + let URL = '' + if exists('*Netrw_get_URL_' .. &filetype) + let URL = call('Netrw_get_URL_' .. &filetype, []) + endif + if !empty(URL) | return URL | endif + " URLs end in letter, digit or forward slash + let URL = matchstr(expand(""), '\<' .. g:netrw_regex_url .. '\ze[^A-Za-z0-9/]*$') + if !empty(URL) | return URL | endif + + " Is it a file in the current work dir ... + let file = expand("") + if filereadable(file) | return file | endif + " ... or in that of the current buffer? + let path = fnamemodify(expand('%'), ':p') + if isdirectory(path) + let dir = path + elseif filereadable(path) + let dir = fnamemodify(path, ':h') + endif + if exists('dir') && filereadable(dir..'/'..file) | return dir..'/'..file | endif + + return '' +endf + +" --------------------------------------------------------------------- +" netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2 +fun! netrw#BrowseXVis() + let dict={} + let dict.a=[getreg('a'), getregtype('a')] + norm! gv"ay + let gxfile= @a + call s:RestoreRegister(dict) + call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile)) +endfun + +" --------------------------------------------------------------------- +" s:NetrwBufRename: renames a buffer without the side effect of retaining an unlisted buffer having the old name {{{2 +" Using the file command on a "[No Name]" buffer does not seem to cause the old "[No Name]" buffer +" to become an unlisted buffer, so in that case don't bwipe it. +fun! s:NetrwBufRename(newname) + " call Dfunc("s:NetrwBufRename(newname<".a:newname.">) buf(%)#".bufnr("%")."<".bufname(bufnr("%")).">") + " call Dredir("ls!","s:NetrwBufRename (before rename)") + let oldbufname= bufname(bufnr("%")) + " call Decho("buf#".bufnr("%").": oldbufname<".oldbufname.">",'~'.expand("")) + + if oldbufname != a:newname + " call Decho("do buffer rename: oldbufname<".oldbufname."> ≠ a:newname<".a:newname.">",'~'.expand("")) + let b:junk= 1 + " call Decho("rename buffer: sil! keepj keepalt file ".fnameescape(a:newname),'~'.expand("")) + exe 'sil! keepj keepalt file '.fnameescape(a:newname) + " call Dredir("ls!","s:NetrwBufRename (before bwipe)~".expand("")) + let oldbufnr= bufnr(oldbufname) + " call Decho("oldbufname<".oldbufname."> oldbufnr#".oldbufnr,'~'.expand("")) + " call Decho("bufnr(%)=".bufnr("%"),'~'.expand("")) + if oldbufname != "" && oldbufnr != -1 && oldbufnr != bufnr("%") + " call Decho("bwipe ".oldbufnr,'~'.expand("")) + exe "bwipe! ".oldbufnr + " else " Decho + " call Decho("did *not* bwipe buf#".oldbufnr,'~'.expand("")) + " call Decho("..reason: if oldbufname<".oldbufname."> is empty",'~'.expand(""))" + " call Decho("..reason: if oldbufnr#".oldbufnr." is -1",'~'.expand(""))" + " call Decho("..reason: if oldbufnr#".oldbufnr." != bufnr(%)#".bufnr("%"),'~'.expand(""))" + endif + " call Dredir("ls!","s:NetrwBufRename (after rename)") + " else " Decho + " call Decho("oldbufname<".oldbufname."> == a:newname: did *not* rename",'~'.expand("")) + endif + + " call Dret("s:NetrwBufRename : buf#".bufnr("%").": oldname<".oldbufname."> newname<".a:newname."> expand(%)<".expand("%").">") +endfun + +" --------------------------------------------------------------------- +" netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2 +fun! netrw#CheckIfRemote(...) + " call Dfunc("netrw#CheckIfRemote() a:0=".a:0) + if a:0 > 0 + let curfile= a:1 + else + let curfile= expand("%") + endif + " Ignore terminal buffers + if &buftype ==# 'terminal' + return 0 + endif + " call Decho("curfile<".curfile.">") + if curfile =~ '^\a\{3,}://' + " call Dret("netrw#CheckIfRemote 1") + return 1 + else + " call Dret("netrw#CheckIfRemote 0") + return 0 + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwChgPerm: (implements "gp") change file permission {{{2 +fun! s:NetrwChgPerm(islocal,curdir) + let ykeep = @@ + call inputsave() + let newperm= input("Enter new permission: ") + call inputrestore() + let chgperm= substitute(g:netrw_chgperm,'\',s:ShellEscape(expand("")),'') + let chgperm= substitute(chgperm,'\',s:ShellEscape(newperm),'') + call system(chgperm) + if v:shell_error != 0 + NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("")."> seems to have failed",75) + endif + if a:islocal + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + endif + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:CheckIfKde: checks if kdeinit is running {{{2 +" Returns 0: kdeinit not running +" 1: kdeinit is running +fun! s:CheckIfKde() + " call Dfunc("s:CheckIfKde()") + " seems kde systems often have gnome-open due to dependencies, even though + " gnome-open's subsidiary display tools are largely absent. Kde systems + " usually have "kdeinit" running, though... (tnx Mikolaj Machowski) + if !exists("s:haskdeinit") + if has("unix") && executable("ps") && !has("win32unix") + let s:haskdeinit= system("ps -e") =~ '\")) + endif + + " call Dret("s:CheckIfKde ".s:haskdeinit) + return s:haskdeinit +endfun + +" --------------------------------------------------------------------- +" s:NetrwClearExplore: clear explore variables (if any) {{{2 +fun! s:NetrwClearExplore() + " call Dfunc("s:NetrwClearExplore()") + 2match none + if exists("s:explore_match") |unlet s:explore_match |endif + if exists("s:explore_indx") |unlet s:explore_indx |endif + if exists("s:netrw_explore_prvdir") |unlet s:netrw_explore_prvdir |endif + if exists("s:dirstarstar") |unlet s:dirstarstar |endif + if exists("s:explore_prvdir") |unlet s:explore_prvdir |endif + if exists("w:netrw_explore_indx") |unlet w:netrw_explore_indx |endif + if exists("w:netrw_explore_listlen")|unlet w:netrw_explore_listlen|endif + if exists("w:netrw_explore_list") |unlet w:netrw_explore_list |endif + if exists("w:netrw_explore_bufnr") |unlet w:netrw_explore_bufnr |endif + " redraw! + " call Dret("s:NetrwClearExplore") +endfun + +" --------------------------------------------------------------------- +" s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2 +fun! s:NetrwEditBuf(bufnum) + " call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)") + if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw" + " call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)) + exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum) + else + " call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)) + exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum) + endif + " call Dret("s:NetrwEditBuf") +endfun + +" --------------------------------------------------------------------- +" s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2 +" NetrwKeepj [keepalt] +fun! s:NetrwEditFile(cmd,opt,fname) + " call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">) ft<".&ft.">") + if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw" + " call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)) + exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname) + else + " call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)) + if a:cmd =~# 'e\%[new]!' && !&hidden && getbufvar(bufname('%'), '&modified', 0) + call setbufvar(bufname('%'), '&bufhidden', 'hide') + endif + exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname) + endif + " call Dret("s:NetrwEditFile") +endfun + +" --------------------------------------------------------------------- +" s:NetrwExploreListUniq: {{{2 +fun! s:NetrwExploreListUniq(explist) + " this assumes that the list is already sorted + let newexplist= [] + for member in a:explist + if !exists("uniqmember") || member != uniqmember + let uniqmember = member + let newexplist = newexplist + [ member ] + endif + endfor + return newexplist +endfun + +" --------------------------------------------------------------------- +" s:NetrwForceChgDir: (gd support) Force treatment as a directory {{{2 +fun! s:NetrwForceChgDir(islocal,newdir) + let ykeep= @@ + if a:newdir !~ '/$' + " ok, looks like force is needed to get directory-style treatment + if a:newdir =~ '@$' + let newdir= substitute(a:newdir,'@$','/','') + elseif a:newdir =~ '[*=|\\]$' + let newdir= substitute(a:newdir,'.$','/','') + else + let newdir= a:newdir.'/' + endif + else + " should already be getting treatment as a directory + let newdir= a:newdir + endif + let newdir= s:NetrwBrowseChgDir(a:islocal,newdir,0) + call s:NetrwBrowse(a:islocal,newdir) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwGlob: does glob() if local, remote listing otherwise {{{2 +" direntry: this is the name of the directory. Will be fnameescape'd to prevent wildcard handling by glob() +" expr : this is the expression to follow the directory. Will use s:ComposePath() +" pare =1: remove the current directory from the resulting glob() filelist +" =0: leave the current directory in the resulting glob() filelist +fun! s:NetrwGlob(direntry,expr,pare) + " call Dfunc("s:NetrwGlob(direntry<".a:direntry."> expr<".a:expr."> pare=".a:pare.")") + if netrw#CheckIfRemote() + keepalt 1sp + keepalt enew + let keep_liststyle = w:netrw_liststyle + let w:netrw_liststyle = s:THINLIST + if s:NetrwRemoteListing() == 0 + keepj keepalt %s@/@@ + let filelist= getline(1,$) + q! + else + " remote listing error -- leave treedict unchanged + let filelist= w:netrw_treedict[a:direntry] + endif + let w:netrw_liststyle= keep_liststyle + else + let path= s:ComposePath(fnameescape(a:direntry), a:expr) + if has("win32") + " escape [ so it is not detected as wildcard character, see :h wildcard + let path= substitute(path, '[', '[[]', 'g') + endif + if v:version > 704 || (v:version == 704 && has("patch656")) + let filelist= glob(path,0,1,1) + else + let filelist= glob(path,0,1) + endif + if a:pare + let filelist= map(filelist,'substitute(v:val, "^.*/", "", "")') + endif + endif + return filelist +endfun + +" --------------------------------------------------------------------- +" s:NetrwForceFile: (gf support) Force treatment as a file {{{2 +fun! s:NetrwForceFile(islocal,newfile) + if a:newfile =~ '[/@*=|\\]$' + let newfile= substitute(a:newfile,'.$','','') + else + let newfile= a:newfile + endif + if a:islocal + call s:NetrwBrowseChgDir(a:islocal,newfile,0) + else + call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,newfile,0)) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwHide: this function is invoked by the "a" map for browsing {{{2 +" and switches the hiding mode. The actual hiding is done by +" s:NetrwListHide(). +" g:netrw_hide= 0: show all +" 1: show not-hidden files +" 2: show hidden files only +fun! s:NetrwHide(islocal) + let ykeep= @@ + let svpos= winsaveview() + + if exists("s:netrwmarkfilelist_{bufnr('%')}") + + " hide the files in the markfile list + for fname in s:netrwmarkfilelist_{bufnr("%")} + if match(g:netrw_list_hide,'\<'.fname.'\>') != -1 + " remove fname from hiding list + let g:netrw_list_hide= substitute(g:netrw_list_hide,'..\<'.escape(fname,g:netrw_fname_escape).'\>..','','') + let g:netrw_list_hide= substitute(g:netrw_list_hide,',,',',','g') + let g:netrw_list_hide= substitute(g:netrw_list_hide,'^,\|,$','','') + else + " append fname to hiding list + if exists("g:netrw_list_hide") && g:netrw_list_hide != "" + let g:netrw_list_hide= g:netrw_list_hide.',\<'.escape(fname,g:netrw_fname_escape).'\>' + else + let g:netrw_list_hide= '\<'.escape(fname,g:netrw_fname_escape).'\>' + endif + endif + endfor + NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) + let g:netrw_hide= 1 + + else + + " switch between show-all/show-not-hidden/show-hidden + let g:netrw_hide=(g:netrw_hide+1)%3 + exe "NetrwKeepj norm! 0" + if g:netrw_hide && g:netrw_list_hide == "" + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49) + let @@= ykeep + return + endif + endif + + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwHideEdit: allows user to edit the file/directory hiding list {{{2 +fun! s:NetrwHideEdit(islocal) + let ykeep= @@ + " save current cursor position + let svpos= winsaveview() + + " get new hiding list from user + call inputsave() + let newhide= input("Edit Hiding List: ",g:netrw_list_hide) + call inputrestore() + let g:netrw_list_hide= newhide + + " refresh the listing + sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./",0)) + + " restore cursor position + call winrestview(svpos) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwHidden: invoked by "gh" {{{2 +fun! s:NetrwHidden(islocal) + let ykeep= @@ + " save current position + let svpos = winsaveview() + + if g:netrw_list_hide =~ '\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+' + " remove .file pattern from hiding list + let g:netrw_list_hide= substitute(g:netrw_list_hide,'\(^\|,\)\\(^\\|\\s\\s\\)\\zs\\.\\S\\+','','') + elseif s:Strlen(g:netrw_list_hide) >= 1 + let g:netrw_list_hide= g:netrw_list_hide . ',\(^\|\s\s\)\zs\.\S\+' + else + let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+' + endif + if g:netrw_list_hide =~ '^,' + let g:netrw_list_hide= strpart(g:netrw_list_hide,1) + endif + + " refresh screen and return to saved position + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwHome: this function determines a "home" for saving bookmarks and history {{{2 +fun! s:NetrwHome() + if exists("g:netrw_home") + let home= expand(g:netrw_home) + else + let home = stdpath('data') + endif + " insure that the home directory exists + if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home)) + " call Decho("insure that the home<".home."> directory exists") + if exists("g:netrw_mkdir") + " call Decho("call system(".g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home)).")") + call system(g:netrw_mkdir." ".s:ShellEscape(s:NetrwFile(home))) + else + " call Decho("mkdir(".home.")") + call mkdir(home) + endif + endif + let g:netrw_home= home + return home +endfun + +" --------------------------------------------------------------------- +" s:NetrwLeftmouse: handles the when in a netrw browsing window {{{2 +fun! s:NetrwLeftmouse(islocal) + if exists("s:netrwdrag") + return + endif + if &ft != "netrw" + return + endif + + let ykeep= @@ + " check if the status bar was clicked on instead of a file/directory name + while getchar(0) != 0 + "clear the input stream + endwhile + call feedkeys("\") + let c = getchar() + let mouse_lnum = v:mouse_lnum + let wlastline = line('w$') + let lastline = line('$') + if mouse_lnum >= wlastline + 1 || v:mouse_win != winnr() + " appears to be a status bar leftmouse click + let @@= ykeep + return + endif + " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode + " Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar + " without this test when its disabled. + " May 26, 2014: edit file, :Lex, resize window -- causes refresh. Reinstated a modified test. See if problems develop. + if v:mouse_col > virtcol('.') + let @@= ykeep + return + endif + + if a:islocal + if exists("b:netrw_curdir") + NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1)) + endif + else + if exists("b:netrw_curdir") + NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) + endif + endif + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwCLeftmouse: used to select a file/directory for a target {{{2 +fun! s:NetrwCLeftmouse(islocal) + if &ft != "netrw" + return + endif + call s:NetrwMarkFileTgt(a:islocal) +endfun + +" --------------------------------------------------------------------- +" s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER (implements ){{{2 +" a:islocal=0 : not used, remote +" a:islocal=1 : not used, local +" a:islocal=2 : used, remote +" a:islocal=3 : used, local +fun! s:NetrwServerEdit(islocal,fname) + " call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)") + let islocal = a:islocal%2 " =0: remote =1: local + let ctrlr = a:islocal >= 2 " =0: not used =1: used + + if (islocal && isdirectory(s:NetrwFile(a:fname))) || (!islocal && a:fname =~ '/$') + " handle directories in the local window -- not in the remote vim server + " user must have closed the NETRWSERVER window. Treat as normal editing from netrw. + let g:netrw_browse_split= 0 + if exists("s:netrw_browse_split") && exists("s:netrw_browse_split_".winnr()) + let g:netrw_browse_split= s:netrw_browse_split_{winnr()} + unlet s:netrw_browse_split_{winnr()} + endif + call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0)) + return + endif + + if has("clientserver") && executable("gvim") + + if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3 + let srvrname = g:netrw_browse_split[0] + let tabnum = g:netrw_browse_split[1] + let winnum = g:netrw_browse_split[2] + + if serverlist() !~ '\<'.srvrname.'\>' + if !ctrlr + " user must have closed the server window and the user did not use , but + " used something like . + if exists("g:netrw_browse_split") + unlet g:netrw_browse_split + endif + let g:netrw_browse_split= 0 + if exists("s:netrw_browse_split_".winnr()) + let g:netrw_browse_split= s:netrw_browse_split_{winnr()} + endif + call s:NetrwBrowseChgDir(islocal,a:fname,0) + return + + elseif has("win32") && executable("start") + " start up remote netrw server under windows + call system("start gvim --servername ".srvrname) + + else + " start up remote netrw server under linux + call system("gvim --servername ".srvrname) + endif + endif + + call remote_send(srvrname,":tabn ".tabnum."\") + call remote_send(srvrname,":".winnum."wincmd w\") + call remote_send(srvrname,":e ".fnameescape(s:NetrwFile(a:fname))."\") + else + + if serverlist() !~ '\<'.g:netrw_servername.'\>' + + if !ctrlr + if exists("g:netrw_browse_split") + unlet g:netrw_browse_split + endif + let g:netrw_browse_split= 0 + call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname,0)) + return + + else + if has("win32") && executable("start") + " start up remote netrw server under windows + call system("start gvim --servername ".g:netrw_servername) + else + " start up remote netrw server under linux + call system("gvim --servername ".g:netrw_servername) + endif + endif + endif + + while 1 + try + call remote_send(g:netrw_servername,":e ".fnameescape(s:NetrwFile(a:fname))."\") + break + catch /^Vim\%((\a\+)\)\=:E241/ + sleep 200m + endtry + endwhile + + if exists("g:netrw_browse_split") + if type(g:netrw_browse_split) != 3 + let s:netrw_browse_split_{winnr()}= g:netrw_browse_split + endif + unlet g:netrw_browse_split + endif + let g:netrw_browse_split= [g:netrw_servername,1,1] + endif + + else + call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use ",98) + endif + +endfun + +" --------------------------------------------------------------------- +" s:NetrwSLeftmouse: marks the file under the cursor. May be dragged to select additional files {{{2 +fun! s:NetrwSLeftmouse(islocal) + if &ft != "netrw" + return + endif + " call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")") + + let s:ngw= s:NetrwGetWord() + call s:NetrwMarkFile(a:islocal,s:ngw) + + " call Dret("s:NetrwSLeftmouse") +endfun + +" --------------------------------------------------------------------- +" s:NetrwSLeftdrag: invoked via a shift-leftmouse and dragging {{{2 +" Used to mark multiple files. +fun! s:NetrwSLeftdrag(islocal) + " call Dfunc("s:NetrwSLeftdrag(islocal=".a:islocal.")") + if !exists("s:netrwdrag") + let s:netrwdrag = winnr() + if a:islocal + nno :call NetrwSLeftrelease(1) + else + nno :call NetrwSLeftrelease(0) + endif + endif + let ngw = s:NetrwGetWord() + if !exists("s:ngw") || s:ngw != ngw + call s:NetrwMarkFile(a:islocal,ngw) + endif + let s:ngw= ngw + " call Dret("s:NetrwSLeftdrag : s:netrwdrag=".s:netrwdrag." buf#".bufnr("%")) +endfun + +" --------------------------------------------------------------------- +" s:NetrwSLeftrelease: terminates shift-leftmouse dragging {{{2 +fun! s:NetrwSLeftrelease(islocal) + " call Dfunc("s:NetrwSLeftrelease(islocal=".a:islocal.") s:netrwdrag=".s:netrwdrag." buf#".bufnr("%")) + if exists("s:netrwdrag") + nunmap + let ngw = s:NetrwGetWord() + if !exists("s:ngw") || s:ngw != ngw + call s:NetrwMarkFile(a:islocal,ngw) + endif + if exists("s:ngw") + unlet s:ngw + endif + unlet s:netrwdrag + endif + " call Dret("s:NetrwSLeftrelease") +endfun + +" --------------------------------------------------------------------- +" s:NetrwListHide: uses [range]g~...~d to delete files that match {{{2 +" comma-separated patterns given in g:netrw_list_hide +fun! s:NetrwListHide() + " call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">") + " call Decho("initial: ".string(getline(w:netrw_bannercnt,'$'))) + let ykeep= @@ + + " find a character not in the "hide" string to use as a separator for :g and :v commands + " How-it-works: take the hiding command, convert it into a range. + " Duplicate characters don't matter. + " Remove all such characters from the '/~@#...890' string. + " Use the first character left as a separator character. + " call Decho("find a character not in the hide string to use as a separator",'~'.expand("")) + let listhide= g:netrw_list_hide + let sep = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1) + " call Decho("sep<".sep."> (sep not in hide string)",'~'.expand("")) + + while listhide != "" + if listhide =~ ',' + let hide = substitute(listhide,',.*$','','e') + let listhide = substitute(listhide,'^.\{-},\(.*\)$','\1','e') + else + let hide = listhide + let listhide = "" + endif + " call Decho("..extracted pattern from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand("")) + if g:netrw_sort_by =~ '^[ts]' + if hide =~ '^\^' + " call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand("")) + let hide= substitute(hide,'^\^','^\(\\d\\+/\)','') + elseif hide =~ '^\\(\^' + let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','') + endif + " call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand("")) + endif + + " Prune the list by hiding any files which match + " call Decho("..prune the list by hiding any files which ".((g:netrw_hide == 1)? "" : "don't")."match hide<".hide.">") + if g:netrw_hide == 1 + " call Decho("..hiding<".hide.">",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d' + elseif g:netrw_hide == 2 + " call Decho("..showing<".hide.">",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @' + endif + " call Decho("..result: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand("")) + endwhile + + if g:netrw_hide == 2 + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d' + " call Decho("..v KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e' + " call Decho("..g KEEP: ".string(getline(w:netrw_bannercnt,'$')),'~'.expand("")) + endif + + " remove any blank lines that have somehow remained. + " This seems to happen under Windows. + exe 'sil! NetrwKeepj 1,$g@^\s*$@d' + + let @@= ykeep + " call Dret("s:NetrwListHide") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2 +" implements the "d" mapping. +fun! s:NetrwMakeDir(usrhost) + + let ykeep= @@ + " get name of new directory from user. A bare will skip. + " if its currently a directory, also request will be skipped, but with + " a message. + call inputsave() + let newdirname= input("Please give directory name: ") + call inputrestore() + + if newdirname == "" + let @@= ykeep + return + endif + + if a:usrhost == "" + + " Local mkdir: + " sanity checks + let fullnewdir= b:netrw_curdir.'/'.newdirname + if isdirectory(s:NetrwFile(fullnewdir)) + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24) + endif + let @@= ykeep + return + endif + if s:FileReadable(fullnewdir) + if !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25) + endif + let @@= ykeep + return + endif + + " requested new local directory is neither a pre-existing file or + " directory, so make it! + if exists("*mkdir") + if has("unix") + call mkdir(fullnewdir,"p",xor(0777, system("umask"))) + else + call mkdir(fullnewdir,"p") + endif + else + let netrw_origdir= s:NetrwGetcwd(1) + if s:NetrwLcd(b:netrw_curdir) + return + endif + call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(newdirname,1)) + if v:shell_error != 0 + let @@= ykeep + call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80) + return + endif + if !g:netrw_keepdir + if s:NetrwLcd(netrw_origdir) + return + endif + endif + endif + + if v:shell_error == 0 + " refresh listing + let svpos= winsaveview() + call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) + call winrestview(svpos) + elseif !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",26) + endif + + elseif !exists("b:netrw_method") || b:netrw_method == 4 + " Remote mkdir: using ssh + let mkdircmd = s:MakeSshCmd(g:netrw_mkdir_cmd) + let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname + call s:NetrwExe("sil! !".mkdircmd." ".s:ShellEscape(newdirname,1)) + if v:shell_error == 0 + " refresh listing + let svpos= winsaveview() + NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) + NetrwKeepj call winrestview(svpos) + elseif !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27) + endif + + elseif b:netrw_method == 2 + " Remote mkdir: using ftp+.netrc + let svpos= winsaveview() + if exists("b:netrw_fname") + let remotepath= b:netrw_fname + else + let remotepath= "" + endif + call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"') + NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) + NetrwKeepj call winrestview(svpos) + + elseif b:netrw_method == 3 + " Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc) + let svpos= winsaveview() + if exists("b:netrw_fname") + let remotepath= b:netrw_fname + else + let remotepath= "" + endif + call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"') + NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) + NetrwKeepj call winrestview(svpos) + endif + + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:TreeSqueezeDir: allows a shift-cr (gvim only) to squeeze the current tree-listing directory {{{2 +fun! s:TreeSqueezeDir(islocal) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") + " its a tree-listing style + let curdepth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e') + let stopline = (exists("w:netrw_bannercnt")? (w:netrw_bannercnt + 1) : 1) + let depth = strchars(substitute(curdepth,' ','','g')) + let srch = -1 + if depth >= 2 + NetrwKeepj norm! 0 + let curdepthm1= substitute(curdepth,'^'.s:treedepthstring,'','') + let srch = search('^'.curdepthm1.'\%('.s:treedepthstring.'\)\@!','bW',stopline) + elseif depth == 1 + NetrwKeepj norm! 0 + let treedepthchr= substitute(s:treedepthstring,' ','','') + let srch = search('^[^'.treedepthchr.']','bW',stopline) + endif + if srch > 0 + call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,s:NetrwGetWord(),1)) + exe srch + endif + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMaps: {{{2 +fun! s:NetrwMaps(islocal) + + " mouse maps: {{{3 + if g:netrw_mousemaps && g:netrw_retmap + " call Decho("set up Rexplore 2-leftmouse",'~'.expand("")) + if !hasmapto("NetrwReturn") + if maparg("<2-leftmouse>","n") == "" || maparg("<2-leftmouse>","n") =~ '^-$' + nmap <2-leftmouse> NetrwReturn + elseif maparg("","n") == "" + nmap NetrwReturn + endif + endif + nno NetrwReturn :Rexplore + endif + + " generate default maps {{{3 + if !hasmapto('NetrwHide') |nmap a NetrwHide_a|endif + if !hasmapto('NetrwBrowseUpDir') |nmap - NetrwBrowseUpDir|endif + if !hasmapto('NetrwOpenFile') |nmap % NetrwOpenFile|endif + if !hasmapto('NetrwBadd_cb') |nmap cb NetrwBadd_cb|endif + if !hasmapto('NetrwBadd_cB') |nmap cB NetrwBadd_cB|endif + if !hasmapto('NetrwLcd') |nmap cd NetrwLcd|endif + if !hasmapto('NetrwSetChgwin') |nmap C NetrwSetChgwin|endif + if !hasmapto('NetrwRefresh') |nmap NetrwRefresh|endif + if !hasmapto('NetrwLocalBrowseCheck') |nmap NetrwLocalBrowseCheck|endif + if !hasmapto('NetrwServerEdit') |nmap NetrwServerEdit|endif + if !hasmapto('NetrwMakeDir') |nmap d NetrwMakeDir|endif + if !hasmapto('NetrwBookHistHandler_gb')|nmap gb NetrwBookHistHandler_gb|endif + + if a:islocal + " local normal-mode maps {{{3 + nnoremap NetrwHide_a :call NetrwHide(1) + nnoremap NetrwBrowseUpDir :call NetrwBrowseUpDir(1) + nnoremap NetrwOpenFile :call NetrwOpenFile(1) + nnoremap NetrwBadd_cb :call NetrwBadd(1,0) + nnoremap NetrwBadd_cB :call NetrwBadd(1,1) + nnoremap NetrwLcd :call NetrwLcd(b:netrw_curdir) + nnoremap NetrwSetChgwin :call NetrwSetChgwin() + nnoremap NetrwLocalBrowseCheck :call netrw#LocalBrowseCheck(NetrwBrowseChgDir(1,NetrwGetWord(),1)) + nnoremap NetrwServerEdit :call NetrwServerEdit(3,NetrwGetWord()) + nnoremap NetrwMakeDir :call NetrwMakeDir("") + nnoremap NetrwBookHistHandler_gb :call NetrwBookHistHandler(1,b:netrw_curdir) + " --------------------------------------------------------------------- + nnoremap gd :call NetrwForceChgDir(1,NetrwGetWord()) + nnoremap gf :call NetrwForceFile(1,NetrwGetWord()) + nnoremap gh :call NetrwHidden(1) + nnoremap gn :call netrw#SetTreetop(0,NetrwGetWord()) + nnoremap gp :call NetrwChgPerm(1,b:netrw_curdir) + nnoremap I :call NetrwBannerCtrl(1) + nnoremap i :call NetrwListStyle(1) + nnoremap ma :call NetrwMarkFileArgList(1,0) + nnoremap mA :call NetrwMarkFileArgList(1,1) + nnoremap mb :call NetrwBookHistHandler(0,b:netrw_curdir) + nnoremap mB :call NetrwBookHistHandler(6,b:netrw_curdir) + nnoremap mc :call NetrwMarkFileCopy(1) + nnoremap md :call NetrwMarkFileDiff(1) + nnoremap me :call NetrwMarkFileEdit(1) + nnoremap mf :call NetrwMarkFile(1,NetrwGetWord()) + nnoremap mF :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir) + nnoremap mg :call NetrwMarkFileGrep(1) + nnoremap mh :call NetrwMarkHideSfx(1) + nnoremap mm :call NetrwMarkFileMove(1) + " nnoremap mp :call NetrwMarkFilePrint(1) + nnoremap mr :call NetrwMarkFileRegexp(1) + nnoremap ms :call NetrwMarkFileSource(1) + nnoremap mT :call NetrwMarkFileTag(1) + nnoremap mt :call NetrwMarkFileTgt(1) + nnoremap mu :call NetrwUnMarkFile(1) + nnoremap mv :call NetrwMarkFileVimCmd(1) + nnoremap mx :call NetrwMarkFileExe(1,0) + nnoremap mX :call NetrwMarkFileExe(1,1) + nnoremap mz :call NetrwMarkFileCompress(1) + nnoremap O :call NetrwObtain(1) + nnoremap o :call NetrwSplit(3) + nnoremap p :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1)) + nnoremap P :call NetrwPrevWinOpen(1) + nnoremap qb :call NetrwBookHistHandler(2,b:netrw_curdir) + nnoremap qf :call NetrwFileInfo(1,NetrwGetWord()) + nnoremap qF :call NetrwMarkFileQFEL(1,getqflist()) + nnoremap qL :call NetrwMarkFileQFEL(1,getloclist(v:count)) + nnoremap s :call NetrwSortStyle(1) + nnoremap S :call NetSortSequence(1) + nnoremap Tb :call NetrwSetTgt(1,'b',v:count1) + nnoremap t :call NetrwSplit(4) + nnoremap Th :call NetrwSetTgt(1,'h',v:count) + nnoremap u :call NetrwBookHistHandler(4,expand("%")) + nnoremap U :call NetrwBookHistHandler(5,expand("%")) + nnoremap v :call NetrwSplit(5) + nnoremap x :call netrw#BrowseX(NetrwBrowseChgDir(1,NetrwGetWord(),1,0),0)" + nnoremap X :call NetrwLocalExecute(expand(""))" + + nnoremap r :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwRefresh(1,NetrwBrowseChgDir(1,'./',0)) + if !hasmapto('NetrwHideEdit') + nmap NetrwHideEdit + endif + nnoremap NetrwHideEdit :call NetrwHideEdit(1) + if !hasmapto('NetrwRefresh') + nmap NetrwRefresh + endif + nnoremap NetrwRefresh :call NetrwRefresh(1,NetrwBrowseChgDir(1,(exists("w:netrw_liststyle") && exists("w:netrw_treetop") && w:netrw_liststyle == 3)? w:netrw_treetop : './',0)) + if s:didstarstar || !mapcheck("","n") + nnoremap :Nexplore + endif + if s:didstarstar || !mapcheck("","n") + nnoremap :Pexplore + endif + if !hasmapto('NetrwTreeSqueeze') + nmap NetrwTreeSqueeze + endif + nnoremap NetrwTreeSqueeze :call TreeSqueezeDir(1) + let mapsafecurdir = escape(b:netrw_curdir, s:netrw_map_escape) + if g:netrw_mousemaps == 1 + nmap NetrwLeftmouse + nmap NetrwCLeftmouse + nmap NetrwMiddlemouse + nmap NetrwSLeftmouse + nmap NetrwSLeftdrag + nmap <2-leftmouse> Netrw2Leftmouse + imap ILeftmouse + imap IMiddlemouse + nno NetrwLeftmouse :exec "norm! \leftmouse>"call NetrwLeftmouse(1) + nno NetrwCLeftmouse :exec "norm! \leftmouse>"call NetrwCLeftmouse(1) + nno NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(1) + nno NetrwSLeftmouse :exec "norm! \leftmouse>"call NetrwSLeftmouse(1) + nno NetrwSLeftdrag :exec "norm! \leftmouse>"call NetrwSLeftdrag(1) + nmap Netrw2Leftmouse - + exe 'nnoremap :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")' + exe 'vnoremap :exec "norm! \leftmouse>"call NetrwLocalRm("'.mapsafecurdir.'")' + endif + exe 'nnoremap :call NetrwLocalRm("'.mapsafecurdir.'")' + exe 'nnoremap D :call NetrwLocalRm("'.mapsafecurdir.'")' + exe 'nnoremap R :call NetrwLocalRename("'.mapsafecurdir.'")' + exe 'nnoremap d :call NetrwMakeDir("")' + exe 'vnoremap :call NetrwLocalRm("'.mapsafecurdir.'")' + exe 'vnoremap D :call NetrwLocalRm("'.mapsafecurdir.'")' + exe 'vnoremap R :call NetrwLocalRename("'.mapsafecurdir.'")' + nnoremap :he netrw-quickhelp + + " support user-specified maps + call netrw#UserMaps(1) + + else + " remote normal-mode maps {{{3 + call s:RemotePathAnalysis(b:netrw_curdir) + nnoremap NetrwHide_a :call NetrwHide(0) + nnoremap NetrwBrowseUpDir :call NetrwBrowseUpDir(0) + nnoremap NetrwOpenFile :call NetrwOpenFile(0) + nnoremap NetrwBadd_cb :call NetrwBadd(0,0) + nnoremap NetrwBadd_cB :call NetrwBadd(0,1) + nnoremap NetrwLcd :call NetrwLcd(b:netrw_curdir) + nnoremap NetrwSetChgwin :call NetrwSetChgwin() + nnoremap NetrwRefresh :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0)) + nnoremap NetrwLocalBrowseCheck :call NetrwBrowse(0,NetrwBrowseChgDir(0,NetrwGetWord(),1)) + nnoremap NetrwServerEdit :call NetrwServerEdit(2,NetrwGetWord()) + nnoremap NetrwBookHistHandler_gb :call NetrwBookHistHandler(1,b:netrw_curdir) + " --------------------------------------------------------------------- + nnoremap gd :call NetrwForceChgDir(0,NetrwGetWord()) + nnoremap gf :call NetrwForceFile(0,NetrwGetWord()) + nnoremap gh :call NetrwHidden(0) + nnoremap gp :call NetrwChgPerm(0,b:netrw_curdir) + nnoremap I :call NetrwBannerCtrl(1) + nnoremap i :call NetrwListStyle(0) + nnoremap ma :call NetrwMarkFileArgList(0,0) + nnoremap mA :call NetrwMarkFileArgList(0,1) + nnoremap mb :call NetrwBookHistHandler(0,b:netrw_curdir) + nnoremap mB :call NetrwBookHistHandler(6,b:netrw_curdir) + nnoremap mc :call NetrwMarkFileCopy(0) + nnoremap md :call NetrwMarkFileDiff(0) + nnoremap me :call NetrwMarkFileEdit(0) + nnoremap mf :call NetrwMarkFile(0,NetrwGetWord()) + nnoremap mF :call NetrwUnmarkList(bufnr("%"),b:netrw_curdir) + nnoremap mg :call NetrwMarkFileGrep(0) + nnoremap mh :call NetrwMarkHideSfx(0) + nnoremap mm :call NetrwMarkFileMove(0) + " nnoremap mp :call NetrwMarkFilePrint(0) + nnoremap mr :call NetrwMarkFileRegexp(0) + nnoremap ms :call NetrwMarkFileSource(0) + nnoremap mT :call NetrwMarkFileTag(0) + nnoremap mt :call NetrwMarkFileTgt(0) + nnoremap mu :call NetrwUnMarkFile(0) + nnoremap mv :call NetrwMarkFileVimCmd(0) + nnoremap mx :call NetrwMarkFileExe(0,0) + nnoremap mX :call NetrwMarkFileExe(0,1) + nnoremap mz :call NetrwMarkFileCompress(0) + nnoremap O :call NetrwObtain(0) + nnoremap o :call NetrwSplit(0) + nnoremap p :call NetrwPreview(NetrwBrowseChgDir(1,NetrwGetWord(),1,1)) + nnoremap P :call NetrwPrevWinOpen(0) + nnoremap qb :call NetrwBookHistHandler(2,b:netrw_curdir) + nnoremap qf :call NetrwFileInfo(0,NetrwGetWord()) + nnoremap qF :call NetrwMarkFileQFEL(0,getqflist()) + nnoremap qL :call NetrwMarkFileQFEL(0,getloclist(v:count)) + nnoremap r :let g:netrw_sort_direction= (g:netrw_sort_direction =~# 'n')? 'r' : 'n'exe "norm! 0"call NetrwBrowse(0,NetrwBrowseChgDir(0,'./',0)) + nnoremap s :call NetrwSortStyle(0) + nnoremap S :call NetSortSequence(0) + nnoremap Tb :call NetrwSetTgt(0,'b',v:count1) + nnoremap t :call NetrwSplit(1) + nnoremap Th :call NetrwSetTgt(0,'h',v:count) + nnoremap u :call NetrwBookHistHandler(4,b:netrw_curdir) + nnoremap U :call NetrwBookHistHandler(5,b:netrw_curdir) + nnoremap v :call NetrwSplit(2) + nnoremap x :call netrw#BrowseX(NetrwBrowseChgDir(0,NetrwGetWord(),1),1) + nmap gx x + if !hasmapto('NetrwHideEdit') + nmap NetrwHideEdit + endif + nnoremap NetrwHideEdit :call NetrwHideEdit(0) + if !hasmapto('NetrwRefresh') + nmap NetrwRefresh + endif + if !hasmapto('NetrwTreeSqueeze') + nmap NetrwTreeSqueeze + endif + nnoremap NetrwTreeSqueeze :call TreeSqueezeDir(0) + + let mapsafepath = escape(s:path, s:netrw_map_escape) + let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape) + + nnoremap NetrwRefresh :call NetrwRefresh(0,NetrwBrowseChgDir(0,'./',0)) + if g:netrw_mousemaps == 1 + nmap NetrwLeftmouse + nno NetrwLeftmouse :exec "norm! \leftmouse>"call NetrwLeftmouse(0) + nmap NetrwCLeftmouse + nno NetrwCLeftmouse :exec "norm! \leftmouse>"call NetrwCLeftmouse(0) + nmap NetrwSLeftmouse + nno NetrwSLeftmouse :exec "norm! \leftmouse>"call NetrwSLeftmouse(0) + nmap NetrwSLeftdrag + nno NetrwSLeftdrag :exec "norm! \leftmouse>"call NetrwSLeftdrag(0) + nmap NetrwMiddlemouse + nno NetrwMiddlemouse :exec "norm! \leftmouse>"call NetrwPrevWinOpen(0) + nmap <2-leftmouse> Netrw2Leftmouse + nmap Netrw2Leftmouse - + imap ILeftmouse + imap IMiddlemouse + imap ISLeftmouse + exe 'nnoremap :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' + exe 'vnoremap :exec "norm! \leftmouse>"call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' + endif + exe 'nnoremap :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' + exe 'nnoremap d :call NetrwMakeDir("'.mapsafeusermach.'")' + exe 'nnoremap D :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' + exe 'nnoremap R :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")' + exe 'vnoremap :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' + exe 'vnoremap D :call NetrwRemoteRm("'.mapsafeusermach.'","'.mapsafepath.'")' + exe 'vnoremap R :call NetrwRemoteRename("'.mapsafeusermach.'","'.mapsafepath.'")' + nnoremap :he netrw-quickhelp + + " support user-specified maps + call netrw#UserMaps(0) + endif " }}}3 +endfun + +" --------------------------------------------------------------------- +" s:NetrwCommands: set up commands {{{2 +" If -buffer, the command is only available from within netrw buffers +" Otherwise, the command is available from any window, so long as netrw +" has been used at least once in the session. +fun! s:NetrwCommands(islocal) + " call Dfunc("s:NetrwCommands(islocal=".a:islocal.")") + + com! -nargs=* -complete=file -bang NetrwMB call s:NetrwBookmark(0,) + com! -nargs=* NetrwC call s:NetrwSetChgwin() + com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"win#".winnr()." not a former netrw window",79)|endif + if a:islocal + com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(1,) + else + com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(0,) + endif + com! -buffer -nargs=? -complete=file MT call s:NetrwMarkTarget() + + " call Dret("s:NetrwCommands") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFiles: apply s:NetrwMarkFile() to named file(s) {{{2 +" glob()ing only works with local files +fun! s:NetrwMarkFiles(islocal,...) + " call Dfunc("s:NetrwMarkFiles(islocal=".a:islocal."...) a:0=".a:0) + let curdir = s:NetrwGetCurdir(a:islocal) + let i = 1 + while i <= a:0 + if a:islocal + if v:version > 704 || (v:version == 704 && has("patch656")) + let mffiles= glob(a:{i},0,1,1) + else + let mffiles= glob(a:{i},0,1) + endif + else + let mffiles= [a:{i}] + endif + " call Decho("mffiles".string(mffiles),'~'.expand("")) + for mffile in mffiles + " call Decho("mffile<".mffile.">",'~'.expand("")) + call s:NetrwMarkFile(a:islocal,mffile) + endfor + let i= i + 1 + endwhile + " call Dret("s:NetrwMarkFiles") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkTarget: implements :MT (mark target) {{{2 +fun! s:NetrwMarkTarget(...) + if a:0 == 0 || (a:0 == 1 && a:1 == "") + let curdir = s:NetrwGetCurdir(1) + let tgt = b:netrw_curdir + else + let curdir = s:NetrwGetCurdir((a:1 =~ '^\a\{3,}://')? 0 : 1) + let tgt = a:1 + endif + let s:netrwmftgt = tgt + let s:netrwmftgt_islocal = tgt !~ '^\a\{3,}://' + let curislocal = b:netrw_curdir !~ '^\a\{3,}://' + let svpos = winsaveview() + call s:NetrwRefresh(curislocal,s:NetrwBrowseChgDir(curislocal,'./',0)) + call winrestview(svpos) +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFile: (invoked by mf) This function is used to both {{{2 +" mark and unmark files. If a markfile list exists, +" then the rename and delete functions will use it instead +" of whatever may happen to be under the cursor at that +" moment. When the mouse and gui are available, +" shift-leftmouse may also be used to mark files. +" +" Creates two lists +" s:netrwmarkfilelist -- holds complete paths to all marked files +" s:netrwmarkfilelist_# -- holds list of marked files in current-buffer's directory (#==bufnr()) +" +" Creates a marked file match string +" s:netrwmarfilemtch_# -- used with 2match to display marked files +" +" Creates a buffer version of islocal +" b:netrw_islocal +fun! s:NetrwMarkFile(islocal,fname) + " call Dfunc("s:NetrwMarkFile(islocal=".a:islocal." fname<".a:fname.">)") + " call Decho("bufnr(%)=".bufnr("%").": ".bufname("%"),'~'.expand("")) + + " sanity check + if empty(a:fname) + " call Dret("s:NetrwMarkFile : empty fname") + return + endif + let curdir = s:NetrwGetCurdir(a:islocal) + + let ykeep = @@ + let curbufnr= bufnr("%") + let leader= '\%(^\|\s\)\zs' + if a:fname =~ '\a$' + let trailer = '\>[@=|\/\*]\=\ze\%( \|\t\|$\)' + else + let trailer = '[@=|\/\*]\=\ze\%( \|\t\|$\)' + endif + + if exists("s:netrwmarkfilelist_".curbufnr) + " markfile list pre-exists + " call Decho("case s:netrwmarkfilelist_".curbufnr." already exists",'~'.expand("")) + " call Decho("starting s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) + " call Decho("starting s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand("")) + let b:netrw_islocal= a:islocal + + if index(s:netrwmarkfilelist_{curbufnr},a:fname) == -1 + " append filename to buffer's markfilelist + " call Decho("append filename<".a:fname."> to local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) + call add(s:netrwmarkfilelist_{curbufnr},a:fname) + let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(a:fname,g:netrw_markfileesc).trailer + + else + " remove filename from buffer's markfilelist + " call Decho("remove filename<".a:fname."> from local markfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) + call filter(s:netrwmarkfilelist_{curbufnr},'v:val != a:fname') + if s:netrwmarkfilelist_{curbufnr} == [] + " local markfilelist is empty; remove it entirely + " call Decho("markfile list now empty",'~'.expand("")) + call s:NetrwUnmarkList(curbufnr,curdir) + else + " rebuild match list to display markings correctly + " call Decho("rebuild s:netrwmarkfilemtch_".curbufnr,'~'.expand("")) + let s:netrwmarkfilemtch_{curbufnr}= "" + let first = 1 + for fname in s:netrwmarkfilelist_{curbufnr} + if first + let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.leader.escape(fname,g:netrw_markfileesc).trailer + else + let s:netrwmarkfilemtch_{curbufnr}= s:netrwmarkfilemtch_{curbufnr}.'\|'.leader.escape(fname,g:netrw_markfileesc).trailer + endif + let first= 0 + endfor + " call Decho("ending s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) + endif + endif + + else + " initialize new markfilelist + " call Decho("case: initialize new markfilelist",'~'.expand("")) + + " call Decho("add fname<".a:fname."> to new markfilelist_".curbufnr,'~'.expand("")) + let s:netrwmarkfilelist_{curbufnr}= [] + call add(s:netrwmarkfilelist_{curbufnr},substitute(a:fname,'[|@]$','','')) + " call Decho("ending s:netrwmarkfilelist_{curbufnr}<".string(s:netrwmarkfilelist_{curbufnr}).">",'~'.expand("")) + + " build initial markfile matching pattern + if a:fname =~ '/$' + let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc) + else + let s:netrwmarkfilemtch_{curbufnr}= leader.escape(a:fname,g:netrw_markfileesc).trailer + endif + " call Decho("ending s:netrwmarkfilemtch_".curbufnr."<".s:netrwmarkfilemtch_{curbufnr}.">",'~'.expand("")) + endif + + " handle global markfilelist + if exists("s:netrwmarkfilelist") + let dname= s:ComposePath(b:netrw_curdir,a:fname) + if index(s:netrwmarkfilelist,dname) == -1 + " append new filename to global markfilelist + call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname)) + " call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand("")) + else + " remove new filename from global markfilelist + " call Decho("remove new filename from global s:markfilelist",'~'.expand("")) + " call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand("")) + call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"') + " call Decho("..ending s:netrwmarkfilelist <".string(s:netrwmarkfilelist).">",'~'.expand("")) + if s:netrwmarkfilelist == [] + " call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand("")) + unlet s:netrwmarkfilelist + endif + endif + else + " initialize new global-directory markfilelist + let s:netrwmarkfilelist= [] + call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname)) + " call Decho("init s:netrwmarkfilelist<".string(s:netrwmarkfilelist).">",'~'.expand("")) + endif + + " set up 2match'ing to netrwmarkfilemtch_# list + if has("syntax") && exists("g:syntax_on") && g:syntax_on + if exists("s:netrwmarkfilemtch_{curbufnr}") && s:netrwmarkfilemtch_{curbufnr} != "" + " " call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/",'~'.expand("")) + if exists("g:did_drchip_netrwlist_syntax") + exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{curbufnr}."/" + endif + else + " " call Decho("2match none",'~'.expand("")) + 2match none + endif + endif + let @@= ykeep + " call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand("")) + " call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist")."> (buf#".curbufnr."list)") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileArgList: ma: move the marked file list to the argument list (tomflist=0) {{{2 +" mA: move the argument list to marked file list (tomflist=1) +" Uses the global marked file list +fun! s:NetrwMarkFileArgList(islocal,tomflist) + let svpos = winsaveview() + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + if a:tomflist + " mA: move argument list to marked file list + while argc() + let fname= argv(0) + exe "argdel ".fnameescape(fname) + call s:NetrwMarkFile(a:islocal,fname) + endwhile + + else + " ma: move marked file list to argument list + if exists("s:netrwmarkfilelist") + + " for every filename in the marked list + for fname in s:netrwmarkfilelist + exe "argadd ".fnameescape(fname) + endfor " for every file in the marked list + + " unmark list and refresh + call s:NetrwUnmarkList(curbufnr,curdir) + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + endif + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileCompress: (invoked by mz) This function is used to {{{2 +" compress/decompress files using the programs +" in g:netrw_compress and g:netrw_uncompress, +" using g:netrw_compress_suffix to know which to +" do. By default: +" g:netrw_compress = "gzip" +" g:netrw_decompress = { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf", ".xz" : "unxz"} +fun! s:NetrwMarkFileCompress(islocal) + let svpos = winsaveview() + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + return + endif + + if exists("s:netrwmarkfilelist_{curbufnr}") && exists("g:netrw_compress") && exists("g:netrw_decompress") + + " for every filename in the marked list + for fname in s:netrwmarkfilelist_{curbufnr} + let sfx= substitute(fname,'^.\{-}\(\.[[:alnum:]]\+\)$','\1','') + if exists("g:netrw_decompress['".sfx."']") + " fname has a suffix indicating that its compressed; apply associated decompression routine + let exe= g:netrw_decompress[sfx] + let exe= netrw#WinPath(exe) + if a:islocal + if g:netrw_keepdir + let fname= s:ShellEscape(s:ComposePath(curdir,fname)) + endif + call system(exe." ".fname) + if v:shell_error + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50) + endif + else + let fname= s:ShellEscape(b:netrw_curdir.fname,1) + NetrwKeepj call s:RemoteSystem(exe." ".fname) + endif + + endif + unlet sfx + + if exists("exe") + unlet exe + elseif a:islocal + " fname not a compressed file, so compress it + call system(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(s:ComposePath(b:netrw_curdir,fname))) + if v:shell_error + call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_compress<".g:netrw_compress."> to something that works",104) + endif + else + " fname not a compressed file, so compress it + NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".s:ShellEscape(fname)) + endif + endfor " for every file in the marked list + + call s:NetrwUnmarkList(curbufnr,curdir) + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileCopy: (invoked by mc) copy marked files to target {{{2 +" If no marked files, then set up directory as the +" target. Currently does not support copying entire +" directories. Uses the local-buffer marked file list. +" Returns 1=success (used by NetrwMarkFileMove()) +" 0=failure +fun! s:NetrwMarkFileCopy(islocal,...) + " call Dfunc("s:NetrwMarkFileCopy(islocal=".a:islocal.") target<".(exists("s:netrwmftgt")? s:netrwmftgt : '---')."> a:0=".a:0) + + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + if b:netrw_curdir !~ '/$' + if !exists("b:netrw_curdir") + let b:netrw_curdir= curdir + endif + let b:netrw_curdir= b:netrw_curdir."/" + endif + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + " call Dret("s:NetrwMarkFileCopy") + return + endif + " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) + + if !exists("s:netrwmftgt") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67) + " call Dret("s:NetrwMarkFileCopy 0") + return 0 + endif + " call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand("")) + + if a:islocal && s:netrwmftgt_islocal + " Copy marked files, local directory to local directory + " call Decho("copy from local to local",'~'.expand("")) + if !executable(g:netrw_localcopycmd) + call netrw#ErrorMsg(s:ERROR,"g:netrw_localcopycmd<".g:netrw_localcopycmd."> not executable on your system, aborting",91) + " call Dfunc("s:NetrwMarkFileMove : g:netrw_localcopycmd<".g:netrw_localcopycmd."> n/a!") + return + endif + + " copy marked files while within the same directory (ie. allow renaming) + if s:StripTrailingSlash(simplify(s:netrwmftgt)) == s:StripTrailingSlash(simplify(b:netrw_curdir)) + if len(s:netrwmarkfilelist_{bufnr('%')}) == 1 + " only one marked file + " call Decho("case: only one marked file",'~'.expand("")) + let args = s:ShellEscape(b:netrw_curdir.s:netrwmarkfilelist_{bufnr('%')}[0]) + let oldname = s:netrwmarkfilelist_{bufnr('%')}[0] + elseif a:0 == 1 + " call Decho("case: handling one input argument",'~'.expand("")) + " this happens when the next case was used to recursively call s:NetrwMarkFileCopy() + let args = s:ShellEscape(b:netrw_curdir.a:1) + let oldname = a:1 + else + " copy multiple marked files inside the same directory + " call Decho("case: handling a multiple marked files",'~'.expand("")) + let s:recursive= 1 + for oldname in s:netrwmarkfilelist_{bufnr("%")} + let ret= s:NetrwMarkFileCopy(a:islocal,oldname) + if ret == 0 + break + endif + endfor + unlet s:recursive + call s:NetrwUnmarkList(curbufnr,curdir) + " call Dret("s:NetrwMarkFileCopy ".ret) + return ret + endif + + call inputsave() + let newname= input("Copy ".oldname." to : ",oldname,"file") + call inputrestore() + if newname == "" + " call Dret("s:NetrwMarkFileCopy 0") + return 0 + endif + let args= s:ShellEscape(oldname) + let tgt = s:ShellEscape(s:netrwmftgt.'/'.newname) + else + let args= join(map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),"s:ShellEscape(b:netrw_curdir.\"/\".v:val)")) + let tgt = s:ShellEscape(s:netrwmftgt) + endif + if !g:netrw_cygwin && has("win32") + let args= substitute(args,'/','\\','g') + let tgt = substitute(tgt, '/','\\','g') + endif + if args =~ "'" |let args= substitute(args,"'\\(.*\\)'",'\1','')|endif + if tgt =~ "'" |let tgt = substitute(tgt ,"'\\(.*\\)'",'\1','')|endif + if args =~ '//'|let args= substitute(args,'//','/','g')|endif + if tgt =~ '//'|let tgt = substitute(tgt ,'//','/','g')|endif + " call Decho("args <".args.">",'~'.expand("")) + " call Decho("tgt <".tgt.">",'~'.expand("")) + if isdirectory(s:NetrwFile(args)) + " call Decho("args<".args."> is a directory",'~'.expand("")) + let copycmd= g:netrw_localcopydircmd + " call Decho("using copydircmd<".copycmd.">",'~'.expand("")) + if !g:netrw_cygwin && has("win32") + " window's xcopy doesn't copy a directory to a target properly. Instead, it copies a directory's + " contents to a target. One must append the source directory name to the target to get xcopy to + " do the right thing. + let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','') + " call Decho("modified tgt for xcopy",'~'.expand("")) + endif + else + let copycmd= g:netrw_localcopycmd + endif + if g:netrw_localcopycmd =~ '\s' + let copycmd = substitute(copycmd,'\s.*$','','') + let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','') + let copycmd = netrw#WinPath(copycmd).copycmdargs + else + let copycmd = netrw#WinPath(copycmd) + endif + " call Decho("args <".args.">",'~'.expand("")) + " call Decho("tgt <".tgt.">",'~'.expand("")) + " call Decho("copycmd<".copycmd.">",'~'.expand("")) + " call Decho("system(".copycmd." '".args."' '".tgt."')",'~'.expand("")) + call system(copycmd.g:netrw_localcopycmdopt." '".args."' '".tgt."'") + if v:shell_error != 0 + if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && g:netrw_keepdir + call netrw#ErrorMsg(s:ERROR,"copy failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",101) + else + call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80) + endif + " call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".s:ShellEscape(s:netrwmftgt)) + return 0 + endif + + elseif a:islocal && !s:netrwmftgt_islocal + " Copy marked files, local directory to remote directory + " call Decho("copy from local to remote",'~'.expand("")) + NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt) + + elseif !a:islocal && s:netrwmftgt_islocal + " Copy marked files, remote directory to local directory + " call Decho("copy from remote to local",'~'.expand("")) + NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt) + + elseif !a:islocal && !s:netrwmftgt_islocal + " Copy marked files, remote directory to remote directory + " call Decho("copy from remote to remote",'~'.expand("")) + let curdir = getcwd() + let tmpdir = s:GetTempfile("") + if tmpdir !~ '/' + let tmpdir= curdir."/".tmpdir + endif + if exists("*mkdir") + call mkdir(tmpdir) + else + call s:NetrwExe("sil! !".g:netrw_localmkdir.g:netrw_localmkdiropt.' '.s:ShellEscape(tmpdir,1)) + if v:shell_error != 0 + call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80) + " call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.s:ShellEscape(tmpdir,1) ) + return + endif + endif + if isdirectory(s:NetrwFile(tmpdir)) + if s:NetrwLcd(tmpdir) + " call Dret("s:NetrwMarkFileCopy : lcd failure") + return + endif + NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir) + let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")') + NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt) + if getcwd() == tmpdir + for fname in s:netrwmarkfilelist_{bufnr('%')} + NetrwKeepj call s:NetrwDelete(fname) + endfor + if s:NetrwLcd(curdir) + " call Dret("s:NetrwMarkFileCopy : lcd failure") + return + endif + if delete(tmpdir,"d") + call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103) + endif + else + if s:NetrwLcd(curdir) + " call Dret("s:NetrwMarkFileCopy : lcd failure") + return + endif + endif + endif + endif + + " ------- + " cleanup + " ------- + " call Decho("cleanup",'~'.expand("")) + " remove markings from local buffer + call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer + " call Decho(" g:netrw_fastbrowse =".g:netrw_fastbrowse,'~'.expand("")) + " call Decho(" s:netrwmftgt =".s:netrwmftgt,'~'.expand("")) + " call Decho(" s:netrwmftgt_islocal=".s:netrwmftgt_islocal,'~'.expand("")) + " call Decho(" curdir =".curdir,'~'.expand("")) + " call Decho(" a:islocal =".a:islocal,'~'.expand("")) + " call Decho(" curbufnr =".curbufnr,'~'.expand("")) + if exists("s:recursive") + " call Decho(" s:recursive =".s:recursive,'~'.expand("")) + else + " call Decho(" s:recursive =n/a",'~'.expand("")) + endif + " see s:LocalFastBrowser() for g:netrw_fastbrowse interpretation (refreshing done for both slow and medium) + if g:netrw_fastbrowse <= 1 + NetrwKeepj call s:LocalBrowseRefresh() + else + " refresh local and targets for fast browsing + if !exists("s:recursive") + " remove markings from local buffer + " call Decho(" remove markings from local buffer",'~'.expand("")) + NetrwKeepj call s:NetrwUnmarkList(curbufnr,curdir) + endif + + " refresh buffers + if s:netrwmftgt_islocal + " call Decho(" refresh s:netrwmftgt=".s:netrwmftgt,'~'.expand("")) + NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt) + endif + if a:islocal && s:netrwmftgt != curdir + " call Decho(" refresh curdir=".curdir,'~'.expand("")) + NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir) + endif + endif + + " call Dret("s:NetrwMarkFileCopy 1") + return 1 +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileDiff: (invoked by md) This function is used to {{{2 +" invoke vim's diff mode on the marked files. +" Either two or three files can be so handled. +" Uses the global marked file list. +fun! s:NetrwMarkFileDiff(islocal) + " call Dfunc("s:NetrwMarkFileDiff(islocal=".a:islocal.") b:netrw_curdir<".b:netrw_curdir.">") + let curbufnr= bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + " call Dret("s:NetrwMarkFileDiff") + return + endif + let curdir= s:NetrwGetCurdir(a:islocal) + " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) + + if exists("s:netrwmarkfilelist_{".curbufnr."}") + let cnt = 0 + for fname in s:netrwmarkfilelist + let cnt= cnt + 1 + if cnt == 1 + " call Decho("diffthis: fname<".fname.">",'~'.expand("")) + exe "NetrwKeepj e ".fnameescape(fname) + diffthis + elseif cnt == 2 || cnt == 3 + below vsplit + " call Decho("diffthis: ".fname,'~'.expand("")) + exe "NetrwKeepj e ".fnameescape(fname) + diffthis + else + break + endif + endfor + call s:NetrwUnmarkList(curbufnr,curdir) + endif + + " call Dret("s:NetrwMarkFileDiff") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileEdit: (invoked by me) put marked files on arg list and start editing them {{{2 +" Uses global markfilelist +fun! s:NetrwMarkFileEdit(islocal) + " call Dfunc("s:NetrwMarkFileEdit(islocal=".a:islocal.")") + + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + " call Dret("s:NetrwMarkFileEdit") + return + endif + " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) + + if exists("s:netrwmarkfilelist_{curbufnr}") + call s:SetRexDir(a:islocal,curdir) + let flist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)")) + " unmark markedfile list + " call s:NetrwUnmarkList(curbufnr,curdir) + call s:NetrwUnmarkAll() + " call Decho("exe sil args ".flist,'~'.expand("")) + exe "sil args ".flist + endif + echo "(use :bn, :bp to navigate files; :Rex to return)" + + " call Dret("s:NetrwMarkFileEdit") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileQFEL: convert a quickfix-error or location list into a marked file list {{{2 +fun! s:NetrwMarkFileQFEL(islocal,qfel) + " call Dfunc("s:NetrwMarkFileQFEL(islocal=".a:islocal.",qfel)") + call s:NetrwUnmarkAll() + let curbufnr= bufnr("%") + + if !empty(a:qfel) + for entry in a:qfel + let bufnmbr= entry["bufnr"] + " call Decho("bufname(".bufnmbr.")<".bufname(bufnmbr)."> line#".entry["lnum"]." text=".entry["text"],'~'.expand("")) + if !exists("s:netrwmarkfilelist_{curbufnr}") + " call Decho("case: no marked file list",'~'.expand("")) + call s:NetrwMarkFile(a:islocal,bufname(bufnmbr)) + elseif index(s:netrwmarkfilelist_{curbufnr},bufname(bufnmbr)) == -1 + " s:NetrwMarkFile will remove duplicate entries from the marked file list. + " So, this test lets two or more hits on the same pattern to be ignored. + " call Decho("case: ".bufname(bufnmbr)." not currently in marked file list",'~'.expand("")) + call s:NetrwMarkFile(a:islocal,bufname(bufnmbr)) + else + " call Decho("case: ".bufname(bufnmbr)." already in marked file list",'~'.expand("")) + endif + endfor + echo "(use me to edit marked files)" + else + call netrw#ErrorMsg(s:WARNING,"can't convert quickfix error list; its empty!",92) + endif + + " call Dret("s:NetrwMarkFileQFEL") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2 +" mx enbloc=0: Uses the local marked-file list, applies command to each file individually +" mX enbloc=1: Uses the global marked-file list, applies command to entire list +fun! s:NetrwMarkFileExe(islocal,enbloc) + let svpos = winsaveview() + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + if a:enbloc == 0 + " individually apply command to files, one at a time + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + return + endif + + if exists("s:netrwmarkfilelist_{curbufnr}") + " get the command + call inputsave() + let cmd= input("Enter command: ","","file") + call inputrestore() + if cmd == "" + return + endif + + " apply command to marked files, individually. Substitute: filename -> % + " If no %, then append a space and the filename to the command + for fname in s:netrwmarkfilelist_{curbufnr} + if a:islocal + if g:netrw_keepdir + let fname= s:ShellEscape(netrw#WinPath(s:ComposePath(curdir,fname))) + endif + else + let fname= s:ShellEscape(netrw#WinPath(b:netrw_curdir.fname)) + endif + if cmd =~ '%' + let xcmd= substitute(cmd,'%',fname,'g') + else + let xcmd= cmd.' '.fname + endif + if a:islocal + let ret= system(xcmd) + else + let ret= s:RemoteSystem(xcmd) + endif + if v:shell_error < 0 + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54) + break + else + if ret !=# '' + echo "\n" + " skip trailing new line + echo ret[0:-2] + else + echo ret + endif + endif + endfor + + " unmark marked file list + call s:NetrwUnmarkList(curbufnr,curdir) + + " refresh the listing + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + else + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) + endif + + else " apply command to global list of files, en bloc + + call inputsave() + let cmd= input("Enter command: ","","file") + call inputrestore() + if cmd == "" + return + endif + if cmd =~ '%' + let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' '),'g') + else + let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'s:ShellEscape(v:val)'),' ') + endif + if a:islocal + call system(cmd) + if v:shell_error < 0 + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54) + endif + else + let ret= s:RemoteSystem(cmd) + endif + call s:NetrwUnmarkAll() + + " refresh the listing + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix +" as the marked file(s) (toggles suffix presence) +" Uses the local marked file list. +fun! s:NetrwMarkHideSfx(islocal) + let svpos = winsaveview() + let curbufnr = bufnr("%") + + " s:netrwmarkfilelist_{curbufnr}: the List of marked files + if exists("s:netrwmarkfilelist_{curbufnr}") + + for fname in s:netrwmarkfilelist_{curbufnr} + " construct suffix pattern + if fname =~ '\.' + let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','') + else + let sfxpat= '^\%(\%(\.\)\@!.\)*$' + endif + " determine if its in the hiding list or not + let inhidelist= 0 + if g:netrw_list_hide != "" + let itemnum = 0 + let hidelist= split(g:netrw_list_hide,',') + for hidepat in hidelist + if sfxpat == hidepat + let inhidelist= 1 + break + endif + let itemnum= itemnum + 1 + endfor + endif + if inhidelist + " remove sfxpat from list + call remove(hidelist,itemnum) + let g:netrw_list_hide= join(hidelist,",") + elseif g:netrw_list_hide != "" + " append sfxpat to non-empty list + let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat + else + " set hiding list to sfxpat + let g:netrw_list_hide= sfxpat + endif + endfor + + " refresh the listing + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + else + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2 +" Uses the local marked-file list. +fun! s:NetrwMarkFileVimCmd(islocal) + let svpos = winsaveview() + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + return + endif + + if exists("s:netrwmarkfilelist_{curbufnr}") + " get the command + call inputsave() + let cmd= input("Enter vim command: ","","file") + call inputrestore() + if cmd == "" + return + endif + + " apply command to marked files. Substitute: filename -> % + " If no %, then append a space and the filename to the command + for fname in s:netrwmarkfilelist_{curbufnr} + if a:islocal + 1split + exe "sil! NetrwKeepj keepalt e ".fnameescape(fname) + exe cmd + exe "sil! keepalt wq!" + else + echo "sorry, \"mv\" not supported yet for remote files" + endif + endfor + + " unmark marked file list + call s:NetrwUnmarkList(curbufnr,curdir) + + " refresh the listing + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + else + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkHideSfx: (invoked by mh) (un)hide files having same suffix +" as the marked file(s) (toggles suffix presence) +" Uses the local marked file list. +fun! s:NetrwMarkHideSfx(islocal) + let svpos = winsaveview() + let curbufnr = bufnr("%") + + " s:netrwmarkfilelist_{curbufnr}: the List of marked files + if exists("s:netrwmarkfilelist_{curbufnr}") + + for fname in s:netrwmarkfilelist_{curbufnr} + " construct suffix pattern + if fname =~ '\.' + let sfxpat= "^.*".substitute(fname,'^.*\(\.[^. ]\+\)$','\1','') + else + let sfxpat= '^\%(\%(\.\)\@!.\)*$' + endif + " determine if its in the hiding list or not + let inhidelist= 0 + if g:netrw_list_hide != "" + let itemnum = 0 + let hidelist= split(g:netrw_list_hide,',') + for hidepat in hidelist + if sfxpat == hidepat + let inhidelist= 1 + break + endif + let itemnum= itemnum + 1 + endfor + endif + if inhidelist + " remove sfxpat from list + call remove(hidelist,itemnum) + let g:netrw_list_hide= join(hidelist,",") + elseif g:netrw_list_hide != "" + " append sfxpat to non-empty list + let g:netrw_list_hide= g:netrw_list_hide.",".sfxpat + else + " set hiding list to sfxpat + let g:netrw_list_hide= sfxpat + endif + endfor + + " refresh the listing + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + else + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileGrep: (invoked by mg) This function applies vimgrep to marked files {{{2 +" Uses the global markfilelist +fun! s:NetrwMarkFileGrep(islocal) + " call Dfunc("s:NetrwMarkFileGrep(islocal=".a:islocal.")") + let svpos = winsaveview() + " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) + let curbufnr = bufnr("%") + let curdir = s:NetrwGetCurdir(a:islocal) + + if exists("s:netrwmarkfilelist") + " call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand("")) + let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)")) + " call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand(""))" + call s:NetrwUnmarkAll() + else + " call Decho('no marked files, using "*"','~'.expand("")) + let netrwmarkfilelist= "*" + endif + + " ask user for pattern + " call Decho("ask user for search pattern",'~'.expand("")) + call inputsave() + let pat= input("Enter pattern: ","") + call inputrestore() + let patbang = "" + if pat =~ '^!' + let patbang = "!" + let pat = strpart(pat,2) + endif + if pat =~ '^\i' + let pat = escape(pat,'/') + let pat = '/'.pat.'/' + else + let nonisi = pat[0] + endif + + " use vimgrep for both local and remote + " call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist,'~'.expand("")) + try + exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist + catch /^Vim\%((\a\+)\)\=:E480/ + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76) + " call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">") + return + endtry + echo "(use :cn, :cp to navigate, :Rex to return)" + + 2match none + " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("")) + NetrwKeepj call winrestview(svpos) + + if exists("nonisi") + " original, user-supplied pattern did not begin with a character from isident + " call Decho("looking for trailing nonisi<".nonisi."> followed by a j, gj, or jg",'~'.expand("")) + if pat =~# nonisi.'j$\|'.nonisi.'gj$\|'.nonisi.'jg$' + call s:NetrwMarkFileQFEL(a:islocal,getqflist()) + endif + endif + + " call Dret("s:NetrwMarkFileGrep") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileMove: (invoked by mm) execute arbitrary command on marked files, one at a time {{{2 +" uses the global marked file list +" s:netrwmfloc= 0: target directory is remote +" = 1: target directory is local +fun! s:NetrwMarkFileMove(islocal) + " call Dfunc("s:NetrwMarkFileMove(islocal=".a:islocal.")") + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + " call Dret("s:NetrwMarkFileMove") + return + endif + " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) + + if !exists("s:netrwmftgt") + NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67) + " call Dret("s:NetrwMarkFileCopy 0") + return 0 + endif + " call Decho("sanity chk passed: s:netrwmftgt<".s:netrwmftgt.">",'~'.expand("")) + + if a:islocal && s:netrwmftgt_islocal + " move: local -> local + " call Decho("move from local to local",'~'.expand("")) + " call Decho("local to local move",'~'.expand("")) + if !executable(g:netrw_localmovecmd) + call netrw#ErrorMsg(s:ERROR,"g:netrw_localmovecmd<".g:netrw_localmovecmd."> not executable on your system, aborting",90) + " call Dfunc("s:NetrwMarkFileMove : g:netrw_localmovecmd<".g:netrw_localmovecmd."> n/a!") + return + endif + let tgt = s:ShellEscape(s:netrwmftgt) + " call Decho("tgt<".tgt.">",'~'.expand("")) + if !g:netrw_cygwin && has("win32") + let tgt= substitute(tgt, '/','\\','g') + " call Decho("windows exception: tgt<".tgt.">",'~'.expand("")) + if g:netrw_localmovecmd =~ '\s' + let movecmd = substitute(g:netrw_localmovecmd,'\s.*$','','') + let movecmdargs = substitute(g:netrw_localmovecmd,'^.\{-}\(\s.*\)$','\1','') + let movecmd = netrw#WinPath(movecmd).movecmdargs + " call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand("")) + else + let movecmd = netrw#WinPath(g:netrw_localmovecmd) + " call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand("")) + endif + else + let movecmd = netrw#WinPath(g:netrw_localmovecmd) + " call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand("")) + endif + for fname in s:netrwmarkfilelist_{bufnr("%")} + if g:netrw_keepdir + " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1 + let fname= b:netrw_curdir."/".fname + endif + if !g:netrw_cygwin && has("win32") + let fname= substitute(fname,'/','\\','g') + endif + " call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand("")) + let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt) + if v:shell_error != 0 + if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir + call netrw#ErrorMsg(s:ERROR,"move failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",100) + else + call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localmovecmd<".g:netrw_localmovecmd.">; it doesn't work!",54) + endif + break + endif + endfor + + elseif a:islocal && !s:netrwmftgt_islocal + " move: local -> remote + " call Decho("move from local to remote",'~'.expand("")) + " call Decho("copy",'~'.expand("")) + let mflist= s:netrwmarkfilelist_{bufnr("%")} + NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) + " call Decho("remove",'~'.expand("")) + for fname in mflist + let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') + let ok = s:NetrwLocalRmFile(b:netrw_curdir,barefname,1) + endfor + unlet mflist + + elseif !a:islocal && s:netrwmftgt_islocal + " move: remote -> local + " call Decho("move from remote to local",'~'.expand("")) + " call Decho("copy",'~'.expand("")) + let mflist= s:netrwmarkfilelist_{bufnr("%")} + NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) + " call Decho("remove",'~'.expand("")) + for fname in mflist + let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') + let ok = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1) + endfor + unlet mflist + + elseif !a:islocal && !s:netrwmftgt_islocal + " move: remote -> remote + " call Decho("move from remote to remote",'~'.expand("")) + " call Decho("copy",'~'.expand("")) + let mflist= s:netrwmarkfilelist_{bufnr("%")} + NetrwKeepj call s:NetrwMarkFileCopy(a:islocal) + " call Decho("remove",'~'.expand("")) + for fname in mflist + let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','') + let ok = s:NetrwRemoteRmFile(b:netrw_curdir,barefname,1) + endfor + unlet mflist + endif + + " ------- + " cleanup + " ------- + " call Decho("cleanup",'~'.expand("")) + + " remove markings from local buffer + call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer + + " refresh buffers + if !s:netrwmftgt_islocal + " call Decho("refresh netrwmftgt<".s:netrwmftgt.">",'~'.expand("")) + NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt) + endif + if a:islocal + " call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">",'~'.expand("")) + NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir) + endif + if g:netrw_fastbrowse <= 1 + " call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh",'~'.expand("")) + NetrwKeepj call s:LocalBrowseRefresh() + endif + + " call Dret("s:NetrwMarkFileMove") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2 +" using the hardcopy command. Local marked-file list only. +fun! s:NetrwMarkFilePrint(islocal) + " call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")") + let curbufnr= bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + " call Dret("s:NetrwMarkFilePrint") + return + endif + " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) + let curdir= s:NetrwGetCurdir(a:islocal) + + if exists("s:netrwmarkfilelist_{curbufnr}") + let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr} + call s:NetrwUnmarkList(curbufnr,curdir) + for fname in netrwmarkfilelist + if a:islocal + if g:netrw_keepdir + let fname= s:ComposePath(curdir,fname) + endif + else + let fname= curdir.fname + endif + 1split + " the autocmds will handle both local and remote files + " call Decho("exe sil e ".escape(fname,' '),'~'.expand("")) + exe "sil NetrwKeepj e ".fnameescape(fname) + " call Decho("hardcopy",'~'.expand("")) + hardcopy + q + endfor + 2match none + endif + " call Dret("s:NetrwMarkFilePrint") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2 +" files when given a regexp (for which a prompt is +" issued) (matches to name of files). +fun! s:NetrwMarkFileRegexp(islocal) + " call Dfunc("s:NetrwMarkFileRegexp(islocal=".a:islocal.")") + + " get the regular expression + call inputsave() + let regexp= input("Enter regexp: ","","file") + call inputrestore() + + if a:islocal + let curdir= s:NetrwGetCurdir(a:islocal) + " call Decho("curdir<".fnameescape(curdir).">") + " get the matching list of files using local glob() + " call Decho("handle local regexp",'~'.expand("")) + let dirname = escape(b:netrw_curdir,g:netrw_glob_escape) + if v:version > 704 || (v:version == 704 && has("patch656")) + let filelist= glob(s:ComposePath(dirname,regexp),0,1,1) + else + let files = glob(s:ComposePath(dirname,regexp),0,0) + let filelist= split(files,"\n") + endif + " call Decho("files<".string(filelist).">",'~'.expand("")) + + " mark the list of files + for fname in filelist + if fname =~ '^'.fnameescape(curdir) + " call Decho("fname<".substitute(fname,'^'.fnameescape(curdir).'/','','').">",'~'.expand("")) + NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^'.fnameescape(curdir).'/','','')) + else + " call Decho("fname<".fname.">",'~'.expand("")) + NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','','')) + endif + endfor + + else + " call Decho("handle remote regexp",'~'.expand("")) + + " convert displayed listing into a filelist + let eikeep = &ei + let areg = @a + sil NetrwKeepj %y a + setl ei=all ma + " call Decho("setl ei=all ma",'~'.expand("")) + 1split + NetrwKeepj call s:NetrwEnew() + NetrwKeepj call s:NetrwOptionsSafe(a:islocal) + sil NetrwKeepj norm! "ap + NetrwKeepj 2 + let bannercnt= search('^" =====','W') + exe "sil NetrwKeepj 1,".bannercnt."d" + setl bt=nofile + if g:netrw_liststyle == s:LONGLIST + sil NetrwKeepj %s/\s\{2,}\S.*$//e + call histdel("/",-1) + elseif g:netrw_liststyle == s:WIDELIST + sil NetrwKeepj %s/\s\{2,}/\r/ge + call histdel("/",-1) + elseif g:netrw_liststyle == s:TREELIST + exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e' + sil! NetrwKeepj g/^ .*$/d + call histdel("/",-1) + call histdel("/",-1) + endif + " convert regexp into the more usual glob-style format + let regexp= substitute(regexp,'\*','.*','g') + " call Decho("regexp<".regexp.">",'~'.expand("")) + exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d" + call histdel("/",-1) + let filelist= getline(1,line("$")) + q! + for filename in filelist + NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','','')) + endfor + unlet filelist + let @a = areg + let &ei = eikeep + endif + echo " (use me to edit marked files)" + + " call Dret("s:NetrwMarkFileRegexp") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileSource: (invoked by ms) This function sources marked files {{{2 +" Uses the local marked file list. +fun! s:NetrwMarkFileSource(islocal) + " call Dfunc("s:NetrwMarkFileSource(islocal=".a:islocal.")") + let curbufnr= bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + " call Dret("s:NetrwMarkFileSource") + return + endif + " call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("")) + let curdir= s:NetrwGetCurdir(a:islocal) + + if exists("s:netrwmarkfilelist_{curbufnr}") + let netrwmarkfilelist = s:netrwmarkfilelist_{bufnr("%")} + call s:NetrwUnmarkList(curbufnr,curdir) + for fname in netrwmarkfilelist + if a:islocal + if g:netrw_keepdir + let fname= s:ComposePath(curdir,fname) + endif + else + let fname= curdir.fname + endif + " the autocmds will handle sourcing both local and remote files + " call Decho("exe so ".fnameescape(fname),'~'.expand("")) + exe "so ".fnameescape(fname) + endfor + 2match none + endif + " call Dret("s:NetrwMarkFileSource") +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileTag: (invoked by mT) This function applies g:netrw_ctags to marked files {{{2 +" Uses the global markfilelist +fun! s:NetrwMarkFileTag(islocal) + let svpos = winsaveview() + let curdir = s:NetrwGetCurdir(a:islocal) + let curbufnr = bufnr("%") + + " sanity check + if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) + NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) + return + endif + + if exists("s:netrwmarkfilelist") + let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "s:ShellEscape(v:val,".!a:islocal.")")) + call s:NetrwUnmarkAll() + + if a:islocal + + call system(g:netrw_ctags." ".netrwmarkfilelist) + if v:shell_error + call netrw#ErrorMsg(s:ERROR,"g:netrw_ctags<".g:netrw_ctags."> is not executable!",51) + endif + + else + let cmd = s:RemoteSystem(g:netrw_ctags." ".netrwmarkfilelist) + call netrw#Obtain(a:islocal,"tags") + let curdir= b:netrw_curdir + 1split + NetrwKeepj e tags + let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','') + exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e' + call histdel("/",-1) + wq! + endif + 2match none + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + call winrestview(svpos) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwMarkFileTgt: (invoked by mt) This function sets up a marked file target {{{2 +" Sets up two variables, +" s:netrwmftgt : holds the target directory +" s:netrwmftgt_islocal : 0=target directory is remote +" 1=target directory is local +fun! s:NetrwMarkFileTgt(islocal) + let svpos = winsaveview() + let curdir = s:NetrwGetCurdir(a:islocal) + let hadtgt = exists("s:netrwmftgt") + if !exists("w:netrw_bannercnt") + let w:netrw_bannercnt= b:netrw_bannercnt + endif + + " set up target + if line(".") < w:netrw_bannercnt + " if cursor in banner region, use b:netrw_curdir for the target unless its already the target + if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") && s:netrwmftgt == b:netrw_curdir + unlet s:netrwmftgt s:netrwmftgt_islocal + if g:netrw_fastbrowse <= 1 + call s:LocalBrowseRefresh() + endif + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + call winrestview(svpos) + return + else + let s:netrwmftgt= b:netrw_curdir + endif + + else + " get word under cursor. + " * If directory, use it for the target. + " * If file, use b:netrw_curdir for the target + let curword= s:NetrwGetWord() + let tgtdir = s:ComposePath(curdir,curword) + if a:islocal && isdirectory(s:NetrwFile(tgtdir)) + let s:netrwmftgt = tgtdir + elseif !a:islocal && tgtdir =~ '/$' + let s:netrwmftgt = tgtdir + else + let s:netrwmftgt = curdir + endif + endif + if a:islocal + " simplify the target (eg. /abc/def/../ghi -> /abc/ghi) + let s:netrwmftgt= simplify(s:netrwmftgt) + endif + if g:netrw_cygwin + let s:netrwmftgt= substitute(system("cygpath ".s:ShellEscape(s:netrwmftgt)),'\n$','','') + let s:netrwmftgt= substitute(s:netrwmftgt,'\n$','','') + endif + let s:netrwmftgt_islocal= a:islocal + + " need to do refresh so that the banner will be updated + " s:LocalBrowseRefresh handles all local-browsing buffers when not fast browsing + if g:netrw_fastbrowse <= 1 + call s:LocalBrowseRefresh() + endif + " call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,w:netrw_treetop,0)) + else + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + endif + call winrestview(svpos) + if !hadtgt + sil! NetrwKeepj norm! j + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwGetCurdir: gets current directory and sets up b:netrw_curdir if necessary {{{2 +fun! s:NetrwGetCurdir(islocal) + " call Dfunc("s:NetrwGetCurdir(islocal=".a:islocal.")") + + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + let b:netrw_curdir = s:NetrwTreePath(w:netrw_treetop) + " call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used s:NetrwTreeDir)",'~'.expand("")) + elseif !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + " call Decho("set b:netrw_curdir<".b:netrw_curdir."> (used getcwd)",'~'.expand("")) + endif + + " call Decho("b:netrw_curdir<".b:netrw_curdir."> ".((b:netrw_curdir !~ '\<\a\{3,}://')? "does not match" : "matches")." url pattern",'~'.expand("")) + if b:netrw_curdir !~ '\<\a\{3,}://' + let curdir= b:netrw_curdir + " call Decho("g:netrw_keepdir=".g:netrw_keepdir,'~'.expand("")) + if g:netrw_keepdir == 0 + call s:NetrwLcd(curdir) + endif + endif + + " call Dret("s:NetrwGetCurdir <".curdir.">") + return b:netrw_curdir +endfun + +" --------------------------------------------------------------------- +" s:NetrwOpenFile: query user for a filename and open it {{{2 +fun! s:NetrwOpenFile(islocal) + " call Dfunc("s:NetrwOpenFile(islocal=".a:islocal.")") + let ykeep= @@ + call inputsave() + let fname= input("Enter filename: ") + call inputrestore() + " call Decho("(s:NetrwOpenFile) fname<".fname.">",'~'.expand("")) + + " determine if Lexplore is in use + if exists("t:netrw_lexbufnr") + " check if t:netrw_lexbufnr refers to a netrw window + " call Decho("(s:netrwOpenFile) ..t:netrw_lexbufnr=".t:netrw_lexbufnr,'~'.expand("")) + let lexwinnr = bufwinnr(t:netrw_lexbufnr) + if lexwinnr != -1 && exists("g:netrw_chgwin") && g:netrw_chgwin != -1 + " call Decho("(s:netrwOpenFile) ..Lexplore in use",'~'.expand("")) + exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w" + exe "NetrwKeepj e ".fnameescape(fname) + let @@= ykeep + " call Dret("s:NetrwOpenFile : creating a file with Lexplore mode") + endif + endif + + " Does the filename contain a path? + if fname !~ '[/\\]' + if exists("b:netrw_curdir") + if exists("g:netrw_quiet") + let netrw_quiet_keep = g:netrw_quiet + endif + let g:netrw_quiet = 1 + " save position for benefit of Rexplore + let s:rexposn_{bufnr("%")}= winsaveview() + " call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand("")) + if b:netrw_curdir =~ '/$' + exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname) + else + exe "e ".fnameescape(b:netrw_curdir."/".fname) + endif + if exists("netrw_quiet_keep") + let g:netrw_quiet= netrw_quiet_keep + else + unlet g:netrw_quiet + endif + endif + else + exe "NetrwKeepj e ".fnameescape(fname) + endif + let @@= ykeep + " call Dret("s:NetrwOpenFile") +endfun + +" --------------------------------------------------------------------- +" netrw#Shrink: shrinks/expands a netrw or Lexplorer window {{{2 +" For the mapping to this function be made via +" netrwPlugin, you'll need to have had +" g:netrw_usetab set to non-zero. +fun! netrw#Shrink() + " call Dfunc("netrw#Shrink() ft<".&ft."> winwidth=".winwidth(0)." lexbuf#".((exists("t:netrw_lexbufnr"))? t:netrw_lexbufnr : 'n/a')) + let curwin = winnr() + let wiwkeep = &wiw + set wiw=1 + + if &ft == "netrw" + if winwidth(0) > g:netrw_wiw + let t:netrw_winwidth= winwidth(0) + exe "vert resize ".g:netrw_wiw + wincmd l + if winnr() == curwin + wincmd h + endif + " call Decho("vert resize 0",'~'.expand("")) + else + exe "vert resize ".t:netrw_winwidth + " call Decho("vert resize ".t:netrw_winwidth,'~'.expand("")) + endif + + elseif exists("t:netrw_lexbufnr") + exe bufwinnr(t:netrw_lexbufnr)."wincmd w" + if winwidth(bufwinnr(t:netrw_lexbufnr)) > g:netrw_wiw + let t:netrw_winwidth= winwidth(0) + exe "vert resize ".g:netrw_wiw + wincmd l + if winnr() == curwin + wincmd h + endif + " call Decho("vert resize 0",'~'.expand("")) + elseif winwidth(bufwinnr(t:netrw_lexbufnr)) >= 0 + exe "vert resize ".t:netrw_winwidth + " call Decho("vert resize ".t:netrw_winwidth,'~'.expand("")) + else + call netrw#Lexplore(0,0) + endif + + else + call netrw#Lexplore(0,0) + endif + let wiw= wiwkeep + + " call Dret("netrw#Shrink") +endfun + +" --------------------------------------------------------------------- +" s:NetSortSequence: allows user to edit the sorting sequence {{{2 +fun! s:NetSortSequence(islocal) + let ykeep= @@ + let svpos= winsaveview() + call inputsave() + let newsortseq= input("Edit Sorting Sequence: ",g:netrw_sort_sequence) + call inputrestore() + + " refresh the listing + let g:netrw_sort_sequence= newsortseq + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwUnmarkList: delete local marked file list and remove their contents from the global marked-file list {{{2 +" User access provided by the mapping. (see :help netrw-mF) +" Used by many MarkFile functions. +fun! s:NetrwUnmarkList(curbufnr,curdir) + " call Dfunc("s:NetrwUnmarkList(curbufnr=".a:curbufnr." curdir<".a:curdir.">)") + + " remove all files in local marked-file list from global list + if exists("s:netrwmarkfilelist") + for mfile in s:netrwmarkfilelist_{a:curbufnr} + let dfile = s:ComposePath(a:curdir,mfile) " prepend directory to mfile + let idx = index(s:netrwmarkfilelist,dfile) " get index in list of dfile + call remove(s:netrwmarkfilelist,idx) " remove from global list + endfor + if s:netrwmarkfilelist == [] + unlet s:netrwmarkfilelist + endif + + " getting rid of the local marked-file lists is easy + unlet s:netrwmarkfilelist_{a:curbufnr} + endif + if exists("s:netrwmarkfilemtch_{a:curbufnr}") + unlet s:netrwmarkfilemtch_{a:curbufnr} + endif + 2match none + " call Dret("s:NetrwUnmarkList") +endfun + +" --------------------------------------------------------------------- +" s:NetrwUnmarkAll: remove the global marked file list and all local ones {{{2 +fun! s:NetrwUnmarkAll() + " call Dfunc("s:NetrwUnmarkAll()") + if exists("s:netrwmarkfilelist") + unlet s:netrwmarkfilelist + endif + sil call s:NetrwUnmarkAll2() + 2match none + " call Dret("s:NetrwUnmarkAll") +endfun + +" --------------------------------------------------------------------- +" s:NetrwUnmarkAll2: unmark all files from all buffers {{{2 +fun! s:NetrwUnmarkAll2() + " call Dfunc("s:NetrwUnmarkAll2()") + redir => netrwmarkfilelist_let + let + redir END + let netrwmarkfilelist_list= split(netrwmarkfilelist_let,'\n') " convert let string into a let list + call filter(netrwmarkfilelist_list,"v:val =~ '^s:netrwmarkfilelist_'") " retain only those vars that start as s:netrwmarkfilelist_ + call map(netrwmarkfilelist_list,"substitute(v:val,'\\s.*$','','')") " remove what the entries are equal to + for flist in netrwmarkfilelist_list + let curbufnr= substitute(flist,'s:netrwmarkfilelist_','','') + unlet s:netrwmarkfilelist_{curbufnr} + unlet s:netrwmarkfilemtch_{curbufnr} + endfor + " call Dret("s:NetrwUnmarkAll2") +endfun + +" --------------------------------------------------------------------- +" s:NetrwUnMarkFile: called via mu map; unmarks *all* marked files, both global and buffer-local {{{2 +" +" Marked files are in two types of lists: +" s:netrwmarkfilelist -- holds complete paths to all marked files +" s:netrwmarkfilelist_# -- holds list of marked files in current-buffer's directory (#==bufnr()) +" +" Marked files suitable for use with 2match are in: +" s:netrwmarkfilemtch_# -- used with 2match to display marked files +fun! s:NetrwUnMarkFile(islocal) + let svpos = winsaveview() + let curbufnr = bufnr("%") + + " unmark marked file list + " (although I expect s:NetrwUpload() to do it, I'm just making sure) + if exists("s:netrwmarkfilelist") + " " call Decho("unlet'ing: s:netrwmarkfilelist",'~'.expand("")) + unlet s:netrwmarkfilelist + endif + + let ibuf= 1 + while ibuf < bufnr("$") + if exists("s:netrwmarkfilelist_".ibuf) + unlet s:netrwmarkfilelist_{ibuf} + unlet s:netrwmarkfilemtch_{ibuf} + endif + let ibuf = ibuf + 1 + endwhile + 2match none + + " call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + call winrestview(svpos) +endfun + +" --------------------------------------------------------------------- +" s:NetrwMenu: generates the menu for gvim and netrw {{{2 +fun! s:NetrwMenu(domenu) + + if !exists("g:NetrwMenuPriority") + let g:NetrwMenuPriority= 80 + endif + + if has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu + " call Dfunc("NetrwMenu(domenu=".a:domenu.")") + + if !exists("s:netrw_menu_enabled") && a:domenu + " call Decho("initialize menu",'~'.expand("")) + let s:netrw_menu_enabled= 1 + exe 'sil! menu '.g:NetrwMenuPriority.'.1 '.g:NetrwTopLvlMenu.'Help ' + exe 'sil! menu '.g:NetrwMenuPriority.'.5 '.g:NetrwTopLvlMenu.'-Sep1- :' + exe 'sil! menu '.g:NetrwMenuPriority.'.6 '.g:NetrwTopLvlMenu.'Go\ Up\ Directory- -' + exe 'sil! menu '.g:NetrwMenuPriority.'.7 '.g:NetrwTopLvlMenu.'Apply\ Special\ Viewerx x' + if g:netrw_dirhistmax > 0 + exe 'sil! menu '.g:NetrwMenuPriority.'.8.1 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Current\ Directorymb mb' + exe 'sil! menu '.g:NetrwMenuPriority.'.8.4 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Prev\ Dir\ (History)u u' + exe 'sil! menu '.g:NetrwMenuPriority.'.8.5 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Goto\ Next\ Dir\ (History)U U' + exe 'sil! menu '.g:NetrwMenuPriority.'.8.6 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Listqb qb' + else + exe 'sil! menu '.g:NetrwMenuPriority.'.8 '.g:NetrwTopLvlMenu.'Bookmarks\ and\ History :echo "(disabled)"'."\" + endif + exe 'sil! menu '.g:NetrwMenuPriority.'.9.1 '.g:NetrwTopLvlMenu.'Browsing\ Control.Horizontal\ Splito o' + exe 'sil! menu '.g:NetrwMenuPriority.'.9.2 '.g:NetrwTopLvlMenu.'Browsing\ Control.Vertical\ Splitv v' + exe 'sil! menu '.g:NetrwMenuPriority.'.9.3 '.g:NetrwTopLvlMenu.'Browsing\ Control.New\ Tabt t' + exe 'sil! menu '.g:NetrwMenuPriority.'.9.4 '.g:NetrwTopLvlMenu.'Browsing\ Control.Previewp p' + exe 'sil! menu '.g:NetrwMenuPriority.'.9.5 '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ File\ Hiding\ List'." \'" + exe 'sil! menu '.g:NetrwMenuPriority.'.9.6 '.g:NetrwTopLvlMenu.'Browsing\ Control.Edit\ Sorting\ SequenceS S' + exe 'sil! menu '.g:NetrwMenuPriority.'.9.7 '.g:NetrwTopLvlMenu.'Browsing\ Control.Quick\ Hide/Unhide\ Dot\ Files'."gh gh" + exe 'sil! menu '.g:NetrwMenuPriority.'.9.8 '.g:NetrwTopLvlMenu.'Browsing\ Control.Refresh\ Listing'." \" + exe 'sil! menu '.g:NetrwMenuPriority.'.9.9 '.g:NetrwTopLvlMenu.'Browsing\ Control.Settings/Options:NetrwSettings '.":NetrwSettings\" + exe 'sil! menu '.g:NetrwMenuPriority.'.10 '.g:NetrwTopLvlMenu.'Delete\ File/DirectoryD D' + exe 'sil! menu '.g:NetrwMenuPriority.'.11.1 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Create\ New\ File% %' + exe 'sil! menu '.g:NetrwMenuPriority.'.11.1 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Current\ Window '."\" + exe 'sil! menu '.g:NetrwMenuPriority.'.11.2 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directoryp p' + exe 'sil! menu '.g:NetrwMenuPriority.'.11.3 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ WindowP P' + exe 'sil! menu '.g:NetrwMenuPriority.'.11.4 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Windowo o' + exe 'sil! menu '.g:NetrwMenuPriority.'.11.5 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tabt t' + exe 'sil! menu '.g:NetrwMenuPriority.'.11.5 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Windowv v' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.1 '.g:NetrwTopLvlMenu.'Explore.Directory\ Name :Explore ' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.2 '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only):Explore\ */ :Explore */' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.2 '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (+subdirs):Explore\ **/ :Explore **/' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.3 '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (curdir\ only):Explore\ *// :Explore *//' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.4 '.g:NetrwTopLvlMenu.'Explore.Files\ Containing\ String\ Pattern\ (+subdirs):Explore\ **// :Explore **//' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.4 '.g:NetrwTopLvlMenu.'Explore.Next\ Match:Nexplore :Nexplore' + exe 'sil! menu '.g:NetrwMenuPriority.'.12.4 '.g:NetrwTopLvlMenu.'Explore.Prev\ Match:Pexplore :Pexplore' + exe 'sil! menu '.g:NetrwMenuPriority.'.13 '.g:NetrwTopLvlMenu.'Make\ Subdirectoryd d' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.1 '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Filemf mf' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.2 '.g:NetrwTopLvlMenu.'Marked\ Files.Mark\ Files\ by\ Regexpmr mr' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.3 '.g:NetrwTopLvlMenu.'Marked\ Files.Hide-Show-List\ Controla a' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.4 '.g:NetrwTopLvlMenu.'Marked\ Files.Copy\ To\ Targetmc mc' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.5 '.g:NetrwTopLvlMenu.'Marked\ Files.DeleteD D' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.6 '.g:NetrwTopLvlMenu.'Marked\ Files.Diffmd md' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.7 '.g:NetrwTopLvlMenu.'Marked\ Files.Editme me' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.8 '.g:NetrwTopLvlMenu.'Marked\ Files.Exe\ Cmdmx mx' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.9 '.g:NetrwTopLvlMenu.'Marked\ Files.Move\ To\ Targetmm mm' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.10 '.g:NetrwTopLvlMenu.'Marked\ Files.ObtainO O' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.11 '.g:NetrwTopLvlMenu.'Marked\ Files.Printmp mp' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.12 '.g:NetrwTopLvlMenu.'Marked\ Files.ReplaceR R' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.13 '.g:NetrwTopLvlMenu.'Marked\ Files.Set\ Targetmt mt' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.14 '.g:NetrwTopLvlMenu.'Marked\ Files.TagmT mT' + exe 'sil! menu '.g:NetrwMenuPriority.'.14.15 '.g:NetrwTopLvlMenu.'Marked\ Files.Zip/Unzip/Compress/Uncompressmz mz' + exe 'sil! menu '.g:NetrwMenuPriority.'.15 '.g:NetrwTopLvlMenu.'Obtain\ FileO O' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.thini :let w:netrw_liststyle=0' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.longi :let w:netrw_liststyle=1' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.widei :let w:netrw_liststyle=2' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.1.1 '.g:NetrwTopLvlMenu.'Style.Listing.treei :let w:netrw_liststyle=3' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.1 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Show\ Alla :let g:netrw_hide=0' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.3 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Normala :let g:netrw_hide=1' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.2.2 '.g:NetrwTopLvlMenu.'Style.Normal-Hide-Show.Hidden\ Onlya :let g:netrw_hide=2' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.3 '.g:NetrwTopLvlMenu.'Style.Reverse\ Sorting\ Order'."r r" + exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.1 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Names :let g:netrw_sort_by="name"' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.2 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Times :let g:netrw_sort_by="time"' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Sizes :let g:netrw_sort_by="size"' + exe 'sil! menu '.g:NetrwMenuPriority.'.16.4.3 '.g:NetrwTopLvlMenu.'Style.Sorting\ Method.Extens :let g:netrw_sort_by="exten"' + exe 'sil! menu '.g:NetrwMenuPriority.'.17 '.g:NetrwTopLvlMenu.'Rename\ File/DirectoryR R' + exe 'sil! menu '.g:NetrwMenuPriority.'.18 '.g:NetrwTopLvlMenu.'Set\ Current\ Directoryc c' + let s:netrw_menucnt= 28 + call s:NetrwBookmarkMenu() " provide some history! uses priorities 2,3, reserves 4, 8.2.x + call s:NetrwTgtMenu() " let bookmarks and history be easy targets + + elseif !a:domenu + let s:netrwcnt = 0 + let curwin = winnr() + windo if getline(2) =~# "Netrw" | let s:netrwcnt= s:netrwcnt + 1 | endif + exe curwin."wincmd w" + + if s:netrwcnt <= 1 + " call Decho("clear menus",'~'.expand("")) + exe 'sil! unmenu '.g:NetrwTopLvlMenu + " call Decho('exe sil! unmenu '.g:NetrwTopLvlMenu.'*','~'.expand("")) + sil! unlet s:netrw_menu_enabled + endif + endif + " call Dret("NetrwMenu") + return + endif + +endfun + +" --------------------------------------------------------------------- +" s:NetrwObtain: obtain file under cursor or from markfile list {{{2 +" Used by the O maps (as NetrwObtain()) +fun! s:NetrwObtain(islocal) + " call Dfunc("NetrwObtain(islocal=".a:islocal.")") + + let ykeep= @@ + if exists("s:netrwmarkfilelist_{bufnr('%')}") + let islocal= s:netrwmarkfilelist_{bufnr('%')}[1] !~ '^\a\{3,}://' + call netrw#Obtain(islocal,s:netrwmarkfilelist_{bufnr('%')}) + call s:NetrwUnmarkList(bufnr('%'),b:netrw_curdir) + else + call netrw#Obtain(a:islocal,s:NetrwGetWord()) + endif + let @@= ykeep + + " call Dret("NetrwObtain") +endfun + +" --------------------------------------------------------------------- +" s:NetrwPrevWinOpen: open file/directory in previous window. {{{2 +" If there's only one window, then the window will first be split. +" Returns: +" choice = 0 : didn't have to choose +" choice = 1 : saved modified file in window first +" choice = 2 : didn't save modified file, opened window +" choice = 3 : cancel open +fun! s:NetrwPrevWinOpen(islocal) + let ykeep= @@ + " grab a copy of the b:netrw_curdir to pass it along to newly split windows + let curdir = b:netrw_curdir + + " get last window number and the word currently under the cursor + let origwin = winnr() + let lastwinnr = winnr("$") + let curword = s:NetrwGetWord() + let choice = 0 + let s:prevwinopen= 1 " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen) + let s:treedir = s:NetrwTreeDir(a:islocal) + let curdir = s:treedir + + let didsplit = 0 + if lastwinnr == 1 + " if only one window, open a new one first + " g:netrw_preview=0: preview window shown in a horizontally split window + " g:netrw_preview=1: preview window shown in a vertically split window + if g:netrw_preview + " vertically split preview window + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize + exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s" + else + " horizontally split preview window + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize + exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s" + endif + let didsplit = 1 + + else + NetrwKeepj call s:SaveBufVars() + let eikeep= &ei + setl ei=all + wincmd p + + if exists("s:lexplore_win") && s:lexplore_win == winnr() + " whoops -- user trying to open file in the Lexplore window. + " Use Lexplore's opening-file window instead. + " exe g:netrw_chgwin."wincmd w" + wincmd p + call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) + endif + + " prevwinnr: the window number of the "prev" window + " prevbufnr: the buffer number of the buffer in the "prev" window + " bnrcnt : the qty of windows open on the "prev" buffer + let prevwinnr = winnr() + let prevbufnr = bufnr("%") + let prevbufname = bufname("%") + let prevmod = &mod + let bnrcnt = 0 + NetrwKeepj call s:RestoreBufVars() + + " if the previous window's buffer has been changed (ie. its modified flag is set), + " and it doesn't appear in any other extant window, then ask the + " user if s/he wants to abandon modifications therein. + if prevmod + windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif + exe prevwinnr."wincmd w" + + if bnrcnt == 1 && &hidden == 0 + " only one copy of the modified buffer in a window, and + " hidden not set, so overwriting will lose the modified file. Ask first... + let choice = confirm("Save modified buffer<".prevbufname."> first?","&Yes\n&No\n&Cancel") + let &ei= eikeep + + if choice == 1 + " Yes -- write file & then browse + let v:errmsg= "" + sil w + if v:errmsg != "" + call netrw#ErrorMsg(s:ERROR,"unable to write <".(exists("prevbufname")? prevbufname : 'n/a').">!",30) + exe origwin."wincmd w" + let &ei = eikeep + let @@ = ykeep + return choice + endif + + elseif choice == 2 + " No -- don't worry about changed file, just browse anyway + echomsg "**note** changes to ".prevbufname." abandoned" + + else + " Cancel -- don't do this + exe origwin."wincmd w" + let &ei= eikeep + let @@ = ykeep + return choice + endif + endif + endif + let &ei= eikeep + endif + + " restore b:netrw_curdir (window split/enew may have lost it) + let b:netrw_curdir= curdir + if a:islocal < 2 + if a:islocal + call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(a:islocal,curword,0)) + else + call s:NetrwBrowse(a:islocal,s:NetrwBrowseChgDir(a:islocal,curword,0)) + endif + endif + let @@= ykeep + return choice +endfun + +" --------------------------------------------------------------------- +" s:NetrwUpload: load fname to tgt (used by NetrwMarkFileCopy()) {{{2 +" Always assumed to be local -> remote +" call s:NetrwUpload(filename, target) +" call s:NetrwUpload(filename, target, fromdirectory) +fun! s:NetrwUpload(fname,tgt,...) + " call Dfunc("s:NetrwUpload(fname<".((type(a:fname) == 1)? a:fname : string(a:fname))."> tgt<".a:tgt.">) a:0=".a:0) + + if a:tgt =~ '^\a\{3,}://' + let tgtdir= substitute(a:tgt,'^\a\{3,}://[^/]\+/\(.\{-}\)$','\1','') + else + let tgtdir= substitute(a:tgt,'^\(.*\)/[^/]*$','\1','') + endif + " call Decho("tgtdir<".tgtdir.">",'~'.expand("")) + + if a:0 > 0 + let fromdir= a:1 + else + let fromdir= getcwd() + endif + " call Decho("fromdir<".fromdir.">",'~'.expand("")) + + if type(a:fname) == 1 + " handle uploading a single file using NetWrite + " call Decho("handle uploading a single file via NetWrite",'~'.expand("")) + 1split + " call Decho("exe e ".fnameescape(s:NetrwFile(a:fname)),'~'.expand("")) + exe "NetrwKeepj e ".fnameescape(s:NetrwFile(a:fname)) + " call Decho("now locally editing<".expand("%").">, has ".line("$")." lines",'~'.expand("")) + if a:tgt =~ '/$' + let wfname= substitute(a:fname,'^.*/','','') + " call Decho("exe w! ".fnameescape(wfname),'~'.expand("")) + exe "w! ".fnameescape(a:tgt.wfname) + else + " call Decho("writing local->remote: exe w ".fnameescape(a:tgt),'~'.expand("")) + exe "w ".fnameescape(a:tgt) + " call Decho("done writing local->remote",'~'.expand("")) + endif + q! + + elseif type(a:fname) == 3 + " handle uploading a list of files via scp + " call Decho("handle uploading a list of files via scp",'~'.expand("")) + let curdir= getcwd() + if a:tgt =~ '^scp:' + if s:NetrwLcd(fromdir) + " call Dret("s:NetrwUpload : lcd failure") + return + endif + let filelist= deepcopy(s:netrwmarkfilelist_{bufnr('%')}) + let args = join(map(filelist,"s:ShellEscape(v:val, 1)")) + if exists("g:netrw_port") && g:netrw_port != "" + let useport= " ".g:netrw_scpport." ".g:netrw_port + else + let useport= "" + endif + let machine = substitute(a:tgt,'^scp://\([^/:]\+\).*$','\1','') + let tgt = substitute(a:tgt,'^scp://[^/]\+/\(.*\)$','\1','') + call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.s:ShellEscape(useport,1)." ".args." ".s:ShellEscape(machine.":".tgt,1)) + if s:NetrwLcd(curdir) + " call Dret("s:NetrwUpload : lcd failure") + return + endif + + elseif a:tgt =~ '^ftp:' + call s:NetrwMethod(a:tgt) + + if b:netrw_method == 2 + " handle uploading a list of files via ftp+.netrc + let netrw_fname = b:netrw_fname + sil NetrwKeepj new + " call Decho("filter input window#".winnr(),'~'.expand("")) + + NetrwKeepj put =g:netrw_ftpmode + " call Decho("filter input: ".getline('$'),'~'.expand("")) + + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + + if tgtdir == "" + let tgtdir= '/' + endif + NetrwKeepj call setline(line("$")+1,'cd "'.tgtdir.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + + for fname in a:fname + NetrwKeepj call setline(line("$")+1,'put "'.s:NetrwFile(fname).'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endfor + + if exists("g:netrw_port") && g:netrw_port != "" + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1)) + else + " call Decho("filter input window#".winnr(),'~'.expand("")) + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)) + endif + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + sil NetrwKeepj g/Local directory now/d + call histdel("/",-1) + if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' + call netrw#ErrorMsg(s:ERROR,getline(1),14) + else + bw!|q + endif + + elseif b:netrw_method == 3 + " upload with ftp + machine, id, passwd, and fname (ie. no .netrc) + let netrw_fname= b:netrw_fname + NetrwKeepj call s:SaveBufVars()|sil NetrwKeepj new|NetrwKeepj call s:RestoreBufVars() + let tmpbufnr= bufnr("%") + setl ff=unix + + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + " call Decho("filter input: ".getline('$'),'~'.expand("")) + else + NetrwKeepj put ='open '.g:netrw_machine + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("g:netrw_uid") && g:netrw_uid != "" + if exists("g:netrw_ftp") && g:netrw_ftp == 1 + NetrwKeepj put =g:netrw_uid + " call Decho("filter input: ".getline('$'),'~'.expand("")) + if exists("s:netrw_passwd") + NetrwKeepj call setline(line("$")+1,'"'.s:netrw_passwd.'"') + endif + " call Decho("filter input: ".getline('$'),'~'.expand("")) + elseif exists("s:netrw_passwd") + NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + endif + + NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + + if exists("b:netrw_fname") && b:netrw_fname != "" + NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endif + + for fname in a:fname + NetrwKeepj call setline(line("$")+1,'put "'.fname.'"') + " call Decho("filter input: ".getline('$'),'~'.expand("")) + endfor + + " perform ftp: + " -i : turns off interactive prompting from ftp + " -n unix : DON'T use <.netrc>, even though it exists + " -n win32: quit being obnoxious about password + NetrwKeepj norm! 1G"_dd + call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) + " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) + sil NetrwKeepj g/Local directory now/d + call histdel("/",-1) + if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying ' + let debugkeep= &debug + setl debug=msg + call netrw#ErrorMsg(s:ERROR,getline(1),15) + let &debug = debugkeep + let mod = 1 + else + bw!|q + endif + elseif !exists("b:netrw_method") || b:netrw_method < 0 + " call Dret("s:#NetrwUpload : unsupported method") + return + endif + else + call netrw#ErrorMsg(s:ERROR,"can't obtain files with protocol from<".a:tgt.">",63) + endif + endif + + " call Dret("s:NetrwUpload") +endfun + +" --------------------------------------------------------------------- +" s:NetrwPreview: supports netrw's "p" map {{{2 +fun! s:NetrwPreview(path) range + " call Dfunc("NetrwPreview(path<".a:path.">)") + " call Decho("g:netrw_alto =".(exists("g:netrw_alto")? g:netrw_alto : 'n/a'),'~'.expand("")) + " call Decho("g:netrw_preview=".(exists("g:netrw_preview")? g:netrw_preview : 'n/a'),'~'.expand("")) + let ykeep= @@ + NetrwKeepj call s:NetrwOptionsSave("s:") + if a:path !~ '^\*\{1,2}/' && a:path !~ '^\a\{3,}://' + NetrwKeepj call s:NetrwOptionsSafe(1) + else + NetrwKeepj call s:NetrwOptionsSafe(0) + endif + if has("quickfix") + " call Decho("has quickfix",'~'.expand("")) + if !isdirectory(s:NetrwFile(a:path)) + " call Decho("good; not previewing a directory",'~'.expand("")) + if g:netrw_preview + " vertical split + let pvhkeep = &pvh + let winsz = (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize + let &pvh = winwidth(0) - winsz + " call Decho("g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily) g:netrw_winsize=".g:netrw_winsize,'~'.expand("")) + else + " horizontal split + let pvhkeep = &pvh + let winsz = (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize + let &pvh = winheight(0) - winsz + " call Decho("!g:netrw_preview: winsz=".winsz." &pvh=".&pvh." (temporarily) g:netrw_winsize=".g:netrw_winsize,'~'.expand("")) + endif + " g:netrw_preview g:netrw_alto + " 1 : vert 1: top -- preview window is vertically split off and on the left + " 1 : vert 0: bot -- preview window is vertically split off and on the right + " 0 : 1: top -- preview window is horizontally split off and on the top + " 0 : 0: bot -- preview window is horizontally split off and on the bottom + " + " Note that the file being previewed is already known to not be a directory, hence we can avoid doing a LocalBrowseCheck() check via + " the BufEnter event set up in netrwPlugin.vim + " call Decho("exe ".(g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path),'~'.expand("")) + let eikeep = &ei + set ei=BufEnter + exe (g:netrw_alto? "top " : "bot ").(g:netrw_preview? "vert " : "")."pedit ".fnameescape(a:path) + let &ei= eikeep + " call Decho("winnr($)=".winnr("$"),'~'.expand("")) + if exists("pvhkeep") + let &pvh= pvhkeep + endif + elseif !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, cannot preview a directory such as <".a:path.">",38) + endif + elseif !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, to preview your vim needs the quickfix feature compiled in",39) + endif + NetrwKeepj call s:NetrwOptionsRestore("s:") + let @@= ykeep + " call Dret("NetrwPreview") +endfun + +" --------------------------------------------------------------------- +" s:NetrwRefresh: {{{2 +fun! s:NetrwRefresh(islocal,dirname) + " call Dfunc("s:NetrwRefresh(islocal<".a:islocal.">,dirname=".a:dirname.") g:netrw_hide=".g:netrw_hide." g:netrw_sort_direction=".g:netrw_sort_direction) + " at the current time (Mar 19, 2007) all calls to NetrwRefresh() call NetrwBrowseChgDir() first. + setl ma noro + " call Decho("setl ma noro",'~'.expand("")) + " call Decho("clear buffer<".expand("%")."> with :%d",'~'.expand("")) + let ykeep = @@ + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + if !exists("w:netrw_treetop") + if exists("b:netrw_curdir") + let w:netrw_treetop= b:netrw_curdir + else + let w:netrw_treetop= getcwd() + endif + endif + NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop) + endif + + " save the cursor position before refresh. + let screenposn = winsaveview() + " call Decho("saving posn to screenposn<".string(screenposn).">",'~'.expand("")) + + " call Decho("win#".winnr().": ".winheight(0)."x".winwidth(0)." curfile<".expand("%").">",'~'.expand("")) + " call Decho("clearing buffer prior to refresh",'~'.expand("")) + sil! NetrwKeepj %d _ + if a:islocal + NetrwKeepj call netrw#LocalBrowseCheck(a:dirname) + else + NetrwKeepj call s:NetrwBrowse(a:islocal,a:dirname) + endif + + " restore position + " call Decho("restoring posn to screenposn<".string(screenposn).">",'~'.expand("")) + NetrwKeepj call winrestview(screenposn) + + " restore file marks + if has("syntax") && exists("g:syntax_on") && g:syntax_on + if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != "" + " " call Decho("exe 2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/",'~'.expand("")) + exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/" + else + " " call Decho("2match none (bufnr(%)=".bufnr("%")."<".bufname("%").">)",'~'.expand("")) + 2match none + endif + endif + + " restore + let @@= ykeep + " call Dret("s:NetrwRefresh") +endfun + +" --------------------------------------------------------------------- +" s:NetrwRefreshDir: refreshes a directory by name {{{2 +" Called by NetrwMarkFileCopy() +" Interfaces to s:NetrwRefresh() and s:LocalBrowseRefresh() +fun! s:NetrwRefreshDir(islocal,dirname) + if g:netrw_fastbrowse == 0 + " slowest mode (keep buffers refreshed, local or remote) + let tgtwin= bufwinnr(a:dirname) + + if tgtwin > 0 + " tgtwin is being displayed, so refresh it + let curwin= winnr() + exe tgtwin."wincmd w" + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + exe curwin."wincmd w" + + elseif bufnr(a:dirname) > 0 + let bn= bufnr(a:dirname) + exe "sil keepj bd ".bn + endif + + elseif g:netrw_fastbrowse <= 1 + NetrwKeepj call s:LocalBrowseRefresh() + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwSetChgwin: set g:netrw_chgwin; a will use the specified +" window number to do its editing in. +" Supports [count]C where the count, if present, is used to specify +" a window to use for editing via the mapping. +fun! s:NetrwSetChgwin(...) + " call Dfunc("s:NetrwSetChgwin() v:count=".v:count) + if a:0 > 0 + " call Decho("a:1<".a:1.">",'~'.expand("")) + if a:1 == "" " :NetrwC win# + let g:netrw_chgwin= winnr() + else " :NetrwC + let g:netrw_chgwin= a:1 + endif + elseif v:count > 0 " [count]C + let g:netrw_chgwin= v:count + else " C + let g:netrw_chgwin= winnr() + endif + echo "editing window now set to window#".g:netrw_chgwin + " call Dret("s:NetrwSetChgwin : g:netrw_chgwin=".g:netrw_chgwin) +endfun + +" --------------------------------------------------------------------- +" s:NetrwSetSort: sets up the sort based on the g:netrw_sort_sequence {{{2 +" What this function does is to compute a priority for the patterns +" in the g:netrw_sort_sequence. It applies a substitute to any +" "files" that satisfy each pattern, putting the priority / in +" front. An "*" pattern handles the default priority. +fun! s:NetrwSetSort() + " call Dfunc("SetSort() bannercnt=".w:netrw_bannercnt) + let ykeep= @@ + if w:netrw_liststyle == s:LONGLIST + let seqlist = substitute(g:netrw_sort_sequence,'\$','\\%(\t\\|\$\\)','ge') + else + let seqlist = g:netrw_sort_sequence + endif + " sanity check -- insure that * appears somewhere + if seqlist == "" + let seqlist= '*' + elseif seqlist !~ '\*' + let seqlist= seqlist.',*' + endif + let priority = 1 + while seqlist != "" + if seqlist =~ ',' + let seq = substitute(seqlist,',.*$','','e') + let seqlist = substitute(seqlist,'^.\{-},\(.*\)$','\1','e') + else + let seq = seqlist + let seqlist = "" + endif + if priority < 10 + let spriority= "00".priority.g:netrw_sepchr + elseif priority < 100 + let spriority= "0".priority.g:netrw_sepchr + else + let spriority= priority.g:netrw_sepchr + endif + " call Decho("priority=".priority." spriority<".spriority."> seq<".seq."> seqlist<".seqlist.">",'~'.expand("")) + + " sanity check + if w:netrw_bannercnt > line("$") + " apparently no files were left after a Hiding pattern was used + " call Dret("SetSort : no files left after hiding") + return + endif + if seq == '*' + let starpriority= spriority + else + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/'.seq.'/s/^/'.spriority.'/' + call histdel("/",-1) + " sometimes multiple sorting patterns will match the same file or directory. + " The following substitute is intended to remove the excess matches. + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^\d\{3}'.g:netrw_sepchr.'\d\{3}\//s/^\d\{3}'.g:netrw_sepchr.'\(\d\{3}\/\).\@=/\1/e' + NetrwKeepj call histdel("/",-1) + endif + let priority = priority + 1 + endwhile + if exists("starpriority") + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v/^\d\{3}'.g:netrw_sepchr.'/s/^/'.starpriority.'/e' + NetrwKeepj call histdel("/",-1) + endif + + " Following line associated with priority -- items that satisfy a priority + " pattern get prefixed by ###/ which permits easy sorting by priority. + " Sometimes files can satisfy multiple priority patterns -- only the latest + " priority pattern needs to be retained. So, at this point, these excess + " priority prefixes need to be removed, but not directories that happen to + " be just digits themselves. + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{3}'.g:netrw_sepchr.'\)\%(\d\{3}'.g:netrw_sepchr.'\)\+\ze./\1/e' + NetrwKeepj call histdel("/",-1) + let @@= ykeep + + " call Dret("SetSort") +endfun + +" --------------------------------------------------------------------- +" s:NetrwSetTgt: sets the target to the specified choice index {{{2 +" Implements [count]Tb (bookhist) +" [count]Th (bookhist) +" See :help netrw-qb for how to make the choice. +fun! s:NetrwSetTgt(islocal,bookhist,choice) + " call Dfunc("s:NetrwSetTgt(islocal=".a:islocal." bookhist<".a:bookhist."> choice#".a:choice.")") + + if a:bookhist == 'b' + " supports choosing a bookmark as a target using a qb-generated list + let choice= a:choice - 1 + if exists("g:netrw_bookmarklist[".choice."]") + call netrw#MakeTgt(g:netrw_bookmarklist[choice]) + else + echomsg "Sorry, bookmark#".a:choice." doesn't exist!" + endif + + elseif a:bookhist == 'h' + " supports choosing a history stack entry as a target using a qb-generated list + let choice= (a:choice % g:netrw_dirhistmax) + 1 + if exists("g:netrw_dirhist_".choice) + let histentry = g:netrw_dirhist_{choice} + call netrw#MakeTgt(histentry) + else + echomsg "Sorry, history#".a:choice." not available!" + endif + endif + + " refresh the display + if !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + endif + call s:NetrwRefresh(a:islocal,b:netrw_curdir) + + " call Dret("s:NetrwSetTgt") +endfun + +" ===================================================================== +" s:NetrwSortStyle: change sorting style (name - time - size - exten) and refresh display {{{2 +fun! s:NetrwSortStyle(islocal) + NetrwKeepj call s:NetrwSaveWordPosn() + let svpos= winsaveview() + + let g:netrw_sort_by= (g:netrw_sort_by =~# '^n')? 'time' : (g:netrw_sort_by =~# '^t')? 'size' : (g:netrw_sort_by =~# '^siz')? 'exten' : 'name' + NetrwKeepj norm! 0 + NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + NetrwKeepj call winrestview(svpos) +endfun + +" --------------------------------------------------------------------- +" s:NetrwSplit: mode {{{2 +" =0 : net and o +" =1 : net and t +" =2 : net and v +" =3 : local and o +" =4 : local and t +" =5 : local and v +fun! s:NetrwSplit(mode) + + let ykeep= @@ + call s:SaveWinVars() + + if a:mode == 0 + " remote and o + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize + if winsz == 0|let winsz= ""|endif + exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s" + let s:didsplit= 1 + NetrwKeepj call s:RestoreWinVars() + NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) + unlet s:didsplit + + elseif a:mode == 1 + " remote and t + let newdir = s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1) + tabnew + let s:didsplit= 1 + NetrwKeepj call s:RestoreWinVars() + NetrwKeepj call s:NetrwBrowse(0,newdir) + unlet s:didsplit + + elseif a:mode == 2 + " remote and v + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize + if winsz == 0|let winsz= ""|endif + exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v" + let s:didsplit= 1 + NetrwKeepj call s:RestoreWinVars() + NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord(),1)) + unlet s:didsplit + + elseif a:mode == 3 + " local and o + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize + if winsz == 0|let winsz= ""|endif + exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s" + let s:didsplit= 1 + NetrwKeepj call s:RestoreWinVars() + NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1)) + unlet s:didsplit + + elseif a:mode == 4 + " local and t + let cursorword = s:NetrwGetWord() + let eikeep = &ei + let netrw_winnr = winnr() + let netrw_line = line(".") + let netrw_col = virtcol(".") + NetrwKeepj norm! H0 + let netrw_hline = line(".") + setl ei=all + exe "NetrwKeepj norm! ".netrw_hline."G0z\" + exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\" + let &ei = eikeep + let netrw_curdir = s:NetrwTreeDir(0) + tabnew + let b:netrw_curdir = netrw_curdir + let s:didsplit = 1 + NetrwKeepj call s:RestoreWinVars() + NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,cursorword,0)) + if &ft == "netrw" + setl ei=all + exe "NetrwKeepj norm! ".netrw_hline."G0z\" + exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\" + let &ei= eikeep + endif + unlet s:didsplit + + elseif a:mode == 5 + " local and v + let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize + if winsz == 0|let winsz= ""|endif + exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v" + let s:didsplit= 1 + NetrwKeepj call s:RestoreWinVars() + NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord(),1)) + unlet s:didsplit + + else + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"(NetrwSplit) unsupported mode=".a:mode,45) + endif + + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwTgtMenu: {{{2 +fun! s:NetrwTgtMenu() + if !exists("s:netrw_menucnt") + return + endif + " call Dfunc("s:NetrwTgtMenu()") + + " the following test assures that gvim is running, has menus available, and has menus enabled. + if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu + if exists("g:NetrwTopLvlMenu") + " call Decho("removing ".g:NetrwTopLvlMenu."Bookmarks menu item(s)",'~'.expand("")) + exe 'sil! unmenu '.g:NetrwTopLvlMenu.'Targets' + endif + if !exists("s:netrw_initbookhist") + call s:NetrwBookHistRead() + endif + + " try to cull duplicate entries + let tgtdict={} + + " target bookmarked places + if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0 + " call Decho("installing bookmarks as easy targets",'~'.expand("")) + let cnt= 1 + for bmd in g:netrw_bookmarklist + if has_key(tgtdict,bmd) + let cnt= cnt + 1 + continue + endif + let tgtdict[bmd]= cnt + let ebmd= escape(bmd,g:netrw_menu_escape) + " show bookmarks for goto menu + " call Decho("menu: Targets: ".bmd,'~'.expand("")) + exe 'sil! menu '.g:NetrwMenuPriority.".19.1.".cnt." ".g:NetrwTopLvlMenu.'Targets.'.ebmd." :call netrw#MakeTgt('".bmd."')\" + let cnt= cnt + 1 + endfor + endif + + " target directory browsing history + if exists("g:netrw_dirhistmax") && g:netrw_dirhistmax > 0 + " call Decho("installing history as easy targets (histmax=".g:netrw_dirhistmax.")",'~'.expand("")) + let histcnt = 1 + while histcnt <= g:netrw_dirhistmax + let priority = g:netrw_dirhistcnt + histcnt + if exists("g:netrw_dirhist_{histcnt}") + let histentry = g:netrw_dirhist_{histcnt} + if has_key(tgtdict,histentry) + let histcnt = histcnt + 1 + continue + endif + let tgtdict[histentry] = histcnt + let ehistentry = escape(histentry,g:netrw_menu_escape) + " call Decho("menu: Targets: ".histentry,'~'.expand("")) + exe 'sil! menu '.g:NetrwMenuPriority.".19.2.".priority." ".g:NetrwTopLvlMenu.'Targets.'.ehistentry." :call netrw#MakeTgt('".histentry."')\" + endif + let histcnt = histcnt + 1 + endwhile + endif + endif + " call Dret("s:NetrwTgtMenu") +endfun + +" --------------------------------------------------------------------- +" s:NetrwTreeDir: determine tree directory given current cursor position {{{2 +" (full path directory with trailing slash returned) +fun! s:NetrwTreeDir(islocal) + + if exists("s:treedir") && exists("s:prevwinopen") + " s:NetrwPrevWinOpen opens a "previous" window -- and thus needs to and does call s:NetrwTreeDir early + let treedir= s:treedir + unlet s:treedir + unlet s:prevwinopen + return treedir + endif + if exists("s:prevwinopen") + unlet s:prevwinopen + endif + + if !exists("b:netrw_curdir") || b:netrw_curdir == "" + let b:netrw_curdir= getcwd() + endif + let treedir = b:netrw_curdir + let s:treecurpos= winsaveview() + + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + + " extract tree directory if on a line specifying a subdirectory (ie. ends with "/") + let curline= substitute(getline('.'),"\t -->.*$",'','') + if curline =~ '/$' + let treedir= substitute(getline('.'),'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e') + elseif curline =~ '@$' + let potentialdir= resolve(s:NetrwTreePath(w:netrw_treetop)) + else + let treedir= "" + endif + + " detect user attempting to close treeroot + if curline !~ '^'.s:treedepthstring && getline('.') != '..' + " now force a refresh + sil! NetrwKeepj %d _ + return b:netrw_curdir + endif + + " COMBAK: a symbolic link may point anywhere -- so it will be used to start a new treetop + " if a:islocal && curline =~ '@$' && isdirectory(s:NetrwFile(potentialdir)) + " let newdir = w:netrw_treetop.'/'.potentialdir + if a:islocal && curline =~ '@$' + if isdirectory(s:NetrwFile(potentialdir)) + let treedir = potentialdir + let w:netrw_treetop = treedir + endif + else + let potentialdir= s:NetrwFile(substitute(curline,'^'.s:treedepthstring.'\+ \(.*\)@$','\1','')) + let treedir = s:NetrwTreePath(w:netrw_treetop) + endif + endif + + " sanity maintenance: keep those //s away... + let treedir= substitute(treedir,'//$','/','') + return treedir +endfun + +" --------------------------------------------------------------------- +" s:NetrwTreeDisplay: recursive tree display {{{2 +fun! s:NetrwTreeDisplay(dir,depth) + " ensure that there are no folds + setl nofen + + " install ../ and shortdir + if a:depth == "" + call setline(line("$")+1,'../') + endif + if a:dir =~ '^\a\{3,}://' + if a:dir == w:netrw_treetop + let shortdir= a:dir + else + let shortdir= substitute(a:dir,'^.*/\([^/]\+\)/$','\1/','e') + endif + call setline(line("$")+1,a:depth.shortdir) + else + let shortdir= substitute(a:dir,'^.*/','','e') + call setline(line("$")+1,a:depth.shortdir.'/') + endif + " append a / to dir if its missing one + let dir= a:dir + + " display subtrees (if any) + let depth= s:treedepthstring.a:depth + + " implement g:netrw_hide for tree listings (uses g:netrw_list_hide) + if g:netrw_hide == 1 + " hide given patterns + let listhide= split(g:netrw_list_hide,',') + for pat in listhide + call filter(w:netrw_treedict[dir],'v:val !~ "'.escape(pat,'\\').'"') + endfor + + elseif g:netrw_hide == 2 + " show given patterns (only) + let listhide= split(g:netrw_list_hide,',') + let entries=[] + for entry in w:netrw_treedict[dir] + for pat in listhide + if entry =~ pat + call add(entries,entry) + break + endif + endfor + endfor + let w:netrw_treedict[dir]= entries + endif + if depth != "" + " always remove "." and ".." entries when there's depth + call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\.$"') + call filter(w:netrw_treedict[dir],'v:val !~ "\\.\\./$"') + call filter(w:netrw_treedict[dir],'v:val !~ "\\.$"') + call filter(w:netrw_treedict[dir],'v:val !~ "\\./$"') + endif + + for entry in w:netrw_treedict[dir] + if dir =~ '/$' + let direntry= substitute(dir.entry,'[@/]$','','e') + else + let direntry= substitute(dir.'/'.entry,'[@/]$','','e') + endif + if entry =~ '/$' && has_key(w:netrw_treedict,direntry) + NetrwKeepj call s:NetrwTreeDisplay(direntry,depth) + elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/') + NetrwKeepj call s:NetrwTreeDisplay(direntry.'/',depth) + elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@') + NetrwKeepj call s:NetrwTreeDisplay(direntry.'@',depth) + else + sil! NetrwKeepj call setline(line("$")+1,depth.entry) + endif + endfor +endfun + +" --------------------------------------------------------------------- +" s:NetrwRefreshTreeDict: updates the contents information for a tree (w:netrw_treedict) {{{2 +fun! s:NetrwRefreshTreeDict(dir) + if !exists("w:netrw_treedict") + return + endif + + for entry in w:netrw_treedict[a:dir] + let direntry= substitute(a:dir.'/'.entry,'[@/]$','','e') + + if entry =~ '/$' && has_key(w:netrw_treedict,direntry) + NetrwKeepj call s:NetrwRefreshTreeDict(direntry) + let filelist = s:NetrwLocalListingList(direntry,0) + let w:netrw_treedict[direntry] = sort(filelist) + + elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/') + NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/') + let filelist = s:NetrwLocalListingList(direntry.'/',0) + let w:netrw_treedict[direntry] = sort(filelist) + + elseif entry =~ '@$' && has_key(w:netrw_treedict,direntry.'@') + NetrwKeepj call s:NetrwRefreshTreeDict(direntry.'/') + let liststar = s:NetrwGlob(direntry.'/','*',1) + let listdotstar= s:NetrwGlob(direntry.'/','.*',1) + + else + endif + endfor +endfun + +" --------------------------------------------------------------------- +" s:NetrwTreeListing: displays tree listing from treetop on down, using NetrwTreeDisplay() {{{2 +" Called by s:PerformListing() +fun! s:NetrwTreeListing(dirname) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + + " update the treetop + if !exists("w:netrw_treetop") + let w:netrw_treetop= a:dirname + let s:netrw_treetop= w:netrw_treetop + " use \V in case the directory contains specials chars like '$' or '~' + elseif (w:netrw_treetop =~ ('^'.'\V'.a:dirname) && s:Strlen(a:dirname) < s:Strlen(w:netrw_treetop)) + \ || a:dirname !~ ('^'.'\V'.w:netrw_treetop) + let w:netrw_treetop= a:dirname + let s:netrw_treetop= w:netrw_treetop + endif + if exists("w:netrw_treetop") + let s:netrw_treetop= w:netrw_treetop + else + let w:netrw_treetop= getcwd() + let s:netrw_treetop= w:netrw_treetop + endif + + if !exists("w:netrw_treedict") + " insure that we have a treedict, albeit empty + let w:netrw_treedict= {} + endif + + " update the dictionary for the current directory + exe "sil! NetrwKeepj keepp ".w:netrw_bannercnt.',$g@^\.\.\=/$@d _' + let w:netrw_treedict[a:dirname]= getline(w:netrw_bannercnt,line("$")) + exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _" + + " if past banner, record word + if exists("w:netrw_bannercnt") && line(".") > w:netrw_bannercnt + let fname= expand("") + else + let fname= "" + endif + + " display from treetop on down + NetrwKeepj call s:NetrwTreeDisplay(w:netrw_treetop,"") + + " remove any blank line remaining as line#1 (happens in treelisting mode with banner suppressed) + while getline(1) =~ '^\s*$' && byte2line(1) > 0 + 1d + endwhile + + exe "setl ".g:netrw_bufsettings + + return + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwTreePath: returns path to current file/directory in tree listing {{{2 +" Normally, treetop is w:netrw_treetop, but a +" user of the function ( netrw#SetTreetop() ) +" wipes that out prior to calling this function +fun! s:NetrwTreePath(treetop) + " call Dfunc("s:NetrwTreePath(treetop<".a:treetop.">) line#".line(".")."<".getline(".").">") + if line(".") < w:netrw_bannercnt + 2 + let treedir= a:treetop + if treedir !~ '/$' + let treedir= treedir.'/' + endif + " call Dret("s:NetrwTreePath ".treedir." : line#".line(".")." ≤ ".(w:netrw_bannercnt+2)) + return treedir + endif + + let svpos = winsaveview() + " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) + let depth = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e') + " call Decho("depth<".depth."> 1st subst",'~'.expand("")) + let depth = substitute(depth,'^'.s:treedepthstring,'','') + " call Decho("depth<".depth."> 2nd subst (first depth removed)",'~'.expand("")) + let curline= getline('.') + " call Decho("curline<".curline.'>','~'.expand("")) + if curline =~ '/$' + " call Decho("extract tree directory from current line",'~'.expand("")) + let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e') + " call Decho("treedir<".treedir.">",'~'.expand("")) + elseif curline =~ '@\s\+-->' + " call Decho("extract tree directory using symbolic link",'~'.expand("")) + let treedir= substitute(curline,'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e') + let treedir= substitute(treedir,'@\s\+-->.*$','','e') + " call Decho("treedir<".treedir.">",'~'.expand("")) + else + " call Decho("do not extract tree directory from current line and set treedir to empty",'~'.expand("")) + let treedir= "" + endif + " construct treedir by searching backwards at correct depth + " call Decho("construct treedir by searching backwards for correct depth",'~'.expand("")) + " call Decho("initial treedir<".treedir."> depth<".depth.">",'~'.expand("")) + while depth != "" && search('^'.depth.'[^'.s:treedepthstring.'].\{-}/$','bW') + let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e') + let treedir= dirname.treedir + let depth = substitute(depth,'^'.s:treedepthstring,'','') + " call Decho("constructing treedir<".treedir.">: dirname<".dirname."> while depth<".depth.">",'~'.expand("")) + endwhile + " call Decho("treedir#1<".treedir.">",'~'.expand("")) + if a:treetop =~ '/$' + let treedir= a:treetop.treedir + else + let treedir= a:treetop.'/'.treedir + endif + " call Decho("treedir#2<".treedir.">",'~'.expand("")) + let treedir= substitute(treedir,'//$','/','') + " call Decho("treedir#3<".treedir.">",'~'.expand("")) + " call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand(""))" + call winrestview(svpos) + " call Dret("s:NetrwTreePath <".treedir.">") + return treedir +endfun + +" --------------------------------------------------------------------- +" s:NetrwWideListing: {{{2 +fun! s:NetrwWideListing() + + if w:netrw_liststyle == s:WIDELIST + " call Dfunc("NetrwWideListing() w:netrw_liststyle=".w:netrw_liststyle.' fo='.&fo.' l:fo='.&l:fo) + " look for longest filename (cpf=characters per filename) + " cpf: characters per filename + " fpl: filenames per line + " fpc: filenames per column + setl ma noro + let dict={} + " save the unnamed register and register 0-9 and a + let dict.a=[getreg('a'), getregtype('a')] + for i in range(0, 9) + let dict[i] = [getreg(i), getregtype(i)] + endfor + let dict.unnamed = [getreg(''), getregtype('')] + " call Decho("setl ma noro",'~'.expand("")) + let b:netrw_cpf= 0 + if line("$") >= w:netrw_bannercnt + " determine the maximum filename size; use that to set cpf + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif' + NetrwKeepj call histdel("/",-1) + else + " restore stored registers + call s:RestoreRegister(dict) + " call Dret("NetrwWideListing") + return + endif + " allow for two spaces to separate columns + let b:netrw_cpf= b:netrw_cpf + 2 + " call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand("")) + + " determine qty files per line (fpl) + let w:netrw_fpl= winwidth(0)/b:netrw_cpf + if w:netrw_fpl <= 0 + let w:netrw_fpl= 1 + endif + " call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl,'~'.expand("")) + + " make wide display + " fpc: files per column of wide listing + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'S",submatch(0)),"\\")/' + NetrwKeepj call histdel("/",-1) + let fpc = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl + let newcolstart = w:netrw_bannercnt + fpc + let newcolend = newcolstart + fpc - 1 + " call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand("")) + if !has('nvim') && has("clipboard") && g:netrw_clipboard + " call Decho("(s:NetrwWideListing) save @* and @+",'~'.expand("")) + sil! let keepregstar = @* + sil! let keepregplus = @+ + endif + while line("$") >= newcolstart + if newcolend > line("$") | let newcolend= line("$") | endif + let newcolqty= newcolend - newcolstart + exe newcolstart + " COMBAK: both of the visual-mode using lines below are problematic vis-a-vis @* + if newcolqty == 0 + exe "sil! NetrwKeepj norm! 0\$h\"ax".w:netrw_bannercnt."G$\"ap" + else + exe "sil! NetrwKeepj norm! 0\".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap' + endif + exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _' + exe 'sil! NetrwKeepj '.w:netrw_bannercnt + endwhile + if !has('nvim') && has("clipboard") + " call Decho("(s:NetrwWideListing) restore @* and @+",'~'.expand("")) + if @* != keepregstar | sil! let @* = keepregstar | endif + if @+ != keepregplus | sil! let @+ = keepregplus | endif + endif + exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e' + NetrwKeepj call histdel("/",-1) + exe 'nno w :call search(''^.\\|\s\s\zs\S'',''W'')'."\" + exe 'nno b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\" + " call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand("")) + exe "setl ".g:netrw_bufsettings + call s:RestoreRegister(dict) + " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + " call Dret("NetrwWideListing") + return + else + if hasmapto("w","n") + sil! nunmap w + endif + if hasmapto("b","n") + sil! nunmap b + endif + endif +endfun + +" --------------------------------------------------------------------- +" s:PerformListing: {{{2 +fun! s:PerformListing(islocal) + " call Dfunc("s:PerformListing(islocal=".a:islocal.")") + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) + " call Decho("settings: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (enter)"." ei<".&ei.">",'~'.expand("")) + sil! NetrwKeepj %d _ + " call DechoBuf(bufnr("%")) + + " set up syntax highlighting {{{3 + " call Decho("--set up syntax highlighting (ie. setl ft=netrw)",'~'.expand("")) + sil! setl ft=netrw + + NetrwKeepj call s:NetrwOptionsSafe(a:islocal) + setl noro ma + " call Decho("setl noro ma bh=".&bh,'~'.expand("")) + + " if exists("g:netrw_silent") && g:netrw_silent == 0 && &ch >= 1 " Decho + " call Decho("Processing your browsing request...",'~'.expand("")) + " endif " Decho + + " call Decho('w:netrw_liststyle='.(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'),'~'.expand("")) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") + " force a refresh for tree listings + " call Decho("force refresh for treelisting: clear buffer<".expand("%")."> with :%d",'~'.expand("")) + sil! NetrwKeepj %d _ + endif + + " save current directory on directory history list + NetrwKeepj call s:NetrwBookHistHandler(3,b:netrw_curdir) + + " Set up the banner {{{3 + if g:netrw_banner + " call Decho("--set up banner",'~'.expand("")) + NetrwKeepj call setline(1,'" ============================================================================') + if exists("g:netrw_pchk") + " this undocumented option allows pchk to run with different versions of netrw without causing spurious + " failure detections. + NetrwKeepj call setline(2,'" Netrw Directory Listing') + else + NetrwKeepj call setline(2,'" Netrw Directory Listing (netrw '.g:loaded_netrw.')') + endif + if exists("g:netrw_pchk") + let curdir= substitute(b:netrw_curdir,expand("$HOME"),'~','') + else + let curdir= b:netrw_curdir + endif + if exists("g:netrw_bannerbackslash") && g:netrw_bannerbackslash + NetrwKeepj call setline(3,'" '.substitute(curdir,'/','\\','g')) + else + NetrwKeepj call setline(3,'" '.curdir) + endif + let w:netrw_bannercnt= 3 + NetrwKeepj exe "sil! NetrwKeepj ".w:netrw_bannercnt + else + " call Decho("--no banner",'~'.expand("")) + NetrwKeepj 1 + let w:netrw_bannercnt= 1 + endif + " call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." win#".winnr(),'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) + + " construct sortby string: [name|time|size|exten] [reversed] + let sortby= g:netrw_sort_by + if g:netrw_sort_direction =~# "^r" + let sortby= sortby." reversed" + endif + + " Sorted by... {{{3 + if g:netrw_banner + " call Decho("--handle specified sorting: g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand("")) + if g:netrw_sort_by =~# "^n" + " call Decho("directories will be sorted by name",'~'.expand("")) + " sorted by name (also includes the sorting sequence in the banner) + NetrwKeepj put ='\" Sorted by '.sortby + NetrwKeepj put ='\" Sort sequence: '.g:netrw_sort_sequence + let w:netrw_bannercnt= w:netrw_bannercnt + 2 + else + " call Decho("directories will be sorted by size or time",'~'.expand("")) + " sorted by time, size, exten + NetrwKeepj put ='\" Sorted by '.sortby + let w:netrw_bannercnt= w:netrw_bannercnt + 1 + endif + exe "sil! NetrwKeepj ".w:netrw_bannercnt + " else " Decho + " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + endif + + " show copy/move target, if any {{{3 + if g:netrw_banner + if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal") + " call Decho("--show copy/move target<".s:netrwmftgt.">",'~'.expand("")) + NetrwKeepj put ='' + if s:netrwmftgt_islocal + sil! NetrwKeepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (local)') + else + sil! NetrwKeepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (remote)') + endif + let w:netrw_bannercnt= w:netrw_bannercnt + 1 + else + " call Decho("s:netrwmftgt does not exist, don't make Copy/Move Tgt",'~'.expand("")) + endif + exe "sil! NetrwKeepj ".w:netrw_bannercnt + endif + + " Hiding... -or- Showing... {{{3 + if g:netrw_banner + " call Decho("--handle hiding/showing in banner (g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">)",'~'.expand("")) + if g:netrw_list_hide != "" && g:netrw_hide + if g:netrw_hide == 1 + NetrwKeepj put ='\" Hiding: '.g:netrw_list_hide + else + NetrwKeepj put ='\" Showing: '.g:netrw_list_hide + endif + let w:netrw_bannercnt= w:netrw_bannercnt + 1 + endif + exe "NetrwKeepj ".w:netrw_bannercnt + + " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + let quickhelp = g:netrw_quickhelp%len(s:QuickHelp) + " call Decho("quickhelp =".quickhelp,'~'.expand("")) + NetrwKeepj put ='\" Quick Help: :help '.s:QuickHelp[quickhelp] + " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + NetrwKeepj put ='\" ==============================================================================' + let w:netrw_bannercnt= w:netrw_bannercnt + 2 + " else " Decho + " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + endif + + " bannercnt should index the line just after the banner + if g:netrw_banner + let w:netrw_bannercnt= w:netrw_bannercnt + 1 + exe "sil! NetrwKeepj ".w:netrw_bannercnt + " call Decho("--w:netrw_bannercnt=".w:netrw_bannercnt." (should index line just after banner) line($)=".line("$"),'~'.expand("")) + " else " Decho + " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + endif + + " get list of files + " call Decho("--Get list of files - islocal=".a:islocal,'~'.expand("")) + if a:islocal + NetrwKeepj call s:LocalListing() + else " remote + NetrwKeepj let badresult= s:NetrwRemoteListing() + if badresult + " call Decho("w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a')." win#".winnr()." buf#".bufnr("%")."<".bufname("%").">",'~'.expand("")) + " call Dret("s:PerformListing : error detected by NetrwRemoteListing") + return + endif + endif + + " manipulate the directory listing (hide, sort) {{{3 + if !exists("w:netrw_bannercnt") + let w:netrw_bannercnt= 0 + endif + " call Decho("--manipulate directory listing (hide, sort)",'~'.expand("")) + " call Decho("g:netrw_banner=".g:netrw_banner." w:netrw_bannercnt=".w:netrw_bannercnt." (banner complete)",'~'.expand("")) + " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + + if !g:netrw_banner || line("$") >= w:netrw_bannercnt + " call Decho("manipulate directory listing (support hide)",'~'.expand("")) + " call Decho("g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">",'~'.expand("")) + if g:netrw_hide && g:netrw_list_hide != "" + NetrwKeepj call s:NetrwListHide() + endif + if !g:netrw_banner || line("$") >= w:netrw_bannercnt + " call Decho("manipulate directory listing (sort) : g:netrw_sort_by<".g:netrw_sort_by.">",'~'.expand("")) + + if g:netrw_sort_by =~# "^n" + " sort by name + " call Decho("sort by name",'~'.expand("")) + NetrwKeepj call s:NetrwSetSort() + + if !g:netrw_banner || w:netrw_bannercnt < line("$") + " call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + if g:netrw_sort_direction =~# 'n' + " name: sort by name of file + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options + else + " reverse direction sorting + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options + endif + endif + + " remove priority pattern prefix + " call Decho("remove priority pattern prefix",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e' + NetrwKeepj call histdel("/",-1) + + elseif g:netrw_sort_by =~# "^ext" + " exten: sort by extension + " The histdel(...,-1) calls remove the last search from the search history + " call Decho("sort by extension",'~'.expand("")) + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g+/+s/^/001'.g:netrw_sepchr.'/' + NetrwKeepj call histdel("/",-1) + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+[./]+s/^/002'.g:netrw_sepchr.'/' + NetrwKeepj call histdel("/",-1) + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v+['.g:netrw_sepchr.'/]+s/^\(.*\.\)\(.\{-\}\)$/\2'.g:netrw_sepchr.'&/e' + NetrwKeepj call histdel("/",-1) + if !g:netrw_banner || w:netrw_bannercnt < line("$") + " call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + if g:netrw_sort_direction =~# 'n' + " normal direction sorting + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options + else + " reverse direction sorting + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options + endif + endif + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^.\{-}'.g:netrw_sepchr.'//e' + NetrwKeepj call histdel("/",-1) + + elseif a:islocal + if !g:netrw_banner || w:netrw_bannercnt < line("$") + " call Decho("g:netrw_sort_direction=".g:netrw_sort_direction,'~'.expand("")) + if g:netrw_sort_direction =~# 'n' + " call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort','~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options + else + " call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort!','~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options + endif + " call Decho("remove leading digits/ (sorting) information from listing",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e' + NetrwKeepj call histdel("/",-1) + endif + endif + + elseif g:netrw_sort_direction =~# 'r' + " call Decho('(s:PerformListing) reverse the sorted listing','~'.expand("")) + if !g:netrw_banner || w:netrw_bannercnt < line('$') + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g/^/m '.w:netrw_bannercnt + call histdel("/",-1) + endif + endif + endif + " call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")",'~'.expand("")) + + " convert to wide/tree listing {{{3 + " call Decho("--modify display if wide/tree listing style",'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#1)",'~'.expand("")) + NetrwKeepj call s:NetrwWideListing() + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#2)",'~'.expand("")) + NetrwKeepj call s:NetrwTreeListing(b:netrw_curdir) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#3)",'~'.expand("")) + + " resolve symbolic links if local and (thin or tree) + if a:islocal && (w:netrw_liststyle == s:THINLIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST)) + " call Decho("--resolve symbolic links if local and thin|tree",'~'.expand("")) + sil! keepp g/@$/call s:ShowLink() + endif + + if exists("w:netrw_bannercnt") && (line("$") >= w:netrw_bannercnt || !g:netrw_banner) + " place cursor on the top-left corner of the file listing + " call Decho("--place cursor on top-left corner of file listing",'~'.expand("")) + exe 'sil! '.w:netrw_bannercnt + sil! NetrwKeepj norm! 0 + " call Decho(" tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) + else + " call Decho("--did NOT place cursor on top-left corner",'~'.expand("")) + " call Decho(" w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'),'~'.expand("")) + " call Decho(" line($)=".line("$"),'~'.expand("")) + " call Decho(" g:netrw_banner=".(exists("g:netrw_banner")? g:netrw_banner : 'n/a'),'~'.expand("")) + endif + + " record previous current directory + let w:netrw_prvdir= b:netrw_curdir + " call Decho("--record netrw_prvdir<".w:netrw_prvdir.">",'~'.expand("")) + + " save certain window-oriented variables into buffer-oriented variables {{{3 + " call Decho("--save some window-oriented variables into buffer oriented variables",'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#4)",'~'.expand("")) + NetrwKeepj call s:SetBufWinVars() + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#5)",'~'.expand("")) + NetrwKeepj call s:NetrwOptionsRestore("w:") + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#6)",'~'.expand("")) + + " set display to netrw display settings + " call Decho("--set display to netrw display settings (".g:netrw_bufsettings.")",'~'.expand("")) + exe "setl ".g:netrw_bufsettings + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#7)",'~'.expand("")) + if g:netrw_liststyle == s:LONGLIST + " call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand("")) + exe "setl ts=".(g:netrw_maxfilenamelen+1) + endif + " call Decho("PerformListing buffer:",'~'.expand("")) + " call DechoBuf(bufnr("%")) + + if exists("s:treecurpos") + " call Decho("s:treecurpos exists; restore posn",'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#8)",'~'.expand("")) + " call Decho("restoring posn to s:treecurpos<".string(s:treecurpos).">",'~'.expand("")) + NetrwKeepj call winrestview(s:treecurpos) + unlet s:treecurpos + endif + + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (return)",'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol()." line($)=".line("$"),'~'.expand("")) + " call Dret("s:PerformListing : curpos<".string(getpos(".")).">") +endfun + +" --------------------------------------------------------------------- +" s:SetupNetrwStatusLine: {{{2 +fun! s:SetupNetrwStatusLine(statline) + " call Dfunc("SetupNetrwStatusLine(statline<".a:statline.">)") + + if !exists("s:netrw_setup_statline") + let s:netrw_setup_statline= 1 + " call Decho("do first-time status line setup",'~'.expand("")) + + if !exists("s:netrw_users_stl") + let s:netrw_users_stl= &stl + endif + if !exists("s:netrw_users_ls") + let s:netrw_users_ls= &laststatus + endif + + " set up User9 highlighting as needed + let dict={} + let dict.a=[getreg('a'), getregtype('a')] + redir @a + try + hi User9 + catch /^Vim\%((\a\{3,})\)\=:E411/ + if &bg == "dark" + hi User9 ctermfg=yellow ctermbg=blue guifg=yellow guibg=blue + else + hi User9 ctermbg=yellow ctermfg=blue guibg=yellow guifg=blue + endif + endtry + redir END + call s:RestoreRegister(dict) + endif + + " set up status line (may use User9 highlighting) + " insure that windows have a statusline + " make sure statusline is displayed + let &l:stl=a:statline + setl laststatus=2 + " call Decho("stl=".&stl,'~'.expand("")) + redraw + + " call Dret("SetupNetrwStatusLine : stl=".&stl) +endfun + +" ========================================= +" Remote Directory Browsing Support: {{{1 +" ========================================= + +" --------------------------------------------------------------------- +" s:NetrwRemoteFtpCmd: unfortunately, not all ftp servers honor options for ls {{{2 +" This function assumes that a long listing will be received. Size, time, +" and reverse sorts will be requested of the server but not otherwise +" enforced here. +fun! s:NetrwRemoteFtpCmd(path,listcmd) + " call Dfunc("NetrwRemoteFtpCmd(path<".a:path."> listcmd<".a:listcmd.">) w:netrw_method=".(exists("w:netrw_method")? w:netrw_method : (exists("b:netrw_method")? b:netrw_method : "???"))) + " call Decho("line($)=".line("$")." win#".winnr()." w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand("")) + " sanity check: {{{3 + if !exists("w:netrw_method") + if exists("b:netrw_method") + let w:netrw_method= b:netrw_method + else + call netrw#ErrorMsg(2,"(s:NetrwRemoteFtpCmd) internal netrw error",93) + " call Dret("NetrwRemoteFtpCmd") + return + endif + endif + + " WinXX ftp uses unix style input, so set ff to unix " {{{3 + let ffkeep= &ff + setl ma ff=unix noro + " call Decho("setl ma ff=unix noro",'~'.expand("")) + + " clear off any older non-banner lines " {{{3 + " note that w:netrw_bannercnt indexes the line after the banner + " call Decho('exe sil! NetrwKeepj '.w:netrw_bannercnt.",$d _ (clear off old non-banner lines)",'~'.expand("")) + exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d _" + + "......................................... + if w:netrw_method == 2 || w:netrw_method == 5 " {{{3 + " ftp + <.netrc>: Method #2 + if a:path != "" + NetrwKeepj put ='cd \"'.a:path.'\"' + endif + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + NetrwKeepj call setline(line("$")+1,a:listcmd) + " exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))' + if exists("g:netrw_port") && g:netrw_port != "" + " call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1),'~'.expand("")) + exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1)." ".s:ShellEscape(g:netrw_port,1) + else + " call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1),'~'.expand("")) + exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".s:ShellEscape(g:netrw_machine,1) + endif + + "......................................... + elseif w:netrw_method == 3 " {{{3 + " ftp + machine,id,passwd,filename: Method #3 + setl ff=unix + if exists("g:netrw_port") && g:netrw_port != "" + NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port + else + NetrwKeepj put ='open '.g:netrw_machine + endif + + " handle userid and password + let host= substitute(g:netrw_machine,'\..*$','','') + " call Decho("host<".host.">",'~'.expand("")) + if exists("s:netrw_hup") && exists("s:netrw_hup[host]") + call NetUserPass("ftp:".host) + endif + if exists("g:netrw_uid") && g:netrw_uid != "" + if exists("g:netrw_ftp") && g:netrw_ftp == 1 + NetrwKeepj put =g:netrw_uid + if exists("s:netrw_passwd") && s:netrw_passwd != "" + NetrwKeepj put ='\"'.s:netrw_passwd.'\"' + endif + elseif exists("s:netrw_passwd") + NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"' + endif + endif + + if a:path != "" + NetrwKeepj put ='cd \"'.a:path.'\"' + endif + if exists("g:netrw_ftpextracmd") + NetrwKeepj put =g:netrw_ftpextracmd + " call Decho("filter input: ".getline('.'),'~'.expand("")) + endif + NetrwKeepj call setline(line("$")+1,a:listcmd) + + " perform ftp: + " -i : turns off interactive prompting from ftp + " -n unix : DON'T use <.netrc>, even though it exists + " -n win32: quit being obnoxious about password + if exists("w:netrw_bannercnt") + " exe w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."),''~''.expand(""))' + call s:NetrwExe(s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) + " else " Decho + " call Decho("WARNING: w:netrw_bannercnt doesn't exist!",'~'.expand("")) + " g/^./call Decho("SKIPPING ftp#".line(".").": ".getline("."),'~'.expand("")) + endif + + "......................................... + elseif w:netrw_method == 9 " {{{3 + " sftp username@machine: Method #9 + " s:netrw_sftp_cmd + setl ff=unix + + " restore settings + let &l:ff= ffkeep + " call Dret("NetrwRemoteFtpCmd") + return + + "......................................... + else " {{{3 + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . bufname("%") . ">",23) + endif + + " cleanup for Windows " {{{3 + if has("win32") + sil! NetrwKeepj %s/\r$//e + NetrwKeepj call histdel("/",-1) + endif + if a:listcmd == "dir" + " infer directory/link based on the file permission string + sil! NetrwKeepj g/d\%([-r][-w][-x]\)\{3}/NetrwKeepj s@$@/@e + sil! NetrwKeepj g/l\%([-r][-w][-x]\)\{3}/NetrwKeepj s/$/@/e + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST) + exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/^\%(\S\+\s\+\)\{8}//e' + NetrwKeepj call histdel("/",-1) + endif + endif + + " ftp's listing doesn't seem to include ./ or ../ " {{{3 + if !search('^\.\/$\|\s\.\/$','wn') + exe 'NetrwKeepj '.w:netrw_bannercnt + NetrwKeepj put ='./' + endif + if !search('^\.\.\/$\|\s\.\.\/$','wn') + exe 'NetrwKeepj '.w:netrw_bannercnt + NetrwKeepj put ='../' + endif + + " restore settings " {{{3 + let &l:ff= ffkeep + " call Dret("NetrwRemoteFtpCmd") +endfun + +" --------------------------------------------------------------------- +" s:NetrwRemoteListing: {{{2 +fun! s:NetrwRemoteListing() + " call Dfunc("s:NetrwRemoteListing() b:netrw_curdir<".b:netrw_curdir.">) win#".winnr()) + + if !exists("w:netrw_bannercnt") && exists("s:bannercnt") + let w:netrw_bannercnt= s:bannercnt + endif + if !exists("w:netrw_bannercnt") && exists("b:bannercnt") + let w:netrw_bannercnt= b:bannercnt + endif + + call s:RemotePathAnalysis(b:netrw_curdir) + + " sanity check: + if exists("b:netrw_method") && b:netrw_method =~ '[235]' + " call Decho("b:netrw_method=".b:netrw_method,'~'.expand("")) + if !executable("ftp") + " call Decho("ftp is not executable",'~'.expand("")) + if !exists("g:netrw_quiet") + call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ftp",18) + endif + call s:NetrwOptionsRestore("w:") + " call Dret("s:NetrwRemoteListing -1") + return -1 + endif + + elseif !exists("g:netrw_list_cmd") || g:netrw_list_cmd == '' + " call Decho("g:netrw_list_cmd<",(exists("g:netrw_list_cmd")? 'n/a' : "-empty-").">",'~'.expand("")) + if !exists("g:netrw_quiet") + if g:netrw_list_cmd == "" + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your g:netrw_list_cmd is empty; perhaps ".g:netrw_ssh_cmd." is not executable on your system",47) + else + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ".g:netrw_list_cmd,19) + endif + endif + + NetrwKeepj call s:NetrwOptionsRestore("w:") + " call Dret("s:NetrwRemoteListing -1") + return -1 + endif " (remote handling sanity check) + " call Decho("passed remote listing sanity checks",'~'.expand("")) + + if exists("b:netrw_method") + " call Decho("setting w:netrw_method to b:netrw_method<".b:netrw_method.">",'~'.expand("")) + let w:netrw_method= b:netrw_method + endif + + if s:method == "ftp" + " use ftp to get remote file listing {{{3 + " call Decho("use ftp to get remote file listing",'~'.expand("")) + let s:method = "ftp" + let listcmd = g:netrw_ftp_list_cmd + if g:netrw_sort_by =~# '^t' + let listcmd= g:netrw_ftp_timelist_cmd + elseif g:netrw_sort_by =~# '^s' + let listcmd= g:netrw_ftp_sizelist_cmd + endif + " call Decho("listcmd<".listcmd."> (using g:netrw_ftp_list_cmd)",'~'.expand("")) + call s:NetrwRemoteFtpCmd(s:path,listcmd) + " exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("raw listing: ".getline("."),''~''.expand(""))' + + " report on missing file or directory messages + if search('[Nn]o such file or directory\|Failed to change directory') + let mesg= getline(".") + if exists("w:netrw_bannercnt") + setl ma + exe w:netrw_bannercnt.",$d _" + setl noma + endif + NetrwKeepj call s:NetrwOptionsRestore("w:") + call netrw#ErrorMsg(s:WARNING,mesg,96) + " call Dret("s:NetrwRemoteListing : -1") + return -1 + endif + + if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || (exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST) + " shorten the listing + " call Decho("generate short listing",'~'.expand("")) + exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt + + " cleanup + if g:netrw_ftp_browse_reject != "" + exe "sil! keepalt NetrwKeepj g/".g:netrw_ftp_browse_reject."/NetrwKeepj d" + NetrwKeepj call histdel("/",-1) + endif + sil! NetrwKeepj %s/\r$//e + NetrwKeepj call histdel("/",-1) + + " if there's no ../ listed, then put ../ in + let line1= line(".") + exe "sil! NetrwKeepj ".w:netrw_bannercnt + let line2= search('\.\.\/\%(\s\|$\)','cnW') + " call Decho("search(".'\.\.\/\%(\s\|$\)'."','cnW')=".line2." w:netrw_bannercnt=".w:netrw_bannercnt,'~'.expand("")) + if line2 == 0 + " call Decho("netrw is putting ../ into listing",'~'.expand("")) + sil! NetrwKeepj put='../' + endif + exe "sil! NetrwKeepj ".line1 + sil! NetrwKeepj norm! 0 + + " call Decho("line1=".line1." line2=".line2." line(.)=".line("."),'~'.expand("")) + if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup + " call Decho("M$ ftp cleanup",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+//' + NetrwKeepj call histdel("/",-1) + else " normal ftp cleanup + " call Decho("normal ftp cleanup",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2/e' + exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*/$#/#e' + exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*$#/#e' + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + endif + endif + + else + " use ssh to get remote file listing {{{3 + " call Decho("use ssh to get remote file listing: s:path<".s:path.">",'~'.expand("")) + let listcmd= s:MakeSshCmd(g:netrw_list_cmd) + " call Decho("listcmd<".listcmd."> (using g:netrw_list_cmd)",'~'.expand("")) + if g:netrw_scp_cmd =~ '^pscp' + " call Decho("1: exe r! ".s:ShellEscape(listcmd.s:path, 1),'~'.expand("")) + exe "NetrwKeepj r! ".listcmd.s:ShellEscape(s:path, 1) + " remove rubbish and adjust listing format of 'pscp' to 'ssh ls -FLa' like + sil! NetrwKeepj g/^Listing directory/NetrwKeepj d + sil! NetrwKeepj g/^d[-rwx][-rwx][-rwx]/NetrwKeepj s+$+/+e + sil! NetrwKeepj g/^l[-rwx][-rwx][-rwx]/NetrwKeepj s+$+@+e + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + if g:netrw_liststyle != s:LONGLIST + sil! NetrwKeepj g/^[dlsp-][-rwx][-rwx][-rwx]/NetrwKeepj s/^.*\s\(\S\+\)$/\1/e + NetrwKeepj call histdel("/",-1) + endif + else + if s:path == "" + " call Decho("2: exe r! ".listcmd,'~'.expand("")) + exe "NetrwKeepj keepalt r! ".listcmd + else + " call Decho("3: exe r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1),'~'.expand("")) + exe "NetrwKeepj keepalt r! ".listcmd.' '.s:ShellEscape(fnameescape(s:path),1) + " call Decho("listcmd<".listcmd."> path<".s:path.">",'~'.expand("")) + endif + endif + + " cleanup + if g:netrw_ssh_browse_reject != "" + " call Decho("cleanup: exe sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d",'~'.expand("")) + exe "sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d" + NetrwKeepj call histdel("/",-1) + endif + endif + + if w:netrw_liststyle == s:LONGLIST + " do a long listing; these substitutions need to be done prior to sorting {{{3 + " call Decho("fix long listing:",'~'.expand("")) + + if s:method == "ftp" + " cleanup + exe "sil! NetrwKeepj ".w:netrw_bannercnt + while getline('.') =~# g:netrw_ftp_browse_reject + sil! NetrwKeepj d + endwhile + " if there's no ../ listed, then put ../ in + let line1= line(".") + sil! NetrwKeepj 1 + sil! NetrwKeepj call search('^\.\.\/\%(\s\|$\)','W') + let line2= line(".") + if line2 == 0 + if b:netrw_curdir != '/' + exe 'sil! NetrwKeepj '.w:netrw_bannercnt."put='../'" + endif + endif + exe "sil! NetrwKeepj ".line1 + sil! NetrwKeepj norm! 0 + endif + + if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup + " call Decho("M$ ftp site listing cleanup",'~'.expand("")) + exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(\|\d\+\)\s\+\)\(\w.*\)$/\2\t\1/' + elseif exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") + " call Decho("normal ftp site listing cleanup: bannercnt=".w:netrw_bannercnt." line($)=".line("$"),'~'.expand("")) + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/ -> .*$//e' + exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2 \t\1/e' + exe 'sil NetrwKeepj '.w:netrw_bannercnt + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + NetrwKeepj call histdel("/",-1) + endif + endif + + " if exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") " Decho + " exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("listing: ".getline("."),''~''.expand(""))' + " endif " Decho + + " call Dret("s:NetrwRemoteListing 0") + return 0 +endfun + +" --------------------------------------------------------------------- +" s:NetrwRemoteRm: remove/delete a remote file or directory {{{2 +fun! s:NetrwRemoteRm(usrhost,path) range + let svpos= winsaveview() + + let all= 0 + if exists("s:netrwmarkfilelist_{bufnr('%')}") + " remove all marked files + for fname in s:netrwmarkfilelist_{bufnr("%")} + let ok= s:NetrwRemoteRmFile(a:path,fname,all) + if ok =~# 'q\%[uit]' + break + elseif ok =~# 'a\%[ll]' + let all= 1 + endif + endfor + call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) + + else + " remove files specified by range + + " preparation for removing multiple files/directories + let keepsol = &l:sol + setl nosol + let ctr = a:firstline + + " remove multiple files and directories + while ctr <= a:lastline + exe "NetrwKeepj ".ctr + let ok= s:NetrwRemoteRmFile(a:path,s:NetrwGetWord(),all) + if ok =~# 'q\%[uit]' + break + elseif ok =~# 'a\%[ll]' + let all= 1 + endif + let ctr= ctr + 1 + endwhile + let &l:sol = keepsol + endif + + " refresh the (remote) directory listing + NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) + NetrwKeepj call winrestview(svpos) +endfun + +" --------------------------------------------------------------------- +" s:NetrwRemoteRmFile: {{{2 +fun! s:NetrwRemoteRmFile(path,rmfile,all) + " call Dfunc("s:NetrwRemoteRmFile(path<".a:path."> rmfile<".a:rmfile.">) all=".a:all) + + let all= a:all + let ok = "" + + if a:rmfile !~ '^"' && (a:rmfile =~ '@$' || a:rmfile !~ '[\/]$') + " attempt to remove file + " call Decho("attempt to remove file (all=".all.")",'~'.expand("")) + if !all + echohl Statement + " call Decho("case all=0:",'~'.expand("")) + call inputsave() + let ok= input("Confirm deletion of file<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") + call inputrestore() + echohl NONE + if ok == "" + let ok="no" + endif + let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') + if ok =~# 'a\%[ll]' + let all= 1 + endif + endif + + if all || ok =~# 'y\%[es]' || ok == "" + " call Decho("case all=".all." or ok<".ok.">".(exists("w:netrw_method")? ': netrw_method='.w:netrw_method : ""),'~'.expand("")) + if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) + " call Decho("case ftp:",'~'.expand("")) + let path= a:path + if path =~ '^\a\{3,}://' + let path= substitute(path,'^\a\{3,}://[^/]\+/','','') + endif + sil! NetrwKeepj .,$d _ + call s:NetrwRemoteFtpCmd(path,"delete ".'"'.a:rmfile.'"') + else + " call Decho("case ssh: g:netrw_rm_cmd<".g:netrw_rm_cmd.">",'~'.expand("")) + let netrw_rm_cmd= s:MakeSshCmd(g:netrw_rm_cmd) + " call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand("")) + if !exists("b:netrw_curdir") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53) + let ok="q" + else + let remotedir= substitute(b:netrw_curdir,'^.\{-}//[^/]\+/\(.*\)$','\1','') + " call Decho("netrw_rm_cmd<".netrw_rm_cmd.">",'~'.expand("")) + " call Decho("remotedir<".remotedir.">",'~'.expand("")) + " call Decho("rmfile<".a:rmfile.">",'~'.expand("")) + if remotedir != "" + let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(remotedir.a:rmfile)) + else + let netrw_rm_cmd= netrw_rm_cmd." ".s:ShellEscape(fnameescape(a:rmfile)) + endif + " call Decho("call system(".netrw_rm_cmd.")",'~'.expand("")) + let ret= system(netrw_rm_cmd) + if v:shell_error != 0 + if exists("b:netrw_curdir") && b:netrw_curdir != getcwd() && !g:netrw_keepdir + call netrw#ErrorMsg(s:ERROR,"remove failed; perhaps due to vim's current directory<".getcwd()."> not matching netrw's (".b:netrw_curdir.") (see :help netrw-cd)",102) + else + call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60) + endif + elseif ret != 0 + call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60) + endif + " call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand("")) + endif + endif + elseif ok =~# 'q\%[uit]' + " call Decho("ok==".ok,'~'.expand("")) + endif + + else + " attempt to remove directory + " call Decho("attempt to remove directory",'~'.expand("")) + if !all + call inputsave() + let ok= input("Confirm deletion of directory<".a:rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") + call inputrestore() + if ok == "" + let ok="no" + endif + let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') + if ok =~# 'a\%[ll]' + let all= 1 + endif + endif + + if all || ok =~# 'y\%[es]' || ok == "" + if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) + NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rmdir ".a:rmfile) + else + let rmfile = substitute(a:path.a:rmfile,'/$','','') + let netrw_rmdir_cmd = s:MakeSshCmd(netrw#WinPath(g:netrw_rmdir_cmd)).' '.s:ShellEscape(netrw#WinPath(rmfile)) + " call Decho("attempt to remove dir: system(".netrw_rmdir_cmd.")",'~'.expand("")) + let ret= system(netrw_rmdir_cmd) + " call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand("")) + + if v:shell_error != 0 + " call Decho("v:shell_error not 0",'~'.expand("")) + let netrw_rmf_cmd= s:MakeSshCmd(netrw#WinPath(g:netrw_rmf_cmd)).' '.s:ShellEscape(netrw#WinPath(substitute(rmfile,'[\/]$','','e'))) + " call Decho("2nd attempt to remove dir: system(".netrw_rmf_cmd.")",'~'.expand("")) + let ret= system(netrw_rmf_cmd) + " call Decho("returned=".ret." errcode=".v:shell_error,'~'.expand("")) + + if v:shell_error != 0 && !exists("g:netrw_quiet") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",22) + endif + endif + endif + + elseif ok =~# 'q\%[uit]' + " call Decho("ok==".ok,'~'.expand("")) + endif + endif + + " call Dret("s:NetrwRemoteRmFile ".ok) + return ok +endfun + +" --------------------------------------------------------------------- +" s:NetrwRemoteRename: rename a remote file or directory {{{2 +fun! s:NetrwRemoteRename(usrhost,path) range + + " preparation for removing multiple files/directories + let svpos = winsaveview() + " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("")) + let ctr = a:firstline + let rename_cmd = s:MakeSshCmd(g:netrw_rename_cmd) + + " rename files given by the markfilelist + if exists("s:netrwmarkfilelist_{bufnr('%')}") + for oldname in s:netrwmarkfilelist_{bufnr("%")} + if exists("subfrom") + let newname= substitute(oldname,subfrom,subto,'') + else + call inputsave() + let newname= input("Moving ".oldname." to : ",oldname) + call inputrestore() + if newname =~ '^s/' + let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','') + let subto = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','') + let newname = substitute(oldname,subfrom,subto,'') + endif + endif + + if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) + NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname) + else + let oldname= s:ShellEscape(a:path.oldname) + let newname= s:ShellEscape(a:path.newname) + let ret = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname) + endif + + endfor + call s:NetrwUnMarkFile(1) + + else + + " attempt to rename files/directories + let keepsol= &l:sol + setl nosol + while ctr <= a:lastline + exe "NetrwKeepj ".ctr + + let oldname= s:NetrwGetWord() + + call inputsave() + let newname= input("Moving ".oldname." to : ",oldname) + call inputrestore() + + if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3) + call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname) + else + let oldname= s:ShellEscape(a:path.oldname) + let newname= s:ShellEscape(a:path.newname) + let ret = system(netrw#WinPath(rename_cmd).' '.oldname.' '.newname) + endif + + let ctr= ctr + 1 + endwhile + let &l:sol= keepsol + endif + + " refresh the directory + NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./',0)) + NetrwKeepj call winrestview(svpos) +endfun + +" ========================================== +" Local Directory Browsing Support: {{{1 +" ========================================== + +" --------------------------------------------------------------------- +" netrw#FileUrlEdit: handles editing file://* files {{{2 +" Should accept: file://localhost/etc/fstab +" file:///etc/fstab +" file:///c:/WINDOWS/clock.avi +" file:///c|/WINDOWS/clock.avi +" file://localhost/c:/WINDOWS/clock.avi +" file://localhost/c|/WINDOWS/clock.avi +" file://c:/foo.txt +" file:///c:/foo.txt +" and %XX (where X is [0-9a-fA-F] is converted into a character with the given hexadecimal value +fun! netrw#FileUrlEdit(fname) + " call Dfunc("netrw#FileUrlEdit(fname<".a:fname.">)") + let fname = a:fname + if fname =~ '^file://localhost/' + " call Decho('converting file://localhost/ -to- file:///','~'.expand("")) + let fname= substitute(fname,'^file://localhost/','file:///','') + " call Decho("fname<".fname.">",'~'.expand("")) + endif + if has("win32") + if fname =~ '^file:///\=\a[|:]/' + " call Decho('converting file:///\a|/ -to- file://\a:/','~'.expand("")) + let fname = substitute(fname,'^file:///\=\(\a\)[|:]/','file://\1:/','') + " call Decho("fname<".fname.">",'~'.expand("")) + endif + endif + let fname2396 = netrw#RFC2396(fname) + let fname2396e= fnameescape(fname2396) + let plainfname= substitute(fname2396,'file://\(.*\)','\1',"") + if has("win32") + " call Decho("windows exception for plainfname",'~'.expand("")) + if plainfname =~ '^/\+\a:' + " call Decho('removing leading "/"s','~'.expand("")) + let plainfname= substitute(plainfname,'^/\+\(\a:\)','\1','') + endif + endif + + " call Decho("fname2396<".fname2396.">",'~'.expand("")) + " call Decho("plainfname<".plainfname.">",'~'.expand("")) + exe "sil doau BufReadPre ".fname2396e + exe 'NetrwKeepj keepalt edit '.plainfname + exe 'sil! NetrwKeepj keepalt bdelete '.fnameescape(a:fname) + + " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + " call Dret("netrw#FileUrlEdit") + exe "sil doau BufReadPost ".fname2396e +endfun + +" --------------------------------------------------------------------- +" netrw#LocalBrowseCheck: {{{2 +fun! netrw#LocalBrowseCheck(dirname) + " This function is called by netrwPlugin.vim's s:LocalBrowseCheck(), s:NetrwRexplore(), + " and by when atop a listed file/directory (via a buffer-local map) + " + " unfortunate interaction -- split window debugging can't be used here, must use + " D-echoRemOn or D-echoTabOn as the BufEnter event triggers + " another call to LocalBrowseCheck() when attempts to write + " to the DBG buffer are made. + " + " The &ft == "netrw" test was installed because the BufEnter event + " would hit when re-entering netrw windows, creating unexpected + " refreshes (and would do so in the middle of NetrwSaveOptions(), too) + " call Dfunc("netrw#LocalBrowseCheck(dirname<".a:dirname.">)") + " call Decho("isdir<".a:dirname."> =".isdirectory(s:NetrwFile(a:dirname)).((exists("s:treeforceredraw")? " treeforceredraw" : "")).'~'.expand("")) + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " getting E930: Cannot use :redir inside execute + "" call Dredir("ls!","netrw#LocalBrowseCheck") + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Decho("current buffer#".bufnr("%")."<".bufname("%")."> ft=".&ft,'~'.expand("")) + + let ykeep= @@ + if isdirectory(s:NetrwFile(a:dirname)) + " call Decho("is-directory ft<".&ft."> b:netrw_curdir<".(exists("b:netrw_curdir")? b:netrw_curdir : " doesn't exist")."> dirname<".a:dirname.">"." line($)=".line("$")." ft<".&ft."> g:netrw_fastbrowse=".g:netrw_fastbrowse,'~'.expand("")) + + if &ft != "netrw" || (exists("b:netrw_curdir") && b:netrw_curdir != a:dirname) || g:netrw_fastbrowse <= 1 + " call Decho("case 1 : ft=".&ft,'~'.expand("")) + " call Decho("s:rexposn_".bufnr("%")."<".bufname("%")."> ".(exists("s:rexposn_".bufnr("%"))? "exists" : "does not exist"),'~'.expand("")) + sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname) + + elseif &ft == "netrw" && line("$") == 1 + " call Decho("case 2 (ft≡netrw && line($)≡1)",'~'.expand("")) + sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname) + + elseif exists("s:treeforceredraw") + " call Decho("case 3 (treeforceredraw)",'~'.expand("")) + unlet s:treeforceredraw + sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname) + endif + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " call Dret("netrw#LocalBrowseCheck") + return + endif + + " The following code wipes out currently unused netrw buffers + " IF g:netrw_fastbrowse is zero (ie. slow browsing selected) + " AND IF the listing style is not a tree listing + if exists("g:netrw_fastbrowse") && g:netrw_fastbrowse == 0 && g:netrw_liststyle != s:TREELIST + " call Decho("wiping out currently unused netrw buffers",'~'.expand("")) + let ibuf = 1 + let buflast = bufnr("$") + while ibuf <= buflast + if bufwinnr(ibuf) == -1 && isdirectory(s:NetrwFile(bufname(ibuf))) + exe "sil! keepj keepalt ".ibuf."bw!" + endif + let ibuf= ibuf + 1 + endwhile + endif + let @@= ykeep + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("")) + " not a directory, ignore it + " call Dret("netrw#LocalBrowseCheck : not a directory, ignoring it; dirname<".a:dirname.">") +endfun + +" --------------------------------------------------------------------- +" s:LocalBrowseRefresh: this function is called after a user has {{{2 +" performed any shell command. The idea is to cause all local-browsing +" buffers to be refreshed after a user has executed some shell command, +" on the chance that s/he removed/created a file/directory with it. +fun! s:LocalBrowseRefresh() + " determine which buffers currently reside in a tab + if !exists("s:netrw_browselist") + return + endif + if !exists("w:netrw_bannercnt") + return + endif + if !empty(getcmdwintype()) + " cannot move away from cmdline window, see :h E11 + return + endif + if exists("s:netrw_events") && s:netrw_events == 1 + " s:LocalFastBrowser gets called (indirectly) from a + let s:netrw_events= 2 + return + endif + let itab = 1 + let buftablist = [] + let ykeep = @@ + while itab <= tabpagenr("$") + let buftablist = buftablist + tabpagebuflist() + let itab = itab + 1 + sil! tabn + endwhile + " GO through all buffers on netrw_browselist (ie. just local-netrw buffers): + " | refresh any netrw window + " | wipe out any non-displaying netrw buffer + let curwinid = win_getid(winnr()) + let ibl = 0 + for ibuf in s:netrw_browselist + if bufwinnr(ibuf) == -1 && index(buftablist,ibuf) == -1 + " wipe out any non-displaying netrw buffer + " (ibuf not shown in a current window AND + " ibuf not in any tab) + exe "sil! keepj bd ".fnameescape(ibuf) + call remove(s:netrw_browselist,ibl) + continue + elseif index(tabpagebuflist(),ibuf) != -1 + " refresh any netrw buffer + exe bufwinnr(ibuf)."wincmd w" + if getline(".") =~# 'Quick Help' + " decrement g:netrw_quickhelp to prevent refresh from changing g:netrw_quickhelp + " (counteracts s:NetrwBrowseChgDir()'s incrementing) + let g:netrw_quickhelp= g:netrw_quickhelp - 1 + endif + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + NetrwKeepj call s:NetrwRefreshTreeDict(w:netrw_treetop) + endif + NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) + endif + let ibl= ibl + 1 + endfor + call win_gotoid(curwinid) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:LocalFastBrowser: handles setting up/taking down fast browsing for the local browser {{{2 +" +" g:netrw_ Directory Is +" fastbrowse Local Remote +" slow 0 D D D=Deleting a buffer implies it will not be re-used (slow) +" med 1 D H H=Hiding a buffer implies it may be re-used (fast) +" fast 2 H H +" +" Deleting a buffer means that it will be re-loaded when examined, hence "slow". +" Hiding a buffer means that it will be re-used when examined, hence "fast". +" (re-using a buffer may not be as accurate) +" +" s:netrw_events : doesn't exist, s:LocalFastBrowser() will install autocmds with medium-speed or fast browsing +" =1: autocmds installed, but ignore next FocusGained event to avoid initial double-refresh of listing. +" BufEnter may be first event, then a FocusGained event. Ignore the first FocusGained event. +" If :Explore used: it sets s:netrw_events to 2, so no FocusGained events are ignored. +" =2: autocmds installed (doesn't ignore any FocusGained events) +fun! s:LocalFastBrowser() + + " initialize browselist, a list of buffer numbers that the local browser has used + if !exists("s:netrw_browselist") + let s:netrw_browselist= [] + endif + + " append current buffer to fastbrowse list + if empty(s:netrw_browselist) || bufnr("%") > s:netrw_browselist[-1] + call add(s:netrw_browselist,bufnr("%")) + endif + + " enable autocmd events to handle refreshing/removing local browser buffers + " If local browse buffer is currently showing: refresh it + " If local browse buffer is currently hidden : wipe it + " g:netrw_fastbrowse=0 : slow speed, never re-use directory listing + " =1 : medium speed, re-use directory listing for remote only + " =2 : fast speed, always re-use directory listing when possible + if g:netrw_fastbrowse <= 1 && !exists("#ShellCmdPost") && !exists("s:netrw_events") + let s:netrw_events= 1 + augroup AuNetrwEvent + au! + if has("win32") + au ShellCmdPost * call s:LocalBrowseRefresh() + else + au ShellCmdPost,FocusGained * call s:LocalBrowseRefresh() + endif + augroup END + + " user must have changed fastbrowse to its fast setting, so remove + " the associated autocmd events + elseif g:netrw_fastbrowse > 1 && exists("#ShellCmdPost") && exists("s:netrw_events") + unlet s:netrw_events + augroup AuNetrwEvent + au! + augroup END + augroup! AuNetrwEvent + endif +endfun + +fun! s:NetrwLocalListingList(dirname,setmaxfilenamelen) + " get the list of files contained in the current directory + let dirname = a:dirname + let dirnamelen = strlen(dirname) + let filelist = s:NetrwGlob(dirname,"*",0) + let filelist = filelist + s:NetrwGlob(dirname,".*",0) + + if g:netrw_cygwin == 0 && has("win32") + elseif index(filelist,'..') == -1 && dirname !~ '/' + " include ../ in the glob() entry if its missing + let filelist= filelist+[s:ComposePath(dirname,"../")] + endif + + if a:setmaxfilenamelen && get(g:, 'netrw_dynamic_maxfilenamelen', 0) + let filelistcopy = map(deepcopy(filelist),'fnamemodify(v:val, ":t")') + let g:netrw_maxfilenamelen = max(map(filelistcopy,'len(v:val)')) + 1 + endif + + let resultfilelist = [] + for filename in filelist + + if getftype(filename) == "link" + " indicate a symbolic link + let pfile= filename."@" + + elseif getftype(filename) == "socket" + " indicate a socket + let pfile= filename."=" + + elseif getftype(filename) == "fifo" + " indicate a fifo + let pfile= filename."|" + + elseif isdirectory(s:NetrwFile(filename)) + " indicate a directory + let pfile= filename."/" + + elseif exists("b:netrw_curdir") && b:netrw_curdir !~ '^.*://' && !isdirectory(s:NetrwFile(filename)) + if has("win32") + if filename =~ '\.[eE][xX][eE]$' || filename =~ '\.[cC][oO][mM]$' || filename =~ '\.[bB][aA][tT]$' + " indicate an executable + let pfile= filename."*" + else + " normal file + let pfile= filename + endif + elseif executable(filename) + " indicate an executable + let pfile= filename."*" + else + " normal file + let pfile= filename + endif + + else + " normal file + let pfile= filename + endif + + if pfile =~ '//$' + let pfile= substitute(pfile,'//$','/','e') + endif + let pfile= strpart(pfile,dirnamelen) + let pfile= substitute(pfile,'^[/\\]','','e') + + if w:netrw_liststyle == s:LONGLIST + let longfile = printf("%-".g:netrw_maxfilenamelen."S",pfile) + let sz = getfsize(filename) + let szlen = 15 - (strdisplaywidth(longfile) - g:netrw_maxfilenamelen) + let szlen = (szlen > 0) ? szlen : 0 + + if g:netrw_sizestyle =~# "[hH]" + let sz= s:NetrwHumanReadable(sz) + endif + let fsz = printf("%".szlen."S",sz) + let pfile= longfile." ".fsz." ".strftime(g:netrw_timefmt,getftime(filename)) + endif + + if g:netrw_sort_by =~# "^t" + " sort by time (handles time up to 1 quintillion seconds, US) + " Decorate listing by prepending a timestamp/ . Sorting will then be done based on time. + let t = getftime(filename) + let ft = printf("%018d",t) + let ftpfile= ft.'/'.pfile + let resultfilelist += [ftpfile] + + elseif g:netrw_sort_by =~ "^s" + " sort by size (handles file sizes up to 1 quintillion bytes, US) + let sz = getfsize(filename) + let fsz = printf("%018d",sz) + let fszpfile= fsz.'/'.pfile + let resultfilelist += [fszpfile] + + else + " sort by name + let resultfilelist += [pfile] + endif + endfor + + return resultfilelist +endfun + +" --------------------------------------------------------------------- +" s:LocalListing: does the job of "ls" for local directories {{{2 +fun! s:LocalListing() + + let filelist = s:NetrwLocalListingList(b:netrw_curdir, 1) + for filename in filelist + sil! NetrwKeepj put =filename + endfor + + " cleanup any windows mess at end-of-line + sil! NetrwKeepj g/^$/d + sil! NetrwKeepj %s/\r$//e + call histdel("/",-1) + exe "setl ts=".(g:netrw_maxfilenamelen+1) +endfun + +" --------------------------------------------------------------------- +" s:NetrwLocalExecute: uses system() to execute command under cursor ("X" command support) {{{2 +fun! s:NetrwLocalExecute(cmd) + " call Dfunc("s:NetrwLocalExecute(cmd<".a:cmd.">)") + let ykeep= @@ + " sanity check + if !executable(a:cmd) + call netrw#ErrorMsg(s:ERROR,"the file<".a:cmd."> is not executable!",89) + let @@= ykeep + " call Dret("s:NetrwLocalExecute") + return + endif + + let optargs= input(":!".a:cmd,"","file") + " call Decho("optargs<".optargs.">",'~'.expand("")) + let result= system(a:cmd.optargs) + " call Decho("result,'~'.expand("")) + + " strip any ansi escape sequences off + let result = substitute(result,"\e\\[[0-9;]*m","","g") + + " show user the result(s) + echomsg result + let @@= ykeep + + " call Dret("s:NetrwLocalExecute") +endfun + +" --------------------------------------------------------------------- +" s:NetrwLocalRename: rename a local file or directory {{{2 +fun! s:NetrwLocalRename(path) range + + if !exists("w:netrw_bannercnt") + let w:netrw_bannercnt= b:netrw_bannercnt + endif + + " preparation for removing multiple files/directories + let ykeep = @@ + let ctr = a:firstline + let svpos = winsaveview() + let all = 0 + + " rename files given by the markfilelist + if exists("s:netrwmarkfilelist_{bufnr('%')}") + for oldname in s:netrwmarkfilelist_{bufnr("%")} + if exists("subfrom") + let newname= substitute(oldname,subfrom,subto,'') + else + call inputsave() + let newname= input("Moving ".oldname." to : ",oldname,"file") + call inputrestore() + if newname =~ '' + " two ctrl-x's : ignore all of string preceding the ctrl-x's + let newname = substitute(newname,'^.*','','') + elseif newname =~ '' + " one ctrl-x : ignore portion of string preceding ctrl-x but after last / + let newname = substitute(newname,'[^/]*','','') + endif + if newname =~ '^s/' + let subfrom = substitute(newname,'^s/\([^/]*\)/.*/$','\1','') + let subto = substitute(newname,'^s/[^/]*/\(.*\)/$','\1','') + let newname = substitute(oldname,subfrom,subto,'') + endif + endif + if !all && filereadable(newname) + call inputsave() + let response= input("File<".newname."> already exists; do you want to overwrite it? (y/all/n) ") + call inputrestore() + if response == "all" + let all= 1 + elseif response != "y" && response != "yes" + " refresh the directory + NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) + NetrwKeepj call winrestview(svpos) + let @@= ykeep + return + endif + endif + call rename(oldname,newname) + endfor + call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) + + else + + " attempt to rename files/directories + while ctr <= a:lastline + exe "NetrwKeepj ".ctr + + " sanity checks + if line(".") < w:netrw_bannercnt + let ctr= ctr + 1 + continue + endif + let curword= s:NetrwGetWord() + if curword == "./" || curword == "../" + let ctr= ctr + 1 + continue + endif + + NetrwKeepj norm! 0 + let oldname= s:ComposePath(a:path,curword) + + call inputsave() + let newname= input("Moving ".oldname." to : ",substitute(oldname,'/*$','','e')) + call inputrestore() + + call rename(oldname,newname) + let ctr= ctr + 1 + endwhile + endif + + " refresh the directory + NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) + NetrwKeepj call winrestview(svpos) + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwLocalRm: {{{2 +fun! s:NetrwLocalRm(path) range + if !exists("w:netrw_bannercnt") + let w:netrw_bannercnt= b:netrw_bannercnt + endif + + " preparation for removing multiple files/directories + let ykeep = @@ + let ret = 0 + let all = 0 + let svpos = winsaveview() + + if exists("s:netrwmarkfilelist_{bufnr('%')}") + " remove all marked files + for fname in s:netrwmarkfilelist_{bufnr("%")} + let ok= s:NetrwLocalRmFile(a:path,fname,all) + if ok =~# 'q\%[uit]' || ok == "no" + break + elseif ok =~# '^a\%[ll]$' + let all= 1 + endif + endfor + call s:NetrwUnMarkFile(1) + + else + " remove (multiple) files and directories + + let keepsol= &l:sol + setl nosol + let ctr = a:firstline + while ctr <= a:lastline + exe "NetrwKeepj ".ctr + + " sanity checks + if line(".") < w:netrw_bannercnt + let ctr= ctr + 1 + continue + endif + let curword= s:NetrwGetWord() + if curword == "./" || curword == "../" + let ctr= ctr + 1 + continue + endif + let ok= s:NetrwLocalRmFile(a:path,curword,all) + if ok =~# 'q\%[uit]' || ok == "no" + break + elseif ok =~# '^a\%[ll]$' + let all= 1 + endif + let ctr= ctr + 1 + endwhile + let &l:sol= keepsol + endif + + " refresh the directory + if bufname("%") != "NetrwMessage" + NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./',0)) + NetrwKeepj call winrestview(svpos) + endif + let @@= ykeep +endfun + +" --------------------------------------------------------------------- +" s:NetrwLocalRmFile: remove file fname given the path {{{2 +" Give confirmation prompt unless all==1 +fun! s:NetrwLocalRmFile(path,fname,all) + " call Dfunc("s:NetrwLocalRmFile(path<".a:path."> fname<".a:fname."> all=".a:all) + + let all= a:all + let ok = "" + NetrwKeepj norm! 0 + let rmfile= s:NetrwFile(s:ComposePath(a:path,escape(a:fname, '\\'))) + " call Decho("rmfile<".rmfile.">",'~'.expand("")) + + if rmfile !~ '^"' && (rmfile =~ '@$' || rmfile !~ '[\/]$') + " attempt to remove file + " call Decho("attempt to remove file<".rmfile.">",'~'.expand("")) + if !all + echohl Statement + call inputsave() + let ok= input("Confirm deletion of file <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") + call inputrestore() + echohl NONE + if ok == "" + let ok="no" + endif + " call Decho("response: ok<".ok.">",'~'.expand("")) + let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') + " call Decho("response: ok<".ok."> (after sub)",'~'.expand("")) + if ok =~# '^a\%[ll]$' + let all= 1 + endif + endif + + if all || ok =~# '^y\%[es]$' || ok == "" + let ret= s:NetrwDelete(rmfile) + " call Decho("errcode=".v:shell_error." ret=".ret,'~'.expand("")) + endif + + else + " attempt to remove directory + if !all + echohl Statement + call inputsave() + let ok= input("Confirm *recursive* deletion of directory <".rmfile."> ","[{y(es)},n(o),a(ll),q(uit)] ") + call inputrestore() + let ok= substitute(ok,'\[{y(es)},n(o),a(ll),q(uit)]\s*','','e') + if ok == "" + let ok="no" + endif + if ok =~# '^a\%[ll]$' + let all= 1 + endif + endif + let rmfile= substitute(rmfile,'[\/]$','','e') + + if all || ok =~# '^y\%[es]$' || ok == "" + if delete(rmfile,"rf") + call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103) + endif + endif + endif + + " call Dret("s:NetrwLocalRmFile ".ok) + return ok +endfun + +" ===================================================================== +" Support Functions: {{{1 + +" --------------------------------------------------------------------- +" netrw#Access: intended to provide access to variable values for netrw's test suite {{{2 +" 0: marked file list of current buffer +" 1: marked file target +fun! netrw#Access(ilist) + if a:ilist == 0 + if exists("s:netrwmarkfilelist_".bufnr('%')) + return s:netrwmarkfilelist_{bufnr('%')} + else + return "no-list-buf#".bufnr('%') + endif + elseif a:ilist == 1 + return s:netrwmftgt + endif +endfun + +" --------------------------------------------------------------------- +" netrw#Call: allows user-specified mappings to call internal netrw functions {{{2 +fun! netrw#Call(funcname,...) + return call("s:".a:funcname,a:000) +endfun + +" --------------------------------------------------------------------- +" netrw#Expose: allows UserMaps and pchk to look at otherwise script-local variables {{{2 +" I expect this function to be used in +" :PChkAssert netrw#Expose("netrwmarkfilelist") +" for example. +fun! netrw#Expose(varname) + " call Dfunc("netrw#Expose(varname<".a:varname.">)") + if exists("s:".a:varname) + exe "let retval= s:".a:varname + " call Decho("retval=".retval,'~'.expand("")) + if exists("g:netrw_pchk") + " call Decho("type(g:netrw_pchk=".g:netrw_pchk.")=".type(retval),'~'.expand("")) + if type(retval) == 3 + let retval = copy(retval) + let i = 0 + while i < len(retval) + let retval[i]= substitute(retval[i],expand("$HOME"),'~','') + let i = i + 1 + endwhile + endif + " call Dret("netrw#Expose ".string(retval)),'~'.expand("")) + return string(retval) + else + " call Decho("g:netrw_pchk doesn't exist",'~'.expand("")) + endif + else + " call Decho("s:".a:varname." doesn't exist",'~'.expand("")) + let retval= "n/a" + endif + + " call Dret("netrw#Expose ".string(retval)) + return retval +endfun + +" --------------------------------------------------------------------- +" netrw#Modify: allows UserMaps to set (modify) script-local variables {{{2 +fun! netrw#Modify(varname,newvalue) + " call Dfunc("netrw#Modify(varname<".a:varname.">,newvalue<".string(a:newvalue).">)") + exe "let s:".a:varname."= ".string(a:newvalue) + " call Dret("netrw#Modify") +endfun + +" --------------------------------------------------------------------- +" netrw#RFC2396: converts %xx into characters {{{2 +fun! netrw#RFC2396(fname) + " call Dfunc("netrw#RFC2396(fname<".a:fname.">)") + let fname = escape(substitute(a:fname,'%\(\x\x\)','\=printf("%c","0x".submatch(1))','ge')," \t") + " call Dret("netrw#RFC2396 ".fname) + return fname +endfun + +" --------------------------------------------------------------------- +" netrw#UserMaps: supports user-specified maps {{{2 +" see :help function() +" +" g:Netrw_UserMaps is a List with members such as: +" [[keymap sequence, function reference],...] +" +" The referenced function may return a string, +" refresh : refresh the display +" -other- : this string will be executed +" or it may return a List of strings. +" +" Each keymap-sequence will be set up with a nnoremap +" to invoke netrw#UserMaps(a:islocal). +" Related functions: +" netrw#Expose(varname) -- see s:varname variables +" netrw#Modify(varname,newvalue) -- modify value of s:varname variable +" netrw#Call(funcname,...) -- call internal netrw function with optional arguments +fun! netrw#UserMaps(islocal) + " call Dfunc("netrw#UserMaps(islocal=".a:islocal.")") + " call Decho("g:Netrw_UserMaps ".(exists("g:Netrw_UserMaps")? "exists" : "does NOT exist"),'~'.expand("")) + + " set up usermaplist + if exists("g:Netrw_UserMaps") && type(g:Netrw_UserMaps) == 3 + " call Decho("g:Netrw_UserMaps has type 3",'~'.expand("")) + for umap in g:Netrw_UserMaps + " call Decho("type(umap[0]<".string(umap[0]).">)=".type(umap[0])." (should be 1=string)",'~'.expand("")) + " call Decho("type(umap[1])=".type(umap[1])." (should be 1=string)",'~'.expand("")) + " if umap[0] is a string and umap[1] is a string holding a function name + if type(umap[0]) == 1 && type(umap[1]) == 1 + " call Decho("nno ".umap[0]." :call s:UserMaps(".a:islocal.",".string(umap[1]).")",'~'.expand("")) + exe "nno ".umap[0]." :call UserMaps(".a:islocal.",'".umap[1]."')" + else + call netrw#ErrorMsg(s:WARNING,"ignoring usermap <".string(umap[0])."> -- not a [string,funcref] entry",99) + endif + endfor + endif + " call Dret("netrw#UserMaps") +endfun + +" --------------------------------------------------------------------- +" netrw#WinPath: tries to insure that the path is windows-acceptable, whether cygwin is used or not {{{2 +fun! netrw#WinPath(path) + " call Dfunc("netrw#WinPath(path<".a:path.">)") + if (!g:netrw_cygwin || &shell !~ '\%(\\|\\)\%(\.exe\)\=$') && has("win32") + " remove cygdrive prefix, if present + let path = substitute(a:path,g:netrw_cygdrive.'/\(.\)','\1:','') + " remove trailing slash (Win95) + let path = substitute(path, '\(\\\|/\)$', '', 'g') + " remove escaped spaces + let path = substitute(path, '\ ', ' ', 'g') + " convert slashes to backslashes + let path = substitute(path, '/', '\', 'g') + else + let path= a:path + endif + " call Dret("netrw#WinPath <".path.">") + return path +endfun + +" --------------------------------------------------------------------- +" s:StripTrailingSlash: removes trailing slashes from a path {{{2 +fun! s:StripTrailingSlash(path) + " remove trailing slash + return substitute(a:path, '[/\\]$', '', 'g') +endfun + +" --------------------------------------------------------------------- +" s:NetrwBadd: adds marked files to buffer list or vice versa {{{2 +" cb : bl2mf=0 add marked files to buffer list +" cB : bl2mf=1 use bufferlist to mark files +" (mnemonic: cb = copy (marked files) to buffer list) +fun! s:NetrwBadd(islocal,bl2mf) + " " call Dfunc("s:NetrwBadd(islocal=".a:islocal." mf2bl=".mf2bl.")") + if a:bl2mf + " cB: add buffer list to marked files + redir => bufl + ls + redir END + let bufl = map(split(bufl,"\n"),'substitute(v:val,''^.\{-}"\(.*\)".\{-}$'',''\1'','''')') + for fname in bufl + call s:NetrwMarkFile(a:islocal,fname) + endfor + else + " cb: add marked files to buffer list + for fname in s:netrwmarkfilelist_{bufnr("%")} + " " call Decho("badd ".fname,'~'.expand("")) + exe "badd ".fnameescape(fname) + endfor + let curbufnr = bufnr("%") + let curdir = s:NetrwGetCurdir(a:islocal) + call s:NetrwUnmarkList(curbufnr,curdir) " remove markings from local buffer + endif + " call Dret("s:NetrwBadd") +endfun + +" --------------------------------------------------------------------- +" s:ComposePath: Appends a new part to a path taking different systems into consideration {{{2 +fun! s:ComposePath(base,subdir) + " call Dfunc("s:ComposePath(base<".a:base."> subdir<".a:subdir.">)") + + if has("amiga") + " call Decho("amiga",'~'.expand("")) + let ec = a:base[s:Strlen(a:base)-1] + if ec != '/' && ec != ':' + let ret = a:base."/" . a:subdir + else + let ret = a:base.a:subdir + endif + + " COMBAK: test on windows with changing to root directory: :e C:/ + elseif a:subdir =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32") + " call Decho("windows",'~'.expand("")) + let ret= a:subdir + + elseif a:base =~ '^\a:[/\\]\([^/\\]\|$\)' && has("win32") + " call Decho("windows",'~'.expand("")) + if a:base =~ '[/\\]$' + let ret= a:base.a:subdir + else + let ret= a:base.'/'.a:subdir + endif + + elseif a:base =~ '^\a\{3,}://' + " call Decho("remote linux/macos",'~'.expand("")) + let urlbase = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\1','') + let curpath = substitute(a:base,'^\(\a\+://.\{-}/\)\(.*\)$','\2','') + if a:subdir == '../' + if curpath =~ '[^/]/[^/]\+/$' + let curpath= substitute(curpath,'[^/]\+/$','','') + else + let curpath="" + endif + let ret= urlbase.curpath + else + let ret= urlbase.curpath.a:subdir + endif + " call Decho("urlbase<".urlbase.">",'~'.expand("")) + " call Decho("curpath<".curpath.">",'~'.expand("")) + " call Decho("ret<".ret.">",'~'.expand("")) + + else + " call Decho("local linux/macos",'~'.expand("")) + let ret = substitute(a:base."/".a:subdir,"//","/","g") + if a:base =~ '^//' + " keeping initial '//' for the benefit of network share listing support + let ret= '/'.ret + endif + let ret= simplify(ret) + endif + + " call Dret("s:ComposePath ".ret) + return ret +endfun + +" --------------------------------------------------------------------- +" s:DeleteBookmark: deletes a file/directory from Netrw's bookmark system {{{2 +" Related Functions: s:MakeBookmark() s:NetrwBookHistHandler() s:NetrwBookmark() +fun! s:DeleteBookmark(fname) + " call Dfunc("s:DeleteBookmark(fname<".a:fname.">)") + call s:MergeBookmarks() + + if exists("g:netrw_bookmarklist") + let indx= index(g:netrw_bookmarklist,a:fname) + if indx == -1 + let indx= 0 + while indx < len(g:netrw_bookmarklist) + if g:netrw_bookmarklist[indx] =~ a:fname + call remove(g:netrw_bookmarklist,indx) + let indx= indx - 1 + endif + let indx= indx + 1 + endwhile + else + " remove exact match + call remove(g:netrw_bookmarklist,indx) + endif + endif + + " call Dret("s:DeleteBookmark") +endfun + +" --------------------------------------------------------------------- +" s:FileReadable: o/s independent filereadable {{{2 +fun! s:FileReadable(fname) + " call Dfunc("s:FileReadable(fname<".a:fname.">)") + + if g:netrw_cygwin + let ret= filereadable(s:NetrwFile(substitute(a:fname,g:netrw_cygdrive.'/\(.\)','\1:/',''))) + else + let ret= filereadable(s:NetrwFile(a:fname)) + endif + + " call Dret("s:FileReadable ".ret) + return ret +endfun + +" --------------------------------------------------------------------- +" s:GetTempfile: gets a tempname that'll work for various o/s's {{{2 +" Places correct suffix on end of temporary filename, +" using the suffix provided with fname +fun! s:GetTempfile(fname) + " call Dfunc("s:GetTempfile(fname<".a:fname.">)") + + if !exists("b:netrw_tmpfile") + " get a brand new temporary filename + let tmpfile= tempname() + " call Decho("tmpfile<".tmpfile."> : from tempname()",'~'.expand("")) + + let tmpfile= substitute(tmpfile,'\','/','ge') + " call Decho("tmpfile<".tmpfile."> : chgd any \\ -> /",'~'.expand("")) + + " sanity check -- does the temporary file's directory exist? + if !isdirectory(s:NetrwFile(substitute(tmpfile,'[^/]\+$','','e'))) + " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your <".substitute(tmpfile,'[^/]\+$','','e')."> directory is missing!",2) + " call Dret("s:GetTempfile getcwd<".getcwd().">") + return "" + endif + + " let netrw#NetSource() know about the tmpfile + let s:netrw_tmpfile= tmpfile " used by netrw#NetSource() and netrw#BrowseX() + " call Decho("tmpfile<".tmpfile."> s:netrw_tmpfile<".s:netrw_tmpfile.">",'~'.expand("")) + + " o/s dependencies + if g:netrw_cygwin != 0 + let tmpfile = substitute(tmpfile,'^\(\a\):',g:netrw_cygdrive.'/\1','e') + elseif has("win32") + if !exists("+shellslash") || !&ssl + let tmpfile = substitute(tmpfile,'/','\','g') + endif + else + let tmpfile = tmpfile + endif + let b:netrw_tmpfile= tmpfile + " call Decho("o/s dependent fixed tempname<".tmpfile.">",'~'.expand("")) + else + " re-use temporary filename + let tmpfile= b:netrw_tmpfile + " call Decho("tmpfile<".tmpfile."> re-using",'~'.expand("")) + endif + + " use fname's suffix for the temporary file + if a:fname != "" + if a:fname =~ '\.[^./]\+$' + " call Decho("using fname<".a:fname.">'s suffix",'~'.expand("")) + if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$' + let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') + elseif a:fname =~ '.txz$' + let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') + else + let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e') + endif + " call Decho("suffix<".suffix.">",'~'.expand("")) + let tmpfile= substitute(tmpfile,'\.tmp$','','e') + " call Decho("chgd tmpfile<".tmpfile."> (removed any .tmp suffix)",'~'.expand("")) + let tmpfile .= suffix + " call Decho("chgd tmpfile<".tmpfile."> (added ".suffix." suffix) netrw_fname<".b:netrw_fname.">",'~'.expand("")) + let s:netrw_tmpfile= tmpfile " supports netrw#NetSource() + endif + endif + + " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + " call Dret("s:GetTempfile <".tmpfile.">") + return tmpfile +endfun + +" --------------------------------------------------------------------- +" s:MakeSshCmd: transforms input command using USEPORT HOSTNAME into {{{2 +" a correct command for use with a system() call +fun! s:MakeSshCmd(sshcmd) + " call Dfunc("s:MakeSshCmd(sshcmd<".a:sshcmd.">) user<".s:user."> machine<".s:machine.">") + if s:user == "" + let sshcmd = substitute(a:sshcmd,'\',s:machine,'') + else + let sshcmd = substitute(a:sshcmd,'\',s:user."@".s:machine,'') + endif + if exists("g:netrw_port") && g:netrw_port != "" + let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'') + elseif exists("s:port") && s:port != "" + let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'') + else + let sshcmd= substitute(sshcmd,"USEPORT ",'','') + endif + " call Dret("s:MakeSshCmd <".sshcmd.">") + return sshcmd +endfun + +" --------------------------------------------------------------------- +" s:MakeBookmark: enters a bookmark into Netrw's bookmark system {{{2 +fun! s:MakeBookmark(fname) + " call Dfunc("s:MakeBookmark(fname<".a:fname.">)") + + if !exists("g:netrw_bookmarklist") + let g:netrw_bookmarklist= [] + endif + + if index(g:netrw_bookmarklist,a:fname) == -1 + " curdir not currently in g:netrw_bookmarklist, so include it + if isdirectory(s:NetrwFile(a:fname)) && a:fname !~ '/$' + call add(g:netrw_bookmarklist,a:fname.'/') + elseif a:fname !~ '/' + call add(g:netrw_bookmarklist,getcwd()."/".a:fname) + else + call add(g:netrw_bookmarklist,a:fname) + endif + call sort(g:netrw_bookmarklist) + endif + + " call Dret("s:MakeBookmark") +endfun + +" --------------------------------------------------------------------- +" s:MergeBookmarks: merge current bookmarks with saved bookmarks {{{2 +fun! s:MergeBookmarks() + " call Dfunc("s:MergeBookmarks() : merge current bookmarks into .netrwbook") + " get bookmarks from .netrwbook file + let savefile= s:NetrwHome()."/.netrwbook" + if filereadable(s:NetrwFile(savefile)) + " call Decho("merge bookmarks (active and file)",'~'.expand("")) + NetrwKeepj call s:NetrwBookHistSave() + " call Decho("bookmark delete savefile<".savefile.">",'~'.expand("")) + NetrwKeepj call delete(savefile) + endif + " call Dret("s:MergeBookmarks") +endfun + +" --------------------------------------------------------------------- +" s:NetrwBMShow: {{{2 +fun! s:NetrwBMShow() + " call Dfunc("s:NetrwBMShow()") + redir => bmshowraw + menu + redir END + let bmshowlist = split(bmshowraw,'\n') + if bmshowlist != [] + let bmshowfuncs= filter(bmshowlist,'v:val =~# "\\d\\+_BMShow()"') + if bmshowfuncs != [] + let bmshowfunc = substitute(bmshowfuncs[0],'^.*:\(call.*BMShow()\).*$','\1','') + if bmshowfunc =~# '^call.*BMShow()' + exe "sil! NetrwKeepj ".bmshowfunc + endif + endif + endif + " call Dret("s:NetrwBMShow : bmshowfunc<".(exists("bmshowfunc")? bmshowfunc : 'n/a').">") +endfun + +" --------------------------------------------------------------------- +" s:NetrwCursor: responsible for setting cursorline/cursorcolumn based upon g:netrw_cursor {{{2 +fun! s:NetrwCursor(editfile) + if !exists("w:netrw_liststyle") + let w:netrw_liststyle= g:netrw_liststyle + endif + " call Dfunc("s:NetrwCursor() ft<".&ft."> liststyle=".w:netrw_liststyle." g:netrw_cursor=".g:netrw_cursor." s:netrw_usercuc=".s:netrw_usercuc." s:netrw_usercul=".s:netrw_usercul) + + " call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul) + + if &ft != "netrw" + " if the current window isn't a netrw directory listing window, then use user cursorline/column + " settings. Affects when netrw is used to read/write a file using scp/ftp/etc. + " call Decho("case ft!=netrw: use user cul,cuc",'~'.expand("")) + + elseif g:netrw_cursor == 8 + if w:netrw_liststyle == s:WIDELIST + setl cursorline + setl cursorcolumn + else + setl cursorline + endif + elseif g:netrw_cursor == 7 + setl cursorline + elseif g:netrw_cursor == 6 + if w:netrw_liststyle == s:WIDELIST + setl cursorline + endif + elseif g:netrw_cursor == 4 + " all styles: cursorline, cursorcolumn + " call Decho("case g:netrw_cursor==4: setl cul cuc",'~'.expand("")) + setl cursorline + setl cursorcolumn + + elseif g:netrw_cursor == 3 + " thin-long-tree: cursorline, user's cursorcolumn + " wide : cursorline, cursorcolumn + if w:netrw_liststyle == s:WIDELIST + " call Decho("case g:netrw_cursor==3 and wide: setl cul cuc",'~'.expand("")) + setl cursorline + setl cursorcolumn + else + " call Decho("case g:netrw_cursor==3 and not wide: setl cul (use user's cuc)",'~'.expand("")) + setl cursorline + endif + + elseif g:netrw_cursor == 2 + " thin-long-tree: cursorline, user's cursorcolumn + " wide : cursorline, user's cursorcolumn + " call Decho("case g:netrw_cursor==2: setl cuc (use user's cul)",'~'.expand("")) + setl cursorline + + elseif g:netrw_cursor == 1 + " thin-long-tree: user's cursorline, user's cursorcolumn + " wide : cursorline, user's cursorcolumn + if w:netrw_liststyle == s:WIDELIST + " call Decho("case g:netrw_cursor==2 and wide: setl cul (use user's cuc)",'~'.expand("")) + setl cursorline + else + " call Decho("case g:netrw_cursor==2 and not wide: (use user's cul,cuc)",'~'.expand("")) + endif + + else + " all styles: user's cursorline, user's cursorcolumn + " call Decho("default: (use user's cul,cuc)",'~'.expand("")) + let &l:cursorline = s:netrw_usercul + let &l:cursorcolumn = s:netrw_usercuc + endif + + " call Decho("(s:NetrwCursor) COMBAK: cuc=".&l:cuc." cul=".&l:cul) + " call Dret("s:NetrwCursor : l:cursorline=".&l:cursorline." l:cursorcolumn=".&l:cursorcolumn) +endfun + +" --------------------------------------------------------------------- +" s:RestoreCursorline: restores cursorline/cursorcolumn to original user settings {{{2 +fun! s:RestoreCursorline() + " call Dfunc("s:RestoreCursorline() currently, cul=".&l:cursorline." cuc=".&l:cursorcolumn." win#".winnr()." buf#".bufnr("%")) + if exists("s:netrw_usercul") + let &l:cursorline = s:netrw_usercul + endif + if exists("s:netrw_usercuc") + let &l:cursorcolumn = s:netrw_usercuc + endif + " call Decho("(s:RestoreCursorline) COMBAK: cuc=".&l:cuc." cul=".&l:cul) + " call Dret("s:RestoreCursorline : restored cul=".&l:cursorline." cuc=".&l:cursorcolumn) +endfun + +" s:RestoreRegister: restores all registers given in the dict {{{2 +fun! s:RestoreRegister(dict) + for [key, val] in items(a:dict) + if key == 'unnamed' + let key = '' + endif + call setreg(key, val[0], val[1]) + endfor +endfun + +" --------------------------------------------------------------------- +" s:NetrwDelete: Deletes a file. {{{2 +" Uses Steve Hall's idea to insure that Windows paths stay +" acceptable. No effect on Unix paths. +" Examples of use: let result= s:NetrwDelete(path) +fun! s:NetrwDelete(path) + " call Dfunc("s:NetrwDelete(path<".a:path.">)") + + let path = netrw#WinPath(a:path) + if !g:netrw_cygwin && has("win32") + if exists("+shellslash") + let sskeep= &shellslash + setl noshellslash + let result = delete(path) + let &shellslash = sskeep + else + " call Decho("exe let result= ".a:cmd."('".path."')",'~'.expand("")) + let result= delete(path) + endif + else + " call Decho("let result= delete(".path.")",'~'.expand("")) + let result= delete(path) + endif + if result < 0 + NetrwKeepj call netrw#ErrorMsg(s:WARNING,"delete(".path.") failed!",71) + endif + + " call Dret("s:NetrwDelete ".result) + return result +endfun + +" --------------------------------------------------------------------- +" s:NetrwBufRemover: removes a buffer that: {{{2s +" has buffer-id > 1 +" is unlisted +" is unnamed +" does not appear in any window +fun! s:NetrwBufRemover(bufid) + " call Dfunc("s:NetrwBufRemover(".a:bufid.")") + " call Decho("buf#".a:bufid." ".((a:bufid > 1)? ">" : "≯")." must be >1 for removal","~".expand("")) + " call Decho("buf#".a:bufid." is ".(buflisted(a:bufid)? "listed" : "unlisted"),"~".expand("")) + " call Decho("buf#".a:bufid." has name <".bufname(a:bufid).">","~".expand("")) + " call Decho("buf#".a:bufid." has winid#".bufwinid(a:bufid),"~".expand("")) + + 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("")) + exe "sil! bd! ".a:bufid + endif + + " call Dret("s:NetrwBufRemover") +endfun + +" --------------------------------------------------------------------- +" s:NetrwEnew: opens a new buffer, passes netrw buffer variables through {{{2 +fun! s:NetrwEnew(...) + " call Dfunc("s:NetrwEnew() a:0=".a:0." win#".winnr()." winnr($)=".winnr("$")." bufnr($)=".bufnr("$")." expand(%)<".expand("%").">") + " call Decho("curdir<".((a:0>0)? a:1 : "")."> buf#".bufnr("%")."<".bufname("%").">",'~'.expand("")) + + " Clean out the last buffer: + " Check if the last buffer has # > 1, is unlisted, is unnamed, and does not appear in a window + " If so, delete it. + call s:NetrwBufRemover(bufnr("$")) + + " grab a function-local-variable copy of buffer variables + " call Decho("make function-local copy of netrw variables",'~'.expand("")) + if exists("b:netrw_bannercnt") |let netrw_bannercnt = b:netrw_bannercnt |endif + if exists("b:netrw_browser_active") |let netrw_browser_active = b:netrw_browser_active |endif + if exists("b:netrw_cpf") |let netrw_cpf = b:netrw_cpf |endif + if exists("b:netrw_curdir") |let netrw_curdir = b:netrw_curdir |endif + if exists("b:netrw_explore_bufnr") |let netrw_explore_bufnr = b:netrw_explore_bufnr |endif + if exists("b:netrw_explore_indx") |let netrw_explore_indx = b:netrw_explore_indx |endif + if exists("b:netrw_explore_line") |let netrw_explore_line = b:netrw_explore_line |endif + if exists("b:netrw_explore_list") |let netrw_explore_list = b:netrw_explore_list |endif + if exists("b:netrw_explore_listlen")|let netrw_explore_listlen = b:netrw_explore_listlen|endif + if exists("b:netrw_explore_mtchcnt")|let netrw_explore_mtchcnt = b:netrw_explore_mtchcnt|endif + if exists("b:netrw_fname") |let netrw_fname = b:netrw_fname |endif + if exists("b:netrw_lastfile") |let netrw_lastfile = b:netrw_lastfile |endif + if exists("b:netrw_liststyle") |let netrw_liststyle = b:netrw_liststyle |endif + if exists("b:netrw_method") |let netrw_method = b:netrw_method |endif + if exists("b:netrw_option") |let netrw_option = b:netrw_option |endif + if exists("b:netrw_prvdir") |let netrw_prvdir = b:netrw_prvdir |endif + + NetrwKeepj call s:NetrwOptionsRestore("w:") + " call Decho("generate a buffer with NetrwKeepj enew!",'~'.expand("")) + " when tree listing uses file TreeListing... a new buffer is made. + " Want the old buffer to be unlisted. + " COMBAK: this causes a problem, see P43 + " setl nobl + let netrw_keepdiff= &l:diff + call s:NetrwEditFile("enew!","","") + let &l:diff= netrw_keepdiff + " call Decho("bufnr($)=".bufnr("$")."<".bufname(bufnr("$"))."> winnr($)=".winnr("$"),'~'.expand("")) + NetrwKeepj call s:NetrwOptionsSave("w:") + + " copy function-local-variables to buffer variable equivalents + " call Decho("copy function-local variables back to buffer netrw variables",'~'.expand("")) + if exists("netrw_bannercnt") |let b:netrw_bannercnt = netrw_bannercnt |endif + if exists("netrw_browser_active") |let b:netrw_browser_active = netrw_browser_active |endif + if exists("netrw_cpf") |let b:netrw_cpf = netrw_cpf |endif + if exists("netrw_curdir") |let b:netrw_curdir = netrw_curdir |endif + if exists("netrw_explore_bufnr") |let b:netrw_explore_bufnr = netrw_explore_bufnr |endif + if exists("netrw_explore_indx") |let b:netrw_explore_indx = netrw_explore_indx |endif + if exists("netrw_explore_line") |let b:netrw_explore_line = netrw_explore_line |endif + if exists("netrw_explore_list") |let b:netrw_explore_list = netrw_explore_list |endif + if exists("netrw_explore_listlen")|let b:netrw_explore_listlen = netrw_explore_listlen|endif + if exists("netrw_explore_mtchcnt")|let b:netrw_explore_mtchcnt = netrw_explore_mtchcnt|endif + if exists("netrw_fname") |let b:netrw_fname = netrw_fname |endif + if exists("netrw_lastfile") |let b:netrw_lastfile = netrw_lastfile |endif + if exists("netrw_liststyle") |let b:netrw_liststyle = netrw_liststyle |endif + if exists("netrw_method") |let b:netrw_method = netrw_method |endif + if exists("netrw_option") |let b:netrw_option = netrw_option |endif + if exists("netrw_prvdir") |let b:netrw_prvdir = netrw_prvdir |endif + + if a:0 > 0 + let b:netrw_curdir= a:1 + if b:netrw_curdir =~ '/$' + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST + setl nobl + file NetrwTreeListing + setl nobl bt=nowrite bh=hide + nno [ :sil call TreeListMove('[') + nno ] :sil call TreeListMove(']') + else + call s:NetrwBufRename(b:netrw_curdir) + endif + endif + endif + if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on") + let &l:bexpr = "netrw#BalloonHelp()" + endif + + " call Dret("s:NetrwEnew : buf#".bufnr("%")."<".bufname("%")."> expand(%)<".expand("%")."> expand(#)<".expand("#")."> bh=".&bh." win#".winnr()." winnr($)#".winnr("$")) +endfun + +" --------------------------------------------------------------------- +" s:NetrwExe: executes a string using "!" {{{2 +fun! s:NetrwExe(cmd) + if has("win32") && exepath(&shell) !~? '\v[\/]?(cmd|pwsh|powershell)(\.exe)?$' && !g:netrw_cygwin + let savedShell=[&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] + set shell& shellcmdflag& shellxquote& shellxescape& + set shellquote& shellpipe& shellredir& shellslash& + try + exe a:cmd + finally + let [&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] = savedShell + endtry + else + exe a:cmd + endif + if v:shell_error + call netrw#ErrorMsg(s:WARNING,"shell signalled an error",106) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwInsureWinVars: insure that a netrw buffer has its w: variables in spite of a wincmd v or s {{{2 +fun! s:NetrwInsureWinVars() + if !exists("w:netrw_liststyle") + " call Dfunc("s:NetrwInsureWinVars() win#".winnr()) + let curbuf = bufnr("%") + let curwin = winnr() + let iwin = 1 + while iwin <= winnr("$") + exe iwin."wincmd w" + if winnr() != curwin && bufnr("%") == curbuf && exists("w:netrw_liststyle") + " looks like ctrl-w_s or ctrl-w_v was used to split a netrw buffer + let winvars= w: + break + endif + let iwin= iwin + 1 + endwhile + exe "keepalt ".curwin."wincmd w" + if exists("winvars") + " call Decho("copying w#".iwin." window variables to w#".curwin,'~'.expand("")) + for k in keys(winvars) + let w:{k}= winvars[k] + endfor + endif + " call Dret("s:NetrwInsureWinVars win#".winnr()) + endif +endfun + +" --------------------------------------------------------------------- +" s:NetrwLcd: handles changing the (local) directory {{{2 +" Returns: 0=success +" -1=failed +fun! s:NetrwLcd(newdir) + " call Dfunc("s:NetrwLcd(newdir<".a:newdir.">)") + " call Decho("changing local directory",'~'.expand("")) + + let err472= 0 + try + exe 'NetrwKeepj sil lcd '.fnameescape(a:newdir) + catch /^Vim\%((\a\+)\)\=:E344/ + " Vim's lcd fails with E344 when attempting to go above the 'root' of a Windows share. + " Therefore, detect if a Windows share is present, and if E344 occurs, just settle at + " 'root' (ie. '\'). The share name may start with either backslashes ('\\Foo') or + " forward slashes ('//Foo'), depending on whether backslashes have been converted to + " forward slashes by earlier code; so check for both. + if has("win32") && !g:netrw_cygwin + if a:newdir =~ '^\\\\\w\+' || a:newdir =~ '^//\w\+' + let dirname = '\' + exe 'NetrwKeepj sil lcd '.fnameescape(dirname) + endif + endif + catch /^Vim\%((\a\+)\)\=:E472/ + let err472= 1 + endtry + + if err472 + call netrw#ErrorMsg(s:ERROR,"unable to change directory to <".a:newdir."> (permissions?)",61) + if exists("w:netrw_prvdir") + let a:newdir= w:netrw_prvdir + else + call s:NetrwOptionsRestore("w:") + " call Decho("setl noma nomod nowrap",'~'.expand("")) + exe "setl ".g:netrw_bufsettings + " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("")) + let a:newdir= dirname + endif + " call Dret("s:NetrwBrowse -1 : reusing buffer#".(exists("bufnum")? bufnum : 'N/A')."<".dirname."> getcwd<".getcwd().">") + return -1 + endif + + " call Decho("getcwd <".getcwd().">") + " call Decho("b:netrw_curdir<".b:netrw_curdir.">") + " call Dret("s:NetrwLcd 0") + return 0 +endfun + +" ------------------------------------------------------------------------ +" s:NetrwSaveWordPosn: used to keep cursor on same word after refresh, {{{2 +" changed sorting, etc. Also see s:NetrwRestoreWordPosn(). +fun! s:NetrwSaveWordPosn() + " call Dfunc("NetrwSaveWordPosn()") + let s:netrw_saveword= '^'.fnameescape(getline('.')).'$' + " call Dret("NetrwSaveWordPosn : saveword<".s:netrw_saveword.">") +endfun + +" --------------------------------------------------------------------- +" s:NetrwHumanReadable: takes a number and makes it "human readable" {{{2 +" 1000 -> 1K, 1000000 -> 1M, 1000000000 -> 1G +fun! s:NetrwHumanReadable(sz) + " call Dfunc("s:NetrwHumanReadable(sz=".a:sz.") type=".type(a:sz)." style=".g:netrw_sizestyle ) + + if g:netrw_sizestyle == 'h' + if a:sz >= 1000000000 + let sz = printf("%.1f",a:sz/1000000000.0)."g" + elseif a:sz >= 10000000 + let sz = printf("%d",a:sz/1000000)."m" + elseif a:sz >= 1000000 + let sz = printf("%.1f",a:sz/1000000.0)."m" + elseif a:sz >= 10000 + let sz = printf("%d",a:sz/1000)."k" + elseif a:sz >= 1000 + let sz = printf("%.1f",a:sz/1000.0)."k" + else + let sz= a:sz + endif + + elseif g:netrw_sizestyle == 'H' + if a:sz >= 1073741824 + let sz = printf("%.1f",a:sz/1073741824.0)."G" + elseif a:sz >= 10485760 + let sz = printf("%d",a:sz/1048576)."M" + elseif a:sz >= 1048576 + let sz = printf("%.1f",a:sz/1048576.0)."M" + elseif a:sz >= 10240 + let sz = printf("%d",a:sz/1024)."K" + elseif a:sz >= 1024 + let sz = printf("%.1f",a:sz/1024.0)."K" + else + let sz= a:sz + endif + + else + let sz= a:sz + endif + + " call Dret("s:NetrwHumanReadable ".sz) + return sz +endfun + +" --------------------------------------------------------------------- +" s:NetrwRestoreWordPosn: used to keep cursor on same word after refresh, {{{2 +" changed sorting, etc. Also see s:NetrwSaveWordPosn(). +fun! s:NetrwRestoreWordPosn() + " call Dfunc("NetrwRestoreWordPosn()") + sil! call search(s:netrw_saveword,'w') + " call Dret("NetrwRestoreWordPosn") +endfun + +" --------------------------------------------------------------------- +" s:RestoreBufVars: {{{2 +fun! s:RestoreBufVars() + " call Dfunc("s:RestoreBufVars()") + + if exists("s:netrw_curdir") |let b:netrw_curdir = s:netrw_curdir |endif + if exists("s:netrw_lastfile") |let b:netrw_lastfile = s:netrw_lastfile |endif + if exists("s:netrw_method") |let b:netrw_method = s:netrw_method |endif + if exists("s:netrw_fname") |let b:netrw_fname = s:netrw_fname |endif + if exists("s:netrw_machine") |let b:netrw_machine = s:netrw_machine |endif + if exists("s:netrw_browser_active")|let b:netrw_browser_active = s:netrw_browser_active|endif + + " call Dret("s:RestoreBufVars") +endfun + +" --------------------------------------------------------------------- +" s:RemotePathAnalysis: {{{2 +fun! s:RemotePathAnalysis(dirname) + " call Dfunc("s:RemotePathAnalysis(a:dirname<".a:dirname.">)") + + " method :// user @ machine :port /path + let dirpat = '^\(\w\{-}\)://\(\(\w\+\)@\)\=\([^/:#]\+\)\%([:#]\(\d\+\)\)\=/\(.*\)$' + let s:method = substitute(a:dirname,dirpat,'\1','') + let s:user = substitute(a:dirname,dirpat,'\3','') + let s:machine = substitute(a:dirname,dirpat,'\4','') + let s:port = substitute(a:dirname,dirpat,'\5','') + let s:path = substitute(a:dirname,dirpat,'\6','') + let s:fname = substitute(s:path,'^.*/\ze.','','') + if s:machine =~ '@' + let dirpat = '^\(.*\)@\(.\{-}\)$' + let s:user = s:user.'@'.substitute(s:machine,dirpat,'\1','') + let s:machine = substitute(s:machine,dirpat,'\2','') + endif + + " call Decho("set up s:method <".s:method .">",'~'.expand("")) + " call Decho("set up s:user <".s:user .">",'~'.expand("")) + " call Decho("set up s:machine<".s:machine.">",'~'.expand("")) + " call Decho("set up s:port <".s:port.">",'~'.expand("")) + " call Decho("set up s:path <".s:path .">",'~'.expand("")) + " call Decho("set up s:fname <".s:fname .">",'~'.expand("")) + + " call Dret("s:RemotePathAnalysis") +endfun + +" --------------------------------------------------------------------- +" s:RemoteSystem: runs a command on a remote host using ssh {{{2 +" Returns status +" Runs system() on +" [cd REMOTEDIRPATH;] a:cmd +" Note that it doesn't do s:ShellEscape(a:cmd)! +fun! s:RemoteSystem(cmd) + " call Dfunc("s:RemoteSystem(cmd<".a:cmd.">)") + if !executable(g:netrw_ssh_cmd) + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"g:netrw_ssh_cmd<".g:netrw_ssh_cmd."> is not executable!",52) + elseif !exists("b:netrw_curdir") + NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53) + else + let cmd = s:MakeSshCmd(g:netrw_ssh_cmd." USEPORT HOSTNAME") + let remotedir= substitute(b:netrw_curdir,'^.*//[^/]\+/\(.*\)$','\1','') + if remotedir != "" + let cmd= cmd.' cd '.s:ShellEscape(remotedir).";" + else + let cmd= cmd.' ' + endif + let cmd= cmd.a:cmd + " call Decho("call system(".cmd.")",'~'.expand("")) + let ret= system(cmd) + endif + " call Dret("s:RemoteSystem ".ret) + return ret +endfun + +" --------------------------------------------------------------------- +" s:RestoreWinVars: (used by Explore() and NetrwSplit()) {{{2 +fun! s:RestoreWinVars() + " call Dfunc("s:RestoreWinVars()") + if exists("s:bannercnt") |let w:netrw_bannercnt = s:bannercnt |unlet s:bannercnt |endif + if exists("s:col") |let w:netrw_col = s:col |unlet s:col |endif + if exists("s:curdir") |let w:netrw_curdir = s:curdir |unlet s:curdir |endif + if exists("s:explore_bufnr") |let w:netrw_explore_bufnr = s:explore_bufnr |unlet s:explore_bufnr |endif + if exists("s:explore_indx") |let w:netrw_explore_indx = s:explore_indx |unlet s:explore_indx |endif + if exists("s:explore_line") |let w:netrw_explore_line = s:explore_line |unlet s:explore_line |endif + if exists("s:explore_listlen")|let w:netrw_explore_listlen = s:explore_listlen|unlet s:explore_listlen|endif + if exists("s:explore_list") |let w:netrw_explore_list = s:explore_list |unlet s:explore_list |endif + if exists("s:explore_mtchcnt")|let w:netrw_explore_mtchcnt = s:explore_mtchcnt|unlet s:explore_mtchcnt|endif + if exists("s:fpl") |let w:netrw_fpl = s:fpl |unlet s:fpl |endif + if exists("s:hline") |let w:netrw_hline = s:hline |unlet s:hline |endif + if exists("s:line") |let w:netrw_line = s:line |unlet s:line |endif + if exists("s:liststyle") |let w:netrw_liststyle = s:liststyle |unlet s:liststyle |endif + if exists("s:method") |let w:netrw_method = s:method |unlet s:method |endif + if exists("s:prvdir") |let w:netrw_prvdir = s:prvdir |unlet s:prvdir |endif + if exists("s:treedict") |let w:netrw_treedict = s:treedict |unlet s:treedict |endif + if exists("s:treetop") |let w:netrw_treetop = s:treetop |unlet s:treetop |endif + if exists("s:winnr") |let w:netrw_winnr = s:winnr |unlet s:winnr |endif + " call Dret("s:RestoreWinVars") +endfun + +" --------------------------------------------------------------------- +" s:Rexplore: implements returning from a buffer to a netrw directory {{{2 +" +" s:SetRexDir() sets up <2-leftmouse> maps (if g:netrw_retmap +" is true) and a command, :Rexplore, which call this function. +" +" s:netrw_posn is set up by s:NetrwBrowseChgDir() +" +" s:rexposn_BUFNR used to save/restore cursor position +fun! s:NetrwRexplore(islocal,dirname) + if exists("s:netrwdrag") + return + endif + " call Dfunc("s:NetrwRexplore() w:netrw_rexlocal=".w:netrw_rexlocal." w:netrw_rexdir<".w:netrw_rexdir."> win#".winnr()) + " call Decho("currently in bufname<".bufname("%").">",'~'.expand("")) + " call Decho("ft=".&ft." win#".winnr()." w:netrw_rexfile<".(exists("w:netrw_rexfile")? w:netrw_rexfile : 'n/a').">",'~'.expand("")) + + if &ft == "netrw" && exists("w:netrw_rexfile") && w:netrw_rexfile != "" + " a :Rex while in a netrw buffer means: edit the file in w:netrw_rexfile + " call Decho("in netrw buffer, will edit file<".w:netrw_rexfile.">",'~'.expand("")) + exe "NetrwKeepj e ".w:netrw_rexfile + unlet w:netrw_rexfile + " call Dret("s:NetrwRexplore returning from netrw to buf#".bufnr("%")."<".bufname("%")."> (ft=".&ft.")") + return + " else " Decho + " call Decho("treating as not-netrw-buffer: ft=".&ft.((&ft == "netrw")? " == netrw" : "!= netrw"),'~'.expand("")) + " call Decho("treating as not-netrw-buffer: w:netrw_rexfile<".((exists("w:netrw_rexfile"))? w:netrw_rexfile : 'n/a').">",'~'.expand("")) + endif + + " --------------------------- + " :Rex issued while in a file + " --------------------------- + + " record current file so :Rex can return to it from netrw + let w:netrw_rexfile= expand("%") + " call Decho("set w:netrw_rexfile<".w:netrw_rexfile."> (win#".winnr().")",'~'.expand("")) + + if !exists("w:netrw_rexlocal") + " call Dret("s:NetrwRexplore w:netrw_rexlocal doesn't exist (".&ft." win#".winnr().")") + return + endif + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + if w:netrw_rexlocal + NetrwKeepj call netrw#LocalBrowseCheck(w:netrw_rexdir) + else + NetrwKeepj call s:NetrwBrowse(0,w:netrw_rexdir) + endif + if exists("s:initbeval") + setl beval + endif + if exists("s:rexposn_".bufnr("%")) + " call Decho("restore posn, then unlet s:rexposn_".bufnr('%')."<".bufname("%").">",'~'.expand("")) + " restore position in directory listing + " call Decho("restoring posn to s:rexposn_".bufnr('%')."<".string(s:rexposn_{bufnr('%')}).">",'~'.expand("")) + NetrwKeepj call winrestview(s:rexposn_{bufnr('%')}) + if exists("s:rexposn_".bufnr('%')) + unlet s:rexposn_{bufnr('%')} + endif + else + " call Decho("s:rexposn_".bufnr('%')."<".bufname("%")."> doesn't exist",'~'.expand("")) + endif + + if has("syntax") && exists("g:syntax_on") && g:syntax_on + if exists("s:explore_match") + exe "2match netrwMarkFile /".s:explore_match."/" + endif + endif + + " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("")) + " call Dret("s:NetrwRexplore : ft=".&ft) +endfun + +" --------------------------------------------------------------------- +" s:SaveBufVars: save selected b: variables to s: variables {{{2 +" use s:RestoreBufVars() to restore b: variables from s: variables +fun! s:SaveBufVars() + " call Dfunc("s:SaveBufVars() buf#".bufnr("%")) + + if exists("b:netrw_curdir") |let s:netrw_curdir = b:netrw_curdir |endif + if exists("b:netrw_lastfile") |let s:netrw_lastfile = b:netrw_lastfile |endif + if exists("b:netrw_method") |let s:netrw_method = b:netrw_method |endif + if exists("b:netrw_fname") |let s:netrw_fname = b:netrw_fname |endif + if exists("b:netrw_machine") |let s:netrw_machine = b:netrw_machine |endif + if exists("b:netrw_browser_active")|let s:netrw_browser_active = b:netrw_browser_active|endif + + " call Dret("s:SaveBufVars") +endfun + +" --------------------------------------------------------------------- +" s:SavePosn: saves position associated with current buffer into a dictionary {{{2 +fun! s:SavePosn(posndict) + " call Dfunc("s:SavePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">") + + if !exists("a:posndict[bufnr('%')]") + let a:posndict[bufnr("%")]= [] + endif + " call Decho("before push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) + call add(a:posndict[bufnr("%")],winsaveview()) + " call Decho("after push: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) + + " call Dret("s:SavePosn posndict") + return a:posndict +endfun + +" --------------------------------------------------------------------- +" s:RestorePosn: restores position associated with current buffer using dictionary {{{2 +fun! s:RestorePosn(posndict) + " call Dfunc("s:RestorePosn(posndict) curbuf#".bufnr("%")."<".bufname("%").">") + if exists("a:posndict") + if has_key(a:posndict,bufnr("%")) + " call Decho("before pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) + let posnlen= len(a:posndict[bufnr("%")]) + if posnlen > 0 + let posnlen= posnlen - 1 + " call Decho("restoring posn posndict[".bufnr("%")."][".posnlen."]=".string(a:posndict[bufnr("%")][posnlen]),'~'.expand("")) + call winrestview(a:posndict[bufnr("%")][posnlen]) + call remove(a:posndict[bufnr("%")],posnlen) + " call Decho("after pop: a:posndict[buf#".bufnr("%")."]=".string(a:posndict[bufnr('%')])) + endif + endif + endif + " call Dret("s:RestorePosn") +endfun + +" --------------------------------------------------------------------- +" s:SaveWinVars: (used by Explore() and NetrwSplit()) {{{2 +fun! s:SaveWinVars() + " call Dfunc("s:SaveWinVars() win#".winnr()) + if exists("w:netrw_bannercnt") |let s:bannercnt = w:netrw_bannercnt |endif + if exists("w:netrw_col") |let s:col = w:netrw_col |endif + if exists("w:netrw_curdir") |let s:curdir = w:netrw_curdir |endif + if exists("w:netrw_explore_bufnr") |let s:explore_bufnr = w:netrw_explore_bufnr |endif + if exists("w:netrw_explore_indx") |let s:explore_indx = w:netrw_explore_indx |endif + if exists("w:netrw_explore_line") |let s:explore_line = w:netrw_explore_line |endif + if exists("w:netrw_explore_listlen")|let s:explore_listlen = w:netrw_explore_listlen|endif + if exists("w:netrw_explore_list") |let s:explore_list = w:netrw_explore_list |endif + if exists("w:netrw_explore_mtchcnt")|let s:explore_mtchcnt = w:netrw_explore_mtchcnt|endif + if exists("w:netrw_fpl") |let s:fpl = w:netrw_fpl |endif + if exists("w:netrw_hline") |let s:hline = w:netrw_hline |endif + if exists("w:netrw_line") |let s:line = w:netrw_line |endif + if exists("w:netrw_liststyle") |let s:liststyle = w:netrw_liststyle |endif + if exists("w:netrw_method") |let s:method = w:netrw_method |endif + if exists("w:netrw_prvdir") |let s:prvdir = w:netrw_prvdir |endif + if exists("w:netrw_treedict") |let s:treedict = w:netrw_treedict |endif + if exists("w:netrw_treetop") |let s:treetop = w:netrw_treetop |endif + if exists("w:netrw_winnr") |let s:winnr = w:netrw_winnr |endif + " call Dret("s:SaveWinVars") +endfun + +" --------------------------------------------------------------------- +" s:SetBufWinVars: (used by NetrwBrowse() and LocalBrowseCheck()) {{{2 +" To allow separate windows to have their own activities, such as +" Explore **/pattern, several variables have been made window-oriented. +" However, when the user splits a browser window (ex: ctrl-w s), these +" variables are not inherited by the new window. SetBufWinVars() and +" UseBufWinVars() get around that. +fun! s:SetBufWinVars() + " call Dfunc("s:SetBufWinVars() win#".winnr()) + if exists("w:netrw_liststyle") |let b:netrw_liststyle = w:netrw_liststyle |endif + if exists("w:netrw_bannercnt") |let b:netrw_bannercnt = w:netrw_bannercnt |endif + if exists("w:netrw_method") |let b:netrw_method = w:netrw_method |endif + if exists("w:netrw_prvdir") |let b:netrw_prvdir = w:netrw_prvdir |endif + if exists("w:netrw_explore_indx") |let b:netrw_explore_indx = w:netrw_explore_indx |endif + if exists("w:netrw_explore_listlen")|let b:netrw_explore_listlen= w:netrw_explore_listlen|endif + if exists("w:netrw_explore_mtchcnt")|let b:netrw_explore_mtchcnt= w:netrw_explore_mtchcnt|endif + if exists("w:netrw_explore_bufnr") |let b:netrw_explore_bufnr = w:netrw_explore_bufnr |endif + if exists("w:netrw_explore_line") |let b:netrw_explore_line = w:netrw_explore_line |endif + if exists("w:netrw_explore_list") |let b:netrw_explore_list = w:netrw_explore_list |endif + " call Dret("s:SetBufWinVars") +endfun + +" --------------------------------------------------------------------- +" s:SetRexDir: set directory for :Rexplore {{{2 +fun! s:SetRexDir(islocal,dirname) + " call Dfunc("s:SetRexDir(islocal=".a:islocal." dirname<".a:dirname.">) win#".winnr()) + let w:netrw_rexdir = a:dirname + let w:netrw_rexlocal = a:islocal + let s:rexposn_{bufnr("%")} = winsaveview() + " call Decho("setting w:netrw_rexdir =".w:netrw_rexdir,'~'.expand("")) + " call Decho("setting w:netrw_rexlocal=".w:netrw_rexlocal,'~'.expand("")) + " call Decho("saving posn to s:rexposn_".bufnr("%")."<".string(s:rexposn_{bufnr("%")}).">",'~'.expand("")) + " call Decho("setting s:rexposn_".bufnr("%")."<".bufname("%")."> to ".string(winsaveview()),'~'.expand("")) + " call Dret("s:SetRexDir : win#".winnr()." ".(a:islocal? "local" : "remote")." dir: ".a:dirname) +endfun + +" --------------------------------------------------------------------- +" s:ShowLink: used to modify thin and tree listings to show links {{{2 +fun! s:ShowLink() + if exists("b:netrw_curdir") + keepp :norm! $?\a + "call histdel("/",-1) + if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treetop") + let basedir = s:NetrwTreePath(w:netrw_treetop) + else + let basedir = b:netrw_curdir.'/' + endif + let fname = basedir.s:NetrwGetWord() + let resname = resolve(fname) + if resname =~ '^\M'.basedir + let dirlen = strlen(basedir) + let resname = strpart(resname,dirlen) + endif + let modline = getline(".")."\t --> ".resname + setl noro ma + call setline(".",modline) + setl ro noma nomod + endif +endfun + +" --------------------------------------------------------------------- +" s:ShowStyle: {{{2 +fun! s:ShowStyle() + if !exists("w:netrw_liststyle") + let liststyle= g:netrw_liststyle + else + let liststyle= w:netrw_liststyle + endif + if liststyle == s:THINLIST + return s:THINLIST.":thin" + elseif liststyle == s:LONGLIST + return s:LONGLIST.":long" + elseif liststyle == s:WIDELIST + return s:WIDELIST.":wide" + elseif liststyle == s:TREELIST + return s:TREELIST.":tree" + else + return 'n/a' + endif +endfun + +" --------------------------------------------------------------------- +" s:Strlen: this function returns the length of a string, even if its using multi-byte characters. {{{2 +" Solution from Nicolai Weibull, vim docs (:help strlen()), +" Tony Mechelynck, and my own invention. +fun! s:Strlen(x) + " "" call Dfunc("s:Strlen(x<".a:x."> g:Align_xstrlen=".g:Align_xstrlen.")") + + if v:version >= 703 && exists("*strdisplaywidth") + let ret= strdisplaywidth(a:x) + + elseif type(g:Align_xstrlen) == 1 + " allow user to specify a function to compute the string length (ie. let g:Align_xstrlen="mystrlenfunc") + exe "let ret= ".g:Align_xstrlen."('".substitute(a:x,"'","''","g")."')" + + elseif g:Align_xstrlen == 1 + " number of codepoints (Latin a + combining circumflex is two codepoints) + " (comment from TM, solution from NW) + let ret= strlen(substitute(a:x,'.','c','g')) + + elseif g:Align_xstrlen == 2 + " number of spacing codepoints (Latin a + combining circumflex is one spacing + " codepoint; a hard tab is one; wide and narrow CJK are one each; etc.) + " (comment from TM, solution from TM) + let ret=strlen(substitute(a:x, '.\Z', 'x', 'g')) + + elseif g:Align_xstrlen == 3 + " virtual length (counting, for instance, tabs as anything between 1 and + " 'tabstop', wide CJK as 2 rather than 1, Arabic alif as zero when immediately + " preceded by lam, one otherwise, etc.) + " (comment from TM, solution from me) + let modkeep= &l:mod + exe "norm! o\" + call setline(line("."),a:x) + let ret= virtcol("$") - 1 + d + NetrwKeepj norm! k + let &l:mod= modkeep + + else + " at least give a decent default + let ret= strlen(a:x) + endif + " "" call Dret("s:Strlen ".ret) + return ret +endfun + +" --------------------------------------------------------------------- +" s:ShellEscape: shellescape(), or special windows handling {{{2 +fun! s:ShellEscape(s, ...) + if has('win32') && $SHELL == '' && &shellslash + return printf('"%s"', substitute(a:s, '"', '""', 'g')) + endif + let f = a:0 > 0 ? a:1 : 0 + return shellescape(a:s, f) +endfun + +" --------------------------------------------------------------------- +" s:TreeListMove: supports [[, ]], [], and ][ in tree mode {{{2 +fun! s:TreeListMove(dir) + " call Dfunc("s:TreeListMove(dir<".a:dir.">)") + let curline = getline('.') + let prvline = (line(".") > 1)? getline(line(".")-1) : '' + let nxtline = (line(".") < line("$"))? getline(line(".")+1) : '' + let curindent = substitute(getline('.'),'^\(\%('.s:treedepthstring.'\)*\)[^'.s:treedepthstring.'].\{-}$','\1','e') + let indentm1 = substitute(curindent,'^'.s:treedepthstring,'','') + let treedepthchr = substitute(s:treedepthstring,' ','','g') + let stopline = exists("w:netrw_bannercnt")? w:netrw_bannercnt : 1 + " call Decho("prvline <".prvline."> #".(line(".")-1), '~'.expand("")) + " call Decho("curline <".curline."> #".line(".") , '~'.expand("")) + " call Decho("nxtline <".nxtline."> #".(line(".")+1), '~'.expand("")) + " call Decho("curindent<".curindent.">" , '~'.expand("")) + " call Decho("indentm1 <".indentm1.">" , '~'.expand("")) + " COMBAK : need to handle when on a directory + " COMBAK : need to handle ]] and ][. In general, needs work!!! + if curline !~ '/$' + if a:dir == '[[' && prvline != '' + NetrwKeepj norm! 0 + let nl = search('^'.indentm1.'\%('.s:treedepthstring.'\)\@!','bWe',stopline) " search backwards + " call Decho("regfile srch back: ".nl,'~'.expand("")) + elseif a:dir == '[]' && nxtline != '' + NetrwKeepj norm! 0 + " call Decho('srchpat<'.'^\%('.curindent.'\)\@!'.'>','~'.expand("")) + let nl = search('^\%('.curindent.'\)\@!','We') " search forwards + if nl != 0 + NetrwKeepj norm! k + else + NetrwKeepj norm! G + endif + " call Decho("regfile srch fwd: ".nl,'~'.expand("")) + endif + endif + + " call Dret("s:TreeListMove") +endfun + +" --------------------------------------------------------------------- +" s:UpdateBuffersMenu: does emenu Buffers.Refresh (but due to locale, the menu item may not be called that) {{{2 +" The Buffers.Refresh menu calls s:BMShow(); unfortunately, that means that that function +" can't be called except via emenu. But due to locale, that menu line may not be called +" Buffers.Refresh; hence, s:NetrwBMShow() utilizes a "cheat" to call that function anyway. +fun! s:UpdateBuffersMenu() + " call Dfunc("s:UpdateBuffersMenu()") + if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu + try + sil emenu Buffers.Refresh\ menu + catch /^Vim\%((\a\+)\)\=:E/ + let v:errmsg= "" + sil NetrwKeepj call s:NetrwBMShow() + endtry + endif + " call Dret("s:UpdateBuffersMenu") +endfun + +" --------------------------------------------------------------------- +" s:UseBufWinVars: (used by NetrwBrowse() and LocalBrowseCheck() {{{2 +" Matching function to s:SetBufWinVars() +fun! s:UseBufWinVars() + " call Dfunc("s:UseBufWinVars()") + if exists("b:netrw_liststyle") && !exists("w:netrw_liststyle") |let w:netrw_liststyle = b:netrw_liststyle |endif + if exists("b:netrw_bannercnt") && !exists("w:netrw_bannercnt") |let w:netrw_bannercnt = b:netrw_bannercnt |endif + if exists("b:netrw_method") && !exists("w:netrw_method") |let w:netrw_method = b:netrw_method |endif + if exists("b:netrw_prvdir") && !exists("w:netrw_prvdir") |let w:netrw_prvdir = b:netrw_prvdir |endif + if exists("b:netrw_explore_indx") && !exists("w:netrw_explore_indx") |let w:netrw_explore_indx = b:netrw_explore_indx |endif + if exists("b:netrw_explore_listlen") && !exists("w:netrw_explore_listlen")|let w:netrw_explore_listlen = b:netrw_explore_listlen|endif + if exists("b:netrw_explore_mtchcnt") && !exists("w:netrw_explore_mtchcnt")|let w:netrw_explore_mtchcnt = b:netrw_explore_mtchcnt|endif + if exists("b:netrw_explore_bufnr") && !exists("w:netrw_explore_bufnr") |let w:netrw_explore_bufnr = b:netrw_explore_bufnr |endif + if exists("b:netrw_explore_line") && !exists("w:netrw_explore_line") |let w:netrw_explore_line = b:netrw_explore_line |endif + if exists("b:netrw_explore_list") && !exists("w:netrw_explore_list") |let w:netrw_explore_list = b:netrw_explore_list |endif + " call Dret("s:UseBufWinVars") +endfun + +" --------------------------------------------------------------------- +" s:UserMaps: supports user-defined UserMaps {{{2 +" * calls a user-supplied funcref(islocal,curdir) +" * interprets result +" See netrw#UserMaps() +fun! s:UserMaps(islocal,funcname) + if !exists("b:netrw_curdir") + let b:netrw_curdir= getcwd() + endif + let Funcref = function(a:funcname) + let result = Funcref(a:islocal) + + if type(result) == 1 + " if result from user's funcref is a string... + if result == "refresh" + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + elseif result != "" + exe result + endif + + elseif type(result) == 3 + " if result from user's funcref is a List... + for action in result + if action == "refresh" + call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./',0)) + elseif action != "" + exe action + endif + endfor + endif +endfun + +" ========================== +" Settings Restoration: {{{1 +" ========================== +let &cpo= s:keepcpo +unlet s:keepcpo + +" =============== +" Modelines: {{{1 +" =============== +" vim:ts=8 sts=2 sw=2 et fdm=marker diff --git a/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim b/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim new file mode 100644 index 0000000000..fd43c16c4e --- /dev/null +++ b/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim @@ -0,0 +1,242 @@ +" Maintainer: Luca Saccarola +" Former Maintainer: Charles E Campbell +" Upstream: +" Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{1 +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" netrwSettings.vim is provided *as is* and comes with no +" warranty of any kind, either expressed or implied. By using +" this plugin, you agree that in no event will the copyright +" holder be liable for any damages resulting from the use +" of this software. + +" Load Once: {{{1 +if exists("g:loaded_netrwSettings") || &cp + finish +endif +let g:loaded_netrwSettings = "v174" +if v:version < 700 + echohl WarningMsg + echo "***warning*** this version of netrwSettings needs vim 7.0" + echohl Normal + finish +endif + +" --------------------------------------------------------------------- +" NetrwSettings: {{{1 +fun! netrwSettings#NetrwSettings() + " this call is here largely just to insure that netrw has been loaded + call netrw#WinPath("") + if !exists("g:loaded_netrw") + echohl WarningMsg | echomsg "***sorry*** netrw needs to be loaded prior to using NetrwSettings" | echohl None + return + endif + + above wincmd s + enew + setlocal noswapfile bh=wipe + set ft=vim + file Netrw\ Settings + + " these variables have the following default effects when they don't + " exist (ie. have not been set by the user in his/her .vimrc) + if !exists("g:netrw_liststyle") + let g:netrw_liststyle= 0 + let g:netrw_list_cmd= "ssh HOSTNAME ls -FLa" + endif + if !exists("g:netrw_silent") + let g:netrw_silent= 0 + endif + if !exists("g:netrw_use_nt_rcp") + let g:netrw_use_nt_rcp= 0 + endif + if !exists("g:netrw_ftp") + let g:netrw_ftp= 0 + endif + if !exists("g:netrw_ignorenetrc") + let g:netrw_ignorenetrc= 0 + endif + + put ='+ ---------------------------------------------' + put ='+ NetrwSettings: by Charles E. Campbell' + put ='+ Press with cursor atop any line for help' + put ='+ ---------------------------------------------' + let s:netrw_settings_stop= line(".") + + put ='' + put ='+ Netrw Protocol Commands' + put = 'let g:netrw_dav_cmd = '.g:netrw_dav_cmd + put = 'let g:netrw_fetch_cmd = '.g:netrw_fetch_cmd + put = 'let g:netrw_ftp_cmd = '.g:netrw_ftp_cmd + put = 'let g:netrw_http_cmd = '.g:netrw_http_cmd + put = 'let g:netrw_rcp_cmd = '.g:netrw_rcp_cmd + put = 'let g:netrw_rsync_cmd = '.g:netrw_rsync_cmd + put = 'let g:netrw_scp_cmd = '.g:netrw_scp_cmd + put = 'let g:netrw_sftp_cmd = '.g:netrw_sftp_cmd + put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd + let s:netrw_protocol_stop= line(".") + put = '' + + put ='+Netrw Transfer Control' + put = 'let g:netrw_cygwin = '.g:netrw_cygwin + put = 'let g:netrw_ftp = '.g:netrw_ftp + put = 'let g:netrw_ftpmode = '.g:netrw_ftpmode + put = 'let g:netrw_ignorenetrc = '.g:netrw_ignorenetrc + put = 'let g:netrw_sshport = '.g:netrw_sshport + put = 'let g:netrw_silent = '.g:netrw_silent + put = 'let g:netrw_use_nt_rcp = '.g:netrw_use_nt_rcp + let s:netrw_xfer_stop= line(".") + put ='' + put ='+ Netrw Messages' + put ='let g:netrw_use_errorwindow = '.g:netrw_use_errorwindow + + put = '' + put ='+ Netrw Browser Control' + if exists("g:netrw_altfile") + put = 'let g:netrw_altfile = '.g:netrw_altfile + else + put = 'let g:netrw_altfile = 0' + endif + put = 'let g:netrw_alto = '.g:netrw_alto + put = 'let g:netrw_altv = '.g:netrw_altv + put = 'let g:netrw_banner = '.g:netrw_banner + if exists("g:netrw_bannerbackslash") + put = 'let g:netrw_bannerbackslash = '.g:netrw_bannerbackslash + else + put = '\" let g:netrw_bannerbackslash = (not defined)' + endif + put = 'let g:netrw_browse_split = '.g:netrw_browse_split + if exists("g:netrw_browsex_viewer") + put = 'let g:netrw_browsex_viewer = '.g:netrw_browsex_viewer + else + put = '\" let g:netrw_browsex_viewer = (not defined)' + endif + put = 'let g:netrw_compress = '.g:netrw_compress + if exists("g:Netrw_corehandler") + put = 'let g:Netrw_corehandler = '.g:Netrw_corehandler + else + put = '\" let g:Netrw_corehandler = (not defined)' + endif + put = 'let g:netrw_ctags = '.g:netrw_ctags + put = 'let g:netrw_cursor = '.g:netrw_cursor + let decompressline= line("$") + put = 'let g:netrw_decompress = '.string(g:netrw_decompress) + if exists("g:netrw_dynamic_maxfilenamelen") + put = 'let g:netrw_dynamic_maxfilenamelen='.g:netrw_dynamic_maxfilenamelen + else + put = '\" let g:netrw_dynamic_maxfilenamelen= (not defined)' + endif + put = 'let g:netrw_dirhistmax = '.g:netrw_dirhistmax + put = 'let g:netrw_errorlvl = '.g:netrw_errorlvl + put = 'let g:netrw_fastbrowse = '.g:netrw_fastbrowse + let fnameescline= line("$") + put = 'let g:netrw_fname_escape = '.string(g:netrw_fname_escape) + put = 'let g:netrw_ftp_browse_reject = '.g:netrw_ftp_browse_reject + put = 'let g:netrw_ftp_list_cmd = '.g:netrw_ftp_list_cmd + put = 'let g:netrw_ftp_sizelist_cmd = '.g:netrw_ftp_sizelist_cmd + put = 'let g:netrw_ftp_timelist_cmd = '.g:netrw_ftp_timelist_cmd + let globescline= line("$") + put = 'let g:netrw_glob_escape = '.string(g:netrw_glob_escape) + put = 'let g:netrw_hide = '.g:netrw_hide + if exists("g:netrw_home") + put = 'let g:netrw_home = '.g:netrw_home + else + put = '\" let g:netrw_home = (not defined)' + endif + put = 'let g:netrw_keepdir = '.g:netrw_keepdir + put = 'let g:netrw_list_cmd = '.g:netrw_list_cmd + put = 'let g:netrw_list_hide = '.g:netrw_list_hide + put = 'let g:netrw_liststyle = '.g:netrw_liststyle + put = 'let g:netrw_localcopycmd = '.g:netrw_localcopycmd + put = 'let g:netrw_localcopycmdopt = '.g:netrw_localcopycmdopt + put = 'let g:netrw_localmkdir = '.g:netrw_localmkdir + put = 'let g:netrw_localmkdiropt = '.g:netrw_localmkdiropt + put = 'let g:netrw_localmovecmd = '.g:netrw_localmovecmd + put = 'let g:netrw_localmovecmdopt = '.g:netrw_localmovecmdopt + put = 'let g:netrw_maxfilenamelen = '.g:netrw_maxfilenamelen + put = 'let g:netrw_menu = '.g:netrw_menu + put = 'let g:netrw_mousemaps = '.g:netrw_mousemaps + put = 'let g:netrw_mkdir_cmd = '.g:netrw_mkdir_cmd + if exists("g:netrw_nobeval") + put = 'let g:netrw_nobeval = '.g:netrw_nobeval + else + put = '\" let g:netrw_nobeval = (not defined)' + endif + put = 'let g:netrw_remote_mkdir = '.g:netrw_remote_mkdir + put = 'let g:netrw_preview = '.g:netrw_preview + put = 'let g:netrw_rename_cmd = '.g:netrw_rename_cmd + put = 'let g:netrw_retmap = '.g:netrw_retmap + put = 'let g:netrw_rm_cmd = '.g:netrw_rm_cmd + put = 'let g:netrw_rmdir_cmd = '.g:netrw_rmdir_cmd + put = 'let g:netrw_rmf_cmd = '.g:netrw_rmf_cmd + put = 'let g:netrw_sort_by = '.g:netrw_sort_by + put = 'let g:netrw_sort_direction = '.g:netrw_sort_direction + put = 'let g:netrw_sort_options = '.g:netrw_sort_options + put = 'let g:netrw_sort_sequence = '.g:netrw_sort_sequence + put = 'let g:netrw_servername = '.g:netrw_servername + put = 'let g:netrw_special_syntax = '.g:netrw_special_syntax + put = 'let g:netrw_ssh_browse_reject = '.g:netrw_ssh_browse_reject + put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd + put = 'let g:netrw_scpport = '.g:netrw_scpport + put = 'let g:netrw_sepchr = '.g:netrw_sepchr + put = 'let g:netrw_sshport = '.g:netrw_sshport + put = 'let g:netrw_timefmt = '.g:netrw_timefmt + let tmpfileescline= line("$") + put ='let g:netrw_tmpfile_escape...' + put = 'let g:netrw_use_noswf = '.g:netrw_use_noswf + put = 'let g:netrw_xstrlen = '.g:netrw_xstrlen + put = 'let g:netrw_winsize = '.g:netrw_winsize + + put ='' + put ='+ For help, place cursor on line and press ' + + 1d + silent %s/^+/"/e + res 99 + silent %s/= \([^0-9].*\)$/= '\1'/e + silent %s/= $/= ''/e + 1 + + call setline(decompressline,"let g:netrw_decompress = ".substitute(string(g:netrw_decompress),"^'\\(.*\\)'$",'\1','')) + call setline(fnameescline, "let g:netrw_fname_escape = '".escape(g:netrw_fname_escape,"'")."'") + call setline(globescline, "let g:netrw_glob_escape = '".escape(g:netrw_glob_escape,"'")."'") + call setline(tmpfileescline,"let g:netrw_tmpfile_escape = '".escape(g:netrw_tmpfile_escape,"'")."'") + + set nomod + + nmap :call NetrwSettingHelp() + nnoremap :call NetrwSettingHelp() + let tmpfile= tempname() + exe 'au BufWriteCmd Netrw\ Settings silent w! '.tmpfile.'|so '.tmpfile.'|call delete("'.tmpfile.'")|set nomod' +endfun + +" --------------------------------------------------------------------- +" NetrwSettingHelp: {{{2 +fun! NetrwSettingHelp() +" call Dfunc("NetrwSettingHelp()") + let curline = getline(".") + if curline =~ '=' + let varhelp = substitute(curline,'^\s*let ','','e') + let varhelp = substitute(varhelp,'\s*=.*$','','e') +" call Decho("trying help ".varhelp) + try + exe "he ".varhelp + catch /^Vim\%((\a\+)\)\=:E149/ + echo "***sorry*** no help available for <".varhelp.">" + endtry + elseif line(".") < s:netrw_settings_stop + he netrw-settings + elseif line(".") < s:netrw_protocol_stop + he netrw-externapp + elseif line(".") < s:netrw_xfer_stop + he netrw-variables + else + he netrw-browse-var + endif +" call Dret("NetrwSettingHelp") +endfun + +" --------------------------------------------------------------------- +" Modelines: {{{1 +" vim:ts=8 fdm=marker diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim b/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim new file mode 100644 index 0000000000..6c1d8582b8 --- /dev/null +++ b/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim @@ -0,0 +1,26 @@ +" Maintainer: Luca Saccarola +" Former Maintainer: Bruno Sutic +" Upstream: + +" netrw_gitignore#Hide: gitignore-based hiding +" Function returns a string of comma separated patterns convenient for +" assignment to `g:netrw_list_hide` option. +" Function can take additional filenames as arguments, example: +" netrw_gitignore#Hide('custom_gitignore1', 'custom_gitignore2') +" +" Usage examples: +" let g:netrw_list_hide = netrw_gitignore#Hide() +" let g:netrw_list_hide = netrw_gitignore#Hide() . 'more,hide,patterns' +" +" Copyright: Copyright (C) 2013 Bruno Sutic {{{1 +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" netrw_gitignore.vim is provided *as is* and comes with no +" warranty of any kind, either expressed or implied. By using +" this plugin, you agree that in no event will the copyright +" holder be liable for any damages resulting from the use +" of this software. +function! netrw_gitignore#Hide(...) + return substitute(substitute(system('git ls-files --other --ignored --exclude-standard --directory'), '\n', ',', 'g'), ',$', '', '') +endfunction diff --git a/runtime/pack/dist/opt/netrw/doc/netrw.txt b/runtime/pack/dist/opt/netrw/doc/netrw.txt new file mode 100644 index 0000000000..9fc7b42bb7 --- /dev/null +++ b/runtime/pack/dist/opt/netrw/doc/netrw.txt @@ -0,0 +1,3786 @@ +*netrw.txt* Nvim + + ------------------------------------------------ + NETRW REFERENCE MANUAL by Charles E. Campbell + ------------------------------------------------ +Original Author: Charles E. Campbell +Copyright: Copyright (C) 2017 Charles E Campbell *netrw-copyright* + The VIM LICENSE applies to the files in this package, including + netrw.vim, netrw.txt, netrwSettings.vim, and + syntax/netrw.vim. Like anything else that's free, netrw.vim and its + associated files are provided *as is* and comes with no warranty of + any kind, either expressed or implied. No guarantees of + merchantability. No guarantees of suitability for any purpose. By + using this plugin, you agree that in no event will the copyright + holder be liable for any damages resulting from the use of this + software. Use at your own risk! + + *netrw* + *dav* *ftp* *netrw-file* *rcp* *scp* + *davs* *http* *netrw.vim* *rsync* *sftp* + *fetch* *network* + +============================================================================== +1. Contents *netrw-contents* {{{1 + +1. Contents..............................................|netrw-contents| +2. Starting With Netrw...................................|netrw-start| +3. Netrw Reference.......................................|netrw-ref| + EXTERNAL APPLICATIONS AND PROTOCOLS.................|netrw-externapp| + READING.............................................|netrw-read| + WRITING.............................................|netrw-write| + SOURCING............................................|netrw-source| + DIRECTORY LISTING...................................|netrw-dirlist| + CHANGING THE USERID AND PASSWORD....................|netrw-chgup| + VARIABLES AND SETTINGS..............................|netrw-variables| + PATHS...............................................|netrw-path| +4. Network-Oriented File Transfer........................|netrw-xfer| + NETRC...............................................|netrw-netrc| + PASSWORD............................................|netrw-passwd| +5. Activation............................................|netrw-activate| +6. Transparent Remote File Editing.......................|netrw-transparent| +7. Ex Commands...........................................|netrw-ex| +8. Variables and Options.................................|netrw-variables| +9. Browsing..............................................|netrw-browse| + Introduction To Browsing............................|netrw-intro-browse| + Quick Reference: Maps...............................|netrw-browse-maps| + Quick Reference: Commands...........................|netrw-browse-cmds| + Banner Display......................................|netrw-I| + Bookmarking A Directory.............................|netrw-mb| + Browsing............................................|netrw-cr| + Squeezing the Current Tree-Listing Directory........|netrw-s-cr| + Browsing With A Horizontally Split Window...........|netrw-o| + Browsing With A New Tab.............................|netrw-t| + Browsing With A Vertically Split Window.............|netrw-v| + Change Listing Style (thin wide long tree)..........|netrw-i| + Changing To A Bookmarked Directory..................|netrw-gb| + Quick hide/unhide of dot-files......................|netrw-gh| + Changing local-only File Permission.................|netrw-gp| + Changing To A Predecessor Directory.................|netrw-u| + Changing To A Successor Directory...................|netrw-U| + Customizing Browsing With A Special Handler.........|netrw-x| + Deleting Bookmarks..................................|netrw-mB| + Deleting Files Or Directories.......................|netrw-D| + Directory Exploring Commands........................|netrw-explore| + Exploring With Stars and Patterns...................|netrw-star| + Displaying Information About File...................|netrw-qf| + Edit File Or Directory Hiding List..................|netrw-ctrl-h| + Editing The Sorting Sequence........................|netrw-S| + Forcing treatment as a file or directory............|netrw-gd| |netrw-gf| + Going Up............................................|netrw--| + Hiding Files Or Directories.........................|netrw-a| + Improving Browsing..................................|netrw-ssh-hack| + Listing Bookmarks And History.......................|netrw-qb| + Making A New Directory..............................|netrw-d| + Making The Browsing Directory The Current Directory.|netrw-cd| + Marking Files.......................................|netrw-mf| + Unmarking Files.....................................|netrw-mF| + Marking Files By Location List......................|netrw-qL| + Marking Files By QuickFix List......................|netrw-qF| + Marking Files By Regular Expression.................|netrw-mr| + Marked Files: Arbitrary Shell Command...............|netrw-mx| + Marked Files: Arbitrary Shell Command, En Bloc......|netrw-mX| + Marked Files: Arbitrary Vim Command.................|netrw-mv| + Marked Files: Argument List.........................|netrw-ma| |netrw-mA| + Marked Files: Buffer List...........................|netrw-cb| |netrw-cB| + Marked Files: Compression And Decompression.........|netrw-mz| + Marked Files: Copying...............................|netrw-mc| + Marked Files: Diff..................................|netrw-md| + Marked Files: Editing...............................|netrw-me| + Marked Files: Grep..................................|netrw-mg| + Marked Files: Hiding and Unhiding by Suffix.........|netrw-mh| + Marked Files: Moving................................|netrw-mm| + Marked Files: Sourcing..............................|netrw-ms| + Marked Files: Setting the Target Directory..........|netrw-mt| + Marked Files: Tagging...............................|netrw-mT| + Marked Files: Target Directory Using Bookmarks......|netrw-Tb| + Marked Files: Target Directory Using History........|netrw-Th| + Marked Files: Unmarking.............................|netrw-mu| + Netrw Browser Variables.............................|netrw-browser-var| + Netrw Browsing And Option Incompatibilities.........|netrw-incompatible| + Netrw Settings Window...............................|netrw-settings-window| + Obtaining A File....................................|netrw-O| + Preview Window......................................|netrw-p| + Previous Window.....................................|netrw-P| + Refreshing The Listing..............................|netrw-ctrl-l| + Reversing Sorting Order.............................|netrw-r| + Renaming Files Or Directories.......................|netrw-R| + Selecting Sorting Style.............................|netrw-s| + Setting Editing Window..............................|netrw-C| +10. Problems and Fixes....................................|netrw-problems| +11. Credits...............................................|netrw-credits| + +============================================================================== +2. Starting With Netrw *netrw-start* {{{1 + +Netrw makes reading files, writing files, browsing over a network, and +local browsing easy! First, make sure that you have plugins enabled, so +you'll need to have at least the following in your <.vimrc>: +(or see |netrw-activate|) > + + set nocp " 'compatible' is not set + filetype plugin on " plugins are enabled +< +(see |'cp'| and |:filetype-plugin-on|) + +Netrw supports "transparent" editing of files on other machines using urls +(see |netrw-transparent|). As an example of this, let's assume you have an +account on some other machine; if you can use scp, try: > + + vim scp://hostname/path/to/file +< +Want to make ssh/scp easier to use? Check out |netrw-ssh-hack|! + +So, what if you have ftp, not ssh/scp? That's easy, too; try > + + vim ftp://hostname/path/to/file +< +Want to make ftp simpler to use? See if your ftp supports a file called +<.netrc> -- typically it goes in your home directory, has read/write +permissions for only the user to read (ie. not group, world, other, etc), +and has lines resembling > + + machine HOSTNAME login USERID password "PASSWORD" + machine HOSTNAME login USERID password "PASSWORD" + ... + default login USERID password "PASSWORD" +< +Windows' ftp doesn't support .netrc; however, one may have in one's .vimrc: > + + let g:netrw_ftp_cmd= 'c:\Windows\System32\ftp -s:C:\Users\MyUserName\MACHINE' +< +Netrw will substitute the host's machine name for "MACHINE" from the URL it is +attempting to open, and so one may specify > + userid + password +for each site in a separate file: c:\Users\MyUserName\MachineName. + +Now about browsing -- when you just want to look around before editing a +file. For browsing on your current host, just "edit" a directory: > + + vim . + vim /home/userid/path +< +For browsing on a remote host, "edit" a directory (but make sure that +the directory name is followed by a "/"): > + + vim scp://hostname/ + vim ftp://hostname/path/to/dir/ +< +See |netrw-browse| for more! + +There are more protocols supported by netrw than just scp and ftp, too: see the +next section, |netrw-externapp|, on how to use these external applications with +netrw and vim. + +PREVENTING LOADING *netrw-noload* + +If you want to use plugins, but for some reason don't wish to use netrw, then +you need to avoid loading both the plugin and the autoload portions of netrw. +You may do so by placing the following two lines in your <.vimrc>: > + + :let g:loaded_netrw = 1 + :let g:loaded_netrwPlugin = 1 +< + +============================================================================== +3. Netrw Reference *netrw-ref* {{{1 + + Netrw supports several protocols in addition to scp and ftp as mentioned + in |netrw-start|. These include dav, fetch, http,... well, just look + at the list in |netrw-externapp|. Each protocol is associated with a + variable which holds the default command supporting that protocol. + +EXTERNAL APPLICATIONS AND PROTOCOLS *netrw-externapp* {{{2 + + Protocol Variable Default Value + -------- ---------------- ------------- + dav: *g:netrw_dav_cmd* = "cadaver" if cadaver is executable + dav: g:netrw_dav_cmd = "curl -o" elseif curl is available + fetch: *g:netrw_fetch_cmd* = "fetch -o" if fetch is available + ftp: *g:netrw_ftp_cmd* = "ftp" + http: *g:netrw_http_cmd* = "elinks" if elinks is available + http: g:netrw_http_cmd = "links" elseif links is available + http: g:netrw_http_cmd = "curl" elseif curl is available + http: g:netrw_http_cmd = "wget" elseif wget is available + http: g:netrw_http_cmd = "fetch" elseif fetch is available + http: *g:netrw_http_put_cmd* = "curl -T" + rcp: *g:netrw_rcp_cmd* = "rcp" + rsync: *g:netrw_rsync_cmd* = "rsync" (see |g:netrw_rsync_sep|) + scp: *g:netrw_scp_cmd* = "scp -q" + sftp: *g:netrw_sftp_cmd* = "sftp" + file: *g:netrw_file_cmd* = "elinks" or "links" + + *g:netrw_http_xcmd* : the option string for http://... protocols are + specified via this variable and may be independently overridden. By + default, the option arguments for the http-handling commands are: > + + elinks : "-source >" + links : "-dump >" + curl : "-L -o" + wget : "-q -O" + fetch : "-o" +< + For example, if your system has elinks, and you'd rather see the + page using an attempt at rendering the text, you may wish to have > + let g:netrw_http_xcmd= "-dump >" +< in your .vimrc. + + g:netrw_http_put_cmd: this option specifies both the executable and + any needed options. This command does a PUT operation to the url. + + +READING *netrw-read* *netrw-nread* {{{2 + + Generally, one may just use the URL notation with a normal editing + command, such as > + + :e ftp://[user@]machine/path +< + Netrw also provides the Nread command: + + :Nread ? give help + :Nread "machine:path" uses rcp + :Nread "machine path" uses ftp w/ <.netrc> + :Nread "machine id password path" uses ftp + :Nread "dav://machine[:port]/path" uses cadaver + :Nread "fetch://[user@]machine/path" uses fetch + :Nread "ftp://[user@]machine[[:#]port]/path" uses ftp w/ <.netrc> + :Nread "http://[user@]machine/path" uses http uses wget + :Nread "rcp://[user@]machine/path" uses rcp + :Nread "rsync://[user@]machine[:port]/path" uses rsync + :Nread "scp://[user@]machine[[:#]port]/path" uses scp + :Nread "sftp://[user@]machine/path" uses sftp + +WRITING *netrw-write* *netrw-nwrite* {{{2 + + One may just use the URL notation with a normal file writing + command, such as > + + :w ftp://[user@]machine/path +< + Netrw also provides the Nwrite command: + + :Nwrite ? give help + :Nwrite "machine:path" uses rcp + :Nwrite "machine path" uses ftp w/ <.netrc> + :Nwrite "machine id password path" uses ftp + :Nwrite "dav://machine[:port]/path" uses cadaver + :Nwrite "ftp://[user@]machine[[:#]port]/path" uses ftp w/ <.netrc> + :Nwrite "rcp://[user@]machine/path" uses rcp + :Nwrite "rsync://[user@]machine[:port]/path" uses rsync + :Nwrite "scp://[user@]machine[[:#]port]/path" uses scp + :Nwrite "sftp://[user@]machine/path" uses sftp + http: not supported! + +SOURCING *netrw-source* {{{2 + + One may just use the URL notation with the normal file sourcing + command, such as > + + :so ftp://[user@]machine/path +< + Netrw also provides the Nsource command: + + :Nsource ? give help + :Nsource "dav://machine[:port]/path" uses cadaver + :Nsource "fetch://[user@]machine/path" uses fetch + :Nsource "ftp://[user@]machine[[:#]port]/path" uses ftp w/ <.netrc> + :Nsource "http://[user@]machine/path" uses http uses wget + :Nsource "rcp://[user@]machine/path" uses rcp + :Nsource "rsync://[user@]machine[:port]/path" uses rsync + :Nsource "scp://[user@]machine[[:#]port]/path" uses scp + :Nsource "sftp://[user@]machine/path" uses sftp + +DIRECTORY LISTING *netrw-trailingslash* *netrw-dirlist* {{{2 + + One may browse a directory to get a listing by simply attempting to + edit the directory: > + + :e scp://[user]@hostname/path/ + :e ftp://[user]@hostname/path/ +< + For remote directory listings (ie. those using scp or ftp), that + trailing "/" is necessary (the slash tells netrw to treat the argument + as a directory to browse instead of as a file to download). + + The Nread command may also be used to accomplish this (again, that + trailing slash is necessary): > + + :Nread [protocol]://[user]@hostname/path/ +< + *netrw-login* *netrw-password* +CHANGING USERID AND PASSWORD *netrw-chgup* *netrw-userpass* {{{2 + + Attempts to use ftp will prompt you for a user-id and a password. + These will be saved in global variables |g:netrw_uid| and + |s:netrw_passwd|; subsequent use of ftp will re-use those two strings, + thereby simplifying use of ftp. However, if you need to use a + different user id and/or password, you'll want to call |NetUserPass()| + first. To work around the need to enter passwords, check if your ftp + supports a <.netrc> file in your home directory. Also see + |netrw-passwd| (and if you're using ssh/scp hoping to figure out how + to not need to use passwords for scp, look at |netrw-ssh-hack|). + + :NetUserPass [uid [password]] -- prompts as needed + :call NetUserPass() -- prompts for uid and password + :call NetUserPass("uid") -- prompts for password + :call NetUserPass("uid","password") -- sets global uid and password + +(Related topics: |ftp| |netrw-userpass| |netrw-start|) + +NETRW VARIABLES AND SETTINGS *netrw-variables* {{{2 + (Also see: + |netrw-browser-var| : netrw browser option variables + |netrw-protocol| : file transfer protocol option variables + |netrw-settings| : additional file transfer options + |netrw-browser-options| : these options affect browsing directories + ) + +Netrw provides a lot of variables which allow you to customize netrw to your +preferences. One way to look at them is via the command :NetrwSettings (see +|netrw-settings|) which will display your current netrw settings. Most such +settings are described below, in |netrw-browser-options|, and in +|netrw-externapp|: + + *b:netrw_lastfile* last file Network-read/written retained on a + per-buffer basis (supports plain :Nw ) + + *g:netrw_bufsettings* the settings that netrw buffers have + (default) noma nomod nonu nowrap ro nobl + + *g:netrw_chgwin* specifies a window number where subsequent file edits + will take place. (also see |netrw-C|) + (default) -1 + + *g:Netrw_funcref* specifies a function (or functions) to be called when + netrw edits a file. The file is first edited, and + then the function reference (|Funcref|) is called. + This variable may also hold a |List| of Funcrefs. + (default) not defined. (the capital in g:Netrw... + is required by its holding a function reference) +> + Example: place in .vimrc; affects all file opening + fun! MyFuncRef() + endfun + let g:Netrw_funcref= function("MyFuncRef") + +< + *g:Netrw_UserMaps* specifies a function or |List| of functions which can + be used to set up user-specified maps and functionality. + See |netrw-usermaps| + + *g:netrw_ftp* if it doesn't exist, use default ftp + =0 use default ftp (uid password) + =1 use alternate ftp method (user uid password) + If you're having trouble with ftp, try changing the + value of this variable to see if the alternate ftp + method works for your setup. + + *g:netrw_ftp_options* Chosen by default, these options are supposed to + turn interactive prompting off and to restrain ftp + from attempting auto-login upon initial connection. + However, it appears that not all ftp implementations + support this (ex. ncftp). + ="-i -n" + + *g:netrw_ftpextracmd* default: doesn't exist + If this variable exists, then any string it contains + will be placed into the commands set to your ftp + client. As an example: + ="passive" + + *g:netrw_ftpmode* ="binary" (default) + ="ascii" + + *g:netrw_ignorenetrc* =0 (default for linux, cygwin) + =1 If you have a <.netrc> file but it doesn't work and + you want it ignored, then set this variable as + shown. (default for Windows + cmd.exe) + + *g:netrw_menu* =0 disable netrw's menu + =1 (default) netrw's menu enabled + + *g:netrw_nogx* if this variable exists, then the "gx" map will not + be available (see |netrw-gx|) + + *g:netrw_uid* (ftp) user-id, retained on a per-vim-session basis + *s:netrw_passwd* (ftp) password, retained on a per-vim-session basis + + *g:netrw_preview* =0 (default) preview window shown in a horizontally + split window + =1 preview window shown in a vertically split window. + Also affects the "previous window" (see |netrw-P|) + in the same way. + The |g:netrw_alto| variable may be used to provide + additional splitting control: + g:netrw_preview g:netrw_alto result + 0 0 |:aboveleft| + 0 1 |:belowright| + 1 0 |:topleft| + 1 1 |:botright| + To control sizing, see |g:netrw_winsize| + + *g:netrw_scpport* = "-P" : option to use to set port for scp + *g:netrw_sshport* = "-p" : option to use to set port for ssh + + *g:netrw_sepchr* =\0xff + =\0x01 for enc == euc-jp (and perhaps it should be for + others, too, please let me know) + Separates priority codes from filenames internally. + See |netrw-p12|. + + *g:netrw_silent* =0 : transfers done normally + =1 : transfers done silently + + *g:netrw_use_errorwindow* =2: messages from netrw will use a popup window + Move the mouse and pause to remove the popup window. + =1 : messages from netrw will use a separate one + line window. This window provides reliable + delivery of messages. + =0 : (default) messages from netrw will use echoerr ; + messages don't always seem to show up this + way, but one doesn't have to quit the window. + + *g:netrw_cygwin* =1 assume scp under windows is from cygwin. Also + permits network browsing to use ls with time and + size sorting (default if windows) + =0 assume Windows' scp accepts windows-style paths + Network browsing uses dir instead of ls + This option is ignored if you're using unix + + *g:netrw_use_nt_rcp* =0 don't use the rcp of WinNT, Win2000 and WinXP + =1 use WinNT's rcp in binary mode (default) + +PATHS *netrw-path* {{{2 + +Paths to files are generally user-directory relative for most protocols. +It is possible that some protocol will make paths relative to some +associated directory, however. +> + example: vim scp://user@host/somefile + example: vim scp://user@host/subdir1/subdir2/somefile +< +where "somefile" is in the "user"'s home directory. If you wish to get a +file using root-relative paths, use the full path: +> + example: vim scp://user@host//somefile + example: vim scp://user@host//subdir1/subdir2/somefile +< + +============================================================================== +4. Network-Oriented File Transfer *netrw-xfer* {{{1 + +Network-oriented file transfer under Vim is implemented by a vim script +() using plugin techniques. It currently supports both reading and +writing across networks using rcp, scp, ftp or ftp+<.netrc>, scp, fetch, +dav/cadaver, rsync, or sftp. + +http is currently supported read-only via use of wget or fetch. + + is a standard plugin which acts as glue between Vim and the +various file transfer programs. It uses autocommand events (BufReadCmd, +FileReadCmd, BufWriteCmd) to intercept reads/writes with url-like filenames. > + + ex. vim ftp://hostname/path/to/file +< +The characters preceding the colon specify the protocol to use; in the +example, it's ftp. The script then formulates a command or a +series of commands (typically ftp) which it issues to an external program +(ftp, scp, etc) which does the actual file transfer/protocol. Files are read +from/written to a temporary file (under Unix/Linux, /tmp/...) which the + script will clean up. + +Now, a word about Jan Minář's "FTP User Name and Password Disclosure"; first, +ftp is not a secure protocol. User names and passwords are transmitted "in +the clear" over the internet; any snooper tool can pick these up; this is not +a netrw thing, this is a ftp thing. If you're concerned about this, please +try to use scp or sftp instead. + +Netrw re-uses the user id and password during the same vim session and so long +as the remote hostname remains the same. + +Jan seems to be a bit confused about how netrw handles ftp; normally multiple +commands are performed in a "ftp session", and he seems to feel that the +uid/password should only be retained over one ftp session. However, netrw +does every ftp operation in a separate "ftp session"; so remembering the +uid/password for just one "ftp session" would be the same as not remembering +the uid/password at all. IMHO this would rapidly grow tiresome as one +browsed remote directories, for example. + +On the other hand, thanks go to Jan M. for pointing out the many +vulnerabilities that netrw (and vim itself) had had in handling "crafted" +filenames. The |shellescape()| and |fnameescape()| functions were written in +response by Bram Moolenaar to handle these sort of problems, and netrw has +been modified to use them. Still, my advice is, if the "filename" looks like +a vim command that you aren't comfortable with having executed, don't open it. + + *netrw-putty* *netrw-pscp* *netrw-psftp* +One may modify any protocol's implementing external application by setting a +variable (ex. scp uses the variable g:netrw_scp_cmd, which is defaulted to +"scp -q"). As an example, consider using PuTTY: > + + let g:netrw_scp_cmd = '"c:\Program Files\PuTTY\pscp.exe" -q -batch' + let g:netrw_sftp_cmd= '"c:\Program Files\PuTTY\psftp.exe"' +< +(note: it has been reported that windows 7 with putty v0.6's "-batch" option + doesn't work, so its best to leave it off for that system) + +See |netrw-p8| for more about putty, pscp, psftp, etc. + +Ftp, an old protocol, seems to be blessed by numerous implementations. +Unfortunately, some implementations are noisy (ie., add junk to the end of the +file). Thus, concerned users may decide to write a NetReadFixup() function +that will clean up after reading with their ftp. Some Unix systems (ie., +FreeBSD) provide a utility called "fetch" which uses the ftp protocol but is +not noisy and more convenient, actually, for to use. +Consequently, if "fetch" is available (ie. executable), it may be preferable +to use it for ftp://... based transfers. + +For rcp, scp, sftp, and http, one may use network-oriented file transfers +transparently; ie. +> + vim rcp://[user@]machine/path + vim scp://[user@]machine/path +< +If your ftp supports <.netrc>, then it too can be transparently used +if the needed triad of machine name, user id, and password are present in +that file. Your ftp must be able to use the <.netrc> file on its own, however. +> + vim ftp://[user@]machine[[:#]portnumber]/path +< +Windows provides an ftp (typically c:\Windows\System32\ftp.exe) which uses +an option, -s:filename (filename can and probably should be a full path) +which contains ftp commands which will be automatically run whenever ftp +starts. You may use this feature to enter a user and password for one site: > + userid + password +< *netrw-windows-netrc* *netrw-windows-s* +If |g:netrw_ftp_cmd| contains -s:[path/]MACHINE, then (on Windows machines +only) netrw will substitute the current machine name requested for ftp +connections for MACHINE. Hence one can have multiple machine.ftp files +containing login and password for ftp. Example: > + + let g:netrw_ftp_cmd= 'c:\Windows\System32\ftp -s:C:\Users\Myself\MACHINE' + vim ftp://myhost.somewhere.net/ + +will use a file > + + C:\Users\Myself\myhost.ftp +< +Often, ftp will need to query the user for the userid and password. +The latter will be done "silently"; ie. asterisks will show up instead of +the actually-typed-in password. Netrw will retain the userid and password +for subsequent read/writes from the most recent transfer so subsequent +transfers (read/write) to or from that machine will take place without +additional prompting. + + *netrw-urls* + +=================================+============================+============+ + | Reading | Writing | Uses | + +=================================+============================+============+ + | DAV: | | | + | dav://host/path | | cadaver | + | :Nread dav://host/path | :Nwrite dav://host/path | cadaver | + +---------------------------------+----------------------------+------------+ + | DAV + SSL: | | | + | davs://host/path | | cadaver | + | :Nread davs://host/path | :Nwrite davs://host/path | cadaver | + +---------------------------------+----------------------------+------------+ + | FETCH: | | | + | fetch://[user@]host/path | | | + | fetch://[user@]host:http/path | Not Available | fetch | + | :Nread fetch://[user@]host/path| | | + +---------------------------------+----------------------------+------------+ + | FILE: | | | + | file:///* | file:///* | | + | file://localhost/* | file://localhost/* | | + +---------------------------------+----------------------------+------------+ + | FTP: (*3) | (*3) | | + | ftp://[user@]host/path | ftp://[user@]host/path | ftp (*2) | + | :Nread ftp://host/path | :Nwrite ftp://host/path | ftp+.netrc | + | :Nread host path | :Nwrite host path | ftp+.netrc | + | :Nread host uid pass path | :Nwrite host uid pass path | ftp | + +---------------------------------+----------------------------+------------+ + | HTTP: wget is executable: (*4) | | | + | http://[user@]host/path | Not Available | wget | + +---------------------------------+----------------------------+------------+ + | HTTP: fetch is executable (*4) | | | + | http://[user@]host/path | Not Available | fetch | + +---------------------------------+----------------------------+------------+ + | RCP: | | | + | rcp://[user@]host/path | rcp://[user@]host/path | rcp | + +---------------------------------+----------------------------+------------+ + | RSYNC: | | | + | rsync://[user@]host/path | rsync://[user@]host/path | rsync | + | :Nread rsync://host/path | :Nwrite rsync://host/path | rsync | + | :Nread rcp://host/path | :Nwrite rcp://host/path | rcp | + +---------------------------------+----------------------------+------------+ + | SCP: | | | + | scp://[user@]host/path | scp://[user@]host/path | scp | + | :Nread scp://host/path | :Nwrite scp://host/path | scp (*1) | + +---------------------------------+----------------------------+------------+ + | SFTP: | | | + | sftp://[user@]host/path | sftp://[user@]host/path | sftp | + | :Nread sftp://host/path | :Nwrite sftp://host/path | sftp (*1) | + +=================================+============================+============+ + + (*1) For an absolute path use scp://machine//path. + + (*2) if <.netrc> is present, it is assumed that it will + work with your ftp client. Otherwise the script will + prompt for user-id and password. + + (*3) for ftp, "machine" may be machine#port or machine:port + if a different port is needed than the standard ftp port + + (*4) for http:..., if wget is available it will be used. Otherwise, + if fetch is available it will be used. + +Both the :Nread and the :Nwrite ex-commands can accept multiple filenames. + + +NETRC *netrw-netrc* + +The <.netrc> file, typically located in your home directory, contains lines +therein which map a hostname (machine name) to the user id and password you +prefer to use with it. + +The typical syntax for lines in a <.netrc> file is given as shown below. +Ftp under Unix usually supports <.netrc>; ftp under Windows usually doesn't. +> + machine {full machine name} login {user-id} password "{password}" + default login {user-id} password "{password}" + +Your ftp client must handle the use of <.netrc> on its own, but if the +<.netrc> file exists, an ftp transfer will not ask for the user-id or +password. + + Note: + Since this file contains passwords, make very sure nobody else can + read this file! Most programs will refuse to use a .netrc that is + readable for others. Don't forget that the system administrator can + still read the file! Ie. for Linux/Unix: chmod 600 .netrc + +Even though Windows' ftp clients typically do not support .netrc, netrw has +a work-around: see |netrw-windows-s|. + + +PASSWORD *netrw-passwd* + +The script attempts to get passwords for ftp invisibly using |inputsecret()|, +a built-in Vim function. See |netrw-userpass| for how to change the password +after one has set it. + +Unfortunately there doesn't appear to be a way for netrw to feed a password to +scp. Thus every transfer via scp will require re-entry of the password. +However, |netrw-ssh-hack| can help with this problem. + + +============================================================================== +5. Activation *netrw-activate* {{{1 + +Network-oriented file transfers are available by default whenever Vim's +|'nocompatible'| mode is enabled. Netrw's script files reside in your +system's plugin, autoload, and syntax directories; just the +plugin/netrwPlugin.vim script is sourced automatically whenever you bring up +vim. The main script in autoload/netrw.vim is only loaded when you actually +use netrw. I suggest that, at a minimum, you have at least the following in +your <.vimrc> customization file: > + + set nocp + if version >= 600 + filetype plugin indent on + endif +< +By also including the following lines in your .vimrc, one may have netrw +immediately activate when using [g]vim without any filenames, showing the +current directory: > + + " Augroup VimStartup: + augroup VimStartup + au! + au VimEnter * if expand("%") == "" | e . | endif + augroup END +< + +============================================================================== +6. Transparent Remote File Editing *netrw-transparent* {{{1 + +Transparent file transfers occur whenever a regular file read or write +(invoked via an |:autocmd| for |BufReadCmd|, |BufWriteCmd|, or |SourceCmd| +events) is made. Thus one may read, write, or source files across networks +just as easily as if they were local files! > + + vim ftp://[user@]machine/path + ... + :wq + +See |netrw-activate| for more on how to encourage your vim to use plugins +such as netrw. + +For password-free use of scp:, see |netrw-ssh-hack|. + + +============================================================================== +7. Ex Commands *netrw-ex* {{{1 + +The usual read/write commands are supported. There are also a few +additional commands available. Often you won't need to use Nwrite or +Nread as shown in |netrw-transparent| (ie. simply use > + :e URL + :r URL + :w URL +instead, as appropriate) -- see |netrw-urls|. In the explanations +below, a {netfile} is a URL to a remote file. + + *:Nwrite* *:Nw* +:[range]Nw[rite] Write the specified lines to the current + file as specified in b:netrw_lastfile. + (related: |netrw-nwrite|) + +:[range]Nw[rite] {netfile} [{netfile}]... + Write the specified lines to the {netfile}. + + *:Nread* *:Nr* +:Nr[ead] Read the lines from the file specified in b:netrw_lastfile + into the current buffer. (related: |netrw-nread|) + +:Nr[ead] {netfile} {netfile}... + Read the {netfile} after the current line. + + *:Nsource* *:Ns* +:Ns[ource] {netfile} + Source the {netfile}. + To start up vim using a remote .vimrc, one may use + the following (all on one line) (tnx to Antoine Mechelynck) > + vim -u NORC -N + --cmd "runtime plugin/netrwPlugin.vim" + --cmd "source scp://HOSTNAME/.vimrc" +< (related: |netrw-source|) + +:call NetUserPass() *NetUserPass()* + If g:netrw_uid and s:netrw_passwd don't exist, + this function will query the user for them. + (related: |netrw-userpass|) + +:call NetUserPass("userid") + This call will set the g:netrw_uid and, if + the password doesn't exist, will query the user for it. + (related: |netrw-userpass|) + +:call NetUserPass("userid","passwd") + This call will set both the g:netrw_uid and s:netrw_passwd. + The user-id and password are used by ftp transfers. One may + effectively remove the user-id and password by using empty + strings (ie. ""). + (related: |netrw-userpass|) + +:NetrwSettings This command is described in |netrw-settings| -- used to + display netrw settings and change netrw behavior. + + +============================================================================== +8. Variables and Options *netrw-var* *netrw-settings* {{{1 + +(also see: |netrw-options| |netrw-variables| |netrw-protocol| + |netrw-browser-settings| |netrw-browser-options| ) + +The script provides several variables which act as options to +affect 's file transfer behavior. These variables typically may be +set in the user's <.vimrc> file: (see also |netrw-settings| |netrw-protocol|) + *netrw-options* +> + ------------- + Netrw Options + ------------- + Option Meaning + -------------- ----------------------------------------------- +< + b:netrw_col Holds current cursor position (during NetWrite) + g:netrw_cygwin =1 assume scp under windows is from cygwin + (default/windows) + =0 assume scp under windows accepts windows + style paths (default/else) + g:netrw_ftp =0 use default ftp (uid password) + g:netrw_ftpmode ="binary" (default) + ="ascii" (your choice) + g:netrw_ignorenetrc =1 (default) + if you have a <.netrc> file but you don't + want it used, then set this variable. Its + mere existence is enough to cause <.netrc> + to be ignored. + b:netrw_lastfile Holds latest method/machine/path. + b:netrw_line Holds current line number (during NetWrite) + g:netrw_silent =0 transfers done normally + =1 transfers done silently + g:netrw_uid Holds current user-id for ftp. + g:netrw_use_nt_rcp =0 don't use WinNT/2K/XP's rcp (default) + =1 use WinNT/2K/XP's rcp, binary mode + ----------------------------------------------------------------------- +< + *netrw-internal-variables* +The script will also make use of the following variables internally, albeit +temporarily. +> + ------------------- + Temporary Variables + ------------------- + Variable Meaning + -------- ------------------------------------ +< + b:netrw_method Index indicating rcp/ftp+.netrc/ftp + w:netrw_method (same as b:netrw_method) + g:netrw_machine Holds machine name parsed from input + b:netrw_fname Holds filename being accessed > + ------------------------------------------------------------ +< + *netrw-protocol* + +Netrw supports a number of protocols. These protocols are invoked using the +variables listed below, and may be modified by the user. +> + ------------------------ + Protocol Control Options + ------------------------ + Option Type Setting Meaning + --------- -------- -------------- --------------------------- +< netrw_ftp variable =doesn't exist userid set by "user userid" + =0 userid set by "user userid" + =1 userid set by "userid" + NetReadFixup function =doesn't exist no change + =exists Allows user to have files + read via ftp automatically + transformed however they wish + by NetReadFixup() + g:netrw_dav_cmd var ="cadaver" if cadaver is executable + g:netrw_dav_cmd var ="curl -o" elseif curl is executable + g:netrw_fetch_cmd var ="fetch -o" if fetch is available + g:netrw_ftp_cmd var ="ftp" + g:netrw_http_cmd var ="fetch -o" if fetch is available + g:netrw_http_cmd var ="wget -O" else if wget is available + g:netrw_http_put_cmd var ="curl -T" + |g:netrw_list_cmd| var ="ssh USEPORT HOSTNAME ls -Fa" + g:netrw_rcp_cmd var ="rcp" + g:netrw_rsync_cmd var ="rsync" + *g:netrw_rsync_sep* var ="/" used to separate the hostname + from the file spec + g:netrw_scp_cmd var ="scp -q" + g:netrw_sftp_cmd var ="sftp" > + ------------------------------------------------------------------------- +< + *netrw-ftp* + +The g:netrw_..._cmd options (|g:netrw_ftp_cmd| and |g:netrw_sftp_cmd|) +specify the external program to use handle the ftp protocol. They may +include command line options (such as -p for passive mode). Example: > + + let g:netrw_ftp_cmd= "ftp -p" +< +Browsing is supported by using the |g:netrw_list_cmd|; the substring +"HOSTNAME" will be changed via substitution with whatever the current request +is for a hostname. + +Two options (|g:netrw_ftp| and |netrw-fixup|) both help with certain ftp's +that give trouble . In order to best understand how to use these options if +ftp is giving you troubles, a bit of discussion is provided on how netrw does +ftp reads. + +For ftp, netrw typically builds up lines of one of the following formats in a +temporary file: +> + IF g:netrw_ftp !exists or is not 1 IF g:netrw_ftp exists and is 1 + ---------------------------------- ------------------------------ +< + open machine [port] open machine [port] + user userid password userid password + [g:netrw_ftpmode] password + [g:netrw_ftpextracmd] [g:netrw_ftpmode] + get filename tempfile [g:netrw_extracmd] + get filename tempfile > + --------------------------------------------------------------------- +< +The |g:netrw_ftpmode| and |g:netrw_ftpextracmd| are optional. + +Netrw then executes the lines above by use of a filter: +> + :%! {g:netrw_ftp_cmd} -i [-n] +< +where + g:netrw_ftp_cmd is usually "ftp", + -i tells ftp not to be interactive + -n means don't use netrc and is used for Method #3 (ftp w/o <.netrc>) + +If <.netrc> exists it will be used to avoid having to query the user for +userid and password. The transferred file is put into a temporary file. +The temporary file is then read into the main editing session window that +requested it and the temporary file deleted. + +If your ftp doesn't accept the "user" command and immediately just demands a +userid, then try putting "let netrw_ftp=1" in your <.vimrc>. + + *netrw-cadaver* +To handle the SSL certificate dialog for untrusted servers, one may pull +down the certificate and place it into /usr/ssl/cert.pem. This operation +renders the server treatment as "trusted". + + *netrw-fixup* *netreadfixup* +If your ftp for whatever reason generates unwanted lines (such as AUTH +messages) you may write a NetReadFixup() function: +> + function! NetReadFixup(method,line1,line2) + " a:line1: first new line in current file + " a:line2: last new line in current file + if a:method == 1 "rcp + elseif a:method == 2 "ftp + <.netrc> + elseif a:method == 3 "ftp + machine,uid,password,filename + elseif a:method == 4 "scp + elseif a:method == 5 "http/wget + elseif a:method == 6 "dav/cadaver + elseif a:method == 7 "rsync + elseif a:method == 8 "fetch + elseif a:method == 9 "sftp + else " complain + endif + endfunction +> +The NetReadFixup() function will be called if it exists and thus allows you to +customize your reading process. + +(Related topics: |ftp| |netrw-userpass| |netrw-start|) + +============================================================================== +9. Browsing *netrw-browsing* *netrw-browse* *netrw-help* {{{1 + *netrw-browser* *netrw-dir* *netrw-list* + +INTRODUCTION TO BROWSING *netrw-intro-browse* {{{2 + (Quick References: |netrw-quickmaps| |netrw-quickcoms|) + +Netrw supports the browsing of directories on your local system and on remote +hosts; browsing includes listing files and directories, entering directories, +editing files therein, deleting files/directories, making new directories, +moving (renaming) files and directories, copying files and directories, etc. +One may mark files and execute any system command on them! The Netrw browser +generally implements the previous explorer's maps and commands for remote +directories, although details (such as pertinent global variable names) +necessarily differ. To browse a directory, simply "edit" it! > + + vim /your/directory/ + vim . + vim c:\your\directory\ +< +(Related topics: |netrw-cr| |netrw-o| |netrw-p| |netrw-P| |netrw-t| + |netrw-mf| |netrw-mx| |netrw-D| |netrw-R| |netrw-v| ) + +The Netrw remote file and directory browser handles two protocols: ssh and +ftp. The protocol in the url, if it is ftp, will cause netrw also to use ftp +in its remote browsing. Specifying any other protocol will cause it to be +used for file transfers; but the ssh protocol will be used to do remote +browsing. + +To use Netrw's remote directory browser, simply attempt to read a "file" with +a trailing slash and it will be interpreted as a request to list a directory: +> + vim [protocol]://[user@]hostname/path/ +< +where [protocol] is typically scp or ftp. As an example, try: > + + vim ftp://ftp.home.vim.org/pub/vim/ +< +For local directories, the trailing slash is not required. Again, because it's +easy to miss: to browse remote directories, the URL must terminate with a +slash! + +If you'd like to avoid entering the password repeatedly for remote directory +listings with ssh or scp, see |netrw-ssh-hack|. To avoid password entry with +ftp, see |netrw-netrc| (if your ftp supports it). + +There are several things you can do to affect the browser's display of files: + + * To change the listing style, press the "i" key (|netrw-i|). + Currently there are four styles: thin, long, wide, and tree. + To make that change "permanent", see |g:netrw_liststyle|. + + * To hide files (don't want to see those xyz~ files anymore?) see + |netrw-ctrl-h|. + + * Press s to sort files by name, time, or size. + +See |netrw-browse-cmds| for all the things you can do with netrw! + + *netrw-getftype* *netrw-filigree* *netrw-ftype* +The |getftype()| function is used to append a bit of filigree to indicate +filetype to locally listed files: + + directory : / + executable : * + fifo : | + links : @ + sockets : = + +The filigree also affects the |g:netrw_sort_sequence|. + + +QUICK HELP *netrw-quickhelp* {{{2 + (Use ctrl-] to select a topic)~ + Intro to Browsing...............................|netrw-intro-browse| + Quick Reference: Maps.........................|netrw-quickmap| + Quick Reference: Commands.....................|netrw-browse-cmds| + Hiding + Edit hiding list..............................|netrw-ctrl-h| + Hiding Files or Directories...................|netrw-a| + Hiding/Unhiding by suffix.....................|netrw-mh| + Hiding dot-files.............................|netrw-gh| + Listing Style + Select listing style (thin/long/wide/tree)....|netrw-i| + Associated setting variable...................|g:netrw_liststyle| + Shell command used to perform listing.........|g:netrw_list_cmd| + Quick file info...............................|netrw-qf| + Sorted by + Select sorting style (name/time/size).........|netrw-s| + Editing the sorting sequence..................|netrw-S| + Sorting options...............................|g:netrw_sort_options| + Associated setting variable...................|g:netrw_sort_sequence| + Reverse sorting order.........................|netrw-r| + + + *netrw-quickmap* *netrw-quickmaps* +QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2 +> + --- ----------------- ---- + Map Quick Explanation Link + --- ----------------- ---- +< Causes Netrw to issue help + Netrw will enter the directory or read the file |netrw-cr| + Netrw will attempt to remove the file/directory |netrw-del| + Edit file hiding list |netrw-ctrl-h| + Causes Netrw to refresh the directory listing |netrw-ctrl-l| + Browse using a gvim server |netrw-ctrl-r| + Shrink/expand a netrw/explore window |netrw-c-tab| + - Makes Netrw go up one directory |netrw--| + a Cycles between normal display, |netrw-a| + hiding (suppress display of files matching g:netrw_list_hide) + and showing (display only files which match g:netrw_list_hide) + cd Make browsing directory the current directory |netrw-cd| + C Setting the editing window |netrw-C| + d Make a directory |netrw-d| + D Attempt to remove the file(s)/directory(ies) |netrw-D| + gb Go to previous bookmarked directory |netrw-gb| + gd Force treatment as directory |netrw-gd| + gf Force treatment as file |netrw-gf| + gh Quick hide/unhide of dot-files |netrw-gh| + gn Make top of tree the directory below the cursor |netrw-gn| + gp Change local-only file permissions |netrw-gp| + i Cycle between thin, long, wide, and tree listings |netrw-i| + I Toggle the displaying of the banner |netrw-I| + mb Bookmark current directory |netrw-mb| + mc Copy marked files to marked-file target directory |netrw-mc| + md Apply diff to marked files (up to 3) |netrw-md| + me Place marked files on arg list and edit them |netrw-me| + mf Mark a file |netrw-mf| + mF Unmark files |netrw-mF| + mg Apply vimgrep to marked files |netrw-mg| + mh Toggle marked file suffices' presence on hiding list |netrw-mh| + mm Move marked files to marked-file target directory |netrw-mm| + mr Mark files using a shell-style |regexp| |netrw-mr| + mt Current browsing directory becomes markfile target |netrw-mt| + mT Apply ctags to marked files |netrw-mT| + mu Unmark all marked files |netrw-mu| + mv Apply arbitrary vim command to marked files |netrw-mv| + mx Apply arbitrary shell command to marked files |netrw-mx| + mX Apply arbitrary shell command to marked files en bloc|netrw-mX| + mz Compress/decompress marked files |netrw-mz| + o Enter the file/directory under the cursor in a new |netrw-o| + browser window. A horizontal split is used. + O Obtain a file specified by cursor |netrw-O| + p Preview the file |netrw-p| + P Browse in the previously used window |netrw-P| + qb List bookmarked directories and history |netrw-qb| + qf Display information on file |netrw-qf| + qF Mark files using a quickfix list |netrw-qF| + qL Mark files using a |location-list| |netrw-qL| + r Reverse sorting order |netrw-r| + R Rename the designated file(s)/directory(ies) |netrw-R| + s Select sorting style: by name, time, or file size |netrw-s| + S Specify suffix priority for name-sorting |netrw-S| + t Enter the file/directory under the cursor in a new tab|netrw-t| + u Change to recently-visited directory |netrw-u| + U Change to subsequently-visited directory |netrw-U| + v Enter the file/directory under the cursor in a new |netrw-v| + browser window. A vertical split is used. + x View file with an associated program |netrw-x| + X Execute filename under cursor via |system()| |netrw-X| + + % Open a new file in netrw's current directory |netrw-%| + + *netrw-mouse* *netrw-leftmouse* *netrw-middlemouse* *netrw-rightmouse* + (gvim only) selects word under mouse as if a + had been pressed (ie. edit file, change directory) + (gvim only) same as P selecting word under mouse; + see |netrw-P| + (gvim only) delete file/directory using word under + mouse + <2-leftmouse> (gvim only) when: + * in a netrw-selected file, AND + * |g:netrw_retmap| == 1 AND + * the user doesn't already have a <2-leftmouse> + mapping defined before netrw is autoloaded, + then a double clicked leftmouse button will return + to the netrw browser window. See |g:netrw_retmap|. + (gvim only) like mf, will mark files. Dragging + the shifted leftmouse will mark multiple files. + (see |netrw-mf|) + + (to disable mouse buttons while browsing: |g:netrw_mousemaps|) + + *netrw-quickcom* *netrw-quickcoms* +QUICK REFERENCE: COMMANDS *netrw-explore-cmds* *netrw-browse-cmds* {{{2 + :NetrwClean[!]............................................|netrw-clean| + :NetrwSettings............................................|netrw-settings| + :Ntree....................................................|netrw-ntree| + :Explore[!] [dir] Explore directory of current file......|netrw-explore| + :Hexplore[!] [dir] Horizontal Split & Explore.............|netrw-explore| + :Lexplore[!] [dir] Left Explorer Toggle...................|netrw-explore| + :Nexplore[!] [dir] Vertical Split & Explore...............|netrw-explore| + :Pexplore[!] [dir] Vertical Split & Explore...............|netrw-explore| + :Rexplore Return to Explorer.....................|netrw-explore| + :Sexplore[!] [dir] Split & Explore directory .............|netrw-explore| + :Texplore[!] [dir] Tab & Explore..........................|netrw-explore| + :Vexplore[!] [dir] Vertical Split & Explore...............|netrw-explore| + + +BANNER DISPLAY *netrw-I* + +One may toggle the displaying of the banner by pressing "I". + +Also See: |g:netrw_banner| + + +BOOKMARKING A DIRECTORY *netrw-mb* *netrw-bookmark* *netrw-bookmarks* {{{2 + +One may easily "bookmark" the currently browsed directory by using > + + mb +< + *.netrwbook* +Bookmarks are retained in between sessions of vim in a file called .netrwbook +as a |List|, which is typically stored in the first directory on the user's +'runtimepath'; entries are kept in sorted order. + +If there are marked files and/or directories, mb will add them to the bookmark +list. + + *netrw-:NetrwMB* +Additionally, one may use :NetrwMB to bookmark files or directories. > + + :NetrwMB[!] [files/directories] + +< No bang: enters files/directories into Netrw's bookmark system + + No argument and in netrw buffer: + if there are marked files : bookmark marked files + otherwise : bookmark file/directory under cursor + No argument and not in netrw buffer: bookmarks current open file + Has arguments : |glob()|s each arg and bookmarks them + + With bang: deletes files/directories from Netrw's bookmark system + +The :NetrwMB command is available outside of netrw buffers (once netrw has been +invoked in the session). + +The file ".netrwbook" holds bookmarks when netrw (and vim) is not active. By +default, its stored on the first directory on the user's |'runtimepath'|. + +Related Topics: + |netrw-gb| how to return (go) to a bookmark + |netrw-mB| how to delete bookmarks + |netrw-qb| how to list bookmarks + |g:netrw_home| controls where .netrwbook is kept + + +BROWSING *netrw-enter* *netrw-cr* {{{2 + +Browsing is simple: move the cursor onto a file or directory of interest. +Hitting the (the return key) will select the file or directory. +Directories will themselves be listed, and files will be opened using the +protocol given in the original read request. + + CAVEAT: There are four forms of listing (see |netrw-i|). Netrw assumes that + two or more spaces delimit filenames and directory names for the long and + wide listing formats. Thus, if your filename or directory name has two or + more sequential spaces embedded in it, or any trailing spaces, then you'll + need to use the "thin" format to select it. + +The |g:netrw_browse_split| option, which is zero by default, may be used to +cause the opening of files to be done in a new window or tab instead of the +default. When the option is one or two, the splitting will be taken +horizontally or vertically, respectively. When the option is set to three, a + will cause the file to appear in a new tab. + + +When using the gui (gvim), one may select a file by pressing the +button. In addition, if + + * |g:netrw_retmap| == 1 AND (its default value is 0) + * in a netrw-selected file, AND + * the user doesn't already have a <2-leftmouse> mapping defined before + netrw is loaded + +then a doubly-clicked leftmouse button will return to the netrw browser +window. + +Netrw attempts to speed up browsing, especially for remote browsing where one +may have to enter passwords, by keeping and re-using previously obtained +directory listing buffers. The |g:netrw_fastbrowse| variable is used to +control this behavior; one may have slow browsing (no buffer re-use), medium +speed browsing (re-use directory buffer listings only for remote directories), +and fast browsing (re-use directory buffer listings as often as possible). +The price for such re-use is that when changes are made (such as new files +are introduced into a directory), the listing may become out-of-date. One may +always refresh directory listing buffers by pressing ctrl-L (see +|netrw-ctrl-l|). + + *netrw-s-cr* +Squeezing the Current Tree-Listing Directory~ + +When the tree listing style is enabled (see |netrw-i|) and one is using +gvim, then the mapping may be used to squeeze (close) the +directory currently containing the cursor. + +Otherwise, one may remap a key combination of one's own choice to get +this effect: > + + nmap YOURKEYCOMBO NetrwTreeSqueeze +< +Put this line in $HOME/ftplugin/netrw/netrw.vim; it needs to be generated +for netrw buffers only. + +Related topics: + |netrw-ctrl-r| |netrw-o| |netrw-p| + |netrw-P| |netrw-t| |netrw-v| +Associated setting variables: + |g:netrw_browse_split| |g:netrw_fastbrowse| + |g:netrw_ftp_list_cmd| |g:netrw_ftp_sizelist_cmd| + |g:netrw_ftp_timelist_cmd| |g:netrw_ssh_browse_reject| + |g:netrw_ssh_cmd| |g:netrw_use_noswf| + + +BROWSING WITH A HORIZONTALLY SPLIT WINDOW *netrw-o* *netrw-horiz* {{{2 + +Normally one enters a file or directory using the . However, the "o" map +allows one to open a new window to hold the new directory listing or file. A +horizontal split is used. (for vertical splitting, see |netrw-v|) + +Normally, the o key splits the window horizontally with the new window and +cursor at the top. + +Associated setting variables: |g:netrw_alto| |g:netrw_winsize| + +Related topics: + |netrw-ctrl-r| |netrw-o| |netrw-p| + |netrw-P| |netrw-t| |netrw-v| +Associated setting variables: + |g:netrw_alto| control above/below splitting + |g:netrw_winsize| control initial sizing + +BROWSING WITH A NEW TAB *netrw-t* {{{2 + +Normally one enters a file or directory using the . The "t" map +allows one to open a new window holding the new directory listing or file in +a new tab. + +If you'd like to have the new listing in a background tab, use |gT|. + +Related topics: + |netrw-ctrl-r| |netrw-o| |netrw-p| + |netrw-P| |netrw-t| |netrw-v| +Associated setting variables: + |g:netrw_winsize| control initial sizing + +BROWSING WITH A VERTICALLY SPLIT WINDOW *netrw-v* {{{2 + +Normally one enters a file or directory using the . However, the "v" map +allows one to open a new window to hold the new directory listing or file. A +vertical split is used. (for horizontal splitting, see |netrw-o|) + +Normally, the v key splits the window vertically with the new window and +cursor at the left. + +There is only one tree listing buffer; using "v" on a displayed subdirectory +will split the screen, but the same buffer will be shown twice. + +Related topics: + |netrw-ctrl-r| |netrw-o| |netrw-p| + |netrw-P| |netrw-t| |netrw-v| +Associated setting variables: + |g:netrw_altv| control right/left splitting + |g:netrw_winsize| control initial sizing + + +BROWSING USING A GVIM SERVER *netrw-ctrl-r* {{{2 + +One may keep a browsing gvim separate from the gvim being used to edit. +Use the map on a file (not a directory) in the netrw browser, and it +will use a gvim server (see |g:netrw_servername|). Subsequent use of +(see |netrw-cr|) will re-use that server for editing files. + +Related topics: + |netrw-ctrl-r| |netrw-o| |netrw-p| + |netrw-P| |netrw-t| |netrw-v| +Associated setting variables: + |g:netrw_servername| : sets name of server + |g:netrw_browse_split| : controls how will open files + + +CHANGE LISTING STYLE (THIN LONG WIDE TREE) *netrw-i* {{{2 + +The "i" map cycles between the thin, long, wide, and tree listing formats. + +The thin listing format gives just the files' and directories' names. + +The long listing is either based on the "ls" command via ssh for remote +directories or displays the filename, file size (in bytes), and the time and +date of last modification for local directories. With the long listing +format, netrw is not able to recognize filenames which have trailing spaces. +Use the thin listing format for such files. + +The wide listing format uses two or more contiguous spaces to delineate +filenames; when using that format, netrw won't be able to recognize or use +filenames which have two or more contiguous spaces embedded in the name or any +trailing spaces. The thin listing format will, however, work with such files. +The wide listing format is the most compact. + +The tree listing format has a top directory followed by files and directories +preceded by one or more "|"s, which indicate the directory depth. One may +open and close directories by pressing the key while atop the directory +name. + +One may make a preferred listing style your default; see |g:netrw_liststyle|. +As an example, by putting the following line in your .vimrc, > + let g:netrw_liststyle= 3 +the tree style will become your default listing style. + +One typical way to use the netrw tree display is to: > + + vim . + (use i until a tree display shows) + navigate to a file + v (edit as desired in vertically split window) + ctrl-w h (to return to the netrw listing) + P (edit newly selected file in the previous window) + ctrl-w h (to return to the netrw listing) + P (edit newly selected file in the previous window) + ...etc... +< +Associated setting variables: |g:netrw_liststyle| |g:netrw_maxfilenamelen| + |g:netrw_timefmt| |g:netrw_list_cmd| + +CHANGE FILE PERMISSION *netrw-gp* {{{2 + +"gp" will ask you for a new permission for the file named under the cursor. +Currently, this only works for local files. + +Associated setting variables: |g:netrw_chgperm| + + +CHANGING TO A BOOKMARKED DIRECTORY *netrw-gb* {{{2 + +To change directory back to a bookmarked directory, use + + {cnt}gb + +Any count may be used to reference any of the bookmarks. +Note that |netrw-qb| shows both bookmarks and history; to go +to a location stored in the history see |netrw-u| and |netrw-U|. + +Related Topics: + |netrw-mB| how to delete bookmarks + |netrw-mb| how to make a bookmark + |netrw-qb| how to list bookmarks + + +CHANGING TO A PREDECESSOR DIRECTORY *netrw-u* *netrw-updir* {{{2 + +Every time you change to a new directory (new for the current session), netrw +will save the directory in a recently-visited directory history list (unless +|g:netrw_dirhistmax| is zero; by default, it holds ten entries). With the "u" +map, one can change to an earlier directory (predecessor). To do the +opposite, see |netrw-U|. + +The "u" map also accepts counts to go back in the history several slots. For +your convenience, qb (see |netrw-qb|) lists the history number which may be +used in that count. + + *.netrwhist* +See |g:netrw_dirhistmax| for how to control the quantity of history stack +slots. The file ".netrwhist" holds history when netrw (and vim) is not +active. By default, its stored on the first directory on the user's +|'runtimepath'|. + +Related Topics: + |netrw-U| changing to a successor directory + |g:netrw_home| controls where .netrwhist is kept + + +CHANGING TO A SUCCESSOR DIRECTORY *netrw-U* *netrw-downdir* {{{2 + +With the "U" map, one can change to a later directory (successor). +This map is the opposite of the "u" map. (see |netrw-u|) Use the +qb map to list both the bookmarks and history. (see |netrw-qb|) + +The "U" map also accepts counts to go forward in the history several slots. + +See |g:netrw_dirhistmax| for how to control the quantity of history stack +slots. + + +CHANGING TREE TOP *netrw-ntree* *:Ntree* *netrw-gn* {{{2 + +One may specify a new tree top for tree listings using > + + :Ntree [dirname] + +Without a "dirname", the current line is used (and any leading depth +information is elided). +With a "dirname", the specified directory name is used. + +The "gn" map will take the word below the cursor and use that for +changing the top of the tree listing. + + +NETRW CLEAN *netrw-clean* *:NetrwClean* {{{2 + +With :NetrwClean one may easily remove netrw from one's home directory; +more precisely, from the first directory on your |'runtimepath'|. + +With :NetrwClean!, netrw will attempt to remove netrw from all directories on +your |'runtimepath'|. Of course, you have to have write/delete permissions +correct to do this. + +With either form of the command, netrw will first ask for confirmation +that the removal is in fact what you want to do. If netrw doesn't have +permission to remove a file, it will issue an error message. + +CUSTOMIZING BROWSING WITH A SPECIAL HANDLER *netrw-x* *netrw-handler* {{{2 + +Certain files, such as html, gif, jpeg, (word/office) doc, etc, files, are +best seen with a special handler (ie. a tool provided with your computer's +operating system). Netrw allows one to invoke such special handlers by: + + * hitting gx with the cursor atop the file path or alternatively x + in a netrw buffer; the former can be disabled by defining the + |g:netrw_nogx| variable + * when in command line, typing :Open , see |:Open| below. + +One may also use visual mode (see |visual-start|) to select the text that the +special handler will use. Normally gx checks for a close-by URL or file name +to pick up the text under the cursor; one may change what |expand()| uses via the +|g:netrw_gx| variable (options include "", ""). Note that +expand("") depends on the |'isfname'| setting. Alternatively, one may +select the text to be used by gx by making a visual selection (see +|visual-block|) and then pressing gx. + +The selection function can be adapted for each filetype by adding a function +`Netrw_get_URL_`, where is given by the 'filetype'. +The function should return the URL or file name to be used by gx, and will +fall back to the default behavior if it returns an empty string. +For example, special handlers for links Markdown and HTML are + +" make gx work on concealed links regardless of exact cursor position: > + + function Netrw_get_URL_markdown() + " markdown URL such as [link text](http://ya.ru 'yandex search') + try + let save_view = winsaveview() + if searchpair('\[.\{-}\](', '', ')\zs', 'cbW', '', line('.')) > 0 + return matchstr(getline('.')[col('.')-1:], + \ '\[.\{-}\](\zs' .. g:netrw_regex_url .. '\ze\(\s\+.\{-}\)\?)') + endif + return '' + finally + call winrestview(save_view) + endtry + endfunction + + function Netrw_get_URL_html() + " HTML URL such as Python is here + " + try + let save_view = winsaveview() + if searchpair('\|/>\)\zs', 'cbW', '', line('.')) > 0 + return matchstr(getline('.')[col('.') - 1 : ], + \ 'href=["'.."'"..']\?\zs\S\{-}\ze["'.."'"..']\?/\?>') + endif + return '' + finally + call winrestview(save_view) + endtry + endfunction +< +Other than a file path, the text under the cursor may be a URL. Netrw uses +by default the following regular expression to determine if the text under the +cursor is a URL: +> + :let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}' +< +Associated setting variables: + |g:netrw_gx| control how gx picks up the text under the cursor + |g:netrw_nogx| prevent gx map while editing + |g:netrw_suppress_gx_mesg| controls gx's suppression of browser messages + +OPENING FILES AND LAUNCHING APPS *netrw-gx* *:Open* *:Launch* {{{2 + +Netrw determines which special handler by the following method: + + * if |g:netrw_browsex_viewer| exists, then it will be used to attempt to + view files. + If the viewer you wish to use does not support handling of a remote URL + directory, set |g:netrw_browsex_support_remote| to 0. + * otherwise: + + * for Windows : explorer.exe is used + * for Mac OS X : open is used. + * for Linux : xdg-open is used. + +To open a path (or URL) by the appropriate handler, type > + + :Open +< +No escaping, neither for the shell nor for Vim's command-line, is needed. + +To launch a specific application , often being > + + :Launch . + +Since can be arbitrarily complex, in particular contain many file +paths, the escaping is left to the user. + +If you disabled the netrw plugin by setting g:loaded_netrwPlugin (see +|netrw-noload|), then you can use > + + :call netrw#Launch(' ') + :call netrw#Open('') +< + *netrw-curdir* +DELETING BOOKMARKS *netrw-mB* {{{2 + +To delete a bookmark, use > + + {cnt}mB + +If there are marked files, then mB will remove them from the +bookmark list. + +Alternatively, one may use :NetrwMB! (see |netrw-:NetrwMB|). > + + :NetrwMB! [files/directories] + +Related Topics: + |netrw-gb| how to return (go) to a bookmark + |netrw-mb| how to make a bookmark + |netrw-qb| how to list bookmarks + + +DELETING FILES OR DIRECTORIES *netrw-delete* *netrw-D* *netrw-del* {{{2 + +If files have not been marked with |netrw-mf|: (local marked file list) + + Deleting/removing files and directories involves moving the cursor to the + file/directory to be deleted and pressing "D". Directories must be empty + first before they can be successfully removed. If the directory is a + softlink to a directory, then netrw will make two requests to remove the + directory before succeeding. Netrw will ask for confirmation before doing + the removal(s). You may select a range of lines with the "V" command + (visual selection), and then pressing "D". + +If files have been marked with |netrw-mf|: (local marked file list) + + Marked files (and empty directories) will be deleted; again, you'll be + asked to confirm the deletion before it actually takes place. + +A further approach is to delete files which match a pattern. + + * use :MF pattern (see |netrw-:MF|); then press "D". + + * use mr (see |netrw-mr|) which will prompt you for pattern. + This will cause the matching files to be marked. Then, + press "D". + +Please note that only empty directories may be deleted with the "D" mapping. +Regular files are deleted with |delete()|, too. + +The |g:netrw_rm_cmd|, |g:netrw_rmf_cmd|, and |g:netrw_rmdir_cmd| variables are +used to control the attempts to remove remote files and directories. The +g:netrw_rm_cmd is used with files, and its default value is: + + g:netrw_rm_cmd: ssh HOSTNAME rm + +The g:netrw_rmdir_cmd variable is used to support the removal of directories. +Its default value is: + + |g:netrw_rmdir_cmd|: ssh HOSTNAME rmdir + +If removing a directory fails with g:netrw_rmdir_cmd, netrw then will attempt +to remove it again using the g:netrw_rmf_cmd variable. Its default value is: + + |g:netrw_rmf_cmd|: ssh HOSTNAME rm -f + +Related topics: |netrw-d| +Associated setting variable: |g:netrw_rm_cmd| |g:netrw_ssh_cmd| + + +*netrw-explore* *netrw-hexplore* *netrw-nexplore* *netrw-pexplore* +*netrw-rexplore* *netrw-sexplore* *netrw-texplore* *netrw-vexplore* *netrw-lexplore* +DIRECTORY EXPLORATION COMMANDS {{{2 + + :[N]Explore[!] [dir]... Explore directory of current file *:Explore* + :[N]Hexplore[!] [dir]... Horizontal Split & Explore *:Hexplore* + :[N]Lexplore[!] [dir]... Left Explorer Toggle *:Lexplore* + :[N]Sexplore[!] [dir]... Split&Explore current file's directory *:Sexplore* + :[N]Vexplore[!] [dir]... Vertical Split & Explore *:Vexplore* + :Texplore [dir]... Tab & Explore *:Texplore* + :Rexplore ... Return to/from Explorer *:Rexplore* + + Used with :Explore **/pattern : (also see |netrw-starstar|) + :Nexplore............. go to next matching file *:Nexplore* + :Pexplore............. go to previous matching file *:Pexplore* + + *netrw-:Explore* +:Explore will open the local-directory browser on the current file's + directory (or on directory [dir] if specified). The window will be + split only if the file has been modified and |'hidden'| is not set, + otherwise the browsing window will take over that window. Normally + the splitting is taken horizontally. + Also see: |netrw-:Rexplore| +:Explore! is like :Explore, but will use vertical splitting. + + *netrw-:Hexplore* +:Hexplore [dir] does an :Explore with |:belowright| horizontal splitting. +:Hexplore! [dir] does an :Explore with |:aboveleft| horizontal splitting. + + *netrw-:Lexplore* +:[N]Lexplore [dir] toggles a full height Explorer window on the left hand side + of the current tab. It will open a netrw window on the current + directory if [dir] is omitted; a :Lexplore [dir] will show the + specified directory in the left-hand side browser display no matter + from which window the command is issued. + + By default, :Lexplore will change an uninitialized |g:netrw_chgwin| + to 2; edits will thus preferentially be made in window#2. + + The [N] specifies a |g:netrw_winsize| just for the new :Lexplore + window. That means that + if [N] < 0 : use |N| columns for the Lexplore window + if [N] = 0 : a normal split is made + if [N] > 0 : use N% of the current window will be used for the + new window + + Those who like this method often also like tree style displays; + see |g:netrw_liststyle|. + +:[N]Lexplore! [dir] is similar to :Lexplore, except that the full-height + Explorer window will open on the right hand side and an + uninitialized |g:netrw_chgwin| will be set to 1 (eg. edits will + preferentially occur in the leftmost window). + + Also see: |netrw-C| |g:netrw_browse_split| |g:netrw_wiw| + |netrw-p| |netrw-P| |g:netrw_chgwin| + |netrw-c-tab| |g:netrw_winsize| + + *netrw-:Sexplore* +:[N]Sexplore will always split the window before invoking the local-directory + browser. As with Explore, the splitting is normally done + horizontally. +:[N]Sexplore! [dir] is like :Sexplore, but the splitting will be done vertically. + + *netrw-:Texplore* +:Texplore [dir] does a |:tabnew| before generating the browser window + + *netrw-:Vexplore* +:[N]Vexplore [dir] does an :Explore with |:leftabove| vertical splitting. +:[N]Vexplore! [dir] does an :Explore with |:rightbelow| vertical splitting. + +The optional parameters are: + + [N]: This parameter will override |g:netrw_winsize| to specify the quantity of + rows and/or columns the new explorer window should have. + Otherwise, the |g:netrw_winsize| variable, if it has been specified by the + user, is used to control the quantity of rows and/or columns new + explorer windows should have. + + [dir]: By default, these explorer commands use the current file's directory. + However, one may explicitly provide a directory (path) to use instead; + ie. > + + :Explore /some/path +< + *netrw-:Rexplore* +:Rexplore This command is a little different from the other Explore commands + as it doesn't necessarily open an Explorer window. + + Return to Explorer~ + When one edits a file using netrw which can occur, for example, + when pressing while the cursor is atop a filename in a netrw + browser window, a :Rexplore issued while editing that file will + return the display to that of the last netrw browser display in + that window. + + Return from Explorer~ + Conversely, when one is editing a directory, issuing a :Rexplore + will return to editing the file that was last edited in that + window. + + The <2-leftmouse> map (which is only available under gvim and + cooperative terms) does the same as :Rexplore. + +Also see: |g:netrw_alto| |g:netrw_altv| |g:netrw_winsize| + + +*netrw-star* *netrw-starpat* *netrw-starstar* *netrw-starstarpat* *netrw-grep* +EXPLORING WITH STARS AND PATTERNS {{{2 + +When Explore, Sexplore, Hexplore, or Vexplore are used with one of the +following four patterns Explore generates a list of files which satisfy the +request for the local file system. These exploration patterns will not work +with remote file browsing. + + */filepat files in current directory which satisfy filepat + **/filepat files in current directory or below which satisfy the + file pattern + *//pattern files in the current directory which contain the + pattern (vimgrep is used) + **//pattern files in the current directory or below which contain + the pattern (vimgrep is used) +< +The cursor will be placed on the first file in the list. One may then +continue to go to subsequent files on that list via |:Nexplore| or to +preceding files on that list with |:Pexplore|. Explore will update the +directory and place the cursor appropriately. + +A plain > + :Explore +will clear the explore list. + +If your console or gui produces recognizable shift-up or shift-down sequences, +then you'll likely find using shift-downarrow and shift-uparrow convenient. +They're mapped by netrw as follows: + + == Nexplore, and + == Pexplore. + +As an example, consider +> + :Explore */*.c + :Nexplore + :Nexplore + :Pexplore +< +The status line will show, on the right hand side of the status line, a +message like "Match 3 of 20". + +Associated setting variables: + |g:netrw_keepdir| |g:netrw_browse_split| + |g:netrw_fastbrowse| |g:netrw_ftp_browse_reject| + |g:netrw_ftp_list_cmd| |g:netrw_ftp_sizelist_cmd| + |g:netrw_ftp_timelist_cmd| |g:netrw_list_cmd| + |g:netrw_liststyle| + + +DISPLAYING INFORMATION ABOUT FILE *netrw-qf* {{{2 + +With the cursor atop a filename, pressing "qf" will reveal the file's size +and last modification timestamp. Currently this capability is only available +for local files. + + +EDIT FILE OR DIRECTORY HIDING LIST *netrw-ctrl-h* *netrw-edithide* {{{2 + +The "" map brings up a requestor allowing the user to change the +file/directory hiding list contained in |g:netrw_list_hide|. The hiding list +consists of one or more patterns delimited by commas. Files and/or +directories satisfying these patterns will either be hidden (ie. not shown) or +be the only ones displayed (see |netrw-a|). + +The "gh" mapping (see |netrw-gh|) quickly alternates between the usual +hiding list and the hiding of files or directories that begin with ".". + +As an example, > + let g:netrw_list_hide= '\(^\|\s\s\)\zs\.\S\+' +Effectively, this makes the effect of a |netrw-gh| command the initial setting. +What it means: + + \(^\|\s\s\) : if the line begins with the following, -or- + two consecutive spaces are encountered + \zs : start the hiding match now + \. : if it now begins with a dot + \S\+ : and is followed by one or more non-whitespace + characters + +Associated setting variables: |g:netrw_hide| |g:netrw_list_hide| +Associated topics: |netrw-a| |netrw-gh| |netrw-mh| + + *netrw-sort-sequence* +EDITING THE SORTING SEQUENCE *netrw-S* *netrw-sortsequence* {{{2 + +When "Sorted by" is name, one may specify priority via the sorting sequence +(g:netrw_sort_sequence). The sorting sequence typically prioritizes the +name-listing by suffix, although any pattern will do. Patterns are delimited +by commas. The default sorting sequence is (all one line): + +For Unix: > + '[\/]$,\ + '[\/]$,\.[a-np-z]$,\.h$,\.c$,\.cpp$,*,\.o$,\.obj$,\.info$, + \.swp$,\.bak$,\~$' +< +The lone * is where all filenames not covered by one of the other patterns +will end up. One may change the sorting sequence by modifying the +g:netrw_sort_sequence variable (either manually or in your <.vimrc>) or by +using the "S" map. + +Related topics: |netrw-s| |netrw-S| +Associated setting variables: |g:netrw_sort_sequence| |g:netrw_sort_options| + + +EXECUTING FILE UNDER CURSOR VIA SYSTEM() *netrw-X* {{{2 + +Pressing X while the cursor is atop an executable file will yield a prompt +using the filename asking for any arguments. Upon pressing a [return], netrw +will then call |system()| with that command and arguments. The result will be +displayed by |:echomsg|, and so |:messages| will repeat display of the result. +Ansi escape sequences will be stripped out. + +See |cmdline-window| for directions for more on how to edit the arguments. + + +FORCING TREATMENT AS A FILE OR DIRECTORY *netrw-gd* *netrw-gf* {{{2 + +Remote symbolic links (ie. those listed via ssh or ftp) are problematic +in that it is difficult to tell whether they link to a file or to a +directory. + +To force treatment as a file: use > + gf +< +To force treatment as a directory: use > + gd +< + +GOING UP *netrw--* {{{2 + +To go up a directory, press "-" or press the when atop the ../ directory +entry in the listing. + +Netrw will use the command in |g:netrw_list_cmd| to perform the directory +listing operation after changing HOSTNAME to the host specified by the +user-prpvided url. By default netrw provides the command as: > + + ssh HOSTNAME ls -FLa +< +where the HOSTNAME becomes the [user@]hostname as requested by the attempt to +read. Naturally, the user may override this command with whatever is +preferred. The NetList function which implements remote browsing +expects that directories will be flagged by a trailing slash. + + +HIDING FILES OR DIRECTORIES *netrw-a* *netrw-hiding* {{{2 + +Netrw's browsing facility allows one to use the hiding list in one of three +ways: ignore it, hide files which match, and show only those files which +match. + +If no files have been marked via |netrw-mf|: + +The "a" map allows the user to cycle through the three hiding modes. + +The |g:netrw_list_hide| variable holds a comma delimited list of patterns +based on regular expressions (ex. ^.*\.obj$,^\.) which specify the hiding list. +(also see |netrw-ctrl-h|) To set the hiding list, use the map. As an +example, to hide files which begin with a ".", one may use the map to +set the hiding list to '^\..*' (or one may put let g:netrw_list_hide= '^\..*' +in one's <.vimrc>). One may then use the "a" key to show all files, hide +matching files, or to show only the matching files. + + Example: \.[ch]$ + This hiding list command will hide/show all *.c and *.h files. + + Example: \.c$,\.h$ + This hiding list command will also hide/show all *.c and *.h + files. + +Don't forget to use the "a" map to select the mode (normal/hiding/show) you +want! + +If files have been marked using |netrw-mf|, then this command will: + + if showing all files or non-hidden files: + modify the g:netrw_list_hide list by appending the marked files to it + and showing only non-hidden files. + + else if showing hidden files only: + modify the g:netrw_list_hide list by removing the marked files from it + and showing only non-hidden files. + endif + + *netrw-gh* *netrw-hide* +As a quick shortcut, one may press > + gh +to toggle between hiding files which begin with a period (dot) and not hiding +them. + +Associated setting variables: |g:netrw_list_hide| |g:netrw_hide| +Associated topics: |netrw-a| |netrw-ctrl-h| |netrw-mh| + + *netrw-gitignore* +Netrw provides a helper function 'netrw_gitignore#Hide()' that, when used with +|g:netrw_list_hide| automatically hides all git-ignored files. + +'netrw_gitignore#Hide' searches for patterns in the following files: > + + './.gitignore' + './.git/info/exclude' + global gitignore file: `git config --global core.excludesfile` + system gitignore file: `git config --system core.excludesfile` +< +Files that do not exist, are ignored. +Git-ignore patterns are taken from existing files, and converted to patterns for +hiding files. For example, if you had '*.log' in your '.gitignore' file, it +would be converted to '.*\.log'. + +To use this function, simply assign its output to |g:netrw_list_hide| option. > + + Example: let g:netrw_list_hide= netrw_gitignore#Hide() + Git-ignored files are hidden in Netrw. + + Example: let g:netrw_list_hide= netrw_gitignore#Hide('my_gitignore_file') + Function can take additional files with git-ignore patterns. + + Example: let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' + Combining 'netrw_gitignore#Hide' with custom patterns. +< + +IMPROVING BROWSING *netrw-listhack* *netrw-ssh-hack* {{{2 + +Especially with the remote directory browser, constantly entering the password +is tedious. + +For Linux/Unix systems, the book "Linux Server Hacks - 100 industrial strength +tips & tools" by Rob Flickenger (O'Reilly, ISBN 0-596-00461-3) gives a tip +for setting up no-password ssh and scp and discusses associated security +issues. It used to be available at http://hacks.oreilly.com/pub/h/66 , +but apparently that address is now being redirected to some "hackzine". +I'll attempt a summary based on that article and on a communication from +Ben Schmidt: + + 1. Generate a public/private key pair on the local machine + (ssh client): > + ssh-keygen -t rsa + (saving the file in ~/.ssh/id_rsa as prompted) +< + 2. Just hit the when asked for passphrase (twice) for no + passphrase. If you do use a passphrase, you will also need to use + ssh-agent so you only have to type the passphrase once per session. + If you don't use a passphrase, simply logging onto your local + computer or getting access to the keyfile in any way will suffice + to access any ssh servers which have that key authorized for login. + + 3. This creates two files: > + ~/.ssh/id_rsa + ~/.ssh/id_rsa.pub +< + 4. On the target machine (ssh server): > + cd + mkdir -p .ssh + chmod 0700 .ssh +< + 5. On your local machine (ssh client): (one line) > + ssh {serverhostname} + cat '>>' '~/.ssh/authorized_keys2' < ~/.ssh/id_rsa.pub +< + or, for OpenSSH, (one line) > + ssh {serverhostname} + cat '>>' '~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub +< +You can test it out with > + ssh {serverhostname} +and you should be log onto the server machine without further need to type +anything. + +If you decided to use a passphrase, do: > + ssh-agent $SHELL + ssh-add + ssh {serverhostname} +You will be prompted for your key passphrase when you use ssh-add, but not +subsequently when you use ssh. For use with vim, you can use > + ssh-agent vim +and, when next within vim, use > + :!ssh-add +Alternatively, you can apply ssh-agent to the terminal you're planning on +running vim in: > + ssh-agent xterm & +and do ssh-add whenever you need. + +For Windows, folks on the vim mailing list have mentioned that Pageant helps +with avoiding the constant need to enter the password. + +Kingston Fung wrote about another way to avoid constantly needing to enter +passwords: + + In order to avoid the need to type in the password for scp each time, you + provide a hack in the docs to set up a non password ssh account. I found a + better way to do that: I can use a regular ssh account which uses a + password to access the material without the need to key-in the password + each time. It's good for security and convenience. I tried ssh public key + authorization + ssh-agent, implementing this, and it works! + + + Ssh hints: + + Thomer Gil has provided a hint on how to speed up netrw+ssh: + http://thomer.com/howtos/netrw_ssh.html + + Alex Young has several hints on speeding ssh up: + http://usevim.com/2012/03/16/editing-remote-files/ + + +LISTING BOOKMARKS AND HISTORY *netrw-qb* *netrw-listbookmark* {{{2 + +Pressing "qb" (query bookmarks) will list both the bookmarked directories and +directory traversal history. + +Related Topics: + |netrw-gb| how to return (go) to a bookmark + |netrw-mb| how to make a bookmark + |netrw-mB| how to delete bookmarks + |netrw-u| change to a predecessor directory via the history stack + |netrw-U| change to a successor directory via the history stack + +MAKING A NEW DIRECTORY *netrw-d* {{{2 + +With the "d" map one may make a new directory either remotely (which depends +on the global variable g:netrw_mkdir_cmd) or locally (which depends on the +global variable g:netrw_localmkdir). Netrw will issue a request for the new +directory's name. A bare at that point will abort the making of the +directory. Attempts to make a local directory that already exists (as either +a file or a directory) will be detected, reported on, and ignored. + +Related topics: |netrw-D| +Associated setting variables: |g:netrw_localmkdir| |g:netrw_mkdir_cmd| + |g:netrw_remote_mkdir| |netrw-%| + + +MAKING THE BROWSING DIRECTORY THE CURRENT DIRECTORY *netrw-cd* {{{2 + +By default, |g:netrw_keepdir| is 1. This setting means that the current +directory will not track the browsing directory. (done for backwards +compatibility with v6's file explorer). + +Setting g:netrw_keepdir to 0 tells netrw to make vim's current directory +track netrw's browsing directory. + +However, given the default setting for g:netrw_keepdir of 1 where netrw +maintains its own separate notion of the current directory, in order to make +the two directories the same, use the "cd" map (type cd). That map will +set Vim's notion of the current directory to netrw's current browsing +directory. + +|netrw-cd| : This map's name was changed from "c" to cd (see |netrw-cd|). + This change was done to allow for |netrw-cb| and |netrw-cB| maps. + +Associated setting variable: |g:netrw_keepdir| + +MARKING FILES *netrw-:MF* *netrw-mf* {{{2 + (also see |netrw-mr|) + +Netrw provides several ways to mark files: + + * One may mark files with the cursor atop a filename and + then pressing "mf". + + * With gvim, in addition one may mark files with + . (see |netrw-mouse|) + + * One may use the :MF command, which takes a list of + files (for local directories, the list may include + wildcards -- see |glob()|) > + + :MF *.c +< + (Note that :MF uses || to break the line + at spaces) + + * Mark files using the |argument-list| (|netrw-mA|) + + * Mark files based upon a |location-list| (|netrw-qL|) + + * Mark files based upon the quickfix list (|netrw-qF|) + (|quickfix-error-lists|) + +The following netrw maps make use of marked files: + + |netrw-a| Hide marked files/directories + |netrw-D| Delete marked files/directories + |netrw-ma| Move marked files' names to |arglist| + |netrw-mA| Move |arglist| filenames to marked file list + |netrw-mb| Append marked files to bookmarks + |netrw-mB| Delete marked files from bookmarks + |netrw-mc| Copy marked files to target + |netrw-md| Apply vimdiff to marked files + |netrw-me| Edit marked files + |netrw-mF| Unmark marked files + |netrw-mg| Apply vimgrep to marked files + |netrw-mm| Move marked files to target + |netrw-ms| Netrw will source marked files + |netrw-mt| Set target for |netrw-mm| and |netrw-mc| + |netrw-mT| Generate tags using marked files + |netrw-mv| Apply vim command to marked files + |netrw-mx| Apply shell command to marked files + |netrw-mX| Apply shell command to marked files, en bloc + |netrw-mz| Compress/Decompress marked files + |netrw-O| Obtain marked files + |netrw-R| Rename marked files + +One may unmark files one at a time the same way one marks them; ie. place +the cursor atop a marked file and press "mf". This process also works +with using gvim. One may unmark all files by pressing +"mu" (see |netrw-mu|). + +Marked files are highlighted using the "netrwMarkFile" highlighting group, +which by default is linked to "Identifier" (see Identifier under +|group-name|). You may change the highlighting group by putting something +like > + + highlight clear netrwMarkFile + hi link netrwMarkFile ..whatever.. +< +into $HOME/.vim/after/syntax/netrw.vim . + +If the mouse is enabled and works with your vim, you may use to +mark one or more files. You may mark multiple files by dragging the shifted +leftmouse. (see |netrw-mouse|) + + *markfilelist* *global_markfilelist* *local_markfilelist* +All marked files are entered onto the global marked file list; there is only +one such list. In addition, every netrw buffer also has its own buffer-local +marked file list; since netrw buffers are associated with specific +directories, this means that each directory has its own local marked file +list. The various commands which operate on marked files use one or the other +of the marked file lists. + +Known Problem: if one is using tree mode (|g:netrw_liststyle|) and several +directories have files with the same name, then marking such a file will +result in all such files being highlighted as if they were all marked. The +|markfilelist|, however, will only have the selected file in it. This problem +is unlikely to be fixed. + + +UNMARKING FILES *netrw-mF* {{{2 + (also see |netrw-mf|, |netrw-mu|) + +The "mF" command will unmark all files in the current buffer. One may also use +mf (|netrw-mf|) on a specific, already marked, file to unmark just that file. + +MARKING FILES BY LOCATION LIST *netrw-qL* {{{2 + (also see |netrw-mf|) + +One may convert |location-list|s into a marked file list using "qL". +You may then proceed with commands such as me (|netrw-me|) to edit them. + + +MARKING FILES BY QUICKFIX LIST *netrw-qF* {{{2 + (also see |netrw-mf|) + +One may convert |quickfix-error-lists| into a marked file list using "qF". +You may then proceed with commands such as me (|netrw-me|) to edit them. +Quickfix error lists are generated, for example, by calls to |:vimgrep|. + + +MARKING FILES BY REGULAR EXPRESSION *netrw-mr* {{{2 + (also see |netrw-mf|) + +One may also mark files by pressing "mr"; netrw will then issue a prompt, +"Enter regexp: ". You may then enter a shell-style regular expression such +as *.c$ (see |glob()|). For remote systems, glob() doesn't work -- so netrw +converts "*" into ".*" (see |regexp|) and marks files based on that. In the +future I may make it possible to use |regexp|s instead of glob()-style +expressions (yet-another-option). + +See |cmdline-window| for directions on more on how to edit the regular +expression. + + +MARKED FILES, ARBITRARY VIM COMMAND *netrw-mv* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the local marked-file list) + +The "mv" map causes netrw to execute an arbitrary vim command on each file on +the local marked file list, individually: + + * 1split + * sil! keepalt e file + * run vim command + * sil! keepalt wq! + +A prompt, "Enter vim command: ", will be issued to elicit the vim command you +wish used. See |cmdline-window| for directions for more on how to edit the +command. + + +MARKED FILES, ARBITRARY SHELL COMMAND *netrw-mx* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the local marked-file list) + +Upon activation of the "mx" map, netrw will query the user for some (external) +command to be applied to all marked files. All "%"s in the command will be +substituted with the name of each marked file in turn. If no "%"s are in the +command, then the command will be followed by a space and a marked filename. + +Example: + (mark files) + mx + Enter command: cat + + The result is a series of shell commands: + cat 'file1' + cat 'file2' + ... + + +MARKED FILES, ARBITRARY SHELL COMMAND, EN BLOC *netrw-mX* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked-file list) + +Upon activation of the 'mX' map, netrw will query the user for some (external) +command to be applied to all marked files on the global marked file list. The +"en bloc" means that one command will be executed on all the files at once: > + + command files + +This approach is useful, for example, to select files and make a tarball: > + + (mark files) + mX + Enter command: tar cf mynewtarball.tar +< +The command that will be run with this example: + + tar cf mynewtarball.tar 'file1' 'file2' ... + + +MARKED FILES: ARGUMENT LIST *netrw-ma* *netrw-mA* + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked-file list) + +Using ma, one moves filenames from the marked file list to the argument list. +Using mA, one moves filenames from the argument list to the marked file list. + +See Also: |netrw-cb| |netrw-cB| |netrw-qF| |argument-list| |:args| + + +MARKED FILES: BUFFER LIST *netrw-cb* *netrw-cB* + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked-file list) + +Using cb, one moves filenames from the marked file list to the buffer list. +Using cB, one copies filenames from the buffer list to the marked file list. + +See Also: |netrw-ma| |netrw-mA| |netrw-qF| |buffer-list| |:buffers| + + +MARKED FILES: COMPRESSION AND DECOMPRESSION *netrw-mz* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the local marked file list) + +If any marked files are compressed, then "mz" will decompress them. +If any marked files are decompressed, then "mz" will compress them +using the command specified by |g:netrw_compress|; by default, +that's "gzip". + +For decompression, netrw uses a |Dictionary| of suffices and their +associated decompressing utilities; see |g:netrw_decompress|. + +Remember that one can mark multiple files by regular expression +(see |netrw-mr|); this is particularly useful to facilitate compressing and +decompressing a large number of files. + +Associated setting variables: |g:netrw_compress| |g:netrw_decompress| + +MARKED FILES: COPYING *netrw-mc* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (Uses the global marked file list) + +Select a target directory with mt (|netrw-mt|). Then change directory, +select file(s) (see |netrw-mf|), and press "mc". The copy is done +from the current window (where one does the mf) to the target. + +If one does not have a target directory set with |netrw-mt|, then netrw +will query you for a directory to copy to. + +One may also copy directories and their contents (local only) to a target +directory. + +Associated setting variables: + |g:netrw_localcopycmd| |g:netrw_localcopycmdopt| + |g:netrw_localcopydircmd| |g:netrw_localcopydircmdopt| + |g:netrw_ssh_cmd| + +MARKED FILES: DIFF *netrw-md* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked file list) + +Use vimdiff to visualize difference between selected files (two or +three may be selected for this). Uses the global marked file list. + +MARKED FILES: EDITING *netrw-me* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked file list) + +The "me" command will place the marked files on the |arglist| and commence +editing them. One may return the to explorer window with |:Rexplore|. +(use |:n| and |:p| to edit next and previous files in the arglist) + +MARKED FILES: GREP *netrw-mg* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked file list) + +The "mg" command will apply |:vimgrep| to the marked files. +The command will ask for the requested pattern; one may then enter: > + + /pattern/[g][j] + ! /pattern/[g][j] + pattern +< +With /pattern/, editing will start with the first item on the |quickfix| list +that vimgrep sets up (see |:copen|, |:cnext|, |:cprevious|, |:cclose|). The |:vimgrep| +command is in use, so without 'g' each line is added to quickfix list only +once; with 'g' every match is included. + +With /pattern/j, "mg" will winnow the current marked file list to just those +marked files also possessing the specified pattern. Thus, one may use > + + mr ...file-pattern... + mg /pattern/j +< +to have a marked file list satisfying the file-pattern but also restricted to +files containing some desired pattern. + + +MARKED FILES: HIDING AND UNHIDING BY SUFFIX *netrw-mh* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the local marked file list) + +The "mh" command extracts the suffices of the marked files and toggles their +presence on the hiding list. Please note that marking the same suffix +this way multiple times will result in the suffix's presence being toggled +for each file (so an even quantity of marked files having the same suffix +is the same as not having bothered to select them at all). + +Related topics: |netrw-a| |g:netrw_list_hide| + +MARKED FILES: MOVING *netrw-mm* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked file list) + + WARNING: moving files is more dangerous than copying them. + A file being moved is first copied and then deleted; if the + copy operation fails and the delete succeeds, you will lose + the file. Either try things out with unimportant files + first or do the copy and then delete yourself using mc and D. + Use at your own risk! + +Select a target directory with mt (|netrw-mt|). Then change directory, +select file(s) (see |netrw-mf|), and press "mm". The move is done +from the current window (where one does the mf) to the target. + +Associated setting variable: |g:netrw_localmovecmd| |g:netrw_ssh_cmd| + +MARKED FILES: SOURCING *netrw-ms* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the local marked file list) + +With "ms", netrw will source the marked files (using vim's |:source| command) + + +MARKED FILES: SETTING THE TARGET DIRECTORY *netrw-mt* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + +Set the marked file copy/move-to target (see |netrw-mc| and |netrw-mm|): + + * If the cursor is atop a file name, then the netrw window's currently + displayed directory is used for the copy/move-to target. + + * Also, if the cursor is in the banner, then the netrw window's currently + displayed directory is used for the copy/move-to target. + Unless the target already is the current directory. In which case, + typing "mf" clears the target. + + * However, if the cursor is atop a directory name, then that directory is + used for the copy/move-to target + + * One may use the :MT [directory] command to set the target *netrw-:MT* + This command uses ||, so spaces in the directory name are + permitted without escaping. + + * With mouse-enabled vim or with gvim, one may select a target by using + + +There is only one copy/move-to target at a time in a vim session; ie. the +target is a script variable (see |s:var|) and is shared between all netrw +windows (in an instance of vim). + +When using menus and gvim, netrw provides a "Targets" entry which allows one +to pick a target from the list of bookmarks and history. + +Related topics: + Marking Files......................................|netrw-mf| + Marking Files by Regular Expression................|netrw-mr| + Marked Files: Target Directory Using Bookmarks.....|netrw-Tb| + Marked Files: Target Directory Using History.......|netrw-Th| + + +MARKED FILES: TAGGING *netrw-mT* {{{2 + (See |netrw-mf| and |netrw-mr| for how to mark files) + (uses the global marked file list) + +The "mT" mapping will apply the command in |g:netrw_ctags| (by default, it is +"ctags") to marked files. For remote browsing, in order to create a tags file +netrw will use ssh (see |g:netrw_ssh_cmd|), and so ssh must be available for +this to work on remote systems. For your local system, see |ctags| on how to +get a version. I myself use hdrtags, currently available at +http://www.drchip.org/astronaut/src/index.html , and have > + + let g:netrw_ctags= "hdrtag" +< +in my <.vimrc>. + +When a remote set of files are tagged, the resulting tags file is "obtained"; +ie. a copy is transferred to the local system's directory. The now local tags +file is then modified so that one may use it through the network. The +modification made concerns the names of the files in the tags; each filename is +preceded by the netrw-compatible URL used to obtain it. When one subsequently +uses one of the go to tag actions (|tags|), the URL will be used by netrw to +edit the desired file and go to the tag. + +Associated setting variables: |g:netrw_ctags| |g:netrw_ssh_cmd| + +MARKED FILES: TARGET DIRECTORY USING BOOKMARKS *netrw-Tb* {{{2 + +Sets the marked file copy/move-to target. + +The |netrw-qb| map will give you a list of bookmarks (and history). +One may choose one of the bookmarks to become your marked file +target by using [count]Tb (default count: 1). + +Related topics: + Copying files to target............................|netrw-mc| + Listing Bookmarks and History......................|netrw-qb| + Marked Files: Setting The Target Directory.........|netrw-mt| + Marked Files: Target Directory Using History.......|netrw-Th| + Marking Files......................................|netrw-mf| + Marking Files by Regular Expression................|netrw-mr| + Moving files to target.............................|netrw-mm| + + +MARKED FILES: TARGET DIRECTORY USING HISTORY *netrw-Th* {{{2 + +Sets the marked file copy/move-to target. + +The |netrw-qb| map will give you a list of history (and bookmarks). +One may choose one of the history entries to become your marked file +target by using [count]Th (default count: 0; ie. the current directory). + +Related topics: + Copying files to target............................|netrw-mc| + Listing Bookmarks and History......................|netrw-qb| + Marked Files: Setting The Target Directory.........|netrw-mt| + Marked Files: Target Directory Using Bookmarks.....|netrw-Tb| + Marking Files......................................|netrw-mf| + Marking Files by Regular Expression................|netrw-mr| + Moving files to target.............................|netrw-mm| + + +MARKED FILES: UNMARKING *netrw-mu* {{{2 + (See |netrw-mf|, |netrw-mF|) + +The "mu" mapping will unmark all currently marked files. This command differs +from "mF" as the latter only unmarks files in the current directory whereas +"mu" will unmark global and all buffer-local marked files. +(see |netrw-mF|) + + + *netrw-browser-settings* +NETRW BROWSER VARIABLES *netrw-browser-options* *netrw-browser-var* {{{2 + +(if you're interested in the netrw file transfer settings, see |netrw-options| + and |netrw-protocol|) + +The browser provides settings in the form of variables which +you may modify; by placing these settings in your <.vimrc>, you may customize +your browsing preferences. (see also: |netrw-settings|) +> + --- ----------- + Var Explanation + --- ----------- +< *g:netrw_altfile* some like |CTRL-^| to return to the last + edited file. Choose that by setting this + parameter to 1. + Others like |CTRL-^| to return to the + netrw browsing buffer. Choose that by setting + this parameter to 0. + default: =0 + + *g:netrw_alto* change from above splitting to below splitting + by setting this variable (see |netrw-o|) + default: =&sb (see |'sb'|) + + *g:netrw_altv* change from left splitting to right splitting + by setting this variable (see |netrw-v|) + default: =&spr (see |'spr'|) + + *g:netrw_banner* enable/suppress the banner + =0: suppress the banner + =1: banner is enabled (default) + + *g:netrw_bannerbackslash* if this variable exists and is not zero, the + banner will be displayed with backslashes + rather than forward slashes. + + *g:netrw_browse_split* when browsing, will open the file by: + =0: re-using the same window (default) + =1: horizontally splitting the window first + =2: vertically splitting the window first + =3: open file in new tab + =4: act like "P" (ie. open previous window) + Note that |g:netrw_preview| may be used + to get vertical splitting instead of + horizontal splitting. + =[servername,tab-number,window-number] + Given a |List| such as this, a remote server + named by the "servername" will be used for + editing. It will also use the specified tab + and window numbers to perform editing + (see |clientserver|, |netrw-ctrl-r|) + This option does not affect the production of + |:Lexplore| windows. + + Related topics: + |g:netrw_alto| |g:netrw_altv| + |netrw-C| |netrw-cr| + |netrw-ctrl-r| + + *g:netrw_browsex_viewer* specify user's preference for a viewer: > + "kfmclient exec" + "gnome-open" +< + *g:netrw_browsex_support_remote* + specify if the specified viewer supports a + remote URL. (see |netrw-handler|). + + *g:netrw_chgperm* Unix/Linux: "chmod PERM FILENAME" + Windows: "cacls FILENAME /e /p PERM" + Used to change access permission for a file. + + *g:netrw_clipboard* =1 + By default, netrw will attempt to insure that + the clipboard's values will remain unchanged. + However, some users report that they have + speed problems with this; consequently, this + option, when set to zero, lets such users + prevent netrw from saving and restoring the + clipboard (the latter is done only as needed). + That means that if the clipboard is changed + (inadvertently) by normal netrw operation that + it will not be restored to its prior state. + + *g:netrw_compress* ="gzip" + Will compress marked files with this + command + + *g:Netrw_corehandler* Allows one to specify something additional + to do when handling files via netrw's + browser's "x" command (see |netrw-x|). If + present, g:Netrw_corehandler specifies + either one or more function references + (see |Funcref|). (the capital g:Netrw... + is required its holding a function reference) + + + *g:netrw_ctags* ="ctags" + The default external program used to create + tags + + *g:netrw_cursor* = 2 (default) + This option controls the use of the + |'cursorline'| (cul) and |'cursorcolumn'| + (cuc) settings by netrw: + + Value Thin-Long-Tree Wide + =0 u-cul u-cuc u-cul u-cuc + =1 u-cul u-cuc cul u-cuc + =2 cul u-cuc cul u-cuc + =3 cul u-cuc cul cuc + =4 cul cuc cul cuc + =5 U-cul U-cuc U-cul U-cuc + =6 U-cul U-cuc cul U-cuc + =7 cul U-cuc cul U-cuc + =8 cul U-cuc cul cuc + + Where + u-cul : user's |'cursorline'| initial setting used + u-cuc : user's |'cursorcolumn'| initial setting used + U-cul : user's |'cursorline'| current setting used + U-cuc : user's |'cursorcolumn'| current setting used + cul : |'cursorline'| will be locally set + cuc : |'cursorcolumn'| will be locally set + + The "initial setting" means the values of + the |'cuc'| and |'cul'| settings in effect when + netrw last saw |g:netrw_cursor| >= 5 or when + netrw was initially run. + + *g:netrw_decompress* = { ".lz4": "lz4 -d", + ".lzo": "lzop -d", + ".lz": "lzip -dk", + ".7z": "7za x", + ".001": "7za x", + ".tar.bz": "tar -xvjf", + ".tar.bz2": "tar -xvjf", + ".tbz": "tar -xvjf", + ".tbz2": "tar -xvjf", + ".tar.gz": "tar -xvzf", + ".tgz": "tar -xvzf", + ".tar.zst": "tar --use-compress-program=unzstd -xvf", + ".tzst": "tar --use-compress-program=unzstd -xvf", + ".tar": "tar -xvf", + ".zip": "unzip", + ".bz": "bunzip2 -k", + ".bz2": "bunzip2 -k", + ".gz": "gunzip -k", + ".lzma": "unlzma -T0 -k", + ".xz": "unxz -T0 -k", + ".zst": "zstd -T0 -d", + ".Z": "uncompress -k", + ".rar": "unrar x -ad", + ".tar.lzma": "tar --lzma -xvf", + ".tlz": "tar --lzma -xvf", + ".tar.xz": "tar -xvJf", + ".txz": "tar -xvJf"} + + A dictionary mapping suffices to + decompression programs. + + *g:netrw_dirhistmax* =10: controls maximum quantity of past + history. May be zero to suppress + history. + (related: |netrw-qb| |netrw-u| |netrw-U|) + + *g:netrw_dynamic_maxfilenamelen* =32: enables dynamic determination of + |g:netrw_maxfilenamelen|, which affects + local file long listing. + + *g:netrw_errorlvl* =0: error levels greater than or equal to + this are permitted to be displayed + 0: notes + 1: warnings + 2: errors + + *g:netrw_fastbrowse* =0: slow speed directory browsing; + never re-uses directory listings; + always obtains directory listings. + =1: medium speed directory browsing; + re-use directory listings only + when remote directory browsing. + (default value) + =2: fast directory browsing; + only obtains directory listings when the + directory hasn't been seen before + (or |netrw-ctrl-l| is used). + + Fast browsing retains old directory listing + buffers so that they don't need to be + re-acquired. This feature is especially + important for remote browsing. However, if + a file is introduced or deleted into or from + such directories, the old directory buffer + becomes out-of-date. One may always refresh + such a directory listing with |netrw-ctrl-l|. + This option gives the user the choice of + trading off accuracy (ie. up-to-date listing) + versus speed. + + *g:netrw_ffkeep* (default: doesn't exist) + If this variable exists and is zero, then + netrw will not do a save and restore for + |'fileformat'|. + + *g:netrw_fname_escape* =' ?&;%' + Used on filenames before remote reading/writing + + *g:netrw_ftp_browse_reject* ftp can produce a number of errors and warnings + that can show up as "directories" and "files" + in the listing. This pattern is used to + remove such embedded messages. By default its + value is: + '^total\s\+\d\+$\| + ^Trying\s\+\d\+.*$\| + ^KERBEROS_V\d rejected\| + ^Security extensions not\| + No such file\| + : connect to address [0-9a-fA-F:]* + : No route to host$' + + *g:netrw_ftp_list_cmd* options for passing along to ftp for directory + listing. Defaults: + unix or g:netrw_cygwin set: : "ls -lF" + otherwise "dir" + + + *g:netrw_ftp_sizelist_cmd* options for passing along to ftp for directory + listing, sorted by size of file. + Defaults: + unix or g:netrw_cygwin set: : "ls -slF" + otherwise "dir" + + *g:netrw_ftp_timelist_cmd* options for passing along to ftp for directory + listing, sorted by time of last modification. + Defaults: + unix or g:netrw_cygwin set: : "ls -tlF" + otherwise "dir" + + *g:netrw_glob_escape* ='[]*?`{~$' (unix) + ='[]*?`{$' (windows + These characters in directory names are + escaped before applying glob() + + *g:netrw_gx* ="" + This option controls how gx (|netrw-gx|) picks + up the text under the cursor. See |expand()| + for possibilities. + + *g:netrw_hide* Controlled by the "a" map (see |netrw-a|) + =0 : show all + =1 : show not-hidden files + =2 : show hidden files only + default: =1 + + *g:netrw_home* The home directory for where bookmarks and + history are saved (as .netrwbook and + .netrwhist). + Netrw uses |expand()| on the string. + default: stdpath("data") (see |stdpath()|) + + *g:netrw_keepdir* =1 (default) keep current directory immune from + the browsing directory. + =0 keep the current directory the same as the + browsing directory. + The current browsing directory is contained in + b:netrw_curdir (also see |netrw-cd|) + + *g:netrw_keepj* ="keepj" (default) netrw attempts to keep the + |:jumps| table unaffected. + ="" netrw will not use |:keepjumps| with + exceptions only for the + saving/restoration of position. + + *g:netrw_list_cmd* command for listing remote directories + default: (if ssh is executable) + "ssh HOSTNAME ls -FLa" + + *g:netrw_list_cmd_options* If this variable exists, then its contents are + appended to the g:netrw_list_cmd. For + example, use "2>/dev/null" to get rid of banner + messages on unix systems. + + + *g:netrw_liststyle* Set the default listing style: + = 0: thin listing (one file per line) + = 1: long listing (one file per line with time + stamp information and file size) + = 2: wide listing (multiple files in columns) + = 3: tree style listing + + *g:netrw_list_hide* comma-separated pattern list for hiding files + Patterns are regular expressions (see |regexp|) + There's some special support for git-ignore + files: you may add the output from the helper + function 'netrw_gitignore#Hide() automatically + hiding all gitignored files. + For more details see |netrw-gitignore|. + default: "" + + Examples: > + let g:netrw_list_hide= '.*\.swp$' + let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' +< + *g:netrw_localcopycmd* ="cp" Linux/Unix/MacOS/Cygwin + =expand("$COMSPEC") Windows + Copies marked files (|netrw-mf|) to target + directory (|netrw-mt|, |netrw-mc|) + + *g:netrw_localcopycmdopt* ='' Linux/Unix/MacOS/Cygwin + =' \c copy' Windows + Options for the |g:netrw_localcopycmd| + + *g:netrw_localcopydircmd* ="cp" Linux/Unix/MacOS/Cygwin + =expand("$COMSPEC") Windows + Copies directories to target directory. + (|netrw-mc|, |netrw-mt|) + + *g:netrw_localcopydircmdopt* =" -R" Linux/Unix/MacOS/Cygwin + =" /c xcopy /e /c /h/ /i /k" Windows + Options for |g:netrw_localcopydircmd| + + *g:netrw_localmkdir* ="mkdir" Linux/Unix/MacOS/Cygwin + =expand("$COMSPEC") Windows + command for making a local directory + + *g:netrw_localmkdiropt* ="" Linux/Unix/MacOS/Cygwin + =" /c mkdir" Windows + Options for |g:netrw_localmkdir| + + *g:netrw_localmovecmd* ="mv" Linux/Unix/MacOS/Cygwin + =expand("$COMSPEC") Windows + Moves marked files (|netrw-mf|) to target + directory (|netrw-mt|, |netrw-mm|) + + *g:netrw_localmovecmdopt* ="" Linux/Unix/MacOS/Cygwin + =" /c move" Windows + Options for |g:netrw_localmovecmd| + + *g:netrw_maxfilenamelen* =32 by default, selected so as to make long + listings fit on 80 column displays. + If your screen is wider, and you have file + or directory names longer than 32 bytes, + you may set this option to keep listings + columnar. + + *g:netrw_mkdir_cmd* command for making a remote directory + via ssh (also see |g:netrw_remote_mkdir|) + default: "ssh USEPORT HOSTNAME mkdir" + + *g:netrw_mousemaps* =1 (default) enables mouse buttons while + browsing to: + leftmouse : open file/directory + shift-leftmouse : mark file + middlemouse : same as P + rightmouse : remove file/directory + =0: disables mouse maps + + *g:netrw_nobeval* doesn't exist (default) + If this variable exists, then balloon + evaluation will be suppressed + (see |'ballooneval'|) + + *g:netrw_sizestyle* not defined: actual bytes (default) + ="b" : actual bytes (default) + ="h" : human-readable (ex. 5k, 4m, 3g) + uses 1000 base + ="H" : human-readable (ex. 5K, 4M, 3G) + uses 1024 base + The long listing (|netrw-i|) and query-file + maps (|netrw-qf|) will display file size + using the specified style. + + *g:netrw_usetab* if this variable exists and is non-zero, then + the map supporting shrinking/expanding a + Lexplore or netrw window will be enabled. + (see |netrw-c-tab|) + + *g:netrw_remote_mkdir* command for making a remote directory + via ftp (also see |g:netrw_mkdir_cmd|) + default: "mkdir" + + *g:netrw_retmap* if it exists and is set to one, then: + * if in a netrw-selected file, AND + * no normal-mode <2-leftmouse> mapping exists, + then the <2-leftmouse> will be mapped for easy + return to the netrw browser window. + example: click once to select and open a file, + double-click to return. + + Note that one may instead choose to: + * let g:netrw_retmap= 1, AND + * nmap YourChoice NetrwReturn + and have another mapping instead of + <2-leftmouse> to invoke the return. + + You may also use the |:Rexplore| command to do + the same thing. + + default: =0 + + *g:netrw_rm_cmd* command for removing remote files + default: "ssh USEPORT HOSTNAME rm" + + *g:netrw_rmdir_cmd* command for removing remote directories + default: "ssh USEPORT HOSTNAME rmdir" + + *g:netrw_rmf_cmd* command for removing remote softlinks + default: "ssh USEPORT HOSTNAME rm -f" + + *g:netrw_servername* use this variable to provide a name for + |netrw-ctrl-r| to use for its server. + default: "NETRWSERVER" + + *g:netrw_sort_by* sort by "name", "time", "size", or + "exten". + default: "name" + + *g:netrw_sort_direction* sorting direction: "normal" or "reverse" + default: "normal" + + *g:netrw_sort_options* sorting is done using |:sort|; this + variable's value is appended to the + sort command. Thus one may ignore case, + for example, with the following in your + .vimrc: > + let g:netrw_sort_options="i" +< default: "" + + *g:netrw_sort_sequence* when sorting by name, first sort by the + comma-separated pattern sequence. Note that + any filigree added to indicate filetypes + should be accounted for in your pattern. + default: '[\/]$,*,\.bak$,\.o$,\.h$, + \.info$,\.swp$,\.obj$' + + *g:netrw_special_syntax* If true, then certain files will be shown + using special syntax in the browser: + + netrwBak : *.bak + netrwCompress: *.gz *.bz2 *.Z *.zip + netrwCoreDump: core.\d\+ + netrwData : *.dat + netrwDoc : *.doc,*.txt,*.pdf, + *.pdf,*.docx + netrwHdr : *.h + netrwLex : *.l *.lex + netrwLib : *.a *.so *.lib *.dll + netrwMakefile: [mM]akefile *.mak + netrwObj : *.o *.obj + netrwPix : *.bmp,*.fit,*.fits,*.gif, + *.jpg,*.jpeg,*.pcx,*.ppc + *.pgm,*.png,*.psd,*.rgb + *.tif,*.xbm,*.xcf + netrwTags : tags ANmenu ANtags + netrwTilde : * + netrwTmp : tmp* *tmp + netrwYacc : *.y + + In addition, those groups mentioned in + |'suffixes'| are also added to the special + file highlighting group. + These syntax highlighting groups are linked + to netrwGray or Folded by default + (see |hl-Folded|), but one may put lines like > + hi link netrwCompress Visual +< into one's <.vimrc> to use one's own + preferences. Alternatively, one may + put such specifications into > + .vim/after/syntax/netrw.vim. +< The netrwGray highlighting is set up by + netrw when > + * netrwGray has not been previously + defined + * the gui is running +< As an example, I myself use a dark-background + colorscheme with the following in + .vim/after/syntax/netrw.vim: > + + hi netrwCompress term=NONE cterm=NONE gui=NONE ctermfg=10 guifg=green ctermbg=0 guibg=black + hi netrwData term=NONE cterm=NONE gui=NONE ctermfg=9 guifg=blue ctermbg=0 guibg=black + hi netrwHdr term=NONE cterm=NONE,italic gui=NONE guifg=SeaGreen1 + hi netrwLex term=NONE cterm=NONE,italic gui=NONE guifg=SeaGreen1 + hi netrwYacc term=NONE cterm=NONE,italic gui=NONE guifg=SeaGreen1 + hi netrwLib term=NONE cterm=NONE gui=NONE ctermfg=14 guifg=yellow + hi netrwObj term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red + hi netrwTilde term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red + hi netrwTmp term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red + hi netrwTags term=NONE cterm=NONE gui=NONE ctermfg=12 guifg=red + hi netrwDoc term=NONE cterm=NONE gui=NONE ctermfg=220 ctermbg=27 guifg=yellow2 guibg=Blue3 + hi netrwSymLink term=NONE cterm=NONE gui=NONE ctermfg=220 ctermbg=27 guifg=grey60 +< + *g:netrw_ssh_browse_reject* ssh can sometimes produce unwanted lines, + messages, banners, and whatnot that one doesn't + want masquerading as "directories" and "files". + Use this pattern to remove such embedded + messages. By default its value is: + '^total\s\+\d\+$' + + *g:netrw_ssh_cmd* One may specify an executable command + to use instead of ssh for remote actions + such as listing, file removal, etc. + default: ssh + + *g:netrw_suppress_gx_mesg* =1 : browsers sometimes produce messages + which are normally unwanted intermixed + with the page. + However, when using links, for example, + those messages are what the browser produces. + By setting this option to 0, netrw will not + suppress browser messages. + + *g:netrw_tmpfile_escape* =' &;' + escape() is applied to all temporary files + to escape these characters. + + *g:netrw_timefmt* specify format string to vim's strftime(). + The default, "%c", is "the preferred date + and time representation for the current + locale" according to my manpage entry for + strftime(); however, not all are satisfied + with it. Some alternatives: + "%a %d %b %Y %T", + " %a %Y-%m-%d %I-%M-%S %p" + default: "%c" + + *g:netrw_use_noswf* netrw normally avoids writing swapfiles + for browser buffers. However, under some + systems this apparently is causing nasty + ml_get errors to appear; if you're getting + ml_get errors, try putting + let g:netrw_use_noswf= 0 + in your .vimrc. + default: 1 + + *g:netrw_winsize* specify initial size of new windows made with + "o" (see |netrw-o|), "v" (see |netrw-v|), + |:Hexplore| or |:Vexplore|. The g:netrw_winsize + is an integer describing the percentage of the + current netrw buffer's window to be used for + the new window. + If g:netrw_winsize is less than zero, then + the absolute value of g:netrw_winsize will be + used to specify the quantity of lines or + columns for the new window. + If g:netrw_winsize is zero, then a normal + split will be made (ie. |'equalalways'| will + take effect, for example). + default: 50 (for 50%) + + *g:netrw_wiw* =1 specifies the minimum window width to use + when shrinking a netrw/Lexplore window + (see |netrw-c-tab|). + + *g:netrw_xstrlen* Controls how netrw computes string lengths, + including multi-byte characters' string + length. (thanks to N Weibull, T Mechelynck) + =0: uses Vim's built-in strlen() + =1: number of codepoints (Latin a + combining + circumflex is two codepoints) (DEFAULT) + =2: number of spacing codepoints (Latin a + + combining circumflex is one spacing + codepoint; a hard tab is one; wide and + narrow CJK are one each; etc.) + =3: virtual length (counting tabs as anything + between 1 and |'tabstop'|, wide CJK as 2 + rather than 1, Arabic alif as zero when + immediately preceded by lam, one + otherwise, etc) + + *g:NetrwTopLvlMenu* This variable specifies the top level + menu name; by default, it's "Netrw.". If + you wish to change this, do so in your + .vimrc. + +NETRW BROWSING AND OPTION INCOMPATIBILITIES *netrw-incompatible* {{{2 + +Netrw has been designed to handle user options by saving them, setting the +options to something that's compatible with netrw's needs, and then restoring +them. However, the autochdir option: > + :set acd +is problematic. Autochdir sets the current directory to that containing the +file you edit; this apparently also applies to directories. In other words, +autochdir sets the current directory to that containing the "file" (even if +that "file" is itself a directory). + +NETRW SETTINGS WINDOW *netrw-settings-window* {{{2 + +With the NetrwSettings.vim plugin, > + :NetrwSettings +will bring up a window with the many variables that netrw uses for its +settings. You may change any of their values; when you save the file, the +settings therein will be used. One may also press "?" on any of the lines for +help on what each of the variables do. + +(also see: |netrw-browser-var| |netrw-protocol| |netrw-variables|) + + +============================================================================== +OBTAINING A FILE *netrw-obtain* *netrw-O* {{{2 + +If there are no marked files: + + When browsing a remote directory, one may obtain a file under the cursor + (ie. get a copy on your local machine, but not edit it) by pressing the O + key. + +If there are marked files: + + The marked files will be obtained (ie. a copy will be transferred to your + local machine, but not set up for editing). + +Only ftp and scp are supported for this operation (but since these two are +available for browsing, that shouldn't be a problem). The status bar will +then show, on its right hand side, a message like "Obtaining filename". The +statusline will be restored after the transfer is complete. + +Netrw can also "obtain" a file using the local browser. Netrw's display +of a directory is not necessarily the same as Vim's "current directory", +unless |g:netrw_keepdir| is set to 0 in the user's <.vimrc>. One may select +a file using the local browser (by putting the cursor on it) and pressing +"O" will then "obtain" the file; ie. copy it to Vim's current directory. + +Related topics: + * To see what the current directory is, use |:pwd| + * To make the currently browsed directory the current directory, see + |netrw-cd| + * To automatically make the currently browsed directory the current + directory, see |g:netrw_keepdir|. + + *netrw-newfile* *netrw-createfile* +OPEN A NEW FILE IN NETRW'S CURRENT DIRECTORY *netrw-%* {{{2 + +To open a new file in netrw's current directory, press "%". This map +will query the user for a new filename; an empty file by that name will +be placed in the netrw's current directory (ie. b:netrw_curdir). + +If Lexplore (|netrw-:Lexplore|) is in use, the new file will be generated +in the |g:netrw_chgwin| window. + +Related topics: |netrw-d| + + +PREVIEW WINDOW *netrw-p* *netrw-preview* {{{2 + +One may use a preview window by using the "p" key when the cursor is atop the +desired filename to be previewed. The display will then split to show both +the browser (where the cursor will remain) and the file (see |:pedit|). By +default, the split will be taken horizontally; one may use vertical splitting +if one has set |g:netrw_preview| first. + +An interesting set of netrw settings is: > + + let g:netrw_preview = 1 + let g:netrw_liststyle = 3 + let g:netrw_winsize = 30 + +These will: + + 1. Make vertical splitting the default for previewing files + 2. Make the default listing style "tree" + 3. When a vertical preview window is opened, the directory listing + will use only 30% of the columns available; the rest of the window + is used for the preview window. + + Related: if you like this idea, you may also find :Lexplore + (|netrw-:Lexplore|) or |g:netrw_chgwin| of interest + +Also see: |g:netrw_chgwin| |netrw-P| |'previewwindow'| |CTRL-W_z| |:pclose| + + +PREVIOUS WINDOW *netrw-P* *netrw-prvwin* {{{2 + +To edit a file or directory under the cursor in the previously used (last +accessed) window (see :he |CTRL-W_p|), press a "P". If there's only one +window, then the one window will be horizontally split (by default). + +If there's more than one window, the previous window will be re-used on +the selected file/directory. If the previous window's associated buffer +has been modified, and there's only one window with that buffer, then +the user will be asked if they wish to save the buffer first (yes, no, or +cancel). + +Related Actions |netrw-cr| |netrw-o| |netrw-t| |netrw-v| +Associated setting variables: + |g:netrw_alto| control above/below splitting + |g:netrw_altv| control right/left splitting + |g:netrw_preview| control horizontal vs vertical splitting + |g:netrw_winsize| control initial sizing + +Also see: |g:netrw_chgwin| |netrw-p| + + +REFRESHING THE LISTING *netrw-refresh* *netrw-ctrl-l* *netrw-ctrl_l* {{{2 + +To refresh either a local or remote directory listing, press ctrl-l () or +hit the when atop the ./ directory entry in the listing. One may also +refresh a local directory by using ":e .". + + +REVERSING SORTING ORDER *netrw-r* *netrw-reverse* {{{2 + +One may toggle between normal and reverse sorting order by pressing the +"r" key. + +Related topics: |netrw-s| +Associated setting variable: |g:netrw_sort_direction| + + +RENAMING FILES OR DIRECTORIES *netrw-move* *netrw-rename* *netrw-R* {{{2 + +If there are no marked files: (see |netrw-mf|) + + Renaming files and directories involves moving the cursor to the + file/directory to be moved (renamed) and pressing "R". You will then be + queried for what you want the file/directory to be renamed to. You may + select a range of lines with the "V" command (visual selection), and then + press "R"; you will be queried for each file as to what you want it + renamed to. + +If there are marked files: (see |netrw-mf|) + + Marked files will be renamed (moved). You will be queried as above in + order to specify where you want the file/directory to be moved. + + If you answer a renaming query with a "s/frompattern/topattern/", then + subsequent files on the marked file list will be renamed by taking each + name, applying that substitute, and renaming each file to the result. + As an example : > + + mr [query: reply with *.c] + R [query: reply with s/^\(.*\)\.c$/\1.cpp/] +< + This example will mark all "*.c" files and then rename them to "*.cpp" + files. Netrw will protect you from overwriting local files without + confirmation, but not remote ones. + + The ctrl-X character has special meaning for renaming files: > + + : a single ctrl-x tells netrw to ignore the portion of the response + lying between the last '/' and the ctrl-x. + + : a pair of contiguous ctrl-x's tells netrw to ignore any + portion of the string preceding the double ctrl-x's. +< + WARNING: ~ + + Note that moving files is a dangerous operation; copies are safer. That's + because a "move" for remote files is actually a copy + delete -- and if + the copy fails and the delete succeeds you may lose the file. + Use at your own risk. + +The *g:netrw_rename_cmd* variable is used to implement remote renaming. By +default its value is: > + + ssh HOSTNAME mv +< +One may rename a block of files and directories by selecting them with +V (|linewise-visual|) when using thin style. + +See |cmdline-editing| for more on how to edit the command line; in particular, +you'll find (initiates cmdline window editing) and (uses the +command line under the cursor) useful in conjunction with the R command. + + +SELECTING SORTING STYLE *netrw-s* *netrw-sort* {{{2 + +One may select the sorting style by name, time, or (file) size. The "s" map +allows one to circulate amongst the three choices; the directory listing will +automatically be refreshed to reflect the selected style. + +Related topics: |netrw-r| |netrw-S| +Associated setting variables: |g:netrw_sort_by| |g:netrw_sort_sequence| + + +SETTING EDITING WINDOW *netrw-editwindow* *netrw-C* *netrw-:NetrwC* {{{2 + +One may select a netrw window for editing with the "C" mapping, using the +:NetrwC [win#] command, or by setting |g:netrw_chgwin| to the selected window +number. Subsequent selection of a file to edit (|netrw-cr|) will use that +window. + + * C : by itself, will select the current window holding a netrw buffer + for subsequent editing via |netrw-cr|. The C mapping is only available + while in netrw buffers. + + * [count]C : the count will be used as the window number to be used + for subsequent editing via |netrw-cr|. + + * :NetrwC will set |g:netrw_chgwin| to the current window + + * :NetrwC win# will set |g:netrw_chgwin| to the specified window + number + +Using > + let g:netrw_chgwin= -1 +will restore the default editing behavior +(ie. subsequent editing will use the current window). + +Related topics: |netrw-cr| |g:netrw_browse_split| +Associated setting variables: |g:netrw_chgwin| + + +SHRINKING OR EXPANDING A NETRW OR LEXPLORE WINDOW *netrw-c-tab* {{{2 + +The key will toggle a netrw or |:Lexplore| window's width, +but only if |g:netrw_usetab| exists and is non-zero (and, of course, +only if your terminal supports differentiating from a plain +). + + * If the current window is a netrw window, toggle its width + (between |g:netrw_wiw| and its original width) + + * Else if there is a |:Lexplore| window in the current tab, toggle + its width + + * Else bring up a |:Lexplore| window + +If |g:netrw_usetab| exists and is zero, or if there is a pre-existing mapping +for , then the will not be mapped. One may map something other +than a , too: (but you'll still need to have had |g:netrw_usetab| set). > + + nmap (whatever) NetrwShrink +< +Related topics: |:Lexplore| +Associated setting variable: |g:netrw_usetab| + + +USER SPECIFIED MAPS *netrw-usermaps* {{{1 + +One may make customized user maps. Specify a variable, |g:Netrw_UserMaps|, +to hold a |List| of lists of keymap strings and function names: > + + [["keymap-sequence","ExampleUserMapFunc"],...] +< +When netrw is setting up maps for a netrw buffer, if |g:Netrw_UserMaps| +exists, then the internal function netrw#UserMaps(islocal) is called. +This function goes through all the entries in the |g:Netrw_UserMaps| list: + + * sets up maps: > + nno KEYMAP-SEQUENCE + :call s:UserMaps(islocal,"ExampleUserMapFunc") +< * refreshes if result from that function call is the string + "refresh" + * if the result string is not "", then that string will be + executed (:exe result) + * if the result is a List, then the above two actions on results + will be taken for every string in the result List + +The user function is passed one argument; it resembles > + + fun! ExampleUserMapFunc(islocal) +< +where a:islocal is 1 if its a local-directory system call or 0 when +remote-directory system call. + + *netrw-call* *netrw-expose* *netrw-modify* +Use netrw#Expose("varname") to access netrw-internal (script-local) + variables. +Use netrw#Modify("varname",newvalue) to change netrw-internal variables. +Use netrw#Call("funcname"[,args]) to call a netrw-internal function with + specified arguments. + +Example: Get a copy of netrw's marked file list: > + + let netrwmarkfilelist= netrw#Expose("netrwmarkfilelist") +< +Example: Modify the value of netrw's marked file list: > + + call netrw#Modify("netrwmarkfilelist",[]) +< +Example: Clear netrw's marked file list via a mapping on gu > + " ExampleUserMap: {{{2 + fun! ExampleUserMap(islocal) + call netrw#Modify("netrwmarkfilelist",[]) + call netrw#Modify('netrwmarkfilemtch_{bufnr("%")}',"") + let retval= ["refresh"] + return retval + endfun + let g:Netrw_UserMaps= [["gu","ExampleUserMap"]] +< + +10. Problems and Fixes *netrw-problems* {{{1 + + (This section is likely to grow as I get feedback) + *netrw-p1* + P1. I use Windows, and my network browsing with ftp doesn't sort by {{{2 + time or size! -or- The remote system is a Windows server; why + don't I get sorts by time or size? + + Windows' ftp has a minimal support for ls (ie. it doesn't + accept sorting options). It doesn't support the -F which + gives an explanatory character (ABC/ for "ABC is a directory"). + Netrw then uses "dir" to get both its thin and long listings. + If you think your ftp does support a full-up ls, put the + following into your <.vimrc>: > + + let g:netrw_ftp_list_cmd = "ls -lF" + let g:netrw_ftp_timelist_cmd= "ls -tlF" + let g:netrw_ftp_sizelist_cmd= "ls -slF" +< + Alternatively, if you have cygwin on your Windows box, put + into your <.vimrc>: > + + let g:netrw_cygwin= 1 +< + This problem also occurs when the remote system is Windows. + In this situation, the various g:netrw_ftp_[time|size]list_cmds + are as shown above, but the remote system will not correctly + modify its listing behavior. + + + *netrw-p2* + P2. I tried rcp://user@host/ (or protocol other than ftp) and netrw {{{2 + used ssh! That wasn't what I asked for... + + Netrw has two methods for browsing remote directories: ssh + and ftp. Unless you specify ftp specifically, ssh is used. + When it comes time to do download a file (not just a directory + listing), netrw will use the given protocol to do so. + + *netrw-p3* + P3. I would like long listings to be the default. {{{2 + + Put the following statement into your |vimrc|: > + + let g:netrw_liststyle= 1 +< + Check out |netrw-browser-var| for more customizations that + you can set. + + *netrw-p4* + P4. My times come up oddly in local browsing {{{2 + + Does your system's strftime() accept the "%c" to yield dates + such as "Sun Apr 27 11:49:23 1997"? If not, do a + "man strftime" and find out what option should be used. Then + put it into your |vimrc|: > + + let g:netrw_timefmt= "%X" (where X is the option) +< + *netrw-p5* + P5. I want my current directory to track my browsing. {{{2 + How do I do that? + + Put the following line in your |vimrc|: +> + let g:netrw_keepdir= 0 +< + *netrw-p6* + P6. I use Chinese (or other non-ascii) characters in my filenames, {{{2 + and netrw (Explore, Sexplore, Hexplore, etc) doesn't display them! + + (taken from an answer provided by Wu Yongwei on the vim + mailing list) + I now see the problem. Your code page is not 936, right? Vim + seems only able to open files with names that are valid in the + current code page, as are many other applications that do not + use the Unicode version of Windows APIs. This is an OS-related + issue. You should not have such problems when the system + locale uses UTF-8, such as modern Linux distros. + + (...it is one more reason to recommend that people use utf-8!) + + *netrw-p7* + P7. I'm getting "ssh is not executable on your system" -- what do I {{{2 + do? + + (Dudley Fox) Most people I know use putty for windows ssh. It + is a free ssh/telnet application. You can read more about it + here: + + http://www.chiark.greenend.org.uk/~sgtatham/putty/ Also: + + (Marlin Unruh) This program also works for me. It's a single + executable, so he/she can copy it into the Windows\System32 + folder and create a shortcut to it. + + (Dudley Fox) You might also wish to consider plink, as it + sounds most similar to what you are looking for. plink is an + application in the putty suite. + + http://the.earth.li/~sgtatham/putty/0.58/htmldoc/Chapter7.html#plink + + (Vissale Neang) Maybe you can try OpenSSH for windows, which + can be obtained from: + + http://sshwindows.sourceforge.net/ + + It doesn't need the full Cygwin package. + + (Antoine Mechelynck) For individual Unix-like programs needed + for work in a native-Windows environment, I recommend getting + them from the GnuWin32 project on sourceforge if it has them: + + http://gnuwin32.sourceforge.net/ + + Unlike Cygwin, which sets up a Unix-like virtual machine on + top of Windows, GnuWin32 is a rewrite of Unix utilities with + Windows system calls, and its programs works quite well in the + cmd.exe "Dos box". + + (dave) Download WinSCP and use that to connect to the server. + In Preferences > Editors, set gvim as your editor: + + - Click "Add..." + - Set External Editor (adjust path as needed, include + the quotes and !.! at the end): + "c:\Program Files\Vim\vim82\gvim.exe" !.! + - Check that the filetype in the box below is + {asterisk}.{asterisk} (all files), or whatever types + you want (cec: change {asterisk} to * ; I had to + write it that way because otherwise the helptags + system thinks it's a tag) + - Make sure it's at the top of the listbox (click it, + then click "Up" if it's not) + If using the Norton Commander style, you just have to hit + to edit a file in a local copy of gvim. + + (Vit Gottwald) How to generate public/private key and save + public key it on server: > + http://www.chiark.greenend.org.uk/~sgtatham/putty/0.60/htmldoc/Chapter8.html#pubkey-gettingready + (8.3 Getting ready for public key authentication) +< + How to use a private key with "pscp": > + + http://www.chiark.greenend.org.uk/~sgtatham/putty/0.60/htmldoc/Chapter5.html + (5.2.4 Using public key authentication with PSCP) +< + (Ben Schmidt) I find the ssh included with cwRsync is + brilliant, and install cwRsync or cwRsyncServer on most + Windows systems I come across these days. I guess COPSSH, + packed by the same person, is probably even better for use as + just ssh on Windows, and probably includes sftp, etc. which I + suspect the cwRsync doesn't, though it might + + (cec) To make proper use of these suggestions above, you will + need to modify the following user-settable variables in your + .vimrc: + + |g:netrw_ssh_cmd| |g:netrw_list_cmd| |g:netrw_mkdir_cmd| + |g:netrw_rm_cmd| |g:netrw_rmdir_cmd| |g:netrw_rmf_cmd| + + The first one (|g:netrw_ssh_cmd|) is the most important; most + of the others will use the string in g:netrw_ssh_cmd by + default. + + *netrw-p8* *netrw-ml_get* + P8. I'm browsing, changing directory, and bang! ml_get errors {{{2 + appear and I have to kill vim. Any way around this? + + Normally netrw attempts to avoid writing swapfiles for + its temporary directory buffers. However, on some systems + this attempt appears to be causing ml_get errors to + appear. Please try setting |g:netrw_use_noswf| to 0 + in your <.vimrc>: > + let g:netrw_use_noswf= 0 +< + *netrw-p9* + P9. I'm being pestered with "[something] is a directory" and {{{2 + "Press ENTER or type command to continue" prompts... + + The "[something] is a directory" prompt is issued by Vim, + not by netrw, and there appears to be no way to work around + it. Coupled with the default cmdheight of 1, this message + causes the "Press ENTER..." prompt. So: read |hit-enter|; + I also suggest that you set your |'cmdheight'| to 2 (or more) in + your <.vimrc> file. + + *netrw-p10* + P10. I want to have two windows; a thin one on the left and my {{{2 + editing window on the right. How may I accomplish this? + + You probably want netrw running as in a side window. If so, you + will likely find that ":[N]Lexplore" does what you want. The + optional "[N]" allows you to select the quantity of columns you + wish the |:Lexplore|r window to start with (see |g:netrw_winsize| + for how this parameter works). + + Previous solution: + + * Put the following line in your <.vimrc>: + let g:netrw_altv = 1 + * Edit the current directory: :e . + * Select some file, press v + * Resize the windows as you wish (see |CTRL-W_<| and + |CTRL-W_>|). If you're using gvim, you can drag + the separating bar with your mouse. + * When you want a new file, use ctrl-w h to go back to the + netrw browser, select a file, then press P (see |CTRL-W_h| + and |netrw-P|). If you're using gvim, you can press + in the browser window and then press the + to select the file. + + + *netrw-p11* + P11. My directory isn't sorting correctly, or unwanted letters are {{{2 + appearing in the listed filenames, or things aren't lining + up properly in the wide listing, ... + + This may be due to an encoding problem. I myself usually use + utf-8, but really only use ascii (ie. bytes from 32-126). + Multibyte encodings use two (or more) bytes per character. + You may need to change |g:netrw_sepchr| and/or |g:netrw_xstrlen|. + + *netrw-p12* + P12. I'm a Windows + putty + ssh user, and when I attempt to {{{2 + browse, the directories are missing trailing "/"s so netrw treats + them as file transfers instead of as attempts to browse + subdirectories. How may I fix this? + + (mikeyao) If you want to use vim via ssh and putty under Windows, + try combining the use of pscp/psftp with plink. pscp/psftp will + be used to connect and plink will be used to execute commands on + the server, for example: list files and directory using 'ls'. + + These are the settings I use to do this: +> + " list files, it's the key setting, if you haven't set, + " you will get a blank buffer + let g:netrw_list_cmd = "plink HOSTNAME ls -Fa" + " if you haven't add putty directory in system path, you should + " specify scp/sftp command. For examples: + "let g:netrw_sftp_cmd = "d:\\dev\\putty\\PSFTP.exe" + "let g:netrw_scp_cmd = "d:\\dev\\putty\\PSCP.exe" +< + *netrw-p13* + P13. I would like to speed up writes using Nwrite and scp/ssh {{{2 + style connections. How? (Thomer M. Gil) + + Try using ssh's ControlMaster and ControlPath (see the ssh_config + man page) to share multiple ssh connections over a single network + connection. That cuts out the cryptographic handshake on each + file write, sometimes speeding it up by an order of magnitude. + (see http://thomer.com/howtos/netrw_ssh.html) + (included by permission) + + Add the following to your ~/.ssh/config: > + + # you change "*" to the hostname you care about + Host * + ControlMaster auto + ControlPath /tmp/%r@%h:%p + +< Then create an ssh connection to the host and leave it running: > + + ssh -N host.domain.com + +< Now remotely open a file with Vim's Netrw and enjoy the + zippiness: > + + vim scp://host.domain.com//home/user/.bashrc +< + *netrw-p14* + P14. How may I use a double-click instead of netrw's usual single {{{2 + click to open a file or directory? (Ben Fritz) + + First, disable netrw's mapping with > + let g:netrw_mousemaps= 0 +< and then create a netrw buffer only mapping in + $HOME/.vim/after/ftplugin/netrw.vim: > + nmap <2-leftmouse> +< Note that setting g:netrw_mousemaps to zero will turn off + all netrw's mouse mappings, not just the one. + (see |g:netrw_mousemaps|) + + *netrw-p15* + P15. When editing remote files (ex. :e ftp://hostname/path/file), {{{2 + under Windows I get an |E303| message complaining that its unable + to open a swap file. + + (romainl) It looks like you are starting Vim from a protected + directory. Start netrw from your $HOME or other writable + directory. + + *netrw-p16* + P16. Netrw is closing buffers on its own. {{{2 + What steps will reproduce the problem? + 1. :Explore, navigate directories, open a file + 2. :Explore, open another file + 3. Buffer opened in step 1 will be closed. o + What is the expected output? What do you see instead? + I expect both buffers to exist, but only the last one does. + + (Lance) Problem is caused by "set autochdir" in .vimrc. + (drchip) I am able to duplicate this problem with |'acd'| set. + It appears that the buffers are not exactly closed; + a ":ls!" will show them (although ":ls" does not). + + *netrw-P17* + P17. How to locally edit a file that's only available via {{{2 + another server accessible via ssh? + See http://stackoverflow.com/questions/12469645/ + "Using Vim to Remotely Edit A File on ServerB Only + Accessible From ServerA" + + *netrw-P18* + P18. How do I get numbering on in directory listings? {{{2 + With |g:netrw_bufsettings|, you can control netrw's buffer + settings; try putting > + let g:netrw_bufsettings="noma nomod nu nobl nowrap ro nornu" +< in your .vimrc. If you'd like to have relative numbering + instead, try > + let g:netrw_bufsettings="noma nomod nonu nobl nowrap ro rnu" +< + *netrw-P19* + P19. How may I have gvim start up showing a directory listing? {{{2 + Try putting the following code snippet into your .vimrc: > + augroup VimStartup + au! + au VimEnter * if expand("%") == "" && argc() == 0 && + \ (v:servername =~ 'GVIM\d*' || v:servername == "") + \ | e . | endif + augroup END +< You may use Lexplore instead of "e" if you're so inclined. + This snippet assumes that you have client-server enabled + (ie. a "huge" vim version). + + *netrw-P20* + P20. I've made a directory (or file) with an accented character, {{{2 + but netrw isn't letting me enter that directory/read that file: + + Its likely that the shell or o/s is using a different encoding + than you have vim (netrw) using. A patch to vim supporting + "systemencoding" may address this issue in the future; for + now, just have netrw use the proper encoding. For example: > + + au FileType netrw set enc=latin1 +< + *netrw-P21* + P21. I get an error message when I try to copy or move a file: {{{2 +> + **error** (netrw) tried using g:netrw_localcopycmd; it doesn't work! +< + What's wrong? + + Netrw uses several system level commands to do things (see + + |g:netrw_localcopycmd|, |g:netrw_localmovecmd|, + |g:netrw_mkdir_cmd|). + + You may need to adjust the default commands for one or more of + these commands by setting them properly in your .vimrc. Another + source of difficulty is that these commands use vim's local + directory, which may not be the same as the browsing directory + shown by netrw (see |g:netrw_keepdir|). + +============================================================================== +11. Credits *netrw-credits* {{{1 + + Vim editor by Bram Moolenaar (Thanks, Bram!) + dav support by C Campbell + fetch support by Bram Moolenaar and C Campbell + ftp support by C Campbell + http support by Bram Moolenaar + rcp + rsync support by C Campbell (suggested by Erik Warendorph) + scp support by raf + sftp support by C Campbell + + inputsecret(), BufReadCmd, BufWriteCmd contributed by C Campbell + + Jérôme Augé -- also using new buffer method with ftp+.netrc + Bram Moolenaar -- obviously vim itself, :e and v:cmdarg use, + fetch,... + Yasuhiro Matsumoto -- pointing out undo+0r problem and a solution + Erik Warendorph -- for several suggestions (g:netrw_..._cmd + variables, rsync etc) + Doug Claar -- modifications to test for success with ftp + operation + +============================================================================== +Modelines: {{{1 +vim:tw=78:ts=8:ft=help:noet:norl:fdm=marker diff --git a/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim b/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim new file mode 100644 index 0000000000..ddf4234aa2 --- /dev/null +++ b/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim @@ -0,0 +1,222 @@ +" Maintainer: Luca Saccarola +" Former Maintainer: Charles E Campbell +" Upstream: +" Copyright: Copyright (C) 1999-2021 Charles E. Campbell {{{1 +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" netrw.vim, netrwPlugin.vim, and netrwSettings.vim are provided +" *as is* and comes with no warranty of any kind, either +" expressed or implied. By using this plugin, you agree that +" in no event will the copyright holder be liable for any damages +" resulting from the use of this software. + +" Load Once: {{{1 +if &cp || exists("g:loaded_netrwPlugin") + finish +endif +let g:loaded_netrwPlugin = "v174" +let s:keepcpo = &cpo +set cpo&vim +"DechoRemOn + +" --------------------------------------------------------------------- +" Public Interface: {{{1 + +" Commands Launch/URL {{{2 +command -complete=shellcmd -nargs=1 Launch call netrw#Launch(trim()) +command -complete=file -nargs=1 Open call netrw#Open(trim()) +" " }}} +" Local Browsing Autocmds: {{{2 +augroup FileExplorer + au! + au BufLeave * if &ft != "netrw"|let w:netrw_prvfile= expand("%:p")|endif + au BufEnter * sil call s:LocalBrowse(expand("")) + au VimEnter * sil call s:VimEnter(expand("")) + if has("win32") + au BufEnter .* sil call s:LocalBrowse(expand("")) + endif +augroup END + +" Network Browsing Reading Writing: {{{2 +augroup Network + au! + au BufReadCmd file://* call netrw#FileUrlEdit(expand("")) + au BufReadCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "sil doau BufReadPost ".fnameescape(expand("")) + au FileReadCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "sil doau FileReadPost ".fnameescape(expand("")) + au BufWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "sil doau BufWritePost ".fnameescape(expand("")) + au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "sil doau FileWritePost ".fnameescape(expand("")) + try + au SourceCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) + catch /^Vim\%((\a\+)\)\=:E216/ + au SourcePre ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) + endtry +augroup END + +" Commands: :Nread, :Nwrite, :NetUserPass {{{2 +com! -count=1 -nargs=* Nread let s:svpos= winsaveview()call netrw#NetRead(,)call winrestview(s:svpos) +com! -range=% -nargs=* Nwrite let s:svpos= winsaveview(),call netrw#NetWrite()call winrestview(s:svpos) +com! -nargs=* NetUserPass call NetUserPass() +com! -nargs=* Nsource let s:svpos= winsaveview()call netrw#NetSource()call winrestview(s:svpos) +com! -nargs=? Ntree call netrw#SetTreetop(1,) + +" Commands: :Explore, :Sexplore, Hexplore, Vexplore, Lexplore {{{2 +com! -nargs=* -bar -bang -count=0 -complete=dir Explore call netrw#Explore(,0,0+0,) +com! -nargs=* -bar -bang -count=0 -complete=dir Sexplore call netrw#Explore(,1,0+0,) +com! -nargs=* -bar -bang -count=0 -complete=dir Hexplore call netrw#Explore(,1,2+0,) +com! -nargs=* -bar -bang -count=0 -complete=dir Vexplore call netrw#Explore(,1,4+0,) +com! -nargs=* -bar -count=0 -complete=dir Texplore call netrw#Explore(,0,6 ,) +com! -nargs=* -bar -bang Nexplore call netrw#Explore(-1,0,0,) +com! -nargs=* -bar -bang Pexplore call netrw#Explore(-2,0,0,) +com! -nargs=* -bar -bang -count=0 -complete=dir Lexplore call netrw#Lexplore(,0,) + +" Commands: NetrwSettings {{{2 +com! -nargs=0 NetrwSettings call netrwSettings#NetrwSettings() +com! -bang NetrwClean call netrw#Clean(0) + +" Maps: +if !exists("g:netrw_nogx") + if maparg('gx','n') == "" + if !hasmapto('NetrwBrowseX') + nmap gx NetrwBrowseX + endif + nno NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX())) + endif + if maparg('gx','x') == "" + if !hasmapto('NetrwBrowseXVis') + xmap gx NetrwBrowseXVis + endif + xno NetrwBrowseXVis :call netrw#BrowseXVis() + endif +endif +if exists("g:netrw_usetab") && g:netrw_usetab + if maparg('','n') == "" + nmap NetrwShrink + endif + nno NetrwShrink :call netrw#Shrink() +endif + +" --------------------------------------------------------------------- +" LocalBrowse: invokes netrw#LocalBrowseCheck() on directory buffers {{{2 +fun! s:LocalBrowse(dirname) + " Unfortunate interaction -- only DechoMsg debugging calls can be safely used here. + " Otherwise, the BufEnter event gets triggered when attempts to write to + " the DBG buffer are made. + + if !exists("s:vimentered") + " If s:vimentered doesn't exist, then the VimEnter event hasn't fired. It will, + " and so s:VimEnter() will then be calling this routine, but this time with s:vimentered defined. + " call Dfunc("s:LocalBrowse(dirname<".a:dirname.">) (s:vimentered doesn't exist)") + " call Dret("s:LocalBrowse") + return + endif + + " call Dfunc("s:LocalBrowse(dirname<".a:dirname.">) (s:vimentered=".s:vimentered.")") + + if has("amiga") + " The check against '' is made for the Amiga, where the empty + " string is the current directory and not checking would break + " things such as the help command. + " call Decho("(LocalBrowse) dirname<".a:dirname."> (isdirectory, amiga)") + if a:dirname != '' && isdirectory(a:dirname) + sil! call netrw#LocalBrowseCheck(a:dirname) + if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt + exe w:netrw_bannercnt + endif + endif + + elseif isdirectory(a:dirname) + " call Decho("(LocalBrowse) dirname<".a:dirname."> ft=".&ft." (isdirectory, not amiga)") + " call Dredir("LocalBrowse ft last set: ","verbose set ft") + " Jul 13, 2021: for whatever reason, preceding the following call with + " a sil! causes an unbalanced if-endif vim error + call netrw#LocalBrowseCheck(a:dirname) + if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt + exe w:netrw_bannercnt + endif + + else + " not a directory, ignore it + " call Decho("(LocalBrowse) dirname<".a:dirname."> not a directory, ignoring...") + endif + + " call Dret("s:LocalBrowse") +endfun + +" --------------------------------------------------------------------- +" s:VimEnter: after all vim startup stuff is done, this function is called. {{{2 +" Its purpose: to look over all windows and run s:LocalBrowse() on +" them, which checks if they're directories and will create a directory +" listing when appropriate. +" It also sets s:vimentered, letting s:LocalBrowse() know that s:VimEnter() +" has already been called. +fun! s:VimEnter(dirname) + " call Dfunc("s:VimEnter(dirname<".a:dirname.">) expand(%)<".expand("%").">") + if has('nvim') || v:version < 802 + " Johann Höchtl: reported that the call range... line causes an E488: Trailing characters + " error with neovim. I suspect its because neovim hasn't updated with recent + " vim patches. As is, this code will have problems with popup terminals + " instantiated before the VimEnter event runs. + " Ingo Karkat : E488 also in Vim 8.1.1602 + let curwin = winnr() + let s:vimentered = 1 + windo call s:LocalBrowse(expand("%:p")) + exe curwin."wincmd w" + else + " the following complicated expression comes courtesy of lacygoill; largely does the same thing as the windo and + " wincmd which are commented out, but avoids some side effects. Allows popup terminal before VimEnter. + let s:vimentered = 1 + call range(1, winnr('$'))->map({_, v -> win_execute(win_getid(v), 'call expand("%:p")->s:LocalBrowse()')}) + endif + " call Dret("s:VimEnter") +endfun + +" --------------------------------------------------------------------- +" NetrwStatusLine: {{{1 +fun! NetrwStatusLine() + " let g:stlmsg= "Xbufnr=".w:netrw_explore_bufnr." bufnr=".bufnr("%")." Xline#".w:netrw_explore_line." line#".line(".") + if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list") + let &stl= s:netrw_explore_stl + if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif + if exists("w:netrw_explore_line")|unlet w:netrw_explore_line|endif + return "" + else + return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen + endif +endfun + +" ------------------------------------------------------------------------ +" NetUserPass: set username and password for subsequent ftp transfer {{{1 +" Usage: :call NetUserPass() -- will prompt for userid and password +" :call NetUserPass("uid") -- will prompt for password +" :call NetUserPass("uid","password") -- sets global userid and password +fun! NetUserPass(...) + + " get/set userid + if a:0 == 0 + " call Dfunc("NetUserPass(a:0<".a:0.">)") + if !exists("g:netrw_uid") || g:netrw_uid == "" + " via prompt + let g:netrw_uid= input('Enter username: ') + endif + else " from command line + " call Dfunc("NetUserPass(a:1<".a:1.">) {") + let g:netrw_uid= a:1 + endif + + " get password + if a:0 <= 1 " via prompt + " call Decho("a:0=".a:0." case <=1:") + let g:netrw_passwd= inputsecret("Enter Password: ") + else " from command line + " call Decho("a:0=".a:0." case >1: a:2<".a:2.">") + let g:netrw_passwd=a:2 + endif + " call Dret("NetUserPass") +endfun + +" ------------------------------------------------------------------------ +" Modelines And Restoration: {{{1 +let &cpo= s:keepcpo +unlet s:keepcpo +" vim:ts=8 sts=2 sw=2 et fdm=marker diff --git a/runtime/pack/dist/opt/netrw/syntax/netrw.vim b/runtime/pack/dist/opt/netrw/syntax/netrw.vim new file mode 100644 index 0000000000..f9b2faba5d --- /dev/null +++ b/runtime/pack/dist/opt/netrw/syntax/netrw.vim @@ -0,0 +1,145 @@ +Maintainer: Luca Saccarola +" Former Maintainer: Charles E Campbell +" Upstream: +" Language: Netrw Listing Syntax + +if exists("b:current_syntax") + finish +endif + +" Directory List Syntax Highlighting: {{{1 +syn cluster NetrwGroup contains=netrwHide,netrwSortBy,netrwSortSeq,netrwQuickHelp,netrwVersion,netrwCopyTgt +syn cluster NetrwTreeGroup contains=netrwDir,netrwSymLink,netrwExe + +syn match netrwPlain "\(\S\+ \)*\S\+" contains=netrwLink,@NoSpell +syn match netrwSpecial "\%(\S\+ \)*\S\+[*|=]\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +syn match netrwDir "\.\{1,2}/" contains=netrwClassify,@NoSpell +syn match netrwDir "\%(\S\+ \)*\S\+/\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +syn match netrwSizeDate "\<\d\+\s\d\{1,2}/\d\{1,2}/\d\{4}\s" skipwhite contains=netrwDateSep,@NoSpell nextgroup=netrwTime +syn match netrwSymLink "\%(\S\+ \)*\S\+@\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +syn match netrwExe "\%(\S\+ \)*\S*[^~]\*\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4') +syn match netrwTreeBar "^\%([-+|│] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup +else +syn match netrwTreeBar "^\%([-+|] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup +endif +syn match netrwTreeBarSpace " " contained + +syn match netrwClassify "[*=|@/]\ze\%(\s\{2,}\|$\)" contained +syn match netrwDateSep "/" contained +syn match netrwTime "\d\{1,2}:\d\{2}:\d\{2}" contained contains=netrwTimeSep +syn match netrwTimeSep ":" + +syn match netrwComment '".*\%(\t\|$\)' contains=@NetrwGroup,@NoSpell +syn match netrwHide '^"\s*\(Hid\|Show\)ing:' skipwhite contains=@NoSpell nextgroup=netrwHidePat +syn match netrwSlash "/" contained +syn match netrwHidePat "[^,]\+" contained skipwhite contains=@NoSpell nextgroup=netrwHideSep +syn match netrwHideSep "," contained skipwhite nextgroup=netrwHidePat +syn match netrwSortBy "Sorted by" contained transparent skipwhite nextgroup=netrwList +syn match netrwSortSeq "Sort sequence:" contained transparent skipwhite nextgroup=netrwList +syn match netrwCopyTgt "Copy/Move Tgt:" contained transparent skipwhite nextgroup=netrwList +syn match netrwList ".*$" contained contains=netrwComma,@NoSpell +syn match netrwComma "," contained +syn region netrwQuickHelp matchgroup=Comment start="Quick Help:\s\+" end="$" contains=netrwHelpCmd,netrwQHTopic,@NoSpell keepend contained +syn match netrwHelpCmd "\S\+\ze:" contained skipwhite contains=@NoSpell nextgroup=netrwCmdSep +syn match netrwQHTopic "([a-zA-Z &]\+)" contained skipwhite +syn match netrwCmdSep ":" contained nextgroup=netrwCmdNote +syn match netrwCmdNote ".\{-}\ze " contained contains=@NoSpell +syn match netrwVersion "(netrw.*)" contained contains=@NoSpell +syn match netrwLink "-->" contained skipwhite + +" ----------------------------- +" Special filetype highlighting {{{1 +" ----------------------------- +if exists("g:netrw_special_syntax") && g:netrw_special_syntax + if exists("+suffixes") && &suffixes != "" + let suflist= join(split(&suffixes,',')) + let suflist= escape(substitute(suflist," ",'\\|','g'),'.~') + exe "syn match netrwSpecFile '\\(\\S\\+ \\)*\\S*\\(".suflist."\\)\\>' contains=netrwTreeBar,@NoSpell" + endif + syn match netrwBak "\(\S\+ \)*\S\+\.bak\>" contains=netrwTreeBar,@NoSpell + syn match netrwCompress "\(\S\+ \)*\S\+\.\%(gz\|bz2\|Z\|zip\)\>" contains=netrwTreeBar,@NoSpell + if has("unix") + syn match netrwCoreDump "\" contains=netrwTreeBar,@NoSpell + endif + syn match netrwLex "\(\S\+ \)*\S\+\.\%(l\|lex\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwYacc "\(\S\+ \)*\S\+\.y\>" contains=netrwTreeBar,@NoSpell + syn match netrwData "\(\S\+ \)*\S\+\.dat\>" contains=netrwTreeBar,@NoSpell + syn match netrwDoc "\(\S\+ \)*\S\+\.\%(doc\|txt\|pdf\|ps\|docx\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwHdr "\(\S\+ \)*\S\+\.\%(h\|hpp\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwLib "\(\S\+ \)*\S*\.\%(a\|so\|lib\|dll\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwMakeFile "\<[mM]akefile\>\|\(\S\+ \)*\S\+\.mak\>" contains=netrwTreeBar,@NoSpell + syn match netrwObj "\(\S\+ \)*\S*\.\%(o\|obj\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwPix "\c\(\S\+ \)*\S*\.\%(bmp\|fits\=\|gif\|je\=pg\|pcx\|ppc\|pgm\|png\|ppm\|psd\|rgb\|tif\|xbm\|xcf\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwTags "\<\(ANmenu\|ANtags\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwTags "\" contains=netrwTreeBar,@NoSpell + syn match netrwTilde "\(\S\+ \)*\S\+\~\*\=\>" contains=netrwTreeBar,@NoSpell + syn match netrwTmp "\\|\(\S\+ \)*\S*tmp\>" contains=netrwTreeBar,@NoSpell +endif + +" --------------------------------------------------------------------- +" Highlighting Links: {{{1 +if !exists("did_drchip_netrwlist_syntax") + let did_drchip_netrwlist_syntax= 1 + hi default link netrwClassify Function + hi default link netrwCmdSep Delimiter + hi default link netrwComment Comment + hi default link netrwDir Directory + hi default link netrwHelpCmd Function + hi default link netrwQHTopic Number + hi default link netrwHidePat Statement + hi default link netrwHideSep netrwComment + hi default link netrwList Statement + hi default link netrwVersion Identifier + hi default link netrwSymLink Question + hi default link netrwExe PreProc + hi default link netrwDateSep Delimiter + + hi default link netrwTreeBar Special + hi default link netrwTimeSep netrwDateSep + hi default link netrwComma netrwComment + hi default link netrwHide netrwComment + hi default link netrwMarkFile TabLineSel + hi default link netrwLink Special + + " special syntax highlighting (see :he g:netrw_special_syntax) + hi default link netrwCoreDump WarningMsg + hi default link netrwData Folded + hi default link netrwHdr netrwPlain + hi default link netrwLex netrwPlain + hi default link netrwLib DiffChange + hi default link netrwMakefile DiffChange + hi default link netrwYacc netrwPlain + hi default link netrwPix Special + + hi default link netrwBak netrwGray + hi default link netrwCompress netrwGray + hi default link netrwSpecFile netrwGray + hi default link netrwObj netrwGray + hi default link netrwTags netrwGray + hi default link netrwTilde netrwGray + hi default link netrwTmp netrwGray +endif + + " set up netrwGray to be understated (but not Ignore'd or Conceal'd, as those + " can be hard/impossible to read). Users may override this in a colorscheme by + " specifying netrwGray highlighting. + redir => s:netrwgray + sil hi netrwGray + redir END + if s:netrwgray !~ 'guifg' + if has("gui") && has("gui_running") + if &bg == "dark" + exe "hi netrwGray gui=NONE guifg=gray30" + else + exe "hi netrwGray gui=NONE guifg=gray70" + endif + else + hi link netrwGray Folded + endif + endif + +" Current Syntax: {{{1 +let b:current_syntax = "netrwlist" +" --------------------------------------------------------------------- +" vim: ts=8 fdm=marker diff --git a/runtime/plugin/netrwPlugin.vim b/runtime/plugin/netrwPlugin.vim index d534b36966..6d7a8660ff 100644 --- a/runtime/plugin/netrwPlugin.vim +++ b/runtime/plugin/netrwPlugin.vim @@ -1,234 +1,9 @@ -" netrwPlugin.vim: Handles file transfer and remote directory listing across a network -" PLUGIN SECTION -" Maintainer: This runtime file is looking for a new maintainer. -" Date: Sep 09, 2021 -" Last Change: -" 2024 May 08 by Vim Project: cleanup legacy Win9X checks -" 2024 Oct 27 by Vim Project: cleanup gx mapping -" 2024 Oct 28 by Vim Project: further improvements -" 2024 Oct 31 by Vim Project: use autoloaded functions -" 2024 Dec 19 by Vim Project: change style (#16248) -" Former Maintainer: Charles E Campbell -" GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim -" Copyright: Copyright (C) 1999-2021 Charles E. Campbell {{{1 -" Permission is hereby granted to use and distribute this code, -" with or without modifications, provided that this copyright -" notice is copied with it. Like anything else that's free, -" netrw.vim, netrwPlugin.vim, and netrwSettings.vim are provided -" *as is* and comes with no warranty of any kind, either -" expressed or implied. By using this plugin, you agree that -" in no event will the copyright holder be liable for any damages -" resulting from the use of this software. -" -" But be doers of the Word, and not only hearers, deluding your own selves {{{1 -" (James 1:22 RSV) -" =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -" Load Once: {{{1 -if &cp || exists("g:loaded_netrwPlugin") - finish -endif -let g:loaded_netrwPlugin = "v173" -let s:keepcpo = &cpo -set cpo&vim -"DechoRemOn - -" --------------------------------------------------------------------- -" Public Interface: {{{1 - -" Commands Launch/URL {{{2 -command -complete=shellcmd -nargs=1 Launch call netrw#Launch(trim()) -command -complete=file -nargs=1 Open call netrw#Open(trim()) -" " }}} -" Local Browsing Autocmds: {{{2 -augroup FileExplorer - au! - au BufLeave * if &ft != "netrw"|let w:netrw_prvfile= expand("%:p")|endif - au BufEnter * sil call s:LocalBrowse(expand("")) - au VimEnter * sil call s:VimEnter(expand("")) - if has("win32") - au BufEnter .* sil call s:LocalBrowse(expand("")) - endif -augroup END - -" Network Browsing Reading Writing: {{{2 -augroup Network - au! - au BufReadCmd file://* call netrw#FileUrlEdit(expand("")) - au BufReadCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "sil doau BufReadPost ".fnameescape(expand("")) - au FileReadCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "sil doau FileReadPost ".fnameescape(expand("")) - au BufWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "sil doau BufWritePost ".fnameescape(expand("")) - au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "sil doau FileWritePost ".fnameescape(expand("")) - try - au SourceCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) - catch /^Vim\%((\a\+)\)\=:E216/ - au SourcePre ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) - endtry -augroup END - -" Commands: :Nread, :Nwrite, :NetUserPass {{{2 -com! -count=1 -nargs=* Nread let s:svpos= winsaveview()call netrw#NetRead(,)call winrestview(s:svpos) -com! -range=% -nargs=* Nwrite let s:svpos= winsaveview(),call netrw#NetWrite()call winrestview(s:svpos) -com! -nargs=* NetUserPass call NetUserPass() -com! -nargs=* Nsource let s:svpos= winsaveview()call netrw#NetSource()call winrestview(s:svpos) -com! -nargs=? Ntree call netrw#SetTreetop(1,) +" Load the netrw package. -" Commands: :Explore, :Sexplore, Hexplore, Vexplore, Lexplore {{{2 -com! -nargs=* -bar -bang -count=0 -complete=dir Explore call netrw#Explore(,0,0+0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Sexplore call netrw#Explore(,1,0+0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Hexplore call netrw#Explore(,1,2+0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Vexplore call netrw#Explore(,1,4+0,) -com! -nargs=* -bar -count=0 -complete=dir Texplore call netrw#Explore(,0,6 ,) -com! -nargs=* -bar -bang Nexplore call netrw#Explore(-1,0,0,) -com! -nargs=* -bar -bang Pexplore call netrw#Explore(-2,0,0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Lexplore call netrw#Lexplore(,0,) - -" Commands: NetrwSettings {{{2 -com! -nargs=0 NetrwSettings call netrwSettings#NetrwSettings() -com! -bang NetrwClean call netrw#Clean(0) - -" Maps: -if !exists("g:netrw_nogx") - if maparg('gx','n') == "" - if !hasmapto('NetrwBrowseX') - nmap gx NetrwBrowseX - endif - nno NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX())) - endif - if maparg('gx','x') == "" - if !hasmapto('NetrwBrowseXVis') - xmap gx NetrwBrowseXVis - endif - xno NetrwBrowseXVis :call netrw#BrowseXVis() - endif -endif -if exists("g:netrw_usetab") && g:netrw_usetab - if maparg('','n') == "" - nmap NetrwShrink - endif - nno NetrwShrink :call netrw#Shrink() +if &cp || exists("g:loaded_netrw") || exists("g:loaded_netrwPlugin") + finish endif -" --------------------------------------------------------------------- -" LocalBrowse: invokes netrw#LocalBrowseCheck() on directory buffers {{{2 -fun! s:LocalBrowse(dirname) - " Unfortunate interaction -- only DechoMsg debugging calls can be safely used here. - " Otherwise, the BufEnter event gets triggered when attempts to write to - " the DBG buffer are made. - - if !exists("s:vimentered") - " If s:vimentered doesn't exist, then the VimEnter event hasn't fired. It will, - " and so s:VimEnter() will then be calling this routine, but this time with s:vimentered defined. - " call Dfunc("s:LocalBrowse(dirname<".a:dirname.">) (s:vimentered doesn't exist)") - " call Dret("s:LocalBrowse") - return - endif - - " call Dfunc("s:LocalBrowse(dirname<".a:dirname.">) (s:vimentered=".s:vimentered.")") - - if has("amiga") - " The check against '' is made for the Amiga, where the empty - " string is the current directory and not checking would break - " things such as the help command. - " call Decho("(LocalBrowse) dirname<".a:dirname."> (isdirectory, amiga)") - if a:dirname != '' && isdirectory(a:dirname) - sil! call netrw#LocalBrowseCheck(a:dirname) - if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt - exe w:netrw_bannercnt - endif - endif - - elseif isdirectory(a:dirname) - " call Decho("(LocalBrowse) dirname<".a:dirname."> ft=".&ft." (isdirectory, not amiga)") - " call Dredir("LocalBrowse ft last set: ","verbose set ft") - " Jul 13, 2021: for whatever reason, preceding the following call with - " a sil! causes an unbalanced if-endif vim error - call netrw#LocalBrowseCheck(a:dirname) - if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt - exe w:netrw_bannercnt - endif - - else - " not a directory, ignore it - " call Decho("(LocalBrowse) dirname<".a:dirname."> not a directory, ignoring...") - endif - - " call Dret("s:LocalBrowse") -endfun - -" --------------------------------------------------------------------- -" s:VimEnter: after all vim startup stuff is done, this function is called. {{{2 -" Its purpose: to look over all windows and run s:LocalBrowse() on -" them, which checks if they're directories and will create a directory -" listing when appropriate. -" It also sets s:vimentered, letting s:LocalBrowse() know that s:VimEnter() -" has already been called. -fun! s:VimEnter(dirname) - " call Dfunc("s:VimEnter(dirname<".a:dirname.">) expand(%)<".expand("%").">") - if has('nvim') || v:version < 802 - " Johann Höchtl: reported that the call range... line causes an E488: Trailing characters - " error with neovim. I suspect its because neovim hasn't updated with recent - " vim patches. As is, this code will have problems with popup terminals - " instantiated before the VimEnter event runs. - " Ingo Karkat : E488 also in Vim 8.1.1602 - let curwin = winnr() - let s:vimentered = 1 - windo call s:LocalBrowse(expand("%:p")) - exe curwin."wincmd w" - else - " the following complicated expression comes courtesy of lacygoill; largely does the same thing as the windo and - " wincmd which are commented out, but avoids some side effects. Allows popup terminal before VimEnter. - let s:vimentered = 1 - call range(1, winnr('$'))->map({_, v -> win_execute(win_getid(v), 'call expand("%:p")->s:LocalBrowse()')}) - endif - " call Dret("s:VimEnter") -endfun - -" --------------------------------------------------------------------- -" NetrwStatusLine: {{{1 -fun! NetrwStatusLine() - " let g:stlmsg= "Xbufnr=".w:netrw_explore_bufnr." bufnr=".bufnr("%")." Xline#".w:netrw_explore_line." line#".line(".") - if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list") - let &stl= s:netrw_explore_stl - if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif - if exists("w:netrw_explore_line")|unlet w:netrw_explore_line|endif - return "" - else - return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen - endif -endfun - -" ------------------------------------------------------------------------ -" NetUserPass: set username and password for subsequent ftp transfer {{{1 -" Usage: :call NetUserPass() -- will prompt for userid and password -" :call NetUserPass("uid") -- will prompt for password -" :call NetUserPass("uid","password") -- sets global userid and password -fun! NetUserPass(...) - - " get/set userid - if a:0 == 0 - " call Dfunc("NetUserPass(a:0<".a:0.">)") - if !exists("g:netrw_uid") || g:netrw_uid == "" - " via prompt - let g:netrw_uid= input('Enter username: ') - endif - else " from command line - " call Dfunc("NetUserPass(a:1<".a:1.">) {") - let g:netrw_uid= a:1 - endif - - " get password - if a:0 <= 1 " via prompt - " call Decho("a:0=".a:0." case <=1:") - let g:netrw_passwd= inputsecret("Enter Password: ") - else " from command line - " call Decho("a:0=".a:0." case >1: a:2<".a:2.">") - let g:netrw_passwd=a:2 - endif - " call Dret("NetUserPass") -endfun +packadd netrw -" ------------------------------------------------------------------------ -" Modelines And Restoration: {{{1 -let &cpo= s:keepcpo -unlet s:keepcpo -" vim:ts=8 sts=2 sw=2 et fdm=marker +" vim:ts=8 sts=2 sw=2 et diff --git a/runtime/syntax/netrw.vim b/runtime/syntax/netrw.vim deleted file mode 100644 index f5b7fdc2c6..0000000000 --- a/runtime/syntax/netrw.vim +++ /dev/null @@ -1,148 +0,0 @@ -" Language : Netrw Listing Syntax -" Maintainer: This runtime file is looking for a new maintainer. -" Former Maintainer: Charles E. Campbell -" Last Change: Nov 07, 2019 -" 2024 Feb 19 by Vim Project (announce adoption) -" Version : 20 -" --------------------------------------------------------------------- -if exists("b:current_syntax") - finish -endif - -" --------------------------------------------------------------------- -" Directory List Syntax Highlighting: {{{1 -syn cluster NetrwGroup contains=netrwHide,netrwSortBy,netrwSortSeq,netrwQuickHelp,netrwVersion,netrwCopyTgt -syn cluster NetrwTreeGroup contains=netrwDir,netrwSymLink,netrwExe - -syn match netrwPlain "\(\S\+ \)*\S\+" contains=netrwLink,@NoSpell -syn match netrwSpecial "\%(\S\+ \)*\S\+[*|=]\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -syn match netrwDir "\.\{1,2}/" contains=netrwClassify,@NoSpell -syn match netrwDir "\%(\S\+ \)*\S\+/\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -syn match netrwSizeDate "\<\d\+\s\d\{1,2}/\d\{1,2}/\d\{4}\s" skipwhite contains=netrwDateSep,@NoSpell nextgroup=netrwTime -syn match netrwSymLink "\%(\S\+ \)*\S\+@\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -syn match netrwExe "\%(\S\+ \)*\S*[^~]\*\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4') -syn match netrwTreeBar "^\%([-+|│] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup -else -syn match netrwTreeBar "^\%([-+|] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup -endif -syn match netrwTreeBarSpace " " contained - -syn match netrwClassify "[*=|@/]\ze\%(\s\{2,}\|$\)" contained -syn match netrwDateSep "/" contained -syn match netrwTime "\d\{1,2}:\d\{2}:\d\{2}" contained contains=netrwTimeSep -syn match netrwTimeSep ":" - -syn match netrwComment '".*\%(\t\|$\)' contains=@NetrwGroup,@NoSpell -syn match netrwHide '^"\s*\(Hid\|Show\)ing:' skipwhite contains=@NoSpell nextgroup=netrwHidePat -syn match netrwSlash "/" contained -syn match netrwHidePat "[^,]\+" contained skipwhite contains=@NoSpell nextgroup=netrwHideSep -syn match netrwHideSep "," contained skipwhite nextgroup=netrwHidePat -syn match netrwSortBy "Sorted by" contained transparent skipwhite nextgroup=netrwList -syn match netrwSortSeq "Sort sequence:" contained transparent skipwhite nextgroup=netrwList -syn match netrwCopyTgt "Copy/Move Tgt:" contained transparent skipwhite nextgroup=netrwList -syn match netrwList ".*$" contained contains=netrwComma,@NoSpell -syn match netrwComma "," contained -syn region netrwQuickHelp matchgroup=Comment start="Quick Help:\s\+" end="$" contains=netrwHelpCmd,netrwQHTopic,@NoSpell keepend contained -syn match netrwHelpCmd "\S\+\ze:" contained skipwhite contains=@NoSpell nextgroup=netrwCmdSep -syn match netrwQHTopic "([a-zA-Z &]\+)" contained skipwhite -syn match netrwCmdSep ":" contained nextgroup=netrwCmdNote -syn match netrwCmdNote ".\{-}\ze " contained contains=@NoSpell -syn match netrwVersion "(netrw.*)" contained contains=@NoSpell -syn match netrwLink "-->" contained skipwhite - -" ----------------------------- -" Special filetype highlighting {{{1 -" ----------------------------- -if exists("g:netrw_special_syntax") && g:netrw_special_syntax - if exists("+suffixes") && &suffixes != "" - let suflist= join(split(&suffixes,',')) - let suflist= escape(substitute(suflist," ",'\\|','g'),'.~') - exe "syn match netrwSpecFile '\\(\\S\\+ \\)*\\S*\\(".suflist."\\)\\>' contains=netrwTreeBar,@NoSpell" - endif - syn match netrwBak "\(\S\+ \)*\S\+\.bak\>" contains=netrwTreeBar,@NoSpell - syn match netrwCompress "\(\S\+ \)*\S\+\.\%(gz\|bz2\|Z\|zip\)\>" contains=netrwTreeBar,@NoSpell - if has("unix") - syn match netrwCoreDump "\" contains=netrwTreeBar,@NoSpell - endif - syn match netrwLex "\(\S\+ \)*\S\+\.\%(l\|lex\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwYacc "\(\S\+ \)*\S\+\.y\>" contains=netrwTreeBar,@NoSpell - syn match netrwData "\(\S\+ \)*\S\+\.dat\>" contains=netrwTreeBar,@NoSpell - syn match netrwDoc "\(\S\+ \)*\S\+\.\%(doc\|txt\|pdf\|ps\|docx\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwHdr "\(\S\+ \)*\S\+\.\%(h\|hpp\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwLib "\(\S\+ \)*\S*\.\%(a\|so\|lib\|dll\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwMakeFile "\<[mM]akefile\>\|\(\S\+ \)*\S\+\.mak\>" contains=netrwTreeBar,@NoSpell - syn match netrwObj "\(\S\+ \)*\S*\.\%(o\|obj\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwPix "\c\(\S\+ \)*\S*\.\%(bmp\|fits\=\|gif\|je\=pg\|pcx\|ppc\|pgm\|png\|ppm\|psd\|rgb\|tif\|xbm\|xcf\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwTags "\<\(ANmenu\|ANtags\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwTags "\" contains=netrwTreeBar,@NoSpell - syn match netrwTilde "\(\S\+ \)*\S\+\~\*\=\>" contains=netrwTreeBar,@NoSpell - syn match netrwTmp "\\|\(\S\+ \)*\S*tmp\>" contains=netrwTreeBar,@NoSpell -endif - -" --------------------------------------------------------------------- -" Highlighting Links: {{{1 -if !exists("did_drchip_netrwlist_syntax") - let did_drchip_netrwlist_syntax= 1 - hi default link netrwClassify Function - hi default link netrwCmdSep Delimiter - hi default link netrwComment Comment - hi default link netrwDir Directory - hi default link netrwHelpCmd Function - hi default link netrwQHTopic Number - hi default link netrwHidePat Statement - hi default link netrwHideSep netrwComment - hi default link netrwList Statement - hi default link netrwVersion Identifier - hi default link netrwSymLink Question - hi default link netrwExe PreProc - hi default link netrwDateSep Delimiter - - hi default link netrwTreeBar Special - hi default link netrwTimeSep netrwDateSep - hi default link netrwComma netrwComment - hi default link netrwHide netrwComment - hi default link netrwMarkFile TabLineSel - hi default link netrwLink Special - - " special syntax highlighting (see :he g:netrw_special_syntax) - hi default link netrwCoreDump WarningMsg - hi default link netrwData Folded - hi default link netrwHdr netrwPlain - hi default link netrwLex netrwPlain - hi default link netrwLib DiffChange - hi default link netrwMakefile DiffChange - hi default link netrwYacc netrwPlain - hi default link netrwPix Special - - hi default link netrwBak netrwGray - hi default link netrwCompress netrwGray - hi default link netrwSpecFile netrwGray - hi default link netrwObj netrwGray - hi default link netrwTags netrwGray - hi default link netrwTilde netrwGray - hi default link netrwTmp netrwGray -endif - - " set up netrwGray to be understated (but not Ignore'd or Conceal'd, as those - " can be hard/impossible to read). Users may override this in a colorscheme by - " specifying netrwGray highlighting. - redir => s:netrwgray - sil hi netrwGray - redir END - if s:netrwgray !~ 'guifg' - if has("gui") && has("gui_running") - if &bg == "dark" - exe "hi netrwGray gui=NONE guifg=gray30" - else - exe "hi netrwGray gui=NONE guifg=gray70" - endif - else - hi link netrwGray Folded - endif - endif - -" Current Syntax: {{{1 -let b:current_syntax = "netrwlist" -" --------------------------------------------------------------------- -" vim: ts=8 fdm=marker -- cgit From 27da6f77578c10090ad33a94ce26006a79784ee3 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sun, 12 Jan 2025 13:10:48 +0100 Subject: refactor: simplify bump_deps.lua Simplify usage and remove redundant flags and code. --- scripts/bump_deps.lua | 459 +++++++++++--------------------------------------- 1 file changed, 96 insertions(+), 363 deletions(-) diff --git a/scripts/bump_deps.lua b/scripts/bump_deps.lua index ad71da5150..333c7ea8ed 100755 --- a/scripts/bump_deps.lua +++ b/scripts/bump_deps.lua @@ -3,40 +3,45 @@ -- Usage: -- ./scripts/bump_deps.lua -h -local M = {} +assert(vim.fn.executable('sed') == 1) -local _trace = false local required_branch_prefix = 'bump-' local commit_prefix = 'build(deps): ' --- Print message -local function p(s) - vim.cmd('set verbose=1') - vim.api.nvim_echo({ { s, '' } }, false, {}) - vim.cmd('set verbose=0') -end - -local function die() - p('') +local repos = { + 'luajit/luajit', + 'libuv/libuv', + 'luvit/luv', + 'neovim/unibilium', + 'juliastrings/utf8proc', + 'tree-sitter/tree-sitter', + 'tree-sitter/tree-sitter-c', + 'tree-sitter-grammars/tree-sitter-lua', + 'tree-sitter-grammars/tree-sitter-vim', + 'neovim/tree-sitter-vimdoc', + 'tree-sitter-grammars/tree-sitter-query', + 'tree-sitter-grammars/tree-sitter-markdown', + 'bytecodealliance/wasmtime', + 'uncrustify/uncrustify', +} + +local dependency_table = {} --- @type table +for _, repo in pairs(repos) do + dependency_table[vim.fs.basename(repo)] = repo +end + +local function die(msg) + print(msg) vim.cmd('cquit 1') end -- Executes and returns the output of `cmd`, or nil on failure. -- if die_on_fail is true, process dies with die_msg on failure --- --- Prints `cmd` if `trace` is enabled. local function _run(cmd, die_on_fail, die_msg) - if _trace then - p('run: ' .. vim.inspect(cmd)) - end - local rv = vim.trim(vim.fn.system(cmd)) or '' + local rv = vim.trim(vim.system(cmd, { text = true }):wait().stdout) or '' if vim.v.shell_error ~= 0 then if die_on_fail then - if _trace then - p(rv) - end - p(die_msg) - die() + die(die_msg) end return nil end @@ -53,106 +58,19 @@ local function run_die(cmd, err_msg) return _run(cmd, true, err_msg) end -local function require_executable(cmd) - local cmd_path = run_die({ 'sh', '-c', 'command -v ' .. cmd }, cmd .. ' not found!') - run_die({ 'test', '-x', cmd_path }, cmd .. ' is not executable') -end - -local function rm_file_if_present(path_to_file) - run({ 'rm', '-f', path_to_file }) -end - -local nvim_src_dir = vim.fn.getcwd() +local nvim_src_dir = run({ 'git', 'rev-parse', '--show-toplevel' }) local deps_file = nvim_src_dir .. '/' .. 'cmake.deps/deps.txt' -local temp_dir = nvim_src_dir .. '/tmp' -run({ 'mkdir', '-p', temp_dir }) - -local function get_dependency(dependency_name) - local dependency_table = { - ['luajit'] = { - repo = 'LuaJIT/LuaJIT', - symbol = 'LUAJIT', - }, - ['libuv'] = { - repo = 'libuv/libuv', - symbol = 'LIBUV', - }, - ['luv'] = { - repo = 'luvit/luv', - symbol = 'LUV', - }, - ['unibilium'] = { - repo = 'neovim/unibilium', - symbol = 'UNIBILIUM', - }, - ['utf8proc'] = { - repo = 'JuliaStrings/utf8proc', - symbol = 'UTF8PROC', - }, - ['tree-sitter'] = { - repo = 'tree-sitter/tree-sitter', - symbol = 'TREESITTER', - }, - ['tree-sitter-c'] = { - repo = 'tree-sitter/tree-sitter-c', - symbol = 'TREESITTER_C', - }, - ['tree-sitter-lua'] = { - repo = 'tree-sitter-grammars/tree-sitter-lua', - symbol = 'TREESITTER_LUA', - }, - ['tree-sitter-vim'] = { - repo = 'tree-sitter-grammars/tree-sitter-vim', - symbol = 'TREESITTER_VIM', - }, - ['tree-sitter-vimdoc'] = { - repo = 'neovim/tree-sitter-vimdoc', - symbol = 'TREESITTER_VIMDOC', - }, - ['tree-sitter-query'] = { - repo = 'tree-sitter-grammars/tree-sitter-query', - symbol = 'TREESITTER_QUERY', - }, - ['tree-sitter-markdown'] = { - repo = 'tree-sitter-grammars/tree-sitter-markdown', - symbol = 'TREESITTER_MARKDOWN', - }, - ['wasmtime'] = { - repo = 'bytecodealliance/wasmtime', - symbol = 'WASMTIME', - }, - ['uncrustify'] = { - repo = 'uncrustify/uncrustify', - symbol = 'UNCRUSTIFY', - }, - } - local dependency = dependency_table[dependency_name] - if dependency == nil then - p('Not a dependency: ' .. dependency_name) - die() - end - dependency.name = dependency_name - return dependency -end - -local function get_gh_commit_sha(repo, ref) - require_executable('gh') - - local sha = run_die( - { 'gh', 'api', 'repos/' .. repo .. '/commits/' .. ref, '--jq', '.sha' }, - 'Failed to get commit hash from GitHub. Not a valid ref?' - ) - return sha -end +--- @param repo string +--- @param ref string local function get_archive_info(repo, ref) - require_executable('curl') + local temp_dir = os.getenv('TMPDIR') or os.getenv('TEMP') local archive_name = ref .. '.tar.gz' local archive_path = temp_dir .. '/' .. archive_name local archive_url = 'https://github.com/' .. repo .. '/archive/' .. archive_name - rm_file_if_present(archive_path) + vim.fs.rm(archive_path, { force = true }) run_die( { 'curl', '-sL', archive_url, '-o', archive_path }, 'Failed to download archive from GitHub' @@ -166,9 +84,7 @@ local function get_archive_info(repo, ref) return { url = archive_url, sha = archive_sha } end -local function write_cmakelists_line(symbol, kind, value) - require_executable('sed') - +local function update_deps_file(symbol, kind, value) run_die({ 'sed', '-i', @@ -178,290 +94,107 @@ local function write_cmakelists_line(symbol, kind, value) }, 'Failed to write ' .. deps_file) end -local function explicit_create_branch(dep) - require_executable('git') +local function ref(name, _ref) + local repo = dependency_table[name] + local symbol = string.gsub(name, 'tree%-sitter', 'treesitter'):gsub('%-', '_'):upper() - local checked_out_branch = run({ 'git', 'rev-parse', '--abbrev-ref', 'HEAD' }) - if checked_out_branch ~= 'master' then - p('Not on master!') - die() - end - run_die({ 'git', 'checkout', '-b', 'bump-' .. dep }, 'git failed to create branch') -end + run_die( + { 'git', 'diff', '--quiet', 'HEAD', '--', deps_file }, + deps_file .. ' has uncommitted changes' + ) -local function verify_branch(new_branch_suffix) - require_executable('git') + local full_repo = string.format('https://github.com/%s.git', repo) + -- `git ls-remote` returning empty string means provided ref is a regular commit hash and not a + -- tag nor HEAD. + local sha = vim.split(assert(run_die({ 'git', 'ls-remote', full_repo, _ref })), '\t')[1] + local commit_sha = sha == '' and _ref or sha + + local archive = get_archive_info(repo, commit_sha) + local comment = string.sub(_ref, 1, 9) local checked_out_branch = assert(run({ 'git', 'rev-parse', '--abbrev-ref', 'HEAD' })) if not checked_out_branch:match('^' .. required_branch_prefix) then - p( + print( "Current branch '" .. checked_out_branch .. "' doesn't seem to start with " .. required_branch_prefix ) - p('Checking out to bump-' .. new_branch_suffix) - explicit_create_branch(new_branch_suffix) + print('Checking out to bump-' .. name) + run_die({ 'git', 'checkout', '-b', 'bump-' .. name }, 'git failed to create branch') end -end - -local function update_cmakelists(dependency, archive, comment) - require_executable('git') - verify_branch(dependency.name) - - p('Updating ' .. dependency.name .. ' to ' .. archive.url .. '\n') - write_cmakelists_line(dependency.symbol, 'URL', archive.url:gsub('/', '\\/')) - write_cmakelists_line(dependency.symbol, 'SHA256', archive.sha) + print('Updating ' .. name .. ' to ' .. archive.url .. '\n') + update_deps_file(symbol, 'URL', archive.url:gsub('/', '\\/')) + update_deps_file(symbol, 'SHA256', archive.sha) run_die({ 'git', 'commit', deps_file, '-m', - commit_prefix .. 'bump ' .. dependency.name .. ' to ' .. comment, + commit_prefix .. 'bump ' .. name .. ' to ' .. comment, }, 'git failed to commit') end -local function verify_cmakelists_committed() - require_executable('git') - - run_die( - { 'git', 'diff', '--quiet', 'HEAD', '--', deps_file }, - deps_file .. ' has uncommitted changes' - ) -end - -local function warn_luv_symbol() - p('warning: ' .. get_dependency('Luv').symbol .. '_VERSION will not be updated') -end - --- return first 9 chars of commit -local function short_commit(commit) - return string.sub(commit, 1, 9) -end - --- TODO: remove hardcoded fork -local function gh_pr(pr_title, pr_body) - require_executable('gh') - - local pr_url = run_die({ - 'gh', - 'pr', - 'create', - '--title', - pr_title, - '--body', - pr_body, - }, 'Failed to create PR') - return pr_url -end - -local function find_git_remote(fork) - require_executable('git') - - local remotes = assert(run({ 'git', 'remote', '-v' })) - local git_remote = '' - for remote in remotes:gmatch('[^\r\n]+') do - local words = {} - for word in remote:gmatch('%w+') do - table.insert(words, word) - end - local match = words[1]:match('/github.com[:/]neovim/neovim/') - if fork == 'fork' then - match = not match - end - if match and words[3] == '(fetch)' then - git_remote = words[0] - break - end - end - if git_remote == '' then - git_remote = 'origin' - end - return git_remote -end - -local function create_pr(pr_title, pr_body) - require_executable('git') - - local push_first = true - - local checked_out_branch = run({ 'git', 'rev-parse', '--abbrev-ref', 'HEAD' }) - if push_first then - local push_remote = - run({ 'git', 'config', '--get', 'branch.' .. checked_out_branch .. '.pushRemote' }) - if push_remote == nil then - push_remote = run({ 'git', 'config', '--get', 'remote.pushDefault' }) - if push_remote == nil then - push_remote = - run({ 'git', 'config', '--get', 'branch.' .. checked_out_branch .. '.remote' }) - if push_remote == nil or push_remote == find_git_remote(nil) then - push_remote = find_git_remote('fork') - end - end - end - - p('Pushing to ' .. push_remote .. '/' .. checked_out_branch) - run_die({ 'git', 'push', push_remote, checked_out_branch }, 'Git failed to push') - end - - local pr_url = gh_pr(pr_title, pr_body) - p('\nCreated PR: ' .. pr_url .. '\n') -end - -function M.commit(dependency_name, commit) - local dependency = assert(get_dependency(dependency_name)) - verify_cmakelists_committed() - local commit_sha = get_gh_commit_sha(dependency.repo, commit) - if commit_sha ~= commit then - p('Not a commit: ' .. commit .. '. Did you mean version?') - die() - end - local archive = get_archive_info(dependency.repo, commit) - if dependency_name == 'Luv' then - warn_luv_symbol() - end - update_cmakelists(dependency, archive, short_commit(commit)) -end - -function M.version(dependency_name, version) - vim.validate('dependency_name', dependency_name, 'string') - vim.validate('version', version, 'string') - local dependency = assert(get_dependency(dependency_name)) - verify_cmakelists_committed() - local commit_sha = get_gh_commit_sha(dependency.repo, version) - if commit_sha == version then - p('Not a version: ' .. version .. '. Did you mean commit?') - die() - end - local archive = get_archive_info(dependency.repo, version) - if dependency_name == 'Luv' then - write_cmakelists_line(dependency.symbol, 'VERSION', version) - end - update_cmakelists(dependency, archive, version) -end - -function M.head(dependency_name) - local dependency = assert(get_dependency(dependency_name)) - verify_cmakelists_committed() - local commit_sha = get_gh_commit_sha(dependency.repo, 'HEAD') - local archive = get_archive_info(dependency.repo, commit_sha) - if dependency_name == 'Luv' then - warn_luv_symbol() - end - update_cmakelists(dependency, archive, 'HEAD - ' .. short_commit(commit_sha)) -end - -function M.create_branch(dep) - explicit_create_branch(dep) -end - -function M.submit_pr() - require_executable('git') - - verify_branch('deps') - - local nvim_remote = find_git_remote(nil) - local relevant_commit = assert(run_die({ - 'git', - 'log', - '--grep=' .. commit_prefix, - '--reverse', - "--format='%s'", - nvim_remote .. '/master..HEAD', - '-1', - }, 'Failed to fetch commits')) - - local pr_title - local pr_body - - if relevant_commit == '' then - pr_title = commit_prefix .. 'bump some dependencies' - pr_body = 'bump some dependencies' - else - relevant_commit = relevant_commit:gsub("'", '') - pr_title = relevant_commit - pr_body = relevant_commit:gsub(commit_prefix:gsub('%(', '%%('):gsub('%)', '%%)'), '') - end - pr_body = pr_body .. '\n\n(add explanations if needed)' - p(pr_title .. '\n' .. pr_body .. '\n') - create_pr(pr_title, pr_body) -end - local function usage() - local this_script = _G.arg[0]:match('[^/]*.lua$') - print(([=[ + local this_script = tostring(vim.fs.basename(_G.arg[0])) + local script_exe = './' .. this_script + local help = ([=[ Bump Nvim dependencies - Usage: nvim -l %s [options] - Bump to HEAD, tagged version, commit, or branch: - nvim -l %s --dep Luv --head - nvim -l %s --dep Luv --version 1.43.0-0 - nvim -l %s --dep Luv --commit abc123 - nvim -l %s --dep Luv --branch - Create a PR: - nvim -l %s --pr + Usage: %s [options] + Bump to HEAD, tagged version or commit: + %s luv --head + %s luv --ref 1.43.0-0 + %s luv --ref abc123 Options: - -h show this message and exit. - --pr submit pr for bumping deps. - --branch create a branch bump- from current branch. - --dep bump to a specific release or tag. + -h, --help show this message and exit. + --list list all dependencies Dependency Options: - --version bump to a specific release or tag. - --commit bump to a specific commit. - --HEAD bump to a current head. + --ref bump to a specific commit or tag. + --head bump to a current head. + ]=]):format(script_exe, script_exe, script_exe, script_exe) + print(help) +end - is one of: - "LuaJIT", "libuv", "Luv", "tree-sitter" - ]=]):format(this_script, this_script, this_script, this_script, this_script, this_script)) +local function list_deps() + local l = 'Dependencies:\n' + for k in vim.spairs(dependency_table) do + l = string.format('%s\n%s%s', l, string.rep(' ', 2), k) + end + print(l) end -local function parseargs() +do local args = {} - for i = 1, #_G.arg do - if _G.arg[i] == '-h' then + local i = 1 + while i <= #_G.arg do + if _G.arg[i] == '-h' or _G.arg[i] == '--help' then args.h = true - elseif _G.arg[i] == '--pr' then - args.pr = true - elseif _G.arg[i] == '--branch' then - args.branch = _G.arg[i + 1] - elseif _G.arg[i] == '--dep' then - args.dep = _G.arg[i + 1] - elseif _G.arg[i] == '--version' then - args.version = _G.arg[i + 1] - elseif _G.arg[i] == '--commit' then - args.commit = _G.arg[i + 1] + elseif _G.arg[i] == '--list' then + args.list = true + elseif _G.arg[i] == '--ref' then + args.ref = _G.arg[i + 1] + i = i + 1 elseif _G.arg[i] == '--head' then - args.head = true + args.ref = 'HEAD' + elseif vim.startswith(_G.arg[i], '--') then + die(string.format('Invalid argument %s\n', _G.arg[i])) + else + args.dep = _G.arg[i] end + i = i + 1 end - return args -end - -local is_main = _G.arg[0]:match('bump_deps.lua') -if is_main then - local args = parseargs() if args.h then usage() - elseif args.pr then - M.submit_pr() - elseif args.head then - M.head(args.dep) - elseif args.branch then - M.create_branch(args.dep) - elseif args.version then - M.version(args.dep, args.version) - elseif args.commit then - M.commit(args.dep, args.commit) - elseif args.pr then - M.submit_pr() + elseif args.list then + list_deps() + elseif args.ref then + ref(args.dep, args.ref) else - print('missing required arg\n') - os.exit(1) + die('missing required arg\n') end -else - return M end -- cgit From 7d04ebd43c29a5f0663e6a2a9c5b54f064247d01 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 18 Jan 2025 10:46:33 +0100 Subject: vim-patch:54cb514: runtime(sh): update syntax script - remove duplicated keywords - add bash coproc and COPROC_PID keywords https://github.com/vim/vim/commit/54cb514c9a8320d77650a63f0f7405aa8cc5b0d7 Co-authored-by: Eisuke Kawashima --- runtime/syntax/sh.vim | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim index d71a966553..67268cdfe3 100644 --- a/runtime/syntax/sh.vim +++ b/runtime/syntax/sh.vim @@ -6,6 +6,7 @@ " Last Change: 2024 Mar 04 by Vim Project " 2024 Nov 03 by Aliaksei Budavei <0x000c70 AT gmail DOT com> (improved bracket expressions, #15941) " 2025 Jan 06 add $PS0 to bashSpecialVariables (#16394) +" 2025 Jan 18 add bash coproc, remove duplicate syn keywords (#16467) " Version: 208 " Former URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH " For options and settings, please use: :help ft-sh-syntax @@ -24,7 +25,7 @@ elseif getline(1) =~ '\' elseif getline(1) =~ '\' let b:is_dash = 1 elseif !exists("g:is_kornshell") && !exists("g:is_bash") && !exists("g:is_posix") && !exists("g:is_sh") && !exists("g:is_dash") - " user did not specify which shell to use, and + " user did not specify which shell to use, and " the script itself does not specify which shell to use. FYI: /bin/sh is ambiguous. " Assuming /bin/sh is executable, and if its a link, find out what it links to. let s:shell = "" @@ -352,7 +353,7 @@ if exists("b:is_bash") ShFoldIfDoFor syn region shCase contained skipwhite skipnl matchgroup=shSnglCase start="\%(\\.\|[^#$()'" \t]\)\{-}\zs)" end=";;" end=";&" end=";;&" end="esac"me=s-1 contains=@shCaseList nextgroup=shCaseStart,shCase,shComment elseif exists("b:is_kornshell") ShFoldIfDoFor syn region shCase contained skipwhite skipnl matchgroup=shSnglCase start="\%(\\.\|[^#$()'" \t]\)\{-}\zs)" end=";;" end=";&" end="esac"me=s-1 contains=@shCaseList nextgroup=shCaseStart,shCase,shComment -else +else ShFoldIfDoFor syn region shCase contained skipwhite skipnl matchgroup=shSnglCase start="\%(\\.\|[^#$()'" \t]\)\{-}\zs)" end=";;" end="esac"me=s-1 contains=@shCaseList nextgroup=shCaseStart,shCase,shComment endif ShFoldIfDoFor syn region shCaseEsac matchgroup=shConditional start="\" end="\" contains=@shCaseEsacList @@ -405,7 +406,7 @@ syn region shCmdParenRegion matchgroup=shCmdSubRegion start="((\@!" skip='\\\\\| if exists("b:is_bash") syn cluster shCommandSubList add=bashSpecialVariables,bashStatement syn cluster shCaseList add=bashAdminStatement,bashStatement - syn keyword bashSpecialVariables contained auto_resume BASH BASH_ALIASES BASH_ALIASES BASH_ARGC BASH_ARGC BASH_ARGV BASH_ARGV BASH_CMDS BASH_CMDS BASH_COMMAND BASH_COMMAND BASH_ENV BASH_EXECUTION_STRING BASH_EXECUTION_STRING BASH_LINENO BASH_LINENO BASHOPTS BASHOPTS BASHPID BASHPID BASH_REMATCH BASH_REMATCH BASH_SOURCE BASH_SOURCE BASH_SUBSHELL BASH_SUBSHELL BASH_VERSINFO BASH_VERSION BASH_XTRACEFD BASH_XTRACEFD CDPATH COLUMNS COLUMNS COMP_CWORD COMP_CWORD COMP_KEY COMP_KEY COMP_LINE COMP_LINE COMP_POINT COMP_POINT COMPREPLY COMPREPLY COMP_TYPE COMP_TYPE COMP_WORDBREAKS COMP_WORDBREAKS COMP_WORDS COMP_WORDS COPROC COPROC DIRSTACK EMACS EMACS ENV ENV EUID FCEDIT FIGNORE FUNCNAME FUNCNAME FUNCNEST FUNCNEST GLOBIGNORE GROUPS histchars HISTCMD HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE HISTSIZE HISTTIMEFORMAT HISTTIMEFORMAT HOME HOSTFILE HOSTNAME HOSTTYPE IFS IGNOREEOF INPUTRC LANG LC_ALL LC_COLLATE LC_CTYPE LC_CTYPE LC_MESSAGES LC_NUMERIC LC_NUMERIC LINENO LINES LINES MACHTYPE MAIL MAILCHECK MAILPATH MAPFILE MAPFILE OLDPWD OPTARG OPTERR OPTIND OSTYPE PATH PIPESTATUS POSIXLY_CORRECT POSIXLY_CORRECT PPID PROMPT_COMMAND PS0 PS1 PS2 PS3 PS4 PWD RANDOM READLINE_LINE READLINE_LINE READLINE_POINT READLINE_POINT REPLY SECONDS SHELL SHELL SHELLOPTS SHLVL TIMEFORMAT TIMEOUT TMPDIR TMPDIR UID + syn keyword bashSpecialVariables contained auto_resume BASH BASH_ALIASES BASH_ARGC BASH_ARGV BASH_CMDS BASH_COMMAND BASH_ENV BASH_EXECUTION_STRING BASH_LINENO BASHOPTS BASHPID BASH_REMATCH BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION BASH_XTRACEFD CDPATH COLUMNS COMP_CWORD COMP_KEY COMP_LINE COMP_POINT COMPREPLY COMP_TYPE COMP_WORDBREAKS COMP_WORDS COPROC COPROC_PID DIRSTACK EMACS ENV EUID FCEDIT FIGNORE FUNCNAME FUNCNEST GLOBIGNORE GROUPS histchars HISTCMD HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE HISTSIZE HISTTIMEFORMAT HOME HOSTFILE HOSTNAME HOSTTYPE IFS IGNOREEOF INPUTRC LANG LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES LC_NUMERIC LINENO LINES MACHTYPE MAIL MAILCHECK MAILPATH MAPFILE OLDPWD OPTARG OPTERR OPTIND OSTYPE PATH PIPESTATUS POSIXLY_CORRECT PPID PROMPT_COMMAND PS0 PS1 PS2 PS3 PS4 PWD RANDOM READLINE_LINE READLINE_POINT REPLY SECONDS SHELL SHELLOPTS SHLVL TIMEFORMAT TIMEOUT TMPDIR UID syn keyword bashStatement chmod clear complete du egrep expr fgrep find gnufind gnugrep grep head less ls mkdir mv rm rmdir rpm sed sleep sort strip tail syn keyword bashAdminStatement daemon killall killproc nice reload restart start status stop syn keyword bashStatement command compgen @@ -540,6 +541,7 @@ if !exists("b:is_posix") endif if exists("b:is_bash") + syn keyword shFunctionKey coproc ShFoldFunctions syn region shFunctionOne matchgroup=shFunction start="^\s*[A-Za-z_0-9:][-a-zA-Z_0-9:]*\s*()\_s*{" end="}" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment ShFoldFunctions syn region shFunctionTwo matchgroup=shFunction start="\%(do\)\@!\&\<[A-Za-z_0-9:][-a-zA-Z_0-9:]*\>\s*\%(()\)\=\_s*{" end="}" contains=shFunctionKey,@shFunctionList contained skipwhite skipnl nextgroup=shFunctionStart,shQuickComment ShFoldFunctions syn region shFunctionThree matchgroup=shFunction start="^\s*[A-Za-z_0-9:][-a-zA-Z_0-9:]*\s*()\_s*(" end=")" contains=@shFunctionList skipwhite skipnl nextgroup=shFunctionStart,shQuickComment @@ -693,10 +695,10 @@ if exists("b:is_kornshell") || exists("b:is_posix") " Additional bash Keywords: {{{1 " ===================== elseif exists("b:is_bash") - syn keyword shStatement bg builtin disown export false fg getopts jobs let printf sleep true unalias + syn keyword shStatement bg builtin disown export false fg getopts jobs let printf true unalias syn keyword shStatement typeset nextgroup=shSetOption syn keyword shStatement fc hash history source suspend times type - syn keyword shStatement bind builtin caller compopt declare dirs disown enable export help logout local mapfile popd pushd readarray shopt source typeset + syn keyword shStatement bind caller compopt declare dirs enable help logout local mapfile popd pushd readarray shopt typeset else syn keyword shStatement login newgrp endif -- cgit From 954d4969c991be1a758c121be6f7d811b5e5cea1 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 18 Jan 2025 10:58:14 +0100 Subject: vim-patch:e064051: runtime(c): add new constexpr keyword to syntax file (C23) closes: vim/vim#16471 https://github.com/vim/vim/commit/e06405181a6189aa56e917c1a7e5090a33b07a8a Co-authored-by: Doug Kearns --- runtime/syntax/c.vim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim index eb56239f3d..68b6778c73 100644 --- a/runtime/syntax/c.vim +++ b/runtime/syntax/c.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: C " Maintainer: The Vim Project -" Last Change: 2025 Jan 15 +" Last Change: 2025 Jan 18 " Former Maintainer: Bram Moolenaar " Quit when a (custom) syntax file was already loaded @@ -318,6 +318,9 @@ syn keyword cStorageClass static register auto volatile extern const if !exists("c_no_c99") && !s:in_cpp_family syn keyword cStorageClass inline restrict endif +if (s:ft ==# "c" && !exists("c_no_c23")) || (s:in_cpp_family && !exists("cpp_no_cpp11")) + syn keyword cStorageClass constexpr +endif if !exists("c_no_c11") syn keyword cStorageClass _Alignas alignas syn keyword cOperator _Alignof alignof -- cgit From 51ccd12b3dbc50300e83f503426abbcb605efcea Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 18 Jan 2025 07:43:21 -0600 Subject: fix(diagnostic)!: make virtual text handler opt-in (#32079) Making this opt-out (on by default) was the wrong choice from the beginning. It is too visually noisy to be enabled by default. BREAKING CHANGE: Users must opt-in to the diagnostic virtual text handler by adding vim.diagnostic.config({ virtual_text = true }) to their config. --- runtime/doc/diagnostic.txt | 2 +- runtime/doc/news.txt | 4 +++- runtime/lua/vim/diagnostic.lua | 4 ++-- test/functional/lua/diagnostic_spec.lua | 26 ++++++++++++++++++++++++-- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 7d97a18437..d4939d8cc7 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -464,7 +464,7 @@ Lua module: vim.diagnostic *diagnostic-api* Fields: ~ • {underline}? (`boolean|vim.diagnostic.Opts.Underline|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Underline`, default: `true`) Use underline for diagnostics. - • {virtual_text}? (`boolean|vim.diagnostic.Opts.VirtualText|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualText`, default: `true`) + • {virtual_text}? (`boolean|vim.diagnostic.Opts.VirtualText|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualText`, default: `false`) Use virtual text for diagnostics. If multiple diagnostics are set for a namespace, one prefix per diagnostic + the last diagnostic message are diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 33ffeae2bb..ef97957d22 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -70,7 +70,9 @@ DIAGNOSTICS the "severity_sort" option. • Diagnostics are filtered by severity before being passed to a diagnostic handler |diagnostic-handlers|. - +• The "virtual_text" handler is disabled by default. Enable with >lua + vim.diagnostic.config({ virtual_text = true }) +< EDITOR • The order in which signs are placed was changed. Higher priority signs will diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 0939ff591e..ead75f7d51 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -70,7 +70,7 @@ end --- Use virtual text for diagnostics. If multiple diagnostics are set for a --- namespace, one prefix per diagnostic + the last diagnostic message are --- shown. ---- (default: `true`) +--- (default: `false`) --- @field virtual_text? boolean|vim.diagnostic.Opts.VirtualText|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualText --- --- Use signs for diagnostics |diagnostic-signs|. @@ -312,7 +312,7 @@ M.severity = { local global_diagnostic_options = { signs = true, underline = true, - virtual_text = true, + virtual_text = false, float = true, update_in_insert = false, severity_sort = false, diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index e52a8991ce..80f4307d5b 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -369,6 +369,9 @@ describe('vim.diagnostic', function() end) it('handles one namespace clearing highlights while the other still has highlights', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) -- 1 Error (1) -- 1 Warning (2) -- 1 Warning (2) + 1 Warning (1) @@ -443,6 +446,10 @@ describe('vim.diagnostic', function() end) it('does not display diagnostics when disabled', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + eq( { 0, 2 }, exec_lua(function() @@ -916,6 +923,10 @@ describe('vim.diagnostic', function() describe('reset()', function() it('diagnostic count is 0 and displayed diagnostics are 0 after call', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + -- 1 Error (1) -- 1 Warning (2) -- 1 Warning (2) + 1 Warning (1) @@ -2117,7 +2128,11 @@ describe('vim.diagnostic', function() end) it('can perform updates after insert_leave', function() - exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + end) + api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) @@ -2258,7 +2273,10 @@ describe('vim.diagnostic', function() end) it('can perform updates while in insert mode, if desired', function() - exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + end) api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) @@ -2292,6 +2310,10 @@ describe('vim.diagnostic', function() end) it('can set diagnostics without displaying them', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + eq( 0, exec_lua(function() -- cgit From a5b1b83a2693ffa7a5a0a22b3693d36ea60051be Mon Sep 17 00:00:00 2001 From: "林玮 (Jade Lin)" Date: Sat, 18 Jan 2025 16:40:40 +0800 Subject: fix(lua): prevent SIGSEGV when lua error is NULL in libuv_worker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Calling `xstrdup` with a NULL pointer causes a SIGSEGV if `lua_tostring` returns NULL in `nlua_luv_thread_common_cfpcall`. Crash stack trace: - `_platform_strlen` → `xstrdup` (memory.c:469) - `nlua_luv_thread_common_cfpcall` (executor.c:281) Solution: Check if `lua_tostring` returns NULL and pass NULL to `event_create` to avoid the crash. --- src/nvim/lua/executor.c | 3 +-- test/functional/lua/thread_spec.lua | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 68d3af6074..a5b48a5d5e 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -276,10 +276,9 @@ static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nres #endif } const char *error = lua_tostring(lstate, -1); - loop_schedule_deferred(&main_loop, event_create(nlua_luv_error_event, - xstrdup(error), + error != NULL ? xstrdup(error) : NULL, (void *)(intptr_t)(is_callback ? kThreadCallback : kThread))); diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index 310705fd97..8ca4bdc4f5 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -19,6 +19,26 @@ describe('thread', function() screen = Screen.new(50, 10) end) + it('handle non-string error', function() + exec_lua [[ + local thread = vim.uv.new_thread(function() + error() + end) + vim.uv.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }|*5 + {3: }| + {9:Error in luv thread:} | + {9:[NULL]} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('') + assert_alive() + end) + it('entry func is executed in protected mode', function() exec_lua [[ local thread = vim.uv.new_thread(function() -- cgit From 71507281fb86deaaa7f47460e8c7a503b46663f6 Mon Sep 17 00:00:00 2001 From: Devon Gardner Date: Sat, 18 Jan 2025 19:49:53 -0500 Subject: fix(coverity/530826): validate_opt_idx unchecked negative idx (#32081) Problem: opt_idx possible negative value used as index Solution: check opt_idx not less than zero (kOptInvalid) --- src/nvim/option.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 073a816d0c..f9eb67ff83 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -980,12 +980,12 @@ static int validate_opt_idx(win_T *win, OptIndex opt_idx, int opt_flags, uint32_ // Skip all options that are not window-local (used when showing // an already loaded buffer in a window). - if ((opt_flags & OPT_WINONLY) && (opt_idx == kOptInvalid || !option_is_window_local(opt_idx))) { + if ((opt_flags & OPT_WINONLY) && !option_is_window_local(opt_idx)) { return FAIL; } // Skip all options that are window-local (used for :vimgrep). - if ((opt_flags & OPT_NOWIN) && opt_idx != kOptInvalid && option_is_window_local(opt_idx)) { + if ((opt_flags & OPT_NOWIN) && option_is_window_local(opt_idx)) { return FAIL; } @@ -3267,7 +3267,7 @@ bool is_option_hidden(OptIndex opt_idx) /// Check if option supports a specific type. bool option_has_type(OptIndex opt_idx, OptValType type) { - return options[opt_idx].type == type; + return opt_idx != kOptInvalid && options[opt_idx].type == type; } /// Check if option supports a specific scope. -- cgit From ee54069d1d279b351e5569cb205b1ee111ec0dc5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 19 Jan 2025 22:14:52 +0800 Subject: vim-patch:8dec6c2: runtime(vim): Update base-syntax, fix is/isnot operator matching (#32100) - Match is? and isnot? operators. - Limit other comparison operators to one match modifier rather than two. closes: vim/vim#16482 https://github.com/vim/vim/commit/8dec6c2e6c2b5157334398e2e6ab7daa91999d80 Co-authored-by: Doug Kearns --- runtime/syntax/vim.vim | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index edc69b907c..4038a65440 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -185,13 +185,13 @@ Vim9 syn keyword vim9Boolean true false " Numbers {{{2 " ======= syn case ignore -syn match vimNumber '\<\d\+\%(\.\d\+\%(e[+-]\=\d\+\)\=\)\=' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment -syn match vimNumber '\<0b[01]\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment -syn match vimNumber '\<0o\=\o\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment -syn match vimNumber '\<0x\x\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment -syn match vimNumber '\<0z\>' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment -syn match vimNumber '\<0z\%(\x\x\)\+\%(\.\%(\x\x\)\+\)*' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment -syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,@vimComment +syn match vimNumber '\<\d\+\%(\.\d\+\%(e[+-]\=\d\+\)\=\)\=' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment +syn match vimNumber '\<0b[01]\+' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment +syn match vimNumber '\<0o\=\o\+' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment +syn match vimNumber '\<0x\x\+' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment +syn match vimNumber '\<0z\>' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment +syn match vimNumber '\<0z\%(\x\x\)\+\%(\.\%(\x\x\)\+\)*' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment +syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment syn case match " All vimCommands are contained by vimIsCommand. {{{2 @@ -274,10 +274,12 @@ syn keyword vimAugroupKey contained aug[roup] skipwhite nextgroup=vimAugroupBan " Operators: {{{2 " ========= syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimRegister,@vimContinue,vim9Comment,vimVar,vimBoolean,vimNull -syn match vimOper "\a\@=\|<=\|=\~\|!\~\|>\|<\|=\|!\~#\)[?#]\{0,2}" skipwhite nextgroup=vimString,vimSpecFile -syn match vimOper "\(\" skipwhite nextgroup=vimString,vimSpecFile +syn match vimOper "\a\@=\|<=\|=\~\|!\~\|>\|<\)[?#]\=" skipwhite nextgroup=vimString,vimSpecFile +syn match vimOper "\" skipwhite nextgroup=vimString,vimSpecFile +syn match vimOper "\ Date: Sun, 19 Jan 2025 00:07:47 +0900 Subject: fix(treesitter): clean up parsing queue --- runtime/lua/vim/treesitter/languagetree.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 945a2301a9..35a77f1afc 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -450,8 +450,8 @@ function LanguageTree:_run_async_callbacks(range, err, trees) for _, cb in ipairs(self._cb_queues[key]) do cb(err, trees) end - self._ranges_being_parsed[key] = false - self._cb_queues[key] = {} + self._ranges_being_parsed[key] = nil + self._cb_queues[key] = nil end --- Run an asynchronous parse, calling {on_parse} when complete. -- cgit From a6f219b06bebf5878b970bebf53db7b942fe8731 Mon Sep 17 00:00:00 2001 From: deepsghimire <70006817+deepsghimire@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:22:04 +0545 Subject: fix(log): unintuitive message for undefined $TMPDIR --- src/nvim/fileio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 031ae30d41..1c9903695e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3276,7 +3276,11 @@ static void vim_mktempdir(void) expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64); if (!os_isdir(tmp)) { if (strequal("$TMPDIR", temp_dirs[i])) { - WLOG("$TMPDIR tempdir not a directory (or does not exist): %s", tmp); + if (!os_getenv("TMPDIR")) { + WLOG("$TMPDIR is unset"); + } else { + WLOG("$TMPDIR tempdir not a directory (or does not exist): \"%s\"", tmp); + } } continue; } -- cgit From d56ba71af11c9048c9085e4f66a47947770bdb29 Mon Sep 17 00:00:00 2001 From: Yochem van Rosmalen Date: Sun, 19 Jan 2025 22:08:10 +0100 Subject: fix(lsp): document_symbol uses loclist by default #32070 Problem: Not able to open document symbols for different buffers Solution: Use the location list as default. To switch back to previous behavior (qflist): vim.lsp.buf.document_symbol({ loclist = false }) Fixes: #31832 --- runtime/doc/lsp.txt | 9 ++++----- runtime/doc/news.txt | 3 +++ runtime/lua/vim/lsp/buf.lua | 9 +++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index e8270123d7..fffd668919 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1445,12 +1445,11 @@ Lua module: vim.lsp.buf *lsp-buf* vim.lsp.buf.definition({ on_list = on_list }) vim.lsp.buf.references(nil, { on_list = on_list }) < - - If you prefer loclist instead of qflist: >lua + • {loclist}? (`boolean`) Whether to use the |location-list| or the + |quickfix| list. >lua vim.lsp.buf.definition({ loclist = true }) - vim.lsp.buf.references(nil, { loclist = true }) + vim.lsp.buf.references(nil, { loclist = false }) < - • {loclist}? (`boolean`) *vim.lsp.LocationOpts* Extends: |vim.lsp.ListOpts| @@ -1553,7 +1552,7 @@ document_highlight() *vim.lsp.buf.document_highlight()* |hl-LspReferenceWrite| document_symbol({opts}) *vim.lsp.buf.document_symbol()* - Lists all symbols in the current buffer in the quickfix window. + Lists all symbols in the current buffer in the |location-list|. Parameters: ~ • {opts} (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ef97957d22..58902abb87 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -26,6 +26,9 @@ LSP • `lsp/` runtimepath files should return a table instead of calling |vim.lsp.config()| (or assigning to `vim.lsp.config`). See |lsp-config| +• `vim.lsp.buf.document_symbol()` uses the |location-list| by default. Use + `vim.lsp.buf.document_symbol({ loclist = false })` to use the |quickfix| + list. OPTIONS diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 0acbc50003..8efc6996dd 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -252,13 +252,13 @@ end --- vim.lsp.buf.definition({ on_list = on_list }) --- vim.lsp.buf.references(nil, { on_list = on_list }) --- ``` +--- @field on_list? fun(t: vim.lsp.LocationOpts.OnList) --- ---- If you prefer loclist instead of qflist: +--- Whether to use the |location-list| or the |quickfix| list. --- ```lua --- vim.lsp.buf.definition({ loclist = true }) ---- vim.lsp.buf.references(nil, { loclist = true }) +--- vim.lsp.buf.references(nil, { loclist = false }) --- ``` ---- @field on_list? fun(t: vim.lsp.LocationOpts.OnList) --- @field loclist? boolean --- @class vim.lsp.LocationOpts.OnList @@ -796,9 +796,10 @@ function M.references(context, opts) end end ---- Lists all symbols in the current buffer in the quickfix window. +--- Lists all symbols in the current buffer in the |location-list|. --- @param opts? vim.lsp.ListOpts function M.document_symbol(opts) + opts = vim.tbl_deep_extend('keep', opts or {}, { loclist = true }) local params = { textDocument = util.make_text_document_params() } request_with_opts(ms.textDocument_documentSymbol, params, opts) end -- cgit From 5f527f24f0ea89e9071e065530cbed449507df5c Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sun, 19 Jan 2025 21:49:02 +0100 Subject: fix(lsp): don't use completion filterText if prefix is empty Follow up to https://github.com/neovim/neovim/pull/32072 If there is no prefix (e.g. at the start of word boundary or a line), it always used the `filterText` because the `match` function always returned false. --- runtime/lua/vim/lsp/completion.lua | 3 +++ test/functional/plugin/lsp/completion_spec.lua | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index 3c7d1f1469..9902c52c33 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -231,6 +231,9 @@ end ---@param prefix string ---@return boolean local function match_item_by_value(value, prefix) + if prefix == '' then + return true + end if vim.o.completeopt:find('fuzzy') ~= nil then return next(vim.fn.matchfuzzy({ value }, prefix)) ~= nil end diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 84c8f5864a..4e90c2fd1b 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -239,13 +239,18 @@ describe('vim.lsp.completion: item conversion', function() }, }, } - local expected = { + assert_completion_matches(' Date: Sun, 19 Jan 2025 17:05:32 +0100 Subject: fix(netrw): re-add missing comment marker in syntax file --- runtime/pack/dist/opt/netrw/syntax/netrw.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/pack/dist/opt/netrw/syntax/netrw.vim b/runtime/pack/dist/opt/netrw/syntax/netrw.vim index f9b2faba5d..a94af0c27a 100644 --- a/runtime/pack/dist/opt/netrw/syntax/netrw.vim +++ b/runtime/pack/dist/opt/netrw/syntax/netrw.vim @@ -1,4 +1,4 @@ -Maintainer: Luca Saccarola +" Maintainer: Luca Saccarola " Former Maintainer: Charles E Campbell " Upstream: " Language: Netrw Listing Syntax -- cgit From 4dc2e016dacfbbeaa6671a23f7ce8a4bb06c853f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 19 Jan 2025 16:51:11 +0100 Subject: vim-patch:d402ba8: runtime(netrw): upstream snapshot of v175 Relevant commits: - release: netrw v175 - fix: prevent netrw to load into the built-in terminal - fix: restore correctly cpo settings - fix(docs): mispelled original authors name - chore: move viml files to new formatting standard fixes: vim/vim#16463 closes: vim/vim#16465 https://github.com/vim/vim/commit/d402ba81e256b21e73a98ec809bd4a9482613553 Co-authored-by: Luca Saccarola --- runtime/pack/dist/opt/netrw/autoload/netrw.vim | 2 +- .../pack/dist/opt/netrw/autoload/netrwSettings.vim | 446 ++++++++++----------- .../dist/opt/netrw/autoload/netrw_gitignore.vim | 9 +- runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim | 344 ++++++++-------- runtime/pack/dist/opt/netrw/syntax/netrw.vim | 260 ++++++------ 5 files changed, 530 insertions(+), 531 deletions(-) diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw.vim b/runtime/pack/dist/opt/netrw/autoload/netrw.vim index d3f60bb8dc..ae794954ce 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim @@ -35,7 +35,7 @@ if exists("s:needspatches") endfor endif -let g:loaded_netrw = "v174" +let g:loaded_netrw = "v175" let s:keepcpo= &cpo setl cpo&vim diff --git a/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim b/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim index fd43c16c4e..884d9ce081 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrwSettings.vim @@ -1,7 +1,7 @@ " Maintainer: Luca Saccarola " Former Maintainer: Charles E Campbell " Upstream: -" Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{1 +" Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{ " Permission is hereby granted to use and distribute this code, " with or without modifications, provided that this copyright " notice is copied with it. Like anything else that's free, @@ -9,13 +9,13 @@ " warranty of any kind, either expressed or implied. By using " this plugin, you agree that in no event will the copyright " holder be liable for any damages resulting from the use -" of this software. +" of this software. }}} -" Load Once: {{{1 -if exists("g:loaded_netrwSettings") || &cp - finish +if &cp || exists("g:loaded_netrwSettings") + finish endif -let g:loaded_netrwSettings = "v174" + +let g:loaded_netrwSettings = "v175" if v:version < 700 echohl WarningMsg echo "***warning*** this version of netrwSettings needs vim 7.0" @@ -23,220 +23,220 @@ if v:version < 700 finish endif -" --------------------------------------------------------------------- -" NetrwSettings: {{{1 -fun! netrwSettings#NetrwSettings() - " this call is here largely just to insure that netrw has been loaded - call netrw#WinPath("") - if !exists("g:loaded_netrw") - echohl WarningMsg | echomsg "***sorry*** netrw needs to be loaded prior to using NetrwSettings" | echohl None - return - endif - - above wincmd s - enew - setlocal noswapfile bh=wipe - set ft=vim - file Netrw\ Settings - - " these variables have the following default effects when they don't - " exist (ie. have not been set by the user in his/her .vimrc) - if !exists("g:netrw_liststyle") - let g:netrw_liststyle= 0 - let g:netrw_list_cmd= "ssh HOSTNAME ls -FLa" - endif - if !exists("g:netrw_silent") - let g:netrw_silent= 0 - endif - if !exists("g:netrw_use_nt_rcp") - let g:netrw_use_nt_rcp= 0 - endif - if !exists("g:netrw_ftp") - let g:netrw_ftp= 0 - endif - if !exists("g:netrw_ignorenetrc") - let g:netrw_ignorenetrc= 0 - endif - - put ='+ ---------------------------------------------' - put ='+ NetrwSettings: by Charles E. Campbell' - put ='+ Press with cursor atop any line for help' - put ='+ ---------------------------------------------' - let s:netrw_settings_stop= line(".") - - put ='' - put ='+ Netrw Protocol Commands' - put = 'let g:netrw_dav_cmd = '.g:netrw_dav_cmd - put = 'let g:netrw_fetch_cmd = '.g:netrw_fetch_cmd - put = 'let g:netrw_ftp_cmd = '.g:netrw_ftp_cmd - put = 'let g:netrw_http_cmd = '.g:netrw_http_cmd - put = 'let g:netrw_rcp_cmd = '.g:netrw_rcp_cmd - put = 'let g:netrw_rsync_cmd = '.g:netrw_rsync_cmd - put = 'let g:netrw_scp_cmd = '.g:netrw_scp_cmd - put = 'let g:netrw_sftp_cmd = '.g:netrw_sftp_cmd - put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd - let s:netrw_protocol_stop= line(".") - put = '' - - put ='+Netrw Transfer Control' - put = 'let g:netrw_cygwin = '.g:netrw_cygwin - put = 'let g:netrw_ftp = '.g:netrw_ftp - put = 'let g:netrw_ftpmode = '.g:netrw_ftpmode - put = 'let g:netrw_ignorenetrc = '.g:netrw_ignorenetrc - put = 'let g:netrw_sshport = '.g:netrw_sshport - put = 'let g:netrw_silent = '.g:netrw_silent - put = 'let g:netrw_use_nt_rcp = '.g:netrw_use_nt_rcp - let s:netrw_xfer_stop= line(".") - put ='' - put ='+ Netrw Messages' - put ='let g:netrw_use_errorwindow = '.g:netrw_use_errorwindow - - put = '' - put ='+ Netrw Browser Control' - if exists("g:netrw_altfile") - put = 'let g:netrw_altfile = '.g:netrw_altfile - else - put = 'let g:netrw_altfile = 0' - endif - put = 'let g:netrw_alto = '.g:netrw_alto - put = 'let g:netrw_altv = '.g:netrw_altv - put = 'let g:netrw_banner = '.g:netrw_banner - if exists("g:netrw_bannerbackslash") - put = 'let g:netrw_bannerbackslash = '.g:netrw_bannerbackslash - else - put = '\" let g:netrw_bannerbackslash = (not defined)' - endif - put = 'let g:netrw_browse_split = '.g:netrw_browse_split - if exists("g:netrw_browsex_viewer") - put = 'let g:netrw_browsex_viewer = '.g:netrw_browsex_viewer - else - put = '\" let g:netrw_browsex_viewer = (not defined)' - endif - put = 'let g:netrw_compress = '.g:netrw_compress - if exists("g:Netrw_corehandler") - put = 'let g:Netrw_corehandler = '.g:Netrw_corehandler - else - put = '\" let g:Netrw_corehandler = (not defined)' - endif - put = 'let g:netrw_ctags = '.g:netrw_ctags - put = 'let g:netrw_cursor = '.g:netrw_cursor - let decompressline= line("$") - put = 'let g:netrw_decompress = '.string(g:netrw_decompress) - if exists("g:netrw_dynamic_maxfilenamelen") - put = 'let g:netrw_dynamic_maxfilenamelen='.g:netrw_dynamic_maxfilenamelen - else - put = '\" let g:netrw_dynamic_maxfilenamelen= (not defined)' - endif - put = 'let g:netrw_dirhistmax = '.g:netrw_dirhistmax - put = 'let g:netrw_errorlvl = '.g:netrw_errorlvl - put = 'let g:netrw_fastbrowse = '.g:netrw_fastbrowse - let fnameescline= line("$") - put = 'let g:netrw_fname_escape = '.string(g:netrw_fname_escape) - put = 'let g:netrw_ftp_browse_reject = '.g:netrw_ftp_browse_reject - put = 'let g:netrw_ftp_list_cmd = '.g:netrw_ftp_list_cmd - put = 'let g:netrw_ftp_sizelist_cmd = '.g:netrw_ftp_sizelist_cmd - put = 'let g:netrw_ftp_timelist_cmd = '.g:netrw_ftp_timelist_cmd - let globescline= line("$") - put = 'let g:netrw_glob_escape = '.string(g:netrw_glob_escape) - put = 'let g:netrw_hide = '.g:netrw_hide - if exists("g:netrw_home") - put = 'let g:netrw_home = '.g:netrw_home - else - put = '\" let g:netrw_home = (not defined)' - endif - put = 'let g:netrw_keepdir = '.g:netrw_keepdir - put = 'let g:netrw_list_cmd = '.g:netrw_list_cmd - put = 'let g:netrw_list_hide = '.g:netrw_list_hide - put = 'let g:netrw_liststyle = '.g:netrw_liststyle - put = 'let g:netrw_localcopycmd = '.g:netrw_localcopycmd - put = 'let g:netrw_localcopycmdopt = '.g:netrw_localcopycmdopt - put = 'let g:netrw_localmkdir = '.g:netrw_localmkdir - put = 'let g:netrw_localmkdiropt = '.g:netrw_localmkdiropt - put = 'let g:netrw_localmovecmd = '.g:netrw_localmovecmd - put = 'let g:netrw_localmovecmdopt = '.g:netrw_localmovecmdopt - put = 'let g:netrw_maxfilenamelen = '.g:netrw_maxfilenamelen - put = 'let g:netrw_menu = '.g:netrw_menu - put = 'let g:netrw_mousemaps = '.g:netrw_mousemaps - put = 'let g:netrw_mkdir_cmd = '.g:netrw_mkdir_cmd - if exists("g:netrw_nobeval") - put = 'let g:netrw_nobeval = '.g:netrw_nobeval - else - put = '\" let g:netrw_nobeval = (not defined)' - endif - put = 'let g:netrw_remote_mkdir = '.g:netrw_remote_mkdir - put = 'let g:netrw_preview = '.g:netrw_preview - put = 'let g:netrw_rename_cmd = '.g:netrw_rename_cmd - put = 'let g:netrw_retmap = '.g:netrw_retmap - put = 'let g:netrw_rm_cmd = '.g:netrw_rm_cmd - put = 'let g:netrw_rmdir_cmd = '.g:netrw_rmdir_cmd - put = 'let g:netrw_rmf_cmd = '.g:netrw_rmf_cmd - put = 'let g:netrw_sort_by = '.g:netrw_sort_by - put = 'let g:netrw_sort_direction = '.g:netrw_sort_direction - put = 'let g:netrw_sort_options = '.g:netrw_sort_options - put = 'let g:netrw_sort_sequence = '.g:netrw_sort_sequence - put = 'let g:netrw_servername = '.g:netrw_servername - put = 'let g:netrw_special_syntax = '.g:netrw_special_syntax - put = 'let g:netrw_ssh_browse_reject = '.g:netrw_ssh_browse_reject - put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd - put = 'let g:netrw_scpport = '.g:netrw_scpport - put = 'let g:netrw_sepchr = '.g:netrw_sepchr - put = 'let g:netrw_sshport = '.g:netrw_sshport - put = 'let g:netrw_timefmt = '.g:netrw_timefmt - let tmpfileescline= line("$") - put ='let g:netrw_tmpfile_escape...' - put = 'let g:netrw_use_noswf = '.g:netrw_use_noswf - put = 'let g:netrw_xstrlen = '.g:netrw_xstrlen - put = 'let g:netrw_winsize = '.g:netrw_winsize - - put ='' - put ='+ For help, place cursor on line and press ' - - 1d - silent %s/^+/"/e - res 99 - silent %s/= \([^0-9].*\)$/= '\1'/e - silent %s/= $/= ''/e - 1 - - call setline(decompressline,"let g:netrw_decompress = ".substitute(string(g:netrw_decompress),"^'\\(.*\\)'$",'\1','')) - call setline(fnameescline, "let g:netrw_fname_escape = '".escape(g:netrw_fname_escape,"'")."'") - call setline(globescline, "let g:netrw_glob_escape = '".escape(g:netrw_glob_escape,"'")."'") - call setline(tmpfileescline,"let g:netrw_tmpfile_escape = '".escape(g:netrw_tmpfile_escape,"'")."'") - - set nomod - - nmap :call NetrwSettingHelp() - nnoremap :call NetrwSettingHelp() - let tmpfile= tempname() - exe 'au BufWriteCmd Netrw\ Settings silent w! '.tmpfile.'|so '.tmpfile.'|call delete("'.tmpfile.'")|set nomod' -endfun - -" --------------------------------------------------------------------- -" NetrwSettingHelp: {{{2 -fun! NetrwSettingHelp() -" call Dfunc("NetrwSettingHelp()") - let curline = getline(".") - if curline =~ '=' - let varhelp = substitute(curline,'^\s*let ','','e') - let varhelp = substitute(varhelp,'\s*=.*$','','e') -" call Decho("trying help ".varhelp) - try - exe "he ".varhelp - catch /^Vim\%((\a\+)\)\=:E149/ - echo "***sorry*** no help available for <".varhelp.">" - endtry - elseif line(".") < s:netrw_settings_stop - he netrw-settings - elseif line(".") < s:netrw_protocol_stop - he netrw-externapp - elseif line(".") < s:netrw_xfer_stop - he netrw-variables - else - he netrw-browse-var - endif -" call Dret("NetrwSettingHelp") -endfun - -" --------------------------------------------------------------------- -" Modelines: {{{1 -" vim:ts=8 fdm=marker +" NetrwSettings: {{{ + +function! netrwSettings#NetrwSettings() + " this call is here largely just to insure that netrw has been loaded + call netrw#WinPath("") + if !exists("g:loaded_netrw") + echohl WarningMsg + echomsg "***sorry*** netrw needs to be loaded prior to using NetrwSettings" + echohl None + return + endif + + above wincmd s + enew + setlocal noswapfile bh=wipe + set ft=vim + file Netrw\ Settings + + " these variables have the following default effects when they don't + " exist (ie. have not been set by the user in his/her .vimrc) + if !exists("g:netrw_liststyle") + let g:netrw_liststyle= 0 + let g:netrw_list_cmd= "ssh HOSTNAME ls -FLa" + endif + if !exists("g:netrw_silent") + let g:netrw_silent= 0 + endif + if !exists("g:netrw_use_nt_rcp") + let g:netrw_use_nt_rcp= 0 + endif + if !exists("g:netrw_ftp") + let g:netrw_ftp= 0 + endif + if !exists("g:netrw_ignorenetrc") + let g:netrw_ignorenetrc= 0 + endif + + put ='+ ---------------------------------------------' + put ='+ NetrwSettings: by Charles E. Campbell' + put ='+ Press with cursor atop any line for help' + put ='+ ---------------------------------------------' + let s:netrw_settings_stop= line(".") + + put ='' + put ='+ Netrw Protocol Commands' + put = 'let g:netrw_dav_cmd = '.g:netrw_dav_cmd + put = 'let g:netrw_fetch_cmd = '.g:netrw_fetch_cmd + put = 'let g:netrw_ftp_cmd = '.g:netrw_ftp_cmd + put = 'let g:netrw_http_cmd = '.g:netrw_http_cmd + put = 'let g:netrw_rcp_cmd = '.g:netrw_rcp_cmd + put = 'let g:netrw_rsync_cmd = '.g:netrw_rsync_cmd + put = 'let g:netrw_scp_cmd = '.g:netrw_scp_cmd + put = 'let g:netrw_sftp_cmd = '.g:netrw_sftp_cmd + put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd + let s:netrw_protocol_stop= line(".") + put = '' + + put ='+Netrw Transfer Control' + put = 'let g:netrw_cygwin = '.g:netrw_cygwin + put = 'let g:netrw_ftp = '.g:netrw_ftp + put = 'let g:netrw_ftpmode = '.g:netrw_ftpmode + put = 'let g:netrw_ignorenetrc = '.g:netrw_ignorenetrc + put = 'let g:netrw_sshport = '.g:netrw_sshport + put = 'let g:netrw_silent = '.g:netrw_silent + put = 'let g:netrw_use_nt_rcp = '.g:netrw_use_nt_rcp + let s:netrw_xfer_stop= line(".") + put ='' + put ='+ Netrw Messages' + put ='let g:netrw_use_errorwindow = '.g:netrw_use_errorwindow + + put = '' + put ='+ Netrw Browser Control' + if exists("g:netrw_altfile") + put = 'let g:netrw_altfile = '.g:netrw_altfile + else + put = 'let g:netrw_altfile = 0' + endif + put = 'let g:netrw_alto = '.g:netrw_alto + put = 'let g:netrw_altv = '.g:netrw_altv + put = 'let g:netrw_banner = '.g:netrw_banner + if exists("g:netrw_bannerbackslash") + put = 'let g:netrw_bannerbackslash = '.g:netrw_bannerbackslash + else + put = '\" let g:netrw_bannerbackslash = (not defined)' + endif + put = 'let g:netrw_browse_split = '.g:netrw_browse_split + if exists("g:netrw_browsex_viewer") + put = 'let g:netrw_browsex_viewer = '.g:netrw_browsex_viewer + else + put = '\" let g:netrw_browsex_viewer = (not defined)' + endif + put = 'let g:netrw_compress = '.g:netrw_compress + if exists("g:Netrw_corehandler") + put = 'let g:Netrw_corehandler = '.g:Netrw_corehandler + else + put = '\" let g:Netrw_corehandler = (not defined)' + endif + put = 'let g:netrw_ctags = '.g:netrw_ctags + put = 'let g:netrw_cursor = '.g:netrw_cursor + let decompressline= line("$") + put = 'let g:netrw_decompress = '.string(g:netrw_decompress) + if exists("g:netrw_dynamic_maxfilenamelen") + put = 'let g:netrw_dynamic_maxfilenamelen='.g:netrw_dynamic_maxfilenamelen + else + put = '\" let g:netrw_dynamic_maxfilenamelen= (not defined)' + endif + put = 'let g:netrw_dirhistmax = '.g:netrw_dirhistmax + put = 'let g:netrw_errorlvl = '.g:netrw_errorlvl + put = 'let g:netrw_fastbrowse = '.g:netrw_fastbrowse + let fnameescline= line("$") + put = 'let g:netrw_fname_escape = '.string(g:netrw_fname_escape) + put = 'let g:netrw_ftp_browse_reject = '.g:netrw_ftp_browse_reject + put = 'let g:netrw_ftp_list_cmd = '.g:netrw_ftp_list_cmd + put = 'let g:netrw_ftp_sizelist_cmd = '.g:netrw_ftp_sizelist_cmd + put = 'let g:netrw_ftp_timelist_cmd = '.g:netrw_ftp_timelist_cmd + let globescline= line("$") + put = 'let g:netrw_glob_escape = '.string(g:netrw_glob_escape) + put = 'let g:netrw_hide = '.g:netrw_hide + if exists("g:netrw_home") + put = 'let g:netrw_home = '.g:netrw_home + else + put = '\" let g:netrw_home = (not defined)' + endif + put = 'let g:netrw_keepdir = '.g:netrw_keepdir + put = 'let g:netrw_list_cmd = '.g:netrw_list_cmd + put = 'let g:netrw_list_hide = '.g:netrw_list_hide + put = 'let g:netrw_liststyle = '.g:netrw_liststyle + put = 'let g:netrw_localcopycmd = '.g:netrw_localcopycmd + put = 'let g:netrw_localcopycmdopt = '.g:netrw_localcopycmdopt + put = 'let g:netrw_localmkdir = '.g:netrw_localmkdir + put = 'let g:netrw_localmkdiropt = '.g:netrw_localmkdiropt + put = 'let g:netrw_localmovecmd = '.g:netrw_localmovecmd + put = 'let g:netrw_localmovecmdopt = '.g:netrw_localmovecmdopt + put = 'let g:netrw_maxfilenamelen = '.g:netrw_maxfilenamelen + put = 'let g:netrw_menu = '.g:netrw_menu + put = 'let g:netrw_mousemaps = '.g:netrw_mousemaps + put = 'let g:netrw_mkdir_cmd = '.g:netrw_mkdir_cmd + if exists("g:netrw_nobeval") + put = 'let g:netrw_nobeval = '.g:netrw_nobeval + else + put = '\" let g:netrw_nobeval = (not defined)' + endif + put = 'let g:netrw_remote_mkdir = '.g:netrw_remote_mkdir + put = 'let g:netrw_preview = '.g:netrw_preview + put = 'let g:netrw_rename_cmd = '.g:netrw_rename_cmd + put = 'let g:netrw_retmap = '.g:netrw_retmap + put = 'let g:netrw_rm_cmd = '.g:netrw_rm_cmd + put = 'let g:netrw_rmdir_cmd = '.g:netrw_rmdir_cmd + put = 'let g:netrw_rmf_cmd = '.g:netrw_rmf_cmd + put = 'let g:netrw_sort_by = '.g:netrw_sort_by + put = 'let g:netrw_sort_direction = '.g:netrw_sort_direction + put = 'let g:netrw_sort_options = '.g:netrw_sort_options + put = 'let g:netrw_sort_sequence = '.g:netrw_sort_sequence + put = 'let g:netrw_servername = '.g:netrw_servername + put = 'let g:netrw_special_syntax = '.g:netrw_special_syntax + put = 'let g:netrw_ssh_browse_reject = '.g:netrw_ssh_browse_reject + put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd + put = 'let g:netrw_scpport = '.g:netrw_scpport + put = 'let g:netrw_sepchr = '.g:netrw_sepchr + put = 'let g:netrw_sshport = '.g:netrw_sshport + put = 'let g:netrw_timefmt = '.g:netrw_timefmt + let tmpfileescline= line("$") + put ='let g:netrw_tmpfile_escape...' + put = 'let g:netrw_use_noswf = '.g:netrw_use_noswf + put = 'let g:netrw_xstrlen = '.g:netrw_xstrlen + put = 'let g:netrw_winsize = '.g:netrw_winsize + + put ='' + put ='+ For help, place cursor on line and press ' + + 1d + silent %s/^+/"/e + res 99 + silent %s/= \([^0-9].*\)$/= '\1'/e + silent %s/= $/= ''/e + 1 + + call setline(decompressline, "let g:netrw_decompress = ".substitute(string(g:netrw_decompress),"^'\\(.*\\)'$",'\1','')) + call setline(fnameescline, "let g:netrw_fname_escape = '".escape(g:netrw_fname_escape,"'")."'") + call setline(globescline, "let g:netrw_glob_escape = '".escape(g:netrw_glob_escape,"'")."'") + call setline(tmpfileescline, "let g:netrw_tmpfile_escape = '".escape(g:netrw_tmpfile_escape,"'")."'") + + set nomod + + nmap :call NetrwSettingHelp() + nnoremap :call NetrwSettingHelp() + let tmpfile= tempname() + exe 'au BufWriteCmd Netrw\ Settings silent w! '.tmpfile.'|so '.tmpfile.'|call delete("'.tmpfile.'")|set nomod' +endfunction + +" }}} +" NetrwSettingHelp: {{{ + +function! NetrwSettingHelp() + let curline = getline(".") + if curline =~ '=' + let varhelp = substitute(curline,'^\s*let ','','e') + let varhelp = substitute(varhelp,'\s*=.*$','','e') + try + exe "he ".varhelp + catch /^Vim\%((\a\+)\)\=:E149/ + echo "***sorry*** no help available for <".varhelp.">" + endtry + elseif line(".") < s:netrw_settings_stop + he netrw-settings + elseif line(".") < s:netrw_protocol_stop + he netrw-externapp + elseif line(".") < s:netrw_xfer_stop + he netrw-variables + else + he netrw-browse-var + endif +endfunction + +" }}} + +" vim:ts=8 sts=4 sw=4 et fdm=marker diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim b/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim index 6c1d8582b8..c76d28db04 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim @@ -12,7 +12,7 @@ " let g:netrw_list_hide = netrw_gitignore#Hide() " let g:netrw_list_hide = netrw_gitignore#Hide() . 'more,hide,patterns' " -" Copyright: Copyright (C) 2013 Bruno Sutic {{{1 +" Copyright: Copyright (C) 2013 Bruno Sutic {{{ " Permission is hereby granted to use and distribute this code, " with or without modifications, provided that this copyright " notice is copied with it. Like anything else that's free, @@ -20,7 +20,10 @@ " warranty of any kind, either expressed or implied. By using " this plugin, you agree that in no event will the copyright " holder be liable for any damages resulting from the use -" of this software. +" of this software. }}} + function! netrw_gitignore#Hide(...) - return substitute(substitute(system('git ls-files --other --ignored --exclude-standard --directory'), '\n', ',', 'g'), ',$', '', '') + return substitute(substitute(system('git ls-files --other --ignored --exclude-standard --directory'), '\n', ',', 'g'), ',$', '', '') endfunction + +" vim:ts=8 sts=4 sw=4 et fdm=marker diff --git a/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim b/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim index ddf4234aa2..8d10c00153 100644 --- a/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim +++ b/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim @@ -1,7 +1,7 @@ " Maintainer: Luca Saccarola " Former Maintainer: Charles E Campbell " Upstream: -" Copyright: Copyright (C) 1999-2021 Charles E. Campbell {{{1 +" Copyright: Copyright (C) 1999-2021 Charles E. Campbell {{{ " Permission is hereby granted to use and distribute this code, " with or without modifications, provided that this copyright " notice is copied with it. Like anything else that's free, @@ -9,214 +9,206 @@ " *as is* and comes with no warranty of any kind, either " expressed or implied. By using this plugin, you agree that " in no event will the copyright holder be liable for any damages -" resulting from the use of this software. +" resulting from the use of this software. }}} -" Load Once: {{{1 if &cp || exists("g:loaded_netrwPlugin") - finish + finish endif -let g:loaded_netrwPlugin = "v174" + +let g:loaded_netrwPlugin = "v175" + let s:keepcpo = &cpo set cpo&vim -"DechoRemOn -" --------------------------------------------------------------------- -" Public Interface: {{{1 +" Commands Launch/URL: {{{ + +command -complete=shellcmd -nargs=1 Launch call netrw#Launch(trim()) +command -complete=file -nargs=1 Open call netrw#Open(trim()) + +" }}} +" Local Browsing Autocmds: {{{ -" Commands Launch/URL {{{2 -command -complete=shellcmd -nargs=1 Launch call netrw#Launch(trim()) -command -complete=file -nargs=1 Open call netrw#Open(trim()) -" " }}} -" Local Browsing Autocmds: {{{2 augroup FileExplorer - au! - au BufLeave * if &ft != "netrw"|let w:netrw_prvfile= expand("%:p")|endif - au BufEnter * sil call s:LocalBrowse(expand("")) - au VimEnter * sil call s:VimEnter(expand("")) - if has("win32") - au BufEnter .* sil call s:LocalBrowse(expand("")) - endif + au! + au BufLeave * if &ft != "netrw"|let w:netrw_prvfile= expand("%:p")|endif + au BufEnter * sil call s:LocalBrowse(expand("")) + au VimEnter * sil call s:VimEnter(expand("")) + if has("win32") + au BufEnter .* sil call s:LocalBrowse(expand("")) + endif augroup END -" Network Browsing Reading Writing: {{{2 +" }}} +" Network Browsing Reading Writing: {{{ + augroup Network - au! - au BufReadCmd file://* call netrw#FileUrlEdit(expand("")) - au BufReadCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "sil doau BufReadPost ".fnameescape(expand("")) - au FileReadCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "sil doau FileReadPost ".fnameescape(expand("")) - au BufWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "sil doau BufWritePost ".fnameescape(expand("")) - au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "sil doau FileWritePost ".fnameescape(expand("")) - try - au SourceCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) - catch /^Vim\%((\a\+)\)\=:E216/ - au SourcePre ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) - endtry + au! + au BufReadCmd file://* call netrw#FileUrlEdit(expand("")) + au BufReadCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "sil doau BufReadPost ".fnameescape(expand("")) + au FileReadCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "sil doau FileReadPost ".fnameescape(expand("")) + au BufWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "sil doau BufWritePost ".fnameescape(expand("")) + au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "sil doau FileWritePost ".fnameescape(expand("")) + try + au SourceCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) + catch /^Vim\%((\a\+)\)\=:E216/ + au SourcePre ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) + endtry augroup END -" Commands: :Nread, :Nwrite, :NetUserPass {{{2 -com! -count=1 -nargs=* Nread let s:svpos= winsaveview()call netrw#NetRead(,)call winrestview(s:svpos) -com! -range=% -nargs=* Nwrite let s:svpos= winsaveview(),call netrw#NetWrite()call winrestview(s:svpos) -com! -nargs=* NetUserPass call NetUserPass() -com! -nargs=* Nsource let s:svpos= winsaveview()call netrw#NetSource()call winrestview(s:svpos) -com! -nargs=? Ntree call netrw#SetTreetop(1,) - -" Commands: :Explore, :Sexplore, Hexplore, Vexplore, Lexplore {{{2 -com! -nargs=* -bar -bang -count=0 -complete=dir Explore call netrw#Explore(,0,0+0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Sexplore call netrw#Explore(,1,0+0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Hexplore call netrw#Explore(,1,2+0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Vexplore call netrw#Explore(,1,4+0,) -com! -nargs=* -bar -count=0 -complete=dir Texplore call netrw#Explore(,0,6 ,) -com! -nargs=* -bar -bang Nexplore call netrw#Explore(-1,0,0,) -com! -nargs=* -bar -bang Pexplore call netrw#Explore(-2,0,0,) -com! -nargs=* -bar -bang -count=0 -complete=dir Lexplore call netrw#Lexplore(,0,) - -" Commands: NetrwSettings {{{2 -com! -nargs=0 NetrwSettings call netrwSettings#NetrwSettings() -com! -bang NetrwClean call netrw#Clean(0) - -" Maps: +" }}} +" Commands: :Nread, :Nwrite, :NetUserPass {{{ + +command! -count=1 -nargs=* Nread let s:svpos= winsaveview()call netrw#NetRead(,)call winrestview(s:svpos) +command! -range=% -nargs=* Nwrite let s:svpos= winsaveview(),call netrw#NetWrite()call winrestview(s:svpos) +command! -nargs=* NetUserPass call NetUserPass() +command! -nargs=* Nsource let s:svpos= winsaveview()call netrw#NetSource()call winrestview(s:svpos) +command! -nargs=? Ntree call netrw#SetTreetop(1,) + +" }}} +" Commands: :Explore, :Sexplore, Hexplore, Vexplore, Lexplore {{{ + +command! -nargs=* -bar -bang -count=0 -complete=dir Explore call netrw#Explore(, 0, 0+0, ) +command! -nargs=* -bar -bang -count=0 -complete=dir Sexplore call netrw#Explore(, 1, 0+0, ) +command! -nargs=* -bar -bang -count=0 -complete=dir Hexplore call netrw#Explore(, 1, 2+0, ) +command! -nargs=* -bar -bang -count=0 -complete=dir Vexplore call netrw#Explore(, 1, 4+0, ) +command! -nargs=* -bar -count=0 -complete=dir Texplore call netrw#Explore(, 0, 6, ) +command! -nargs=* -bar -bang -count=0 -complete=dir Lexplore call netrw#Lexplore(, 0, ) +command! -nargs=* -bar -bang Nexplore call netrw#Explore(-1, 0, 0, ) +command! -nargs=* -bar -bang Pexplore call netrw#Explore(-2, 0, 0, ) + +" }}} +" Commands: NetrwSettings {{{ + +command! -nargs=0 NetrwSettings call netrwSettings#NetrwSettings() +command! -bang NetrwClean call netrw#Clean(0) + +" }}} +" Maps: {{{ + if !exists("g:netrw_nogx") - if maparg('gx','n') == "" - if !hasmapto('NetrwBrowseX') - nmap gx NetrwBrowseX + if maparg('gx','n') == "" + if !hasmapto('NetrwBrowseX') + nmap gx NetrwBrowseX + endif + nno NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX())) endif - nno NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX())) - endif - if maparg('gx','x') == "" - if !hasmapto('NetrwBrowseXVis') - xmap gx NetrwBrowseXVis + if maparg('gx','x') == "" + if !hasmapto('NetrwBrowseXVis') + xmap gx NetrwBrowseXVis + endif + xno NetrwBrowseXVis :call netrw#BrowseXVis() endif - xno NetrwBrowseXVis :call netrw#BrowseXVis() - endif endif + if exists("g:netrw_usetab") && g:netrw_usetab - if maparg('','n') == "" - nmap NetrwShrink - endif - nno NetrwShrink :call netrw#Shrink() + if maparg('','n') == "" + nmap NetrwShrink + endif + nno NetrwShrink :call netrw#Shrink() endif -" --------------------------------------------------------------------- -" LocalBrowse: invokes netrw#LocalBrowseCheck() on directory buffers {{{2 -fun! s:LocalBrowse(dirname) - " Unfortunate interaction -- only DechoMsg debugging calls can be safely used here. - " Otherwise, the BufEnter event gets triggered when attempts to write to - " the DBG buffer are made. - - if !exists("s:vimentered") - " If s:vimentered doesn't exist, then the VimEnter event hasn't fired. It will, - " and so s:VimEnter() will then be calling this routine, but this time with s:vimentered defined. - " call Dfunc("s:LocalBrowse(dirname<".a:dirname.">) (s:vimentered doesn't exist)") - " call Dret("s:LocalBrowse") - return - endif - - " call Dfunc("s:LocalBrowse(dirname<".a:dirname.">) (s:vimentered=".s:vimentered.")") - - if has("amiga") - " The check against '' is made for the Amiga, where the empty - " string is the current directory and not checking would break - " things such as the help command. - " call Decho("(LocalBrowse) dirname<".a:dirname."> (isdirectory, amiga)") - if a:dirname != '' && isdirectory(a:dirname) - sil! call netrw#LocalBrowseCheck(a:dirname) - if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt - exe w:netrw_bannercnt - endif - endif +" }}} +" LocalBrowse: invokes netrw#LocalBrowseCheck() on directory buffers {{{ - elseif isdirectory(a:dirname) - " call Decho("(LocalBrowse) dirname<".a:dirname."> ft=".&ft." (isdirectory, not amiga)") - " call Dredir("LocalBrowse ft last set: ","verbose set ft") - " Jul 13, 2021: for whatever reason, preceding the following call with - " a sil! causes an unbalanced if-endif vim error - call netrw#LocalBrowseCheck(a:dirname) - if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt - exe w:netrw_bannercnt +function! s:LocalBrowse(dirname) + " do not trigger in the terminal + " https://github.com/vim/vim/issues/16463 + if &buftype ==# 'terminal' + return endif - else - " not a directory, ignore it - " call Decho("(LocalBrowse) dirname<".a:dirname."> not a directory, ignoring...") - endif + if !exists("s:vimentered") + " If s:vimentered doesn't exist, then the VimEnter event hasn't fired. It will, + " and so s:VimEnter() will then be calling this routine, but this time with s:vimentered defined. + return + endif - " call Dret("s:LocalBrowse") -endfun + if has("amiga") + " The check against '' is made for the Amiga, where the empty + " string is the current directory and not checking would break + " things such as the help command. + if a:dirname != '' && isdirectory(a:dirname) + sil! call netrw#LocalBrowseCheck(a:dirname) + if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt + exe w:netrw_bannercnt + endif + endif + elseif isdirectory(a:dirname) + " Jul 13, 2021: for whatever reason, preceding the following call with + " a sil! causes an unbalanced if-endif vim error + call netrw#LocalBrowseCheck(a:dirname) + if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt + exe w:netrw_bannercnt + endif + endif +endfunction -" --------------------------------------------------------------------- -" s:VimEnter: after all vim startup stuff is done, this function is called. {{{2 +" }}} +" s:VimEnter: after all vim startup stuff is done, this function is called. {{{ " Its purpose: to look over all windows and run s:LocalBrowse() on " them, which checks if they're directories and will create a directory " listing when appropriate. " It also sets s:vimentered, letting s:LocalBrowse() know that s:VimEnter() " has already been called. -fun! s:VimEnter(dirname) - " call Dfunc("s:VimEnter(dirname<".a:dirname.">) expand(%)<".expand("%").">") - if has('nvim') || v:version < 802 - " Johann Höchtl: reported that the call range... line causes an E488: Trailing characters - " error with neovim. I suspect its because neovim hasn't updated with recent - " vim patches. As is, this code will have problems with popup terminals - " instantiated before the VimEnter event runs. - " Ingo Karkat : E488 also in Vim 8.1.1602 - let curwin = winnr() - let s:vimentered = 1 - windo call s:LocalBrowse(expand("%:p")) - exe curwin."wincmd w" - else - " the following complicated expression comes courtesy of lacygoill; largely does the same thing as the windo and - " wincmd which are commented out, but avoids some side effects. Allows popup terminal before VimEnter. - let s:vimentered = 1 - call range(1, winnr('$'))->map({_, v -> win_execute(win_getid(v), 'call expand("%:p")->s:LocalBrowse()')}) - endif - " call Dret("s:VimEnter") -endfun - -" --------------------------------------------------------------------- -" NetrwStatusLine: {{{1 -fun! NetrwStatusLine() - " let g:stlmsg= "Xbufnr=".w:netrw_explore_bufnr." bufnr=".bufnr("%")." Xline#".w:netrw_explore_line." line#".line(".") - if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list") - let &stl= s:netrw_explore_stl - if exists("w:netrw_explore_bufnr")|unlet w:netrw_explore_bufnr|endif - if exists("w:netrw_explore_line")|unlet w:netrw_explore_line|endif - return "" - else - return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen - endif -endfun - -" ------------------------------------------------------------------------ -" NetUserPass: set username and password for subsequent ftp transfer {{{1 +function! s:VimEnter(dirname) + if has('nvim') || v:version < 802 + " Johann Höchtl: reported that the call range... line causes an E488: Trailing characters + " error with neovim. I suspect its because neovim hasn't updated with recent + " vim patches. As is, this code will have problems with popup terminals + " instantiated before the VimEnter event runs. + " Ingo Karkat : E488 also in Vim 8.1.1602 + let curwin = winnr() + let s:vimentered = 1 + windo call s:LocalBrowse(expand("%:p")) + exe curwin."wincmd w" + else + " the following complicated expression comes courtesy of lacygoill; largely does the same thing as the windo and + " wincmd which are commented out, but avoids some side effects. Allows popup terminal before VimEnter. + let s:vimentered = 1 + call range(1, winnr('$'))->map({_, v -> win_execute(win_getid(v), 'call expand("%:p")->s:LocalBrowse()')}) + endif +endfunction + +" }}} +" NetrwStatusLine: {{{ + +function! NetrwStatusLine() + if !exists("w:netrw_explore_bufnr") || w:netrw_explore_bufnr != bufnr("%") || !exists("w:netrw_explore_line") || w:netrw_explore_line != line(".") || !exists("w:netrw_explore_list") + let &stl= s:netrw_explore_stl + unlet! w:netrw_explore_bufnr w:netrw_explore_line + return "" + else + return "Match ".w:netrw_explore_mtchcnt." of ".w:netrw_explore_listlen + endif +endfunction + +" }}} +" NetUserPass: set username and password for subsequent ftp transfer {{{ " Usage: :call NetUserPass() -- will prompt for userid and password " :call NetUserPass("uid") -- will prompt for password " :call NetUserPass("uid","password") -- sets global userid and password -fun! NetUserPass(...) - - " get/set userid - if a:0 == 0 - " call Dfunc("NetUserPass(a:0<".a:0.">)") - if !exists("g:netrw_uid") || g:netrw_uid == "" - " via prompt - let g:netrw_uid= input('Enter username: ') +function! NetUserPass(...) + " get/set userid + if a:0 == 0 + if !exists("g:netrw_uid") || g:netrw_uid == "" + " via prompt + let g:netrw_uid= input('Enter username: ') + endif + else " from command line + let g:netrw_uid= a:1 + endif + + " get password + if a:0 <= 1 " via prompt + let g:netrw_passwd= inputsecret("Enter Password: ") + else " from command line + let g:netrw_passwd=a:2 endif - else " from command line - " call Dfunc("NetUserPass(a:1<".a:1.">) {") - let g:netrw_uid= a:1 - endif - - " get password - if a:0 <= 1 " via prompt - " call Decho("a:0=".a:0." case <=1:") - let g:netrw_passwd= inputsecret("Enter Password: ") - else " from command line - " call Decho("a:0=".a:0." case >1: a:2<".a:2.">") - let g:netrw_passwd=a:2 - endif - " call Dret("NetUserPass") -endfun - -" ------------------------------------------------------------------------ -" Modelines And Restoration: {{{1 +endfunction + +" }}} + let &cpo= s:keepcpo unlet s:keepcpo -" vim:ts=8 sts=2 sw=2 et fdm=marker + +" vim:ts=8 sts=4 sw=4 et fdm=marker diff --git a/runtime/pack/dist/opt/netrw/syntax/netrw.vim b/runtime/pack/dist/opt/netrw/syntax/netrw.vim index a94af0c27a..8042854a12 100644 --- a/runtime/pack/dist/opt/netrw/syntax/netrw.vim +++ b/runtime/pack/dist/opt/netrw/syntax/netrw.vim @@ -4,142 +4,146 @@ " Language: Netrw Listing Syntax if exists("b:current_syntax") - finish + finish endif -" Directory List Syntax Highlighting: {{{1 -syn cluster NetrwGroup contains=netrwHide,netrwSortBy,netrwSortSeq,netrwQuickHelp,netrwVersion,netrwCopyTgt -syn cluster NetrwTreeGroup contains=netrwDir,netrwSymLink,netrwExe - -syn match netrwPlain "\(\S\+ \)*\S\+" contains=netrwLink,@NoSpell -syn match netrwSpecial "\%(\S\+ \)*\S\+[*|=]\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -syn match netrwDir "\.\{1,2}/" contains=netrwClassify,@NoSpell -syn match netrwDir "\%(\S\+ \)*\S\+/\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -syn match netrwSizeDate "\<\d\+\s\d\{1,2}/\d\{1,2}/\d\{4}\s" skipwhite contains=netrwDateSep,@NoSpell nextgroup=netrwTime -syn match netrwSymLink "\%(\S\+ \)*\S\+@\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell -syn match netrwExe "\%(\S\+ \)*\S*[^~]\*\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +let b:current_syntax = "netrwlist" + +" Directory List Syntax Highlighting: {{{ + +syn cluster NetrwGroup contains=netrwHide,netrwSortBy,netrwSortSeq,netrwQuickHelp,netrwVersion,netrwCopyTgt +syn cluster NetrwTreeGroup contains=netrwDir,netrwSymLink,netrwExe + +syn match netrwPlain "\(\S\+ \)*\S\+" contains=netrwLink,@NoSpell +syn match netrwSpecial "\%(\S\+ \)*\S\+[*|=]\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +syn match netrwDir "\.\{1,2}/" contains=netrwClassify,@NoSpell +syn match netrwDir "\%(\S\+ \)*\S\+/\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +syn match netrwSizeDate "\<\d\+\s\d\{1,2}/\d\{1,2}/\d\{4}\s" skipwhite contains=netrwDateSep,@NoSpell nextgroup=netrwTime +syn match netrwSymLink "\%(\S\+ \)*\S\+@\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell +syn match netrwExe "\%(\S\+ \)*\S*[^~]\*\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4') -syn match netrwTreeBar "^\%([-+|│] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup + syn match netrwTreeBar "^\%([-+|│] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup else -syn match netrwTreeBar "^\%([-+|] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup + syn match netrwTreeBar "^\%([-+|] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup endif -syn match netrwTreeBarSpace " " contained - -syn match netrwClassify "[*=|@/]\ze\%(\s\{2,}\|$\)" contained -syn match netrwDateSep "/" contained -syn match netrwTime "\d\{1,2}:\d\{2}:\d\{2}" contained contains=netrwTimeSep -syn match netrwTimeSep ":" - -syn match netrwComment '".*\%(\t\|$\)' contains=@NetrwGroup,@NoSpell -syn match netrwHide '^"\s*\(Hid\|Show\)ing:' skipwhite contains=@NoSpell nextgroup=netrwHidePat -syn match netrwSlash "/" contained -syn match netrwHidePat "[^,]\+" contained skipwhite contains=@NoSpell nextgroup=netrwHideSep -syn match netrwHideSep "," contained skipwhite nextgroup=netrwHidePat -syn match netrwSortBy "Sorted by" contained transparent skipwhite nextgroup=netrwList -syn match netrwSortSeq "Sort sequence:" contained transparent skipwhite nextgroup=netrwList -syn match netrwCopyTgt "Copy/Move Tgt:" contained transparent skipwhite nextgroup=netrwList -syn match netrwList ".*$" contained contains=netrwComma,@NoSpell -syn match netrwComma "," contained -syn region netrwQuickHelp matchgroup=Comment start="Quick Help:\s\+" end="$" contains=netrwHelpCmd,netrwQHTopic,@NoSpell keepend contained -syn match netrwHelpCmd "\S\+\ze:" contained skipwhite contains=@NoSpell nextgroup=netrwCmdSep -syn match netrwQHTopic "([a-zA-Z &]\+)" contained skipwhite -syn match netrwCmdSep ":" contained nextgroup=netrwCmdNote -syn match netrwCmdNote ".\{-}\ze " contained contains=@NoSpell -syn match netrwVersion "(netrw.*)" contained contains=@NoSpell -syn match netrwLink "-->" contained skipwhite - -" ----------------------------- -" Special filetype highlighting {{{1 -" ----------------------------- +syn match netrwTreeBarSpace " " contained + +syn match netrwClassify "[*=|@/]\ze\%(\s\{2,}\|$\)" contained +syn match netrwDateSep "/" contained +syn match netrwTime "\d\{1,2}:\d\{2}:\d\{2}" contained contains=netrwTimeSep +syn match netrwTimeSep ":" + +syn match netrwComment '".*\%(\t\|$\)' contains=@NetrwGroup,@NoSpell +syn match netrwHide '^"\s*\(Hid\|Show\)ing:' skipwhite contains=@NoSpell nextgroup=netrwHidePat +syn match netrwSlash "/" contained +syn match netrwHidePat "[^,]\+" contained skipwhite contains=@NoSpell nextgroup=netrwHideSep +syn match netrwHideSep "," contained skipwhite nextgroup=netrwHidePat +syn match netrwSortBy "Sorted by" contained transparent skipwhite nextgroup=netrwList +syn match netrwSortSeq "Sort sequence:" contained transparent skipwhite nextgroup=netrwList +syn match netrwCopyTgt "Copy/Move Tgt:" contained transparent skipwhite nextgroup=netrwList +syn match netrwList ".*$" contained contains=netrwComma,@NoSpell +syn match netrwComma "," contained +syn region netrwQuickHelp matchgroup=Comment start="Quick Help:\s\+" end="$" contains=netrwHelpCmd,netrwQHTopic,@NoSpell keepend contained +syn match netrwHelpCmd "\S\+\ze:" contained skipwhite contains=@NoSpell nextgroup=netrwCmdSep +syn match netrwQHTopic "([a-zA-Z &]\+)" contained skipwhite +syn match netrwCmdSep ":" contained nextgroup=netrwCmdNote +syn match netrwCmdNote ".\{-}\ze " contained contains=@NoSpell +syn match netrwVersion "(netrw.*)" contained contains=@NoSpell +syn match netrwLink "-->" contained skipwhite + +" }}} +" Special filetype highlighting {{{ + if exists("g:netrw_special_syntax") && g:netrw_special_syntax - if exists("+suffixes") && &suffixes != "" - let suflist= join(split(&suffixes,',')) - let suflist= escape(substitute(suflist," ",'\\|','g'),'.~') - exe "syn match netrwSpecFile '\\(\\S\\+ \\)*\\S*\\(".suflist."\\)\\>' contains=netrwTreeBar,@NoSpell" - endif - syn match netrwBak "\(\S\+ \)*\S\+\.bak\>" contains=netrwTreeBar,@NoSpell - syn match netrwCompress "\(\S\+ \)*\S\+\.\%(gz\|bz2\|Z\|zip\)\>" contains=netrwTreeBar,@NoSpell - if has("unix") - syn match netrwCoreDump "\" contains=netrwTreeBar,@NoSpell - endif - syn match netrwLex "\(\S\+ \)*\S\+\.\%(l\|lex\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwYacc "\(\S\+ \)*\S\+\.y\>" contains=netrwTreeBar,@NoSpell - syn match netrwData "\(\S\+ \)*\S\+\.dat\>" contains=netrwTreeBar,@NoSpell - syn match netrwDoc "\(\S\+ \)*\S\+\.\%(doc\|txt\|pdf\|ps\|docx\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwHdr "\(\S\+ \)*\S\+\.\%(h\|hpp\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwLib "\(\S\+ \)*\S*\.\%(a\|so\|lib\|dll\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwMakeFile "\<[mM]akefile\>\|\(\S\+ \)*\S\+\.mak\>" contains=netrwTreeBar,@NoSpell - syn match netrwObj "\(\S\+ \)*\S*\.\%(o\|obj\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwPix "\c\(\S\+ \)*\S*\.\%(bmp\|fits\=\|gif\|je\=pg\|pcx\|ppc\|pgm\|png\|ppm\|psd\|rgb\|tif\|xbm\|xcf\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwTags "\<\(ANmenu\|ANtags\)\>" contains=netrwTreeBar,@NoSpell - syn match netrwTags "\" contains=netrwTreeBar,@NoSpell - syn match netrwTilde "\(\S\+ \)*\S\+\~\*\=\>" contains=netrwTreeBar,@NoSpell - syn match netrwTmp "\\|\(\S\+ \)*\S*tmp\>" contains=netrwTreeBar,@NoSpell + if exists("+suffixes") && &suffixes != "" + let suflist= join(split(&suffixes,',')) + let suflist= escape(substitute(suflist," ",'\\|','g'),'.~') + exe "syn match netrwSpecFile '\\(\\S\\+ \\)*\\S*\\(".suflist."\\)\\>' contains=netrwTreeBar,@NoSpell" + endif + syn match netrwBak "\(\S\+ \)*\S\+\.bak\>" contains=netrwTreeBar,@NoSpell + syn match netrwCompress "\(\S\+ \)*\S\+\.\%(gz\|bz2\|Z\|zip\)\>" contains=netrwTreeBar,@NoSpell + if has("unix") + syn match netrwCoreDump "\" contains=netrwTreeBar,@NoSpell + endif + syn match netrwLex "\(\S\+ \)*\S\+\.\%(l\|lex\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwYacc "\(\S\+ \)*\S\+\.y\>" contains=netrwTreeBar,@NoSpell + syn match netrwData "\(\S\+ \)*\S\+\.dat\>" contains=netrwTreeBar,@NoSpell + syn match netrwDoc "\(\S\+ \)*\S\+\.\%(doc\|txt\|pdf\|ps\|docx\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwHdr "\(\S\+ \)*\S\+\.\%(h\|hpp\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwLib "\(\S\+ \)*\S*\.\%(a\|so\|lib\|dll\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwMakeFile "\<[mM]akefile\>\|\(\S\+ \)*\S\+\.mak\>" contains=netrwTreeBar,@NoSpell + syn match netrwObj "\(\S\+ \)*\S*\.\%(o\|obj\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwPix "\c\(\S\+ \)*\S*\.\%(bmp\|fits\=\|gif\|je\=pg\|pcx\|ppc\|pgm\|png\|ppm\|psd\|rgb\|tif\|xbm\|xcf\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwTags "\<\(ANmenu\|ANtags\)\>" contains=netrwTreeBar,@NoSpell + syn match netrwTags "\" contains=netrwTreeBar,@NoSpell + syn match netrwTilde "\(\S\+ \)*\S\+\~\*\=\>" contains=netrwTreeBar,@NoSpell + syn match netrwTmp "\\|\(\S\+ \)*\S*tmp\>" contains=netrwTreeBar,@NoSpell endif -" --------------------------------------------------------------------- -" Highlighting Links: {{{1 +" }}} +" Highlighting Links: {{{ + if !exists("did_drchip_netrwlist_syntax") - let did_drchip_netrwlist_syntax= 1 - hi default link netrwClassify Function - hi default link netrwCmdSep Delimiter - hi default link netrwComment Comment - hi default link netrwDir Directory - hi default link netrwHelpCmd Function - hi default link netrwQHTopic Number - hi default link netrwHidePat Statement - hi default link netrwHideSep netrwComment - hi default link netrwList Statement - hi default link netrwVersion Identifier - hi default link netrwSymLink Question - hi default link netrwExe PreProc - hi default link netrwDateSep Delimiter - - hi default link netrwTreeBar Special - hi default link netrwTimeSep netrwDateSep - hi default link netrwComma netrwComment - hi default link netrwHide netrwComment - hi default link netrwMarkFile TabLineSel - hi default link netrwLink Special - - " special syntax highlighting (see :he g:netrw_special_syntax) - hi default link netrwCoreDump WarningMsg - hi default link netrwData Folded - hi default link netrwHdr netrwPlain - hi default link netrwLex netrwPlain - hi default link netrwLib DiffChange - hi default link netrwMakefile DiffChange - hi default link netrwYacc netrwPlain - hi default link netrwPix Special - - hi default link netrwBak netrwGray - hi default link netrwCompress netrwGray - hi default link netrwSpecFile netrwGray - hi default link netrwObj netrwGray - hi default link netrwTags netrwGray - hi default link netrwTilde netrwGray - hi default link netrwTmp netrwGray + let did_drchip_netrwlist_syntax= 1 + hi default link netrwClassify Function + hi default link netrwCmdSep Delimiter + hi default link netrwComment Comment + hi default link netrwDir Directory + hi default link netrwHelpCmd Function + hi default link netrwQHTopic Number + hi default link netrwHidePat Statement + hi default link netrwHideSep netrwComment + hi default link netrwList Statement + hi default link netrwVersion Identifier + hi default link netrwSymLink Question + hi default link netrwExe PreProc + hi default link netrwDateSep Delimiter + + hi default link netrwTreeBar Special + hi default link netrwTimeSep netrwDateSep + hi default link netrwComma netrwComment + hi default link netrwHide netrwComment + hi default link netrwMarkFile TabLineSel + hi default link netrwLink Special + + " special syntax highlighting (see :he g:netrw_special_syntax) + hi default link netrwCoreDump WarningMsg + hi default link netrwData Folded + hi default link netrwHdr netrwPlain + hi default link netrwLex netrwPlain + hi default link netrwLib DiffChange + hi default link netrwMakefile DiffChange + hi default link netrwYacc netrwPlain + hi default link netrwPix Special + + hi default link netrwBak netrwGray + hi default link netrwCompress netrwGray + hi default link netrwSpecFile netrwGray + hi default link netrwObj netrwGray + hi default link netrwTags netrwGray + hi default link netrwTilde netrwGray + hi default link netrwTmp netrwGray endif - " set up netrwGray to be understated (but not Ignore'd or Conceal'd, as those - " can be hard/impossible to read). Users may override this in a colorscheme by - " specifying netrwGray highlighting. - redir => s:netrwgray - sil hi netrwGray - redir END - if s:netrwgray !~ 'guifg' - if has("gui") && has("gui_running") - if &bg == "dark" - exe "hi netrwGray gui=NONE guifg=gray30" - else - exe "hi netrwGray gui=NONE guifg=gray70" - endif - else - hi link netrwGray Folded - endif - endif - -" Current Syntax: {{{1 -let b:current_syntax = "netrwlist" -" --------------------------------------------------------------------- -" vim: ts=8 fdm=marker +" set up netrwGray to be understated (but not Ignore'd or Conceal'd, as those +" can be hard/impossible to read). Users may override this in a colorscheme by +" specifying netrwGray highlighting. +redir => s:netrwgray +sil hi netrwGray +redir END + +if s:netrwgray !~ 'guifg' + if has("gui") && has("gui_running") + if &bg == "dark" + exe "hi netrwGray gui=NONE guifg=gray30" + else + exe "hi netrwGray gui=NONE guifg=gray70" + endif + else + hi link netrwGray Folded + endif +endif + +" }}} + +" vim:ts=8 sts=4 sw=4 et fdm=marker -- cgit From b172dd57faac8de98291b644c0b0e1ee6d4691e0 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 19 Jan 2025 16:50:38 +0100 Subject: vim-patch:2cb8246: runtime(tex): add texEmphStyle to texMatchGroup in syntax script fixes: vim/vim#16228 https://github.com/vim/vim/commit/2cb8246eb969594a6969b03dcf8ea89aa7deda6e Co-authored-by: Christian Brabandt Co-authored-by: Github User JordL --- runtime/syntax/tex.vim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/runtime/syntax/tex.vim b/runtime/syntax/tex.vim index 77a40e11d3..4f35bba939 100644 --- a/runtime/syntax/tex.vim +++ b/runtime/syntax/tex.vim @@ -3,7 +3,8 @@ " Maintainer: This runtime file is looking for a new maintainer. " Former Maintainer: Charles E. Campbell " Last Change: Apr 22, 2022 -" 2024 Feb 19 by Vim Project (announce adoption) +" 2024 Feb 19 by Vim Project: announce adoption +" 2025 Jan 18 by Vim Project: add texEmphStyle to texMatchGroup, #16228 " Version: 121 " Former URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_TEX " @@ -176,11 +177,11 @@ if !s:tex_excludematcher endif if !s:tex_nospell if !s:tex_no_error - syn cluster texMatchGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texError,texInput,texLength,texLigature,texMatcher,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell + syn cluster texMatchGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texError,texInput,texLength,texLigature,texMatcher,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texEmphStyle,texZone,texInputFile,texOption,@Spell syn cluster texMatchNMGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texError,texInput,texLength,texLigature,texMatcherNM,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell syn cluster texStyleGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texError,texInput,texLength,texLigature,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,texStyleStatement,texStyleMatcher,@Spell else - syn cluster texMatchGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcher,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell + syn cluster texMatchGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcher,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texEmphStyle,texZone,texInputFile,texOption,@Spell syn cluster texMatchNMGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcherNM,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell syn cluster texStyleGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,texStyleStatement,texStyleMatcher,@Spell endif -- cgit From 5b025b499ec430f1733409f0fb5ba3f88ce25a88 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 18 Jan 2025 10:42:11 +0100 Subject: vim-patch:9.1.1030: filetype: setting bash filetype is backwards incompatible Problem: filetype: setting bash filetype is backwards incompatible Solution: revert patch v9.1.0965, detect bash scripts again as sh filetype This reverts commit b9b762c21f2b61e0e7d8fee43d4d3dc8ecffd721. related: vim/vim#16309 https://github.com/vim/vim/commit/727c567a0934643e2d6e1dd92d4e636b17d9067f Co-authored-by: Christian Brabandt vim-patch:9.1.1033: tests: shaderslang was removed from test_filetype erroneously Problem: tests: shaderslang was removed from test_filetype erroneously (Christian Clason, after v9.1.1030) Solution: restore the test https://github.com/vim/vim/commit/1d2867df0c5dfa3d2444229f9e4b23d6ff935956 Co-authored-by: Christian Brabandt --- runtime/lua/vim/filetype/detect.lua | 1 - test/functional/lua/filetype_spec.lua | 2 +- test/old/testdir/test_filetype.vim | 16 +++++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 30a9951f6a..31c88c80bd 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1527,7 +1527,6 @@ local function sh(path, contents, name) vim.b[b].is_kornshell = nil vim.b[b].is_sh = nil end - return M.shell(path, contents, 'bash'), on_detect -- Ubuntu links sh to dash elseif matchregex(name, [[\<\(sh\|dash\)\>]]) then on_detect = function(b) diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index d64c024d7f..b75ff75b05 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -119,7 +119,7 @@ describe('vim.filetype', function() it('works with contents #22180', function() eq( - 'bash', + 'sh', exec_lua(function() -- Needs to be set so detect#sh doesn't fail vim.g.ft_ignore_pat = '\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$' diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 7213f3b032..020603015f 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -128,9 +128,6 @@ func s:GetFilenameChecks() abort \ 'ave': ['file.ave'], \ 'awk': ['file.awk', 'file.gawk'], \ 'b': ['file.mch', 'file.ref', 'file.imp'], - \ 'bash': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', - \ '.bash-aliases', '.bash_history', '.bash-history', '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', - \ 'PKGBUILD', 'file.bash', 'file.bats', 'file.cygport'], \ 'basic': ['file.bas', 'file.bi', 'file.bm'], \ 'bass': ['file.bass'], \ 'bc': ['file.bc'], @@ -686,10 +683,11 @@ func s:GetFilenameChecks() abort \ 'services': ['/etc/services', 'any/etc/services'], \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'], \ 'sexplib': ['file.sexp'], - \ 'sh': ['/usr/share/doc/bash-completion/filter.sh', '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', - \ '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', 'user-dirs.defaults', 'user-dirs.dirs', - \ 'makepkg.conf', '.makepkg.conf', 'file.mdd', '.env', '.envrc', 'devscripts.conf', '.devscripts', 'file.lo', - \ 'file.la', 'file.lai'], + \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '.bash_history', '.bash-history', + \ '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', + \ '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', 'file.bats', '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', + \ 'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport', '.env', '.envrc', 'devscripts.conf', + \ '.devscripts', 'file.lo', 'file.la', 'file.lai'], \ 'shaderslang': ['file.slang'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'sil': ['file.sil'], @@ -981,11 +979,11 @@ func s:GetScriptChecks() abort \ 'clojure': [['#!/path/clojure']], \ 'scala': [['#!/path/scala']], \ 'sh': [['#!/path/sh'], + \ ['#!/path/bash'], + \ ['#!/path/bash2'], \ ['#!/path/dash'], \ ['#!/path/ksh'], \ ['#!/path/ksh93']], - \ 'bash': [['#!/path/bash'], - \ ['#!/path/bash2']], \ 'csh': [['#!/path/csh']], \ 'tcsh': [['#!/path/tcsh']], \ 'zsh': [['#!/path/zsh']], -- cgit From 27c88069538bf64dace1ed39512d914e88615ac1 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Mon, 20 Jan 2025 21:17:36 +0900 Subject: docs(treesitter): expose LanguageTree:parent() #32108 Plugins may want to climb up the LanguageTree. Also add missing type annotations for other methods. --- runtime/doc/treesitter.txt | 15 +++++++++++++++ runtime/lua/vim/treesitter/languagetree.lua | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 83fa2c5a17..3d76c8c0ff 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1502,6 +1502,9 @@ analysis on a tree should use a timer to throttle too frequent updates. LanguageTree:children() *LanguageTree:children()* Returns a map of language to child tree. + Return: ~ + (`table`) + LanguageTree:contains({range}) *LanguageTree:contains()* Determines whether {range} is contained in the |LanguageTree|. @@ -1566,6 +1569,9 @@ LanguageTree:is_valid({exclude_children}) *LanguageTree:is_valid()* LanguageTree:lang() *LanguageTree:lang()* Gets the language of this tree node. + Return: ~ + (`string`) + *LanguageTree:language_for_range()* LanguageTree:language_for_range({range}) Gets the appropriate language that contains {range}. @@ -1614,6 +1620,12 @@ LanguageTree:node_for_range({range}, {opts}) Return: ~ (`TSNode?`) +LanguageTree:parent() *LanguageTree:parent()* + Returns the parent tree. `nil` for the root tree. + + Return: ~ + (`vim.treesitter.LanguageTree?`) + LanguageTree:parse({range}, {on_parse}) *LanguageTree:parse()* Recursively parse all regions in the language tree using |treesitter-parsers| for the corresponding languages and run injection @@ -1674,6 +1686,9 @@ LanguageTree:register_cbs({cbs}, {recursive}) LanguageTree:source() *LanguageTree:source()* Returns the source content of the language tree (bufnr or string). + Return: ~ + (`integer|string`) + *LanguageTree:tree_for_range()* LanguageTree:tree_for_range({range}, {opts}) Gets the tree that contains {range}. diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 35a77f1afc..3db7fe5c9e 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -267,6 +267,7 @@ function LanguageTree:trees() end --- Gets the language of this tree node. +--- @return string function LanguageTree:lang() return self._lang end @@ -307,11 +308,13 @@ function LanguageTree:is_valid(exclude_children) end --- Returns a map of language to child tree. +--- @return table function LanguageTree:children() return self._children end --- Returns the source content of the language tree (bufnr or string). +--- @return integer|string function LanguageTree:source() return self._source end @@ -630,7 +633,8 @@ function LanguageTree:add_child(lang) return self._children[lang] end ---- @package +---Returns the parent tree. `nil` for the root tree. +---@return vim.treesitter.LanguageTree? function LanguageTree:parent() return self._parent end -- cgit From 19b25f3feacfedc18a57eb2a1368a1ed07ac5faa Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 19 Jan 2025 13:30:11 +0100 Subject: feat(api): deprecate nvim_buf_add_highlight() This was kept for a while as it was a useful short hand and initially matched what highlights what actually properly implemented. But now |vim.hl.range()| is a better high-level shorthand with full support for native multi-line ranges. --- runtime/doc/api.txt | 77 +++--------------------------------- runtime/doc/deprecated.txt | 1 + runtime/doc/map.txt | 16 ++++---- runtime/doc/ui.txt | 2 +- runtime/lua/vim/_meta/api.lua | 39 +++++-------------- src/nvim/api/deprecated.c | 86 ++++++++++++++++++++++++++++++++++++++++ src/nvim/api/extmark.c | 91 +------------------------------------------ 7 files changed, 110 insertions(+), 202 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 5a6361d45f..5163f24ac8 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -406,18 +406,9 @@ Another use case are plugins that show output in an append-only buffer, and want to add highlights to the outputs. Highlight data cannot be preserved on writing and loading a buffer to file, nor in undo/redo cycles. -Highlights are registered using the |nvim_buf_add_highlight()| function. If an -external highlighter plugin wants to add many highlights in a batch, -performance can be improved by calling |nvim_buf_add_highlight()| as an -asynchronous notification, after first (synchronously) requesting a source id. - -|nvim_buf_add_highlight()| adds highlights as |extmarks|. If highlights need to -be tracked or manipulated after adding them, it is better to use -|nvim_buf_set_extmark()| directly, as this function returns the placed |extmark| -id. Thus, instead of >lua - vim.api.nvim_buf_add_highlight(buf, ns_id, hl_group, line, col_start, col_end) -< -use >lua +Highlights are registered using the |nvim_buf_set_extmark()| function, which +adds highlights as |extmarks|. If highlights need to be tracked or manipulated +after adding them, the returned |extmark| id can be used. >lua -- create the highlight through an extmark extid = vim.api.nvim_buf_set_extmark(buf, ns_id, line, col_start, {end_col = col_end, hl_group = hl_group}) @@ -428,29 +419,7 @@ use >lua vim.api.nvim_buf_set_extmark(buf, ns_id, NEW_LINE, col_start, {end_col = col_end, hl_group = NEW_HL_GROUP, id = extid}) < -Example using the Python API client (|pynvim|): ->python - src = vim.new_highlight_source() - buf = vim.current.buffer - for i in range(5): - buf.add_highlight("String",i,0,-1,src_id=src) - # some time later ... - buf.clear_namespace(src) -< -If the highlights don't need to be deleted or updated, just pass -1 as -src_id (this is the default in python). Use |nvim_buf_clear_namespace()| to -clear highlights from a specific source, in a specific line range or the -entire buffer by passing in the line range 0, -1 (the latter is the default in -python as used above). - -Example using the API from Vimscript: >vim - - call nvim_buf_set_lines(0, 0, 0, v:true, ["test text"]) - let src = nvim_buf_add_highlight(0, 0, "String", 1, 0, 4) - call nvim_buf_add_highlight(0, src, "Identifier", 0, 5, -1) - " some time later ... - call nvim_buf_clear_namespace(0, src, 0, -1) - +See also |vim.hl.range()|. ============================================================================== Floating windows *api-floatwin* @@ -2491,42 +2460,6 @@ nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* ============================================================================== Extmark Functions *api-extmark* - *nvim_buf_add_highlight()* -nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, - {col_end}) - Adds a highlight to buffer. - - Useful for plugins that dynamically generate highlights to a buffer (like - a semantic highlighter or linter). The function adds a single highlight to - a buffer. Unlike |matchaddpos()| highlights follow changes to line - numbering (as lines are inserted/removed above the highlighted line), like - signs and marks do. - - Namespaces are used for batch deletion/updating of a set of highlights. To - create a namespace, use |nvim_create_namespace()| which returns a - namespace id. Pass it in to this function as `ns_id` to add highlights to - the namespace. All highlights in the same namespace can then be cleared - with single call to |nvim_buf_clear_namespace()|. If the highlight never - will be deleted by an API call, pass `ns_id = -1`. - - As a shorthand, `ns_id = 0` can be used to create a new namespace for the - highlight, the allocated id is then returned. If `hl_group` is the empty - string no highlight is added, but a new `ns_id` is still returned. This is - supported for backwards compatibility, new code should use - |nvim_create_namespace()| to create a new empty namespace. - - Parameters: ~ - • {buffer} Buffer handle, or 0 for current buffer - • {ns_id} namespace to use or -1 for ungrouped highlight - • {hl_group} Name of the highlight group to use - • {line} Line to highlight (zero-indexed) - • {col_start} Start of (byte-indexed) column range to highlight - • {col_end} End of (byte-indexed) column range to highlight, or -1 to - highlight to end of line - - Return: ~ - The ns_id that was used - *nvim_buf_clear_namespace()* nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end}) Clears |namespace|d objects (highlights, |extmarks|, virtual text) from a @@ -2764,7 +2697,7 @@ nvim_create_namespace({name}) *nvim_create_namespace()* Creates a new namespace or gets an existing one. *namespace* Namespaces are used for buffer highlights and virtual text, see - |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. + |nvim_buf_set_extmark()|. Namespaces can be named or anonymous. If `name` matches an existing namespace, the associated id is returned. If `name` is an empty string a diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 4f320aeab3..ff9c21fad9 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -22,6 +22,7 @@ API • nvim_out_write() Use |nvim_echo()|. • nvim_err_write() Use |nvim_echo()| with `{err=true}`. • nvim_err_writeln() Use |nvim_echo()| with `{err=true}`. +• nvim_buf_add_highlight() Use |vim.hl.range()| or |nvim_buf_set_extmark()| DIAGNOSTICS • *vim.diagnostic.goto_next()* Use |vim.diagnostic.jump()| with `{count=1, float=true}` instead. diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 1e472422ce..52c04333fc 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1545,7 +1545,7 @@ Your command preview routine must implement this protocol: 3. Add required highlights to the target buffers. If preview buffer is provided, add required highlights to the preview buffer as well. All highlights must be added to the preview namespace which is provided as an - argument to the preview callback (see |nvim_buf_add_highlight()| and + argument to the preview callback (see |vim.hl.range()| and |nvim_buf_set_extmark()| for help on how to add highlights to a namespace). 4. Return an integer (0, 1, 2) which controls how Nvim behaves as follows: 0: No preview is shown. @@ -1574,13 +1574,12 @@ supports incremental command preview: if start_idx then -- Highlight the match - vim.api.nvim_buf_add_highlight( + vim.hl.range( buf, preview_ns, 'Substitute', - line1 + i - 2, - start_idx - 1, - end_idx + {line1 + i - 2, start_idx - 1}, + {line1 + i - 2, end_idx}, ) -- Add lines and set highlights in the preview buffer @@ -1595,13 +1594,12 @@ supports incremental command preview: false, { prefix .. line } ) - vim.api.nvim_buf_add_highlight( + vim.hl.range( preview_buf, preview_ns, 'Substitute', - preview_buf_line, - #prefix + start_idx - 1, - #prefix + end_idx + {preview_buf_line, #prefix + start_idx - 1}, + {preview_buf_line, #prefix + end_idx}, ) preview_buf_line = preview_buf_line + 1 end diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 4d212e2779..d8a0e3d0d7 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -562,7 +562,7 @@ with the following possible keys: "ui": Builtin UI highlight. |highlight-groups| "syntax": Highlight applied to a buffer by a syntax declaration or other runtime/plugin functionality such as - |nvim_buf_add_highlight()| + |nvim_buf_set_extmark()| "terminal": highlight from a process running in a |terminal-emulator|. Contains no further semantic information. `ui_name`: Highlight name from |highlight-groups|. Only for "ui" kind. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 670e867c1e..4168d5b857 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -163,35 +163,14 @@ function vim.api.nvim__stats() end --- @return any function vim.api.nvim__unpack(str) end ---- Adds a highlight to buffer. ---- ---- Useful for plugins that dynamically generate highlights to a buffer ---- (like a semantic highlighter or linter). The function adds a single ---- highlight to a buffer. Unlike `matchaddpos()` highlights follow changes to ---- line numbering (as lines are inserted/removed above the highlighted line), ---- like signs and marks do. ---- ---- Namespaces are used for batch deletion/updating of a set of highlights. To ---- create a namespace, use `nvim_create_namespace()` which returns a namespace ---- id. Pass it in to this function as `ns_id` to add highlights to the ---- namespace. All highlights in the same namespace can then be cleared with ---- single call to `nvim_buf_clear_namespace()`. If the highlight never will be ---- deleted by an API call, pass `ns_id = -1`. ---- ---- As a shorthand, `ns_id = 0` can be used to create a new namespace for the ---- highlight, the allocated id is then returned. If `hl_group` is the empty ---- string no highlight is added, but a new `ns_id` is still returned. This is ---- supported for backwards compatibility, new code should use ---- `nvim_create_namespace()` to create a new empty namespace. ---- ---- @param buffer integer Buffer handle, or 0 for current buffer ---- @param ns_id integer namespace to use or -1 for ungrouped highlight ---- @param hl_group string Name of the highlight group to use ---- @param line integer Line to highlight (zero-indexed) ---- @param col_start integer Start of (byte-indexed) column range to highlight ---- @param col_end integer End of (byte-indexed) column range to highlight, ---- or -1 to highlight to end of line ---- @return integer # The ns_id that was used +--- @deprecated +--- @param buffer integer +--- @param ns_id integer +--- @param hl_group string +--- @param line integer +--- @param col_start integer +--- @param col_end integer +--- @return integer function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) end --- Activates buffer-update events on a channel, or as Lua callbacks. @@ -987,7 +966,7 @@ function vim.api.nvim_create_buf(listed, scratch) end --- Creates a new namespace or gets an existing one. [namespace]() --- --- Namespaces are used for buffer highlights and virtual text, see ---- `nvim_buf_add_highlight()` and `nvim_buf_set_extmark()`. +--- `nvim_buf_set_extmark()`. --- --- Namespaces can be named or anonymous. If `name` matches an existing --- namespace, the associated id is returned. If `name` is an empty string diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index b9e7d7143a..1d81b21be6 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -21,6 +21,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" +#include "nvim/marktree.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" #include "nvim/message.h" @@ -84,6 +85,17 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) return buf->b_fnum; } +static uint32_t src2ns(Integer *src_id) +{ + if (*src_id == 0) { + *src_id = nvim_create_namespace((String)STRING_INIT); + } + if (*src_id < 0) { + return (((uint32_t)1) << 31) - 1; + } + return (uint32_t)(*src_id); +} + /// Clears highlights and virtual text from namespace and range of lines /// /// @deprecated use |nvim_buf_clear_namespace()|. @@ -102,6 +114,80 @@ void nvim_buf_clear_highlight(Buffer buffer, Integer ns_id, Integer line_start, nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err); } +/// Adds a highlight to buffer. +/// +/// @deprecated use |nvim_buf_set_extmark()| or |vim.hl.range()| +/// +/// Namespaces are used for batch deletion/updating of a set of highlights. To +/// create a namespace, use |nvim_create_namespace()| which returns a namespace +/// id. Pass it in to this function as `ns_id` to add highlights to the +/// namespace. All highlights in the same namespace can then be cleared with +/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be +/// deleted by an API call, pass `ns_id = -1`. +/// +/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the +/// highlight, the allocated id is then returned. If `hl_group` is the empty +/// string no highlight is added, but a new `ns_id` is still returned. This is +/// supported for backwards compatibility, new code should use +/// |nvim_create_namespace()| to create a new empty namespace. +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id namespace to use or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line Line to highlight (zero-indexed) +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Error details, if any +/// @return The ns_id that was used +Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line, + Integer col_start, Integer col_end, Error *err) + FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(13) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { + return 0; + }); + VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { + return 0; + }); + + if (col_end < 0 || col_end > MAXCOL) { + col_end = MAXCOL; + } + + uint32_t ns = src2ns(&ns_id); + + if (!(line < buf->b_ml.ml_line_count)) { + // safety check, we can't add marks outside the range + return ns_id; + } + + int hl_id = 0; + if (hl_group.size > 0) { + hl_id = syn_check_group(hl_group.data, hl_group.size); + } else { + return ns_id; + } + + int end_line = (int)line; + if (col_end == MAXCOL) { + col_end = 0; + end_line++; + } + + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; + + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, + decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); + return ns_id; +} /// Set the virtual text (annotation) for a buffer line. /// /// @deprecated use nvim_buf_set_extmark to use full virtual text functionality. diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index a237e7531a..aa07bf39b3 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -49,7 +49,7 @@ void api_extmark_free_all_mem(void) /// Creates a new namespace or gets an existing one. [namespace]() /// /// Namespaces are used for buffer highlights and virtual text, see -/// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. +/// |nvim_buf_set_extmark()|. /// /// Namespaces can be named or anonymous. If `name` matches an existing /// namespace, the associated id is returned. If `name` is an empty string @@ -857,95 +857,6 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id); } -uint32_t src2ns(Integer *src_id) -{ - if (*src_id == 0) { - *src_id = nvim_create_namespace((String)STRING_INIT); - } - if (*src_id < 0) { - return (((uint32_t)1) << 31) - 1; - } - return (uint32_t)(*src_id); -} - -/// Adds a highlight to buffer. -/// -/// Useful for plugins that dynamically generate highlights to a buffer -/// (like a semantic highlighter or linter). The function adds a single -/// highlight to a buffer. Unlike |matchaddpos()| highlights follow changes to -/// line numbering (as lines are inserted/removed above the highlighted line), -/// like signs and marks do. -/// -/// Namespaces are used for batch deletion/updating of a set of highlights. To -/// create a namespace, use |nvim_create_namespace()| which returns a namespace -/// id. Pass it in to this function as `ns_id` to add highlights to the -/// namespace. All highlights in the same namespace can then be cleared with -/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be -/// deleted by an API call, pass `ns_id = -1`. -/// -/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the -/// highlight, the allocated id is then returned. If `hl_group` is the empty -/// string no highlight is added, but a new `ns_id` is still returned. This is -/// supported for backwards compatibility, new code should use -/// |nvim_create_namespace()| to create a new empty namespace. -/// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param ns_id namespace to use or -1 for ungrouped highlight -/// @param hl_group Name of the highlight group to use -/// @param line Line to highlight (zero-indexed) -/// @param col_start Start of (byte-indexed) column range to highlight -/// @param col_end End of (byte-indexed) column range to highlight, -/// or -1 to highlight to end of line -/// @param[out] err Error details, if any -/// @return The ns_id that was used -Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line, - Integer col_start, Integer col_end, Error *err) - FUNC_API_SINCE(1) -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf) { - return 0; - } - - VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { - return 0; - }); - VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { - return 0; - }); - - if (col_end < 0 || col_end > MAXCOL) { - col_end = MAXCOL; - } - - uint32_t ns = src2ns(&ns_id); - - if (!(line < buf->b_ml.ml_line_count)) { - // safety check, we can't add marks outside the range - return ns_id; - } - - int hl_id = 0; - if (hl_group.size > 0) { - hl_id = syn_check_group(hl_group.data, hl_group.size); - } else { - return ns_id; - } - - int end_line = (int)line; - if (col_end == MAXCOL) { - col_end = 0; - end_line++; - } - - DecorInline decor = DECOR_INLINE_INIT; - decor.data.hl.hl_id = hl_id; - - extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); - return ns_id; -} - /// Clears |namespace|d objects (highlights, |extmarks|, virtual text) from /// a region. /// -- cgit From 8a236c242a76825a6a9266feda45794c7328c807 Mon Sep 17 00:00:00 2001 From: Guilherme Soares <48023091+guilhas07@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:00:13 +0000 Subject: fix(lsp): set floating window filetype after setup #32112 Problem: The filetype for the floating window buffer is being set before its context is fully initialized. This results in `FileType` events not receiving the correct context. Solution: Set the filetype after the floating preview window and its buffer variables are fully configured to ensure proper context is provided. --- runtime/lua/vim/lsp/util.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 4e0adf3366..b9b53d36a8 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1568,8 +1568,6 @@ function M.open_floating_preview(contents, syntax, opts) if do_stylize then local width = M._make_floating_popup_size(contents, opts) contents = M._normalize_markdown(contents, { width = width }) - vim.bo[floating_bufnr].filetype = 'markdown' - vim.treesitter.start(floating_bufnr) else -- Clean up input: trim empty lines contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true }) @@ -1635,9 +1633,6 @@ function M.open_floating_preview(contents, syntax, opts) }) end - if do_stylize then - vim.wo[floating_winnr].conceallevel = 2 - end vim.wo[floating_winnr].foldenable = false -- Disable folding. vim.wo[floating_winnr].wrap = opts.wrap -- Soft wrapping. vim.wo[floating_winnr].breakindent = true -- Slightly better list presentation. @@ -1646,6 +1641,12 @@ function M.open_floating_preview(contents, syntax, opts) vim.bo[floating_bufnr].modifiable = false vim.bo[floating_bufnr].bufhidden = 'wipe' + if do_stylize then + vim.wo[floating_winnr].conceallevel = 2 + vim.bo[floating_bufnr].filetype = 'markdown' + vim.treesitter.start(floating_bufnr) + end + return floating_bufnr, floating_winnr end -- cgit From ded15ca8c210965442d39ab214d4838b80a3fdc6 Mon Sep 17 00:00:00 2001 From: Konrad Malik Date: Mon, 20 Jan 2025 15:10:00 +0100 Subject: fix: completion.enable(false,...) deletes invalid augroup #32121 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: vim.lsp.completion.enable(true, client.id, bufnr) vim.lsp.completion.enable(false, client.id, bufnr) Error detected while processing LspDetach Autocommands for "*": Error executing lua callback: …/lsp/completion.lua:701: Vim:E367: No such group: "vim/lsp/completion-22" stack traceback: [C]: in function 'nvim_del_augroup_by_name' …/lsp/completion.lua:701: in function 'disable_completions' …/lsp/completion.lua:724: in function 'enable' Solution: Delete the correct augroup. --- runtime/lua/vim/lsp/completion.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index 9902c52c33..bdf31d8514 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -615,6 +615,12 @@ local function on_complete_done() end end +---@param bufnr integer +---@return string +local function get_augroup(bufnr) + return string.format('nvim.lsp.completion_%d', bufnr) +end + --- @class vim.lsp.completion.BufferOpts --- @field autotrigger? boolean Default: false When true, completion triggers automatically based on the server's `triggerCharacters`. --- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|. @@ -639,8 +645,7 @@ local function enable_completions(client_id, bufnr, opts) }) -- Set up autocommands. - local group = - api.nvim_create_augroup(string.format('nvim.lsp.completion_%d', bufnr), { clear = true }) + local group = api.nvim_create_augroup(get_augroup(bufnr), { clear = true }) api.nvim_create_autocmd('CompleteDone', { group = group, buffer = bufnr, @@ -708,7 +713,7 @@ local function disable_completions(client_id, bufnr) handle.clients[client_id] = nil if not next(handle.clients) then buf_handles[bufnr] = nil - api.nvim_del_augroup_by_name(string.format('vim/lsp/completion-%d', bufnr)) + api.nvim_del_augroup_by_name(get_augroup(bufnr)) else for char, clients in pairs(handle.triggers) do --- @param c vim.lsp.Client -- cgit From 92556be33d04668c58a37794de5562af6297b3ac Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 16 Jan 2025 16:03:06 +0100 Subject: fix(messages): compute msg_col after last newline in ext_messages Problem: We want to keep track of the current message column, which is done very rudimentary for ext_messages; only checking if the message ends in a newline to reset the column, while computing the entire cellwidth of the message, which may contain (multiple) newlines not necessarily at the end (since 21718c6). This introduced a noticeable delay for large messages (e.g. :=vim). Solution: Calculate the cellwidth of the message after the last newline. Use it to keep track of the current message column. This might not be a functional change currently, since it only affects messages with (multiple) newlines not at the end of a message, which I don't think we emit internally, and msg_col is reset for a new kind. It does fix the performance problem. --- src/nvim/message.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index 5423446ef9..4c20edb7eb 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -153,7 +153,6 @@ static Array *msg_ext_chunks = NULL; static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40); static sattr_T msg_ext_last_attr = -1; static int msg_ext_last_hl_id; -static size_t msg_ext_cur_len = 0; static bool msg_ext_history = false; ///< message was added to history static bool msg_ext_overwrite = false; ///< will overwrite last message @@ -2246,14 +2245,13 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse // Concat pieces with the same highlight size_t len = maxlen < 0 ? strlen(str) : strnlen(str, (size_t)maxlen); ga_concat_len(&msg_ext_last_chunk, str, len); - msg_ext_cur_len += len; - msg_col += (int)mb_string2cells(str); - // When message ends in newline, reset variables used to format message: msg_advance(). - assert(len > 0); - if (str[len - 1] == '\n') { - msg_ext_cur_len = 0; - msg_col = 0; - } + + // Find last newline in the message and calculate the current message column + const char *lastline = strrchr(str, '\n'); + maxlen -= (int)(lastline ? (lastline - str) : 0); + const char *p = lastline ? lastline + 1 : str; + int col = (int)(maxlen < 0 ? mb_string2cells(p) : mb_string2cells_len(p, (size_t)(maxlen))); + msg_col = (lastline ? 0 : msg_col) + col; return; } @@ -3155,7 +3153,7 @@ static Array *msg_ext_init_chunks(void) { Array *tofree = msg_ext_chunks; msg_ext_chunks = xcalloc(1, sizeof(*msg_ext_chunks)); - msg_ext_cur_len = 0; + msg_col = 0; return tofree; } @@ -3472,14 +3470,6 @@ void msg_advance(int col) msg_col = col; // for redirection, may fill it up later return; } - if (ui_has(kUIMessages)) { - // TODO(bfredl): use byte count as a basic proxy. - // later on we might add proper support for formatted messages. - while (msg_ext_cur_len < (size_t)col) { - msg_putchar(' '); - } - return; - } col = MIN(col, Columns - 1); // not enough room while (msg_col < col) { msg_putchar(' '); -- cgit From 5b1136a99c7fc6db4cfe6865b72c069a4697c1a5 Mon Sep 17 00:00:00 2001 From: Donatas Date: Mon, 20 Jan 2025 16:40:26 +0200 Subject: feat(inccommand): preview 'nomodifiable' buffers #32034 Problem: Incremental preview is not allowed on 'nomodifiable' buffers. Solution: - Allow preview on 'nomodifiable' buffers. - Restore the 'modifiable' option in case the preview function changes it. --- runtime/doc/news.txt | 2 ++ src/nvim/ex_getln.c | 4 ++- test/functional/ui/inccommand_user_spec.lua | 44 +++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 58902abb87..58dab586d9 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -319,6 +319,8 @@ PLUGINS • EditorConfig • spelling_language property is now supported. +• 'inccommand' incremental preview can run on 'nomodifiable' buffers and + restores their 'modifiable' state STARTUP diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 423b50cd32..1eecee2a38 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -163,6 +163,7 @@ typedef struct { typedef struct { buf_T *buf; OptInt save_b_p_ul; + int save_b_p_ma; int save_b_changed; pos_T save_b_op_start; pos_T save_b_op_end; @@ -2419,6 +2420,7 @@ static void cmdpreview_prepare(CpInfo *cpinfo) if (!set_has(ptr_t, &saved_bufs, buf)) { CpBufInfo cp_bufinfo; cp_bufinfo.buf = buf; + cp_bufinfo.save_b_p_ma = buf->b_p_ma; cp_bufinfo.save_b_p_ul = buf->b_p_ul; cp_bufinfo.save_b_changed = buf->b_changed; cp_bufinfo.save_b_op_start = buf->b_op_start; @@ -2509,6 +2511,7 @@ static void cmdpreview_restore_state(CpInfo *cpinfo) } buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels' + buf->b_p_ma = cp_bufinfo.save_b_p_ma; // Restore 'modifiable' } for (size_t i = 0; i < cpinfo->win_info.size; i++) { @@ -2704,7 +2707,6 @@ static int command_line_changed(CommandLineState *s) && current_sctx.sc_sid == 0 // only if interactive && *p_icm != NUL // 'inccommand' is set && !exmode_active // not in ex mode - && curbuf->b_p_ma // buffer is modifiable && cmdline_star == 0 // not typing a password && !vpeekc_any() && cmdpreview_may_show(s)) { diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index 2d26d2c5e0..3eee9a6e07 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -253,6 +253,50 @@ describe("'inccommand' for user commands", function() ]] end) + it("can preview 'nomodifiable' buffer", function() + exec_lua([[ + vim.api.nvim_create_user_command("PreviewTest", function() end, { + preview = function(ev) + vim.bo.modifiable = true + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"cats"}) + return 2 + end, + }) + ]]) + command('set inccommand=split') + + command('set nomodifiable') + eq(false, api.nvim_get_option_value('modifiable', { buf = 0 })) + + feed(':PreviewTest') + + screen:expect([[ + cats | + {1:~ }|*8 + {3:[No Name] [+] }| + | + {1:~ }|*4 + {2:[Preview] }| + :PreviewTest^ | + ]]) + feed('') + screen:expect([[ + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | + ^ | + {1:~ }|*7 + | + ]]) + + eq(false, api.nvim_get_option_value('modifiable', { buf = 0 })) + end) + it('works with inccommand=nosplit', function() command('set inccommand=nosplit') feed(':Replace text cats') -- cgit From 0bc75ac78eba28c22d0facd3743fbe2dc573ee90 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 20 Jan 2025 17:34:53 +0100 Subject: ci(news): treat deprecated.txt as part of news.txt This is because we reference to deprecated.txt from news.txt, so deprecation news updates are made only in deprecated.txt. --- .github/workflows/news.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/news.yml b/.github/workflows/news.yml index ca07876197..e2d9a058c3 100644 --- a/.github/workflows/news.yml +++ b/.github/workflows/news.yml @@ -20,7 +20,7 @@ jobs: type="$(echo "$message" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" breaking="$(echo "$message" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')" if [[ "$type" == "feat" ]] || [[ "$type" == "perf" ]] || [[ "$breaking" == "breaking-change" ]]; then - ! git diff HEAD~${{ github.event.pull_request.commits }}..HEAD --quiet runtime/doc/news.txt || + ! git diff HEAD~${{ github.event.pull_request.commits }}..HEAD --quiet runtime/doc/news.txt runtime/doc/deprecated.txt || { echo " Pull request includes a new feature, performance improvement -- cgit From f50f86b9ff5dd2aab7838801d3c1cad898ea0c77 Mon Sep 17 00:00:00 2001 From: Konrad Malik Date: Mon, 20 Jan 2025 17:17:46 +0100 Subject: fix(treesitter): compute folds on_changedtree only if not nil --- runtime/lua/vim/treesitter/_fold.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 4a571bbaf7..f8d18d8427 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -387,7 +387,9 @@ function M.foldexpr(lnum) parser:register_cbs({ on_changedtree = function(tree_changes) - on_changedtree(bufnr, foldinfos[bufnr], tree_changes) + if foldinfos[bufnr] then + on_changedtree(bufnr, foldinfos[bufnr], tree_changes) + end end, on_bytes = function(_, _, start_row, start_col, _, old_row, old_col, _, new_row, new_col, _) -- cgit From 855a2a75e6f7d08376c221a46e0179e5c76176ec Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 20 Jan 2025 22:44:56 +0100 Subject: vim-patch:4335fcf: runtime(kconfig): updated ftplugin and syntax script https://github.com/vim/vim/commit/4335fcfed1b0a29c07b10f97d1f309dd8f964de6 Co-authored-by: Christian Brabandt --- runtime/doc/syntax.txt | 10 + runtime/ftplugin/kconfig.vim | 3 +- runtime/syntax/kconfig.vim | 1392 +++++++++++++++++++++--------------------- 3 files changed, 708 insertions(+), 697 deletions(-) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index e3a43eaf1a..75d6d85183 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1745,6 +1745,16 @@ To disable numbers having their own color add the following to your vimrc: > If you want quotes to have different highlighting than strings > let g:jq_quote_highlight = 1 +KCONFIG *ft-kconfig-syntax* + +Kconfig syntax highlighting language. For syntax syncing, you can configure +the following variable (default: 50): > + + let kconfig_minlines = 50 + +To configure a bit more (heavier) highlighting, set the following variable: > + + let kconfig_syntax_heavy = 1 LACE *lace.vim* *ft-lace-syntax* diff --git a/runtime/ftplugin/kconfig.vim b/runtime/ftplugin/kconfig.vim index 767490701b..1c2857ec50 100644 --- a/runtime/ftplugin/kconfig.vim +++ b/runtime/ftplugin/kconfig.vim @@ -2,7 +2,7 @@ " Vim syntax file " Maintainer: Christian Brabandt " Previous Maintainer: Nikolai Weibull -" Latest Revision: 2024-04-12 +" Latest Revision: 2025 Jan 20 " License: Vim (see :h license) " Repository: https://github.com/chrisbra/vim-kconfig @@ -19,4 +19,5 @@ setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql " For matchit.vim if exists("loaded_matchit") let b:match_words = '^\:\,^\:\,^\:\' + let b:undo_ftplugin .= "| unlet! b:match_words" endif diff --git a/runtime/syntax/kconfig.vim b/runtime/syntax/kconfig.vim index 0aecc00060..64a47a5995 100644 --- a/runtime/syntax/kconfig.vim +++ b/runtime/syntax/kconfig.vim @@ -1,7 +1,7 @@ " Vim syntax file " Maintainer: Christian Brabandt " Previous Maintainer: Nikolai Weibull -" Latest Revision: 2024-07-19 +" Latest Revision: 2025 Jan 20 " License: Vim (see :h license) " Repository: https://github.com/chrisbra/vim-kconfig @@ -12,722 +12,722 @@ endif let s:cpo_save = &cpo set cpo&vim +exe "syn sync minlines=" . get(g:, 'kconfig_minlines', 50) + if exists("g:kconfig_syntax_heavy") -syn match kconfigBegin '^' nextgroup=kconfigKeyword - \ skipwhite - -syn keyword kconfigTodo contained TODO FIXME XXX NOTE - -syn match kconfigComment display '#.*$' contains=kconfigTodo - -syn keyword kconfigKeyword config nextgroup=kconfigSymbol - \ skipwhite - -syn keyword kconfigKeyword menuconfig nextgroup=kconfigSymbol - \ skipwhite - -syn keyword kconfigKeyword comment menu mainmenu - \ nextgroup=kconfigKeywordPrompt - \ skipwhite - -syn keyword kconfigKeyword choice - \ nextgroup=@kconfigConfigOptions - \ skipwhite skipnl - -syn keyword kconfigKeyword endmenu endchoice - -syn keyword kconfigPreProc source - \ nextgroup=kconfigPath - \ skipwhite - -" TODO: This is a hack. The who .*Expr stuff should really be generated so -" that we can reuse it for various nextgroups. -syn keyword kconfigConditional if endif - \ nextgroup=@kconfigConfigOptionIfExpr - \ skipwhite - -syn match kconfigKeywordPrompt '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=@kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigPath '"[^"\\]*\%(\\.[^"\\]*\)*"\|\S\+' - \ contained - -syn match kconfigSymbol '\<\k\+\>' - \ contained - \ nextgroup=@kconfigConfigOptions - \ skipwhite skipnl - -" FIXME: There is – probably – no reason to cluster these instead of just -" defining them in the same group. -syn cluster kconfigConfigOptions contains=kconfigTypeDefinition, - \ kconfigInputPrompt, - \ kconfigDefaultValue, - \ kconfigDependencies, - \ kconfigReverseDependencies, - \ kconfigNumericalRanges, - \ kconfigHelpText, - \ kconfigDefBool, - \ kconfigOptional - -syn keyword kconfigTypeDefinition bool boolean tristate string hex int - \ contained - \ nextgroup=kconfigTypeDefPrompt, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigTypeDefPrompt '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigTypeDefPrompt "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn keyword kconfigInputPrompt prompt - \ contained - \ nextgroup=kconfigPromptPrompt - \ skipwhite - -syn match kconfigPromptPrompt '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigPromptPrompt "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn keyword kconfigDefaultValue default - \ contained - \ nextgroup=@kconfigConfigOptionExpr - \ skipwhite - -syn match kconfigDependencies 'depends on\|requires' - \ contained - \ nextgroup=@kconfigConfigOptionIfExpr - \ skipwhite - -syn keyword kconfigReverseDependencies select - \ contained - \ nextgroup=@kconfigRevDepSymbol - \ skipwhite - -syn cluster kconfigRevDepSymbol contains=kconfigRevDepCSymbol, - \ kconfigRevDepNCSymbol - -syn match kconfigRevDepCSymbol '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigRevDepCSymbol "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigRevDepNCSymbol '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn keyword kconfigNumericalRanges range - \ contained - \ nextgroup=@kconfigRangeSymbol - \ skipwhite - -syn cluster kconfigRangeSymbol contains=kconfigRangeCSymbol, - \ kconfigRangeNCSymbol - -syn match kconfigRangeCSymbol '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=@kconfigRangeSymbol2 - \ skipwhite skipnl - -syn match kconfigRangeCSymbol "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=@kconfigRangeSymbol2 - \ skipwhite skipnl - -syn match kconfigRangeNCSymbol '\<\k\+\>' - \ contained - \ nextgroup=@kconfigRangeSymbol2 - \ skipwhite skipnl - -syn cluster kconfigRangeSymbol2 contains=kconfigRangeCSymbol2, - \ kconfigRangeNCSymbol2 - -syn match kconfigRangeCSymbol2 "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigRangeNCSymbol2 '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn region kconfigHelpText contained - \ matchgroup=kconfigConfigOption - \ start='\%(help\|---help---\)\ze\s*\n\z(\s\+\)' - \ skip='^$' - \ end='^\z1\@!' - \ nextgroup=@kconfigConfigOptions - \ skipwhite skipnl - -" XXX: Undocumented -syn keyword kconfigDefBool def_bool - \ contained - \ nextgroup=@kconfigDefBoolSymbol - \ skipwhite - -syn cluster kconfigDefBoolSymbol contains=kconfigDefBoolCSymbol, - \ kconfigDefBoolNCSymbol - -syn match kconfigDefBoolCSymbol '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigDefBoolCSymbol "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigDefBoolNCSymbol '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ @kconfigConfigOptions - \ skipwhite skipnl - -" XXX: This is actually only a valid option for “choice”, but treating it -" specially would require a lot of extra groups. -syn keyword kconfigOptional optional - \ contained - \ nextgroup=@kconfigConfigOptions - \ skipwhite skipnl - -syn keyword kconfigConfigOptionIf if - \ contained - \ nextgroup=@kconfigConfigOptionIfExpr - \ skipwhite - -syn cluster kconfigConfigOptionIfExpr contains=@kconfigConfOptIfExprSym, - \ kconfigConfOptIfExprNeg, - \ kconfigConfOptIfExprGroup - -syn cluster kconfigConfOptIfExprSym contains=kconfigConfOptIfExprCSym, - \ kconfigConfOptIfExprNCSym - -syn match kconfigConfOptIfExprCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=@kconfigConfigOptions, - \ kconfigConfOptIfExprAnd, - \ kconfigConfOptIfExprOr, - \ kconfigConfOptIfExprEq, - \ kconfigConfOptIfExprNEq - \ skipwhite skipnl - -syn match kconfigConfOptIfExprCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=@kconfigConfigOptions, - \ kconfigConfOptIfExprAnd, - \ kconfigConfOptIfExprOr, - \ kconfigConfOptIfExprEq, - \ kconfigConfOptIfExprNEq - \ skipwhite skipnl - -syn match kconfigConfOptIfExprNCSym '\<\k\+\>' - \ contained - \ nextgroup=@kconfigConfigOptions, - \ kconfigConfOptIfExprAnd, - \ kconfigConfOptIfExprOr, - \ kconfigConfOptIfExprEq, - \ kconfigConfOptIfExprNEq - \ skipwhite skipnl - -syn cluster kconfigConfOptIfExprSym2 contains=kconfigConfOptIfExprCSym2, - \ kconfigConfOptIfExprNCSym2 - -syn match kconfigConfOptIfExprEq '=' - \ contained - \ nextgroup=@kconfigConfOptIfExprSym2 - \ skipwhite - -syn match kconfigConfOptIfExprNEq '!=' - \ contained - \ nextgroup=@kconfigConfOptIfExprSym2 - \ skipwhite - -syn match kconfigConfOptIfExprCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=@kconfigConfigOptions, - \ kconfigConfOptIfExprAnd, - \ kconfigConfOptIfExprOr - \ skipwhite skipnl - -syn match kconfigConfOptIfExprNCSym2 '\<\k\+\>' - \ contained - \ nextgroup=@kconfigConfigOptions, - \ kconfigConfOptIfExprAnd, - \ kconfigConfOptIfExprOr - \ skipwhite skipnl - -syn match kconfigConfOptIfExprNeg '!' - \ contained - \ nextgroup=@kconfigConfigOptionIfExpr - \ skipwhite - -syn match kconfigConfOptIfExprAnd '&&' - \ contained - \ nextgroup=@kconfigConfigOptionIfExpr - \ skipwhite - -syn match kconfigConfOptIfExprOr '||' - \ contained - \ nextgroup=@kconfigConfigOptionIfExpr - \ skipwhite - -syn match kconfigConfOptIfExprGroup '(' - \ contained - \ nextgroup=@kconfigConfigOptionIfGExp - \ skipwhite - -" TODO: hm, this kind of recursion doesn't work right. We need another set of -" expressions that have kconfigConfigOPtionIfGExp as nextgroup and a matcher -" for '(' that sets it all off. -syn cluster kconfigConfigOptionIfGExp contains=@kconfigConfOptIfGExpSym, - \ kconfigConfOptIfGExpNeg, - \ kconfigConfOptIfExprGroup - -syn cluster kconfigConfOptIfGExpSym contains=kconfigConfOptIfGExpCSym, - \ kconfigConfOptIfGExpNCSym - -syn match kconfigConfOptIfGExpCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=@kconfigConfigIf, - \ kconfigConfOptIfGExpAnd, - \ kconfigConfOptIfGExpOr, - \ kconfigConfOptIfGExpEq, - \ kconfigConfOptIfGExpNEq - \ skipwhite skipnl - -syn match kconfigConfOptIfGExpCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=@kconfigConfigIf, - \ kconfigConfOptIfGExpAnd, - \ kconfigConfOptIfGExpOr, - \ kconfigConfOptIfGExpEq, - \ kconfigConfOptIfGExpNEq - \ skipwhite skipnl - -syn match kconfigConfOptIfGExpNCSym '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfOptIfExprGrpE, - \ kconfigConfOptIfGExpAnd, - \ kconfigConfOptIfGExpOr, - \ kconfigConfOptIfGExpEq, - \ kconfigConfOptIfGExpNEq - \ skipwhite skipnl - -syn cluster kconfigConfOptIfGExpSym2 contains=kconfigConfOptIfGExpCSym2, - \ kconfigConfOptIfGExpNCSym2 - -syn match kconfigConfOptIfGExpEq '=' - \ contained - \ nextgroup=@kconfigConfOptIfGExpSym2 - \ skipwhite - -syn match kconfigConfOptIfGExpNEq '!=' - \ contained - \ nextgroup=@kconfigConfOptIfGExpSym2 - \ skipwhite - -syn match kconfigConfOptIfGExpCSym2 '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfOptIfExprGrpE, - \ kconfigConfOptIfGExpAnd, - \ kconfigConfOptIfGExpOr - \ skipwhite skipnl - -syn match kconfigConfOptIfGExpCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfOptIfExprGrpE, - \ kconfigConfOptIfGExpAnd, - \ kconfigConfOptIfGExpOr - \ skipwhite skipnl - -syn match kconfigConfOptIfGExpNCSym2 '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfOptIfExprGrpE, - \ kconfigConfOptIfGExpAnd, - \ kconfigConfOptIfGExpOr - \ skipwhite skipnl - -syn match kconfigConfOptIfGExpNeg '!' - \ contained - \ nextgroup=@kconfigConfigOptionIfGExp - \ skipwhite - -syn match kconfigConfOptIfGExpAnd '&&' - \ contained - \ nextgroup=@kconfigConfigOptionIfGExp - \ skipwhite - -syn match kconfigConfOptIfGExpOr '||' - \ contained - \ nextgroup=@kconfigConfigOptionIfGExp - \ skipwhite - -syn match kconfigConfOptIfExprGrpE ')' - \ contained - \ nextgroup=@kconfigConfigOptions, - \ kconfigConfOptIfExprAnd, - \ kconfigConfOptIfExprOr - \ skipwhite skipnl - - -syn cluster kconfigConfigOptionExpr contains=@kconfigConfOptExprSym, - \ kconfigConfOptExprNeg, - \ kconfigConfOptExprGroup - -syn cluster kconfigConfOptExprSym contains=kconfigConfOptExprCSym, - \ kconfigConfOptExprNCSym - -syn match kconfigConfOptExprCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr, - \ kconfigConfOptExprEq, - \ kconfigConfOptExprNEq, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigConfOptExprCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr, - \ kconfigConfOptExprEq, - \ kconfigConfOptExprNEq, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigConfOptExprNCSym '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr, - \ kconfigConfOptExprEq, - \ kconfigConfOptExprNEq, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn cluster kconfigConfOptExprSym2 contains=kconfigConfOptExprCSym2, - \ kconfigConfOptExprNCSym2 - -syn match kconfigConfOptExprEq '=' - \ contained - \ nextgroup=@kconfigConfOptExprSym2 - \ skipwhite - -syn match kconfigConfOptExprNEq '!=' - \ contained - \ nextgroup=@kconfigConfOptExprSym2 - \ skipwhite - -syn match kconfigConfOptExprCSym2 '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigConfOptExprCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigConfOptExprNCSym2 '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr, - \ @kconfigConfigOptions - \ skipwhite skipnl - -syn match kconfigConfOptExprNeg '!' - \ contained - \ nextgroup=@kconfigConfigOptionExpr - \ skipwhite - -syn match kconfigConfOptExprAnd '&&' - \ contained - \ nextgroup=@kconfigConfigOptionExpr - \ skipwhite - -syn match kconfigConfOptExprOr '||' - \ contained - \ nextgroup=@kconfigConfigOptionExpr - \ skipwhite - -syn match kconfigConfOptExprGroup '(' - \ contained - \ nextgroup=@kconfigConfigOptionGExp - \ skipwhite - -syn cluster kconfigConfigOptionGExp contains=@kconfigConfOptGExpSym, - \ kconfigConfOptGExpNeg, - \ kconfigConfOptGExpGroup - -syn cluster kconfigConfOptGExpSym contains=kconfigConfOptGExpCSym, - \ kconfigConfOptGExpNCSym - -syn match kconfigConfOptGExpCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfOptExprGrpE, - \ kconfigConfOptGExpAnd, - \ kconfigConfOptGExpOr, - \ kconfigConfOptGExpEq, - \ kconfigConfOptGExpNEq - \ skipwhite skipnl - -syn match kconfigConfOptGExpCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfOptExprGrpE, - \ kconfigConfOptGExpAnd, - \ kconfigConfOptGExpOr, - \ kconfigConfOptGExpEq, - \ kconfigConfOptGExpNEq - \ skipwhite skipnl - -syn match kconfigConfOptGExpNCSym '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfOptExprGrpE, - \ kconfigConfOptGExpAnd, - \ kconfigConfOptGExpOr, - \ kconfigConfOptGExpEq, - \ kconfigConfOptGExpNEq - \ skipwhite skipnl - -syn cluster kconfigConfOptGExpSym2 contains=kconfigConfOptGExpCSym2, - \ kconfigConfOptGExpNCSym2 - -syn match kconfigConfOptGExpEq '=' - \ contained - \ nextgroup=@kconfigConfOptGExpSym2 - \ skipwhite - -syn match kconfigConfOptGExpNEq '!=' - \ contained - \ nextgroup=@kconfigConfOptGExpSym2 - \ skipwhite - -syn match kconfigConfOptGExpCSym2 '"[^"\\]*\%(\\.[^"\\]*\)*"' - \ contained - \ nextgroup=kconfigConfOptExprGrpE, - \ kconfigConfOptGExpAnd, - \ kconfigConfOptGExpOr - \ skipwhite skipnl - -syn match kconfigConfOptGExpCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" - \ contained - \ nextgroup=kconfigConfOptExprGrpE, - \ kconfigConfOptGExpAnd, - \ kconfigConfOptGExpOr - \ skipwhite skipnl - -syn match kconfigConfOptGExpNCSym2 '\<\k\+\>' - \ contained - \ nextgroup=kconfigConfOptExprGrpE, - \ kconfigConfOptGExpAnd, - \ kconfigConfOptGExpOr - \ skipwhite skipnl - -syn match kconfigConfOptGExpNeg '!' - \ contained - \ nextgroup=@kconfigConfigOptionGExp - \ skipwhite - -syn match kconfigConfOptGExpAnd '&&' - \ contained - \ nextgroup=@kconfigConfigOptionGExp - \ skipwhite - -syn match kconfigConfOptGExpOr '||' - \ contained - \ nextgroup=@kconfigConfigOptionGExp - \ skipwhite - -syn match kconfigConfOptExprGrpE ')' - \ contained - \ nextgroup=kconfigConfigOptionIf, - \ kconfigConfOptExprAnd, - \ kconfigConfOptExprOr - \ skipwhite skipnl - -syn sync minlines=50 - -hi def link kconfigTodo Todo -hi def link kconfigComment Comment -hi def link kconfigKeyword Keyword -hi def link kconfigPreProc PreProc -hi def link kconfigConditional Conditional -hi def link kconfigPrompt String -hi def link kconfigKeywordPrompt kconfigPrompt -hi def link kconfigPath String -hi def link kconfigSymbol String -hi def link kconfigConstantSymbol Constant -hi def link kconfigConfigOption Type -hi def link kconfigTypeDefinition kconfigConfigOption -hi def link kconfigTypeDefPrompt kconfigPrompt -hi def link kconfigInputPrompt kconfigConfigOption -hi def link kconfigPromptPrompt kconfigPrompt -hi def link kconfigDefaultValue kconfigConfigOption -hi def link kconfigDependencies kconfigConfigOption -hi def link kconfigReverseDependencies kconfigConfigOption -hi def link kconfigRevDepCSymbol kconfigConstantSymbol -hi def link kconfigRevDepNCSymbol kconfigSymbol -hi def link kconfigNumericalRanges kconfigConfigOption -hi def link kconfigRangeCSymbol kconfigConstantSymbol -hi def link kconfigRangeNCSymbol kconfigSymbol -hi def link kconfigRangeCSymbol2 kconfigConstantSymbol -hi def link kconfigRangeNCSymbol2 kconfigSymbol -hi def link kconfigHelpText Normal -hi def link kconfigDefBool kconfigConfigOption -hi def link kconfigDefBoolCSymbol kconfigConstantSymbol -hi def link kconfigDefBoolNCSymbol kconfigSymbol -hi def link kconfigOptional kconfigConfigOption -hi def link kconfigConfigOptionIf Conditional -hi def link kconfigConfOptIfExprCSym kconfigConstantSymbol -hi def link kconfigConfOptIfExprNCSym kconfigSymbol -hi def link kconfigOperator Operator -hi def link kconfigConfOptIfExprEq kconfigOperator -hi def link kconfigConfOptIfExprNEq kconfigOperator -hi def link kconfigConfOptIfExprCSym2 kconfigConstantSymbol -hi def link kconfigConfOptIfExprNCSym2 kconfigSymbol -hi def link kconfigConfOptIfExprNeg kconfigOperator -hi def link kconfigConfOptIfExprAnd kconfigOperator -hi def link kconfigConfOptIfExprOr kconfigOperator -hi def link kconfigDelimiter Delimiter -hi def link kconfigConfOptIfExprGroup kconfigDelimiter -hi def link kconfigConfOptIfGExpCSym kconfigConstantSymbol -hi def link kconfigConfOptIfGExpNCSym kconfigSymbol -hi def link kconfigConfOptIfGExpEq kconfigOperator -hi def link kconfigConfOptIfGExpNEq kconfigOperator -hi def link kconfigConfOptIfGExpCSym2 kconfigConstantSymbol -hi def link kconfigConfOptIfGExpNCSym2 kconfigSymbol -hi def link kconfigConfOptIfGExpNeg kconfigOperator -hi def link kconfigConfOptIfGExpAnd kconfigOperator -hi def link kconfigConfOptIfGExpOr kconfigOperator -hi def link kconfigConfOptIfExprGrpE kconfigDelimiter -hi def link kconfigConfOptExprCSym kconfigConstantSymbol -hi def link kconfigConfOptExprNCSym kconfigSymbol -hi def link kconfigConfOptExprEq kconfigOperator -hi def link kconfigConfOptExprNEq kconfigOperator -hi def link kconfigConfOptExprCSym2 kconfigConstantSymbol -hi def link kconfigConfOptExprNCSym2 kconfigSymbol -hi def link kconfigConfOptExprNeg kconfigOperator -hi def link kconfigConfOptExprAnd kconfigOperator -hi def link kconfigConfOptExprOr kconfigOperator -hi def link kconfigConfOptExprGroup kconfigDelimiter -hi def link kconfigConfOptGExpCSym kconfigConstantSymbol -hi def link kconfigConfOptGExpNCSym kconfigSymbol -hi def link kconfigConfOptGExpEq kconfigOperator -hi def link kconfigConfOptGExpNEq kconfigOperator -hi def link kconfigConfOptGExpCSym2 kconfigConstantSymbol -hi def link kconfigConfOptGExpNCSym2 kconfigSymbol -hi def link kconfigConfOptGExpNeg kconfigOperator -hi def link kconfigConfOptGExpAnd kconfigOperator -hi def link kconfigConfOptGExpOr kconfigOperator -hi def link kconfigConfOptExprGrpE kconfigConfOptIfExprGroup + syn match kconfigBegin '^' nextgroup=kconfigKeyword + \ skipwhite + + syn keyword kconfigTodo contained TODO FIXME XXX NOTE + + syn match kconfigComment display '#.*$' contains=kconfigTodo + + syn keyword kconfigKeyword config nextgroup=kconfigSymbol + \ skipwhite + + syn keyword kconfigKeyword menuconfig nextgroup=kconfigSymbol + \ skipwhite + + syn keyword kconfigKeyword comment menu mainmenu + \ nextgroup=kconfigKeywordPrompt + \ skipwhite + + syn keyword kconfigKeyword choice + \ nextgroup=@kconfigConfigOptions + \ skipwhite skipnl + + syn keyword kconfigKeyword endmenu endchoice + + syn keyword kconfigPreProc source + \ nextgroup=kconfigPath + \ skipwhite + + " TODO: This is a hack. The who .*Expr stuff should really be generated so + " that we can reuse it for various nextgroups. + syn keyword kconfigConditional if endif + \ nextgroup=@kconfigConfigOptionIfExpr + \ skipwhite + + syn match kconfigKeywordPrompt '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=@kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigPath '"[^"\\]*\%(\\.[^"\\]*\)*"\|\S\+' + \ contained + + syn match kconfigSymbol '\<\k\+\>' + \ contained + \ nextgroup=@kconfigConfigOptions + \ skipwhite skipnl + + " FIXME: There is – probably – no reason to cluster these instead of just + " defining them in the same group. + syn cluster kconfigConfigOptions contains=kconfigTypeDefinition, + \ kconfigInputPrompt, + \ kconfigDefaultValue, + \ kconfigDependencies, + \ kconfigReverseDependencies, + \ kconfigNumericalRanges, + \ kconfigHelpText, + \ kconfigDefBool, + \ kconfigOptional + + syn keyword kconfigTypeDefinition bool boolean tristate string hex int + \ contained + \ nextgroup=kconfigTypeDefPrompt, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigTypeDefPrompt '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigTypeDefPrompt "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn keyword kconfigInputPrompt prompt + \ contained + \ nextgroup=kconfigPromptPrompt + \ skipwhite + + syn match kconfigPromptPrompt '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigPromptPrompt "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn keyword kconfigDefaultValue default + \ contained + \ nextgroup=@kconfigConfigOptionExpr + \ skipwhite + + syn match kconfigDependencies 'depends on\|requires' + \ contained + \ nextgroup=@kconfigConfigOptionIfExpr + \ skipwhite + + syn keyword kconfigReverseDependencies select + \ contained + \ nextgroup=@kconfigRevDepSymbol + \ skipwhite + + syn cluster kconfigRevDepSymbol contains=kconfigRevDepCSymbol, + \ kconfigRevDepNCSymbol + + syn match kconfigRevDepCSymbol '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigRevDepCSymbol "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigRevDepNCSymbol '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn keyword kconfigNumericalRanges range + \ contained + \ nextgroup=@kconfigRangeSymbol + \ skipwhite + + syn cluster kconfigRangeSymbol contains=kconfigRangeCSymbol, + \ kconfigRangeNCSymbol + + syn match kconfigRangeCSymbol '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=@kconfigRangeSymbol2 + \ skipwhite skipnl + + syn match kconfigRangeCSymbol "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=@kconfigRangeSymbol2 + \ skipwhite skipnl + + syn match kconfigRangeNCSymbol '\<\k\+\>' + \ contained + \ nextgroup=@kconfigRangeSymbol2 + \ skipwhite skipnl + + syn cluster kconfigRangeSymbol2 contains=kconfigRangeCSymbol2, + \ kconfigRangeNCSymbol2 + + syn match kconfigRangeCSymbol2 "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigRangeNCSymbol2 '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn region kconfigHelpText contained + \ matchgroup=kconfigConfigOption + \ start='\%(help\|---help---\)\ze\s*\n\z(\s\+\)' + \ skip='^$' + \ end='^\z1\@!' + \ nextgroup=@kconfigConfigOptions + \ skipwhite skipnl + + " XXX: Undocumented + syn keyword kconfigDefBool def_bool + \ contained + \ nextgroup=@kconfigDefBoolSymbol + \ skipwhite + + syn cluster kconfigDefBoolSymbol contains=kconfigDefBoolCSymbol, + \ kconfigDefBoolNCSymbol + + syn match kconfigDefBoolCSymbol '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigDefBoolCSymbol "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigDefBoolNCSymbol '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ @kconfigConfigOptions + \ skipwhite skipnl + + " XXX: This is actually only a valid option for “choice”, but treating it + " specially would require a lot of extra groups. + syn keyword kconfigOptional optional + \ contained + \ nextgroup=@kconfigConfigOptions + \ skipwhite skipnl + + syn keyword kconfigConfigOptionIf if + \ contained + \ nextgroup=@kconfigConfigOptionIfExpr + \ skipwhite + + syn cluster kconfigConfigOptionIfExpr contains=@kconfigConfOptIfExprSym, + \ kconfigConfOptIfExprNeg, + \ kconfigConfOptIfExprGroup + + syn cluster kconfigConfOptIfExprSym contains=kconfigConfOptIfExprCSym, + \ kconfigConfOptIfExprNCSym + + syn match kconfigConfOptIfExprCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=@kconfigConfigOptions, + \ kconfigConfOptIfExprAnd, + \ kconfigConfOptIfExprOr, + \ kconfigConfOptIfExprEq, + \ kconfigConfOptIfExprNEq + \ skipwhite skipnl + + syn match kconfigConfOptIfExprCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=@kconfigConfigOptions, + \ kconfigConfOptIfExprAnd, + \ kconfigConfOptIfExprOr, + \ kconfigConfOptIfExprEq, + \ kconfigConfOptIfExprNEq + \ skipwhite skipnl + + syn match kconfigConfOptIfExprNCSym '\<\k\+\>' + \ contained + \ nextgroup=@kconfigConfigOptions, + \ kconfigConfOptIfExprAnd, + \ kconfigConfOptIfExprOr, + \ kconfigConfOptIfExprEq, + \ kconfigConfOptIfExprNEq + \ skipwhite skipnl + + syn cluster kconfigConfOptIfExprSym2 contains=kconfigConfOptIfExprCSym2, + \ kconfigConfOptIfExprNCSym2 + + syn match kconfigConfOptIfExprEq '=' + \ contained + \ nextgroup=@kconfigConfOptIfExprSym2 + \ skipwhite + + syn match kconfigConfOptIfExprNEq '!=' + \ contained + \ nextgroup=@kconfigConfOptIfExprSym2 + \ skipwhite + + syn match kconfigConfOptIfExprCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=@kconfigConfigOptions, + \ kconfigConfOptIfExprAnd, + \ kconfigConfOptIfExprOr + \ skipwhite skipnl + + syn match kconfigConfOptIfExprNCSym2 '\<\k\+\>' + \ contained + \ nextgroup=@kconfigConfigOptions, + \ kconfigConfOptIfExprAnd, + \ kconfigConfOptIfExprOr + \ skipwhite skipnl + + syn match kconfigConfOptIfExprNeg '!' + \ contained + \ nextgroup=@kconfigConfigOptionIfExpr + \ skipwhite + + syn match kconfigConfOptIfExprAnd '&&' + \ contained + \ nextgroup=@kconfigConfigOptionIfExpr + \ skipwhite + + syn match kconfigConfOptIfExprOr '||' + \ contained + \ nextgroup=@kconfigConfigOptionIfExpr + \ skipwhite + + syn match kconfigConfOptIfExprGroup '(' + \ contained + \ nextgroup=@kconfigConfigOptionIfGExp + \ skipwhite + + " TODO: hm, this kind of recursion doesn't work right. We need another set of + " expressions that have kconfigConfigOPtionIfGExp as nextgroup and a matcher + " for '(' that sets it all off. + syn cluster kconfigConfigOptionIfGExp contains=@kconfigConfOptIfGExpSym, + \ kconfigConfOptIfGExpNeg, + \ kconfigConfOptIfExprGroup + + syn cluster kconfigConfOptIfGExpSym contains=kconfigConfOptIfGExpCSym, + \ kconfigConfOptIfGExpNCSym + + syn match kconfigConfOptIfGExpCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=@kconfigConfigIf, + \ kconfigConfOptIfGExpAnd, + \ kconfigConfOptIfGExpOr, + \ kconfigConfOptIfGExpEq, + \ kconfigConfOptIfGExpNEq + \ skipwhite skipnl + + syn match kconfigConfOptIfGExpCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=@kconfigConfigIf, + \ kconfigConfOptIfGExpAnd, + \ kconfigConfOptIfGExpOr, + \ kconfigConfOptIfGExpEq, + \ kconfigConfOptIfGExpNEq + \ skipwhite skipnl + + syn match kconfigConfOptIfGExpNCSym '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfOptIfExprGrpE, + \ kconfigConfOptIfGExpAnd, + \ kconfigConfOptIfGExpOr, + \ kconfigConfOptIfGExpEq, + \ kconfigConfOptIfGExpNEq + \ skipwhite skipnl + + syn cluster kconfigConfOptIfGExpSym2 contains=kconfigConfOptIfGExpCSym2, + \ kconfigConfOptIfGExpNCSym2 + + syn match kconfigConfOptIfGExpEq '=' + \ contained + \ nextgroup=@kconfigConfOptIfGExpSym2 + \ skipwhite + + syn match kconfigConfOptIfGExpNEq '!=' + \ contained + \ nextgroup=@kconfigConfOptIfGExpSym2 + \ skipwhite + + syn match kconfigConfOptIfGExpCSym2 '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfOptIfExprGrpE, + \ kconfigConfOptIfGExpAnd, + \ kconfigConfOptIfGExpOr + \ skipwhite skipnl + + syn match kconfigConfOptIfGExpCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfOptIfExprGrpE, + \ kconfigConfOptIfGExpAnd, + \ kconfigConfOptIfGExpOr + \ skipwhite skipnl + + syn match kconfigConfOptIfGExpNCSym2 '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfOptIfExprGrpE, + \ kconfigConfOptIfGExpAnd, + \ kconfigConfOptIfGExpOr + \ skipwhite skipnl + + syn match kconfigConfOptIfGExpNeg '!' + \ contained + \ nextgroup=@kconfigConfigOptionIfGExp + \ skipwhite + + syn match kconfigConfOptIfGExpAnd '&&' + \ contained + \ nextgroup=@kconfigConfigOptionIfGExp + \ skipwhite + + syn match kconfigConfOptIfGExpOr '||' + \ contained + \ nextgroup=@kconfigConfigOptionIfGExp + \ skipwhite + + syn match kconfigConfOptIfExprGrpE ')' + \ contained + \ nextgroup=@kconfigConfigOptions, + \ kconfigConfOptIfExprAnd, + \ kconfigConfOptIfExprOr + \ skipwhite skipnl + + + syn cluster kconfigConfigOptionExpr contains=@kconfigConfOptExprSym, + \ kconfigConfOptExprNeg, + \ kconfigConfOptExprGroup + + syn cluster kconfigConfOptExprSym contains=kconfigConfOptExprCSym, + \ kconfigConfOptExprNCSym + + syn match kconfigConfOptExprCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr, + \ kconfigConfOptExprEq, + \ kconfigConfOptExprNEq, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigConfOptExprCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr, + \ kconfigConfOptExprEq, + \ kconfigConfOptExprNEq, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigConfOptExprNCSym '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr, + \ kconfigConfOptExprEq, + \ kconfigConfOptExprNEq, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn cluster kconfigConfOptExprSym2 contains=kconfigConfOptExprCSym2, + \ kconfigConfOptExprNCSym2 + + syn match kconfigConfOptExprEq '=' + \ contained + \ nextgroup=@kconfigConfOptExprSym2 + \ skipwhite + + syn match kconfigConfOptExprNEq '!=' + \ contained + \ nextgroup=@kconfigConfOptExprSym2 + \ skipwhite + + syn match kconfigConfOptExprCSym2 '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigConfOptExprCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigConfOptExprNCSym2 '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr, + \ @kconfigConfigOptions + \ skipwhite skipnl + + syn match kconfigConfOptExprNeg '!' + \ contained + \ nextgroup=@kconfigConfigOptionExpr + \ skipwhite + + syn match kconfigConfOptExprAnd '&&' + \ contained + \ nextgroup=@kconfigConfigOptionExpr + \ skipwhite + + syn match kconfigConfOptExprOr '||' + \ contained + \ nextgroup=@kconfigConfigOptionExpr + \ skipwhite + + syn match kconfigConfOptExprGroup '(' + \ contained + \ nextgroup=@kconfigConfigOptionGExp + \ skipwhite + + syn cluster kconfigConfigOptionGExp contains=@kconfigConfOptGExpSym, + \ kconfigConfOptGExpNeg, + \ kconfigConfOptGExpGroup + + syn cluster kconfigConfOptGExpSym contains=kconfigConfOptGExpCSym, + \ kconfigConfOptGExpNCSym + + syn match kconfigConfOptGExpCSym '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfOptExprGrpE, + \ kconfigConfOptGExpAnd, + \ kconfigConfOptGExpOr, + \ kconfigConfOptGExpEq, + \ kconfigConfOptGExpNEq + \ skipwhite skipnl + + syn match kconfigConfOptGExpCSym "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfOptExprGrpE, + \ kconfigConfOptGExpAnd, + \ kconfigConfOptGExpOr, + \ kconfigConfOptGExpEq, + \ kconfigConfOptGExpNEq + \ skipwhite skipnl + + syn match kconfigConfOptGExpNCSym '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfOptExprGrpE, + \ kconfigConfOptGExpAnd, + \ kconfigConfOptGExpOr, + \ kconfigConfOptGExpEq, + \ kconfigConfOptGExpNEq + \ skipwhite skipnl + + syn cluster kconfigConfOptGExpSym2 contains=kconfigConfOptGExpCSym2, + \ kconfigConfOptGExpNCSym2 + + syn match kconfigConfOptGExpEq '=' + \ contained + \ nextgroup=@kconfigConfOptGExpSym2 + \ skipwhite + + syn match kconfigConfOptGExpNEq '!=' + \ contained + \ nextgroup=@kconfigConfOptGExpSym2 + \ skipwhite + + syn match kconfigConfOptGExpCSym2 '"[^"\\]*\%(\\.[^"\\]*\)*"' + \ contained + \ nextgroup=kconfigConfOptExprGrpE, + \ kconfigConfOptGExpAnd, + \ kconfigConfOptGExpOr + \ skipwhite skipnl + + syn match kconfigConfOptGExpCSym2 "'[^'\\]*\%(\\.[^'\\]*\)*'" + \ contained + \ nextgroup=kconfigConfOptExprGrpE, + \ kconfigConfOptGExpAnd, + \ kconfigConfOptGExpOr + \ skipwhite skipnl + + syn match kconfigConfOptGExpNCSym2 '\<\k\+\>' + \ contained + \ nextgroup=kconfigConfOptExprGrpE, + \ kconfigConfOptGExpAnd, + \ kconfigConfOptGExpOr + \ skipwhite skipnl + + syn match kconfigConfOptGExpNeg '!' + \ contained + \ nextgroup=@kconfigConfigOptionGExp + \ skipwhite + + syn match kconfigConfOptGExpAnd '&&' + \ contained + \ nextgroup=@kconfigConfigOptionGExp + \ skipwhite + + syn match kconfigConfOptGExpOr '||' + \ contained + \ nextgroup=@kconfigConfigOptionGExp + \ skipwhite + + syn match kconfigConfOptExprGrpE ')' + \ contained + \ nextgroup=kconfigConfigOptionIf, + \ kconfigConfOptExprAnd, + \ kconfigConfOptExprOr + \ skipwhite skipnl + + hi def link kconfigTodo Todo + hi def link kconfigComment Comment + hi def link kconfigKeyword Keyword + hi def link kconfigPreProc PreProc + hi def link kconfigConditional Conditional + hi def link kconfigPrompt String + hi def link kconfigKeywordPrompt kconfigPrompt + hi def link kconfigPath String + hi def link kconfigSymbol String + hi def link kconfigConstantSymbol Constant + hi def link kconfigConfigOption Type + hi def link kconfigTypeDefinition kconfigConfigOption + hi def link kconfigTypeDefPrompt kconfigPrompt + hi def link kconfigInputPrompt kconfigConfigOption + hi def link kconfigPromptPrompt kconfigPrompt + hi def link kconfigDefaultValue kconfigConfigOption + hi def link kconfigDependencies kconfigConfigOption + hi def link kconfigReverseDependencies kconfigConfigOption + hi def link kconfigRevDepCSymbol kconfigConstantSymbol + hi def link kconfigRevDepNCSymbol kconfigSymbol + hi def link kconfigNumericalRanges kconfigConfigOption + hi def link kconfigRangeCSymbol kconfigConstantSymbol + hi def link kconfigRangeNCSymbol kconfigSymbol + hi def link kconfigRangeCSymbol2 kconfigConstantSymbol + hi def link kconfigRangeNCSymbol2 kconfigSymbol + hi def link kconfigHelpText Normal + hi def link kconfigDefBool kconfigConfigOption + hi def link kconfigDefBoolCSymbol kconfigConstantSymbol + hi def link kconfigDefBoolNCSymbol kconfigSymbol + hi def link kconfigOptional kconfigConfigOption + hi def link kconfigConfigOptionIf Conditional + hi def link kconfigConfOptIfExprCSym kconfigConstantSymbol + hi def link kconfigConfOptIfExprNCSym kconfigSymbol + hi def link kconfigOperator Operator + hi def link kconfigConfOptIfExprEq kconfigOperator + hi def link kconfigConfOptIfExprNEq kconfigOperator + hi def link kconfigConfOptIfExprCSym2 kconfigConstantSymbol + hi def link kconfigConfOptIfExprNCSym2 kconfigSymbol + hi def link kconfigConfOptIfExprNeg kconfigOperator + hi def link kconfigConfOptIfExprAnd kconfigOperator + hi def link kconfigConfOptIfExprOr kconfigOperator + hi def link kconfigDelimiter Delimiter + hi def link kconfigConfOptIfExprGroup kconfigDelimiter + hi def link kconfigConfOptIfGExpCSym kconfigConstantSymbol + hi def link kconfigConfOptIfGExpNCSym kconfigSymbol + hi def link kconfigConfOptIfGExpEq kconfigOperator + hi def link kconfigConfOptIfGExpNEq kconfigOperator + hi def link kconfigConfOptIfGExpCSym2 kconfigConstantSymbol + hi def link kconfigConfOptIfGExpNCSym2 kconfigSymbol + hi def link kconfigConfOptIfGExpNeg kconfigOperator + hi def link kconfigConfOptIfGExpAnd kconfigOperator + hi def link kconfigConfOptIfGExpOr kconfigOperator + hi def link kconfigConfOptIfExprGrpE kconfigDelimiter + hi def link kconfigConfOptExprCSym kconfigConstantSymbol + hi def link kconfigConfOptExprNCSym kconfigSymbol + hi def link kconfigConfOptExprEq kconfigOperator + hi def link kconfigConfOptExprNEq kconfigOperator + hi def link kconfigConfOptExprCSym2 kconfigConstantSymbol + hi def link kconfigConfOptExprNCSym2 kconfigSymbol + hi def link kconfigConfOptExprNeg kconfigOperator + hi def link kconfigConfOptExprAnd kconfigOperator + hi def link kconfigConfOptExprOr kconfigOperator + hi def link kconfigConfOptExprGroup kconfigDelimiter + hi def link kconfigConfOptGExpCSym kconfigConstantSymbol + hi def link kconfigConfOptGExpNCSym kconfigSymbol + hi def link kconfigConfOptGExpEq kconfigOperator + hi def link kconfigConfOptGExpNEq kconfigOperator + hi def link kconfigConfOptGExpCSym2 kconfigConstantSymbol + hi def link kconfigConfOptGExpNCSym2 kconfigSymbol + hi def link kconfigConfOptGExpNeg kconfigOperator + hi def link kconfigConfOptGExpAnd kconfigOperator + hi def link kconfigConfOptGExpOr kconfigOperator + hi def link kconfigConfOptExprGrpE kconfigConfOptIfExprGroup else -syn keyword kconfigTodo contained TODO FIXME XXX NOTE + syn keyword kconfigTodo contained TODO FIXME XXX NOTE -syn match kconfigComment display '#.*$' contains=kconfigTodo + syn match kconfigComment display '#.*$' contains=kconfigTodo -syn keyword kconfigKeyword config menuconfig comment mainmenu + syn keyword kconfigKeyword config menuconfig comment mainmenu -syn keyword kconfigConditional menu endmenu choice endchoice if endif + syn keyword kconfigConditional menu endmenu choice endchoice if endif -syn keyword kconfigPreProc source - \ nextgroup=kconfigPath - \ skipwhite + syn keyword kconfigPreProc source + \ nextgroup=kconfigPath + \ skipwhite -syn keyword kconfigTriState y m n + syn keyword kconfigTriState y m n -syn match kconfigSpecialChar contained '\\.' -syn match kconfigSpecialChar '\\$' + syn match kconfigSpecialChar contained '\\.' + syn match kconfigSpecialChar '\\$' -syn region kconfigPath matchgroup=kconfigPath - \ start=+"+ skip=+\\\\\|\\\"+ end=+"+ - \ contains=kconfigSpecialChar + syn region kconfigPath matchgroup=kconfigPath + \ start=+"+ skip=+\\\\\|\\\"+ end=+"+ + \ contains=kconfigSpecialChar -syn region kconfigPath matchgroup=kconfigPath - \ start=+'+ skip=+\\\\\|\\\'+ end=+'+ - \ contains=kconfigSpecialChar + syn region kconfigPath matchgroup=kconfigPath + \ start=+'+ skip=+\\\\\|\\\'+ end=+'+ + \ contains=kconfigSpecialChar -syn match kconfigPath '\S\+' - \ contained + syn match kconfigPath '\S\+' + \ contained -syn region kconfigString matchgroup=kconfigString - \ start=+"+ skip=+\\\\\|\\\"+ end=+"+ - \ contains=kconfigSpecialChar + syn region kconfigString matchgroup=kconfigString + \ start=+"+ skip=+\\\\\|\\\"+ end=+"+ + \ contains=kconfigSpecialChar -syn region kconfigString matchgroup=kconfigString - \ start=+'+ skip=+\\\\\|\\\'+ end=+'+ - \ contains=kconfigSpecialChar + syn region kconfigString matchgroup=kconfigString + \ start=+'+ skip=+\\\\\|\\\'+ end=+'+ + \ contains=kconfigSpecialChar -syn keyword kconfigType bool boolean tristate string hex int + syn keyword kconfigType bool boolean tristate string hex int -syn keyword kconfigOption prompt default requires select range - \ optional -syn match kconfigOption 'depends\%( on\)\=' + syn keyword kconfigOption prompt default requires select range + \ optional + syn match kconfigOption 'depends\%( on\)\=' -syn keyword kconfigMacro def_bool def_tristate + syn keyword kconfigMacro def_bool def_tristate -syn region kconfigHelpText - \ matchgroup=kconfigOption - \ start='\%(help\|---help---\)\ze\s*\n\z(\s\+\)' - \ skip='^$' - \ end='^\z1\@!' + syn region kconfigHelpText + \ matchgroup=kconfigOption + \ start='\%(help\|---help---\)\ze\s*\n\z(\s\+\)' + \ skip='^$' + \ end='^\z1\@!' -hi def link kconfigTodo Todo -hi def link kconfigComment Comment -hi def link kconfigKeyword Keyword -hi def link kconfigConditional Conditional -hi def link kconfigPreProc PreProc -hi def link kconfigTriState Boolean -hi def link kconfigSpecialChar SpecialChar -hi def link kconfigPath String -hi def link kconfigString String -hi def link kconfigType Type -hi def link kconfigOption Identifier -hi def link kconfigHelpText Normal -hi def link kconfigmacro Macro + hi def link kconfigTodo Todo + hi def link kconfigComment Comment + hi def link kconfigKeyword Keyword + hi def link kconfigConditional Conditional + hi def link kconfigPreProc PreProc + hi def link kconfigTriState Boolean + hi def link kconfigSpecialChar SpecialChar + hi def link kconfigPath String + hi def link kconfigString String + hi def link kconfigType Type + hi def link kconfigOption Identifier + hi def link kconfigHelpText Normal + hi def link kconfigmacro Macro endif -- cgit From 05435bf10585e7f850ff44acd63446fbcc56bfe5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 21 Jan 2025 08:10:14 +0800 Subject: vim-patch:c273f1a: runtime(vim): Update base-syntax, match ternary and falsy operators (#32132) fixes: vim/vim#14423 fixes: vim/vim#16227 closes: vim/vim#16484 https://github.com/vim/vim/commit/c273f1ac770e86767206c8193bab659b25d3b41b Co-authored-by: Doug Kearns --- runtime/syntax/vim.vim | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 4038a65440..c4e231d145 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -276,6 +276,10 @@ syn keyword vimAugroupKey contained aug[roup] skipwhite nextgroup=vimAugroupBan syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimRegister,@vimContinue,vim9Comment,vimVar,vimBoolean,vimNull syn match vimOper "\a\@=\|<=\|=\~\|!\~\|>\|<\)[?#]\=" skipwhite nextgroup=vimString,vimSpecFile syn match vimOper "\" skipwhite nextgroup=vimString,vimSpecFile @@ -291,9 +295,9 @@ endif syn cluster vimFuncList contains=vimFuncBang,vimFunctionError,vimFuncKey,vimFuncSID,Tag syn cluster vimDefList contains=vimFuncBang,vimFunctionError,vimDefKey,vimFuncSID,Tag -syn cluster vimFuncBodyCommon contains=@vimCmdList,vimCmplxRepeat,vimContinue,vimCtrlChar,vimDef,vimEnvvar,vimFBVar,vimFunc,vimFunction,vimLetHereDoc,vimNotation,vimNotFunc,vimNumber,vimOper,vimOperParen,vimRegister,vimSearch,vimSpecFile,vimString,vimSubst,vimFuncFold,vimDefFold -syn cluster vimFuncBodyList contains=@vimFuncBodyCommon,vimComment,vimLineComment,vimFuncVar,vimInsert,vimConst,vimLet -syn cluster vimDefBodyList contains=@vimFuncBodyCommon,vim9Comment,vim9LineComment,vim9Const,vim9Final,vim9Var,vim9Null,vim9Boolean,vim9For +syn cluster vimFuncBodyCommon contains=@vimCmdList,vimCmplxRepeat,vimContinue,vimCtrlChar,vimDef,vimEnvvar,vimFBVar,vimFunc,vimFunction,vimLetHereDoc,vimNotation,vimNotFunc,vimNumber,vimOper,vimOperParen,vimRegister,vimSpecFile,vimString,vimSubst,vimFuncFold,vimDefFold +syn cluster vimFuncBodyList contains=@vimFuncBodyCommon,vimComment,vimLineComment,vimFuncVar,vimInsert,vimConst,vimLet,vimSearch +syn cluster vimDefBodyList contains=@vimFuncBodyCommon,vim9Comment,vim9LineComment,vim9Const,vim9Final,vim9Var,vim9Null,vim9Boolean,vim9For,vim9Search syn region vimFuncPattern contained matchgroup=vimOper start="/" end="$" contains=@vimSubstList syn match vimFunction "\" skipwhite nextgroup=vimCmdSep,vimComment,vimFuncPattern contains=vimFuncKey @@ -1019,8 +1023,10 @@ syn match vim9CommentTitleLeader '#\s\+'ms=s+1 contained " Searches And Globals: {{{2 " ==================== -syn match vimSearch '^\s*[/?].*' contains=vimSearchDelim +VimL syn match vimSearch '^\s*[/?].*' contains=vimSearchDelim syn match vimSearchDelim '^\s*\zs[/?]\|[/?]$' contained +Vim9 syn match vim9Search '^\s*:[/?].*' contains=vim9SearchDelim +syn match vim9SearchDelim '^\s*\zs:[/?]\|[/?]$' contained contains=vimCmdSep syn region vimGlobal matchgroup=Statement start='\ Date: Fri, 17 Jan 2025 13:44:07 +0100 Subject: feat(extmark): stack multiple highlight groups in `hl_group` This has been possible in the "backend" for a while but API was missing. Followup: we will need a `details2=true` mode for `nvim_get_hl_id_by_name` to return information in a way forward compatible with even further enhancements. --- runtime/doc/api.txt | 2 ++ runtime/doc/news.txt | 1 + runtime/lua/vim/_meta/api.lua | 3 +++ runtime/lua/vim/_meta/api_keysets.lua | 2 +- src/nvim/api/extmark.c | 48 +++++++++++++++++++++++++++++++-- src/nvim/api/keysets_defs.h | 2 +- test/functional/ui/decorations_spec.lua | 43 +++++++++++++++++++++++++++++ 7 files changed, 97 insertions(+), 4 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 5a6361d45f..1cc4350654 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2660,6 +2660,8 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) and below highlight groups can be supplied either as a string or as an integer, the latter of which can be obtained using |nvim_get_hl_id_by_name()|. + Multiple highlight groups can be stacked by passing an + array (highest priority last). • hl_eol : when true, for a multiline highlight covering the EOL of a line, continue the highlight for the rest of the screen line (just like for diff and cursorline highlight). diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 33ffeae2bb..b777d22a26 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -188,6 +188,7 @@ API • |nvim_echo()| `err` field to print error messages and `chunks` accepts highlight group IDs. • |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline". +• |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups DEFAULTS diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 670e867c1e..3ffbc89b08 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -595,6 +595,9 @@ function vim.api.nvim_buf_line_count(buffer) end --- - hl_group : highlight group used for the text range. This and below --- highlight groups can be supplied either as a string or as an integer, --- the latter of which can be obtained using `nvim_get_hl_id_by_name()`. +--- +--- Multiple highlight groups can be stacked by passing an array (highest +--- priority last). --- - hl_eol : when true, for a multiline highlight covering the --- EOL of a line, continue the highlight for the rest --- of the screen line (just like for diff and diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 98e916115e..26c2c963de 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -241,7 +241,7 @@ error('Cannot require a meta file') --- @field end_line? integer --- @field end_row? integer --- @field end_col? integer ---- @field hl_group? integer|string +--- @field hl_group? any --- @field virt_text? any[] --- @field virt_text_pos? string --- @field virt_text_win_col? integer diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index a237e7531a..18e37012ee 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -385,6 +385,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - hl_group : highlight group used for the text range. This and below /// highlight groups can be supplied either as a string or as an integer, /// the latter of which can be obtained using |nvim_get_hl_id_by_name()|. +/// +/// Multiple highlight groups can be stacked by passing an array (highest +/// priority last). /// - hl_eol : when true, for a multiline highlight covering the /// EOL of a line, continue the highlight for the rest /// of the screen line (just like for diff and @@ -499,6 +502,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT; char *url = NULL; bool has_hl = false; + bool has_hl_multiple = false; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -551,8 +555,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col2 = (int)val; } - hl.hl_id = (int)opts->hl_group; - has_hl = hl.hl_id > 0; + if (HAS_KEY(opts, set_extmark, hl_group)) { + if (opts->hl_group.type == kObjectTypeArray) { + Array arr = opts->hl_group.data.array; + if (arr.size >= 1) { + hl.hl_id = object_to_hl_id(arr.items[0], "hl_group item", err); + if (ERROR_SET(err)) { + goto error; + } + } + for (size_t i = 1; i < arr.size; i++) { + int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err); + if (ERROR_SET(err)) { + goto error; + } + if (hl_id) { + has_hl_multiple = true; + } + } + } else { + hl.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err); + if (ERROR_SET(err)) { + goto error; + } + } + has_hl = hl.hl_id > 0; + } + sign.hl_id = (int)opts->sign_hl_group; sign.cursorline_hl_id = (int)opts->cursorline_hl_group; sign.number_hl_id = (int)opts->number_hl_group; @@ -794,6 +823,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } + if (has_hl_multiple) { + Array arr = opts->hl_group.data.array; + for (size_t i = arr.size - 1; i > 0; i--) { // skip hl_group[0], handled as hl.hl_id below + int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err); + if (hl_id > 0) { + DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT; + sh.hl_id = hl_id; + sh.flags = opts->hl_eol ? kSHHlEol : 0; + sh.next = decor_indexed; + decor_indexed = decor_put_sh(sh); + decor_flags |= MT_FLAG_DECOR_HL; + } + } + } + DecorInline decor = DECOR_INLINE_INIT; if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL || schar_high(hl.conceal_char)) { diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 664406ab6e..953e467f1e 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -29,7 +29,7 @@ typedef struct { Integer end_line; Integer end_row; Integer end_col; - HLGroupID hl_group; + Object hl_group; Array virt_text; String virt_text_pos; Integer virt_text_win_col; diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index e364c473b7..c2030b9527 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -834,6 +834,9 @@ describe('extmark decorations', function() [42] = {undercurl = true, special = Screen.colors.Red}; [43] = {background = Screen.colors.Yellow, undercurl = true, special = Screen.colors.Red}; [44] = {background = Screen.colors.LightMagenta}; + [45] = { background = Screen.colors.Red, special = Screen.colors.Red, foreground = Screen.colors.Red }; + [46] = { background = Screen.colors.Blue, foreground = Screen.colors.Blue, special = Screen.colors.Red }; + [47] = { background = Screen.colors.Green, foreground = Screen.colors.Blue, special = Screen.colors.Red }; } ns = api.nvim_create_namespace 'test' @@ -1924,6 +1927,46 @@ describe('extmark decorations', function() ]]} end) + it('highlight can combine multiple groups', function() + screen:try_resize(50, 3) + command('hi Group1 guibg=Red guifg=Red guisp=Red') + command('hi Group2 guibg=Blue guifg=Blue') + command('hi Group3 guibg=Green') + insert([[example text]]) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {} }) + screen:expect([[ + example tex^t | + {1:~ }| + | + ]]) + + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {'Group1'} }) + screen:expect([[ + {45:example tex^t} | + {1:~ }| + | + ]]) + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2'} }) + screen:expect([[ + {46:example tex^t} | + {1:~ }| + | + ]]) + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', 'Group3'}, hl_eol=true }) + screen:expect([[ + {47:example tex^t }| + {1:~ }| + | + ]]) + + eq('Invalid hl_group: hl_group item', + pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', {'fail'}}, hl_eol=true })) + end) + + it('highlight works after TAB with sidescroll #14201', function() screen:try_resize(50, 3) command('set nowrap') -- cgit From 44dbfcfba4b09bb0e38f4a3f1960fa256a7bed71 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 21 Jan 2025 20:50:33 +0800 Subject: feat(tui): recognize X1 and X2 mouse events Ref: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Other-buttons --- src/nvim/os/input.c | 1 + src/nvim/tui/input.c | 4 ++++ src/nvim/tui/termkey/driver-csi.c | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 579b08c554..3259fb500b 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -403,6 +403,7 @@ static unsigned handle_mouse_event(const char **ptr, uint8_t *buf, unsigned bufs if (type != KS_EXTRA || !((mouse_code >= KE_LEFTMOUSE && mouse_code <= KE_RIGHTRELEASE) + || (mouse_code >= KE_X1MOUSE && mouse_code <= KE_X2RELEASE) || (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT) || mouse_code == KE_MOUSEMOVE)) { return bufsize; diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 9d901e94ce..1f73f2d135 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -387,6 +387,10 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Middle"); } else if (button == 3) { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Right"); + } else if (button == 8) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "X1"); + } else if (button == 9) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "X2"); } switch (ev) { diff --git a/src/nvim/tui/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c index 9741ec72b9..d427be50ff 100644 --- a/src/nvim/tui/termkey/driver-csi.c +++ b/src/nvim/tui/termkey/driver-csi.c @@ -327,6 +327,12 @@ TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKe btn = code + 4 - 64; break; + case 128: + case 129: + *event = drag ? TERMKEY_MOUSE_DRAG : TERMKEY_MOUSE_PRESS; + btn = code + 8 - 128; + break; + default: *event = TERMKEY_MOUSE_UNKNOWN; } -- cgit From 06a1f82f1cc37225b6acc46e63bd2eb36e034b1a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 21 Jan 2025 21:00:56 +0800 Subject: feat(terminal): forward X1 and X2 mouse events Ref: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Other-buttons --- src/nvim/terminal.c | 22 +++++++++++++--- src/nvim/vterm/mouse.c | 27 ++++++++++++++------ test/functional/terminal/buffer_spec.lua | 44 ++++++++++++++++++++++---------- test/unit/vterm_spec.lua | 24 +++++++++++++++++ 4 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 197a225209..897c393488 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -792,17 +792,23 @@ static int terminal_execute(VimState *state, int key) case K_LEFTMOUSE: case K_LEFTDRAG: case K_LEFTRELEASE: - case K_MOUSEMOVE: case K_MIDDLEMOUSE: case K_MIDDLEDRAG: case K_MIDDLERELEASE: case K_RIGHTMOUSE: case K_RIGHTDRAG: case K_RIGHTRELEASE: + case K_X1MOUSE: + case K_X1DRAG: + case K_X1RELEASE: + case K_X2MOUSE: + case K_X2DRAG: + case K_X2RELEASE: case K_MOUSEDOWN: case K_MOUSEUP: case K_MOUSELEFT: case K_MOUSERIGHT: + case K_MOUSEMOVE: if (send_mouse_event(s->term, key)) { return 0; } @@ -1804,8 +1810,6 @@ static bool send_mouse_event(Terminal *term, int c) pressed = true; FALLTHROUGH; case K_LEFTRELEASE: button = 1; break; - case K_MOUSEMOVE: - button = 0; break; case K_MIDDLEDRAG: case K_MIDDLEMOUSE: pressed = true; FALLTHROUGH; @@ -1816,6 +1820,16 @@ static bool send_mouse_event(Terminal *term, int c) pressed = true; FALLTHROUGH; case K_RIGHTRELEASE: button = 3; break; + case K_X1DRAG: + case K_X1MOUSE: + pressed = true; FALLTHROUGH; + case K_X1RELEASE: + button = 8; break; + case K_X2DRAG: + case K_X2MOUSE: + pressed = true; FALLTHROUGH; + case K_X2RELEASE: + button = 9; break; case K_MOUSEDOWN: pressed = true; button = 4; break; case K_MOUSEUP: @@ -1824,6 +1838,8 @@ static bool send_mouse_event(Terminal *term, int c) pressed = true; button = 7; break; case K_MOUSERIGHT: pressed = true; button = 6; break; + case K_MOUSEMOVE: + button = 0; break; default: return false; } diff --git a/src/nvim/vterm/mouse.c b/src/nvim/vterm/mouse.c index 9b8be4b60a..2f3b1d9e2f 100644 --- a/src/nvim/vterm/mouse.c +++ b/src/nvim/vterm/mouse.c @@ -1,3 +1,4 @@ +#include "nvim/math.h" #include "nvim/tui/termkey/termkey.h" #include "nvim/vterm/mouse.h" #include "nvim/vterm/vterm.h" @@ -24,6 +25,9 @@ static void output_mouse(VTermState *state, int code, int pressed, int modifiers code = 3; } + if (code & 0x80) { + break; + } vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", (code | modifiers) + 0x20, col + 0x21, row + 0x21); break; @@ -74,11 +78,16 @@ void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) if ((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || (state->mouse_flags & MOUSE_WANT_MOVE)) { - int button = state->mouse_buttons & 0x01 ? 1 - : state->mouse_buttons & 0x02 ? 2 - : state->mouse_buttons & - 0x04 ? 3 : 4; - output_mouse(state, button - 1 + 0x20, 1, (int)mod, col, row); + if (state->mouse_buttons) { + int button = xctz((uint64_t)state->mouse_buttons) + 1; + if (button < 4) { + output_mouse(state, button - 1 + 0x20, 1, (int)mod, col, row); + } else if (button >= 8 && button < 12) { + output_mouse(state, button - 8 + 0x80 + 0x20, 1, (int)mod, col, row); + } + } else { + output_mouse(state, 3 + 0x20, 1, (int)mod, col, row); + } } } @@ -88,7 +97,7 @@ void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) int old_buttons = state->mouse_buttons; - if (button > 0 && button <= 3) { + if ((button > 0 && button <= 3) || (button >= 8 && button <= 11)) { if (pressed) { state->mouse_buttons |= (1 << (button - 1)); } else { @@ -96,8 +105,8 @@ void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) } } - // Most of the time we don't get button releases from 4/5 - if (state->mouse_buttons == old_buttons && button < 4) { + // Most of the time we don't get button releases from 4/5/6/7 + if (state->mouse_buttons == old_buttons && (button < 4 || button > 7)) { return; } @@ -109,5 +118,7 @@ void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) output_mouse(state, button - 1, pressed, (int)mod, state->mouse_col, state->mouse_row); } else if (button < 8) { output_mouse(state, button - 4 + 0x40, pressed, (int)mod, state->mouse_col, state->mouse_row); + } else if (button < 12) { + output_mouse(state, button - 8 + 0x80, pressed, (int)mod, state->mouse_col, state->mouse_row); } } diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index a524e49ef4..d36dc60a93 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -594,18 +594,36 @@ describe('terminal input', function() '', '', '', - '', - '', - '<2-LeftMouse>', - '<2-LeftRelease>', - '', - '', - '<2-RightMouse>', - '<2-RightRelease>', - '', - '', - '<2-MiddleMouse>', - '<2-MiddleRelease>', + '<0,0>', + '<0,1>', + '<0,1>', + '<2-LeftMouse><0,1>', + '<2-LeftDrag><0,0>', + '<2-LeftRelease><0,0>', + '<0,0>', + '<0,1>', + '<0,1>', + '<2-MiddleMouse><0,1>', + '<2-MiddleDrag><0,0>', + '<2-MiddleRelease><0,0>', + '<0,0>', + '<0,1>', + '<0,1>', + '<2-RightMouse><0,1>', + '<2-RightDrag><0,0>', + '<2-RightRelease><0,0>', + '<0,0>', + '<0,1>', + '<0,1>', + '<2-X1Mouse><0,1>', + '<2-X1Drag><0,0>', + '<2-X1Release><0,0>', + '<0,0>', + '<0,1>', + '<0,1>', + '<2-X2Mouse><0,1>', + '<2-X2Drag><0,0>', + '<2-X2Release><0,0>', '', '', '', @@ -622,7 +640,7 @@ describe('terminal input', function() {5:[No Name] 0,0-1 All}| %s^ {MATCH: *}| {3:-- TERMINAL --} | - ]]):format(key)) + ]]):format(key:gsub('<%d+,%d+>$', ''))) end end) diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index bad2b3e658..c5293a21cb 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -2079,6 +2079,18 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) mousebtn('u', 1, vt) expect_output('\x1b[<0;301;301m') + -- Button 8 on SGR extended encoding mode + mousebtn('d', 8, vt) + expect_output('\x1b[<128;301;301M') + mousebtn('u', 8, vt) + expect_output('\x1b[<128;301;301m') + + -- Button 9 on SGR extended encoding mode + mousebtn('d', 9, vt) + expect_output('\x1b[<129;301;301M') + mousebtn('u', 9, vt) + expect_output('\x1b[<129;301;301m') + -- DECRQM on SGR extended encoding mode push('\x1b[?1005$p', vt) expect_output('\x1b[?1005;2$y') @@ -2094,6 +2106,18 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) mousebtn('u', 1, vt) expect_output('\x1b[3;301;301M') + -- Button 8 on rxvt extended encoding mode + mousebtn('d', 8, vt) + expect_output('\x1b[128;301;301M') + mousebtn('u', 8, vt) + expect_output('\x1b[3;301;301M') + + -- Button 9 on rxvt extended encoding mode + mousebtn('d', 9, vt) + expect_output('\x1b[129;301;301M') + mousebtn('u', 9, vt) + expect_output('\x1b[3;301;301M') + -- DECRQM on rxvt extended encoding mode push('\x1b[?1005$p', vt) expect_output('\x1b[?1005;2$y') -- cgit From a8b6fa07c4d9143f3bd279ce8fd87e8121da16e1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 22 Jan 2025 09:28:27 +0800 Subject: fix(search): avoid quadratic time complexity when computing fuzzy score (#32153) --- src/nvim/search.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nvim/search.c b/src/nvim/search.c index 5a53122739..6e87b07d06 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2993,6 +2993,8 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz, FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { assert(numMatches > 0); // suppress clang "result of operation is garbage" + const char *p = str; + uint32_t sidx = 0; // Initialize score int score = 100; bool is_exact_match = true; @@ -3026,12 +3028,12 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz, // Check for bonuses based on neighbor character value if (currIdx > 0) { // Camel case - const char *p = str; int neighbor = ' '; - for (uint32_t sidx = 0; sidx < currIdx; sidx++) { + while (sidx < currIdx) { neighbor = utf_ptr2char(p); MB_PTR_ADV(p); + sidx++; } const int curr = utf_ptr2char(p); -- cgit From a66f6add29fd8b2ee352c6089ceca6ab4f522385 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 22 Jan 2025 10:55:41 +0800 Subject: vim-patch:9.1.1046: fuzzymatching doesn't prefer matching camelcase (#32155) Problem: fuzzymatching doesn't prefer matching camelcase (Tomasz N) Solution: Add extra score when case matches (glepnir) fixes: vim/vim#16434 closes: vim/vim#16439 https://github.com/vim/vim/commit/9dfc7e5e6169594f6f4607ef1ba9dd347a9194d2 Co-authored-by: glepnir --- src/nvim/search.c | 58 +++++++++++++++++++++++++++++----- test/old/testdir/test_matchfuzzy.vim | 60 +++++++++++++++++++++--------------- 2 files changed, 87 insertions(+), 31 deletions(-) diff --git a/src/nvim/search.c b/src/nvim/search.c index 6e87b07d06..9f8ceae2a0 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2973,6 +2973,10 @@ typedef struct { #define CAMEL_BONUS 30 /// bonus if the first letter is matched #define FIRST_LETTER_BONUS 15 +/// bonus if exact match +#define EXACT_MATCH_BONUS 100 +/// bonus if case match when no ignorecase +#define CASE_MATCH_BONUS 25 /// penalty applied for every letter in str before the first match #define LEADING_LETTER_PENALTY (-5) /// maximum penalty for leading letters @@ -2988,16 +2992,23 @@ typedef struct { /// Compute a score for a fuzzy matched string. The matching character locations /// are in "matches". -static int fuzzy_match_compute_score(const char *const str, const int strSz, - const uint32_t *const matches, const int numMatches) +static int fuzzy_match_compute_score(const char *const fuzpat, const char *const str, + const int strSz, const uint32_t *const matches, + const int numMatches) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { assert(numMatches > 0); // suppress clang "result of operation is garbage" const char *p = str; uint32_t sidx = 0; + bool is_exact_match = true; + const char *const orig_fuzpat = fuzpat - numMatches; + const char *curpat = orig_fuzpat; + int pat_idx = 0; + // Track consecutive camel case matches + int consecutive_camel = 0; + // Initialize score int score = 100; - bool is_exact_match = true; // Apply leading letter penalty int penalty = LEADING_LETTER_PENALTY * (int)matches[0]; @@ -3013,6 +3024,7 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz, // Apply ordering bonuses for (int i = 0; i < numMatches; i++) { const uint32_t currIdx = matches[i]; + bool is_camel = false; if (i > 0) { const uint32_t prevIdx = matches[i - 1]; @@ -3022,9 +3034,12 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz, score += SEQUENTIAL_BONUS; } else { score += GAP_PENALTY * (int)(currIdx - prevIdx); + // Reset consecutive camel count on gap + consecutive_camel = 0; } } + int curr; // Check for bonuses based on neighbor character value if (currIdx > 0) { // Camel case @@ -3035,10 +3050,19 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz, MB_PTR_ADV(p); sidx++; } - const int curr = utf_ptr2char(p); + curr = utf_ptr2char(p); + // Enhanced camel case scoring if (mb_islower(neighbor) && mb_isupper(curr)) { - score += CAMEL_BONUS; + score += CAMEL_BONUS * 2; // Double the camel case bonus + is_camel = true; + consecutive_camel++; + // Additional bonus for consecutive camel + if (consecutive_camel > 1) { + score += CAMEL_BONUS; + } + } else { + consecutive_camel = 0; } // Bonus if the match follows a separator character @@ -3050,16 +3074,36 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz, } else { // First letter score += FIRST_LETTER_BONUS; + curr = utf_ptr2char(p); + } + + // Case matching bonus + if (mb_isalpha(curr)) { + while (pat_idx < i && *curpat) { + MB_PTR_ADV(curpat); + pat_idx++; + } + + if (curr == utf_ptr2char(curpat)) { + score += CASE_MATCH_BONUS; + // Extra bonus for exact case match in camel + if (is_camel) { + score += CASE_MATCH_BONUS / 2; + } + } } + // Check exact match condition if (currIdx != (uint32_t)i) { is_exact_match = false; } } + // Boost score for exact matches if (is_exact_match && numMatches == strSz) { - score += 100; + score += EXACT_MATCH_BONUS; } + return score; } @@ -3138,7 +3182,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s // Calculate score if (matched) { - *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch); + *outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches, nextMatch); } // Return best result diff --git a/test/old/testdir/test_matchfuzzy.vim b/test/old/testdir/test_matchfuzzy.vim index c2dc07ed10..0c982c19f1 100644 --- a/test/old/testdir/test_matchfuzzy.vim +++ b/test/old/testdir/test_matchfuzzy.vim @@ -100,15 +100,15 @@ endfunc " Test for the matchfuzzypos() function func Test_matchfuzzypos() - call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl')) - call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [178, 177]], matchfuzzypos(['world', 'curl'], 'rl')) + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [178, 177]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) call assert_equal([['hello', 'hello world hello world'], - \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [375, 257]], + \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [500, 382]], \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) - call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa')) - call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) - call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) - call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a ')) + call assert_equal([['aaaaaaa'], [[0, 1, 2]], [266]], matchfuzzypos(['aaaaaaa'], 'aaa')) + call assert_equal([['a b'], [[0, 3]], [269]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0, 3]], [269]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0]], [137]], matchfuzzypos(['a b'], ' a ')) call assert_equal([[], [], []], matchfuzzypos(['a b'], ' ')) call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab')) let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256)) @@ -117,33 +117,33 @@ func Test_matchfuzzypos() call assert_equal([[], [], []], matchfuzzypos([], 'abc')) " match in a long string - call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]], + call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-60]], \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) " preference for camel case match - call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc')) + call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [269]], matchfuzzypos(['xabcxxaBc'], 'abc')) " preference for match after a separator (_ or space) - call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab')) + call assert_equal([['xabx_ab'], [[5, 6]], [195]], matchfuzzypos(['xabx_ab'], 'ab')) " preference for leading letter match - call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab')) + call assert_equal([['abcxabc'], [[0, 1]], [200]], matchfuzzypos(['abcxabc'], 'ab')) " preference for sequential match - call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one')) + call assert_equal([['aobncedone'], [[7, 8, 9]], [233]], matchfuzzypos(['aobncedone'], 'one')) " best recursive match - call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one')) + call assert_equal([['xoone'], [[2, 3, 4]], [243]], matchfuzzypos(['xoone'], 'one')) " match multiple words (separated by space) - call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) + call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [519]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo', {'matchseq': 1})) - call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz')) - call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [326]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1})) + call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [519]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz')) + call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [476]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1})) call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two')) call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t ")) - call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [757]], ['grace']->matchfuzzypos('race ace grace')) + call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [1057]], ['grace']->matchfuzzypos('race ace grace')) let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] - call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [267]], \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) - call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [267]], \ matchfuzzypos(l, 'cam', {'key' : 'val'})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'})) @@ -161,6 +161,18 @@ func Test_matchfuzzypos() " Nvim doesn't have null functions " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:') + " case match + call assert_equal([['Match', 'match'], [[0, 1], [0, 1]], [202, 177]], matchfuzzypos(['match', 'Match'], 'Ma')) + call assert_equal([['match', 'Match'], [[0, 1], [0, 1]], [202, 177]], matchfuzzypos(['Match', 'match'], 'ma')) + " CamelCase has high weight even case match + call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['mytestcase', 'MyTestCase'], 'mtc')) + call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['MyTestCase', 'mytestcase'], 'mtc')) + call assert_equal(['MyTest', 'Mytest', 'mytest', ],matchfuzzy(['Mytest', 'mytest', 'MyTest'], 'MyT')) + call assert_equal(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'], + \ matchfuzzy(['CamelCaseMatchIngAlg', 'camelcasematchingalg', 'camelCaseMatchingAlg'], 'CamelCase')) + call assert_equal(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'], + \ matchfuzzy(['CamelCaseMatchIngAlg', 'camelcasematchingalg', 'camelCaseMatchingAlg'], 'CamelcaseM')) + let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}] call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:') endfunc @@ -211,12 +223,12 @@ func Test_matchfuzzypos_mbyte() call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) " reverse the order of characters call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) - call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]], + call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [252, 143]], \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], - \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]], + \ [[0, 1], [0, 1], [0, 1], [0, 2]], [176, 173, 170, 135]], \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) - call assert_equal([['ααααααα'], [[0, 1, 2]], [191]], + call assert_equal([['ααααααα'], [[0, 1, 2]], [216]], \ matchfuzzypos(['ααααααα'], 'ααα')) call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl')) @@ -229,10 +241,10 @@ func Test_matchfuzzypos_mbyte() call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘')) " match in a long string - call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]], + call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-110]], \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) " preference for camel case match - call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) + call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [219]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) " preference for match after a separator (_ or space) call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ')) " preference for leading letter match -- cgit From d46ebd2a74036a349606213fcd2a8b3530adebcf Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 22 Jan 2025 17:10:29 +0800 Subject: fix(startup): avoid crash with completion from -l script (#32160) Related #27764 --- src/nvim/drawscreen.c | 7 ++----- src/nvim/main.c | 2 +- test/functional/core/main_spec.lua | 12 +++++++++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index b1ea38e280..7ebd4f2866 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -177,14 +177,11 @@ bool default_grid_alloc(void) resizing = true; // Allocation of the screen buffers is done only when the size changes and - // when Rows and Columns have been set and we have started doing full - // screen stuff. + // when Rows and Columns have been set. if ((default_grid.chars != NULL && Rows == default_grid.rows && Columns == default_grid.cols) - || Rows == 0 - || Columns == 0 - || (!full_screen && default_grid.chars == NULL)) { + || Rows == 0 || Columns == 0) { resizing = false; return false; } diff --git a/src/nvim/main.c b/src/nvim/main.c index 58d110e8b2..0bd4277d19 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -360,7 +360,7 @@ int main(int argc, char **argv) setbuf(stdout, NULL); // NOLINT(bugprone-unsafe-functions) - full_screen = !silent_mode || exmode_active; + full_screen = !silent_mode; // Set the default values for the options that use Rows and Columns. win_init_size(); diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 6add49ceae..65a7c556b8 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -67,12 +67,22 @@ describe('command-line option', function() eq(#'100500\n', attrs.size) end) - it('does not crash when run completion in Ex mode', function() + it('does not crash when running completion in Ex mode', function() local p = n.spawn_wait('--clean', '-e', '-s', '--cmd', 'exe "norm! i\\\\"', '--cmd', 'qa!') eq(0, p.status) end) + it('does not crash when running completion from -l script', function() + local lua_fname = 'Xinscompl.lua' + write_file(lua_fname, [=[vim.cmd([[exe "norm! i\\"]])]=]) + finally(function() + os.remove(lua_fname) + end) + local p = n.spawn_wait('--clean', '-l', lua_fname) + eq(0, p.status) + end) + it('does not crash after reading from stdin in non-headless mode', function() skip(is_os('win')) local screen = Screen.new(40, 8) -- cgit From 22fd52325bf60cadaf24bca328a602764f53d6a9 Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Wed, 22 Jan 2025 07:02:30 -0600 Subject: fix(inspector): update semantic token namespace (#32157) This updates the extmark namespace to search for when filtering out semantic tokens to match the new namespace style recently introduced. --- runtime/lua/tohtml.lua | 4 ++-- runtime/lua/vim/_inspector.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index ed42b28725..1402dfe494 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -492,7 +492,7 @@ local function _styletable_extmarks_highlight(state, extmark, namespaces) end ---TODO(altermo) LSP semantic tokens (and some other extmarks) are only ---generated in visible lines, and not in the whole buffer. - if (namespaces[extmark[4].ns_id] or ''):find('vim_lsp_semantic_tokens') then + if (namespaces[extmark[4].ns_id] or ''):find('nvim.lsp.semantic_tokens') then notify('lsp semantic tokens are not supported, HTML may be incorrect') return end @@ -514,7 +514,7 @@ local function _styletable_extmarks_virt_text(state, extmark, namespaces) end ---TODO(altermo) LSP semantic tokens (and some other extmarks) are only ---generated in visible lines, and not in the whole buffer. - if (namespaces[extmark[4].ns_id] or ''):find('vim_lsp_inlayhint') then + if (namespaces[extmark[4].ns_id] or ''):find('nvim.lsp.inlayhint') then notify('lsp inlay hints are not supported, HTML may be incorrect') return end diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index 15b0733671..b0eb1d663b 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -128,13 +128,13 @@ function vim.inspect_pos(bufnr, row, col, filter) if filter.semantic_tokens then results.semantic_tokens = vim.tbl_filter(function(extmark) - return extmark.ns:find('vim_lsp_semantic_tokens') == 1 + return extmark.ns:find('nvim.lsp.semantic_tokens') == 1 end, extmarks) end if filter.extmarks then results.extmarks = vim.tbl_filter(function(extmark) - return extmark.ns:find('vim_lsp_semantic_tokens') ~= 1 + return extmark.ns:find('nvim.lsp.semantic_tokens') ~= 1 and (filter.extmarks == 'all' or extmark.opts.hl_group) end, extmarks) end -- cgit From 34344b939c060d36db719f17088639744ca61c94 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 22 Jan 2025 16:36:57 +0100 Subject: fix(editor): avoid scrolling :substitute confirm message #32149 Regression from 48e2a73. --- src/nvim/ex_cmds.c | 2 ++ test/functional/ui/cmdline_spec.lua | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 073b9594ed..34217107aa 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3799,6 +3799,8 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n typed = prompt_for_input(p, HLF_R, true, NULL); xfree(p); + msg_didout = false; // don't scroll up + gotocmdline(true); p_lz = save_p_lz; RedrawingDisabled = temp; diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index a2722a4139..d6dd62108c 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1050,6 +1050,24 @@ describe('cmdline redraw', function() {6:[Y]es, (N)o, (C)ancel: }^ | ]]) end) + + it('substitute confirm prompt does not scroll', function() + screen:try_resize(75, screen._height) + command('call setline(1, "foo")') + command('set report=0') + feed(':%s/foo/bar/c') + screen:expect([[ + {2:foo} | + {1:~ }|*3 + {6:replace with bar? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)}^ | + ]]) + feed('y') + screen:expect([[ + ^bar | + {1:~ }|*3 + 1 substitution on 1 line | + ]]) + end) end) describe('statusline is redrawn on entering cmdline', function() -- cgit From af0ef2ac9dd19b7c4005a3197334d3e7d554646c Mon Sep 17 00:00:00 2001 From: Siddhant Agarwal <68201519+siddhantdev@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:16:24 +0530 Subject: feat(lua): vim.hl.range() "timeout" #32012 Problem: `vim.hl.on_yank()` has a "timeout" behavior but this is not available for `vim.hl.range()`. Solution: Add `timeout` arg to `vim.hl.range()`. --- runtime/doc/lua.txt | 2 ++ runtime/doc/news.txt | 1 + runtime/lua/vim/hl.lua | 43 +++++++++++++++++++++++++---------------- test/functional/lua/hl_spec.lua | 24 +++++++++++++++++++++++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6e5a77ff7a..0eca3286d0 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -685,6 +685,8 @@ vim.hl.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts}) whether the range is end-inclusive • {priority}? (`integer`, default: `vim.hl.priorities.user`) Highlight priority + • {timeout}? (`integer`, default: -1 no timeout) Time in ms + before highlight is cleared ============================================================================== diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 0d5fa3ca18..0f1ec01d19 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -194,6 +194,7 @@ API highlight group IDs. • |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline". • |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups +• |vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight DEFAULTS diff --git a/runtime/lua/vim/hl.lua b/runtime/lua/vim/hl.lua index f5ace7fdc5..070748d31e 100644 --- a/runtime/lua/vim/hl.lua +++ b/runtime/lua/vim/hl.lua @@ -17,6 +17,9 @@ M.priorities = { user = 200, } +local range_timer --- @type uv.uv_timer_t? +local range_hl_clear --- @type fun()? + --- @class vim.hl.range.Opts --- @inlinedoc --- @@ -31,6 +34,10 @@ M.priorities = { --- Highlight priority --- (default: `vim.hl.priorities.user`) --- @field priority? integer +--- +--- Time in ms before highlight is cleared +--- (default: -1 no timeout) +--- @field timeout? integer --- Apply highlight group to range of text. --- @@ -45,6 +52,7 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local regtype = opts.regtype or 'v' local inclusive = opts.inclusive or false local priority = opts.priority or M.priorities.user + local timeout = opts.timeout or -1 local v_maxcol = vim.v.maxcol @@ -100,6 +108,19 @@ function M.range(bufnr, ns, higroup, start, finish, opts) end end + if range_timer and not range_timer:is_closing() then + range_timer:close() + assert(range_hl_clear) + range_hl_clear() + end + + range_hl_clear = function() + range_timer = nil + range_hl_clear = nil + pcall(vim.api.nvim_buf_clear_namespace, bufnr, ns, 0, -1) + pcall(vim.api.nvim__ns_set, { wins = {} }) + end + for _, res in ipairs(region) do local start_row = res[1][2] - 1 local start_col = res[1][3] - 1 @@ -113,11 +134,13 @@ function M.range(bufnr, ns, higroup, start, finish, opts) strict = false, }) end + + if timeout ~= -1 then + range_timer = vim.defer_fn(range_hl_clear, timeout) + end end local yank_ns = api.nvim_create_namespace('nvim.hlyank') -local yank_timer --- @type uv.uv_timer_t? -local yank_cancel --- @type fun()? --- Highlight the yanked text during a |TextYankPost| event. --- @@ -152,31 +175,17 @@ function M.on_yank(opts) end local higroup = opts.higroup or 'IncSearch' - local timeout = opts.timeout or 150 local bufnr = vim.api.nvim_get_current_buf() local winid = vim.api.nvim_get_current_win() - if yank_timer then - yank_timer:close() - assert(yank_cancel) - yank_cancel() - end vim.api.nvim__ns_set(yank_ns, { wins = { winid } }) M.range(bufnr, yank_ns, higroup, "'[", "']", { regtype = event.regtype, inclusive = event.inclusive, priority = opts.priority or M.priorities.user, + timeout = opts.timeout or 150, }) - - yank_cancel = function() - yank_timer = nil - yank_cancel = nil - pcall(vim.api.nvim_buf_clear_namespace, bufnr, yank_ns, 0, -1) - pcall(vim.api.nvim__ns_set, { wins = {} }) - end - - yank_timer = vim.defer_fn(yank_cancel, timeout) end return M diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua index 3f903d43a9..aed37cc294 100644 --- a/test/functional/lua/hl_spec.lua +++ b/test/functional/lua/hl_spec.lua @@ -104,6 +104,30 @@ describe('vim.hl.range', function() | ]]) end) + + it('removes highlight after given `timeout`', function() + local timeout = 50 + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.hl.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { timeout = timeout }) + end) + screen:expect([[ + {10:^asdfghjkl}{100:$} | + {10:«口=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口口}{1:$} | + zxcvbnm{1:$} | + | + ]]) + screen:expect([[ + ^asdfghjkl{1:$} | + «口=口»{1:$} | + qwertyuiop{1:$} | + 口口=口口{1:$} | + zxcvbnm{1:$} | + | + ]]) + end) end) describe('vim.hl.on_yank', function() -- cgit From fd55c7df6f7eb61c65e93c6dd8beffaeed93d2ed Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 23 Jan 2025 08:11:53 +0800 Subject: test(lua/hl_spec): set timeout for transient state (#32169) --- test/functional/lua/hl_spec.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua index aed37cc294..512f6be48f 100644 --- a/test/functional/lua/hl_spec.lua +++ b/test/functional/lua/hl_spec.lua @@ -106,19 +106,22 @@ describe('vim.hl.range', function() end) it('removes highlight after given `timeout`', function() - local timeout = 50 + local timeout = 100 exec_lua(function() local ns = vim.api.nvim_create_namespace('') vim.hl.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { timeout = timeout }) end) - screen:expect([[ + screen:expect({ + grid = [[ {10:^asdfghjkl}{100:$} | {10:«口=口»}{100:$} | {10:qwertyuiop}{100:$} | {10:口口=口口}{1:$} | zxcvbnm{1:$} | | - ]]) + ]], + timeout = timeout, + }) screen:expect([[ ^asdfghjkl{1:$} | «口=口»{1:$} | -- cgit From a9c12d4c298813ed3aee36b2b4d5d0912c7201ea Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 23 Jan 2025 08:12:10 +0800 Subject: vim-patch:9.1.1048: crash after scrolling and pasting in silent Ex mode (#32168) Problem: Crash after scrolling and pasting in silent Ex mode. (fizz-is-on-the-way) Solution: Don't move cursor to line 0 when scrolling. (zeertzjq) closes: vim/vim#16506 https://github.com/vim/vim/commit/df098fedbc2c481e91ea7e6207dab90359a92cc3 --- src/nvim/move.c | 5 ++++- test/old/testdir/test_normal.vim | 21 +++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/nvim/move.c b/src/nvim/move.c index d912858420..afd569ba7d 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -2490,7 +2490,10 @@ int pagescroll(Direction dir, int count, bool half) if (!nochange) { // Place cursor at top or bottom of window. validate_botline(curwin); - curwin->w_cursor.lnum = (dir == FORWARD ? curwin->w_topline : curwin->w_botline - 1); + linenr_T lnum = (dir == FORWARD ? curwin->w_topline : curwin->w_botline - 1); + // In silent Ex mode the value of w_botline - 1 may be 0, + // but cursor lnum needs to be at least 1. + curwin->w_cursor.lnum = MAX(lnum, 1); } } diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index c89e73bada..1d9609cbe1 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -1338,11 +1338,27 @@ func Test_scroll_in_ex_mode() call writefile(['done'], 'Xdone') qa! END - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') call assert_equal(1, RunVim([], [], '--clean -X -Z -e -s -S Xscript')) call assert_equal(['done'], readfile('Xdone')) - call delete('Xscript') + call delete('Xdone') +endfunc + +func Test_scroll_and_paste_in_ex_mode() + throw 'Skipped: does not work when Nvim is run from :!' + " This used to crash because of moving cursor to line 0. + let lines =<< trim END + v/foo/vi|YY9PYQ + v/bar/vi|YY9PYQ + v/bar/exe line('.') == 1 ? "vi|Y\9PYQ" : "vi|YQ" + call writefile(['done'], 'Xdone') + qa! + END + call writefile(lines, 'Xscript', 'D') + call assert_equal(1, RunVim([], [], '-u NONE -i NONE -n -X -Z -e -s -S Xscript')) + call assert_equal(['done'], readfile('Xdone')) + call delete('Xdone') endfunc @@ -4303,4 +4319,5 @@ func Test_normal_go() bwipe! endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable -- cgit From 4c9f3689a1c0646c8d743a2958af286b05c04ac5 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:39:26 +0800 Subject: fix(checkhealth): failed if 'lua' in plugin name --- runtime/lua/vim/health.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index ee376f3a11..3268c82613 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -118,7 +118,7 @@ local function filepath_to_healthcheck(path) func = 'health#' .. name .. '#check' filetype = 'v' else - local subpath = path:gsub('.*lua/', '') + local subpath = path:gsub('.*/lua/', '') if vim.fs.basename(subpath) == 'health.lua' then -- */health.lua name = vim.fs.dirname(subpath) -- cgit From 28998e1f8a9cdca27ada7030757b7a47e99ce5b6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 23 Jan 2025 16:33:41 +0800 Subject: vim-patch:9.1.1045: filetype: N-Tripels and TriG files are not recognized (#32170) Problem: filetype: N-Tripels and TriG files are not recognized Solution: detect '*.nt' files as ntriples filetype and '*.trig' files as trig filetype (Gordian Dziwis) closes: vim/vim#16493 https://github.com/vim/vim/commit/c04334c33f543a6b84a4442cf235d84f5eaef6bb Co-authored-by: Gordian Dziwis --- runtime/lua/vim/filetype.lua | 2 ++ test/old/testdir/test_filetype.vim | 2 ++ 2 files changed, 4 insertions(+) diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index efc41269f8..37e6227090 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -845,6 +845,7 @@ local extension = { tr = 'nroff', nsi = 'nsis', nsh = 'nsis', + nt = 'ntriples', nu = 'nu', obj = 'obj', objdump = 'objdump', @@ -1240,6 +1241,7 @@ local extension = { toml = 'toml', tpp = 'tpp', treetop = 'treetop', + trig = 'trig', slt = 'tsalt', tsscl = 'tsscl', tssgm = 'tssgm', diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 020603015f..e39b93eea4 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -549,6 +549,7 @@ func s:GetFilenameChecks() abort \ 'nqc': ['file.nqc'], \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], + \ 'ntriples': ['file.nt'], \ 'nu': ['file.nu'], \ 'obj': ['file.obj'], \ 'objdump': ['file.objdump', 'file.cppobjdump'], @@ -814,6 +815,7 @@ func s:GetFilenameChecks() abort \ 'tpp': ['file.tpp'], \ 'trace32': ['file.cmm', 'file.t32'], \ 'treetop': ['file.treetop'], + \ 'trig': ['file.trig'], \ 'trustees': ['trustees.conf'], \ 'tsalt': ['file.slt'], \ 'tsscl': ['file.tsscl'], -- cgit From 8634bd46b26f28fa26950128b0cc585560bd6a9a Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 20 Jan 2025 22:45:47 +0100 Subject: vim-patch:9.1.1042: filetype: just files are not recognized Problem: filetype: just files are not recognized Solution: adjust filetype detection pattern, detect just shebang line, include just ftplugin, indent and syntax plugin (Peter Benjamin) closes: vim/vim#16466 https://github.com/vim/vim/commit/72755b3c8e91ec90447969b736f080e0de36003d Co-authored-by: Peter Benjamin --- runtime/ftplugin/just.vim | 17 ++ runtime/indent/just.vim | 51 +++++ runtime/lua/vim/filetype.lua | 5 + runtime/lua/vim/filetype/detect.lua | 1 + runtime/syntax/just.vim | 406 ++++++++++++++++++++++++++++++++++++ test/old/testdir/test_filetype.vim | 1 + 6 files changed, 481 insertions(+) create mode 100644 runtime/ftplugin/just.vim create mode 100644 runtime/indent/just.vim create mode 100644 runtime/syntax/just.vim diff --git a/runtime/ftplugin/just.vim b/runtime/ftplugin/just.vim new file mode 100644 index 0000000000..6f2acddf96 --- /dev/null +++ b/runtime/ftplugin/just.vim @@ -0,0 +1,17 @@ +" Vim ftplugin file +" Language: Justfile +" Maintainer: Peter Benjamin <@pbnj> +" Last Change: 2025 Jan 19 +" Credits: The original author, Noah Bogart + +" Only do this when not done yet for this buffer +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +setlocal iskeyword+=- +setlocal comments=n:# +setlocal commentstring=#\ %s + +let b:undo_ftplugin = "setlocal iskeyword< comments< commentstring<" diff --git a/runtime/indent/just.vim b/runtime/indent/just.vim new file mode 100644 index 0000000000..d7f82b118f --- /dev/null +++ b/runtime/indent/just.vim @@ -0,0 +1,51 @@ +" Vim indent file +" Language: Justfile +" Maintainer: Peter Benjamin <@pbnj> +" Last Change: 2025 Jan 19 +" Credits: The original author, Noah Bogart + +" Only load this indent file when no other was loaded yet. +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +setlocal indentexpr=GetJustfileIndent() +setlocal indentkeys=0},0),!^F,o,O,0=''',0=\"\"\" + +let b:undo_indent = "setlocal indentexpr< indentkeys<" + +if exists("*GetJustfileIndent") + finish +endif + +function GetJustfileIndent() + if v:lnum < 2 + return 0 + endif + + let prev_line = getline(v:lnum - 1) + let last_indent = indent(v:lnum - 1) + + if getline(v:lnum) =~ "\\v^\\s+%([})]|'''$|\"\"\"$)" + return last_indent - shiftwidth() + elseif prev_line =~ '\V#' + return last_indent + elseif prev_line =~ "\\v%([:{(]|^.*\\S.*%([^']'''|[^\"]\"\"\"))\\s*$" + return last_indent + shiftwidth() + elseif prev_line =~ '\\$' + if v:lnum == 2 || getline(v:lnum - 2) !~ '\\$' + if prev_line =~ '\v:\=@!' + return last_indent + shiftwidth() + shiftwidth() + else + return last_indent + shiftwidth() + endif + endif + elseif v:lnum > 2 && getline(v:lnum - 2) =~ '\\$' + return last_indent - shiftwidth() + elseif prev_line =~ '\v:\s*%(\h|\()' && prev_line !~ '\V:=' + return last_indent + shiftwidth() + endif + + return last_indent +endfunction diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 37e6227090..a8f3d18bfa 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -661,6 +661,8 @@ local extension = { jsp = 'jsp', jl = 'julia', just = 'just', + Just = 'just', + JUST = 'just', kl = 'karel', KL = 'karel', kdl = 'kdl', @@ -1650,8 +1652,11 @@ local filename = { ['.vsconfig'] = 'jsonc', ['bun.lock'] = 'jsonc', ['.justfile'] = 'just', + ['.Justfile'] = 'just', + ['.JUSTFILE'] = 'just', ['justfile'] = 'just', ['Justfile'] = 'just', + ['JUSTFILE'] = 'just', Kconfig = 'kconfig', ['Kconfig.debug'] = 'kconfig', ['Config.in'] = 'kconfig', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 31c88c80bd..855893530e 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1877,6 +1877,7 @@ local patterns_hashbang = { ruby = 'ruby', ['node\\(js\\)\\=\\>\\|js\\>'] = { 'javascript', { vim_regex = true } }, ['rhino\\>'] = { 'javascript', { vim_regex = true } }, + just = 'just', -- BC calculator ['^bc\\>'] = { 'bc', { vim_regex = true } }, ['sed\\>'] = { 'sed', { vim_regex = true } }, diff --git a/runtime/syntax/just.vim b/runtime/syntax/just.vim new file mode 100644 index 0000000000..77423a3c91 --- /dev/null +++ b/runtime/syntax/just.vim @@ -0,0 +1,406 @@ +" Vim syntax file +" Language: Justfile +" Maintainer: Peter Benjamin <@pbnj> +" Last Change: 2025 Jan 19 +" Credits: The original author, Noah Bogart + +if exists('b:current_syntax') + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +let b:current_syntax = 'just' + +" syncing fromstart prevents mismatched highlighting when jumping around in a justfile +" linebreaks= keeps multi-line constructs highlighted correctly while typing +syn sync fromstart linebreaks=10 + +" a-zA-Z0-9_- +syn iskeyword @,48-57,_,- + +syn match justComment "#.*$" contains=@Spell,justCommentTodo +syn match justCommentInBody '#.*$' contained contains=justCommentTodo,justInterpolation,@justOtherCurlyBraces +syn keyword justCommentTodo TODO FIXME XXX contained +syn match justShebang "^\s*#!.*$" contains=justInterpolation,@justOtherCurlyBraces +syn match justName "\h\k*" contained +syn match justFunction "\h\k*" contained + +syn match justPreBodyComment "\v%(\s|\\\n)*%([^\\]\n)@3%(%(\s|\\\n)*\=)@!" contained + +syn region justRecipeParenDefault + \ matchgroup=justRecipeDepParamsParen start='\v%(\=%(\s|\\\n)*)@<=\(' end='\V)' + \ contained + \ contains=@justExpr +syn match justRecipeSubsequentDeps '\V&&' contained + +syn match justRecipeNoDeps '\v:%(\s|\\\n)*\n|:#@=|:%(\s|\\\n)+#@=' + \ transparent contained + \ contains=justRecipeColon + \ nextgroup=justPreBodyComment,@justBodies +syn region justRecipeDeps start="\v:%(\s|\\\n)*%([a-zA-Z_(]|\&\&)" skip='\\\n' end="\v#@=|\\@1 Date: Thu, 16 Jan 2025 18:10:22 +0100 Subject: feat(api): combined highlights in nvim_eval_statusline() Problem: Combined highlighting was not applied to nvim_eval_statusline(), and 'statuscolumn' sign segment/numhl highlights. Solution: Add an additional `groups` element to the return value of `nvim_eval_statusline()->highlights`. This is an array of stacked highlight groups (highest priority last). Also resolve combined highlights for the 'statuscolumn' sign segment/numhl highlights. Expose/synchronize some drawline.c logic that is now mimicked in three different places. --- runtime/doc/api.txt | 5 +- runtime/doc/news.txt | 6 +- runtime/lua/vim/_meta/api.lua | 4 +- src/nvim/api/vim.c | 49 +++++++++------- src/nvim/decoration.c | 6 +- src/nvim/drawline.c | 66 +++++++++++----------- src/nvim/drawscreen.c | 24 +++++--- src/nvim/option_vars.h | 44 +-------------- src/nvim/statusline.c | 23 ++++---- src/nvim/statusline_defs.h | 65 ++++++++++++++++++---- test/functional/api/vim_spec.lua | 32 +++++------ test/functional/ui/float_spec.lua | 41 +++++++------- test/functional/ui/sign_spec.lua | 20 +------ test/functional/ui/statuscolumn_spec.lua | 95 ++++++++++++++++---------------- 14 files changed, 244 insertions(+), 236 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index b452db9f3e..c5ade72f93 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -672,7 +672,10 @@ nvim_eval_statusline({str}, {opts}) *nvim_eval_statusline()* true. Each element of the array is a |Dict| with these keys: • start: (number) Byte index (0-based) of first character that uses the highlight. - • group: (string) Name of highlight group. + • group: (string) Name of highlight group. May be removed in the + future, use `groups` instead. + • groups: (array) Names of stacked highlight groups (highest + priority last). nvim_exec_lua({code}, {args}) *nvim_exec_lua()* Execute Lua code. Parameters (if any) are available as `...` inside the diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 0f1ec01d19..099fc17c5d 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -421,8 +421,10 @@ These existing features changed their behavior. using the upgraded implementation. • Custom highlights in 'rulerformat', 'statuscolumn', 'statusline', 'tabline', - 'winbar' and the number column (through |:sign-define| `numhl`) now combine - with their respective highlight groups, as opposed to |hl-Normal|. + 'winbar', and the sign/number column are stacked with their respective + highlight groups, as opposed to |hl-Normal|. + This is also reflected in the `highlights` from |nvim_eval_statusline()|, + with a new `groups` field containing an array of stacked highlight groups. • |vim.on_key()| callbacks won't be invoked recursively when a callback itself consumes input. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 2f9ab5b846..6d9a17ea2b 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1131,7 +1131,9 @@ function vim.api.nvim_eval(expr) end --- the "highlights" key in {opts} is true. Each element of the array is a --- |Dict| with these keys: --- - start: (number) Byte index (0-based) of first character that uses the highlight. ---- - group: (string) Name of highlight group. +--- - group: (string) Name of highlight group. May be removed in the future, use +--- `groups` instead. +--- - groups: (array) Names of stacked highlight groups (highest priority last). function vim.api.nvim_eval_statusline(str, opts) end --- @deprecated diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 950c70026b..c103a56032 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -28,6 +28,7 @@ #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/decoration.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" #include "nvim/errors.h" #include "nvim/eval.h" @@ -1983,7 +1984,9 @@ Array nvim_get_mark(String name, Dict(empty) *opts, Arena *arena, Error *err) /// the "highlights" key in {opts} is true. Each element of the array is a /// |Dict| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. -/// - group: (string) Name of highlight group. +/// - group: (string) Name of highlight group. May be removed in the future, use +/// `groups` instead. +/// - groups: (array) Names of stacked highlight groups (highest priority last). Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err) FUNC_API_SINCE(8) FUNC_API_FAST { @@ -2035,6 +2038,7 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, }); int stc_hl_id = 0; + int scl_hl_id = 0; statuscol_T statuscol = { 0 }; SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 }; @@ -2043,23 +2047,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, int cul_id = 0; int num_id = 0; linenr_T lnum = statuscol_lnum; + foldinfo_T cursorline_fi = { 0 }; decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id); statuscol.sattrs = sattrs; statuscol.foldinfo = fold_info(wp, lnum); - wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; + win_update_cursorline(wp, &cursorline_fi); + statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? cul_id : 0; + scl_hl_id = use_cursor_line_highlight(wp, lnum) ? HLF_CLS : HLF_SC; - if (wp->w_p_cul) { - if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) { - wp->w_cursorline = statuscol.foldinfo.fi_lnum; - } - statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & kOptCuloptFlagNumber); - } - - statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0; if (num_id) { stc_hl_id = num_id; - } else if (statuscol.use_cul) { + } else if (use_cursor_line_highlight(wp, lnum)) { stc_hl_id = HLF_CLN; } else if (wp->w_p_rnu) { stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB); @@ -2112,22 +2111,19 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list + const char *dfltname = get_default_stl_hl(opts->use_tabline ? NULL : wp, + opts->use_winbar, stc_hl_id); if (hltab->start == NULL || (hltab->start - buf) != 0) { - Dict hl_info = arena_dict(arena, 2); - const char *grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, - opts->use_winbar, stc_hl_id); - + Dict hl_info = arena_dict(arena, 3); PUT_C(hl_info, "start", INTEGER_OBJ(0)); - PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname)); - + PUT_C(hl_info, "group", CSTR_AS_OBJ(dfltname)); + Array groups = arena_array(arena, 1); + ADD_C(groups, CSTR_AS_OBJ(dfltname)); + PUT_C(hl_info, "groups", ARRAY_OBJ(groups)); ADD_C(hl_values, DICT_OBJ(hl_info)); } for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) { - Dict hl_info = arena_dict(arena, 2); - - PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf)); - const char *grpname; if (sp->userhl == 0) { grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id); @@ -2137,7 +2133,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); grpname = arena_memdupz(arena, user_group, strlen(user_group)); } + + const char *combine = sp->item == STL_SIGNCOL ? syn_id2name(scl_hl_id) + : sp->item == STL_FOLDCOL ? grpname : dfltname; + Dict hl_info = arena_dict(arena, 3); + PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf)); PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname)); + Array groups = arena_array(arena, 1 + (combine != grpname)); + if (combine != grpname) { + ADD_C(groups, CSTR_AS_OBJ(combine)); + } + ADD_C(groups, CSTR_AS_OBJ(grpname)); + PUT_C(hl_info, "groups", ARRAY_OBJ(groups)); ADD_C(hl_values, DICT_OBJ(hl_info)); } PUT_C(result, "highlights", ARRAY_OBJ(hl_values)); diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index a9f3ba0c3b..149504f424 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -856,9 +856,9 @@ static const uint32_t sign_filter[4] = {[kMTMetaSignText] = kMTFilterSelect, /// Return the sign attributes on the currently refreshed row. /// /// @param[out] sattrs Output array for sign text and texthl id -/// @param[out] line_attr Highest priority linehl id -/// @param[out] cul_attr Highest priority culhl id -/// @param[out] num_attr Highest priority numhl id +/// @param[out] line_id Highest priority linehl id +/// @param[out] cul_id Highest priority culhl id +/// @param[out] num_id Highest priority numhl id void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id, int *cul_id, int *num_id) { diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index d5273ff3d1..74a766bd0c 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -81,6 +81,8 @@ typedef struct { int cul_attr; ///< set when 'cursorline' active int line_attr; ///< attribute for the whole line int line_attr_lowprio; ///< low-priority attribute for the line + int sign_num_attr; ///< line number attribute (sign numhl) + int sign_cul_attr; ///< cursorline sign attribute (sign culhl) int fromcol; ///< start of inverting int tocol; ///< end of inverting @@ -397,7 +399,7 @@ static void draw_col_fill(winlinevars_T *wlv, schar_T fillchar, int width, int a } /// Return true if CursorLineSign highlight is to be used. -static bool use_cursor_line_highlight(win_T *wp, linenr_T lnum) +bool use_cursor_line_highlight(win_T *wp, linenr_T lnum) { return wp->w_p_cul && lnum == wp->w_cursorline @@ -460,16 +462,15 @@ void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, in /// If "nrcol" is true, the sign is going to be displayed in the number column. /// Otherwise the sign is going to be displayed in the sign column. If there is no /// sign, draw blank cells instead. -static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, int sign_cul_attr) +static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx) { SignTextAttrs sattr = wlv->sattrs[sign_idx]; int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); if (sattr.text[0] && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { - int attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr) - ? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0; - attr = hl_combine_attr(scl_attr, attr); int fill = nrcol ? number_width(wp) + 1 : SIGN_WIDTH; + int attr = wlv->sign_cul_attr ? wlv->sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0; + attr = hl_combine_attr(scl_attr, attr); draw_col_fill(wlv, schar_from_ascii(' '), fill, attr); int sign_pos = wlv->off - SIGN_WIDTH - (int)nrcol; assert(sign_pos >= 0); @@ -544,7 +545,7 @@ static int get_line_number_attr(win_T *wp, winlinevars_T *wlv) /// Display the absolute or relative line number. After the first row fill with /// blanks when the 'n' flag isn't in 'cpo'. -static void draw_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int sign_cul_attr) +static void draw_lnum_col(win_T *wp, winlinevars_T *wlv) { bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL; @@ -557,12 +558,12 @@ static void draw_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int // then display the sign instead of the line number. if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text[0] && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { - draw_sign(true, wp, wlv, 0, sign_cul_attr); + draw_sign(true, wp, wlv, 0); } else { // Draw the line number (empty space after wrapping). int width = number_width(wp) + 1; int attr = hl_combine_attr(get_line_number_attr(wp, wlv), - wlv->filler_todo <= 0 ? sign_num_attr : 0); + wlv->filler_todo <= 0 ? wlv->sign_num_attr : 0); if (wlv->row == wlv->startrow + wlv->filler_lines && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) { char buf[32]; @@ -631,22 +632,25 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir char *p = buf; char transbuf[MAXPATHL]; - int attr = stcp->num_attr; size_t len = strlen(buf); + int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); + int num_attr = hl_combine_attr(get_line_number_attr(wp, wlv), + wlv->filler_todo <= 0 ? wlv->sign_num_attr : 0); + int cur_attr = num_attr; // Draw each segment with the specified highlighting. for (stl_hlrec_t *sp = stcp->hlrec; sp->start != NULL; sp++) { ptrdiff_t textlen = sp->start - p; // Make all characters printable. size_t translen = transstr_buf(p, textlen, transbuf, MAXPATHL, true); - draw_col_buf(wp, wlv, transbuf, translen, attr, false); + draw_col_buf(wp, wlv, transbuf, translen, cur_attr, false); + int attr = sp->item == STL_SIGNCOL ? scl_attr : sp->item == STL_FOLDCOL ? 0 : num_attr; + cur_attr = hl_combine_attr(attr, sp->userhl < 0 ? syn_id2attr(-sp->userhl) : 0); p = sp->start; - int hl = sp->userhl; - attr = hl < 0 ? hl_combine_attr(stcp->num_attr, syn_id2attr(-hl)) : stcp->num_attr; } size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true); - draw_col_buf(wp, wlv, transbuf, translen, attr, false); - draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, stcp->num_attr); + draw_col_buf(wp, wlv, transbuf, translen, num_attr, false); + draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, num_attr); } static void handle_breakindent(win_T *wp, winlinevars_T *wlv) @@ -1201,11 +1205,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s area_highlighting = true; } - int line_attr = 0; - int sign_cul_attr = 0; - int sign_num_attr = 0; + int sign_line_attr = 0; // TODO(bfredl, vigoux): line_attr should not take priority over decoration! - decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, &line_attr, &sign_cul_attr, &sign_num_attr); + decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, + &sign_line_attr, &wlv.sign_cul_attr, &wlv.sign_num_attr); statuscol_T statuscol = { 0 }; if (*wp->w_p_stc != NUL) { @@ -1214,19 +1217,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s statuscol.sattrs = wlv.sattrs; statuscol.foldinfo = foldinfo; statuscol.width = win_col_off(wp) - (wp == cmdwin_win); - statuscol.use_cul = use_cursor_line_highlight(wp, lnum); - statuscol.sign_cul_id = statuscol.use_cul ? sign_cul_attr : 0; - statuscol.num_attr = sign_num_attr > 0 ? syn_id2attr(sign_num_attr) : 0; - } else { - if (sign_cul_attr > 0) { - sign_cul_attr = syn_id2attr(sign_cul_attr); - } - if (sign_num_attr > 0) { - sign_num_attr = syn_id2attr(sign_num_attr); - } + statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? wlv.sign_cul_attr : 0; + } else if (wlv.sign_cul_attr > 0) { + wlv.sign_cul_attr = use_cursor_line_highlight(wp, lnum) ? syn_id2attr(wlv.sign_cul_attr) : 0; } - if (line_attr > 0) { - wlv.line_attr = syn_id2attr(line_attr); + if (wlv.sign_num_attr > 0) { + wlv.sign_num_attr = syn_id2attr(wlv.sign_num_attr); + } + if (sign_line_attr > 0) { + wlv.line_attr = syn_id2attr(sign_line_attr); } // Highlight the current line in the quickfix window. @@ -1549,9 +1548,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // skip columns } else if (statuscol.draw) { // Draw 'statuscolumn' if it is set. - if (sign_num_attr == 0) { - statuscol.num_attr = get_line_number_attr(wp, &wlv); - } const int v = (int)(ptr - line); draw_statuscol(wp, &wlv, lnum, wlv.row - startrow - wlv.filler_lines, col_rows, &statuscol); if (wp->w_redr_statuscol) { @@ -1568,10 +1564,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // wp->w_scwidth is zero if signcol=number is used for (int sign_idx = 0; sign_idx < wp->w_scwidth; sign_idx++) { - draw_sign(false, wp, &wlv, sign_idx, sign_cul_attr); + draw_sign(false, wp, &wlv, sign_idx); } - draw_lnum_col(wp, &wlv, sign_num_attr, sign_cul_attr); + draw_lnum_col(wp, &wlv); } win_col_offset = wlv.off; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 7ebd4f2866..66c9b2be29 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2029,14 +2029,7 @@ static void win_update(win_T *wp) } foldinfo_T cursorline_fi = { 0 }; - wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; - if (wp->w_p_cul) { - // Make sure that the cursorline on a closed fold is redrawn - cursorline_fi = fold_info(wp, wp->w_cursor.lnum); - if (cursorline_fi.fi_level != 0 && cursorline_fi.fi_lines > 0) { - wp->w_cursorline = cursorline_fi.fi_lnum; - } - } + win_update_cursorline(wp, &cursorline_fi); win_check_ns_hl(wp); @@ -2862,3 +2855,18 @@ bool win_cursorline_standout(const win_T *wp) { return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } + +/// Update w_cursorline, taking care to set it to the to the start of a closed fold. +/// +/// @param[out] foldinfo foldinfo for the cursor line +void win_update_cursorline(win_T *wp, foldinfo_T *foldinfo) +{ + wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; + if (wp->w_p_cul) { + // Make sure that the cursorline on a closed fold is redrawn + *foldinfo = fold_info(wp, wp->w_cursor.lnum); + if (foldinfo->fi_level != 0 && foldinfo->fi_lines > 0) { + wp->w_cursorline = foldinfo->fi_lnum; + } + } +} diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 9975e7870f..340a12a32f 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -3,6 +3,7 @@ #include "nvim/macros_defs.h" #include "nvim/os/os_defs.h" #include "nvim/sign_defs.h" +#include "nvim/statusline_defs.h" #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -212,49 +213,6 @@ enum { #define COM_ALL "nbsmexflrO" // all flags for 'comments' option #define COM_MAX_LEN 50 // maximum length of a part -/// 'statusline' option flags -enum { - STL_FILEPATH = 'f', ///< Path of file in buffer. - STL_FULLPATH = 'F', ///< Full path of file in buffer. - STL_FILENAME = 't', ///< Last part (tail) of file path. - STL_COLUMN = 'c', ///< Column og cursor. - STL_VIRTCOL = 'v', ///< Virtual column. - STL_VIRTCOL_ALT = 'V', ///< - with 'if different' display. - STL_LINE = 'l', ///< Line number of cursor. - STL_NUMLINES = 'L', ///< Number of lines in buffer. - STL_BUFNO = 'n', ///< Current buffer number. - STL_KEYMAP = 'k', ///< 'keymap' when active. - STL_OFFSET = 'o', ///< Offset of character under cursor. - STL_OFFSET_X = 'O', ///< - in hexadecimal. - STL_BYTEVAL = 'b', ///< Byte value of character. - STL_BYTEVAL_X = 'B', ///< - in hexadecimal. - STL_ROFLAG = 'r', ///< Readonly flag. - STL_ROFLAG_ALT = 'R', ///< - other display. - STL_HELPFLAG = 'h', ///< Window is showing a help file. - STL_HELPFLAG_ALT = 'H', ///< - other display. - STL_FILETYPE = 'y', ///< 'filetype'. - STL_FILETYPE_ALT = 'Y', ///< - other display. - STL_PREVIEWFLAG = 'w', ///< Window is showing the preview buf. - STL_PREVIEWFLAG_ALT = 'W', ///< - other display. - STL_MODIFIED = 'm', ///< Modified flag. - STL_MODIFIED_ALT = 'M', ///< - other display. - STL_QUICKFIX = 'q', ///< Quickfix window description. - STL_PERCENTAGE = 'p', ///< Percentage through file. - STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%. - STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y). - STL_PAGENUM = 'N', ///< Page number (when printing). - STL_SHOWCMD = 'S', ///< 'showcmd' buffer - STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn' - STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn' - STL_VIM_EXPR = '{', ///< Start of expression to substitute. - STL_SEPARATE = '=', ///< Separation between alignment sections. - STL_TRUNCMARK = '<', ///< Truncation mark if line is too long. - STL_USER_HL = '*', ///< Highlight from (User)1..9 or 0. - STL_HIGHLIGHT = '#', ///< Highlight name. - STL_TABPAGENR = 'T', ///< Tab page label nr. - STL_TABCLOSENR = 'X', ///< Tab page close nr. - STL_CLICK_FUNC = '@', ///< Click region start. -}; /// C string containing all 'statusline' option flags #define STL_ALL ((char[]) { \ STL_FILEPATH, STL_FULLPATH, STL_FILENAME, STL_COLUMN, STL_VIRTCOL, \ diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index b8515fa3e2..f0437db1bb 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -1633,12 +1633,12 @@ stcsign: break; } foldsignitem = curitem; + lnum = (linenr_T)get_vim_var_nr(VV_LNUM); if (fdc > 0) { schar_T fold_buf[9]; - fill_foldcolumn(wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM), - 0, fdc, NULL, fold_buf); - stl_items[curitem].minwid = -(stcp->use_cul ? HLF_CLF : HLF_FC); + fill_foldcolumn(wp, stcp->foldinfo, lnum, 0, fdc, NULL, fold_buf); + stl_items[curitem].minwid = -(use_cursor_line_highlight(wp, lnum) ? HLF_CLF : HLF_FC); size_t buflen = 0; // TODO(bfredl): this is very backwards. we must support schar_T // being used directly in 'statuscolumn' @@ -1651,18 +1651,18 @@ stcsign: for (int i = 0; i < width; i++) { stl_items[curitem].start = out_p + signlen; if (fdc == 0) { - if (stcp->sattrs[i].text[0] && get_vim_var_nr(VV_VIRTNUM) == 0) { - SignTextAttrs sattrs = stcp->sattrs[i]; - signlen += describe_sign_text(buf_tmp + signlen, sattrs.text); - stl_items[curitem].minwid = -(stcp->sign_cul_id ? stcp->sign_cul_id : sattrs.hl_id); + SignTextAttrs sattr = stcp->sattrs[i]; + if (sattr.text[0] && get_vim_var_nr(VV_VIRTNUM) == 0) { + signlen += describe_sign_text(buf_tmp + signlen, sattr.text); + stl_items[curitem].minwid = -(stcp->sign_cul_id ? stcp->sign_cul_id : sattr.hl_id); } else { buf_tmp[signlen++] = ' '; buf_tmp[signlen++] = ' '; buf_tmp[signlen] = NUL; - stl_items[curitem].minwid = -(stcp->use_cul ? HLF_CLS : HLF_SC); + stl_items[curitem].minwid = 0; } } - stl_items[curitem++].type = Highlight; + stl_items[curitem++].type = fdc > 0 ? HighlightFold : HighlightSign; } str = buf_tmp; break; @@ -2117,9 +2117,12 @@ stcsign: *hltab = stl_hltab; stl_hlrec_t *sp = stl_hltab; for (int l = 0; l < itemcnt; l++) { - if (stl_items[l].type == Highlight) { + if (stl_items[l].type == Highlight + || stl_items[l].type == HighlightFold || stl_items[l].type == HighlightSign) { sp->start = stl_items[l].start; sp->userhl = stl_items[l].minwid; + unsigned type = stl_items[l].type; + sp->item = type == HighlightSign ? STL_SIGNCOL : type == HighlightFold ? STL_FOLDCOL : 0; sp++; } } diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h index 118f4a257b..f640d63150 100644 --- a/src/nvim/statusline_defs.h +++ b/src/nvim/statusline_defs.h @@ -5,6 +5,50 @@ #include "nvim/fold_defs.h" #include "nvim/sign_defs.h" +/// 'statusline' item flags +typedef enum { + STL_FILEPATH = 'f', ///< Path of file in buffer. + STL_FULLPATH = 'F', ///< Full path of file in buffer. + STL_FILENAME = 't', ///< Last part (tail) of file path. + STL_COLUMN = 'c', ///< Column og cursor. + STL_VIRTCOL = 'v', ///< Virtual column. + STL_VIRTCOL_ALT = 'V', ///< - with 'if different' display. + STL_LINE = 'l', ///< Line number of cursor. + STL_NUMLINES = 'L', ///< Number of lines in buffer. + STL_BUFNO = 'n', ///< Current buffer number. + STL_KEYMAP = 'k', ///< 'keymap' when active. + STL_OFFSET = 'o', ///< Offset of character under cursor. + STL_OFFSET_X = 'O', ///< - in hexadecimal. + STL_BYTEVAL = 'b', ///< Byte value of character. + STL_BYTEVAL_X = 'B', ///< - in hexadecimal. + STL_ROFLAG = 'r', ///< Readonly flag. + STL_ROFLAG_ALT = 'R', ///< - other display. + STL_HELPFLAG = 'h', ///< Window is showing a help file. + STL_HELPFLAG_ALT = 'H', ///< - other display. + STL_FILETYPE = 'y', ///< 'filetype'. + STL_FILETYPE_ALT = 'Y', ///< - other display. + STL_PREVIEWFLAG = 'w', ///< Window is showing the preview buf. + STL_PREVIEWFLAG_ALT = 'W', ///< - other display. + STL_MODIFIED = 'm', ///< Modified flag. + STL_MODIFIED_ALT = 'M', ///< - other display. + STL_QUICKFIX = 'q', ///< Quickfix window description. + STL_PERCENTAGE = 'p', ///< Percentage through file. + STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%. + STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y). + STL_PAGENUM = 'N', ///< Page number (when printing). + STL_SHOWCMD = 'S', ///< 'showcmd' buffer + STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn' + STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn' + STL_VIM_EXPR = '{', ///< Start of expression to substitute. + STL_SEPARATE = '=', ///< Separation between alignment sections. + STL_TRUNCMARK = '<', ///< Truncation mark if line is too long. + STL_USER_HL = '*', ///< Highlight from (User)1..9 or 0. + STL_HIGHLIGHT = '#', ///< Highlight name. + STL_TABPAGENR = 'T', ///< Tab page label nr. + STL_TABCLOSENR = 'X', ///< Tab page close nr. + STL_CLICK_FUNC = '@', ///< Click region start. +} StlFlag; + /// Status line click definition typedef struct { enum { @@ -26,27 +70,26 @@ typedef struct { /// Used for highlighting in the status line. typedef struct stl_hlrec stl_hlrec_t; struct stl_hlrec { - char *start; - int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID + char *start; ///< Where the item starts in the status line output buffer + int userhl; ///< 0: no HL, 1-9: User HL, < 0 for syn ID + StlFlag item; ///< Item flag belonging to highlight (used for 'statuscolumn') }; /// Used for building the status line. typedef struct stl_item stl_item_t; struct stl_item { - // Where the item starts in the status line output buffer - char *start; - // Function to run for ClickFunc items. - char *cmd; - // The minimum width of the item - int minwid; - // The maximum width of the item - int maxwid; + char *start; ///< Where the item starts in the status line output buffer + char *cmd; ///< Function to run for ClickFunc items + int minwid; ///< The minimum width of the item + int maxwid; ///< The maximum width of the item enum { Normal, Empty, Group, Separate, Highlight, + HighlightSign, + HighlightFold, TabPage, ClickFunc, Trunc, @@ -56,10 +99,8 @@ struct stl_item { /// Struct to hold info for 'statuscolumn' typedef struct { int width; ///< width of the status column - int num_attr; ///< default highlight attr int sign_cul_id; ///< cursorline sign highlight id bool draw; ///< whether to draw the statuscolumn - bool use_cul; ///< whether to use cursorline attrs stl_hlrec_t *hlrec; ///< highlight groups foldinfo_T foldinfo; ///< fold information SignTextAttrs *sattrs; ///< sign attributes diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 044213d83a..dabe3a2c9a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3993,8 +3993,8 @@ describe('API', function() str = 'TextWithWarningHighlightTextWithUserHighlight', width = 45, highlights = { - { start = 0, group = 'WarningMsg' }, - { start = 24, group = 'User1' }, + { start = 0, group = 'WarningMsg', groups = { 'StatusLine', 'WarningMsg' } }, + { start = 24, group = 'User1', groups = { 'StatusLine', 'User1' } }, }, }, api.nvim_eval_statusline( @@ -4009,7 +4009,7 @@ describe('API', function() str = 'TextWithNoHighlight', width = 19, highlights = { - { start = 0, group = 'StatusLine' }, + { start = 0, group = 'StatusLine', groups = { 'StatusLine' } }, }, }, api.nvim_eval_statusline('TextWithNoHighlight', { highlights = true })) end) @@ -4021,8 +4021,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'StatusLineNC' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'StatusLineNC', groups = { 'StatusLineNC' } }, + { start = 19, group = 'WarningMsg', groups = { 'StatusLineNC', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4038,8 +4038,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'TabLineFill' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'TabLineFill', groups = { 'TabLineFill' } }, + { start = 19, group = 'WarningMsg', groups = { 'TabLineFill', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4055,8 +4055,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'WinBar' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'WinBar', groups = { 'WinBar' } }, + { start = 19, group = 'WarningMsg', groups = { 'WinBar', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4083,11 +4083,11 @@ describe('API', function() str = '││bbaa 4 ', width = 9, highlights = { - { group = 'CursorLineFold', start = 0 }, - { group = 'Normal', start = 6 }, - { group = 'ErrorMsg', start = 6 }, - { group = 'IncSearch', start = 8 }, - { group = 'Normal', start = 10 }, + { group = 'CursorLineFold', start = 0, groups = { 'CursorLineFold' } }, + { group = 'Normal', start = 6, groups = { 'Normal' } }, + { group = 'ErrorMsg', start = 6, groups = { 'CursorLineSign', 'ErrorMsg' } }, + { group = 'IncSearch', start = 8, groups = { 'CursorLineSign', 'IncSearch' } }, + { group = 'Normal', start = 10, groups = { 'Normal' } }, }, }, api.nvim_eval_statusline( '%C%s%=%l ', @@ -4098,8 +4098,8 @@ describe('API', function() str = ' 3 ', width = 9, highlights = { - { group = 'LineNr', start = 0 }, - { group = 'ErrorMsg', start = 8 }, + { group = 'LineNr', start = 0, groups = { 'LineNr' } }, + { group = 'ErrorMsg', start = 8, groups = { 'LineNr', 'ErrorMsg' } }, }, }, api.nvim_eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index ca26c46fc5..27ab0feb9c 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -1709,31 +1709,34 @@ describe('float window', function() feed('ixygg') api.nvim_open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'}) if multigrid then - screen:expect{grid=[[ - ## grid 1 - [2:----------------------------------------]|*6 - [3:----------------------------------------]| - ## grid 2 - {20: 1}{30: }{22:^x}{21: }| - {14: 2}{19: }{22:y} | - {14: 3}{19: }{22: } | - {0:~ }|*3 - ## grid 3 - | - ## grid 4 - {15:x }| - {15:y }| - {15: }|*2 - ]], float_pos={[4] = {1001, "NW", 1, 4, 10, true}}} + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + {20: 1}{19: }{22:^x}{21: }| + {14: 2}{19: }{22:y} | + {14: 3}{19: }{22: } | + {0:~ }|*3 + ## grid 3 + | + ## grid 4 + {15:x }| + {15:y }| + {15: }|*2 + ]], + float_pos = { [4] = { 1001, "NW", 1, 4, 10, true, 50 } }, + }) else - screen:expect{grid=[[ - {20: 1}{30: }{22:^x}{21: }| + screen:expect([[ + {20: 1}{19: }{22:^x}{21: }| {14: 2}{19: }{22:y} | {14: 3}{19: }{22: } {15:x } | {0:~ }{15:y }{0: }| {0:~ }{15: }{0: }|*2 | - ]]} + ]]) end end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index bd3887b44f..ff03d86979 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -18,8 +18,6 @@ describe('Signs', function() [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Yellow }, [103] = { background = Screen.colors.Yellow, reverse = true }, [104] = { reverse = true, foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [105] = { bold = true, background = Screen.colors.Red1, foreground = Screen.colors.Gray100 }, - [106] = { foreground = Screen.colors.Brown, reverse = true }, } end) @@ -125,14 +123,7 @@ describe('Signs', function() ]]) -- Check that 'statuscolumn' correctly applies numhl exec('set statuscolumn=%s%=%l\\ ') - screen:expect([[ - {102:>>}{8: 1 }a | - {7: }{8: 2 }{9:b }| - {7: }{13: 3 }c | - {101:>>}{13: 4 }{9:^ }| - {1:~ }|*9 - | - ]]) + screen:expect_unchanged() end) it('highlights the cursorline sign with culhl', function() @@ -189,14 +180,7 @@ describe('Signs', function() -- Check that 'statuscolumn' cursorline/signcolumn highlights are the same (#21726) exec('set statuscolumn=%s') - screen:expect([[ - {102:>>}a | - {105:>>}^b | - {102:>>}c | - {106: } | - {1:~ }|*9 - | - ]]) + screen:expect_unchanged() end) it('multiple signs #9295', function() diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index ba60bab7e6..220c573b13 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -232,17 +232,17 @@ describe('statuscolumn', function() end) it('works with wrapped lines, signs and folds', function() - command([[set stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) - command("call setline(1,repeat([repeat('aaaaa',10)],16))") screen:add_extra_attr_ids { [100] = { foreground = Screen.colors.Red, background = Screen.colors.LightGray }, - [101] = { - bold = true, - background = Screen.colors.WebGray, - foreground = Screen.colors.DarkBlue, - }, + [101] = { background = Screen.colors.Gray90, bold = true }, + [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Grey }, + [103] = { bold = true, background = Screen.colors.Grey, foreground = Screen.colors.Blue1 }, } - command('hi! CursorLine guifg=Red guibg=NONE') + command([[set cursorline stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) + command("call setline(1,repeat([repeat('aaaaa',10)],16))") + command('hi! CursorLine gui=bold') + command('sign define num1 numhl=Special') + command('sign place 1 line=8 name=num1 buffer=1') screen:expect([[ {8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | @@ -252,8 +252,8 @@ describe('statuscolumn', function() {8: │ }a | {8: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | - {8: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {8: │ }a | + {29: 8│ }{101:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {29: │ }{101:a }| {8: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | {8:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:@@@}| @@ -261,7 +261,8 @@ describe('statuscolumn', function() ]]) command([[set stc=%C%s%=%l│\ ]]) screen:expect_unchanged() - command('set signcolumn=auto:2 foldcolumn=auto') + command('hi! CursorLine guifg=Red guibg=NONE gui=NONE') + command('set nocursorline signcolumn=auto:2 foldcolumn=auto') command('sign define piet1 text=>> texthl=LineNr') command('sign define piet2 text=>! texthl=NonText') command('sign place 1 line=4 name=piet1 buffer=1') @@ -269,11 +270,11 @@ describe('statuscolumn', function() command('sign place 3 line=6 name=piet1 buffer=1') command('sign place 4 line=6 name=piet2 buffer=1') screen:expect([[ - {8:>>}{7: }{8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {102:>>}{7: }{8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | - {1:>!}{7: }{8: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {103:>!}{7: }{8: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | - {1:>!}{8:>> 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {103:>!}{102:>>}{8: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | {7: }{8: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | @@ -288,11 +289,11 @@ describe('statuscolumn', function() -- Check that alignment works properly with signs after %= command([[set stc=%C%=%{v:virtnum?'':v:lnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | @@ -305,15 +306,15 @@ describe('statuscolumn', function() ]]) command('set cursorline') screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {101:+}{15: 8│}{101: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7:+}{15: 8│}{7: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{8: 9│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8:10│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| @@ -323,15 +324,15 @@ describe('statuscolumn', function() -- v:lnum is the same value on wrapped lines command([[set stc=%C%=%{v:lnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 4│}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 5│}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 6│}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 7│}{7: }{8: }aaaaaa | - {101:+}{15: 8│}{101: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7:+}{15: 8│}{7: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{8: 9│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 9│}{7: }{8: }aaaaaa | {7: }{8:10│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| @@ -341,15 +342,15 @@ describe('statuscolumn', function() -- v:relnum is the same value on wrapped lines command([[set stc=%C%=\ %{v:relnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 4│}{7: }{8: }aaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 3│}{7: }{8: }aaaaaaa | - {7: }{8: 2│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 2│}{7: }{8: }aaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 1│}{7: }{8: }aaaaaaa | - {101:+}{15: 0│}{101: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7:+}{15: 0│}{7: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 1│}{7: }{8: }aaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| @@ -358,15 +359,15 @@ describe('statuscolumn', function() ]]) command([[set stc=%C%=\ %{v:virtnum?'':v:relnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {7: }{8: 2│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {101:+}{15: 0│}{101: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7:+}{15: 0│}{7: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| @@ -383,15 +384,15 @@ describe('statuscolumn', function() command('sign place 10 line=6 name=piet2 buffer=1') command('sign place 11 line=6 name=piet1 buffer=1') screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 2│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {101:+}{15: 0│}{101: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaa}| + {7:+}{15: 0│}{7: }{15: }{100:^+-- 1 line: aaaaaaaaaaaaaaaa}| {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| @@ -402,11 +403,11 @@ describe('statuscolumn', function() command('set cpoptions+=n') feed('Hgjg0') screen:expect([[ - {101: }{15: 0│>>}{101: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:^aaaaaaaaaaaaaaaaaaaaa }| - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 2│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | @@ -421,11 +422,11 @@ describe('statuscolumn', function() command('sign unplace 2') feed('J2gjg0') screen:expect([[ - {101: }{15: 0│>>}{101: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: } {19:aaaaaaaaaaaaaaaaaaaaa aaaaaaa}| {7: } {19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: } {19:^aaaaaaaaaaaaaa }| - {7: }{8: 1│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 1│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: } aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: } aaaaaaaaaaaaaaaaaaaaa | @@ -439,11 +440,11 @@ describe('statuscolumn', function() command('set nobreakindent') feed('$g0') screen:expect([[ - {101: }{15: 0│>>}{101: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa}| {7: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:^aaaa }| - {7: }{8: 1│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 1│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | @@ -465,11 +466,11 @@ describe('statuscolumn', function() ]]) command('set foldcolumn=0 signcolumn=number stc=%l') screen:expect([[ - {8:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {102:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: }virt_line | {8: }virt_line above | - {8:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {102:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {15: 8}{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {8: 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | @@ -585,7 +586,7 @@ describe('statuscolumn', function() command([[set stc=%6s\ %l]]) exec_lua('vim.api.nvim_buf_set_extmark(0, ns, 7, 0, {sign_text = "𒀀"})') screen:expect([[ - {8: 𒀀 8}^aaaaa | + {8: }{7:𒀀 }{8: 8}^aaaaa | {8: }{7: }{8: 9}aaaaa | | ]]) -- cgit From a450fda4ededdd93e3dc571d82a6737f6b9d50d9 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 23 Jan 2025 10:42:00 +0200 Subject: fix(lsp): prefer `on_list` over `loclist` in default handler Problem: setting `loclist = true` makes `on_list` being ignored. This was not a problem before, but with `vim.lsp.buf.document_symbol` using `loclist = true` as default it is needed to explicitly pass `loclist = false` in order to use custom `on_list`. Solution: prefer `on_list` over `loclist` and document the latter as taking effect only in the default handler. --- runtime/doc/lsp.txt | 2 +- runtime/lua/vim/lsp/buf.lua | 2 +- runtime/lua/vim/lsp/handlers.lua | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index fffd668919..351f9a56ac 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1446,7 +1446,7 @@ Lua module: vim.lsp.buf *lsp-buf* vim.lsp.buf.references(nil, { on_list = on_list }) < • {loclist}? (`boolean`) Whether to use the |location-list| or the - |quickfix| list. >lua + |quickfix| list in the default handler. >lua vim.lsp.buf.definition({ loclist = true }) vim.lsp.buf.references(nil, { loclist = false }) < diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8efc6996dd..c57fdbee18 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -254,7 +254,7 @@ end --- ``` --- @field on_list? fun(t: vim.lsp.LocationOpts.OnList) --- ---- Whether to use the |location-list| or the |quickfix| list. +--- Whether to use the |location-list| or the |quickfix| list in the default handler. --- ```lua --- vim.lsp.buf.definition({ loclist = true }) --- vim.lsp.buf.references(nil, { loclist = false }) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 5da4033f89..a86ea99413 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -247,12 +247,12 @@ local function response_to_list(map_result, entity, title_fn) local items = map_result(result, ctx.bufnr) local list = { title = title, items = items, context = ctx } - if config.loclist then - vim.fn.setloclist(0, {}, ' ', list) - vim.cmd.lopen() - elseif config.on_list then + if config.on_list then assert(vim.is_callable(config.on_list), 'on_list is not a function') config.on_list(list) + elseif config.loclist then + vim.fn.setloclist(0, {}, ' ', list) + vim.cmd.lopen() else vim.fn.setqflist({}, ' ', list) vim.cmd('botright copen') -- cgit From 2cd72258f6be0ea20f0341be9bc0d306c4533535 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 21 Jan 2025 11:15:31 +0100 Subject: fix(mouse): 'statuscolumn' fold and popopmenu handling Problem: A right-click on the 'statuscolumn' does not open the popupmenu, even if a cell without a clickdef is clicked. Clicking the %C fold item does not open/close the fold. Solution: Open the popupmenu when there is no clickdef like right-clicking the sign/numbercolumn does. Fill "linebuf_vcol" when drawing the 'statuscolumn' to handle foldcolumn item clicks. --- src/nvim/drawline.c | 25 ++-- src/nvim/mouse.c | 190 +++++++++++++++---------------- src/nvim/statusline.c | 10 +- src/nvim/statusline_defs.h | 1 + test/functional/ui/statuscolumn_spec.lua | 89 ++++++++++++--- 5 files changed, 189 insertions(+), 126 deletions(-) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 74a766bd0c..5196d5c9f4 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -368,14 +368,14 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, // TODO(bfredl): integrate with grid.c linebuf code? madness? static void draw_col_buf(win_T *wp, winlinevars_T *wlv, const char *text, size_t len, int attr, - bool vcol) + const colnr_T *fold_vcol, bool inc_vcol) { const char *ptr = text; while (ptr < text + len && wlv->off < wp->w_grid.cols) { int cells = line_putchar(wp->w_buffer, &ptr, &linebuf_char[wlv->off], wp->w_grid.cols - wlv->off, wlv->off); int myattr = attr; - if (vcol) { + if (inc_vcol) { advance_color_col(wlv, wlv->vcol); if (wlv->color_cols && wlv->vcol == *wlv->color_cols) { myattr = hl_combine_attr(win_hl_attr(wp, HLF_MC), myattr); @@ -383,7 +383,7 @@ static void draw_col_buf(win_T *wp, winlinevars_T *wlv, const char *text, size_t } for (int c = 0; c < cells; c++) { linebuf_attr[wlv->off] = myattr; - linebuf_vcol[wlv->off] = vcol ? wlv->vcol++ : -1; + linebuf_vcol[wlv->off] = inc_vcol ? wlv->vcol++ : fold_vcol ? *(fold_vcol++) : -1; wlv->off++; } } @@ -412,7 +412,7 @@ static void draw_foldcolumn(win_T *wp, winlinevars_T *wlv) int fdc = compute_foldcolumn(wp, 0); if (fdc > 0) { int attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLF : HLF_FC); - fill_foldcolumn(wp, wlv->foldinfo, wlv->lnum, attr, fdc, &wlv->off, NULL); + fill_foldcolumn(wp, wlv->foldinfo, wlv->lnum, attr, fdc, &wlv->off, NULL, NULL); } } @@ -421,8 +421,9 @@ static void draw_foldcolumn(win_T *wp, winlinevars_T *wlv) /// @param fdc Current width of the foldcolumn /// @param[out] wlv_off Pointer to linebuf offset, incremented for default column /// @param[out] out_buffer Char array to fill, only used for 'statuscolumn' +/// @param[out] out_vcol vcol array to fill, only used for 'statuscolumn' void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, int fdc, int *wlv_off, - schar_T *out_buffer) + colnr_T *out_vcol, schar_T *out_buffer) { bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; int level = foldinfo.fi_level; @@ -448,10 +449,12 @@ void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, in symbol = schar_from_ascii('>'); } + int vcol = i >= level ? -1 : (i == closedcol - 1 && closed) ? -2 : -3; if (out_buffer) { + out_vcol[i] = vcol; out_buffer[i] = symbol; } else { - linebuf_vcol[*wlv_off] = i >= level ? -1 : (i == closedcol - 1 && closed) ? -2 : -3; + linebuf_vcol[*wlv_off] = vcol; linebuf_attr[*wlv_off] = attr; linebuf_char[(*wlv_off)++] = symbol; } @@ -577,7 +580,7 @@ static void draw_lnum_col(win_T *wp, winlinevars_T *wlv) char *num = skipwhite(buf); rl_mirror_ascii(num, skiptowhite(num)); } - draw_col_buf(wp, wlv, buf, (size_t)width, attr, false); + draw_col_buf(wp, wlv, buf, (size_t)width, attr, NULL, false); } else { draw_col_fill(wlv, schar_from_ascii(' '), width, attr); } @@ -632,6 +635,7 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir char *p = buf; char transbuf[MAXPATHL]; + colnr_T *fold_vcol = NULL; size_t len = strlen(buf); int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); int num_attr = hl_combine_attr(get_line_number_attr(wp, wlv), @@ -643,13 +647,14 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir ptrdiff_t textlen = sp->start - p; // Make all characters printable. size_t translen = transstr_buf(p, textlen, transbuf, MAXPATHL, true); - draw_col_buf(wp, wlv, transbuf, translen, cur_attr, false); + draw_col_buf(wp, wlv, transbuf, translen, cur_attr, fold_vcol, false); int attr = sp->item == STL_SIGNCOL ? scl_attr : sp->item == STL_FOLDCOL ? 0 : num_attr; cur_attr = hl_combine_attr(attr, sp->userhl < 0 ? syn_id2attr(-sp->userhl) : 0); + fold_vcol = sp->item == STL_FOLDCOL ? stcp->fold_vcol : NULL; p = sp->start; } size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true); - draw_col_buf(wp, wlv, transbuf, translen, num_attr, false); + draw_col_buf(wp, wlv, transbuf, translen, num_attr, fold_vcol, false); draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, num_attr); } @@ -722,7 +727,7 @@ static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv) // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. int attr = hl_combine_attr(wlv->cul_attr, win_hl_attr(wp, HLF_AT)); colnr_T vcol_before = wlv->vcol; - draw_col_buf(wp, wlv, sbr, strlen(sbr), attr, true); + draw_col_buf(wp, wlv, sbr, strlen(sbr), attr, NULL, true); wlv->vcol_sbr = wlv->vcol; // Correct start of highlighted area for 'showbreak'. diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 1289adfabb..b0ab235f6b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -280,6 +280,54 @@ static int get_fpos_of_mouse(pos_T *mpos) return IN_BUFFER; } +static int do_popup(int which_button, int m_pos_flag, pos_T m_pos) +{ + int jump_flags = 0; + if (strcmp(p_mousem, "popup_setpos") == 0) { + // First set the cursor position before showing the popup menu. + if (VIsual_active) { + // set MOUSE_MAY_STOP_VIS if we are outside the selection + // or the current window (might have false negative here) + if (m_pos_flag != IN_BUFFER) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else { + if (VIsual_mode == 'V') { + if ((curwin->w_cursor.lnum <= VIsual.lnum + && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) + || (VIsual.lnum < curwin->w_cursor.lnum + && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } else if ((ltoreq(curwin->w_cursor, VIsual) + && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) + || (lt(VIsual, curwin->w_cursor) + && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (VIsual_mode == Ctrl_V) { + colnr_T leftcol, rightcol; + getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); + getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); + if (m_pos.col < leftcol || m_pos.col > rightcol) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + } + } else { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + if (jump_flags) { + jump_flags = jump_to_mouse(jump_flags, NULL, which_button); + redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); + update_screen(); + setcursor(); + ui_flush(); // Update before showing popup menu + } + show_popupmenu(); + got_click = false; // ignore release events + return jump_flags; +} + /// Do the appropriate action for the current mouse click in the current mode. /// Not used for Command-line mode. /// @@ -324,24 +372,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT bool is_click; // If false it's a drag or release event bool is_drag; // If true it's a drag event - int jump_flags = 0; // flags for jump_to_mouse() - pos_T start_visual; - bool moved; // Has cursor moved? - bool in_winbar; // mouse in window bar - bool in_statuscol; // mouse in status column - bool in_status_line; // mouse in status line static bool in_tab_line = false; // mouse clicked in tab line - bool in_sep_line; // mouse in vertical separator line - int c1; - win_T *old_curwin = curwin; static pos_T orig_cursor; - colnr_T leftcol, rightcol; - pos_T end_visual; - int old_active = VIsual_active; - int old_mode = VIsual_mode; - int regname; - - pos_T save_cursor = curwin->w_cursor; while (true) { which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); @@ -434,12 +466,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) return false; } - if (oap != NULL) { - regname = oap->regname; - } else { - regname = 0; - } - + int regname = oap != NULL ? oap->regname : 0; // Middle mouse button does a 'put' of the selected text if (which_button == MOUSE_MIDDLE) { if (State == MODE_NORMAL) { @@ -496,12 +523,10 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) } } + // flags for jump_to_mouse() // When dragging or button-up stay in the same window. - if (!is_click) { - jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; - } - - start_visual.lnum = 0; + int jump_flags = is_click ? 0 : (MOUSE_FOCUS|MOUSE_DID_MOVE); + win_T *old_curwin = curwin; if (tab_page_click_defs != NULL) { // only when initialized // Check for clicking in the tab page line. @@ -515,8 +540,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) // click in a tab selects that tab page if (is_click && cmdwin_type == 0 && mouse_col < Columns) { + int tabnr = tab_page_click_defs[mouse_col].tabnr; in_tab_line = true; - c1 = tab_page_click_defs[mouse_col].tabnr; switch (tab_page_click_defs[mouse_col].type) { case kStlClickDisabled: @@ -527,11 +552,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) // double click opens new page end_visual_mode(); tabpage_new(); - tabpage_move(c1 == 0 ? 9999 : c1 - 1); + tabpage_move(tabnr == 0 ? 9999 : tabnr - 1); } else { // Go to specified tab page, or next one if not clicking // on a label. - goto_tabpage(c1); + goto_tabpage(tabnr); // It's like clicking on the status line of a window. if (curwin != old_curwin) { @@ -542,7 +567,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) } FALLTHROUGH; case kStlClickTabClose: - mouse_tab_close(c1); + mouse_tab_close(tabnr); break; case kStlClickFuncRun: call_click_def_func(tab_page_click_defs, mouse_col, which_button); @@ -556,75 +581,33 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) } } + int m_pos_flag = 0; + pos_T m_pos = { 0 }; // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: // right button up -> pop-up menu // shift-left button -> right button // alt-left button -> alt-right button if (mouse_model_popup()) { - pos_T m_pos; - int m_pos_flag = get_fpos_of_mouse(&m_pos); - if (m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) { - goto popupexit; - } - if (which_button == MOUSE_RIGHT - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + m_pos_flag = get_fpos_of_mouse(&m_pos); + if (!(m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) + && which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) { if (!is_click) { // Ignore right button release events, only shows the popup // menu on the button down event. return false; } - jump_flags = 0; - if (strcmp(p_mousem, "popup_setpos") == 0) { - // First set the cursor position before showing the popup menu. - if (VIsual_active) { - // set MOUSE_MAY_STOP_VIS if we are outside the selection - // or the current window (might have false negative here) - if (m_pos_flag != IN_BUFFER) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else { - if (VIsual_mode == 'V') { - if ((curwin->w_cursor.lnum <= VIsual.lnum - && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) - || (VIsual.lnum < curwin->w_cursor.lnum - && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } else if ((ltoreq(curwin->w_cursor, VIsual) - && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) - || (lt(VIsual, curwin->w_cursor) - && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); - getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); - if (m_pos.col < leftcol || m_pos.col > rightcol) { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - } - } else { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - if (jump_flags) { - jump_flags = jump_to_mouse(jump_flags, NULL, which_button); - redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); - update_screen(); - setcursor(); - ui_flush(); // Update before showing popup menu - } - show_popupmenu(); - got_click = false; // ignore release events - return (jump_flags & CURSOR_MOVED) != 0; + return (do_popup(which_button, m_pos_flag, m_pos) & CURSOR_MOVED); } - if (which_button == MOUSE_LEFT - && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { + // Only do this translation when mouse is over the buffer text + if (!(m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) + && (which_button == MOUSE_LEFT && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)))) { which_button = MOUSE_RIGHT; mod_mask &= ~MOD_MASK_SHIFT; } } -popupexit: + pos_T end_visual = { 0 }; + pos_T start_visual = { 0 }; if ((State & (MODE_NORMAL | MODE_INSERT)) && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { if (which_button == MOUSE_LEFT) { @@ -664,15 +647,15 @@ popupexit: } // JUMP! - jump_flags = jump_to_mouse(jump_flags, - oap == NULL ? NULL : &(oap->inclusive), - which_button); + int old_active = VIsual_active; + pos_T save_cursor = curwin->w_cursor; + jump_flags = jump_to_mouse(jump_flags, oap == NULL ? NULL : &(oap->inclusive), which_button); - moved = (jump_flags & CURSOR_MOVED); - in_winbar = (jump_flags & MOUSE_WINBAR); - in_statuscol = (jump_flags & MOUSE_STATUSCOL); - in_status_line = (jump_flags & IN_STATUS_LINE); - in_sep_line = (jump_flags & IN_SEP_LINE); + bool moved = (jump_flags & CURSOR_MOVED); + bool in_winbar = (jump_flags & MOUSE_WINBAR); + bool in_statuscol = (jump_flags & MOUSE_STATUSCOL); + bool in_status_line = (jump_flags & IN_STATUS_LINE); + bool in_sep_line = (jump_flags & IN_SEP_LINE); if ((in_winbar || in_status_line || in_statuscol) && is_click) { // Handle click event on window bar, status line or status column @@ -705,6 +688,12 @@ popupexit: if (click_defs != NULL) { switch (click_defs[click_col].type) { case kStlClickDisabled: + // If there is no click definition, still open the popupmenu for a + // statuscolumn click like a click in the sign/number column does. + if (in_statuscol && mouse_model_popup() + && which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) { + do_popup(which_button, m_pos_flag, m_pos); + } break; case kStlClickFuncRun: call_click_def_func(click_defs, click_col, which_button); @@ -715,7 +704,9 @@ popupexit: } } - return false; + if (!(in_statuscol && (jump_flags & (MOUSE_FOLD_CLOSE|MOUSE_FOLD_OPEN)))) { + return false; + } } else if (in_winbar || in_statuscol) { // A drag or release event in the window bar and status column has no side effects. return false; @@ -760,6 +751,7 @@ popupexit: mouse_row = 0; } + int old_mode = VIsual_mode; if (start_visual.lnum) { // right click in visual mode linenr_T diff; // When ALT is pressed make Visual mode blockwise. @@ -770,6 +762,7 @@ popupexit: // In Visual-block mode, divide the area in four, pick up the corner // that is in the quarter that the cursor is in. if (VIsual_mode == Ctrl_V) { + colnr_T leftcol, rightcol; getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); if (curwin->w_curswant > (leftcol + rightcol) / 2) { end_visual.col = leftcol; @@ -831,7 +824,6 @@ popupexit: // Middle mouse click: Put text before cursor. if (which_button == MOUSE_MIDDLE) { - int c2; if (regname == 0 && eval_has_provider("clipboard", false)) { regname = '*'; } @@ -843,6 +835,7 @@ popupexit: dir = FORWARD; } + int c1, c2; if (fixindent) { c1 = (dir == BACKWARD) ? '[' : ']'; c2 = 'p'; @@ -1258,8 +1251,6 @@ retnomove: if (flags & MOUSE_SETPOS) { goto retnomove; // ugly goto... } - win_T *old_curwin = curwin; - pos_T old_cursor = curwin->w_cursor; if (row < 0 || col < 0) { // check if it makes sense return IN_UNKNOWN; @@ -1299,13 +1290,15 @@ retnomove: grid = mouse_grid; } + win_T *old_curwin = curwin; + pos_T old_cursor = curwin->w_cursor; if (!keep_focus) { if (on_winbar) { return IN_OTHER_WIN | MOUSE_WINBAR; } if (on_statuscol) { - return IN_OTHER_WIN | MOUSE_STATUSCOL; + goto foldclick; } fdc = win_fdccol_count(wp); @@ -1497,6 +1490,7 @@ retnomove: } } +foldclick:; colnr_T col_from_screen = -1; int mouse_fold_flags = 0; mouse_check_grid(&col_from_screen, &mouse_fold_flags); @@ -1535,7 +1529,7 @@ retnomove: *inclusive = false; } - count = IN_BUFFER; + count = on_statuscol ? (IN_OTHER_WIN|MOUSE_STATUSCOL) : IN_BUFFER; if (curwin != old_curwin || curwin->w_cursor.lnum != old_cursor.lnum || curwin->w_cursor.col != old_cursor.col) { count |= CURSOR_MOVED; // Cursor has moved diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index f0437db1bb..6947a14a2c 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -1157,9 +1157,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op } } - // If the group is longer than it is allowed to be - // truncate by removing bytes from the start of the group text. - if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { + // If the group is longer than it is allowed to be truncate by removing + // bytes from the start of the group text. Don't truncate when item is a + // 'statuscolumn' fold item to ensure correctness of the mouse clicks. + if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid + && stl_items[stl_groupitems[groupdepth]].type != HighlightFold) { // { Determine the number of bytes to remove // Find the first character that should be included. @@ -1637,7 +1639,7 @@ stcsign: if (fdc > 0) { schar_T fold_buf[9]; - fill_foldcolumn(wp, stcp->foldinfo, lnum, 0, fdc, NULL, fold_buf); + fill_foldcolumn(wp, stcp->foldinfo, lnum, 0, fdc, NULL, stcp->fold_vcol, fold_buf); stl_items[curitem].minwid = -(use_cursor_line_highlight(wp, lnum) ? HLF_CLF : HLF_FC); size_t buflen = 0; // TODO(bfredl): this is very backwards. we must support schar_T diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h index f640d63150..83dda1e035 100644 --- a/src/nvim/statusline_defs.h +++ b/src/nvim/statusline_defs.h @@ -103,5 +103,6 @@ typedef struct { bool draw; ///< whether to draw the statuscolumn stl_hlrec_t *hlrec; ///< highlight groups foldinfo_T foldinfo; ///< fold information + colnr_T fold_vcol[9]; ///< vcol array filled for fold item SignTextAttrs *sattrs; ///< sign attributes } statuscol_T; diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 220c573b13..255eeb4754 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -13,8 +13,6 @@ local api = n.api local pcall_err = t.pcall_err local assert_alive = n.assert_alive -local mousemodels = { 'extend', 'popup', 'popup_setpos' } - describe('statuscolumn', function() local screen before_each(function() @@ -592,7 +590,7 @@ describe('statuscolumn', function() ]]) end) - for _, model in ipairs(mousemodels) do + for _, model in ipairs({ 'extend', 'popup', 'popup_setpos' }) do describe('with mousemodel=' .. model, function() before_each(function() command('set mousemodel=' .. model) @@ -651,23 +649,56 @@ describe('statuscolumn', function() -- Check that statusline click doesn't register as statuscolumn click api.nvim_input_mouse('right', 'press', '', 0, 12, 0) eq('', eval('g:testvar')) + -- Check that rightclick still opens popupmenu if there is no clickdef + if model == 'popup' then + api.nvim_set_option_value('statuscolumn', '%0@MyClickFunc@%=%l%TNoClick', {}) + api.nvim_input_mouse('right', 'press', '', 0, 1, 0) + screen:expect([[ + {5:[No Name] }| + {8: 4NoClick}^aaaaa | + {8: 5NoClick}aaaaa | + {8: 6NoClick}aaaaa | + {8: 7NoClick}aaaaa | + {8: 8NoClick}aaaaa | + {8: 9NoClick}aaaaa | + {8:10NoClick}aaaaa | + {8:11NoClick}aaaaa | + {8:12NoClick}aaaaa | + {8:13NoClick}aaaaa | + {8:14NoClick}aaaaa | + {3:[No Name] [+] }| + | + ]]) + api.nvim_input_mouse('right', 'press', '', 0, 1, 3) + screen:expect([[ + {5:[No Name] }| + {8: 4NoClick}^aaaaa | + {8: 5}{4: Inspect } | + {8: 6}{4: } | + {8: 7}{4: Paste } | + {8: 8}{4: Select All } | + {8: 9}{4: } | + {8:10}{4: How-to disable mouse } | + {8:11NoClick}aaaaa | + {8:12NoClick}aaaaa | + {8:13NoClick}aaaaa | + {8:14NoClick}aaaaa | + {3:[No Name] [+] }| + | + ]]) + end end) it('clicks and highlights work with control characters', function() api.nvim_set_option_value('statuscolumn', '\t%#NonText#\1%0@MyClickFunc@\t\1%T\t%##\1', {}) - screen:expect { - grid = [[ - {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |*4 - {1:^I}{0:^A^I^A^I}{1:^A}^aaaaa | - {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |*8 + screen:expect([[ + {8:^I}{1:^A^I^A^I}{8:^A}aaaaa |*4 + {8:^I}{1:^A^I^A^I}{8:^A}^aaaaa | + {8:^I}{1:^A^I^A^I}{8:^A}aaaaa |*8 | - ]], - attr_ids = { - [0] = { foreground = Screen.colors.Blue, bold = true }, -- NonText - [1] = { foreground = Screen.colors.Brown }, -- LineNr - }, - } + ]]) api.nvim_input_mouse('right', 'press', '', 0, 4, 3) + feed('') -- Close popupmenu eq('', eval('g:testvar')) api.nvim_input_mouse('left', 'press', '', 0, 5, 8) eq('', eval('g:testvar')) @@ -713,6 +744,36 @@ describe('statuscolumn', function() | ]]) end) + + it('foldcolumn item can be clicked', function() + api.nvim_set_option_value('statuscolumn', '|%C|', {}) + api.nvim_set_option_value('foldcolumn', '2', {}) + api.nvim_set_option_value('mousetime', 0, {}) + feed('ggzfjzfjzo') + local s1 = [[ + {8:|}{7:-+}{8:|}{13:^+--- 2 lines: aaaaa·····························}| + {8:|}{7:│ }{8:|}aaaaa | + {8:|}{7: }{8:|}aaaaa |*11 + | + ]] + screen:expect(s1) + api.nvim_input_mouse('left', 'press', '', 0, 0, 2) + screen:expect([[ + {8:|}{7:--}{8:|}^aaaaa | + {8:|}{7:││}{8:|}aaaaa | + {8:|}{7:│ }{8:|}aaaaa | + {8:|}{7: }{8:|}aaaaa |*10 + | + ]]) + api.nvim_input_mouse('left', 'press', '', 0, 0, 1) + screen:expect(s1) + api.nvim_input_mouse('left', 'press', '', 0, 0, 1) + screen:expect([[ + {8:|}{7:+ }{8:|}{13:^+-- 3 lines: aaaaa······························}| + {8:|}{7: }{8:|}aaaaa |*12 + | + ]]) + end) end) end -- cgit From 8bc28978b632362ae658f8d4a6327e07a8b371b2 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Fri, 24 Jan 2025 00:42:24 +0100 Subject: fix(column): apply custom highlight to last 'statuscolumn' segment (#32182) --- src/nvim/drawline.c | 4 ++-- test/functional/ui/statuscolumn_spec.lua | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 5196d5c9f4..4a7bc9170a 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -654,8 +654,8 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir p = sp->start; } size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true); - draw_col_buf(wp, wlv, transbuf, translen, num_attr, fold_vcol, false); - draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, num_attr); + draw_col_buf(wp, wlv, transbuf, translen, cur_attr, fold_vcol, false); + draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, cur_attr); } static void handle_breakindent(win_T *wp, winlinevars_T *wlv) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 255eeb4754..328e212a22 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -227,6 +227,14 @@ describe('statuscolumn', function() {1: }{8:8│}aaaaa | | ]]) + -- Last segment and fillchar are highlighted properly + command("set stc=%#Error#%{v:relnum?'Foo':'FooBar'}") + screen:expect([[ + {9:Foo }aaaaa |*4 + {9:FooBar}^aaaaa | + {9:Foo }aaaaa |*8 + | + ]]) end) it('works with wrapped lines, signs and folds', function() -- cgit From 2470db02c5136525b8abce1ee0889d94f8d81d98 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Jan 2025 08:52:55 +0800 Subject: vim-patch:partial:9.1.1050: too many strlen() calls in os_unix.c (#32188) Problem: too many strlen() calls in os_unix.c Solution: refactor os_unix.c and remove calls to strlen() (John Marriott) closes: vim/vim#16496 https://github.com/vim/vim/commit/efc41a5958bf25b352e0916af5f57dafbbb44f17 Omit os_expand_wildcards() change: Nvim's code is more complicated and harder to refactor. Co-authored-by: John Marriott --- src/nvim/os/fs.c | 6 +++--- src/nvim/os/shell.c | 10 ++++++---- src/nvim/path.c | 15 +++++++-------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index d0da37b8e7..451994241d 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -370,8 +370,8 @@ static bool is_executable_in_path(const char *name, char **abspath) char *path = xstrdup(path_env); #endif - size_t buf_len = strlen(name) + strlen(path) + 2; - char *buf = xmalloc(buf_len); + const size_t bufsize = strlen(name) + strlen(path) + 2; + char *buf = xmalloc(bufsize); // Walk through all entries in $PATH to check if "name" exists there and // is an executable file. @@ -382,7 +382,7 @@ static bool is_executable_in_path(const char *name, char **abspath) // Combine the $PATH segment with `name`. xmemcpyz(buf, p, (size_t)(e - p)); - (void)append_path(buf, name, buf_len); + (void)append_path(buf, name, bufsize); #ifdef MSWIN if (is_executable_ext(buf, abspath)) { diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 5347c8db9a..d60d0b3e55 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -1208,10 +1208,11 @@ static void read_input(StringBuilder *buf) size_t len = 0; linenr_T lnum = curbuf->b_op_start.lnum; char *lp = ml_get(lnum); + size_t lplen = (size_t)ml_get_len(lnum); while (true) { - size_t l = strlen(lp + written); - if (l == 0) { + lplen -= written; + if (lplen == 0) { len = 0; } else if (lp[written] == NL) { // NL -> NUL translation @@ -1219,11 +1220,11 @@ static void read_input(StringBuilder *buf) kv_push(*buf, NUL); } else { char *s = vim_strchr(lp + written, NL); - len = s == NULL ? l : (size_t)(s - (lp + written)); + len = s == NULL ? lplen : (size_t)(s - (lp + written)); kv_concat_len(*buf, lp + written, len); } - if (len == l) { + if (len == lplen) { // Finished a line, add a NL, unless this line should not have one. if (lnum != curbuf->b_op_end.lnum || (!curbuf->b_p_bin && curbuf->b_p_fixeol) @@ -1236,6 +1237,7 @@ static void read_input(StringBuilder *buf) break; } lp = ml_get(lnum); + lplen = (size_t)ml_get_len(lnum); written = 0; } else if (len > 0) { written += len; diff --git a/src/nvim/path.c b/src/nvim/path.c index 1ebc318809..08a63eacd0 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1875,11 +1875,12 @@ void path_fix_case(char *name) return; } + size_t taillen = strlen(tail); const char *entry; while ((entry = os_scandir_next(&dir))) { // Only accept names that differ in case and are the same byte // length. TODO: accept different length name. - if (STRICMP(tail, entry) == 0 && strlen(tail) == strlen(entry)) { + if (STRICMP(tail, entry) == 0 && taillen == strlen(entry)) { char newname[MAXPATHL + 1]; // Verify the inode is equal. @@ -2270,14 +2271,12 @@ int append_path(char *path, const char *to_append, size_t max_len) // Combine the path segments, separated by a slash. if (current_length > 0 && !vim_ispathsep_nocolon(path[current_length - 1])) { - current_length += 1; // Count the trailing slash. - // +1 for the NUL at the end. - if (current_length + 1 > max_len) { - return FAIL; + if (current_length + STRLEN_LITERAL(PATHSEPSTR) + 1 > max_len) { + return FAIL; // No space for trailing slash. } - - xstrlcat(path, PATHSEPSTR, max_len); + xstrlcpy(path + current_length, PATHSEPSTR, max_len - current_length); + current_length += STRLEN_LITERAL(PATHSEPSTR); } // +1 for the NUL at the end. @@ -2285,7 +2284,7 @@ int append_path(char *path, const char *to_append, size_t max_len) return FAIL; } - xstrlcat(path, to_append, max_len); + xstrlcpy(path + current_length, to_append, max_len - current_length); return OK; } -- cgit From 3702bcb139275beacb9d3d37f833b16d899f5013 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 16 Jan 2025 18:08:45 +0100 Subject: ci(tests): add arm64 runner Problem: Linux `aarch64`/`arm64` builds are not tested. Solution: Add `ubuntu-arm` runners to test matrix (using `RelWithDebInfo` build). --- .github/scripts/install_deps.sh | 2 +- .github/workflows/test.yml | 1 + runtime/doc/support.txt | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh index 2aec8ea553..5e2c88c7af 100755 --- a/.github/scripts/install_deps.sh +++ b/.github/scripts/install_deps.sh @@ -30,7 +30,7 @@ if [[ $os == Linux ]]; then fi if [[ -n $TEST ]]; then - sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb inotify-tools + sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb inotify-tools xdg-utils # Use default CC to avoid compilation problems when installing Python modules CC=cc python3 -m pip -q install --user --upgrade --break-system-packages pynvim diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c7802d2210..e7032a3dda 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -107,6 +107,7 @@ jobs: { runner: ubuntu-24.04, os: ubuntu, flavor: asan, cc: clang, flags: -D ENABLE_ASAN_UBSAN=ON }, { runner: ubuntu-24.04, os: ubuntu, flavor: tsan, cc: clang, flags: -D ENABLE_TSAN=ON }, { runner: ubuntu-24.04, os: ubuntu, flavor: release, cc: gcc, flags: -D CMAKE_BUILD_TYPE=Release }, + { runner: ubuntu-24.04-arm, os: ubuntu, flavor: arm, cc: gcc, flags: -D CMAKE_BUILD_TYPE=RelWithDebInfo }, { runner: macos-13, os: macos, flavor: intel, cc: clang, flags: -D CMAKE_FIND_FRAMEWORK=NEVER, deps_flags: -D CMAKE_FIND_FRAMEWORK=NEVER }, { runner: macos-15, os: macos, flavor: arm, cc: clang, flags: -D CMAKE_FIND_FRAMEWORK=NEVER, deps_flags: -D CMAKE_FIND_FRAMEWORK=NEVER }, { runner: ubuntu-24.04, os: ubuntu, flavor: puc-lua, cc: gcc, deps_flags: -D USE_BUNDLED_LUAJIT=OFF -D USE_BUNDLED_LUA=ON, flags: -D PREFER_LUA=ON }, diff --git a/runtime/doc/support.txt b/runtime/doc/support.txt index a2776fca0d..088677e813 100644 --- a/runtime/doc/support.txt +++ b/runtime/doc/support.txt @@ -12,7 +12,8 @@ Support *support* Supported platforms *supported-platforms* `System` `Tier` `Versions` `Tested versions` -Linux 1 >= 2.6.32, glibc >= 2.12 Ubuntu 24.04 +Linux (x86_64) 1 >= 2.6.32, glibc >= 2.12 Ubuntu 24.04 +Linux (arm64) 1 >= 2.6.32, glibc >= 2.12 Ubuntu 24.04 macOS (Intel) 1 >= 11 macOS 13 macOS (M1) 1 >= 11 macOS 15 Windows 64-bit 1 >= Windows 10 Version 1809 Windows Server 2022 -- cgit From 0fd4ef5da7448fa3449643b23d6aa3af1640efe8 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 19 Jan 2025 17:36:35 +0100 Subject: ci(tests): remove build-types jobs Problem: Some CI jobs are redundant: `RelWithDebInfo` is already tested on Linux-Arm64; `MinSizeRel` and Ninja Multi Config are not sufficiently relevant in practice to spend CI cycles on. Solution: Remove `build-types` job. --- .github/workflows/test.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e7032a3dda..a366dea082 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -204,37 +204,6 @@ jobs: windows: uses: ./.github/workflows/test_windows.yml - # This job tests the following things: - # - Check if MinSizeRel and RelWithDebInfo compiles correctly. - # - Test the above build types with the GCC compiler specifically. - # Empirically the difference in warning levels between GCC and other - # compilers is particularly big. - # - Test if the build works with multi-config generators. We mostly use - # single-config generators so it's nice to have a small sanity check for - # multi-config. - build-types: - runs-on: ubuntu-24.04 - timeout-minutes: 10 - env: - CC: gcc - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup - - - name: Build third-party deps - run: | - cmake -S cmake.deps -B .deps -G "Ninja Multi-Config" - cmake --build .deps - - - name: Configure - run: cmake --preset ci -G "Ninja Multi-Config" - - - name: RelWithDebInfo - run: cmake --build build --config RelWithDebInfo - - - name: MinSizeRel - run: cmake --build build --config MinSizeRel - with-external-deps: runs-on: ubuntu-24.04 timeout-minutes: 10 -- cgit From d98827b634af29d74079d1848dd5e8c5d2be1233 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Wed, 22 Jan 2025 23:13:40 +0100 Subject: fix(messages): avoid empty msg_showmode with 'noshowmode' --- src/nvim/message.c | 6 +++- test/functional/lua/ui_event_spec.lua | 63 ++++++++++++++++++----------------- test/functional/ui/messages_spec.lua | 22 +++++++++--- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index 4c20edb7eb..6fc102e4ff 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -3183,7 +3183,11 @@ void msg_ext_flush_showmode(void) { // Showmode messages doesn't interrupt normal message flow, so we use // separate event. Still reuse the same chunking logic, for simplicity. - if (ui_has(kUIMessages)) { + // This is called unconditionally; check if we are emitting, or have + // emitted non-empty "content". + static bool clear = false; + if (ui_has(kUIMessages) && (msg_ext_last_attr != -1 || clear)) { + clear = msg_ext_last_attr != -1; msg_ext_emit_chunk(); Array *tofree = msg_ext_init_chunks(); ui_call_msg_showmode(*tofree); diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index c6f98c8640..8bfd574fb9 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -345,31 +345,36 @@ describe('vim.ui_attach', function() vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) end) ]]) + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect(s1) + feed('QQQQQQ') screen:expect({ grid = [[ - ^ | - {1:~ }|*4 - ]], - }) - feed('ifoo') - screen:expect({ - grid = [[ - foo^ | - {1:~ }|*4 - ]], - showmode = { { '-- INSERT --', 5, 11 } }, - }) - feed(':1mes clear:mes') - screen:expect({ - grid = [[ - foo | - {3: }| - {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 1.} | + {9:obal 'err' (a nil value)} | + {9:stack traceback:} | + {9: [string ""]:2: in function}| + {9: <[string ""]:1>} | {100:Press ENTER or type command to continue}^ | ]], - cmdline = { { abort = false } }, + messages = { + { + content = { { 'Press ENTER or type command to continue', 100, 18 } }, + history = true, + kind = 'return_prompt', + }, + }, }) + feed(':1mes clear:mes') + screen:expect([[ + | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 1.} | + {100:Press ENTER or type command to continue}^ | + ]]) feed('') -- Also when scheduled exec_lua([[ @@ -378,7 +383,7 @@ describe('vim.ui_attach', function() end) ]]) screen:expect({ - any = 'fo^o', + grid = s1, messages = { { content = { @@ -410,14 +415,12 @@ describe('vim.ui_attach', function() }, }) feed(':1mes clear:mes') - screen:expect({ - grid = [[ - foo | - {3: }| - {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 2.} | - {100:Press ENTER or type command to continue}^ | - ]], - }) + screen:expect([[ + | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 2.} | + {100:Press ENTER or type command to continue}^ | + ]]) end) end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index b70bd0e808..4038e596fa 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1391,13 +1391,25 @@ stack traceback: screen_showmode(...) showmode = showmode + 1 end + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect(s1) + eq(showmode, 0) + feed('i') screen:expect({ - grid = [[ - ^ | - {1:~ }|*4 - ]], + grid = s1, + showmode = { { '-- INSERT --', 5, 11 } }, }) - eq(showmode, 1) + eq(showmode, 2) + command('set noshowmode') + feed('') + screen:expect(s1) + eq(showmode, 3) + feed('i') + screen:expect_unchanged() + eq(showmode, 3) end) it('emits single message for multiline print())', function() -- cgit From c6d2cbf8f51abfa0c9d244ef384a15b0b69e16c6 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 23 Jan 2025 12:42:38 +0100 Subject: fix(lua): pop retval for fast context LuaRef Problem: nlua_call_ref_ctx() does not pop the return value in fast context that did not error. Solution: Fall through to end; calling nlua_call_pop_retval(). --- src/nvim/lua/executor.c | 2 +- test/functional/lua/ui_event_spec.lua | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index a5b48a5d5e..71c5cd4585 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1587,8 +1587,8 @@ Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, Lu if (nlua_fast_cfpcall(lstate, nargs, 1, -1) < 0) { // error is already scheduled, set anyways to convey failure. api_set_error(err, kErrorTypeException, "fast context failure"); + return NIL; } - return NIL; } else if (nlua_pcall(lstate, nargs, 1)) { // if err is passed, the caller will deal with the error. if (err) { diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 8bfd574fb9..80457555d4 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -423,4 +423,13 @@ describe('vim.ui_attach', function() {100:Press ENTER or type command to continue}^ | ]]) end) + + it('sourcing invalid file does not crash #32166', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace("") + vim.ui_attach(ns, { ext_messages = true }, function() end) + ]]) + feed((':luafile %s'):format(testlog)) + n.assert_alive() + end) end) -- cgit From 931ee5591fa764a769946318e05062098baf7c21 Mon Sep 17 00:00:00 2001 From: georgev93 <39860568+georgev93@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:57:45 -0500 Subject: feat(extmarks): virtual text can be right-aligned, truncated #31921 Problem: Right aligned virtual text can cover up buffer text if virtual text is too long Solution: An additional option for `virt_text_pos` called `eol_right_align` has been added to truncate virtual text if it would have otherwise covered up buffer text. This ensures the virtual text extends no further left than EOL. --- runtime/doc/api.txt | 7 ++++ runtime/doc/diagnostic.txt | 4 +-- runtime/doc/news.txt | 2 ++ runtime/lua/vim/_meta/api.lua | 9 +++++ runtime/lua/vim/diagnostic.lua | 2 +- src/nvim/api/extmark.c | 11 ++++++ src/nvim/decoration.h | 4 +-- src/nvim/decoration_defs.h | 5 +-- src/nvim/drawline.c | 44 +++++++++++++++++++++-- test/functional/ui/decorations_spec.lua | 63 +++++++++++++++++++++++++++++++++ 10 files changed, 142 insertions(+), 9 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index c5ade72f93..1a5df18f6c 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2609,6 +2609,13 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) last). • virt_text_pos : position of virtual text. Possible values: • "eol": right after eol character (default). + • "eol_right_align": display right aligned in the window + unless the virtual text is longer than the space + available. If the virtual text is too long, it is + truncated to fit in the window after the EOL character. + If the line is wrapped, the virtual text is shown after + the end of the line rather than the previous screen + line. • "overlay": display over the specified column, without shifting the underlying text. • "right_align": display right aligned in the window. diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index d4939d8cc7..6b1456d5a6 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -626,8 +626,8 @@ Lua module: vim.diagnostic *diagnostic-api* • {hl_mode}? (`'replace'|'combine'|'blend'`) See |nvim_buf_set_extmark()|. • {virt_text}? (`[string,any][]`) See |nvim_buf_set_extmark()|. - • {virt_text_pos}? (`'eol'|'overlay'|'right_align'|'inline'`) See - |nvim_buf_set_extmark()|. + • {virt_text_pos}? (`'eol'|'eol_right_align'|'inline'|'overlay'|'right_align'`) + See |nvim_buf_set_extmark()|. • {virt_text_win_col}? (`integer`) See |nvim_buf_set_extmark()|. • {virt_text_hide}? (`boolean`) See |nvim_buf_set_extmark()|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 099fc17c5d..1d53f168ff 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -195,6 +195,8 @@ API • |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline". • |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups • |vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight +• |nvim_buf_set_extmark()| virt_text_pos accepts `eol_right_align` to + allow for right aligned text that truncates before covering up buffer text. DEFAULTS diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 6d9a17ea2b..50fb7e4f9d 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -589,6 +589,15 @@ function vim.api.nvim_buf_line_count(buffer) end --- (highest priority last). --- - virt_text_pos : position of virtual text. Possible values: --- - "eol": right after eol character (default). +--- - "eol_right_align": display right aligned in the window +--- unless the virtual text is longer than +--- the space available. If the virtual +--- text is too long, it is truncated to +--- fit in the window after the EOL +--- character. If the line is wrapped, the +--- virtual text is shown after the end of +--- the line rather than the previous +--- screen line. --- - "overlay": display over the specified column, without --- shifting the underlying text. --- - "right_align": display right aligned in the window. diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index ead75f7d51..04118999cf 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -220,7 +220,7 @@ end --- @field virt_text? [string,any][] --- --- See |nvim_buf_set_extmark()|. ---- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline' +--- @field virt_text_pos? 'eol'|'eol_right_align'|'inline'|'overlay'|'right_align' --- --- See |nvim_buf_set_extmark()|. --- @field virt_text_win_col? integer diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index e66140da5a..778e857057 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -400,6 +400,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// (highest priority last). /// - virt_text_pos : position of virtual text. Possible values: /// - "eol": right after eol character (default). +/// - "eol_right_align": display right aligned in the window +/// unless the virtual text is longer than +/// the space available. If the virtual +/// text is too long, it is truncated to +/// fit in the window after the EOL +/// character. If the line is wrapped, the +/// virtual text is shown after the end of +/// the line rather than the previous +/// screen line. /// - "overlay": display over the specified column, without /// shifting the underlying text. /// - "right_align": display right aligned in the window. @@ -620,6 +629,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer virt_text.pos = kVPosOverlay; } else if (strequal("right_align", str.data)) { virt_text.pos = kVPosRightAlign; + } else if (strequal("eol_right_align", str.data)) { + virt_text.pos = kVPosEndOfLineRightAlign; } else if (strequal("inline", str.data)) { virt_text.pos = kVPosInline; } else { diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index a2f4fefd45..bdbb1795cb 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -15,8 +15,8 @@ // actual Decor* data is in decoration_defs.h /// Keep in sync with VirtTextPos in decoration_defs.h -EXTERN const char *const virt_text_pos_str[] -INIT( = { "eol", "overlay", "win_col", "right_align", "inline" }); +EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "eol_right_align", "inline", + "overlay", "right_align", "win_col" }); /// Keep in sync with HlMode in decoration_defs.h EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" }); diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h index 58ba93a7ba..36ad6df7a0 100644 --- a/src/nvim/decoration_defs.h +++ b/src/nvim/decoration_defs.h @@ -19,10 +19,11 @@ typedef kvec_t(VirtTextChunk) VirtText; /// Keep in sync with virt_text_pos_str[] in decoration.h typedef enum { kVPosEndOfLine, + kVPosEndOfLineRightAlign, + kVPosInline, kVPosOverlay, - kVPosWinCol, kVPosRightAlign, - kVPosInline, + kVPosWinCol, } VirtTextPos; typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 4a7bc9170a..37a42917b0 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -263,6 +263,9 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int int *const indices = state->ranges_i.items; DecorRangeSlot *const slots = state->slots.items; + /// Total width of all virtual text with "eol_right_align" alignment + int totalWidthOfEolRightAlignedVirtText = 0; + for (int i = 0; i < end; i++) { DecorRange *item = &slots[indices[i]].range; if (!(item->start_row == state->row && decor_virt_pos(item))) { @@ -277,7 +280,44 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int if (decor_virt_pos(item) && item->draw_col == -1) { bool updated = true; VirtTextPos pos = decor_virt_pos_kind(item); - if (pos == kVPosRightAlign) { + + if (do_eol && pos == kVPosEndOfLineRightAlign) { + int eolOffset = 0; + if (totalWidthOfEolRightAlignedVirtText == 0) { + // Look ahead to the remaining decor items + for (int j = i; j < end; j++) { + /// A future decor to be handled in this function's call + DecorRange *lookaheadItem = &slots[indices[j]].range; + + if (lookaheadItem->start_row != state->row + || !decor_virt_pos(lookaheadItem) + || lookaheadItem->draw_col != -1) { + continue; + } + + /// The Virtual Text of the decor item we're looking ahead to + DecorVirtText *lookaheadVt = NULL; + if (item->kind == kDecorKindVirtText) { + assert(item->data.vt); + lookaheadVt = item->data.vt; + } + + if (decor_virt_pos_kind(lookaheadItem) == kVPosEndOfLineRightAlign) { + // An extra space is added for single character spacing in EOL alignment + totalWidthOfEolRightAlignedVirtText += (lookaheadVt->width + 1); + } + } + + // Remove one space from the total width since there's no single space after the last entry + totalWidthOfEolRightAlignedVirtText--; + + if (totalWidthOfEolRightAlignedVirtText <= (right_pos - state->eol_col)) { + eolOffset = right_pos - totalWidthOfEolRightAlignedVirtText - state->eol_col; + } + } + + item->draw_col = state->eol_col + eolOffset; + } else if (pos == kVPosRightAlign) { right_pos -= vt->width; item->draw_col = right_pos; } else if (pos == kVPosEndOfLine && do_eol) { @@ -304,7 +344,7 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int int vcol = item->draw_col - col_off; int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, vt->hl_mode, max_col, vcol); - if (vt->pos == kVPosEndOfLine && do_eol) { + if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) { state->eol_col = col + 1; } *end_col = MAX(*end_col, col); diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index c2030b9527..7969dd5d3b 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -509,6 +509,69 @@ describe('decorations providers', function() ]]} end) + it('can have virtual text of the style: eol_right_align', function() + insert(mulholland) + setup_provider [[ + local hl = api.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = api.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'+'}, {'1234567890', 'ErrorMsg'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive +{2:1234567890}| + try_start(); +{2:1234567890}| + bufref_T save_buf; +{2:1234567890}| + switch_buffer(&save_buf, buf); +{2:12345678}| + posp = getmark(mark, false); +{2:1234567890}| + restore_buffer(&save_buf);^ +{2:1234567890}| + | + ]]} + end) + + it('multiple eol_right_align', function() + insert(mulholland) + setup_provider [[ + local hl = api.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = api.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'11111'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + api.nvim_buf_set_extmark(0, test_ns, line, 0, { + virt_text = {{'22222'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive 11111 22222| + try_start(); 11111 22222| + bufref_T save_buf; 11111 22222| + switch_buffer(&save_buf, buf); 11111 222| + posp = getmark(mark, false); 11111 22222| + restore_buffer(&save_buf);^ 11111 22222| + | + ]]} + end) + it('virtual text works with wrapped lines', function() insert(mulholland) feed('ggJj3JjJ') -- cgit From 851137f67905f6038e51b5b7d1490fbedea4faaa Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:05:47 +0800 Subject: fix(log): log unset $TMPDIR at "debug" level #32137 --- src/nvim/fileio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 1c9903695e..61b252f823 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3277,7 +3277,7 @@ static void vim_mktempdir(void) if (!os_isdir(tmp)) { if (strequal("$TMPDIR", temp_dirs[i])) { if (!os_getenv("TMPDIR")) { - WLOG("$TMPDIR is unset"); + DLOG("$TMPDIR is unset"); } else { WLOG("$TMPDIR tempdir not a directory (or does not exist): \"%s\"", tmp); } -- cgit From 63aa167f944b147b9d4b8c417a37f4beb212d984 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Jan 2025 06:44:23 +0800 Subject: vim-patch:9.1.1049: insert-completed items are always sorted Problem: insert-completed items are always sorted, although the LSP spec[1] standard defines sortText in the returned completionitem list. This means that the server has sorted the results. When fuzzy is enabled, this will break the server's sorting results. Solution: disable sorting of candidates when "nosort" is set in 'completeopt' [1] https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem closes: vim/vim#16501 https://github.com/vim/vim/commit/f400a0cc41113eb75516bdd7f38aeaa15208ba2c Co-authored-by: glepnir --- runtime/doc/options.txt | 4 ++++ runtime/lua/vim/_meta/options.lua | 4 ++++ src/nvim/insexpand.c | 19 +++++++++++-------- src/nvim/options.lua | 5 +++++ test/old/testdir/test_ins_complete.vim | 17 ++++++++++++++++- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 8d171183d6..1f19db5eb9 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1571,6 +1571,10 @@ A jump table for the options with a short description can be found at |Q_op|. list of alternatives, but not how the candidates are collected (using different completion types). + nosort Disable sorting of completion candidates based on fuzzy + scores when "fuzzy" is enabled. Candidates will appear + in their original order. + *'completeslash'* *'csl'* 'completeslash' 'csl' string (default "") local to buffer diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 107b1ffdfb..f0f0d1a768 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1098,6 +1098,10 @@ vim.go.cia = vim.go.completeitemalign --- list of alternatives, but not how the candidates are --- collected (using different completion types). --- +--- nosort Disable sorting of completion candidates based on fuzzy +--- scores when "fuzzy" is enabled. Candidates will appear +--- in their original order. +--- --- @type string vim.o.completeopt = "menu,preview" vim.o.cot = vim.o.completeopt diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index a5f5a4eda3..83e0602e7e 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1212,7 +1212,8 @@ static int ins_compl_build_pum(void) int max_fuzzy_score = 0; unsigned cur_cot_flags = get_cot_flags(); bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; - bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; + bool fuzzy_nosort = (cur_cot_flags & kOptCotFlagNosort) != 0; + bool fuzzy_filter = fuzzy_nosort || (cur_cot_flags & kOptCotFlagFuzzy) != 0; compl_T *match_head = NULL, *match_tail = NULL; // If the current match is the original text don't find the first @@ -1232,14 +1233,14 @@ static int ins_compl_build_pum(void) comp->cp_in_match_array = false; // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, // set the cp_score for later comparisons. - if (compl_fuzzy_match && compl_leader.data != NULL && compl_leader.size > 0) { + if (fuzzy_filter && compl_leader.data != NULL && compl_leader.size > 0) { comp->cp_score = fuzzy_match_str(comp->cp_str.data, compl_leader.data); } if (!match_at_original_text(comp) && (compl_leader.data == NULL || ins_compl_equal(comp, compl_leader.data, compl_leader.size) - || (compl_fuzzy_match && comp->cp_score > 0))) { + || (fuzzy_filter && comp->cp_score > 0))) { compl_match_arraysize++; comp->cp_in_match_array = true; if (match_head == NULL) { @@ -1248,7 +1249,7 @@ static int ins_compl_build_pum(void) match_tail->cp_match_next = comp; } match_tail = comp; - if (!shown_match_ok && !compl_fuzzy_match) { + if (!shown_match_ok && !fuzzy_filter) { if (comp == compl_shown_match || did_find_shown_match) { // This item is the shown match or this is the // first displayed item after the shown match. @@ -1261,18 +1262,20 @@ static int ins_compl_build_pum(void) shown_compl = comp; } cur = i; - } else if (compl_fuzzy_match) { + } else if (fuzzy_filter) { if (i == 0) { shown_compl = comp; } // Update the maximum fuzzy score and the shown match // if the current item's score is higher - if (comp->cp_score > max_fuzzy_score) { + if (!fuzzy_nosort && comp->cp_score > max_fuzzy_score) { did_find_shown_match = true; max_fuzzy_score = comp->cp_score; if (!compl_no_select) { compl_shown_match = comp; } + } else if (fuzzy_nosort && i == 0 && !compl_no_select) { + compl_shown_match = shown_compl; } if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) { cur = i; @@ -1282,7 +1285,7 @@ static int ins_compl_build_pum(void) i++; } - if (comp == compl_shown_match && !compl_fuzzy_match) { + if (comp == compl_shown_match && !fuzzy_filter) { did_find_shown_match = true; // When the original text is the shown match don't set // compl_shown_match. @@ -1323,7 +1326,7 @@ static int ins_compl_build_pum(void) comp = match_next; } - if (compl_fuzzy_match && compl_leader.data != NULL && compl_leader.size > 0) { + if (fuzzy_filter && !fuzzy_nosort && compl_leader.data != NULL && compl_leader.size > 0) { for (i = 0; i < compl_match_arraysize; i++) { compl_match_array[i].pum_idx = i; } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index ee8034a871..c6f5221c8b 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1493,6 +1493,7 @@ local options = { 'noinsert', 'noselect', 'fuzzy', + 'nosort', }, flags = true, deny_duplicates = true, @@ -1537,6 +1538,10 @@ local options = { difference how completion candidates are reduced from the list of alternatives, but not how the candidates are collected (using different completion types). + + nosort Disable sorting of completion candidates based on fuzzy + scores when "fuzzy" is enabled. Candidates will appear + in their original order. ]=], full_name = 'completeopt', list = 'onecomma', diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index f1ff45ff90..988b7995ed 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -2778,7 +2778,7 @@ func Test_complete_fuzzy_match() if a:findstart return col(".") endif - return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}] + return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}, #{word: "你好吗"}, #{word: "我好"}] endfunc new @@ -2837,6 +2837,21 @@ func Test_complete_fuzzy_match() call feedkeys("STe\\x\\0", 'tx!') call assert_equal('Tex', getline('.')) + " test case for nosort option + set cot=menuone,menu,noinsert,fuzzy,nosort + " fooBaz" should have a higher score when the leader is "fb". + " With `nosort`, "foobar" should still be shown first in the popup menu. + call feedkeys("S\\fb", 'tx') + call assert_equal('foobar', g:word) + call feedkeys("S\\好", 'tx') + call assert_equal("你好吗", g:word) + + set cot+=noselect + call feedkeys("S\\好", 'tx') + call assert_equal(v:null, g:word) + call feedkeys("S\\好\", 'tx') + call assert_equal('你好吗', g:word) + " clean up set omnifunc= bw! -- cgit From 689c23b2ac5c8cb5953e45f9f0cf6c369e8d88df Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 25 Jan 2025 22:34:54 +0800 Subject: vim-patch:9.1.1053: "nosort" enables fuzzy filtering even if "fuzzy" isn't in 'completeopt' Problem: "nosort" enables fuzzy filtering even if "fuzzy" isn't in 'completeopt' (after v9.1.1049) Solution: Only enable fuzzy filtering when "fuzzy" is in 'completeopt'. (zeertzjq) closes: vim/vim#16510 https://github.com/vim/vim/commit/d65aa1bbdb808ef8fecde6df240c48cc39a52a8e --- src/nvim/insexpand.c | 10 +++++----- test/old/testdir/test_ins_complete.vim | 9 +++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 83e0602e7e..8eb1a49e7d 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1212,8 +1212,8 @@ static int ins_compl_build_pum(void) int max_fuzzy_score = 0; unsigned cur_cot_flags = get_cot_flags(); bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; - bool fuzzy_nosort = (cur_cot_flags & kOptCotFlagNosort) != 0; - bool fuzzy_filter = fuzzy_nosort || (cur_cot_flags & kOptCotFlagFuzzy) != 0; + bool fuzzy_filter = (cur_cot_flags & kOptCotFlagFuzzy) != 0; + bool fuzzy_sort = fuzzy_filter && !(cur_cot_flags & kOptCotFlagNosort); compl_T *match_head = NULL, *match_tail = NULL; // If the current match is the original text don't find the first @@ -1268,13 +1268,13 @@ static int ins_compl_build_pum(void) } // Update the maximum fuzzy score and the shown match // if the current item's score is higher - if (!fuzzy_nosort && comp->cp_score > max_fuzzy_score) { + if (fuzzy_sort && comp->cp_score > max_fuzzy_score) { did_find_shown_match = true; max_fuzzy_score = comp->cp_score; if (!compl_no_select) { compl_shown_match = comp; } - } else if (fuzzy_nosort && i == 0 && !compl_no_select) { + } else if (!fuzzy_sort && i == 0 && !compl_no_select) { compl_shown_match = shown_compl; } if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) { @@ -1326,7 +1326,7 @@ static int ins_compl_build_pum(void) comp = match_next; } - if (fuzzy_filter && !fuzzy_nosort && compl_leader.data != NULL && compl_leader.size > 0) { + if (fuzzy_sort && compl_leader.data != NULL && compl_leader.size > 0) { for (i = 0; i < compl_match_arraysize; i++) { compl_match_array[i].pum_idx = i; } diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 988b7995ed..1c63e8f4cc 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -2839,8 +2839,8 @@ func Test_complete_fuzzy_match() " test case for nosort option set cot=menuone,menu,noinsert,fuzzy,nosort - " fooBaz" should have a higher score when the leader is "fb". - " With `nosort`, "foobar" should still be shown first in the popup menu. + " "fooBaz" should have a higher score when the leader is "fb". + " With "nosort", "foobar" should still be shown first in the popup menu. call feedkeys("S\\fb", 'tx') call assert_equal('foobar', g:word) call feedkeys("S\\好", 'tx') @@ -2852,6 +2852,11 @@ func Test_complete_fuzzy_match() call feedkeys("S\\好\", 'tx') call assert_equal('你好吗', g:word) + " "nosort" shouldn't enable fuzzy filtering when "fuzzy" isn't present. + set cot=menuone,noinsert,nosort + call feedkeys("S\\fooB\", 'tx') + call assert_equal('fooBaz', getline('.')) + " clean up set omnifunc= bw! -- cgit From d4a65dad23b910ad0e5f44cc1720e8bd82bd749b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 25 Jan 2025 23:03:43 +0800 Subject: vim-patch:9.1.1052: tests: off-by-one error in CheckCWD in test_debugger.vim (#32202) Problem: tests: off-by-one error in CheckCWD in test_debugger.vim Solution: Fix off-by-one in CheckCWD leading to local tests failure (Yee Cheng Chin) Vim's test_debugger's Test_debug_backtrace_level test will fail if you happen to run it in a Vim repository with full path of directory being exactly 29 characters (e.g. `/Users/bob/developing/src/vim`). The test does term dump comparison and the printout will overflow if the CWD is too long. It does have a function to skip to test if it detects that but it's off by one leading to this one situation where it will fail. The reason why the logic didn't account for this is that Vim's message printing will overflow the text if it prints a message at exactly the width of the terminal. This could be considered a bug / quirk but that will be another issue. closes: vim/vim#16517 https://github.com/vim/vim/commit/3acfbb4b548f4b1659ff1368a1b626cdd263acbe Co-authored-by: Yee Cheng Chin --- test/old/testdir/test_debugger.vim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/old/testdir/test_debugger.vim b/test/old/testdir/test_debugger.vim index 3a469e8a17..5820b58247 100644 --- a/test/old/testdir/test_debugger.vim +++ b/test/old/testdir/test_debugger.vim @@ -6,10 +6,11 @@ source check.vim func CheckCWD() " Check that the longer lines don't wrap due to the length of the script name - " in cwd + " in cwd. Need to subtract by 1 since Vim will still wrap the message if it + " just fits. let script_len = len( getcwd() .. '/Xtest1.vim' ) let longest_line = len( 'Breakpoint in "" line 1' ) - if script_len > ( 75 - longest_line ) + if script_len > ( 75 - longest_line - 1 ) throw 'Skipped: Your CWD has too many characters' endif endfunc -- cgit From e3e1c1e76581e63ea588a553f9b91f36a61d9d51 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 25 Jan 2025 15:40:47 +0100 Subject: vim-patch:509a8d5: runtime(just): fix typo in syntax file closes: vim/vim#16515 https://github.com/vim/vim/commit/509a8d58f9a8ce00744114c1f21f0d951a559ecd Co-authored-by: Peter Benjamin --- runtime/syntax/just.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/syntax/just.vim b/runtime/syntax/just.vim index 77423a3c91..79c81d0f9c 100644 --- a/runtime/syntax/just.vim +++ b/runtime/syntax/just.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Justfile " Maintainer: Peter Benjamin <@pbnj> -" Last Change: 2025 Jan 19 +" Last Change: 2025 Jan 25 " Credits: The original author, Noah Bogart if exists('b:current_syntax') @@ -402,5 +402,5 @@ hi def link justUserDefinedError Exception hi def link justVariadicPrefix Statement hi def link justVariadicPrefixError Error -let &cpo = s:cpo_sav -unlet s:cpo_sav +let &cpo = s:cpo_save +unlet s:cpo_save -- cgit From 974a3f7a438bb772bd681987b25214784c52c7da Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 25 Jan 2025 15:41:29 +0100 Subject: vim-patch:9918120: runtime(filetype): Improve Verilog detection by checking for modules definition While at it, also increase the maximum number of lines to check to 500. fixes: vim/vim#16513 https://github.com/vim/vim/commit/99181205c5f8284a30f839107a12932924168f17 Co-authored-by: Christian Brabandt --- runtime/lua/vim/filetype/detect.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 855893530e..705ce33872 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1763,7 +1763,7 @@ function M.v(_, bufnr) return vim.g.filetype_v end local in_comment = 0 - for _, line in ipairs(getlines(bufnr, 1, 200)) do + for _, line in ipairs(getlines(bufnr, 1, 500)) do if line:find('^%s*/%*') then in_comment = 1 end @@ -1777,7 +1777,7 @@ function M.v(_, bufnr) or line:find('%(%*') and not line:find('/[/*].*%(%*') then return 'coq' - elseif findany(line, { ';%s*$', ';%s*/[/*]' }) then + elseif findany(line, { ';%s*$', ';%s*/[/*]', '^%s*module' }) then return 'verilog' end end -- cgit From b8e947ed4ed04f9aeef471f579451bbf2bb2993d Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 25 Jan 2025 16:26:13 +0100 Subject: vim-patch:fb49e3c: runtime(filetype): commit 99181205c5f8284a3 breaks V lang detection so make the regex more strict and have it check for a parenthesis. See: https://github.com/vlang/v/blob/master/examples/submodule/mymodules/submodule/sub_functions.v related: vim/vim#16513 https://github.com/vim/vim/commit/fb49e3cde79de4ce558c86d21a56eb9d60aeabd5 Co-authored-by: Christian Brabandt --- runtime/lua/vim/filetype/detect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 705ce33872..8f66418733 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1777,7 +1777,7 @@ function M.v(_, bufnr) or line:find('%(%*') and not line:find('/[/*].*%(%*') then return 'coq' - elseif findany(line, { ';%s*$', ';%s*/[/*]', '^%s*module' }) then + elseif findany(line, { ';%s*$', ';%s*/[/*]', '^%s*module%s+%w+%s*%(' }) then return 'verilog' end end -- cgit From d84a95da7e11555376a0ce60a0d4b5fbe15892d3 Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 27 Jan 2025 07:28:33 +0800 Subject: feat(api): nvim_get_autocmds filter by id#31549 Problem: nvim_get_autocmds cannot filter by id. Solution: Support it. --- runtime/doc/api.txt | 40 +++++++++-------- runtime/lua/vim/_meta/api.lua | 33 +++++++------- runtime/lua/vim/_meta/api_keysets.lua | 1 + src/nvim/api/autocmd.c | 41 +++++++++-------- src/nvim/api/keysets_defs.h | 1 + test/functional/api/autocmd_spec.lua | 83 +++++++++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+), 53 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 1a5df18f6c..07bd7dd192 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3511,33 +3511,35 @@ nvim_get_autocmds({opts}) *nvim_get_autocmds()* Parameters: ~ • {opts} Dict with at least one of the following: - • group (string|integer): the autocommand group name or id to - match against. - • event (string|array): event or events to match against + • buffer: (integer) Buffer number or list of buffer numbers + for buffer local autocommands |autocmd-buflocal|. Cannot be + used with {pattern} + • event: (string|table) event or events to match against |autocmd-events|. - • pattern (string|array): pattern or patterns to match against + • id: (integer) Autocommand ID to match. + • group: (string|table) the autocommand group name or id to + match against. + • pattern: (string|table) pattern or patterns to match against |autocmd-pattern|. Cannot be used with {buffer} - • buffer: Buffer number or list of buffer numbers for buffer - local autocommands |autocmd-buflocal|. Cannot be used with - {pattern} Return: ~ Array of autocommands matching the criteria, with each item containing the following fields: - • id (number): the autocommand id (only when defined with the API). - • group (integer): the autocommand group id. - • group_name (string): the autocommand group name. - • desc (string): the autocommand description. - • event (string): the autocommand event. - • command (string): the autocommand command. Note: this will be empty + • buffer: (integer) the buffer number. + • buflocal: (boolean) true if the autocommand is buffer local. + • command: (string) the autocommand command. Note: this will be empty if a callback is set. - • callback (function|string|nil): Lua function or name of a Vim script - function which is executed when this autocommand is triggered. - • once (boolean): whether the autocommand is only run once. - • pattern (string): the autocommand pattern. If the autocommand is + • callback: (function|string|nil): Lua function or name of a Vim + script function which is executed when this autocommand is + triggered. + • desc: (string) the autocommand description. + • event: (string) the autocommand event. + • id: (integer) the autocommand id (only when defined with the API). + • group: (integer) the autocommand group id. + • group_name: (string) the autocommand group name. + • once: (boolean) whether the autocommand is only run once. + • pattern: (string) the autocommand pattern. If the autocommand is buffer local |autocmd-buffer-local|: - • buflocal (boolean): true if the autocommand is buffer local. - • buffer (number): the buffer number. ============================================================================== diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 50fb7e4f9d..c7d5db60b1 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1246,27 +1246,28 @@ function vim.api.nvim_get_all_options_info() end --- match any combination of them. --- --- @param opts vim.api.keyset.get_autocmds Dict with at least one of the following: ---- - group (string|integer): the autocommand group name or id to match against. ---- - event (string|array): event or events to match against `autocmd-events`. ---- - pattern (string|array): pattern or patterns to match against `autocmd-pattern`. ---- Cannot be used with {buffer} ---- - buffer: Buffer number or list of buffer numbers for buffer local autocommands +--- - buffer: (integer) Buffer number or list of buffer numbers for buffer local autocommands --- `autocmd-buflocal`. Cannot be used with {pattern} +--- - event: (string|table) event or events to match against `autocmd-events`. +--- - id: (integer) Autocommand ID to match. +--- - group: (string|table) the autocommand group name or id to match against. +--- - pattern: (string|table) pattern or patterns to match against `autocmd-pattern`. +--- Cannot be used with {buffer} --- @return vim.api.keyset.get_autocmds.ret[] # Array of autocommands matching the criteria, with each item --- containing the following fields: ---- - id (number): the autocommand id (only when defined with the API). ---- - group (integer): the autocommand group id. ---- - group_name (string): the autocommand group name. ---- - desc (string): the autocommand description. ---- - event (string): the autocommand event. ---- - command (string): the autocommand command. Note: this will be empty if a callback is set. ---- - callback (function|string|nil): Lua function or name of a Vim script function +--- - buffer: (integer) the buffer number. +--- - buflocal: (boolean) true if the autocommand is buffer local. +--- - command: (string) the autocommand command. Note: this will be empty if a callback is set. +--- - callback: (function|string|nil): Lua function or name of a Vim script function --- which is executed when this autocommand is triggered. ---- - once (boolean): whether the autocommand is only run once. ---- - pattern (string): the autocommand pattern. +--- - desc: (string) the autocommand description. +--- - event: (string) the autocommand event. +--- - id: (integer) the autocommand id (only when defined with the API). +--- - group: (integer) the autocommand group id. +--- - group_name: (string) the autocommand group name. +--- - once: (boolean) whether the autocommand is only run once. +--- - pattern: (string) the autocommand pattern. --- If the autocommand is buffer local |autocmd-buffer-local|: ---- - buflocal (boolean): true if the autocommand is buffer local. ---- - buffer (number): the buffer number. function vim.api.nvim_get_autocmds(opts) end --- Gets information about a channel. diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 26c2c963de..4d0665872b 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -117,6 +117,7 @@ error('Cannot require a meta file') --- @field group? integer|string --- @field pattern? string|string[] --- @field buffer? integer|integer[] +--- @field id? integer --- @class vim.api.keyset.get_commands --- @field builtin? boolean diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index ae349fdb95..1e19e90151 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -68,32 +68,31 @@ static int64_t next_autocmd_id = 1; /// match any combination of them. /// /// @param opts Dict with at least one of the following: -/// - group (string|integer): the autocommand group name or id to match against. -/// - event (string|array): event or events to match against |autocmd-events|. -/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|. -/// Cannot be used with {buffer} -/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands +/// - buffer: (integer) Buffer number or list of buffer numbers for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern} +/// - event: (string|table) event or events to match against |autocmd-events|. +/// - id: (integer) Autocommand ID to match. +/// - group: (string|table) the autocommand group name or id to match against. +/// - pattern: (string|table) pattern or patterns to match against |autocmd-pattern|. +/// Cannot be used with {buffer} /// @return Array of autocommands matching the criteria, with each item /// containing the following fields: -/// - id (number): the autocommand id (only when defined with the API). -/// - group (integer): the autocommand group id. -/// - group_name (string): the autocommand group name. -/// - desc (string): the autocommand description. -/// - event (string): the autocommand event. -/// - command (string): the autocommand command. Note: this will be empty if a callback is set. -/// - callback (function|string|nil): Lua function or name of a Vim script function +/// - buffer: (integer) the buffer number. +/// - buflocal: (boolean) true if the autocommand is buffer local. +/// - command: (string) the autocommand command. Note: this will be empty if a callback is set. +/// - callback: (function|string|nil): Lua function or name of a Vim script function /// which is executed when this autocommand is triggered. -/// - once (boolean): whether the autocommand is only run once. -/// - pattern (string): the autocommand pattern. +/// - desc: (string) the autocommand description. +/// - event: (string) the autocommand event. +/// - id: (integer) the autocommand id (only when defined with the API). +/// - group: (integer) the autocommand group id. +/// - group_name: (string) the autocommand group name. +/// - once: (boolean) whether the autocommand is only run once. +/// - pattern: (string) the autocommand pattern. /// If the autocommand is buffer local |autocmd-buffer-local|: -/// - buflocal (boolean): true if the autocommand is buffer local. -/// - buffer (number): the buffer number. Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) FUNC_API_SINCE(9) { - // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) - ArrayBuilder autocmd_list = KV_INITIAL_VALUE; kvi_init(autocmd_list); char *pattern_filters[AUCMD_MAX_PATTERNS]; @@ -127,6 +126,8 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) }); } + int id = (HAS_KEY(opts, get_autocmds, id)) ? (int)opts->id : -1; + if (HAS_KEY(opts, get_autocmds, event)) { check_event = true; @@ -237,6 +238,10 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) continue; } + if (id != -1 && ac->id != id) { + continue; + } + // Skip autocmds from invalid groups if passed. if (group != 0 && ap->group != group) { continue; diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 953e467f1e..6625908cda 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -263,6 +263,7 @@ typedef struct { Union(Integer, String) group; Union(String, ArrayOf(String)) pattern; Union(Integer, ArrayOf(Integer)) buffer; + Integer id; } Dict(get_autocmds); typedef struct { diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index bb6456f45b..64b28e7bf7 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -899,6 +899,89 @@ describe('autocmd api', function() eq([[:echo "Buffer"]], normalized_aus[1].command) end) end) + + describe('id', function() + it('gets events by ID', function() + local id = api.nvim_create_autocmd('BufEnter', { + command = 'echo "hello"', + }) + eq({ + { + buflocal = false, + command = 'echo "hello"', + event = 'BufEnter', + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id })) + end) + + it('gets events by ID by other filters', function() + local group_name = 'NVIM_GET_AUTOCMDS_ID' + local group = api.nvim_create_augroup(group_name, { clear = true }) + local id = api.nvim_create_autocmd('BufEnter', { + command = 'set number', + group = group, + }) + api.nvim_create_autocmd('WinEnter', { + group = group, + command = 'set cot&', + }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'BufEnter', + group = group, + group_name = group_name, + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id, group = group })) + end) + + it('gets events by ID and a specific event', function() + local id = api.nvim_create_autocmd('InsertEnter', { command = 'set number' }) + api.nvim_create_autocmd('InsertEnter', { command = 'set wrap' }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'InsertEnter', + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id, event = 'InsertEnter' })) + end) + + it('gets events by ID and a specific pattern', function() + local id = api.nvim_create_autocmd('InsertEnter', { + pattern = '*.c', + command = 'set number', + }) + api.nvim_create_autocmd('InsertEnter', { + pattern = '*.c', + command = 'set wrap', + }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'InsertEnter', + id = id, + once = false, + pattern = '*.c', + }, + }, api.nvim_get_autocmds({ id = id, pattern = '*.c' })) + end) + + it('empty result when id does not found', function() + eq({}, api.nvim_get_autocmds({ id = 255 })) + end) + end) end) describe('nvim_exec_autocmds', function() -- cgit From 1759b7844a2d468310b6d0ceca899fec6f2d4b84 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 26 Jan 2025 15:33:03 -0800 Subject: feat(diagnostic): virtual_lines #31959 --- runtime/doc/diagnostic.txt | 26 ++- runtime/doc/news.txt | 2 + runtime/lua/vim/diagnostic.lua | 276 ++++++++++++++++++++++++++++++++ src/nvim/highlight_group.c | 5 + test/functional/lua/diagnostic_spec.lua | 109 ++++++++++++- test/functional/ui/cmdline_spec.lua | 2 +- test/functional/ui/messages_spec.lua | 4 +- 7 files changed, 417 insertions(+), 7 deletions(-) diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 6b1456d5a6..80197670ee 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -97,8 +97,8 @@ If a diagnostic handler is configured with a "severity" key then the list of diagnostics passed to that handler will be filtered using the value of that key (see example below). -Nvim provides these handlers by default: "virtual_text", "signs", and -"underline". +Nvim provides these handlers by default: "virtual_text", "virtual_lines", +"signs", and "underline". *diagnostic-handlers-example* The example below creates a new handler that notifies the user of diagnostics @@ -170,6 +170,16 @@ show a sign for the highest severity diagnostic on a given line: >lua } < + *diagnostic-toggle-virtual-lines-example* +Diagnostic handlers can also be toggled. For example, you might want to toggle +the `virtual_lines` handler with the following keymap: >lua + + vim.keymap.set('n', 'gK', function() + local new_config = not vim.diagnostic.config().virtual_lines + vim.diagnostic.config({ virtual_lines = new_config }) + end, { desc = 'Toggle diagnostic virtual_lines' }) +< + *diagnostic-loclist-example* Whenever the |location-list| is opened, the following `show` handler will show the most recent diagnostics: >lua @@ -469,6 +479,8 @@ Lua module: vim.diagnostic *diagnostic-api* diagnostics are set for a namespace, one prefix per diagnostic + the last diagnostic message are shown. + • {virtual_lines}? (`boolean|vim.diagnostic.Opts.VirtualLines|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualLines`, default: `false`) + Use virtual lines for diagnostics. • {signs}? (`boolean|vim.diagnostic.Opts.Signs|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Signs`, default: `true`) Use signs for diagnostics |diagnostic-signs|. • {float}? (`boolean|vim.diagnostic.Opts.Float|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Float`) @@ -590,6 +602,16 @@ Lua module: vim.diagnostic *diagnostic-api* diagnostics matching the given severity |diagnostic-severity|. +*vim.diagnostic.Opts.VirtualLines* + + Fields: ~ + • {current_line}? (`boolean`, default: `false`) Only show diagnostics + for the current line. + • {format}? (`fun(diagnostic:vim.Diagnostic): string`) A function + that takes a diagnostic as input and returns a + string. The return value is the text used to display + the diagnostic. + *vim.diagnostic.Opts.VirtualText* Fields: ~ diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 1d53f168ff..adad08ddf2 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -235,6 +235,8 @@ DIAGNOSTICS • |vim.diagnostic.config()| accepts a "jump" table to specify defaults for |vim.diagnostic.jump()|. +• A "virtual_lines" diagnostic handler was added to render diagnostics using + virtual lines below the respective code. EDITOR diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 04118999cf..2d86fbe38c 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -73,6 +73,10 @@ end --- (default: `false`) --- @field virtual_text? boolean|vim.diagnostic.Opts.VirtualText|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualText --- +--- Use virtual lines for diagnostics. +--- (default: `false`) +--- @field virtual_lines? boolean|vim.diagnostic.Opts.VirtualLines|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualLines +--- --- Use signs for diagnostics |diagnostic-signs|. --- (default: `true`) --- @field signs? boolean|vim.diagnostic.Opts.Signs|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Signs @@ -101,6 +105,7 @@ end --- @field update_in_insert boolean --- @field underline vim.diagnostic.Opts.Underline --- @field virtual_text vim.diagnostic.Opts.VirtualText +--- @field virtual_lines vim.diagnostic.Opts.VirtualLines --- @field signs vim.diagnostic.Opts.Signs --- @field severity_sort {reverse?:boolean} @@ -228,6 +233,16 @@ end --- See |nvim_buf_set_extmark()|. --- @field virt_text_hide? boolean +--- @class vim.diagnostic.Opts.VirtualLines +--- +--- Only show diagnostics for the current line. +--- (default: `false`) +--- @field current_line? boolean +--- +--- A function that takes a diagnostic as input and returns a string. +--- The return value is the text used to display the diagnostic. +--- @field format? fun(diagnostic:vim.Diagnostic): string + --- @class vim.diagnostic.Opts.Signs --- --- Only show virtual text for diagnostics matching the given @@ -313,6 +328,7 @@ local global_diagnostic_options = { signs = true, underline = true, virtual_text = false, + virtual_lines = false, float = true, update_in_insert = false, severity_sort = false, @@ -328,6 +344,7 @@ local global_diagnostic_options = { --- @class (private) vim.diagnostic.Handler --- @field show? fun(namespace: integer, bufnr: integer, diagnostics: vim.Diagnostic[], opts?: vim.diagnostic.OptsResolved) --- @field hide? fun(namespace:integer, bufnr:integer) +--- @field _augroup? integer --- @nodoc --- @type table @@ -581,6 +598,7 @@ end -- TODO(lewis6991): these highlight maps can only be indexed with an integer, however there usage -- implies they can be indexed with any vim.diagnostic.Severity local virtual_text_highlight_map = make_highlight_map('VirtualText') +local virtual_lines_highlight_map = make_highlight_map('VirtualLines') local underline_highlight_map = make_highlight_map('Underline') local floating_highlight_map = make_highlight_map('Floating') local sign_highlight_map = make_highlight_map('Sign') @@ -1603,6 +1621,264 @@ M.handlers.virtual_text = { end, } +--- Some characters (like tabs) take up more than one cell. Additionally, inline +--- virtual text can make the distance between 2 columns larger. +--- A diagnostic aligned under such characters needs to account for that and that +--- many spaces to its left. +--- @param bufnr integer +--- @param lnum integer +--- @param start_col integer +--- @param end_col integer +--- @return integer +local function distance_between_cols(bufnr, lnum, start_col, end_col) + return api.nvim_buf_call(bufnr, function() + local s = vim.fn.virtcol({ lnum + 1, start_col }) + local e = vim.fn.virtcol({ lnum + 1, end_col + 1 }) + return e - 1 - s + end) +end + +--- @param namespace integer +--- @param bufnr integer +--- @param diagnostics vim.Diagnostic[] +local function render_virtual_lines(namespace, bufnr, diagnostics) + table.sort(diagnostics, function(d1, d2) + if d1.lnum == d2.lnum then + return d1.col < d2.col + else + return d1.lnum < d2.lnum + end + end) + + api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) + + if not next(diagnostics) then + return + end + + -- This loop reads each line, putting them into stacks with some extra data since + -- rendering each line requires understanding what is beneath it. + local ElementType = { Space = 1, Diagnostic = 2, Overlap = 3, Blank = 4 } ---@enum ElementType + local line_stacks = {} ---@type table + local prev_lnum = -1 + local prev_col = 0 + for _, diag in ipairs(diagnostics) do + if not line_stacks[diag.lnum] then + line_stacks[diag.lnum] = {} + end + + local stack = line_stacks[diag.lnum] + + if diag.lnum ~= prev_lnum then + table.insert(stack, { + ElementType.Space, + string.rep(' ', distance_between_cols(bufnr, diag.lnum, 0, diag.col)), + }) + elseif diag.col ~= prev_col then + table.insert(stack, { + ElementType.Space, + string.rep( + ' ', + -- +1 because indexing starts at 0 in one API but at 1 in the other. + -- -1 for non-first lines, since the previous column was already drawn. + distance_between_cols(bufnr, diag.lnum, prev_col + 1, diag.col) - 1 + ), + }) + else + table.insert(stack, { ElementType.Overlap, diag.severity }) + end + + if diag.message:find('^%s*$') then + table.insert(stack, { ElementType.Blank, diag }) + else + table.insert(stack, { ElementType.Diagnostic, diag }) + end + + prev_lnum, prev_col = diag.lnum, diag.col + end + + local chars = { + cross = '┼', + horizontal = '─', + horizontal_up = '┴', + up_right = '└', + vertical = '│', + vertical_right = '├', + } + + for lnum, stack in pairs(line_stacks) do + local virt_lines = {} + + -- Note that we read in the order opposite to insertion. + for i = #stack, 1, -1 do + if stack[i][1] == ElementType.Diagnostic then + local diagnostic = stack[i][2] + local left = {} ---@type {[1]:string, [2]:string} + local overlap = false + local multi = false + + -- Iterate the stack for this line to find elements on the left. + for j = 1, i - 1 do + local type = stack[j][1] + local data = stack[j][2] + if type == ElementType.Space then + if multi then + ---@cast data string + table.insert(left, { + string.rep(chars.horizontal, data:len()), + virtual_lines_highlight_map[diagnostic.severity], + }) + else + table.insert(left, { data, '' }) + end + elseif type == ElementType.Diagnostic then + -- If an overlap follows this line, don't add an extra column. + if stack[j + 1][1] ~= ElementType.Overlap then + table.insert(left, { chars.vertical, virtual_lines_highlight_map[data.severity] }) + end + overlap = false + elseif type == ElementType.Blank then + if multi then + table.insert( + left, + { chars.horizontal_up, virtual_lines_highlight_map[data.severity] } + ) + else + table.insert(left, { chars.up_right, virtual_lines_highlight_map[data.severity] }) + end + multi = true + elseif type == ElementType.Overlap then + overlap = true + end + end + + local center_char ---@type string + if overlap and multi then + center_char = chars.cross + elseif overlap then + center_char = chars.vertical_right + elseif multi then + center_char = chars.horizontal_up + else + center_char = chars.up_right + end + local center = { + { + string.format('%s%s', center_char, string.rep(chars.horizontal, 4) .. ' '), + virtual_lines_highlight_map[diagnostic.severity], + }, + } + + -- We can draw on the left side if and only if: + -- a. Is the last one stacked this line. + -- b. Has enough space on the left. + -- c. Is just one line. + -- d. Is not an overlap. + local msg ---@type string + if diagnostic.code then + msg = string.format('%s: %s', diagnostic.code, diagnostic.message) + else + msg = diagnostic.message + end + for msg_line in msg:gmatch('([^\n]+)') do + local vline = {} + vim.list_extend(vline, left) + vim.list_extend(vline, center) + vim.list_extend(vline, { { msg_line, virtual_lines_highlight_map[diagnostic.severity] } }) + + table.insert(virt_lines, vline) + + -- Special-case for continuation lines: + if overlap then + center = { + { chars.vertical, virtual_lines_highlight_map[diagnostic.severity] }, + { ' ', '' }, + } + else + center = { { ' ', '' } } + end + end + end + end + + api.nvim_buf_set_extmark(bufnr, namespace, lnum, 0, { virt_lines = virt_lines }) + end +end + +--- @param diagnostics vim.Diagnostic[] +--- @param namespace integer +--- @param bufnr integer +local function render_virtual_lines_at_current_line(diagnostics, namespace, bufnr) + local line_diagnostics = {} + local lnum = api.nvim_win_get_cursor(0)[1] - 1 + + for _, diag in ipairs(diagnostics) do + if (lnum == diag.lnum) or (diag.end_lnum and lnum >= diag.lnum and lnum <= diag.end_lnum) then + table.insert(line_diagnostics, diag) + end + end + + render_virtual_lines(namespace, bufnr, line_diagnostics) +end + +M.handlers.virtual_lines = { + show = function(namespace, bufnr, diagnostics, opts) + vim.validate('namespace', namespace, 'number') + vim.validate('bufnr', bufnr, 'number') + vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics') + vim.validate('opts', opts, 'table', true) + + bufnr = vim._resolve_bufnr(bufnr) + opts = opts or {} + + if not api.nvim_buf_is_loaded(bufnr) then + return + end + + local ns = M.get_namespace(namespace) + if not ns.user_data.virt_lines_ns then + ns.user_data.virt_lines_ns = + api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_lines', ns.name)) + end + if not M.handlers.virtual_lines._augroup then + M.handlers.virtual_lines._augroup = + api.nvim_create_augroup('nvim.lsp.diagnostic.virt_lines', { clear = true }) + end + + api.nvim_clear_autocmds({ group = M.handlers.virtual_lines._augroup }) + + if opts.virtual_lines.format then + diagnostics = reformat_diagnostics(opts.virtual_lines.format, diagnostics) + end + + if opts.virtual_lines.current_line == true then + api.nvim_create_autocmd('CursorMoved', { + buffer = bufnr, + group = M.handlers.virtual_lines._augroup, + callback = function() + render_virtual_lines_at_current_line(diagnostics, ns.user_data.virt_lines_ns, bufnr) + end, + }) + -- Also show diagnostics for the current line before the first CursorMoved event. + render_virtual_lines_at_current_line(diagnostics, ns.user_data.virt_lines_ns, bufnr) + else + render_virtual_lines(ns.user_data.virt_lines_ns, bufnr, diagnostics) + end + + save_extmarks(ns.user_data.virt_lines_ns, bufnr) + end, + hide = function(namespace, bufnr) + local ns = M.get_namespace(namespace) + if ns.user_data.virt_lines_ns then + diagnostic_cache_extmarks[bufnr][ns.user_data.virt_lines_ns] = {} + if api.nvim_buf_is_valid(bufnr) then + api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_lines_ns, 0, -1) + end + api.nvim_clear_autocmds({ group = M.handlers.virtual_lines._augroup }) + end + end, +} + --- Get virtual text chunks to display using |nvim_buf_set_extmark()|. --- --- Exported for backward compatibility with diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index ad4b2732f6..f3c0d66bb8 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -232,6 +232,11 @@ static const char *highlight_init_both[] = { "default link DiagnosticVirtualTextInfo DiagnosticInfo", "default link DiagnosticVirtualTextHint DiagnosticHint", "default link DiagnosticVirtualTextOk DiagnosticOk", + "default link DiagnosticVirtualLinesError DiagnosticError", + "default link DiagnosticVirtualLinesWarn DiagnosticWarn", + "default link DiagnosticVirtualLinesInfo DiagnosticInfo", + "default link DiagnosticVirtualLinesHint DiagnosticHint", + "default link DiagnosticVirtualLinesOk DiagnosticOk", "default link DiagnosticSignError DiagnosticError", "default link DiagnosticSignWarn DiagnosticWarn", "default link DiagnosticSignInfo DiagnosticInfo", diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 80f4307d5b..08c287735e 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -113,6 +113,18 @@ describe('vim.diagnostic', function() ) end + function _G.get_virt_lines_extmarks(ns) + ns = vim.diagnostic.get_namespace(ns) + local virt_lines_ns = ns.user_data.virt_lines_ns + return vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + virt_lines_ns, + 0, + -1, + { details = true } + ) + end + ---@param ns integer function _G.get_underline_extmarks(ns) ---@type integer @@ -161,6 +173,11 @@ describe('vim.diagnostic', function() 'DiagnosticUnderlineOk', 'DiagnosticUnderlineWarn', 'DiagnosticUnnecessary', + 'DiagnosticVirtualLinesError', + 'DiagnosticVirtualLinesHint', + 'DiagnosticVirtualLinesInfo', + 'DiagnosticVirtualLinesOk', + 'DiagnosticVirtualLinesWarn', 'DiagnosticVirtualTextError', 'DiagnosticVirtualTextHint', 'DiagnosticVirtualTextInfo', @@ -582,7 +599,7 @@ describe('vim.diagnostic', function() vim.diagnostic.set( _G.diagnostic_ns, _G.diagnostic_bufnr, - { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } ) vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) @@ -1017,7 +1034,7 @@ describe('vim.diagnostic', function() vim.diagnostic.set( _G.diagnostic_ns, _G.diagnostic_bufnr, - { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } ) vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) @@ -2119,6 +2136,94 @@ describe('vim.diagnostic', function() end) end) + describe('handlers.virtual_lines', function() + it('includes diagnostic code and message', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Missed symbol `,`', 0, 0, 0, 0, 'lua_ls', 'miss-symbol'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + + eq('miss-symbol: Missed symbol `,`', result[1][3][1]) + end) + + it('adds space to the left of the diagnostic', function() + local error_offset = 5 + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, error_offset, 0, error_offset, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + + eq(error_offset, result[1][1][1]:len()) + end) + + it('highlights diagnostics in multiple lines by default', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(2, #result) + eq('Error here!', result[1][4].virt_lines[1][3][1]) + eq('Another error there!', result[2][4].virt_lines[1][3][1]) + end) + + it('can highlight diagnostics only in the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_lines = { current_line = true } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq('Error here!', result[1][4].virt_lines[1][3][1]) + end) + + it('supports a format function for diagnostic messages', function() + local result = exec_lua(function() + vim.diagnostic.config({ + virtual_lines = { + format = function() + return 'Error here!' + end, + }, + }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Invalid syntax', 0, 0, 0, 0), + }) + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + eq('Error here!', result[1][3][1]) + end) + end) + describe('set()', function() it('validation', function() matches( diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index d6dd62108c..ce7c9596bb 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -858,7 +858,7 @@ local function test_cmdline(linegrid) cmdline = { { content = { { '' } }, - hl_id = 237, + hl_id = 242, pos = 0, prompt = 'Prompt:', }, diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 4038e596fa..c996d117f2 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -254,11 +254,11 @@ describe('ui/ext_messages', function() { content = { { '\n@character ' }, - { 'xxx', 26, 150 }, + { 'xxx', 26, 155 }, { ' ' }, { 'links to', 18, 5 }, { ' Character\n@character.special ' }, - { 'xxx', 16, 151 }, + { 'xxx', 16, 156 }, { ' ' }, { 'links to', 18, 5 }, { ' SpecialChar' }, -- cgit From c7d4a77ff9040c8b242f9d12a98472fe6217328c Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 27 Jan 2025 08:02:33 +0800 Subject: refactor(api): add missing cast #31960 --- src/nvim/api/extmark.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 778e857057..8b31196eef 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -62,7 +62,7 @@ Integer nvim_create_namespace(String name) { handle_T id = map_get(String, int)(&namespace_ids, name); if (id > 0) { - return id; + return (Integer)id; } id = next_namespace_id++; if (name.size > 0) { -- cgit From 21aed725d2ac9ef5c6eb2ab631a6e1c3ad9b25fb Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 26 Jan 2025 18:35:19 +0100 Subject: vim-patch:db23436: runtime(asm): add byte directives to syntax script closes: vim/vim#16523 https://github.com/vim/vim/commit/db23436b92a1b08e91146ef462482f2c1a79dfe8 Co-authored-by: Nir Lichtman --- runtime/syntax/asm.vim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/runtime/syntax/asm.vim b/runtime/syntax/asm.vim index 73f283a4a7..18f3de1c63 100644 --- a/runtime/syntax/asm.vim +++ b/runtime/syntax/asm.vim @@ -3,8 +3,8 @@ " Maintainer: Doug Kearns dougkearns@gmail.com " Previous Maintainers: Erik Wognsen " Kevin Dahlhausen -" Contributors: Ori Avtalion, Lakshay Garg -" Last Change: 2020 Oct 31 +" Contributors: Ori Avtalion, Lakshay Garg, Nir Lichtman +" Last Change: 2025 Jan 26 " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -32,6 +32,9 @@ syn match asmType "\.single" syn match asmType "\.space" syn match asmType "\.string" syn match asmType "\.word" +syn match asmType "\.2byte" +syn match asmType "\.4byte" +syn match asmType "\.8byte" syn match asmIdentifier "[a-z_][a-z0-9_]*" syn match asmLabel "[a-z_][a-z0-9_]*:"he=e-1 -- cgit From b288fa8d62c3f129d333d3ea6abc3234039cad37 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 24 Jan 2025 15:38:10 +0100 Subject: build(bump_deps): abort if archive doesn't exist Also use git tag archive over commit sha if possible. --- scripts/bump_deps.lua | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/scripts/bump_deps.lua b/scripts/bump_deps.lua index 333c7ea8ed..a44e4a2d5e 100755 --- a/scripts/bump_deps.lua +++ b/scripts/bump_deps.lua @@ -3,6 +3,7 @@ -- Usage: -- ./scripts/bump_deps.lua -h +assert(vim.fn.executable('gh') == 1) assert(vim.fn.executable('sed') == 1) local required_branch_prefix = 'bump-' @@ -38,14 +39,14 @@ end -- Executes and returns the output of `cmd`, or nil on failure. -- if die_on_fail is true, process dies with die_msg on failure local function _run(cmd, die_on_fail, die_msg) - local rv = vim.trim(vim.system(cmd, { text = true }):wait().stdout) or '' - if vim.v.shell_error ~= 0 then + local rv = vim.system(cmd):wait() + if rv.code ~= 0 then if die_on_fail then die(die_msg) end return nil end - return rv + return vim.trim(rv.stdout) end -- Run a command, return nil on failure @@ -70,9 +71,8 @@ local function get_archive_info(repo, ref) local archive_path = temp_dir .. '/' .. archive_name local archive_url = 'https://github.com/' .. repo .. '/archive/' .. archive_name - vim.fs.rm(archive_path, { force = true }) run_die( - { 'curl', '-sL', archive_url, '-o', archive_path }, + { 'curl', '-sfL', archive_url, '-o', archive_path }, 'Failed to download archive from GitHub' ) @@ -84,6 +84,23 @@ local function get_archive_info(repo, ref) return { url = archive_url, sha = archive_sha } end +local function get_gh_commit_sha(repo, ref) + local full_repo = string.format('https://github.com/%s.git', repo) + local tag_exists = run_die({ 'git', 'ls-remote', full_repo, 'refs/tags/' .. ref }) ~= '' + -- We'd rather use the git tag over commit sha if possible + if tag_exists then + return ref + end + + local sha = assert( + run_die( + { 'gh', 'api', 'repos/' .. repo .. '/commits/' .. ref, '--jq', '.sha' }, + 'Failed to get commit hash from GitHub. Not a valid ref?' + ) + ) + return sha +end + local function update_deps_file(symbol, kind, value) run_die({ 'sed', @@ -103,13 +120,9 @@ local function ref(name, _ref) deps_file .. ' has uncommitted changes' ) - local full_repo = string.format('https://github.com/%s.git', repo) - -- `git ls-remote` returning empty string means provided ref is a regular commit hash and not a - -- tag nor HEAD. - local sha = vim.split(assert(run_die({ 'git', 'ls-remote', full_repo, _ref })), '\t')[1] - local commit_sha = sha == '' and _ref or sha + _ref = get_gh_commit_sha(repo, _ref) - local archive = get_archive_info(repo, commit_sha) + local archive = get_archive_info(repo, _ref) local comment = string.sub(_ref, 1, 9) local checked_out_branch = assert(run({ 'git', 'rev-parse', '--abbrev-ref', 'HEAD' })) -- cgit From eb60cd74fb5caa997e6253bef6a1f0b58e1b6ec6 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 27 Jan 2025 16:16:06 +0100 Subject: build(deps)!: bump tree-sitter to HEAD, wasmtime to v29.0.1 (#32200) Breaking change: `ts_node_child_containing_descendant()` was removed Breaking change: tree-sitter 0.25 (HEAD) required --- cmake.deps/deps.txt | 8 ++++---- runtime/doc/deprecated.txt | 4 ---- runtime/doc/news.txt | 11 ++++++++--- runtime/lua/vim/treesitter/_meta/tsnode.lua | 6 ------ src/nvim/CMakeLists.txt | 4 ++-- src/nvim/lua/treesitter.c | 16 +++------------- test/functional/treesitter/node_spec.lua | 26 -------------------------- 7 files changed, 17 insertions(+), 58 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index b7a9812676..4f3fcc69d0 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -50,11 +50,11 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.7.tar.gz -TREESITTER_SHA256 7cbc13c974d6abe978cafc9da12d1e79e07e365c42af75e43ec1b5cdc03ed447 +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/9515be4fc16d3dfe6fba5ae4a7a058f32bcf535d.tar.gz +TREESITTER_SHA256 cbdea399736b55d61cfb581bc8d80620d487f4ec8f8d60b7fe00406e39a98d6d -WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.3.tar.gz -WASMTIME_SHA256 17850ca356fce6ea8bcd3847692b3233588ddf32ff31fcccac67ad06bcac0a3a +WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v29.0.1.tar.gz +WASMTIME_SHA256 b94b6c6fd6aebaf05d4c69c1b12b5dc217b0d42c1a95f435b33af63dddfa5304 UNCRUSTIFY_URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.80.1.tar.gz UNCRUSTIFY_SHA256 0e2616ec2f78e12816388c513f7060072ff7942b42f1175eb28b24cb75aaec48 diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index ff9c21fad9..68258fedb4 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -65,10 +65,6 @@ LUA • *vim.highlight* Renamed to |vim.hl|. • vim.validate(opts: table) Use form 1. See |vim.validate()|. -TREESITTER -• *TSNode:child_containing_descendant()* Use |TSNode:child_with_descendant()| - instead; it is identical except that it can return the descendant itself. - VIMSCRIPT • *termopen()* Use |jobstart() with `{term: v:true}`. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index adad08ddf2..1dee72314a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -36,6 +36,12 @@ OPTIONS • 'jumpoptions' flag "unload" has been renamed to "clean". • The `msghistory` option has been removed in favor of 'messagesopt'. +TREESITTER + +• *TSNode:child_containing_descendant()* has been removed in the tree-sitter + library and is no longer available; use |TSNode:child_with_descendant()| + instead. + ============================================================================== BREAKING CHANGES *news-breaking* @@ -366,9 +372,8 @@ TREESITTER • |treesitter-directive-trim!| can trim all whitespace (not just empty lines) from both sides of a node. • |vim.treesitter.get_captures_at_pos()| now returns the `id` of each capture -• New |TSNode:child_with_descendant()|, which is nearly identical to - |TSNode:child_containing_descendant()| except that it can return the - descendant itself. +• New |TSNode:child_with_descendant()|, which efficiently gets the node's + child that contains a given node as descendant. • |LanguageTree:parse()| optionally supports asynchronous invocation, which is activated by passing the `on_parse` callback parameter. diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua index d982b6a505..0c1b376fba 100644 --- a/runtime/lua/vim/treesitter/_meta/tsnode.lua +++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua @@ -68,12 +68,6 @@ function TSNode:named_child_count() end --- @return TSNode? function TSNode:named_child(index) end ---- Get the node's child that contains {descendant}. ---- @param descendant TSNode ---- @return TSNode? ---- @deprecated -function TSNode:child_containing_descendant(descendant) end - --- Get the node's child that contains {descendant} (includes {descendant}). --- --- For example, with the following node hierarchy: diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 5b9946db39..6e27e39f9a 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -31,7 +31,7 @@ target_link_libraries(main_lib INTERFACE ${LUV_LIBRARY}) find_package(Iconv REQUIRED) find_package(Libuv 1.28.0 REQUIRED) find_package(Lpeg REQUIRED) -find_package(Treesitter 0.24.0 REQUIRED) +find_package(Treesitter 0.25.0 REQUIRED) find_package(Unibilium 2.0 REQUIRED) find_package(UTF8proc REQUIRED) @@ -49,7 +49,7 @@ if(ENABLE_LIBINTL) endif() if(ENABLE_WASMTIME) - find_package(Wasmtime 25.0.3 EXACT REQUIRED) + find_package(Wasmtime 29.0.1 EXACT REQUIRED) target_link_libraries(main_lib INTERFACE wasmtime) target_compile_definitions(nvim_bin PRIVATE HAVE_WASMTIME) endif() diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 3493384a8f..c7999ac077 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -218,7 +218,7 @@ static int add_language(lua_State *L, bool is_wasm) ? load_language_from_wasm(L, path, lang_name) : load_language_from_object(L, path, lang_name, symbol_name); - uint32_t lang_version = ts_language_version(lang); + uint32_t lang_version = ts_language_abi_version(lang); if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || lang_version > TREE_SITTER_LANGUAGE_VERSION) { return luaL_error(L, @@ -300,7 +300,7 @@ int tslua_inspect_lang(lua_State *L) lua_pushboolean(L, ts_language_is_wasm(lang)); lua_setfield(L, -2, "_wasm"); - lua_pushinteger(L, ts_language_version(lang)); // [retval, version] + lua_pushinteger(L, ts_language_abi_version(lang)); // [retval, version] lua_setfield(L, -2, "_abi_version"); return 1; @@ -476,7 +476,7 @@ static int parser_parse(lua_State *L) #undef BUFSIZE } - input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 }; + input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8, NULL }; new_tree = ts_parser_parse(p, old_tree, input); break; @@ -837,7 +837,6 @@ static struct luaL_Reg node_meta[] = { { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, { "__has_ancestor", __has_ancestor }, - { "child_containing_descendant", node_child_containing_descendant }, { "child_with_descendant", node_child_with_descendant }, { "iter_children", node_iter_children }, { "next_sibling", node_next_sibling }, @@ -1181,15 +1180,6 @@ static int __has_ancestor(lua_State *L) return 1; } -static int node_child_containing_descendant(lua_State *L) -{ - TSNode node = node_check(L, 1); - TSNode descendant = node_check(L, 2); - TSNode child = ts_node_child_containing_descendant(node, descendant); - push_node(L, child, 1); - return 1; -} - static int node_child_with_descendant(lua_State *L) { TSNode node = node_check(L, 1); diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index 9839022c5e..235bf7861c 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -163,32 +163,6 @@ describe('treesitter node API', function() eq(3, lua_eval('child:byte_length()')) end) - it('child_containing_descendant() works', function() - insert([[ - int main() { - int x = 3; - }]]) - - exec_lua(function() - local tree = vim.treesitter.get_parser(0, 'c'):parse()[1] - _G.root = tree:root() - _G.main = _G.root:child(0) - _G.body = _G.main:child(2) - _G.statement = _G.body:child(1) - _G.declarator = _G.statement:child(1) - _G.value = _G.declarator:child(1) - end) - - eq(lua_eval('main:type()'), lua_eval('root:child_containing_descendant(value):type()')) - eq(lua_eval('body:type()'), lua_eval('main:child_containing_descendant(value):type()')) - eq(lua_eval('statement:type()'), lua_eval('body:child_containing_descendant(value):type()')) - eq( - lua_eval('declarator:type()'), - lua_eval('statement:child_containing_descendant(value):type()') - ) - eq(vim.NIL, lua_eval('declarator:child_containing_descendant(value)')) - end) - it('child_with_descendant() works', function() insert([[ int main() { -- cgit From c1718d68634460117ef5731643669d59587ec628 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 24 Jan 2025 17:17:12 +0100 Subject: ci(release): add linux-arm64 appimage and tarball Problem: No releases for ARM Linux. Solution: Provide appimages and tarballs for `linux-arm64`. Rename x86 releases to `linux-x86_64` for consistency. --- .github/workflows/notes.md | 51 +++++++++++++++++------ .github/workflows/release.yml | 93 ++++++++++++++++++++++++++++++------------ BUILD.md | 2 +- INSTALL.md | 21 +++++----- cmake.packaging/CMakeLists.txt | 2 +- runtime/doc/support.txt | 4 +- scripts/genappimage.sh | 24 +++++------ 7 files changed, 134 insertions(+), 63 deletions(-) diff --git a/.github/workflows/notes.md b/.github/workflows/notes.md index 25f4a5fb32..ed792f8d85 100644 --- a/.github/workflows/notes.md +++ b/.github/workflows/notes.md @@ -34,25 +34,49 @@ Note: On Windows "Server" you may need to [install vcruntime140.dll](https://lea 3. Extract: `tar xzvf nvim-macos-arm64.tar.gz` 4. Run `./nvim-macos-arm64/bin/nvim` -### Linux (x64) +### Linux (x86_64) glibc 2.31 or newer is required. Or you may try the (unsupported) [builds for older glibc](https://github.com/neovim/neovim-releases). #### AppImage -1. Download **nvim.appimage** -2. Run `chmod u+x nvim.appimage && ./nvim.appimage` +1. Download **nvim-linux-x86_64.appimage** +2. Run `chmod u+x nvim-linux-x86_64.appimage && ./nvim-linux-x86_64.appimage` - If your system does not have FUSE you can [extract the appimage](https://github.com/AppImage/AppImageKit/wiki/FUSE#type-2-appimage): ``` - ./nvim.appimage --appimage-extract + ./nvim-linux-x86_64.appimage --appimage-extract ./squashfs-root/usr/bin/nvim ``` +> [!NOTE] +> This appimage is also published as `nvim.appimage` for backward compatibility, but scripts should be updated to use the new name. + #### Tarball -1. Download **nvim-linux64.tar.gz** -2. Extract: `tar xzvf nvim-linux64.tar.gz` -3. Run `./nvim-linux64/bin/nvim` +1. Download **nvim-linux-x86_64.tar.gz** +2. Extract: `tar xzvf nvim-linux-x86_64.tar.gz` +3. Run `./nvim-linux-x86_64/bin/nvim` + +> [!NOTE] +> This tarball is also published as `nvim-linux64.tar.gz` for backward compatibility, but scripts should be updated to use the new name. + +### Linux (arm64) + +#### AppImage + +1. Download **nvim-linux-arm64.appimage** +2. Run `chmod u+x nvim-linux-arm64.appimage && ./nvim-linux-arm64.appimage` + - If your system does not have FUSE you can [extract the appimage](https://github.com/AppImage/AppImageKit/wiki/FUSE#type-2-appimage): + ``` + ./nvim-linux-arm64.appimage --appimage-extract + ./squashfs-root/usr/bin/nvim + ``` + +#### Tarball + +1. Download **nvim-linux-arm64.tar.gz** +2. Extract: `tar xzvf nvim-linux-arm64.tar.gz` +3. Run `./nvim-linux-arm64/bin/nvim` ### Other @@ -61,11 +85,14 @@ glibc 2.31 or newer is required. Or you may try the (unsupported) [builds for ol ## SHA256 Checksums ``` -${SHA_LINUX_64_TAR} -${SHA_APP_IMAGE} -${SHA_APP_IMAGE_ZSYNC} -${SHA_MACOS_X86_64} +${SHA_APPIMAGE_ARM64} +${SHA_APPIMAGE_ARM64_ZSYNC} +${SHA_LINUX_ARM64_TAR} +${SHA_APPIMAGE_X86_64} +${SHA_APPIMAGE_X86_64_ZSYNC} +${SHA_LINUX_X86_64_TAR} ${SHA_MACOS_ARM64} -${SHA_WIN_64_ZIP} +${SHA_MACOS_X86_64} ${SHA_WIN_64_MSI} +${SHA_WIN_64_ZIP} ``` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de90a077ff..2062d58103 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,10 +39,21 @@ jobs: printf "appimage_tag=${APPIMAGE_TAG}\n" >> $GITHUB_OUTPUT linux: - runs-on: ubuntu-20.04 needs: setup + strategy: + fail-fast: false + matrix: + runner: [ ubuntu-20.04, ubuntu-24.04-arm ] + include: + - runner: ubuntu-20.04 + arch: x86_64 + cc: gcc-10 + - runner: ubuntu-24.04-arm + arch: aarch64 + runs-on: ${{ matrix.runner }} env: - CC: gcc-10 + CC: ${{ matrix.cc }} + LDAI_NO_APPSTREAM: 1 # skip checking (broken) AppStream metadata for issues outputs: version: ${{ steps.build.outputs.version }} steps: @@ -52,22 +63,25 @@ jobs: fetch-depth: 0 - run: ./.github/scripts/install_deps.sh - run: echo "CMAKE_BUILD_TYPE=${{ needs.setup.outputs.build_type }}" >> $GITHUB_ENV + - if: matrix.arch == 'aarch64' + run: sudo apt-get update && sudo apt-get install -y libfuse2t64 - name: appimage - run: ./scripts/genappimage.sh ${{ needs.setup.outputs.appimage_tag }} + run: | + ./scripts/genappimage.sh ${{ needs.setup.outputs.appimage_tag }} - name: tar.gz run: cpack --config build/CPackConfig.cmake -G TGZ - uses: actions/upload-artifact@v4 with: - name: appimage + name: appimage-${{ matrix.arch }} path: | - build/bin/nvim.appimage - build/bin/nvim.appimage.zsync + build/bin/nvim-linux-${{ matrix.arch }}.appimage + build/bin/nvim-linux-${{ matrix.arch }}.appimage.zsync retention-days: 1 - uses: actions/upload-artifact@v4 with: - name: nvim-linux64 + name: nvim-linux-${{ matrix.arch }} path: | - build/nvim-linux64.tar.gz + build/nvim-linux-${{ matrix.arch }}.tar.gz retention-days: 1 - name: Export version id: build @@ -75,7 +89,6 @@ jobs: printf 'version<> $GITHUB_OUTPUT ./build/bin/nvim --version | head -n 3 >> $GITHUB_OUTPUT printf 'END\n' >> $GITHUB_OUTPUT - macos: needs: setup strategy: @@ -104,7 +117,6 @@ jobs: -D CMAKE_BUILD_TYPE=${{ needs.setup.outputs.build_type }} \ -D CMAKE_FIND_FRAMEWORK=NEVER cmake --build .deps - - name: Build neovim run: | cmake -B build -G Ninja \ @@ -112,7 +124,6 @@ jobs: -D ENABLE_LIBINTL=OFF \ -D CMAKE_FIND_FRAMEWORK=NEVER cmake --build build - - name: Package run: cpack --config build/CPackConfig.cmake @@ -185,23 +196,45 @@ jobs: echo 'PRERELEASE=') >> $GITHUB_ENV gh release delete stable --yes || true git push origin :stable || true + - name: Rename aarch64 artifacts + run: | + cd ./nvim-linux-aarch64 + mv nvim-linux-aarch64.tar.gz nvim-linux-arm64.tar.gz + cd ../appimage-aarch64 + mv nvim-linux-aarch64.appimage nvim-linux-arm64.appimage + mv nvim-linux-aarch64.appimage.zsync nvim-linux-arm64.appimage.zsync # `sha256sum` outputs , so we cd into each dir to drop the # containing folder from the output. - - name: Generate Linux64 SHA256 checksums + - name: Generate Linux x86_64 SHA256 checksums + run: | + cd ./nvim-linux-x86_64 + sha256sum nvim-linux-x86_64.tar.gz > nvim-linux-x86_64.tar.gz.sha256sum + echo "SHA_LINUX_X86_64_TAR=$(cat nvim-linux-x86_64.tar.gz.sha256sum)" >> $GITHUB_ENV + - name: Generate Linux arm64 SHA256 checksums run: | - cd ./nvim-linux64 - sha256sum nvim-linux64.tar.gz > nvim-linux64.tar.gz.sha256sum - echo "SHA_LINUX_64_TAR=$(cat nvim-linux64.tar.gz.sha256sum)" >> $GITHUB_ENV - - name: Generate App Image SHA256 checksums + cd ./nvim-linux-aarch64 + sha256sum nvim-linux-arm64.tar.gz > nvim-linux-arm64.tar.gz.sha256sum + echo "SHA_LINUX_ARM64_TAR=$(cat nvim-linux-arm64.tar.gz.sha256sum)" >> $GITHUB_ENV + - name: Generate AppImage x64_64 SHA256 checksums run: | - cd ./appimage - sha256sum nvim.appimage > nvim.appimage.sha256sum - echo "SHA_APP_IMAGE=$(cat nvim.appimage.sha256sum)" >> $GITHUB_ENV - - name: Generate App Image Zsync SHA256 checksums + cd ./appimage-x86_64 + sha256sum nvim-linux-x86_64.appimage > nvim-linux-x86_64.appimage.sha256sum + echo "SHA_APPIMAGE_X86_64=$(cat nvim-linux-x86_64.appimage.sha256sum)" >> $GITHUB_ENV + - name: Generate AppImage x86_64 Zsync SHA256 checksums run: | - cd ./appimage - sha256sum nvim.appimage.zsync > nvim.appimage.zsync.sha256sum - echo "SHA_APP_IMAGE_ZSYNC=$(cat nvim.appimage.zsync.sha256sum)" >> $GITHUB_ENV + cd ./appimage-x86_64 + sha256sum nvim-linux-x86_64.appimage.zsync > nvim-linux-x86_64.appimage.zsync.sha256sum + echo "SHA_APPIMAGE_X86_64_ZSYNC=$(cat nvim-linux-x86_64.appimage.zsync.sha256sum)" >> $GITHUB_ENV + - name: Generate AppImage x64_64 SHA256 checksums + run: | + cd ./appimage-aarch64 + sha256sum nvim-linux-arm64.appimage > nvim-linux-arm64.appimage.sha256sum + echo "SHA_APPIMAGE_ARM64=$(cat nvim-linux-arm64.appimage.sha256sum)" >> $GITHUB_ENV + - name: Generate AppImage arm64 Zsync SHA256 checksums + run: | + cd ./appimage-aarch64 + sha256sum nvim-linux-arm64.appimage.zsync > nvim-linux-arm64.appimage.zsync.sha256sum + echo "SHA_APPIMAGE_ARM64_ZSYNC=$(cat nvim-linux-arm64.appimage.zsync.sha256sum)" >> $GITHUB_ENV - name: Generate macos x86_64 SHA256 checksums run: | cd ./nvim-macos-x86_64 @@ -219,6 +252,16 @@ jobs: echo "SHA_WIN_64_ZIP=$(cat nvim-win64.zip.sha256sum)" >> $GITHUB_ENV sha256sum nvim-win64.msi > nvim-win64.msi.sha256sum echo "SHA_WIN_64_MSI=$(cat nvim-win64.msi.sha256sum)" >> $GITHUB_ENV + - name: Create linux64 aliases # For backward compatibility; remove for 0.12 + run: | + cd ./nvim-linux-x86_64 + cp nvim-linux-x86_64.tar.gz nvim-linux64.tar.gz + cp nvim-linux-x86_64.tar.gz.sha256sum nvim-linux64.tar.gz.sha256sum + cd ../appimage-x86_64 + cp nvim-linux-x86_64.appimage nvim.appimage + cp nvim-linux-x86_64.appimage.sha256sum nvim.appimage.sha256sum + cp nvim-linux-x86_64.appimage.zsync nvim.appimage.zsync + cp nvim-linux-x86_64.appimage.zsync.sha256sum nvim.appimage.zsync.sha256sum - name: Publish release env: NVIM_VERSION: ${{ needs.linux.outputs.version }} @@ -226,6 +269,6 @@ jobs: run: | envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md" if [ "$TAG_NAME" != "nightly" ]; then - gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux64/* appimage/* nvim-win64/* + gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-aarch64/* appimage-x86_64/* appimage-aarch64/* nvim-win64/* fi - gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux64/* appimage/* nvim-win64/* + gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-aarch64/* appimage-x86_64/* appimage-aarch64/* nvim-win64/* diff --git a/BUILD.md b/BUILD.md index 7ded17138a..cfa26a4569 100644 --- a/BUILD.md +++ b/BUILD.md @@ -13,7 +13,7 @@ - To build on Windows, see the [Building on Windows](#building-on-windows) section. _MSVC (Visual Studio) is recommended._ 5. `sudo make install` - Default install location is `/usr/local` - - On Debian/Ubuntu, instead of `sudo make install`, you can try `cd build && cpack -G DEB && sudo dpkg -i nvim-linux64.deb` to build DEB-package and install it. This helps ensure clean removal of installed files. Note: This is an unsupported, "best-effort" feature of the Nvim build. + - On Debian/Ubuntu, instead of `sudo make install`, you can try `cd build && cpack -G DEB && sudo dpkg -i nvim-linux-.deb` (with `` either `x86_64` or `arm64`) to build DEB-package and install it. This helps ensure clean removal of installed files. Note: This is an unsupported, "best-effort" feature of the Nvim build. **Notes**: - From the repository's root directory, running `make` will download and build all the needed dependencies and put the `nvim` executable in `build/bin`. diff --git a/INSTALL.md b/INSTALL.md index 807922e2e3..0701bf2720 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,9 +15,10 @@ Install from download Downloads are available on the [Releases](https://github.com/neovim/neovim/releases) page. * Latest [stable release](https://github.com/neovim/neovim/releases/latest) - * [macOS x86](https://github.com/neovim/neovim/releases/latest/download/nvim-macos-x86_64.tar.gz) - * [macOS arm](https://github.com/neovim/neovim/releases/latest/download/nvim-macos-arm64.tar.gz) - * [Linux](https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz) + * [macOS x86_64](https://github.com/neovim/neovim/releases/latest/download/nvim-macos-x86_64.tar.gz) + * [macOS arm64](https://github.com/neovim/neovim/releases/latest/download/nvim-macos-arm64.tar.gz) + * [Linux x86_64](https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz) + * [Linux arm64](https://github.com/neovim/neovim/releases/latest/download/nvim-linux-arm64.tar.gz) * [Windows](https://github.com/neovim/neovim/releases/latest/download/nvim-win64.msi) * Latest [development prerelease](https://github.com/neovim/neovim/releases/nightly) @@ -118,24 +119,24 @@ Then add this to your shell config (`~/.bashrc`, `~/.zshrc`, ...): ### AppImage ("universal" Linux package) -The [Releases](https://github.com/neovim/neovim/releases) page provides an [AppImage](https://appimage.org) that runs on most Linux systems. No installation is needed, just download `nvim.appimage` and run it. (It might not work if your Linux distribution is more than 4 years old.) +The [Releases](https://github.com/neovim/neovim/releases) page provides an [AppImage](https://appimage.org) that runs on most Linux systems. No installation is needed, just download `nvim-linux-x86_64.appimage` and run it. (It might not work if your Linux distribution is more than 4 years old.) The following instructions assume an `x86_64` architecture; on ARM Linux replace with `arm64`. - curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage - chmod u+x nvim.appimage - ./nvim.appimage + curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-86_64.appimage + chmod u+x nvim-linux-x86_64.appimage + ./nvim-linux-x86_64.appimage To expose nvim globally: mkdir -p /opt/nvim - mv nvim.appimage /opt/nvim/nvim + mv nvim-linux-x86_64.appimage /opt/nvim/nvim And the following line to your shell config (`~/.bashrc`, `~/.zshrc`, ...): export PATH="$PATH:/opt/nvim/" -If the `./nvim.appimage` command fails, try: +If the `./nvim-linux-x86_64.appimage` command fails, try: ```sh -./nvim.appimage --appimage-extract +./nvim-linux-x86_64.appimage --appimage-extract ./squashfs-root/AppRun --version # Optional: exposing nvim globally. diff --git a/cmake.packaging/CMakeLists.txt b/cmake.packaging/CMakeLists.txt index 8c158c39dc..54b6285954 100644 --- a/cmake.packaging/CMakeLists.txt +++ b/cmake.packaging/CMakeLists.txt @@ -53,7 +53,7 @@ elseif(APPLE) set(CPACK_GENERATOR TGZ) set(CPACK_PACKAGE_ICON ${CMAKE_CURRENT_LIST_DIR}/neovim.icns) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(CPACK_PACKAGE_FILE_NAME "nvim-linux64") + set(CPACK_PACKAGE_FILE_NAME "nvim-linux-${CMAKE_SYSTEM_PROCESSOR}") set(CPACK_GENERATOR TGZ DEB) set(CPACK_DEBIAN_PACKAGE_NAME "Neovim") # required set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Neovim.io") # required diff --git a/runtime/doc/support.txt b/runtime/doc/support.txt index 088677e813..103fb90b6d 100644 --- a/runtime/doc/support.txt +++ b/runtime/doc/support.txt @@ -14,8 +14,8 @@ Supported platforms *supported-platforms* `System` `Tier` `Versions` `Tested versions` Linux (x86_64) 1 >= 2.6.32, glibc >= 2.12 Ubuntu 24.04 Linux (arm64) 1 >= 2.6.32, glibc >= 2.12 Ubuntu 24.04 -macOS (Intel) 1 >= 11 macOS 13 -macOS (M1) 1 >= 11 macOS 15 +macOS (x86_64) 1 >= 11 macOS 13 +macOS (arm64) 1 >= 11 macOS 15 Windows 64-bit 1 >= Windows 10 Version 1809 Windows Server 2022 FreeBSD 1 >= 10 FreeBSD 14 OpenBSD 2 >= 7 diff --git a/scripts/genappimage.sh b/scripts/genappimage.sh index e8aac42a9c..63cda6d6ec 100755 --- a/scripts/genappimage.sh +++ b/scripts/genappimage.sh @@ -40,16 +40,16 @@ export VERSION cd "$APP_BUILD_DIR" || exit # Only downloads linuxdeploy if the remote file is different from local -if [ -e "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage ]; then - curl -Lo "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage \ - -z "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage \ - https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +if [ -e "$APP_BUILD_DIR"/linuxdeploy-"$ARCH".AppImage ]; then + curl -Lo "$APP_BUILD_DIR"/linuxdeploy-"$ARCH".AppImage \ + -z "$APP_BUILD_DIR"/linuxdeploy-"$ARCH".AppImage \ + https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-"$ARCH".AppImage else - curl -Lo "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage \ - https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + curl -Lo "$APP_BUILD_DIR"/linuxdeploy-"$ARCH".AppImage \ + https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-"$ARCH".AppImage fi -chmod +x "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage +chmod +x "$APP_BUILD_DIR"/linuxdeploy-"$ARCH".AppImage # metainfo is not packaged automatically by linuxdeploy mkdir -p "$APP_DIR/usr/share/metainfo/" @@ -76,23 +76,23 @@ chmod 755 AppRun cd "$APP_BUILD_DIR" || exit # Get out of AppImage directory. # Set the name of the file generated by appimage -export OUTPUT=nvim.appimage +export OUTPUT=nvim-linux-"$ARCH".appimage # If it's a release generate the zsync file if [ -n "$TAG" ]; then - export UPDATE_INFORMATION="gh-releases-zsync|neovim|neovim|$TAG|nvim.appimage.zsync" + export UPDATE_INFORMATION="gh-releases-zsync|neovim|neovim|$TAG|nvim-linux-$ARCH.appimage.zsync" fi # Generate AppImage. # - Expects: $ARCH, $APP, $VERSION env vars # - Expects: ./$APP.AppDir/ directory -# - Produces: ./nvim.appimage -./linuxdeploy-x86_64.AppImage --appdir $APP.AppDir -d "$ROOT_DIR"/runtime/nvim.desktop -i \ +# - Produces: ./nvim-linux-$ARCH.appimage +./linuxdeploy-"$ARCH".AppImage --appdir $APP.AppDir -d "$ROOT_DIR"/runtime/nvim.desktop -i \ "$ROOT_DIR/runtime/nvim.png" --output appimage # Moving the final executable to a different folder so it isn't in the # way for a subsequent build. -mv "$ROOT_DIR"/build/nvim.appimage* "$ROOT_DIR"/build/bin +mv "$ROOT_DIR"/build/nvim-linux-"$ARCH".appimage* "$ROOT_DIR"/build/bin echo 'genappimage.sh: finished' -- cgit From be01b361d8ff0e8bf8f93cdf4e4f1055ecd341c2 Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 27 Jan 2025 23:52:27 +0800 Subject: fix(float): cannot set title/footer independently #31993 Problem: `nvim_win_set_config` cannot set the title and footer independently. When only one is given, the other is reset to the default of "left". Solution: Reuse existing title/footer value if not provided. --- src/nvim/api/win_config.c | 10 ++- test/functional/ui/float_spec.lua | 169 +++++++++++++++++++++++++++++++++----- 2 files changed, 155 insertions(+), 24 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 225189a3f9..1132452faf 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -895,7 +895,7 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, *is_present = true; } -static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type, +static bool parse_bordertext_pos(win_T *wp, String bordertext_pos, BorderTextType bordertext_type, WinConfig *fconfig, Error *err) { AlignTextPos *align; @@ -909,7 +909,9 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex } if (bordertext_pos.size == 0) { - *align = kAlignLeft; + if (!wp) { + *align = kAlignLeft; + } return true; } @@ -1250,7 +1252,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco } // handles unset 'title_pos' same as empty string - if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) { + if (!parse_bordertext_pos(wp, config->title_pos, kBorderTextTitle, fconfig, err)) { goto fail; } } else { @@ -1277,7 +1279,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco } // handles unset 'footer_pos' same as empty string - if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) { + if (!parse_bordertext_pos(wp, config->footer_pos, kBorderTextFooter, fconfig, err)) { goto fail; } } else { diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 27ab0feb9c..15231e0f8c 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -2333,6 +2333,61 @@ describe('float window', function() | ]]} end + + -- reuse before title pos + api.nvim_win_set_config(win, {title= 'new'}) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:new}{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 2, 5, true, 50}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{5:╔══════}{11:new}{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]) + end end) it('border with footer', function() @@ -2478,6 +2533,61 @@ describe('float window', function() | ]]} end + + -- reuse before footer pos + api.nvim_win_set_config(win, { footer = 'new' }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚══════}{11:new}{5:╝}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 2, 5, true, 50}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚══════}{11:new}{5:╝}{0: }| + | + ]]) + end end) it('border with title and footer', function() @@ -8710,38 +8820,58 @@ describe('float window', function() ## grid 3 | ## grid 4 - {5:╔}{11:win_100}{5:═════════════╗}| + {5:╔══════}{11:win_100}{5:═══════╗}| {5:║}{7: }{5:║}| {5:║}{7:~ }{5:║}|*2 {5:╚════════════════════╝}| ## grid 6 - {5:┌}{11:win_150}{5:─────────────┐}| + {5:┌──────}{11:win_150}{5:───────┐}| {5:│}{8: }{5:│}| {5:│}{8:~ }{5:│}|*2 {5:└────────────────────┘}| - ]], float_pos={ + ]], + float_pos = { [4] = {1001, "NW", 1, 1, 5, true, 100}; [6] = {1003, "NW", 1, 3, 7, true, 150}; - }, win_viewport={ + }, + win_viewport = { [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; - }, win_viewport_margins={ - [2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 }, - [4] = { bottom = 1, left = 1, right = 1, top = 1, win = 1001 }, - [6] = { bottom = 1, left = 1, right = 1, top = 1, win = 1003 } - }}) - else - screen:expect({ - grid = [[ - ^ | - {0:~ }{5:╔}{11:w}{5:┌}{11:win_150}{5:─────────────┐}{0: }| - {0:~ }{5:║}{7: }{5:│}{8: }{5:│}{0: }| - {0:~ }{5:║}{7:~}{5:│}{8:~ }{5:│}{0: }|*2 - {0:~ }{5:╚═└────────────────────┘}{0: }| - | - ]] + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + }, + [6] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1003 + } + }, }) + else + screen:expect([[ + ^ | + {0:~ }{5:╔═┌──────}{11:win_150}{5:───────┐}{0: }| + {0:~ }{5:║}{7: }{5:│}{8: }{5:│}{0: }| + {0:~ }{5:║}{7:~}{5:│}{8:~ }{5:│}{0: }|*2 + {0:~ }{5:╚═└────────────────────┘}{0: }| + | + ]]) end end) @@ -9724,4 +9854,3 @@ describe('float window', function() with_ext_multigrid(false) end) end) - -- cgit From 83479b95abae84b4b2b4a0331503298ddc5ff47b Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 27 Jan 2025 16:57:23 +0100 Subject: fix(mpack): remove invalid bool definition This causes build failures with gcc 15. Fixes #31723 --- src/mpack/conv.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mpack/conv.h b/src/mpack/conv.h index 71f14a067e..e46a2b16d7 100644 --- a/src/mpack/conv.h +++ b/src/mpack/conv.h @@ -14,10 +14,6 @@ typedef mpack_sint32_t mpack_sintmax_t; typedef mpack_uint32_t mpack_uintmax_t; #endif -#ifndef bool -# define bool unsigned -#endif - MPACK_API mpack_token_t mpack_pack_nil(void) FUNUSED FPURE; MPACK_API mpack_token_t mpack_pack_boolean(unsigned v) FUNUSED FPURE; MPACK_API mpack_token_t mpack_pack_uint(mpack_uintmax_t v) FUNUSED FPURE; -- cgit From 6aa42e8f92bd8bea49b7b2accfe4ab67a5344e41 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Jan 2025 13:01:25 +0000 Subject: fix: resolve all remaining LuaLS diagnostics --- .luarc.json | 3 ++- runtime/doc/treesitter.txt | 10 ++++++-- runtime/lua/coxpcall.lua | 15 ++++++----- runtime/lua/man.lua | 1 + runtime/lua/tohtml.lua | 6 +++-- runtime/lua/vim/_editor.lua | 12 ++++++--- runtime/lua/vim/_inspector.lua | 3 +++ runtime/lua/vim/_meta/api.lua | 4 +++ runtime/lua/vim/_options.lua | 5 ++++ runtime/lua/vim/diagnostic.lua | 13 +++++++--- runtime/lua/vim/filetype.lua | 4 +-- runtime/lua/vim/filetype/detect.lua | 8 +++--- runtime/lua/vim/fs.lua | 4 +-- runtime/lua/vim/glob.lua | 1 + runtime/lua/vim/health.lua | 7 +++--- runtime/lua/vim/health/health.lua | 13 +++++++--- runtime/lua/vim/inspect.lua | 1 + runtime/lua/vim/lsp/_changetracking.lua | 2 +- runtime/lua/vim/lsp/_meta.lua | 4 +-- runtime/lua/vim/lsp/_snippet_grammar.lua | 1 + runtime/lua/vim/lsp/buf.lua | 37 +++++++++++++++++----------- runtime/lua/vim/lsp/client.lua | 12 +++++---- runtime/lua/vim/lsp/completion.lua | 2 +- runtime/lua/vim/lsp/diagnostic.lua | 3 ++- runtime/lua/vim/lsp/handlers.lua | 2 +- runtime/lua/vim/lsp/protocol.lua | 7 ------ runtime/lua/vim/lsp/semantic_tokens.lua | 2 +- runtime/lua/vim/lsp/util.lua | 6 ++++- runtime/lua/vim/provider/health.lua | 34 ++++++++++++++----------- runtime/lua/vim/re.lua | 1 + runtime/lua/vim/shared.lua | 3 +-- runtime/lua/vim/treesitter.lua | 9 ++++--- runtime/lua/vim/treesitter/_meta/misc.lua | 8 +++++- runtime/lua/vim/treesitter/_meta/tsnode.lua | 1 + runtime/lua/vim/treesitter/_query_linter.lua | 4 ++- runtime/lua/vim/treesitter/dev.lua | 10 +------- runtime/lua/vim/treesitter/language.lua | 2 +- runtime/lua/vim/treesitter/languagetree.lua | 10 +++++--- runtime/lua/vim/treesitter/query.lua | 2 ++ runtime/lua/vim/uri.lua | 5 ++-- scripts/gen_eval_files.lua | 4 +++ 41 files changed, 177 insertions(+), 104 deletions(-) diff --git a/.luarc.json b/.luarc.json index 2bd57d6973..1a3cd5b378 100644 --- a/.luarc.json +++ b/.luarc.json @@ -10,7 +10,8 @@ "${3rd}/luv/library" ], "ignoreDir": [ - "test" + "test", + "_vim9script.lua" ], "checkThirdParty": "Disable" }, diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 3d76c8c0ff..61d90e53c8 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -834,6 +834,12 @@ TSNode:range({include_bytes}) *TSNode:range()* Parameters: ~ • {include_bytes} (`boolean?`) + Return (multiple): ~ + (`integer`) + (`integer`) + (`integer`) + (`integer`) + TSNode:sexpr() *TSNode:sexpr()* Get an S-expression representing the node as a string. @@ -942,7 +948,7 @@ get_node_range({node_or_range}) *vim.treesitter.get_node_range()* Returns the node's range or an unpacked range table Parameters: ~ - • {node_or_range} (`TSNode|table`) Node or table of positions + • {node_or_range} (`TSNode|Range4`) Node or table of positions Return (multiple): ~ (`integer`) start_row @@ -1181,7 +1187,7 @@ inspect({lang}) *vim.treesitter.language.inspect()* • {lang} (`string`) Language Return: ~ - (`table`) + (`TSLangInfo`) register({lang}, {filetype}) *vim.treesitter.language.register()* Register a parser named {lang} to be used for {filetype}(s). diff --git a/runtime/lua/coxpcall.lua b/runtime/lua/coxpcall.lua index 75e7a43567..43e321eac3 100644 --- a/runtime/lua/coxpcall.lua +++ b/runtime/lua/coxpcall.lua @@ -31,22 +31,23 @@ end -- No need to do anything if pcall and xpcall are already safe. if isCoroutineSafe(pcall) and isCoroutineSafe(xpcall) then - copcall = pcall - coxpcall = xpcall + _G.copcall = pcall + _G.coxpcall = xpcall return { pcall = pcall, xpcall = xpcall, running = coroutine.running } end ------------------------------------------------------------------------------- -- Implements xpcall with coroutines ------------------------------------------------------------------------------- -local performResume, handleReturnValue +local performResume local oldpcall, oldxpcall = pcall, xpcall local pack = table.pack or function(...) return {n = select("#", ...), ...} end local unpack = table.unpack or unpack local running = coroutine.running +--- @type table local coromap = setmetatable({}, { __mode = "k" }) -function handleReturnValue(err, co, status, ...) +local function handleReturnValue(err, co, status, ...) if not status then return false, err(debug.traceback(co, (...)), ...) end @@ -61,11 +62,12 @@ function performResume(err, co, ...) return handleReturnValue(err, co, coroutine.resume(co, ...)) end +--- @diagnostic disable-next-line: unused-vararg local function id(trace, ...) return trace end -function coxpcall(f, err, ...) +function _G.coxpcall(f, err, ...) local current = running() if not current then if err == id then @@ -88,6 +90,7 @@ function coxpcall(f, err, ...) end end +--- @param coro? thread local function corunning(coro) if coro ~= nil then assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro)) @@ -105,7 +108,7 @@ end -- Implements pcall with coroutines ------------------------------------------------------------------------------- -function copcall(f, ...) +function _G.copcall(f, ...) return coxpcall(f, id, ...) end diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index e322a1f680..7485f60978 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -201,6 +201,7 @@ local function highlight_man_page() api.nvim_buf_set_lines(0, 0, -1, false, lines) for _, hl in ipairs(hls) do + --- @diagnostic disable-next-line: deprecated api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final) end diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 1402dfe494..4415a8cdca 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -317,7 +317,7 @@ end --- @return nil|integer local function register_hl(state, hl) if type(hl) == 'table' then - hl = hl[#hl] + hl = hl[#hl] --- @type string|integer end if type(hl) == 'nil' then return @@ -1162,7 +1162,9 @@ local function extend_pre(out, state) s = s .. _pre_text_to_html(state, row) end local true_line_len = #line + 1 - for k in pairs(style_line) do + for k in + pairs(style_line --[[@as table]]) + do if type(k) == 'number' and k > true_line_len then true_line_len = k end diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 4b28b63746..a77ea9bb91 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -425,6 +425,7 @@ local VIM_CMD_ARG_MAX = 20 --- vim.cmd.colorscheme('blue') --- ``` --- +---@diagnostic disable-next-line: undefined-doc-param ---@param command string|table Command(s) to execute. --- If a string, executes multiple lines of Vimscript at once. In this --- case, it is an alias to |nvim_exec2()|, where `opts.output` is set @@ -441,10 +442,12 @@ vim.cmd = setmetatable({}, { return '' end end, + --- @param t table __index = function(t, command) t[command] = function(...) - local opts + local opts --- @type vim.api.keyset.cmd if select('#', ...) == 1 and type(select(1, ...)) == 'table' then + --- @type vim.api.keyset.cmd opts = select(1, ...) -- Move indexed positions in opts to opt.args @@ -455,6 +458,7 @@ vim.cmd = setmetatable({}, { break end opts.args[i] = opts[i] + --- @diagnostic disable-next-line: no-unknown opts[i] = nil end end @@ -529,7 +533,7 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) end if pos1[1] > pos2[1] or (pos1[1] == pos2[1] and pos1[2] > pos2[2]) then - pos1, pos2 = pos2, pos1 + pos1, pos2 = pos2, pos1 --- @type [integer, integer], [integer, integer] end -- getpos() may return {0,0,0,0} @@ -701,6 +705,7 @@ function vim._on_key(buf, typed_buf) local discard = false for k, v in pairs(on_key_cbs) do local fn = v[1] + --- @type boolean, any local ok, rv = xpcall(function() return fn(buf, typed_buf) end, debug.traceback) @@ -828,6 +833,7 @@ function vim.str_utfindex(s, encoding, index, strict_indexing) -- Return (multiple): ~ -- (`integer`) UTF-32 index -- (`integer`) UTF-16 index + --- @diagnostic disable-next-line: redundant-return-value return col32, col16 end @@ -1000,7 +1006,7 @@ function vim._expand_pat(pat, env) or vim.v == final_env and { 'v:', 'var' } or { nil, nil } ) - assert(prefix, "Can't resolve final_env") + assert(prefix and type, "Can't resolve final_env") local vars = vim.fn.getcompletion(prefix .. match_part, type) --- @type string[] insert_keys(vim .iter(vars) diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index b0eb1d663b..35063dffca 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -1,3 +1,5 @@ +--- @diagnostic disable:no-unknown + --- @class vim._inspector.Filter --- @inlinedoc --- @@ -78,6 +80,7 @@ function vim.inspect_pos(bufnr, row, col, filter) -- treesitter if filter.treesitter then for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do + --- @diagnostic disable-next-line: inject-field capture.hl_group = '@' .. capture.capture .. '.' .. capture.lang results.treesitter[#results.treesitter + 1] = resolve_hl(capture) end diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index c7d5db60b1..3d10729d23 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -3,6 +3,10 @@ -- DO NOT EDIT error('Cannot require a meta file') +--- This file embeds vimdoc as the function descriptions +--- so ignore any doc related errors. +--- @diagnostic disable: undefined-doc-name,luadoc-miss-symbol + vim.api = {} --- @private diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 8338c5ead7..973ad87ee8 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -688,6 +688,7 @@ local function remove_value(info, current, new) end local function create_option_accessor(scope) + --- @diagnostic disable-next-line: no-unknown local option_mt local function make_option(name, value) @@ -696,6 +697,7 @@ local function create_option_accessor(scope) if type(value) == 'table' and getmetatable(value) == option_mt then assert(name == value._name, "must be the same value, otherwise that's weird.") + --- @diagnostic disable-next-line: no-unknown value = value._value end @@ -719,6 +721,7 @@ local function create_option_accessor(scope) end, append = function(self, right) + --- @diagnostic disable-next-line: no-unknown self._value = add_value(self._info, self._value, right) self:_set() end, @@ -728,6 +731,7 @@ local function create_option_accessor(scope) end, prepend = function(self, right) + --- @diagnostic disable-next-line: no-unknown self._value = prepend_value(self._info, self._value, right) self:_set() end, @@ -737,6 +741,7 @@ local function create_option_accessor(scope) end, remove = function(self, right) + --- @diagnostic disable-next-line: no-unknown self._value = remove_value(self._info, self._value, right) self:_set() end, diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 2d86fbe38c..2538cd3048 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -891,6 +891,7 @@ local function set_list(loclist, opts) if open then if not loclist then -- First navigate to the diagnostics quickfix list. + --- @type integer local nr = vim.fn.getqflist({ id = qf_id, nr = 0 }).nr api.nvim_command(('silent %dchistory'):format(nr)) @@ -2250,18 +2251,24 @@ function M.open_float(opts, ...) if not opts.focus_id then opts.focus_id = scope end + + --- @diagnostic disable-next-line: param-type-mismatch local float_bufnr, winnr = vim.lsp.util.open_floating_preview(lines, 'plaintext', opts) vim.bo[float_bufnr].path = vim.bo[bufnr].path + + --- @diagnostic disable-next-line: deprecated + local add_highlight = api.nvim_buf_add_highlight + for i, hl in ipairs(highlights) do local line = lines[i] local prefix_len = hl.prefix and hl.prefix.length or 0 local suffix_len = hl.suffix and hl.suffix.length or 0 if prefix_len > 0 then - api.nvim_buf_add_highlight(float_bufnr, -1, hl.prefix.hlname, i - 1, 0, prefix_len) + add_highlight(float_bufnr, -1, hl.prefix.hlname, i - 1, 0, prefix_len) end - api.nvim_buf_add_highlight(float_bufnr, -1, hl.hlname, i - 1, prefix_len, #line - suffix_len) + add_highlight(float_bufnr, -1, hl.hlname, i - 1, prefix_len, #line - suffix_len) if suffix_len > 0 then - api.nvim_buf_add_highlight(float_bufnr, -1, hl.suffix.hlname, i - 1, #line - suffix_len, -1) + add_highlight(float_bufnr, -1, hl.suffix.hlname, i - 1, #line - suffix_len, -1) end end diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index a8f3d18bfa..cc7358ee49 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -732,8 +732,8 @@ local extension = { mc = detect.mc, quake = 'm3quake', m4 = function(path, bufnr) - path = path:lower() - return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4' or nil + local pathl = path:lower() + return not (pathl:find('html%.m4$') or pathl:find('fvwm2rc')) and 'm4' or nil end, eml = 'mail', mk = detect.make, diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 8f66418733..91c0406dc0 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -881,7 +881,7 @@ end --- (refactor of filetype.vim since the patterns are case-insensitive) --- @type vim.filetype.mapfn function M.log(path, _) - path = path:lower() + path = path:lower() --- @type string LuaLS bug if findany( path, @@ -1167,7 +1167,7 @@ end --- @type vim.filetype.mapfn function M.perl(path, bufnr) local dir_name = vim.fs.dirname(path) - if fn.expand(path, '%:e') == 't' and (dir_name == 't' or dir_name == 'xt') then + if fn.fnamemodify(path, '%:e') == 't' and (dir_name == 't' or dir_name == 'xt') then return 'perl' end local first_line = getline(bufnr, 1) @@ -1375,7 +1375,7 @@ end local udev_rules_pattern = '^%s*udev_rules%s*=%s*"([%^"]+)/*".*' --- @type vim.filetype.mapfn function M.rules(path) - path = path:lower() + path = path:lower() --- @type string LuaLS bug if findany(path, { '/etc/udev/.*%.rules$', @@ -1398,7 +1398,7 @@ function M.rules(path) if not ok then return 'hog' end - local dir = fn.expand(path, ':h') + local dir = fn.fnamemodify(path, ':h') for _, line in ipairs(config_lines) do local match = line:match(udev_rules_pattern) if match then diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 5940fa4386..8b4242223a 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -546,7 +546,7 @@ local function expand_home(path, sep) home = home:sub(1, -2) end - path = home .. path:sub(2) + path = home .. path:sub(2) --- @type string end return path @@ -620,7 +620,7 @@ function M.normalize(path, opts) -- Expand environment variables if `opts.expand_env` isn't `false` if opts.expand_env == nil or opts.expand_env then - path = path:gsub('%$([%w_]+)', uv.os_getenv) + path = path:gsub('%$([%w_]+)', uv.os_getenv) --- @type string end if win then diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua index 4f86d5e1ca..242c70d4b2 100644 --- a/runtime/lua/vim/glob.lua +++ b/runtime/lua/vim/glob.lua @@ -53,6 +53,7 @@ function M.to_lpeg(pattern) end -- luacheck: pop + --- @diagnostic disable-next-line: missing-fields local p = P({ 'Pattern', Pattern = V('Elem') ^ -1 * V('End'), diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 3268c82613..a265e2b901 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -126,7 +126,7 @@ local function filepath_to_healthcheck(path) -- */health/init.lua name = vim.fs.dirname(vim.fs.dirname(subpath)) end - name = name:gsub('/', '.') + name = assert(name:gsub('/', '.')) --- @type string func = 'require("' .. name .. '.health").check()' filetype = 'l' @@ -235,7 +235,7 @@ local function format_report_message(status, msg, ...) -- Report each suggestion for _, v in ipairs(varargs) do if v then - output = output .. '\n - ' .. indent_after_line1(v, 6) + output = output .. '\n - ' .. indent_after_line1(v, 6) --- @type string end end end @@ -445,8 +445,7 @@ function M._check(mods, plugin_names) -- Quit with 'q' inside healthcheck buffers. vim.keymap.set('n', 'q', function() - local ok, _ = pcall(vim.cmd.close) - if not ok then + if not pcall(vim.cmd.close) then vim.cmd.bdelete() end end, { buffer = bufnr, silent = true, noremap = true, nowait = true }) diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua index d226f35f9a..dd6fe7f608 100644 --- a/runtime/lua/vim/health/health.lua +++ b/runtime/lua/vim/health/health.lua @@ -183,13 +183,16 @@ end local function check_rplugin_manifest() health.start('Remote Plugins') - local existing_rplugins = {} - for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do + local existing_rplugins = {} --- @type table + --- @type {path:string}[] + local items = vim.fn['remote#host#PluginsForHost']('python3') + for _, item in ipairs(items) do existing_rplugins[item.path] = 'python3' end local require_update = false local handle_path = function(path) + --- @type string[] local python_glob = vim.fn.glob(path .. '/rplugin/python*', true, true) if vim.tbl_isempty(python_glob) then return @@ -198,6 +201,7 @@ local function check_rplugin_manifest() local python_dir = python_glob[1] local python_version = vim.fs.basename(python_dir) + --- @type string[] local scripts = vim.fn.glob(python_dir .. '/*.py', true, true) vim.list_extend(scripts, vim.fn.glob(python_dir .. '/*/__init__.py', true, true)) @@ -227,7 +231,10 @@ local function check_rplugin_manifest() end end - for _, path in ipairs(vim.fn.map(vim.split(vim.o.runtimepath, ','), 'resolve(v:val)')) do + --- @type string[] + local paths = vim.fn.map(vim.split(vim.o.runtimepath, ','), 'resolve(v:val)') + + for _, path in ipairs(paths) do handle_path(path) end diff --git a/runtime/lua/vim/inspect.lua b/runtime/lua/vim/inspect.lua index c232f69590..cdf34897d4 100644 --- a/runtime/lua/vim/inspect.lua +++ b/runtime/lua/vim/inspect.lua @@ -1,3 +1,4 @@ +--- @diagnostic disable: no-unknown local inspect = { _VERSION = 'inspect.lua 3.1.0', _URL = 'http://github.com/kikito/inspect.lua', diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua index c2ff66b90e..265a74c8fa 100644 --- a/runtime/lua/vim/lsp/_changetracking.lua +++ b/runtime/lua/vim/lsp/_changetracking.lua @@ -64,7 +64,7 @@ local state_by_group = setmetatable({}, { ---@param client vim.lsp.Client ---@return vim.lsp.CTGroup local function get_group(client) - local allow_inc_sync = vim.F.if_nil(client.flags.allow_incremental_sync, true) --- @type boolean + local allow_inc_sync = vim.F.if_nil(client.flags.allow_incremental_sync, true) local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') local sync_kind = change_capability or protocol.TextDocumentSyncKind.None if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then diff --git a/runtime/lua/vim/lsp/_meta.lua b/runtime/lua/vim/lsp/_meta.lua index bf693ccc57..589a49c003 100644 --- a/runtime/lua/vim/lsp/_meta.lua +++ b/runtime/lua/vim/lsp/_meta.lua @@ -1,8 +1,8 @@ ---@meta error('Cannot require a meta file') ----@alias lsp.Handler fun(err: lsp.ResponseError?, result: any, context: lsp.HandlerContext): ...any ----@alias lsp.MultiHandler fun(results: table, context: lsp.HandlerContext): ...any +---@alias lsp.Handler fun(err: lsp.ResponseError?, result: any, context: lsp.HandlerContext, config?: table): ...any +---@alias lsp.MultiHandler fun(results: table, context: lsp.HandlerContext, config?: table): ...any ---@class lsp.HandlerContext ---@field method string diff --git a/runtime/lua/vim/lsp/_snippet_grammar.lua b/runtime/lua/vim/lsp/_snippet_grammar.lua index 9318fefcbc..f06d6e9afd 100644 --- a/runtime/lua/vim/lsp/_snippet_grammar.lua +++ b/runtime/lua/vim/lsp/_snippet_grammar.lua @@ -127,6 +127,7 @@ local function node(type) end -- stylua: ignore +--- @diagnostic disable-next-line: missing-fields local G = P({ 'snippet'; snippet = Ct(Cg( diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index c57fdbee18..638a0d0f3f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -450,10 +450,10 @@ local function range_from_selection(bufnr, mode) -- A user can start visual selection at the end and move backwards -- Normalize the range to start < end if start_row == end_row and end_col < start_col then - end_col, start_col = start_col, end_col + end_col, start_col = start_col, end_col --- @type integer, integer elseif end_row < start_row then - start_row, end_row = end_row, start_row - start_col, end_col = end_col, start_col + start_row, end_row = end_row, start_row --- @type integer, integer + start_col, end_col = end_col, start_col --- @type integer, integer end if mode == 'V' then start_col = 1 @@ -553,25 +553,30 @@ function M.format(opts) --- @param client vim.lsp.Client --- @param params lsp.DocumentFormattingParams - --- @return lsp.DocumentFormattingParams + --- @return lsp.DocumentFormattingParams|lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams local function set_range(client, params) - local to_lsp_range = function(r) ---@return lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams + --- @param r {start:[integer,integer],end:[integer, integer]} + local function to_lsp_range(r) return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range end + local ret = params --[[@as lsp.DocumentFormattingParams|lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams]] if passed_multiple_ranges then - params.ranges = vim.tbl_map(to_lsp_range, range) + ret = params --[[@as lsp.DocumentRangesFormattingParams]] + --- @cast range {start:[integer,integer],end:[integer, integer]} + ret.ranges = vim.tbl_map(to_lsp_range, range) elseif range then - params.range = to_lsp_range(range) + ret = params --[[@as lsp.DocumentRangeFormattingParams]] + ret.range = to_lsp_range(range) end - return params + return ret end if opts.async then - --- @param idx integer - --- @param client vim.lsp.Client + --- @param idx? integer + --- @param client? vim.lsp.Client local function do_format(idx, client) - if not client then + if not idx or not client then return end local params = set_range(client, util.make_formatting_params(opts.formatting_options)) @@ -650,16 +655,16 @@ function M.rename(new_name, opts) )[1] end - --- @param idx integer + --- @param idx? integer --- @param client? vim.lsp.Client local function try_use_client(idx, client) - if not client then + if not idx or not client then return end --- @param name string local function rename(name) - local params = util.make_position_params(win, client.offset_encoding) + local params = util.make_position_params(win, client.offset_encoding) --[[@as lsp.RenameParams]] params.newName = name local handler = client.handlers[ms.textDocument_rename] or lsp.handlers[ms.textDocument_rename] @@ -1229,6 +1234,7 @@ function M.code_action(opts) for _, client in ipairs(clients) do ---@type lsp.CodeActionParams local params + if opts.range then assert(type(opts.range) == 'table', 'code_action range must be a table') local start = assert(opts.range.start, 'range must have a `start` property') @@ -1241,6 +1247,9 @@ function M.code_action(opts) else params = util.make_range_params(win, client.offset_encoding) end + + --- @cast params lsp.CodeActionParams + if context.diagnostics then params.context = context else diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index a082613bb0..253ccc48f4 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -904,18 +904,20 @@ end function Client:_get_registration(method, bufnr) bufnr = vim._resolve_bufnr(bufnr) for _, reg in ipairs(self.registrations[method] or {}) do - if not reg.registerOptions or not reg.registerOptions.documentSelector then + local regoptions = reg.registerOptions --[[@as {documentSelector:lsp.TextDocumentFilter[]}]] + if not regoptions or not regoptions.documentSelector then return reg end - local documentSelector = reg.registerOptions.documentSelector + local documentSelector = regoptions.documentSelector local language = self:_get_language_id(bufnr) local uri = vim.uri_from_bufnr(bufnr) local fname = vim.uri_to_fname(uri) for _, filter in ipairs(documentSelector) do + local flang, fscheme, fpat = filter.language, filter.scheme, filter.pattern if - not (filter.language and language ~= filter.language) - and not (filter.scheme and not vim.startswith(uri, filter.scheme .. ':')) - and not (filter.pattern and not vim.glob.to_lpeg(filter.pattern):match(fname)) + not (flang and language ~= flang) + and not (fscheme and not vim.startswith(uri, fscheme .. ':')) + and not (type(fpat) == 'string' and not vim.glob.to_lpeg(fpat):match(fname)) then return reg end diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index bdf31d8514..cf6d07745f 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -470,7 +470,7 @@ local function trigger(bufnr, clients) local server_start_boundary --- @type integer? for client_id, response in pairs(responses) do if response.err then - vim.notify_once(response.err.message, vim.log.levels.warn) + vim.notify_once(response.err.message, vim.log.levels.WARN) end local result = response.result diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index cf39338cc1..fe24928a69 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -20,7 +20,7 @@ end ---@return lsp.DiagnosticSeverity local function severity_vim_to_lsp(severity) if type(severity) == 'string' then - severity = vim.diagnostic.severity[severity] + severity = vim.diagnostic.severity[severity] --- @type integer end return severity end @@ -89,6 +89,7 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) string.format('Unsupported Markup message from LSP client %d', client_id), vim.lsp.log_levels.ERROR ) + --- @diagnostic disable-next-line: undefined-field,no-unknown message = diagnostic.message.value end local line = buf_lines and buf_lines[start.line + 1] or '' diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index a86ea99413..b35140dfad 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -47,7 +47,7 @@ RSC[ms.dollar_progress] = function(_, params, ctx) local value = params.value if type(value) == 'table' then - kind = value.kind + kind = value.kind --- @type string -- Carry over title of `begin` messages to `report` and `end` messages -- So that consumers always have it available, even if they consume a -- subset of the full sequence diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index cfd47d8f7c..fbfd0cd6b0 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -15,7 +15,6 @@ local sysname = vim.uv.os_uname().sysname --- @class vim.lsp.protocol.constants --- @nodoc local constants = { - --- @enum lsp.DiagnosticSeverity DiagnosticSeverity = { -- Reports an error. Error = 1, @@ -27,7 +26,6 @@ local constants = { Hint = 4, }, - --- @enum lsp.DiagnosticTag DiagnosticTag = { -- Unused or unnecessary code Unnecessary = 1, @@ -35,7 +33,6 @@ local constants = { Deprecated = 2, }, - ---@enum lsp.MessageType MessageType = { -- An error message. Error = 1, @@ -50,7 +47,6 @@ local constants = { }, -- The file event type. - ---@enum lsp.FileChangeType FileChangeType = { -- The file got created. Created = 1, @@ -149,7 +145,6 @@ local constants = { }, -- Represents reasons why a text document is saved. - ---@enum lsp.TextDocumentSaveReason TextDocumentSaveReason = { -- Manually triggered, e.g. by the user pressing save, by starting debugging, -- or by an API call. @@ -246,7 +241,6 @@ local constants = { -- Defines whether the insert text in a completion item should be interpreted as -- plain text or a snippet. - --- @enum lsp.InsertTextFormat InsertTextFormat = { -- The primary text to be inserted is treated as a plain string. PlainText = 1, @@ -305,7 +299,6 @@ local constants = { SourceOrganizeImports = 'source.organizeImports', }, -- The reason why code actions were requested. - ---@enum lsp.CodeActionTriggerKind CodeActionTriggerKind = { -- Code actions were explicitly requested by the user or by an extension. Invoked = 1, diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index a31202553b..dd8b654856 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -139,7 +139,7 @@ local function tokens_to_ranges(data, bufnr, client, request) if token_type then local modifiers = modifiers_from_number(data[i + 4], token_modifiers) - local end_char = start_char + data[i + 2] + local end_char = start_char + data[i + 2] --- @type integer LuaLS bug local buf_line = lines and lines[line + 1] or '' local start_col = vim.str_byteindex(buf_line, encoding, start_char, false) local end_col = vim.str_byteindex(buf_line, encoding, end_char, false) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index b9b53d36a8..e16a905c44 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -49,7 +49,8 @@ local function get_border_size(opts) if not border_size[border] then border_error(border) end - return unpack(border_size[border]) + local r = border_size[border] + return r[1], r[2] end if 8 % #border ~= 0 then @@ -1897,6 +1898,7 @@ function M.make_position_params(window, position_encoding) 'position_encoding param is required in vim.lsp.util.make_position_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) + --- @diagnostic disable-next-line: deprecated position_encoding = M._get_offset_encoding(buf) end return { @@ -1953,6 +1955,7 @@ function M.make_range_params(window, position_encoding) 'position_encoding param is required in vim.lsp.util.make_range_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) + --- @diagnostic disable-next-line: deprecated position_encoding = M._get_offset_encoding(buf) end local position = make_position_param(window, position_encoding) @@ -1982,6 +1985,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding) 'position_encoding param is required in vim.lsp.util.make_given_range_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) + --- @diagnostic disable-next-line: deprecated position_encoding = M._get_offset_encoding(bufnr) end --- @type [integer, integer] diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index 5ecb00f49b..fa01951b02 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -10,22 +10,20 @@ end -- Attempts to construct a shell command from an args list. -- Only for display, to help users debug a failed command. +--- @param cmd string|string[] local function shellify(cmd) if type(cmd) ~= 'table' then return cmd end - local escaped = {} + local escaped = {} --- @type string[] for i, v in ipairs(cmd) do - if v:match('[^A-Za-z_/.-]') then - escaped[i] = vim.fn.shellescape(v) - else - escaped[i] = v - end + escaped[i] = v:match('[^A-Za-z_/.-]') and vim.fn.shellescape(v) or v end return table.concat(escaped, ' ') end -- Handler for s:system() function. +--- @param self {output: string, stderr: string, add_stderr_to_output: boolean} local function system_handler(self, _, data, event) if event == 'stderr' then if self.add_stderr_to_output then @@ -38,7 +36,7 @@ local function system_handler(self, _, data, event) end end ---- @param cmd table List of command arguments to execute +--- @param cmd string|string[] List of command arguments to execute --- @param args? table Optional arguments: --- - stdin (string): Data to write to the job's stdin --- - stderr (boolean): Append stderr to stdout @@ -47,8 +45,8 @@ end local function system(cmd, args) args = args or {} local stdin = args.stdin or '' - local stderr = vim.F.if_nil(args.stderr, false) - local ignore_error = vim.F.if_nil(args.ignore_error, false) + local stderr = args.stderr or false + local ignore_error = args.ignore_error or false local shell_error_code = 0 local opts = { @@ -530,13 +528,14 @@ local function version_info(python) if rc ~= 0 or nvim_version == '' then nvim_version = 'unable to find pynvim module version' local base = vim.fs.basename(nvim_path) - local metas = vim.fn.glob(base .. '-*/METADATA', true, 1) - vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', true, 1)) - vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', true, 1)) + local metas = vim.fn.glob(base .. '-*/METADATA', true, true) + vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', true, true)) + vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', true, true)) metas = table.sort(metas, compare) if metas and next(metas) ~= nil then for line in io.lines(metas[1]) do + --- @cast line string local version = line:match('^Version: (%S+)') if version then nvim_version = version @@ -762,6 +761,7 @@ local function python() -- subshells launched from Nvim. local bin_dir = iswin and 'Scripts' or 'bin' local venv_bins = vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true) + --- @param v string venv_bins = vim.tbl_filter(function(v) -- XXX: Remove irrelevant executables found in bin/. return not v:match('python.*%-config') @@ -809,6 +809,7 @@ local function python() msg, bin_dir, table.concat( + --- @param v string vim.tbl_map(function(v) return vim.fs.basename(v) end, venv_bins), @@ -817,12 +818,15 @@ local function python() ) end local conj = '\nBut ' + local msgs = {} --- @type string[] for _, err in ipairs(errors) do - msg = msg .. conj .. err + msgs[#msgs + 1] = msg + msgs[#msgs + 1] = conj + msgs[#msgs + 1] = err conj = '\nAnd ' end - msg = msg .. '\nSo invoking Python may lead to unexpected results.' - health.warn(msg, vim.tbl_keys(hints)) + msgs[#msgs + 1] = '\nSo invoking Python may lead to unexpected results.' + health.warn(table.concat(msgs), vim.tbl_keys(hints)) else health.info(msg) health.info( diff --git a/runtime/lua/vim/re.lua b/runtime/lua/vim/re.lua index 114f74eb80..e0a36703e3 100644 --- a/runtime/lua/vim/re.lua +++ b/runtime/lua/vim/re.lua @@ -1,3 +1,4 @@ +--- @diagnostic disable: no-unknown -- -- Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' for license) -- written by Roberto Ierusalimschy diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 02b12490af..f19533f474 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -7,8 +7,7 @@ -- so this wouldn't be a separate case to consider) ---@nodoc ----@diagnostic disable-next-line: lowercase-global -vim = vim or {} +_G.vim = _G.vim or {} ---@generic T ---@param orig T diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 0269699dfd..10638e10d8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -149,7 +149,7 @@ end --- Returns the node's range or an unpacked range table --- ----@param node_or_range (TSNode | table) Node or table of positions +---@param node_or_range TSNode|Range4 Node or table of positions --- ---@return integer start_row ---@return integer start_col @@ -157,7 +157,8 @@ end ---@return integer end_col function M.get_node_range(node_or_range) if type(node_or_range) == 'table' then - return unpack(node_or_range) + --- @cast node_or_range -TSNode LuaLS bug + return M._range.unpack4(node_or_range) else return node_or_range:range(false) end @@ -238,7 +239,9 @@ function M.node_contains(node, range) -- allow a table so nodes can be mocked vim.validate('node', node, { 'userdata', 'table' }) vim.validate('range', range, M._range.validate, 'integer list with 4 or 6 elements') - return M._range.contains({ node:range() }, range) + --- @diagnostic disable-next-line: missing-fields LuaLS bug + local nrange = { node:range() } --- @type Range4 + return M._range.contains(nrange, range) end --- Returns a list of highlight captures at the given position diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua index 33701ef254..c532257f49 100644 --- a/runtime/lua/vim/treesitter/_meta/misc.lua +++ b/runtime/lua/vim/treesitter/_meta/misc.lua @@ -20,9 +20,15 @@ error('Cannot require a meta file') ---@class (exact) TSQueryInfo ---@field captures string[] ---@field patterns table +--- +---@class TSLangInfo +---@field fields string[] +---@field symbols table +---@field _wasm boolean +---@field _abi_version integer --- @param lang string ---- @return table +--- @return TSLangInfo vim._ts_inspect_language = function(lang) end ---@return integer diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua index 0c1b376fba..b261b87253 100644 --- a/runtime/lua/vim/treesitter/_meta/tsnode.lua +++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua @@ -104,6 +104,7 @@ function TSNode:end_() end --- - end column --- - end byte (if {include_bytes} is `true`) --- @param include_bytes boolean? +--- @return integer, integer, integer, integer function TSNode:range(include_bytes) end --- @nodoc diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index f6645beb28..3dfc6b0cfe 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -138,7 +138,9 @@ local function lint_match(buf, match, query, lang_context, diagnostics) -- perform language-independent checks only for first lang if lang_context.is_first_lang and cap_id == 'error' then local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ') - add_lint_for_node(diagnostics, { node:range() }, 'Syntax error: ' .. node_text) + ---@diagnostic disable-next-line: missing-fields LuaLS varargs bug + local range = { node:range() } --- @type Range4 + add_lint_for_node(diagnostics, range, 'Syntax error: ' .. node_text) end -- other checks rely on Neovim parser introspection diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 42c25dbdad..ab08e1a527 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -137,14 +137,6 @@ end local decor_ns = api.nvim_create_namespace('nvim.treesitter.dev') ----@param range Range4 ----@return string -local function range_to_string(range) - ---@type integer, integer, integer, integer - local row, col, end_row, end_col = unpack(range) - return string.format('[%d, %d] - [%d, %d]', row, col, end_row, end_col) -end - ---@param w integer ---@return boolean closed Whether the window was closed. local function close_win(w) @@ -227,7 +219,7 @@ function TSTreeView:draw(bufnr) local lang_hl_marks = {} ---@type table[] for i, item in self:iter() do - local range_str = range_to_string({ item.node:range() }) + local range_str = ('[%d, %d] - [%d, %d]'):format(item.node:range()) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' local text ---@type string diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 238a078703..16d19bfc5a 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -175,7 +175,7 @@ end --- (`"`). --- ---@param lang string Language ----@return table +---@return TSLangInfo function M.inspect(lang) M.add(lang) return vim._ts_inspect_language(lang) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 3db7fe5c9e..ecace67419 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -123,7 +123,7 @@ function LanguageTree.new(source, lang, opts) local injections = opts.injections or {} - --- @type vim.treesitter.LanguageTree + --- @class vim.treesitter.LanguageTree local self = { _source = source, _lang = lang, @@ -190,7 +190,7 @@ end ---Measure execution time of a function ---@generic R1, R2, R3 ----@param f fun(): R1, R2, R2 +---@param f fun(): R1, R2, R3 ---@return number, R1, R2, R3 local function tcall(f, ...) local start = vim.uv.hrtime() @@ -198,6 +198,7 @@ local function tcall(f, ...) local r = { f(...) } --- @type number local duration = (vim.uv.hrtime() - start) / 1000000 + --- @diagnostic disable-next-line: redundant-return-value return duration, unpack(r) end @@ -550,14 +551,14 @@ function LanguageTree:_parse(range, timeout) local no_regions_parsed = 0 local query_time = 0 local total_parse_time = 0 - local is_finished --- @type boolean -- At least 1 region is invalid if not self:is_valid(true) then + local is_finished changes, no_regions_parsed, total_parse_time, is_finished = self:_parse_regions(range, timeout) timeout = timeout and math.max(timeout - total_parse_time, 0) if not is_finished then - return self._trees, is_finished + return self._trees, false end -- Need to run injections when we parsed something if no_regions_parsed > 0 then @@ -740,6 +741,7 @@ function LanguageTree:set_included_regions(new_regions) if type(range) == 'table' and #range == 4 then region[i] = Range.add_bytes(self._source, range --[[@as Range4]]) elseif type(range) == 'userdata' then + --- @diagnostic disable-next-line: missing-fields LuaLS varargs bug region[i] = { range:range(true) } end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e43d0a8ad4..8055270a7f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -262,6 +262,7 @@ local explicit_queries = setmetatable({}, { ---@param query_name string Name of the query (e.g., "highlights") ---@param text string Query text (unparsed). function M.set(lang, query_name, text) + --- @diagnostic disable-next-line: undefined-field LuaLS bad at generics M.get:clear(lang, query_name) explicit_queries[lang][query_name] = M.parse(lang, text) end @@ -291,6 +292,7 @@ api.nvim_create_autocmd('OptionSet', { pattern = { 'runtimepath' }, group = api.nvim_create_augroup('nvim.treesitter.query_cache_reset', { clear = true }), callback = function() + --- @diagnostic disable-next-line: undefined-field LuaLS bad at generics M.get:clear() end, }) diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 6323f61256..8b6f1a61ee 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -60,9 +60,10 @@ end ---@param path string Path to file ---@return string URI function M.uri_from_fname(path) - local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') ---@type string? + local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') ---@type string?, string? local is_windows = volume_path ~= nil if is_windows then + assert(fname) path = volume_path .. M.uri_encode(fname:gsub('\\', '/')) else path = M.uri_encode(path) @@ -111,7 +112,7 @@ function M.uri_to_fname(uri) uri = M.uri_decode(uri) --TODO improve this. if is_windows_file_uri(uri) then - uri = uri:gsub('^file:/+', ''):gsub('/', '\\') + uri = uri:gsub('^file:/+', ''):gsub('/', '\\') --- @type string else uri = uri:gsub('^file:/+', '/') ---@type string end diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 58d3eeeadc..aaf76a0411 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -72,6 +72,10 @@ local LUA_API_META_HEADER = { '-- DO NOT EDIT', "error('Cannot require a meta file')", '', + '--- This file embeds vimdoc as the function descriptions', + '--- so ignore any doc related errors.', + '--- @diagnostic disable: undefined-doc-name,luadoc-miss-symbol', + '', 'vim.api = {}', } -- cgit From c47496791a80f8b6b9e37866010305482de4c8ca Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Mon, 27 Jan 2025 14:25:06 -0800 Subject: docs(treesitter): fix TSNode:range() type signature #32224 Uses an overload to properly show the different return type based on the input parameter. --- runtime/doc/treesitter.txt | 2 +- runtime/ftplugin/help.lua | 2 +- runtime/lua/vim/treesitter/_meta/tsnode.lua | 11 +---------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 61d90e53c8..6286924f10 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -832,7 +832,7 @@ TSNode:range({include_bytes}) *TSNode:range()* • end byte (if {include_bytes} is `true`) Parameters: ~ - • {include_bytes} (`boolean?`) + • {include_bytes} (`false?`) Return (multiple): ~ (`integer`) diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index 479e4d8b9f..a6169a1d9d 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -53,7 +53,7 @@ for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do for id, nodes in pairs(match) do local name = query.captures[id] local node = nodes[1] - local start, _, end_ = node:parent():range() --[[@as integer]] + local start, _, end_ = node:parent():range() if name == 'code' then vim.api.nvim_buf_set_extmark(0, run_message_ns, start, 0, { diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua index b261b87253..552905c3f0 100644 --- a/runtime/lua/vim/treesitter/_meta/tsnode.lua +++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua @@ -103,18 +103,9 @@ function TSNode:end_() end --- - end row --- - end column --- - end byte (if {include_bytes} is `true`) ---- @param include_bytes boolean? ---- @return integer, integer, integer, integer -function TSNode:range(include_bytes) end - ---- @nodoc --- @param include_bytes false? --- @return integer, integer, integer, integer -function TSNode:range(include_bytes) end - ---- @nodoc ---- @param include_bytes true ---- @return integer, integer, integer, integer, integer, integer +--- @overload fun(self: TSNode, include_bytes: true): integer, integer, integer, integer, integer, integer function TSNode:range(include_bytes) end --- Get the node's type as a string. -- cgit From 318676ad13483fa1f9b2733d6915620f2525ca12 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 27 Jan 2025 17:40:26 +0100 Subject: ci(release)!: remove backwards compatible releases Remove `nvim-linux64.tar.gz` and `nvim.appimage` as maintaining these is too much work. Also fix directory names to be consistent. --- .github/workflows/notes.md | 6 ------ .github/workflows/release.yml | 31 +++++++------------------------ cmake.packaging/CMakeLists.txt | 4 ++++ scripts/genappimage.sh | 11 +++++++++-- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/.github/workflows/notes.md b/.github/workflows/notes.md index ed792f8d85..fe2317d483 100644 --- a/.github/workflows/notes.md +++ b/.github/workflows/notes.md @@ -48,18 +48,12 @@ glibc 2.31 or newer is required. Or you may try the (unsupported) [builds for ol ./squashfs-root/usr/bin/nvim ``` -> [!NOTE] -> This appimage is also published as `nvim.appimage` for backward compatibility, but scripts should be updated to use the new name. - #### Tarball 1. Download **nvim-linux-x86_64.tar.gz** 2. Extract: `tar xzvf nvim-linux-x86_64.tar.gz` 3. Run `./nvim-linux-x86_64/bin/nvim` -> [!NOTE] -> This tarball is also published as `nvim-linux64.tar.gz` for backward compatibility, but scripts should be updated to use the new name. - ### Linux (arm64) #### AppImage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2062d58103..0c9cf4b142 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,7 +49,7 @@ jobs: arch: x86_64 cc: gcc-10 - runner: ubuntu-24.04-arm - arch: aarch64 + arch: arm64 runs-on: ${{ matrix.runner }} env: CC: ${{ matrix.cc }} @@ -63,7 +63,7 @@ jobs: fetch-depth: 0 - run: ./.github/scripts/install_deps.sh - run: echo "CMAKE_BUILD_TYPE=${{ needs.setup.outputs.build_type }}" >> $GITHUB_ENV - - if: matrix.arch == 'aarch64' + - if: matrix.arch == 'arm64' run: sudo apt-get update && sudo apt-get install -y libfuse2t64 - name: appimage run: | @@ -196,13 +196,6 @@ jobs: echo 'PRERELEASE=') >> $GITHUB_ENV gh release delete stable --yes || true git push origin :stable || true - - name: Rename aarch64 artifacts - run: | - cd ./nvim-linux-aarch64 - mv nvim-linux-aarch64.tar.gz nvim-linux-arm64.tar.gz - cd ../appimage-aarch64 - mv nvim-linux-aarch64.appimage nvim-linux-arm64.appimage - mv nvim-linux-aarch64.appimage.zsync nvim-linux-arm64.appimage.zsync # `sha256sum` outputs , so we cd into each dir to drop the # containing folder from the output. - name: Generate Linux x86_64 SHA256 checksums @@ -212,7 +205,7 @@ jobs: echo "SHA_LINUX_X86_64_TAR=$(cat nvim-linux-x86_64.tar.gz.sha256sum)" >> $GITHUB_ENV - name: Generate Linux arm64 SHA256 checksums run: | - cd ./nvim-linux-aarch64 + cd ./nvim-linux-arm64 sha256sum nvim-linux-arm64.tar.gz > nvim-linux-arm64.tar.gz.sha256sum echo "SHA_LINUX_ARM64_TAR=$(cat nvim-linux-arm64.tar.gz.sha256sum)" >> $GITHUB_ENV - name: Generate AppImage x64_64 SHA256 checksums @@ -227,12 +220,12 @@ jobs: echo "SHA_APPIMAGE_X86_64_ZSYNC=$(cat nvim-linux-x86_64.appimage.zsync.sha256sum)" >> $GITHUB_ENV - name: Generate AppImage x64_64 SHA256 checksums run: | - cd ./appimage-aarch64 + cd ./appimage-arm64 sha256sum nvim-linux-arm64.appimage > nvim-linux-arm64.appimage.sha256sum echo "SHA_APPIMAGE_ARM64=$(cat nvim-linux-arm64.appimage.sha256sum)" >> $GITHUB_ENV - name: Generate AppImage arm64 Zsync SHA256 checksums run: | - cd ./appimage-aarch64 + cd ./appimage-arm64 sha256sum nvim-linux-arm64.appimage.zsync > nvim-linux-arm64.appimage.zsync.sha256sum echo "SHA_APPIMAGE_ARM64_ZSYNC=$(cat nvim-linux-arm64.appimage.zsync.sha256sum)" >> $GITHUB_ENV - name: Generate macos x86_64 SHA256 checksums @@ -252,16 +245,6 @@ jobs: echo "SHA_WIN_64_ZIP=$(cat nvim-win64.zip.sha256sum)" >> $GITHUB_ENV sha256sum nvim-win64.msi > nvim-win64.msi.sha256sum echo "SHA_WIN_64_MSI=$(cat nvim-win64.msi.sha256sum)" >> $GITHUB_ENV - - name: Create linux64 aliases # For backward compatibility; remove for 0.12 - run: | - cd ./nvim-linux-x86_64 - cp nvim-linux-x86_64.tar.gz nvim-linux64.tar.gz - cp nvim-linux-x86_64.tar.gz.sha256sum nvim-linux64.tar.gz.sha256sum - cd ../appimage-x86_64 - cp nvim-linux-x86_64.appimage nvim.appimage - cp nvim-linux-x86_64.appimage.sha256sum nvim.appimage.sha256sum - cp nvim-linux-x86_64.appimage.zsync nvim.appimage.zsync - cp nvim-linux-x86_64.appimage.zsync.sha256sum nvim.appimage.zsync.sha256sum - name: Publish release env: NVIM_VERSION: ${{ needs.linux.outputs.version }} @@ -269,6 +252,6 @@ jobs: run: | envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md" if [ "$TAG_NAME" != "nightly" ]; then - gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-aarch64/* appimage-x86_64/* appimage-aarch64/* nvim-win64/* + gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* appimage-x86_64/* appimage-arm64/* nvim-win64/* fi - gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-aarch64/* appimage-x86_64/* appimage-aarch64/* nvim-win64/* + gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* appimage-x86_64/* appimage-arm64/* nvim-win64/* diff --git a/cmake.packaging/CMakeLists.txt b/cmake.packaging/CMakeLists.txt index 54b6285954..dc611e9560 100644 --- a/cmake.packaging/CMakeLists.txt +++ b/cmake.packaging/CMakeLists.txt @@ -1,3 +1,7 @@ +if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + set(CMAKE_SYSTEM_PROCESSOR arm64) +endif() + set(CPACK_PACKAGE_NAME "Neovim") set(CPACK_PACKAGE_VENDOR "neovim.io") set(CPACK_PACKAGE_FILE_NAME "nvim") diff --git a/scripts/genappimage.sh b/scripts/genappimage.sh index 63cda6d6ec..e683a9dcd1 100755 --- a/scripts/genappimage.sh +++ b/scripts/genappimage.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -e ######################################################################## # Package the binaries built as an AppImage @@ -11,6 +11,7 @@ if [ -z "$ARCH" ]; then ARCH="$(arch)" export ARCH fi +ARCH_ORIGINAL=$ARCH TAG=$1 @@ -75,6 +76,12 @@ chmod 755 AppRun cd "$APP_BUILD_DIR" || exit # Get out of AppImage directory. +# We want to be consistent, so always use arm64 over aarch64 +if [[ "$ARCH" == 'aarch64' ]]; then + ARCH="arm64" + export ARCH +fi + # Set the name of the file generated by appimage export OUTPUT=nvim-linux-"$ARCH".appimage @@ -87,7 +94,7 @@ fi # - Expects: $ARCH, $APP, $VERSION env vars # - Expects: ./$APP.AppDir/ directory # - Produces: ./nvim-linux-$ARCH.appimage -./linuxdeploy-"$ARCH".AppImage --appdir $APP.AppDir -d "$ROOT_DIR"/runtime/nvim.desktop -i \ +./linuxdeploy-"$ARCH_ORIGINAL".AppImage --appdir $APP.AppDir -d "$ROOT_DIR"/runtime/nvim.desktop -i \ "$ROOT_DIR/runtime/nvim.png" --output appimage # Moving the final executable to a different folder so it isn't in the -- cgit From cb924764a40deea7e9c94fc60eac144e318a6f16 Mon Sep 17 00:00:00 2001 From: Judit Novak Date: Tue, 28 Jan 2025 17:17:37 +0100 Subject: fix(runtime): "E121 Undefined variable s:termguicolors" #32209 Problem: dircolors syntaxt termguicolors support was not taking dynamic termguicolors changes into account. Solution: initializing missing script-internal data on dynamic termguicolors change. --- runtime/syntax/dircolors.vim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/syntax/dircolors.vim b/runtime/syntax/dircolors.vim index 74a7068488..d968ed8fdd 100644 --- a/runtime/syntax/dircolors.vim +++ b/runtime/syntax/dircolors.vim @@ -85,6 +85,9 @@ endfunction function! s:get_hi_str(color, place) abort if a:color >= 0 && a:color <= 255 if has('gui_running') || &termguicolors + if ! exists("s:termguicolors") + call s:set_guicolors() + endif return ' gui' . a:place . '=' . s:termguicolors[a:color] elseif a:color <= 7 || &t_Co == 256 || &t_Co == 88 return ' cterm' . a:place . '=' . a:color -- cgit From a119dab40f939121d5e5a0c622f19911c9c9ce03 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 28 Jan 2025 17:34:07 +0100 Subject: fix(treesitter): avoid computing foldlevels for reloaded buffer #32233 --- runtime/lua/vim/treesitter/_fold.lua | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index f8d18d8427..ad4110b83d 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -269,10 +269,15 @@ local function schedule_if_loaded(bufnr, fn) end ---@param bufnr integer ----@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] -local function on_changedtree(bufnr, foldinfo, tree_changes) +local function on_changedtree(bufnr, tree_changes) schedule_if_loaded(bufnr, function() + -- Buffer reload clears `foldinfos[bufnr]`, which may still be nil when callback is invoked. + local foldinfo = foldinfos[bufnr] + if not foldinfo then + return + end + local srow_upd, erow_upd ---@type integer?, integer? local max_erow = api.nvim_buf_line_count(bufnr) -- TODO(ribru17): Replace this with a proper .all() awaiter once #19624 is resolved @@ -303,13 +308,18 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) end ---@param bufnr integer ----@param foldinfo TS.FoldInfo ---@param start_row integer ---@param old_row integer ---@param old_col integer ---@param new_row integer ---@param new_col integer -local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, new_row, new_col) +local function on_bytes(bufnr, start_row, start_col, old_row, old_col, new_row, new_col) + -- Buffer reload clears `foldinfos[bufnr]`, which may still be nil when callback is invoked. + local foldinfo = foldinfos[bufnr] + if not foldinfo then + return + end + -- extend the end to fully include the range local end_row_old = start_row + old_row + 1 local end_row_new = start_row + new_row + 1 @@ -348,7 +358,7 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, -- is invoked. For example, `J` with non-zero count triggers multiple on_bytes before executing -- the scheduled callback. So we accumulate the edited ranges in `on_bytes_range`. schedule_if_loaded(bufnr, function() - if not foldinfo.on_bytes_range then + if not (foldinfo.on_bytes_range and foldinfos[bufnr]) then return end local srow, erow = foldinfo.on_bytes_range[1], foldinfo.on_bytes_range[2] @@ -387,13 +397,11 @@ function M.foldexpr(lnum) parser:register_cbs({ on_changedtree = function(tree_changes) - if foldinfos[bufnr] then - on_changedtree(bufnr, foldinfos[bufnr], tree_changes) - end + on_changedtree(bufnr, tree_changes) end, on_bytes = function(_, _, start_row, start_col, _, old_row, old_col, _, new_row, new_col, _) - on_bytes(bufnr, foldinfos[bufnr], start_row, start_col, old_row, old_col, new_row, new_col) + on_bytes(bufnr, start_row, start_col, old_row, old_col, new_row, new_col) end, on_detach = function() -- cgit From b88874d33c15bb0fd7a421230f8bf819056d7665 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 28 Jan 2025 09:59:04 -0800 Subject: fix(treesitter): empty queries can disable injections (#31748) **Problem:** Currently, if users want to efficiently disable injections, they have to delete the injection query files at their runtime path. This is because we only check for existence of the files before running the query over the entire buffer. **Solution:** Check for existence of query files, *and* that those files actually have captures. This will allow users to just comment out existing queries (or better yet, just add their own injection query to `~/.config/nvim` which contains only comments) to disable running the query over the entire buffer (a potentially slow operation) --- runtime/doc/treesitter.txt | 4 ++++ runtime/lua/vim/treesitter/languagetree.lua | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 6286924f10..d87439a1e0 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -572,6 +572,10 @@ associated with patterns: • `injection.parent` - indicates that the captured node’s text should be parsed with the same language as the node's parent LanguageTree. +Injection queries are currently run over the entire buffer, which can be slow +for large buffers. To disable injections for, e.g., `c`, just place an +empty `queries/c/injections.scm` file in your 'runtimepath'. + ============================================================================== VIM.TREESITTER *lua-treesitter* diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index ecace67419..5e1156fa68 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -953,7 +953,7 @@ end --- @private --- @return table function LanguageTree:_get_injections() - if not self._injection_query then + if not self._injection_query or #self._injection_query.captures == 0 then return {} end -- cgit From 6711fa27ca6e822bfd2394ec513671617cc53efd Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 28 Jan 2025 12:22:25 -0800 Subject: fix(treesitter): recalculate folds on VimEnter #32240 **Problem:** In the case where the user sets the treesitter foldexpr upon startup in their `init.lua`, the fold info will be calculated before the parser has been loaded in, meaning folds will be properly calculated until edits or `:e`. **Solution:** Refresh fold information upon `VimEnter` as a sanity check to ensure that a parser really doesn't exist before always returning `'0'` in the foldexpr. --- runtime/lua/vim/treesitter/_fold.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index ad4110b83d..38318347a7 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -380,7 +380,7 @@ function M.foldexpr(lnum) if not foldinfos[bufnr] then foldinfos[bufnr] = FoldInfo.new(bufnr) - api.nvim_create_autocmd('BufUnload', { + api.nvim_create_autocmd({ 'BufUnload', 'VimEnter' }, { buffer = bufnr, once = true, callback = function() -- cgit From a7be4b7bf857de9680ee3d1723a9f616e8a20776 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 28 Jan 2025 20:32:40 -0500 Subject: test(unit/strings_spec): provide context for vim_snprintf tests Since these assertions all use a common function to perform the test assertions, it's difficult to figure out which test failed: ERROR test/unit/testutil.lua @ 785: vim_snprintf() positional arguments test/unit/testutil.lua:757: test/unit/testutil.lua:741: (string) ' test/unit/strings_spec.lua:143: Expected objects to be the same. Passed in: (number) 6400 Expected: (number) 6' exit code: 256 Adding context to the assertion makes it clearer what the problem is: ERROR test/unit/testutil.lua @ 785: vim_snprintf() positional arguments test/unit/testutil.lua:757: test/unit/testutil.lua:741: (string) ' test/unit/strings_spec.lua:149: snprintf(buf, 0, "%1$0.*2$b", cdata: 0xf78d0f38, cdata: 0xf78dc4e0) = 001100 Expected objects to be the same. Passed in: (number) 6400 Expected: (number) 6' exit code: 256 --- test/unit/strings_spec.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index 25cdc27b28..86d528f0e8 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -140,7 +140,13 @@ end) describe('vim_snprintf()', function() local function a(expected, buf, bsize, fmt, ...) - eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...)) + local args = { ... } + local ctx = string.format('snprintf(buf, %d, "%s"', bsize, fmt) + for _, x in ipairs(args) do + ctx = ctx .. ', ' .. tostring(x) + end + ctx = ctx .. string.format(') = %s', expected) + eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...), ctx) if bsize > 0 then local actual = ffi.string(buf, math.min(#expected + 1, bsize)) eq(expected:sub(1, bsize - 1) .. '\0', actual) -- cgit From da0ae953490098c28bad4791e08e2cc4c2e385e2 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Tue, 28 Jan 2025 23:59:28 -0800 Subject: feat(treesitter): support modelines in `query.set()` (#30257) --- runtime/doc/news.txt | 2 + runtime/doc/treesitter.txt | 16 ++++++-- runtime/lua/vim/treesitter/query.lua | 66 +++++++++++++++++++++++++------ test/functional/treesitter/query_spec.lua | 28 +++++++++++++ 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 1dee72314a..65417971ab 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -376,6 +376,8 @@ TREESITTER child that contains a given node as descendant. • |LanguageTree:parse()| optionally supports asynchronous invocation, which is activated by passing the `on_parse` callback parameter. +• |vim.treesitter.query.set()| can now inherit and/or extend runtime file + queries in addition to overriding. TUI diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index d87439a1e0..df974d750c 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1349,7 +1349,7 @@ parse({lang}, {query}) *vim.treesitter.query.parse()* `info.captures`). • `info.patterns`: information about predicates. - Example (to try it, use `g==` or select the code then run `:'<,'>lua`): >lua + Example: >lua local query = vim.treesitter.query.parse('vimdoc', [[ ; query ((h1) @str @@ -1466,8 +1466,18 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) set({lang}, {query_name}, {text}) *vim.treesitter.query.set()* Sets the runtime query named {query_name} for {lang} - This allows users to override any runtime files and/or configuration set - by plugins. + This allows users to override or extend any runtime files and/or + configuration set by plugins. + + For example, you could enable spellchecking of `C` identifiers with the + following code: >lua + vim.treesitter.query.set( + 'c', + 'highlights', + [[;inherits c + (identifier) @spell]]) + ]]) +< Parameters: ~ • {lang} (`string`) Language to use for the query diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8055270a7f..10fb82e533 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -5,6 +5,9 @@ local api = vim.api local language = require('vim.treesitter.language') local memoize = vim.func._memoize +local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$' +local EXTENDS_FORMAT = '^;+%s*extends%s*$' + local M = {} local function is_directive(name) @@ -167,9 +170,6 @@ function M.get_files(lang, query_name, is_included) -- ;+ inherits: ({language},)*{language} -- -- {language} ::= {lang} | ({lang}) - local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$' - local EXTENDS_FORMAT = '^;+%s*extends%s*$' - for _, filename in ipairs(lang_files) do local file, err = io.open(filename, 'r') if not file then @@ -242,8 +242,8 @@ local function read_query_files(filenames) return table.concat(contents, '') end --- The explicitly set queries from |vim.treesitter.query.set()| ----@type table> +-- The explicitly set query strings from |vim.treesitter.query.set()| +---@type table> local explicit_queries = setmetatable({}, { __index = function(t, k) local lang_queries = {} @@ -255,16 +255,27 @@ local explicit_queries = setmetatable({}, { --- Sets the runtime query named {query_name} for {lang} --- ---- This allows users to override any runtime files and/or configuration +--- This allows users to override or extend any runtime files and/or configuration --- set by plugins. --- +--- For example, you could enable spellchecking of `C` identifiers with the +--- following code: +--- ```lua +--- vim.treesitter.query.set( +--- 'c', +--- 'highlights', +--- [[;inherits c +--- (identifier) @spell]]) +--- ]]) +--- ``` +--- ---@param lang string Language to use for the query ---@param query_name string Name of the query (e.g., "highlights") ---@param text string Query text (unparsed). function M.set(lang, query_name, text) --- @diagnostic disable-next-line: undefined-field LuaLS bad at generics M.get:clear(lang, query_name) - explicit_queries[lang][query_name] = M.parse(lang, text) + explicit_queries[lang][query_name] = text end --- Returns the runtime query {query_name} for {lang}. @@ -274,12 +285,43 @@ end --- ---@return vim.treesitter.Query? : Parsed query. `nil` if no query files are found. M.get = memoize('concat-2', function(lang, query_name) + local query_string ---@type string + if explicit_queries[lang][query_name] then - return explicit_queries[lang][query_name] - end + local query_files = {} + local base_langs = {} ---@type string[] + + for line in explicit_queries[lang][query_name]:gmatch('([^\n]*)\n?') do + if not vim.startswith(line, ';') then + break + end + + local lang_list = line:match(MODELINE_FORMAT) + if lang_list then + for _, incl_lang in ipairs(vim.split(lang_list, ',')) do + local is_optional = incl_lang:match('%(.*%)') - local query_files = M.get_files(lang, query_name) - local query_string = read_query_files(query_files) + if is_optional then + add_included_lang(base_langs, lang, incl_lang:sub(2, #incl_lang - 1)) + else + add_included_lang(base_langs, lang, incl_lang) + end + end + elseif line:match(EXTENDS_FORMAT) then + table.insert(base_langs, lang) + end + end + + for _, base_lang in ipairs(base_langs) do + local base_files = M.get_files(base_lang, query_name, true) + vim.list_extend(query_files, base_files) + end + + query_string = read_query_files(query_files) .. explicit_queries[lang][query_name] + else + local query_files = M.get_files(lang, query_name) + query_string = read_query_files(query_files) + end if #query_string == 0 then return nil @@ -303,7 +345,7 @@ api.nvim_create_autocmd('OptionSet', { --- - `captures`: a list of unique capture names defined in the query (alias: `info.captures`). --- - `info.patterns`: information about predicates. --- ---- Example (to try it, use `g==` or select the code then run `:'<,'>lua`): +--- Example: --- ```lua --- local query = vim.treesitter.query.parse('vimdoc', [[ --- ; query diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 6bab171ee8..6db0ffe5a0 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -812,6 +812,34 @@ void ui_refresh(void) ) end) + it('supports "; extends" modeline in custom queries', function() + insert('int zeero = 0;') + local result = exec_lua(function() + vim.treesitter.query.set( + 'c', + 'highlights', + [[; extends + (identifier) @spell]] + ) + local query = vim.treesitter.query.get('c', 'highlights') + local parser = vim.treesitter.get_parser(0, 'c') + local root = parser:parse()[1]:root() + local res = {} + for id, node in query:iter_captures(root, 0) do + table.insert(res, { query.captures[id], vim.treesitter.get_node_text(node, 0) }) + end + return res + end) + eq({ + { 'type.builtin', 'int' }, + { 'variable', 'zeero' }, + { 'spell', 'zeero' }, + { 'operator', '=' }, + { 'number', '0' }, + { 'punctuation.delimiter', ';' }, + }, result) + end) + describe('Query:iter_captures', function() it('includes metadata for all captured nodes #23664', function() insert([[ -- cgit From 19f00bf32cebfa66a17e0f5945d62d7da1859623 Mon Sep 17 00:00:00 2001 From: Daniel Petrovic Date: Wed, 29 Jan 2025 09:02:49 +0100 Subject: fix(treesitter) Set modeline=false in TSHighlighter:destroy (#32234) Problem: `TSHighlighter:destroy()` causes double-processing of the modeline and failure of `b:undo_ftplugin`. Solution: Disable modeline in `TSHighlighter:destroy()` by setting `modeline=false` if executing `syntaxset` autocommands for the `FileType` event. Co-authored-by: Daniel Petrovic --- runtime/lua/vim/treesitter/highlighter.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index c11fa1999d..6dd47811bd 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -160,7 +160,10 @@ function TSHighlighter:destroy() vim.bo[self.bufnr].spelloptions = self.orig_spelloptions vim.b[self.bufnr].ts_highlight = nil if vim.g.syntax_on == 1 then - api.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr }) + api.nvim_exec_autocmds( + 'FileType', + { group = 'syntaxset', buffer = self.bufnr, modeline = false } + ) end end end -- cgit From e7ebc5c13d2d1658005a7fb477bc92718044746f Mon Sep 17 00:00:00 2001 From: notomo Date: Sat, 25 Jan 2025 21:15:01 +0900 Subject: fix(treesitter): stop async parsing if buffer is invalid Problem: Error occurs if delete buffer in the middle of parsing. Solution: Check if buffer is valid in parsing. --- runtime/lua/vim/treesitter/languagetree.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 5e1156fa68..8ea1c44cdc 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -475,13 +475,18 @@ function LanguageTree:_async_parse(range, on_parse) return end - local buf = vim.b[self._source] + local source = self._source + local buf = vim.b[source] local ct = buf.changedtick local total_parse_time = 0 local redrawtime = vim.o.redrawtime local timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil local function step() + if type(source) == 'number' and not vim.api.nvim_buf_is_valid(source) then + return nil + end + -- If buffer was changed in the middle of parsing, reset parse state if buf.changedtick ~= ct then ct = buf.changedtick -- cgit From 216ec739721494fb31111f19b1dee356a0d88ba1 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 29 Jan 2025 12:07:27 +0100 Subject: fix(ui): avoid redundant ext_cmdline events (#32237) Problem: `cmdline_show` is emitted unnecessarily each event loop iteration, because `cmdline_was_last_drawn` is never set. Solution: Keep track of whether the cmdline was last drawn to avoid unnecessarily emitting cmdline_show. Set `redraw_state` to emit `cmdline_pos` when emitting `CursorMovedC`. Only emit `cmdline_pos` when cmdline was last drawn. --- src/nvim/drawscreen.c | 7 +++-- src/nvim/ex_getln.c | 54 ++++++++++++++++++----------------- test/functional/lua/ui_event_spec.lua | 4 +-- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 66c9b2be29..4d7f80bf76 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -692,8 +692,11 @@ int update_screen(void) decor_providers_invoke_end(); - // either cmdline is cleared, not drawn or mode is last drawn - cmdline_was_last_drawn = false; + // Either cmdline is cleared, not drawn or mode is last drawn. + // This does not (necessarily) overwrite an external cmdline. + if (!ui_has(kUICmdline)) { + cmdline_was_last_drawn = false; + } return OK; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1eecee2a38..b5fa05e5a4 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -679,6 +679,15 @@ static void init_ccline(int firstc, int indent) } } +static void ui_ext_cmdline_hide(bool abort) +{ + if (ui_has(kUICmdline)) { + cmdline_was_last_drawn = false; + ccline.redraw_state = kCmdRedrawNone; + ui_call_cmdline_hide(ccline.level, abort); + } +} + /// Internal entry point for cmdline mode. /// /// @param count only used for incremental search @@ -954,8 +963,7 @@ theend: char *p = ccline.cmdbuff; if (ui_has(kUICmdline)) { - ccline.redraw_state = kCmdRedrawNone; - ui_call_cmdline_hide(ccline.level, s->gotesc); + ui_ext_cmdline_hide(s->gotesc); msg_ext_clear_later(); } if (!cmd_silent) { @@ -2209,14 +2217,19 @@ end: return ccline.one_key ? 0 : command_line_changed(s); } -static int command_line_not_changed(CommandLineState *s) +/// Trigger CursorMovedC autocommands. +static void may_trigger_cursormovedc(CommandLineState *s) { - // Trigger CursorMovedC autocommands. if (ccline.cmdpos != s->prev_cmdpos) { trigger_cmd_autocmd(get_cmdline_type(), EVENT_CURSORMOVEDC); s->prev_cmdpos = ccline.cmdpos; + ccline.redraw_state = MAX(ccline.redraw_state, kCmdRedrawPos); } +} +static int command_line_not_changed(CommandLineState *s) +{ + may_trigger_cursormovedc(s); // Incremental searches for "/" and "?": // Enter command_line_not_changed() when a character has been read but the // command line did not change. Then we only search and redraw if something @@ -2696,11 +2709,7 @@ static int command_line_changed(CommandLineState *s) // Trigger CmdlineChanged autocommands. do_autocmd_cmdlinechanged(s->firstc > 0 ? s->firstc : '-'); - // Trigger CursorMovedC autocommands. - if (ccline.cmdpos != s->prev_cmdpos) { - trigger_cmd_autocmd(get_cmdline_type(), EVENT_CURSORMOVEDC); - s->prev_cmdpos = ccline.cmdpos; - } + may_trigger_cursormovedc(s); const bool prev_cmdpreview = cmdpreview; if (s->firstc == ':' @@ -2741,7 +2750,6 @@ static int command_line_changed(CommandLineState *s) static void abandon_cmdline(void) { dealloc_cmdbuff(); - ccline.redraw_state = kCmdRedrawNone; if (msg_scrolled == 0) { compute_cmdrow(); } @@ -3385,7 +3393,7 @@ color_cmdline_error: // when cmdline_star is true. static void draw_cmdline(int start, int len) { - if (!color_cmdline(&ccline)) { + if (ccline.cmdbuff == NULL || !color_cmdline(&ccline)) { return; } @@ -3491,8 +3499,7 @@ void ui_ext_cmdline_block_leave(void) ui_call_cmdline_block_hide(); } -/// Extra redrawing needed for redraw! and on ui_attach -/// assumes "redrawcmdline()" will already be invoked +/// Extra redrawing needed for redraw! and on ui_attach. void cmdline_screen_cleared(void) { if (!ui_has(kUICmdline)) { @@ -3515,6 +3522,7 @@ void cmdline_screen_cleared(void) } line = line->prev_ccline; } + redrawcmd(); } /// called by ui_flush, do what redraws necessary to keep cmdline updated. @@ -3527,12 +3535,14 @@ void cmdline_ui_flush(void) CmdlineInfo *line = &ccline; while (level > 0 && line) { if (line->level == level) { - if (line->redraw_state == kCmdRedrawAll) { + CmdRedraw redraw_state = line->redraw_state; + line->redraw_state = kCmdRedrawNone; + if (redraw_state == kCmdRedrawAll) { + cmdline_was_last_drawn = true; ui_ext_cmdline_show(line); - } else if (line->redraw_state == kCmdRedrawPos) { + } else if (redraw_state == kCmdRedrawPos && cmdline_was_last_drawn) { ui_call_cmdline_pos(line->cmdpos, line->level); } - line->redraw_state = kCmdRedrawNone; level--; } line = line->prev_ccline; @@ -3900,12 +3910,7 @@ void compute_cmdrow(void) void cursorcmd(void) { - if (cmd_silent) { - return; - } - - if (ui_has(kUICmdline)) { - ccline.redraw_state = MAX(ccline.redraw_state, kCmdRedrawPos); + if (cmd_silent || ui_has(kUICmdline)) { return; } @@ -4507,10 +4512,7 @@ static int open_cmdwin(void) curwin->w_cursor.col = ccline.cmdpos; changed_line_abv_curs(); invalidate_botline(curwin); - if (ui_has(kUICmdline)) { - ccline.redraw_state = kCmdRedrawNone; - ui_call_cmdline_hide(ccline.level, false); - } + ui_ext_cmdline_hide(false); redraw_later(curwin, UPD_SOME_VALID); // No Ex mode here! diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 80457555d4..ddb10127e4 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -202,7 +202,7 @@ describe('vim.ui_attach', function() feed([[:call confirm("Save changes?", "&Yes\n&No\n&Cancel")]]) screen:expect({ grid = [[ - ^5 | + ^4 | {1:~ }|*4 ]], cmdline = { @@ -224,7 +224,7 @@ describe('vim.ui_attach', function() feed('n') screen:expect({ grid = [[ - ^5 | + ^4 | {1:~ }|*4 ]], cmdline = { { abort = false } }, -- cgit From 1426f3f3ce91816351412f8cdf5849b76fd5a4a0 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 28 Jan 2025 20:54:32 -0500 Subject: test(unit/strings_spec): use correct type for binary values When 9.0.1856 was ported, the numbers being formatted as binary were cast to "unsigned int" rather than uvarnumber_T, as is done upstream. --- test/unit/strings_spec.lua | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index 86d528f0e8..2b7a4d6261 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -1,6 +1,7 @@ local t = require('test.unit.testutil') local itp = t.gen_itp(it) +local child_call_once = t.child_call_once local cimport = t.cimport local eq = t.eq local ffi = t.ffi @@ -8,6 +9,12 @@ local to_cstr = t.to_cstr local strings = cimport('stdlib.h', './src/nvim/strings.h', './src/nvim/memory.h') +local UVARNUM_TYPE + +child_call_once(function() + UVARNUM_TYPE = ffi.typeof('uvarnumber_T') +end) + describe('vim_strsave_escaped()', function() local vim_strsave_escaped = function(s, chars) local res = strings.vim_strsave_escaped(to_cstr(s), to_cstr(chars)) @@ -153,6 +160,9 @@ describe('vim_snprintf()', function() end end + local function uv(n) + return ffi.cast(UVARNUM_TYPE, n) + end local function i(n) return ffi.cast('int', n) end @@ -187,7 +197,7 @@ describe('vim_snprintf()', function() a(' 1234567', buf, bsize, '%9ld', l(1234567)) a('1234567 ', buf, bsize, '%-9ld', l(1234567)) a('deadbeef', buf, bsize, '%x', u(0xdeadbeef)) - a('001100', buf, bsize, '%06b', u(12)) + a('001100', buf, bsize, '%06b', uv(12)) a('one two', buf, bsize, '%s %s', 'one', 'two') a('1.234000', buf, bsize, '%f', 1.234) a('1.234000e+00', buf, bsize, '%e', 1.234) @@ -229,10 +239,10 @@ describe('vim_snprintf()', function() a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three') a('1234567', buf, bsize, '%1$d', i(1234567)) a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef)) - a('001100', buf, bsize, '%2$0*1$b', i(6), u(12)) - a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6)) + a('001100', buf, bsize, '%2$0*1$b', i(6), uv(12)) + a('001100', buf, bsize, '%1$0.*2$b', uv(12), i(6)) a('one two', buf, bsize, '%1$s %2$s', 'one', 'two') - a('001100', buf, bsize, '%06b', u(12)) + a('001100', buf, bsize, '%06b', uv(12)) a('two one', buf, bsize, '%2$s %1$s', 'one', 'two') a('1.234000', buf, bsize, '%1$f', 1.234) a('1.234000e+00', buf, bsize, '%1$e', 1.234) -- cgit From 35c5e231078365033524b0aa2166118a1b2ef600 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 29 Jan 2025 15:22:20 +0100 Subject: ci!: store artifact shasums in a single shasum.txt file Users can parse this file to get the shasum they require. --- .github/workflows/notes.md | 13 --------- .github/workflows/release.yml | 63 +++++++++---------------------------------- 2 files changed, 13 insertions(+), 63 deletions(-) diff --git a/.github/workflows/notes.md b/.github/workflows/notes.md index fe2317d483..092bab2720 100644 --- a/.github/workflows/notes.md +++ b/.github/workflows/notes.md @@ -77,16 +77,3 @@ glibc 2.31 or newer is required. Or you may try the (unsupported) [builds for ol - Install by [package manager](https://github.com/neovim/neovim/blob/master/INSTALL.md#install-from-package) ## SHA256 Checksums - -``` -${SHA_APPIMAGE_ARM64} -${SHA_APPIMAGE_ARM64_ZSYNC} -${SHA_LINUX_ARM64_TAR} -${SHA_APPIMAGE_X86_64} -${SHA_APPIMAGE_X86_64_ZSYNC} -${SHA_LINUX_X86_64_TAR} -${SHA_MACOS_ARM64} -${SHA_MACOS_X86_64} -${SHA_WIN_64_MSI} -${SHA_WIN_64_ZIP} -``` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c9cf4b142..9f26e667f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: run: cpack --config build/CPackConfig.cmake -G TGZ - uses: actions/upload-artifact@v4 with: - name: appimage-${{ matrix.arch }} + name: nvim-appimage-${{ matrix.arch }} path: | build/bin/nvim-linux-${{ matrix.arch }}.appimage build/bin/nvim-linux-${{ matrix.arch }}.appimage.zsync @@ -198,60 +198,23 @@ jobs: git push origin :stable || true # `sha256sum` outputs , so we cd into each dir to drop the # containing folder from the output. - - name: Generate Linux x86_64 SHA256 checksums - run: | - cd ./nvim-linux-x86_64 - sha256sum nvim-linux-x86_64.tar.gz > nvim-linux-x86_64.tar.gz.sha256sum - echo "SHA_LINUX_X86_64_TAR=$(cat nvim-linux-x86_64.tar.gz.sha256sum)" >> $GITHUB_ENV - - name: Generate Linux arm64 SHA256 checksums - run: | - cd ./nvim-linux-arm64 - sha256sum nvim-linux-arm64.tar.gz > nvim-linux-arm64.tar.gz.sha256sum - echo "SHA_LINUX_ARM64_TAR=$(cat nvim-linux-arm64.tar.gz.sha256sum)" >> $GITHUB_ENV - - name: Generate AppImage x64_64 SHA256 checksums - run: | - cd ./appimage-x86_64 - sha256sum nvim-linux-x86_64.appimage > nvim-linux-x86_64.appimage.sha256sum - echo "SHA_APPIMAGE_X86_64=$(cat nvim-linux-x86_64.appimage.sha256sum)" >> $GITHUB_ENV - - name: Generate AppImage x86_64 Zsync SHA256 checksums - run: | - cd ./appimage-x86_64 - sha256sum nvim-linux-x86_64.appimage.zsync > nvim-linux-x86_64.appimage.zsync.sha256sum - echo "SHA_APPIMAGE_X86_64_ZSYNC=$(cat nvim-linux-x86_64.appimage.zsync.sha256sum)" >> $GITHUB_ENV - - name: Generate AppImage x64_64 SHA256 checksums - run: | - cd ./appimage-arm64 - sha256sum nvim-linux-arm64.appimage > nvim-linux-arm64.appimage.sha256sum - echo "SHA_APPIMAGE_ARM64=$(cat nvim-linux-arm64.appimage.sha256sum)" >> $GITHUB_ENV - - name: Generate AppImage arm64 Zsync SHA256 checksums - run: | - cd ./appimage-arm64 - sha256sum nvim-linux-arm64.appimage.zsync > nvim-linux-arm64.appimage.zsync.sha256sum - echo "SHA_APPIMAGE_ARM64_ZSYNC=$(cat nvim-linux-arm64.appimage.zsync.sha256sum)" >> $GITHUB_ENV - - name: Generate macos x86_64 SHA256 checksums - run: | - cd ./nvim-macos-x86_64 - sha256sum nvim-macos-x86_64.tar.gz > nvim-macos-x86_64.tar.gz.sha256sum - echo "SHA_MACOS_X86_64=$(cat nvim-macos-x86_64.tar.gz.sha256sum)" >> $GITHUB_ENV - - name: Generate macos arm64 SHA256 checksums - run: | - cd ./nvim-macos-arm64 - sha256sum nvim-macos-arm64.tar.gz > nvim-macos-arm64.tar.gz.sha256sum - echo "SHA_MACOS_ARM64=$(cat nvim-macos-arm64.tar.gz.sha256sum)" >> $GITHUB_ENV - - name: Generate Win64 SHA256 checksums - run: | - cd ./nvim-win64 - sha256sum nvim-win64.zip > nvim-win64.zip.sha256sum - echo "SHA_WIN_64_ZIP=$(cat nvim-win64.zip.sha256sum)" >> $GITHUB_ENV - sha256sum nvim-win64.msi > nvim-win64.msi.sha256sum - echo "SHA_WIN_64_MSI=$(cat nvim-win64.msi.sha256sum)" >> $GITHUB_ENV + - run: | + for i in nvim-*; do + ( + cd $i || exit + sha256sum * >> $GITHUB_WORKSPACE/shasum.txt + ) + done - name: Publish release env: NVIM_VERSION: ${{ needs.linux.outputs.version }} DEBUG: api run: | envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md" + echo '```' >> "$RUNNER_TEMP/notes.md" + cat shasum.txt >> "$RUNNER_TEMP/notes.md" + echo '```' >> "$RUNNER_TEMP/notes.md" if [ "$TAG_NAME" != "nightly" ]; then - gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* appimage-x86_64/* appimage-arm64/* nvim-win64/* + gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* nvim-appimage-x86_64/* nvim-appimage-arm64/* nvim-win64/* shasum.txt fi - gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* appimage-x86_64/* appimage-arm64/* nvim-win64/* + gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* nvim-appimage-x86_64/* nvim-appimage-arm64/* nvim-win64/* shasum.txt -- cgit From efa664c7ed21b63f2cf0a8caa53161fe7e32b2bb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 30 Jan 2025 14:39:13 +0800 Subject: vim-patch:9.1.1056: Vim doesn't highlight to be inserted text when completing (#32251) Problem: Vim doesn't highlight to be inserted text when completing Solution: Add support for the "preinsert" 'completeopt' value (glepnir) Support automatically inserting the currently selected candidate word that does not belong to the latter part of the leader. fixes: vim/vim#3433 closes: vim/vim#16403 https://github.com/vim/vim/commit/edd4ac3e895ce16034c7e098f1d68e0155d97886 Co-authored-by: glepnir --- runtime/doc/dev_vimpatch.txt | 1 + runtime/doc/news.txt | 1 + runtime/doc/options.txt | 6 ++ runtime/lua/vim/_meta/options.lua | 6 ++ src/nvim/ascii_defs.h | 9 +++ src/nvim/edit.c | 5 +- src/nvim/insexpand.c | 58 ++++++++++++++-- src/nvim/options.lua | 7 ++ test/old/testdir/gen_opt_test.vim | 4 +- test/old/testdir/test_ins_complete.vim | 117 +++++++++++++++++++++++++++++++++ 10 files changed, 205 insertions(+), 9 deletions(-) diff --git a/runtime/doc/dev_vimpatch.txt b/runtime/doc/dev_vimpatch.txt index 5119613b55..76be24878a 100644 --- a/runtime/doc/dev_vimpatch.txt +++ b/runtime/doc/dev_vimpatch.txt @@ -188,6 +188,7 @@ information. vim_strcat strncat xstrlcat VIM_ISWHITE ascii_iswhite IS_WHITE_OR_NUL ascii_iswhite_or_nul + IS_WHITE_NL_OR_NUL ascii_iswhite_nl_or_nul vim_isalpha mb_isalpha vim_isNormalIDc ascii_isident vim_islower vim_isupper mb_islower mb_isupper diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 65417971ab..2208428f75 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -309,6 +309,7 @@ LUA OPTIONS • 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|. +• 'completeopt' flag "preinsert" highlights text to be inserted. • 'messagesopt' configures |:messages| and |hit-enter| prompt. • 'tabclose' controls which tab page to focus when closing a tab page. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 1f19db5eb9..b4ae3cc8fd 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1575,6 +1575,12 @@ A jump table for the options with a short description can be found at |Q_op|. scores when "fuzzy" is enabled. Candidates will appear in their original order. + preinsert + Preinsert the portion of the first candidate word that is + not part of the current completion leader and using the + |hl-ComplMatchIns| highlight group. Does not work when + "fuzzy" is also included. + *'completeslash'* *'csl'* 'completeslash' 'csl' string (default "") local to buffer diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index f0f0d1a768..452959970d 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1102,6 +1102,12 @@ vim.go.cia = vim.go.completeitemalign --- scores when "fuzzy" is enabled. Candidates will appear --- in their original order. --- +--- preinsert +--- Preinsert the portion of the first candidate word that is +--- not part of the current completion leader and using the +--- `hl-ComplMatchIns` highlight group. Does not work when +--- "fuzzy" is also included. +--- --- @type string vim.o.completeopt = "menu,preview" vim.o.cot = vim.o.completeopt diff --git a/src/nvim/ascii_defs.h b/src/nvim/ascii_defs.h index 155a18fb95..86187b553c 100644 --- a/src/nvim/ascii_defs.h +++ b/src/nvim/ascii_defs.h @@ -103,6 +103,15 @@ static inline bool ascii_iswhite_or_nul(int c) return ascii_iswhite(c) || c == NUL; } +/// Checks if `c` is a space or tab or newline character or NUL. +/// +/// @see {ascii_isdigit} +static inline bool ascii_iswhite_nl_or_nul(int c) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE +{ + return ascii_iswhite(c) || c == '\n' || c == NUL; +} + /// Check whether character is a decimal digit. /// /// Library isdigit() function is officially locale-dependent and, for diff --git a/src/nvim/edit.c b/src/nvim/edit.c index e55cda1c21..9e17c93f3f 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -609,7 +609,10 @@ static int insert_execute(VimState *state, int key) && (s->c == CAR || s->c == K_KENTER || s->c == NL))) && stop_arrow() == OK) { ins_compl_delete(false); - ins_compl_insert(false); + ins_compl_insert(false, false); + } else if (ascii_iswhite_nl_or_nul(s->c) && ins_compl_preinsert_effect()) { + // Delete preinserted text when typing special chars + ins_compl_delete(false); } } } diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 8eb1a49e7d..73b84175d7 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1781,12 +1781,33 @@ int ins_compl_len(void) return compl_length; } +/// Return true when preinsert is set otherwise FALSE. +static bool ins_compl_has_preinsert(void) +{ + return (get_cot_flags() & (kOptCotFlagFuzzy|kOptCotFlagPreinsert)) == kOptCotFlagPreinsert; +} + +/// Returns true if the pre-insert effect is valid and the cursor is within +/// the `compl_ins_end_col` range. +bool ins_compl_preinsert_effect(void) +{ + if (!ins_compl_has_preinsert()) { + return false; + } + + return curwin->w_cursor.col < compl_ins_end_col; +} + /// Delete one character before the cursor and show the subset of the matches /// that match the word that is now before the cursor. /// Returns the character to be used, NUL if the work is done and another char /// to be got from the user. int ins_compl_bs(void) { + if (ins_compl_preinsert_effect()) { + ins_compl_delete(false); + } + char *line = get_cursor_line_ptr(); char *p = line + curwin->w_cursor.col; MB_PTR_BACK(line, p); @@ -1872,8 +1893,13 @@ static void ins_compl_new_leader(void) ins_compl_show_pum(); // Don't let Enter select the original text when there is no popup menu. + if (compl_match_array == NULL) { + compl_enter_selects = false; + } else if (ins_compl_has_preinsert() && compl_leader.size > 0) { + ins_compl_insert(false, true); + } // Don't let Enter select when use user function and refresh_always is set - if (compl_match_array == NULL || ins_compl_refresh_always()) { + if (ins_compl_refresh_always()) { compl_enter_selects = false; } } @@ -1896,6 +1922,10 @@ void ins_compl_addleader(int c) { int cc; + if (ins_compl_preinsert_effect()) { + ins_compl_delete(false); + } + if (stop_arrow() == FAIL) { return; } @@ -3696,6 +3726,12 @@ void ins_compl_delete(bool new_leader) // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. int col = compl_col + (compl_status_adding() ? compl_length : orig_col); + bool has_preinsert = ins_compl_preinsert_effect(); + if (has_preinsert) { + col = compl_col + (int)ins_compl_leader_len() - compl_length; + curwin->w_cursor.col = compl_ins_end_col; + } + if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { return; @@ -3713,15 +3749,24 @@ void ins_compl_delete(bool new_leader) /// Insert the new text being completed. /// "in_compl_func" is true when called from complete_check(). -void ins_compl_insert(bool in_compl_func) +/// "move_cursor" is used when 'completeopt' includes "preinsert" and when true +/// cursor needs to move back from the inserted text to the compl_leader. +void ins_compl_insert(bool in_compl_func, bool move_cursor) { int compl_len = get_compl_len(); + bool preinsert = ins_compl_has_preinsert(); + char *str = compl_shown_match->cp_str.data; + size_t leader_len = ins_compl_leader_len(); + // Make sure we don't go over the end of the string, this can happen with // illegal bytes. if (compl_len < (int)compl_shown_match->cp_str.size) { - ins_compl_insert_bytes(compl_shown_match->cp_str.data + compl_len, -1); + ins_compl_insert_bytes(str + compl_len, -1); + if (preinsert && move_cursor) { + curwin->w_cursor.col -= (colnr_T)(strlen(str) - leader_len); + } } - compl_used_match = !match_at_original_text(compl_shown_match); + compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert); dict_T *dict = ins_compl_dict_alloc(compl_shown_match); set_vim_var_dict(VV_COMPLETED_ITEM, dict); @@ -3923,6 +3968,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match unsigned cur_cot_flags = get_cot_flags(); bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0; bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; + bool compl_preinsert = ins_compl_has_preinsert(); // When user complete function return -1 for findstart which is next // time of 'always', compl_shown_match become NULL. @@ -3967,13 +4013,13 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Insert the text of the new completion, or the compl_leader. - if (compl_no_insert && !started) { + if (compl_no_insert && !started && !compl_preinsert) { ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1); compl_used_match = false; restore_orig_extmarks(); } else if (insert_match) { if (!compl_get_longest || compl_used_match) { - ins_compl_insert(in_compl_func); + ins_compl_insert(in_compl_func, true); } else { assert(compl_leader.data != NULL); ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); diff --git a/src/nvim/options.lua b/src/nvim/options.lua index c6f5221c8b..fdd5799b46 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1494,6 +1494,7 @@ local options = { 'noselect', 'fuzzy', 'nosort', + 'preinsert', }, flags = true, deny_duplicates = true, @@ -1542,6 +1543,12 @@ local options = { nosort Disable sorting of completion candidates based on fuzzy scores when "fuzzy" is enabled. Candidates will appear in their original order. + + preinsert + Preinsert the portion of the first candidate word that is + not part of the current completion leader and using the + |hl-ComplMatchIns| highlight group. Does not work when + "fuzzy" is also included. ]=], full_name = 'completeopt', list = 'onecomma', diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index bcb0f3d4c4..f9fcc4ae1b 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -87,7 +87,7 @@ let test_values = { \ ['xxx', 'help,nofile']], \ 'clipboard': [['', 'unnamed'], ['xxx', '\ze*', 'exclude:\\%(']], \ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup', - \ 'noinsert', 'noselect', 'fuzzy', 'menu,longest'], + \ 'noinsert', 'noselect', 'fuzzy', 'preinsert', 'menu,longest'], \ ['xxx', 'menu,,,longest,']], \ 'encoding': [['utf8'], []], \ 'foldcolumn': [[0, 1, 4, 'auto', 'auto:1', 'auto:9'], [-1, 13, 999]], @@ -186,7 +186,7 @@ let test_values = { \ ['xxx']], \ 'concealcursor': [['', 'n', 'v', 'i', 'c', 'nvic'], ['xxx']], "\ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup', - "\ " 'popuphidden', 'noinsert', 'noselect', 'fuzzy', 'menu,longest'], + "\ " 'popuphidden', 'noinsert', 'noselect', 'fuzzy', 'preinsert', 'menu,longest'], "\ " ['xxx', 'menu,,,longest,']], \ 'completeitemalign': [['abbr,kind,menu', 'menu,abbr,kind'], \ ['', 'xxx', 'abbr', 'abbr,menu', 'abbr,menu,kind,abbr', diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 1c63e8f4cc..969d5012f4 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -2964,4 +2964,121 @@ func Test_complete_info_completed() set cot& endfunc +function Test_completeopt_preinsert() + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "fobar"}, #{word: "foobar"}, #{word: "你的"}, #{word: "你好世界"}] + endfunc + set omnifunc=Omni_test + set completeopt=menu,menuone,preinsert + + new + call feedkeys("S\\f", 'tx') + call assert_equal("fobar", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("S\\foo", 'tx') + call assert_equal("foobar", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("S\\foo\\\", 'tx') + call assert_equal("", getline('.')) + call feedkeys("\\", 'tx') + + " delete a character and input new leader + call feedkeys("S\\foo\b", 'tx') + call assert_equal("fobar", getline('.')) + call feedkeys("\\", 'tx') + + " delete preinsert when prepare completion + call feedkeys("S\\f\", 'tx') + call assert_equal("f ", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("S\\你", 'tx') + call assert_equal("你的", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("S\\你好", 'tx') + call assert_equal("你好世界", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("Shello wo\\\\\f", 'tx') + call assert_equal("hello fobar wo", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("Shello wo\\\\\f\", 'tx') + call assert_equal("hello wo", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("Shello wo\\\\\foo", 'tx') + call assert_equal("hello foobar wo", getline('.')) + call feedkeys("\\", 'tx') + + call feedkeys("Shello wo\\\\\foo\b", 'tx') + call assert_equal("hello fobar wo", getline('.')) + call feedkeys("\\", 'tx') + + " confrim + call feedkeys("S\\f\", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + " cancel + call feedkeys("S\\fo\", 'tx') + call assert_equal("fo", getline('.')) + call assert_equal(2, col('.')) + + call feedkeys("S hello hero\h\\", 'tx') + call assert_equal("hello", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("Sh\\\", 'tx') + call assert_equal("hello", getline('.')) + call assert_equal(5, col('.')) + + " delete preinsert part + call feedkeys("S\\fo ", 'tx') + call assert_equal("fo ", getline('.')) + call assert_equal(3, col('.')) + + " whole line + call feedkeys("Shello hero\\\", 'tx') + call assert_equal("hello hero", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("Shello hero\he\\", 'tx') + call assert_equal("hello hero", getline('.')) + call assert_equal(2, col('.')) + + " can not work with fuzzy + set cot+=fuzzy + call feedkeys("S\\", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + " test for fuzzy and noinsert + set cot+=noinsert + call feedkeys("S\\fb", 'tx') + call assert_equal("fb", getline('.')) + call assert_equal(2, col('.')) + + call feedkeys("S\\你", 'tx') + call assert_equal("你", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("S\\fb\", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + bw! + bw! + set cot& + set omnifunc& + delfunc Omni_test + autocmd! CompleteChanged +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable -- cgit From b3b255396d9fad56c074099b0cdcdbbecab79d4a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 30 Jan 2025 18:59:01 +0800 Subject: vim-patch:9.1.1057: Superfluous cleanup steps in test_ins_complete.vim (#32257) Problem: Superfluous cleanup steps in test_ins_complete.vim. Solution: Remove unnecessary :bw! and :autocmd! commands. Also remove unnecessary STRLEN() in insexpand.c (zeertzjq) closes: vim/vim#16542 https://github.com/vim/vim/commit/8297e2cee337c626c6691e81b25e1f1897c71b86 --- src/nvim/insexpand.c | 9 +++++---- test/old/testdir/test_ins_complete.vim | 6 +----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 73b84175d7..01029ddda2 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3755,15 +3755,16 @@ void ins_compl_insert(bool in_compl_func, bool move_cursor) { int compl_len = get_compl_len(); bool preinsert = ins_compl_has_preinsert(); - char *str = compl_shown_match->cp_str.data; + char *cp_str = compl_shown_match->cp_str.data; + size_t cp_str_len = compl_shown_match->cp_str.size; size_t leader_len = ins_compl_leader_len(); // Make sure we don't go over the end of the string, this can happen with // illegal bytes. - if (compl_len < (int)compl_shown_match->cp_str.size) { - ins_compl_insert_bytes(str + compl_len, -1); + if (compl_len < (int)cp_str_len) { + ins_compl_insert_bytes(cp_str + compl_len, -1); if (preinsert && move_cursor) { - curwin->w_cursor.col -= (colnr_T)(strlen(str) - leader_len); + curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); } } compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert); diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 969d5012f4..64a0ee3124 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -2927,7 +2927,6 @@ func Test_complete_info_matches() \], g:compl_info['items']) call assert_false(has_key(g:compl_info, 'matches')) - bw! bw! unlet g:what delfunc ShownInfo @@ -2958,7 +2957,6 @@ func Test_complete_info_completed() call feedkeys("Go\\\\dd", 'tx') call assert_equal({}, g:compl_info) - bw! bw! delfunc ShownInfo set cot& @@ -3021,7 +3019,7 @@ function Test_completeopt_preinsert() call assert_equal("hello fobar wo", getline('.')) call feedkeys("\\", 'tx') - " confrim + " confirm call feedkeys("S\\f\", 'tx') call assert_equal("fobar", getline('.')) call assert_equal(5, col('.')) @@ -3073,12 +3071,10 @@ function Test_completeopt_preinsert() call assert_equal("fobar", getline('.')) call assert_equal(5, col('.')) - bw! bw! set cot& set omnifunc& delfunc Omni_test - autocmd! CompleteChanged endfunc " vim: shiftwidth=2 sts=2 expandtab nofoldenable -- cgit From 86ae59c6128641d8be612c3bdc9f4b190434c0d3 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 30 Jan 2025 14:21:40 +0200 Subject: fix(colorscheme): distinguish CursorLine/Folded/StatusLineNC highlights #32256 Problem: currently `CursorLine`, `Folded`, `StatusLineNC` highlight groups have the same background color in default color scheme (Grey3). This is a result of optimizing their highlighting to be different from `Normal` but not draw too much attention. However, this design has a usability issue as those groups are sometimes appear together which can make it hard (but not impossible) to differentiate between them. This was previously partially resolved with `StatusLineNC` using Grey1 as background (and thus be different from `CursorLine` but closer to `Normal`), but optimizing more towards it being a visible separator between windows was decided to be more important. Solution: make `Folded` use Grey1 and `StatusLineNC` use Grey4. This makes all three highlight groups have different backgrounds with the following consequences: - Folds now have the same background as floating windows. It makes them there differentiable only by the value of 'foldtext' (which is usually enough). Optimizing for the case "cursor line is next to the fold" seems to be more useful than for the case "make folds in floating window differ by background". - Statusline of inactive windows now draw more attention to them. The benefits are that they are different from cursor column and are better window separators. - Inactive tabline (both `TabLine` and `TabLineFill`) now also draws a bit more attention to it (as they are linked to `StatusLineNC`) but with the benefit of also being different from `CursorLine`. --- src/nvim/highlight_group.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index f3c0d66bb8..901d2c84bc 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -364,7 +364,7 @@ static const char *highlight_init_light[] = { "ErrorMsg guifg=NvimDarkRed ctermfg=1", "FloatShadow guibg=NvimLightGrey4 ctermbg=0 blend=80", "FloatShadowThrough guibg=NvimLightGrey4 ctermbg=0 blend=100", - "Folded guifg=NvimDarkGrey4 guibg=NvimLightGrey3", + "Folded guifg=NvimDarkGrey4 guibg=NvimLightGrey1", "LineNr guifg=NvimLightGrey4", "MatchParen guibg=NvimLightGrey4 gui=bold cterm=bold,underline", "ModeMsg guifg=NvimDarkGreen ctermfg=2", @@ -387,7 +387,7 @@ static const char *highlight_init_light[] = { "SpellLocal guisp=NvimDarkGreen gui=undercurl cterm=undercurl", "SpellRare guisp=NvimDarkCyan gui=undercurl cterm=undercurl", "StatusLine guifg=NvimLightGrey3 guibg=NvimDarkGrey3 cterm=reverse", - "StatusLineNC guifg=NvimDarkGrey3 guibg=NvimLightGrey3 cterm=bold,underline", + "StatusLineNC guifg=NvimDarkGrey2 guibg=NvimLightGrey4 cterm=bold,underline", "Title guifg=NvimDarkGrey2 gui=bold cterm=bold", "Visual guibg=NvimLightGrey4 ctermfg=15 ctermbg=0", "WarningMsg guifg=NvimDarkYellow ctermfg=3", @@ -448,7 +448,7 @@ static const char *highlight_init_dark[] = { "ErrorMsg guifg=NvimLightRed ctermfg=9", "FloatShadow guibg=NvimDarkGrey4 ctermbg=0 blend=80", "FloatShadowThrough guibg=NvimDarkGrey4 ctermbg=0 blend=100", - "Folded guifg=NvimLightGrey4 guibg=NvimDarkGrey3", + "Folded guifg=NvimLightGrey4 guibg=NvimDarkGrey1", "LineNr guifg=NvimDarkGrey4", "MatchParen guibg=NvimDarkGrey4 gui=bold cterm=bold,underline", "ModeMsg guifg=NvimLightGreen ctermfg=10", @@ -471,7 +471,7 @@ static const char *highlight_init_dark[] = { "SpellLocal guisp=NvimLightGreen gui=undercurl cterm=undercurl", "SpellRare guisp=NvimLightCyan gui=undercurl cterm=undercurl", "StatusLine guifg=NvimDarkGrey3 guibg=NvimLightGrey3 cterm=reverse", - "StatusLineNC guifg=NvimLightGrey3 guibg=NvimDarkGrey3 cterm=bold,underline", + "StatusLineNC guifg=NvimLightGrey2 guibg=NvimDarkGrey4 cterm=bold,underline", "Title guifg=NvimLightGrey2 gui=bold cterm=bold", "Visual guibg=NvimDarkGrey4 ctermfg=0 ctermbg=15", "WarningMsg guifg=NvimLightYellow ctermfg=11", -- cgit From e71d2c817d1a2475551f58a98e411f6b39a5be3f Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 13 Jan 2025 15:48:02 +0100 Subject: docs: misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dustin S. Co-authored-by: Ferenc Fejes Co-authored-by: Maria José Solano Co-authored-by: Yochem van Rosmalen Co-authored-by: brianhuster Co-authored-by: zeertzjq --- INSTALL.md | 8 ++++---- runtime/doc/api.txt | 2 +- runtime/doc/gui.txt | 2 +- runtime/doc/lsp.txt | 5 +++-- runtime/doc/starting.txt | 2 +- runtime/doc/treesitter.txt | 13 ++++++++++--- runtime/lua/vim/_defaults.lua | 2 +- runtime/lua/vim/filetype/detect.lua | 2 +- runtime/lua/vim/lsp.lua | 5 +++-- runtime/lua/vim/lsp/buf.lua | 2 +- runtime/lua/vim/treesitter/languagetree.lua | 2 +- src/nvim/api/ui.c | 2 +- src/nvim/event/socket.c | 2 +- src/nvim/ex_getln.c | 2 +- src/nvim/normal.c | 13 ++++++------- src/nvim/os/signal.c | 3 +-- src/nvim/path.c | 2 +- 17 files changed, 38 insertions(+), 31 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 0701bf2720..6c2ec3a273 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -108,20 +108,20 @@ For arm64: The [Releases](https://github.com/neovim/neovim/releases) page provides pre-built binaries for Linux systems. ```sh -curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz +curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz sudo rm -rf /opt/nvim -sudo tar -C /opt -xzf nvim-linux64.tar.gz +sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz ``` Then add this to your shell config (`~/.bashrc`, `~/.zshrc`, ...): - export PATH="$PATH:/opt/nvim-linux64/bin" + export PATH="$PATH:/opt/nvim-linux-x86_64/bin" ### AppImage ("universal" Linux package) The [Releases](https://github.com/neovim/neovim/releases) page provides an [AppImage](https://appimage.org) that runs on most Linux systems. No installation is needed, just download `nvim-linux-x86_64.appimage` and run it. (It might not work if your Linux distribution is more than 4 years old.) The following instructions assume an `x86_64` architecture; on ARM Linux replace with `arm64`. - curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-86_64.appimage + curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.appimage chmod u+x nvim-linux-x86_64.appimage ./nvim-linux-x86_64.appimage diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 07bd7dd192..92f5a261ee 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -422,7 +422,7 @@ after adding them, the returned |extmark| id can be used. >lua See also |vim.hl.range()|. ============================================================================== -Floating windows *api-floatwin* +Floating windows *api-floatwin* *floating-windows* Floating windows ("floats") are displayed on top of normal windows. This is useful to implement simple widgets, such as tooltips displayed next to the diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 6fc5b27580..ecb4de09bb 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -22,7 +22,7 @@ Third-party GUIs *third-party-guis* *vscode* Nvim provides a builtin "terminal UI" (|TUI|), but also works with many (third-party) GUIs which may provide a fresh look or extra features on top of -Nvim. For example, "vscode-neovim" essentally allows you to use VSCode as +Nvim. For example, "vscode-neovim" essentially allows you to use VSCode as a Nvim GUI. - vscode-neovim (Nvim in VSCode!) https://github.com/vscode-neovim/vscode-neovim diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 351f9a56ac..25ef7f24cc 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -882,8 +882,9 @@ foldexpr({lnum}) *vim.lsp.foldexpr()* callback = function(args) local client = vim.lsp.get_client_by_id(args.data.client_id) if client:supports_method('textDocument/foldingRange') then - vim.wo.foldmethod = 'expr' - vim.wo.foldexpr = 'v:lua.vim.lsp.foldexpr()' + local win = vim.api.nvim_get_current_win() + vim.wo[win][0].foldmethod = 'expr' + vim.wo[win][0].foldexpr = 'v:lua.vim.lsp.foldexpr()' end end, }) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index c0254c3fa1..d8d5e42397 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -290,7 +290,7 @@ argument. *-n* -n Disables |swap-file| by setting 'updatecount' to 0 (after executing any |vimrc|). Recovery after a crash will be - impossible. Improves peformance when working with a file on + impossible. Improves performance when working with a file on a very slow medium (usb drive, network share). Enable it again by setting 'updatecount' to some value, e.g. diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index df974d750c..257a6a24e7 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -497,7 +497,7 @@ convention, nodes to be concealed are captured as `@conceal`, but any capture can be used. For example, the following query can be used to hide code block delimiters in Markdown: >query - (fenced_code_block_delimiter @conceal (#set! conceal "")) + ((fenced_code_block_delimiter) @conceal (#set! conceal "")) < It is also possible to replace a node with a single character, which (unlike legacy syntax) can be given a custom highlight. For example, the following @@ -508,6 +508,13 @@ still highlighted the same as other operators: >query < Conceals specified in this way respect 'conceallevel'. +Note that although you can use any string for `conceal`, only the first +character will be used: >query + + ; identifiers will be concealed with 'f'. + ((identifier) @conceal (#set! conceal "foo")) +< + *treesitter-highlight-priority* Treesitter uses |nvim_buf_set_extmark()| to set highlights with a default priority of 100. This enables plugins to set a highlighting priority lower or @@ -1666,8 +1673,8 @@ LanguageTree:parse({range}, {on_parse}) *LanguageTree:parse()* Function invoked when parsing completes. When provided and `vim.g._ts_force_sync_parsing` is not set, parsing will run asynchronously. The first argument to the function is - a string respresenting the error type, in case of a - failure (currently only possible for timeouts). The second + a string representing the error type, in case of a failure + (currently only possible for timeouts). The second argument is the list of trees returned by the parse (upon success), or `nil` if the parse timed out (determined by 'redrawtime'). diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 28f1542f64..69204e3fe6 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -652,7 +652,7 @@ do -- This autocommand updates the value of 'background' anytime we receive -- an OSC 11 response from the terminal emulator. If the user has set - -- 'background' explictly then we will delete this autocommand, + -- 'background' explicitly then we will delete this autocommand, -- effectively disabling automatic background setting. local force = false local id = vim.api.nvim_create_autocmd('TermResponse', { diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 91c0406dc0..fc0b45ecd8 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -34,7 +34,7 @@ local matchregex = vim.filetype._matchregex -- can be detected from the first five lines of the file. --- @type vim.filetype.mapfn function M.asm(path, bufnr) - -- tiasm uses `* commment` + -- tiasm uses `* comment` local lines = table.concat(getlines(bufnr, 1, 10), '\n') if findany(lines, { '^%*', '\n%*', 'Texas Instruments Incorporated' }) then return 'tiasm' diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5226c8ae37..a45f9adeb6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1395,8 +1395,9 @@ end --- callback = function(args) --- local client = vim.lsp.get_client_by_id(args.data.client_id) --- if client:supports_method('textDocument/foldingRange') then ---- vim.wo.foldmethod = 'expr' ---- vim.wo.foldexpr = 'v:lua.vim.lsp.foldexpr()' +--- local win = vim.api.nvim_get_current_win() +--- vim.wo[win][0].foldmethod = 'expr' +--- vim.wo[win][0].foldexpr = 'v:lua.vim.lsp.foldexpr()' --- end --- end, --- }) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 638a0d0f3f..48aa809ebd 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -423,7 +423,7 @@ end --- ---@see vim.lsp.protocol.CompletionTriggerKind function M.completion(context) - vim.depends('vim.lsp.buf.completion', 'vim.lsp.commpletion.trigger', '0.12') + vim.depends('vim.lsp.buf.completion', 'vim.lsp.completion.trigger', '0.12') return lsp.buf_request( 0, ms.textDocument_completion, diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 8ea1c44cdc..1f7872715f 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -523,7 +523,7 @@ end --- only the root tree without injections). --- @param on_parse fun(err?: string, trees?: table)? Function invoked when parsing completes. --- When provided and `vim.g._ts_force_sync_parsing` is not set, parsing will run ---- asynchronously. The first argument to the function is a string respresenting the error type, +--- asynchronously. The first argument to the function is a string representing the error type, --- in case of a failure (currently only possible for timeouts). The second argument is the list --- of trees returned by the parse (upon success), or `nil` if the parse timed out (determined --- by 'redrawtime'). diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index db8a5d93d0..f6f32a73ed 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -537,7 +537,7 @@ static void prepare_call(RemoteUI *ui, const char *name) ui_alloc_buf(ui); } - // To optimize data transfer(especially for "grid_line"), we bundle adjacent + // To optimize data transfer (especially for "grid_line"), we bundle adjacent // calls to same method together, so only add a new call entry if the last // method call is different from "name" diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index 1214c3e336..c340ef2826 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -33,7 +33,7 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint char *host_end = strrchr(addr, ':'); if (host_end && addr != host_end) { - // Split user specified address into two strings, addr(hostname) and port. + // Split user specified address into two strings, addr (hostname) and port. // The port part in watcher->addr will be updated later. *host_end = NUL; char *port = host_end + 1; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index b5fa05e5a4..fc20748309 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2593,7 +2593,7 @@ static bool cmdpreview_may_show(CommandLineState *s) // Place it there in case preview callback flushes it. #30696 cursorcmd(); // Flush now: external cmdline may itself wish to update the screen which is - // currently disallowed during cmdpreview(no longer needed in case that changes). + // currently disallowed during cmdpreview (no longer needed in case that changes). cmdline_ui_flush(); // Swap invalid command range if needed diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 74f851a097..7d0080622d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -97,7 +97,7 @@ typedef struct { bool previous_got_int; // `got_int` was true bool cmdwin; // command-line window normal mode bool noexmode; // true if the normal mode was pushed from - // ex mode(:global or :visual for example) + // ex mode (:global or :visual for example) bool toplevel; // top-level normal mode oparg_T oa; // operator arguments cmdarg_T ca; // command arguments @@ -504,9 +504,9 @@ bool op_pending(void) /// Normal state entry point. This is called on: /// /// - Startup, In this case the function never returns. -/// - The command-line window is opened(`q:`). Returns when `cmdwin_result` != 0. +/// - The command-line window is opened (`q:`). Returns when `cmdwin_result` != 0. /// - The :visual command is called from :global in ex mode, `:global/PAT/visual` -/// for example. Returns when re-entering ex mode(because ex mode recursion is +/// for example. Returns when re-entering ex mode (because ex mode recursion is /// not allowed) /// /// This used to be called main_loop() on main.c @@ -642,8 +642,7 @@ static bool normal_need_redraw_mode_message(NormalState *s) return ( // 'showmode' is set and messages can be printed ((p_smd && msg_silent == 0 - // must restart insert mode(ctrl+o or ctrl+l) or we just entered visual - // mode + // must restart insert mode (ctrl+o or ctrl+l) or just entered visual mode && (restart_edit != 0 || (VIsual_active && s->old_pos.lnum == curwin->w_cursor.lnum && s->old_pos.col == curwin->w_cursor.col)) @@ -6625,8 +6624,8 @@ static void nv_event(cmdarg_T *cap) // `input_get` branch was not executed (!multiqueue_empty(loop.events), which // could have `may_garbage_collect` set to true in `normal_check`). // - // That is because here we may run code that calls `input_get` - // later(`f_confirm` or `get_keystroke` for example), but in these cases it is + // That is because here we may run code that calls `input_get` later + // (`f_confirm` or `get_keystroke` for example), but in these cases it is // not safe to perform garbage collection because there could be unreferenced // lists or dicts being used. may_garbage_collect = false; diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index ecedf144e5..98d13fc9d1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -187,8 +187,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) switch (signum) { #ifdef SIGPWR case SIGPWR: - // Signal of a power failure(eg batteries low), flush the swap files to - // be safe + // Signal of a power failure (eg batteries low), flush the swap files to be safe ml_sync_all(false, false, true); break; #endif diff --git a/src/nvim/path.c b/src/nvim/path.c index 08a63eacd0..3df77571a1 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2073,7 +2073,7 @@ char *path_shorten_fname(char *full_path, char *dir_name) /// @param[in] flags Flags passed to expand_wildcards(). /// /// @returns OK when *file is set to allocated array of matches -/// and *num_file(can be zero) to the number of matches. +/// and *num_file (can be zero) to the number of matches. /// If FAIL is returned, *num_file and *file are either /// unchanged or *num_file is set to 0 and *file is set /// to NULL or points to "". -- cgit From 9cc060218b66c600f7f50ecbec5ba6f1b3a9da82 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 1 Feb 2025 07:49:05 +0800 Subject: vim-patch:9.1.1059: completion: input text deleted with preinsert when adding leader (#32276) Problem: completion: input text deleted with preinsert when adding leader Solution: remove compl_length and check the ptr for being equal to pattern when preinsert is active (glepnir) closes: vim/vim#16545 https://github.com/vim/vim/commit/bfb4eea7869b0118221cd145a774d74191ce6130 Co-authored-by: glepnir --- src/nvim/insexpand.c | 5 +++-- test/old/testdir/test_ins_complete.vim | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 01029ddda2..44e72b2b01 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3469,7 +3469,8 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ int len; char *ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos, &len, &cont_s_ipos); - if (ptr == NULL) { + if (ptr == NULL + || (ins_compl_has_preinsert() && strcmp(ptr, compl_pattern.data) == 0)) { continue; } if (ins_compl_add_infercase(ptr, len, p_ic, @@ -3728,7 +3729,7 @@ void ins_compl_delete(bool new_leader) int col = compl_col + (compl_status_adding() ? compl_length : orig_col); bool has_preinsert = ins_compl_preinsert_effect(); if (has_preinsert) { - col = compl_col + (int)ins_compl_leader_len() - compl_length; + col += (int)ins_compl_leader_len(); curwin->w_cursor.col = compl_ins_end_col; } diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 64a0ee3124..f276d1e719 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -3051,6 +3051,10 @@ function Test_completeopt_preinsert() call assert_equal("hello hero", getline('.')) call assert_equal(2, col('.')) + call feedkeys("Shello hero\h\\er", 'tx') + call assert_equal("hero", getline('.')) + call assert_equal(3, col('.')) + " can not work with fuzzy set cot+=fuzzy call feedkeys("S\\", 'tx') -- cgit From f3381a8b64befece6055f1993b7bff029f281e02 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 1 Feb 2025 09:57:36 +0100 Subject: build(deps): bump tree-sitter to v0.25.0 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 4f3fcc69d0..610b313c03 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -50,8 +50,8 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/9515be4fc16d3dfe6fba5ae4a7a058f32bcf535d.tar.gz -TREESITTER_SHA256 cbdea399736b55d61cfb581bc8d80620d487f4ec8f8d60b7fe00406e39a98d6d +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.0.tar.gz +TREESITTER_SHA256 3729e98e54a41a4c03f4f6c0be580d14ed88c113d75acf15397b16e05cf91129 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v29.0.1.tar.gz WASMTIME_SHA256 b94b6c6fd6aebaf05d4c69c1b12b5dc217b0d42c1a95f435b33af63dddfa5304 -- cgit From 096ae3bfd7075dce69c70182ccedcd6d33e66d31 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 30 Jan 2025 13:34:46 -0800 Subject: fix(treesitter): nil access when running string parser async --- runtime/lua/vim/treesitter/languagetree.lua | 23 ++++++++++++++--------- test/functional/treesitter/parser_spec.lua | 9 +++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 1f7872715f..4e4da5a5ec 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -476,21 +476,26 @@ function LanguageTree:_async_parse(range, on_parse) end local source = self._source - local buf = vim.b[source] - local ct = buf.changedtick + local is_buffer_parser = type(source) == 'number' + local buf = is_buffer_parser and vim.b[source] or nil + local ct = is_buffer_parser and buf.changedtick or nil local total_parse_time = 0 local redrawtime = vim.o.redrawtime local timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil local function step() - if type(source) == 'number' and not vim.api.nvim_buf_is_valid(source) then - return nil - end + if is_buffer_parser then + if + not vim.api.nvim_buf_is_valid(source --[[@as number]]) + then + return nil + end - -- If buffer was changed in the middle of parsing, reset parse state - if buf.changedtick ~= ct then - ct = buf.changedtick - total_parse_time = 0 + -- If buffer was changed in the middle of parsing, reset parse state + if buf.changedtick ~= ct then + ct = buf.changedtick + total_parse_time = 0 + end end local parse_time, trees, finished = tcall(self._parse, self, range, timeout) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 825c2b8f29..a6d3a340f7 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -504,6 +504,15 @@ end]] eq({ 0, 0, 0, 13 }, ret) end) + it('can run async parses with string parsers', function() + local ret = exec_lua(function() + local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c') + return { parser:parse(nil, function() end)[1]:root():range() } + end) + + eq({ 0, 0, 0, 13 }, ret) + end) + it('allows to run queries with string parsers', function() local txt = [[ int foo = 42; -- cgit From 0985e784d8dce58748343207e176bf61303b7d68 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 07:00:45 +0800 Subject: vim-patch:9.1.1065: no digraph for "Approaches the limit" (#32289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: no digraph for "Approaches the limit" Solution: Add the digraph using .= (Hans Ginzel) Add digraph Approaches the Limit ≐ U+2250 https://www.fileformat.info/info/unicode/char/2250/index.htm closes: vim/vim#16508 https://github.com/vim/vim/commit/3a621188ee52badfe7aa783db12588a78dcd8ed6 Co-authored-by: Hans Ginzel --- runtime/doc/digraph.txt | 5 +++-- src/nvim/digraph.c | 1 + test/old/testdir/test_digraph.vim | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt index 1e91e5e4b8..13df8ad4ac 100644 --- a/runtime/doc/digraph.txt +++ b/runtime/doc/digraph.txt @@ -119,8 +119,8 @@ see them. On most systems Vim uses the same digraphs. They work for the Unicode and ISO-8859-1 character sets. These default digraphs are taken from the RFC1345 -mnemonics. To make it easy to remember the mnemonic, the second character has -a standard meaning: +mnemonics (with some additions). To make it easy to remember the mnemonic, +the second character has a standard meaning: char name char meaning ~ Exclamation mark ! Grave @@ -1064,6 +1064,7 @@ the 1', 2' and 3' digraphs. ≅ ?= 2245 8773 APPROXIMATELY EQUAL TO ≈ ?2 2248 8776 ALMOST EQUAL TO ≌ =? 224C 8780 ALL EQUAL TO + ≐ .= 2250 8784 APPROACHES THE LIMIT ≓ HI 2253 8787 IMAGE OF OR APPROXIMATELY EQUAL TO ≠ != 2260 8800 NOT EQUAL TO ≡ =3 2261 8801 IDENTICAL TO diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 326e929fb6..f32123e686 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1023,6 +1023,7 @@ static digr_T digraphdefault[] = { '?', '=', 0x2245 }, { '?', '2', 0x2248 }, { '=', '?', 0x224c }, + { '.', '=', 0x2250 }, { 'H', 'I', 0x2253 }, { '!', '=', 0x2260 }, { '=', '3', 0x2261 }, diff --git a/test/old/testdir/test_digraph.vim b/test/old/testdir/test_digraph.vim index 9c32b85f61..0a71cbba99 100644 --- a/test/old/testdir/test_digraph.vim +++ b/test/old/testdir/test_digraph.vim @@ -40,6 +40,9 @@ func Test_digraphs() " Quadruple prime call Put_Dig("'4") call assert_equal("⁗", getline('.')) + " APPROACHES THE LIMIT + call Put_Dig(".=") + call assert_equal("≐", getline('.')) " Not a digraph call Put_Dig("a\") call Put_Dig("\a") -- cgit From 289c9d21cb91ec6c47496230ca49eef42a04250c Mon Sep 17 00:00:00 2001 From: Felipe Vicentin <42344207+Vinschers@users.noreply.github.com> Date: Sun, 2 Feb 2025 01:25:38 +0100 Subject: fix(autocmds): once=true Lua event-handler may call itself #29544 Problem: Event handler declared with `once=true` can re-trigger itself (i.e. more than once!) by calling `nvim_exec_autocmds` or `:doautocmd`. Analysis: This happens because the callback is executed before deletion/cleanup (`aucmd_del`). And calling `aucmd_del` before `call_autocmd_callback` breaks the autocmd execution... Solution: Set `ac->pat=NULL` to temporarily "delete" the autocmd, then restore it after executing the callback. Fix #25526 Co-authored-by: Justin M. Keyes --- src/nvim/autocmd.c | 38 +++++++++++++++++++++----------- test/functional/autocmd/autocmd_spec.lua | 24 +++++++++++++++++--- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index eb7c8c2880..6a7cf1c9a3 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2025,7 +2025,8 @@ static void aucmd_next(AutoPatCmd *apc) apc->auidx = SIZE_MAX; } -static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) +/// Executes an autocmd callback function (as opposed to an Ex command). +static bool au_callback(const AutoCmd *ac, const AutoPatCmd *apc) { Callback callback = ac->exec.callable.cb; if (callback.type == kCallbackLua) { @@ -2106,16 +2107,24 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) apc->script_ctx = current_sctx; char *retval; - if (ac->exec.type == CALLABLE_CB) { - // Can potentially reallocate kvec_t data and invalidate the ac pointer - if (call_autocmd_callback(ac, apc)) { - // If an autocommand callback returns true, delete the autocommand - oneshot = true; - } - - // TODO(tjdevries): - // - // Major Hack Alert: + switch (ac->exec.type) { + case CALLABLE_EX: + retval = xstrdup(ac->exec.callable.cmd); + break; + case CALLABLE_CB: { + AutoCmd ac_copy = *ac; + // Mark oneshot handler as "removed" now, to prevent recursion by e.g. `:doautocmd`. #25526 + ac->pat = oneshot ? NULL : ac->pat; + // May reallocate `acs` kvec_t data and invalidate the `ac` pointer. + bool rv = au_callback(&ac_copy, apc); + if (oneshot) { + // Restore `pat`. Use `acs` because `ac` may have been invalidated by the callback. + kv_A(*acs, apc->auidx).pat = ac_copy.pat; + } + // If an autocommand callback returns true, delete the autocommand + oneshot = oneshot || rv; + + // HACK(tjdevries): // We just return "not-null" and continue going. // This would be a good candidate for a refactor. You would need to refactor: // 1. do_cmdline to accept something besides a string @@ -2124,8 +2133,11 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) // and instead we loop over all the matches and just execute one-by-one. // However, my expectation would be that could be expensive. retval = xcalloc(1, 1); - } else { - retval = xstrdup(ac->exec.callable.cmd); + break; + } + case CALLABLE_NONE: + default: + abort(); } // Remove one-shot ("once") autocmd in anticipation of its execution. diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index c62e4752e0..1b7275ebf6 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -160,7 +160,7 @@ describe('autocmd', function() it('++once', function() -- :help autocmd-once -- - -- ":autocmd ... ++once" executes its handler once, then removes the handler. + -- ":autocmd … ++once" executes its handler once, then removes the handler. -- local expected = { 'Many1', @@ -206,7 +206,7 @@ describe('autocmd', function() ) -- - -- ":autocmd ... ++once" handlers can be deleted. + -- ":autocmd … ++once" handlers can be deleted. -- expected = {} command('let g:foo = []') @@ -216,7 +216,7 @@ describe('autocmd', function() eq(expected, eval('g:foo')) -- - -- ":autocmd ... ++once ++nested" + -- ":autocmd … ++once ++nested" -- expected = { 'OptionSet-Once', @@ -250,6 +250,24 @@ describe('autocmd', function() --- Autocommands ---]]), fn.execute('autocmd Tabnew') ) + + -- + -- :autocmd does not recursively call ++once Lua handlers. + -- + exec_lua [[vim.g.count = 0]] + eq(0, eval('g:count')) + exec_lua [[ + vim.api.nvim_create_autocmd('User', { + once = true, + pattern = nil, + callback = function() + vim.g.count = vim.g.count + 1 + vim.api.nvim_exec_autocmds('User', { pattern = nil }) + end, + }) + vim.api.nvim_exec_autocmds('User', { pattern = nil }) + ]] + eq(1, eval('g:count')) end) it('internal `aucmd_win` window', function() -- cgit From a857b251d123112eda78945e163fe7fd0438ff59 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 14:24:38 +0800 Subject: vim-patch: port some userfunc.c refactorings from Vim (#32292) Port one_function_arg() and get_function_body() from Vim. vim-patch:8.2.2865: skipping over function body fails Problem: Skipping over function body fails. Solution: Do not define the function when skipping. https://github.com/vim/vim/commit/d87c21a918d8d611750f22d68fc638bf7a79b1d5 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 764 +++++++++++++++++++++++++---------------------- 1 file changed, 401 insertions(+), 363 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index a24a3d5622..2e549fcf37 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -104,6 +104,48 @@ hashtab_T *func_tbl_get(void) return &func_hashtab; } +/// Get one function argument. +/// Return a pointer to after the type. +/// When something is wrong return "arg". +static char *one_function_arg(char *arg, garray_T *newargs, bool skip) +{ + char *p = arg; + + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit((uint8_t)(*arg)) + || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) + || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { + if (!skip) { + semsg(_("E125: Illegal argument: %s"), arg); + } + return arg; + } + + if (newargs != NULL) { + ga_grow(newargs, 1); + uint8_t c = (uint8_t)(*p); + *p = NUL; + char *arg_copy = xstrdup(arg); + + // Check for duplicate argument name. + for (int i = 0; i < newargs->ga_len; i++) { + if (strcmp(((char **)(newargs->ga_data))[i], arg_copy) == 0) { + semsg(_("E853: Duplicate argument name: %s"), arg_copy); + xfree(arg_copy); + return arg; + } + } + ((char **)(newargs->ga_data))[newargs->ga_len] = arg_copy; + newargs->ga_len++; + + *p = (char)c; + } + + return p; +} + /// Get function arguments. static int get_function_args(char **argp, char endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) @@ -134,36 +176,11 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * mustend = true; } else { arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') { - p++; - } - if (arg == p || isdigit((uint8_t)(*arg)) - || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) - || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { - if (!skip) { - semsg(_("E125: Illegal argument: %s"), arg); - } + p = one_function_arg(p, newargs, skip); + if (p == arg) { break; } - if (newargs != NULL) { - ga_grow(newargs, 1); - uint8_t c = (uint8_t)(*p); - *p = NUL; - arg = xstrdup(arg); - - // Check for duplicate argument name. - for (int i = 0; i < newargs->ga_len; i++) { - if (strcmp(((char **)(newargs->ga_data))[i], arg) == 0) { - semsg(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto err_ret; - } - } - ((char **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - *p = (char)c; - } if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; @@ -2186,8 +2203,6 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) return saved; } -#define MAX_FUNC_NESTING 50 - /// List functions. /// /// @param regmatch When NULL, all of them. @@ -2218,349 +2233,100 @@ static void list_functions(regmatch_T *regmatch) } } -/// ":function" -void ex_function(exarg_T *eap) +#define MAX_FUNC_NESTING 50 + +/// Read the body of a function, put every line in "newlines". +/// This stops at "endfunction". +/// "newlines" must already have been initialized. +static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in, + char **line_to_free, bool show_block) { - char *theline; - char *line_to_free = NULL; bool saved_wait_return = need_wait_return; - char *arg; - char *line_arg = NULL; - garray_T newargs; - garray_T default_args; - garray_T newlines; - int varargs = false; - int flags = 0; - ufunc_T *fp; - bool overwrite = false; - funcdict_T fudi; - static int func_nr = 0; // number for nameless function - hashtab_T *ht; - bool is_heredoc = false; + char *line_arg = line_arg_in; + int indent = 2; + int nesting = 0; char *skip_until = NULL; + int ret = FAIL; + bool is_heredoc = false; char *heredoc_trimmed = NULL; - bool show_block = false; bool do_concat = true; - // ":function" without argument: list functions. - if (ends_excmd(*eap->arg)) { - if (!eap->skip) { - list_functions(NULL); + while (true) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; } - eap->nextcmd = check_nextcmd(eap->arg); - return; - } + need_wait_return = false; - // ":function /pat": list functions matching pattern. - if (*eap->arg == '/') { - char *p = skip_regexp(eap->arg + 1, '/', true); - if (!eap->skip) { - regmatch_T regmatch; + char *theline; + char *p; + char *arg; - char c = *p; - *p = NUL; - regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); - *p = c; - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - list_functions(®match); - vim_regfree(regmatch.regprog); + if (line_arg != NULL) { + // Use eap->arg, split up in parts by line breaks. + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) { + line_arg += strlen(line_arg); + } else { + *p = NUL; + line_arg = p + 1; } - } - if (*p == '/') { - p++; - } - eap->nextcmd = check_nextcmd(p); - return; - } - - // Get the function name. There are these situations: - // func function name - // "name" == func, "fudi.fd_dict" == NULL - // dict.func new dictionary entry - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func - // dict.func existing dict entry with a Funcref - // "name" == func, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // dict.func existing dict entry that's not a Funcref - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // s:func script-local function name - // g:func global function name, same as "func" - char *p = eap->arg; - char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); - int paren = (vim_strchr(p, '(') != NULL); - if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - // Return on an invalid expression in braces, unless the expression - // evaluation has been cancelled due to an aborting error, an - // interrupt, or an exception. - if (!aborting()) { - if (fudi.fd_newkey != NULL) { - semsg(_(e_dictkey), fudi.fd_newkey); + } else { + xfree(*line_to_free); + if (eap->ea_getline == NULL) { + theline = getcmdline(':', 0, indent, do_concat); + } else { + theline = eap->ea_getline(':', eap->cookie, indent, do_concat); } - xfree(fudi.fd_newkey); - return; - } - eap->skip = true; - } - - // An error in a function call during evaluation of an expression in magic - // braces should not cause the function not to be defined. - const int saved_did_emsg = did_emsg; - did_emsg = false; - - // - // ":function func" with only function name: list function. - // If bang is given: - // - include "!" in function head - // - exclude line numbers from function body - // - if (!paren) { - if (!ends_excmd(*skipwhite(p))) { - semsg(_(e_trailing_arg), p); - goto ret_free; + *line_to_free = theline; } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) { - *p = NUL; + if (KeyTyped) { + lines_left = Rows - 1; } - if (!eap->skip && !got_int) { - fp = find_func(name); - if (fp != NULL) { - // Check no function was added or removed from a callback, e.g. at - // the more prompt. "fp" may then be invalid. - const int prev_ht_changed = func_hashtab.ht_changed; - - if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) { - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum(j + 1); - if (j < 9) { - msg_putchar(' '); - } - if (j < 99) { - msg_putchar(' '); - } - if (function_list_modified(prev_ht_changed)) { - break; - } - } - msg_prt_line(FUNCLINE(fp, j), false); - line_breakcheck(); // show multiple lines at a time! - } - if (!got_int) { - msg_putchar('\n'); - if (!function_list_modified(prev_ht_changed)) { - msg_puts(eap->forceit ? "endfunction" : " endfunction"); - } - } - } + if (theline == NULL) { + if (skip_until != NULL) { + semsg(_(e_missing_heredoc_end_marker_str), skip_until); } else { - emsg_funcname(N_("E123: Undefined function: %s"), name); + emsg(_("E126: Missing :endfunction")); } + goto theend; } - goto ret_free; - } - - // ":function name(arg1, arg2)" Define function. - p = skipwhite(p); - if (*p != '(') { - if (!eap->skip) { - semsg(_("E124: Missing '(': %s"), eap->arg); - goto ret_free; - } - // attempt to continue by skipping some text - if (vim_strchr(p, '(') != NULL) { - p = vim_strchr(p, '('); + if (show_block) { + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, theline); } - } - p = skipwhite(p + 1); - - ga_init(&newargs, (int)sizeof(char *), 3); - ga_init(&newlines, (int)sizeof(char *), 3); - if (!eap->skip) { - // Check the name of the function. Unless it's a dictionary function - // (that we are overwriting). - if (name != NULL) { - arg = name; + // Detect line continuation: SOURCING_LNUM increased more than one. + linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie); + if (SOURCING_LNUM < sourcing_lnum_off) { + sourcing_lnum_off -= SOURCING_LNUM; } else { - arg = fudi.fd_newkey; + sourcing_lnum_off = 0; } - if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { - char *name_base = arg; - if ((uint8_t)(*arg) == K_SPECIAL) { - name_base = vim_strchr(arg, '_'); - if (name_base == NULL) { - name_base = arg + 3; + + if (skip_until != NULL) { + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python <dv_scope == VAR_DEF_SCOPE) { - emsg(_("E862: Cannot use g: here")); - goto ret_free; - } - } - - if (get_function_args(&p, ')', &newargs, &varargs, - &default_args, eap->skip) == FAIL) { - goto errret_2; - } - - if (KeyTyped && ui_has(kUICmdline)) { - show_block = true; - ui_ext_cmdline_block_append(0, eap->cmd); - } - - // find extra arguments "range", "dict", "abort" and "closure" - while (true) { - p = skipwhite(p); - if (strncmp(p, "range", 5) == 0) { - flags |= FC_RANGE; - p += 5; - } else if (strncmp(p, "dict", 4) == 0) { - flags |= FC_DICT; - p += 4; - } else if (strncmp(p, "abort", 5) == 0) { - flags |= FC_ABORT; - p += 5; - } else if (strncmp(p, "closure", 7) == 0) { - flags |= FC_CLOSURE; - p += 7; - if (current_funccal == NULL) { - emsg_funcname(N_("E932: Closure function should not be at top level: %s"), - name == NULL ? "" : name); - goto erret; - } - } else { - break; - } - } - - // When there is a line break use what follows for the function body. - // Makes 'exe "func Test()\n...\nendfunc"' work. - if (*p == '\n') { - line_arg = p + 1; - } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { - semsg(_(e_trailing_arg), p); - } - - // Read the body of the function, until ":endfunction" is found. - if (KeyTyped) { - // Check if the function already exists, don't let the user type the - // whole function before telling them it doesn't work! For a script we - // need to skip the body to be able to find what follows. - if (!eap->skip && !eap->forceit) { - if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) { - emsg(_(e_funcdict)); - } else if (name != NULL && find_func(name) != NULL) { - emsg_funcname(e_funcexts, name); - } - } - - if (!eap->skip && did_emsg) { - goto erret; - } - - if (!ui_has(kUICmdline)) { - msg_putchar('\n'); // don't overwrite the function name - } - cmdline_row = msg_row; - } - - // Save the starting line number. - linenr_T sourcing_lnum_top = SOURCING_LNUM; - - int indent = 2; - int nesting = 0; - while (true) { - if (KeyTyped) { - msg_scroll = true; - saved_wait_return = false; - } - need_wait_return = false; - - if (line_arg != NULL) { - // Use eap->arg, split up in parts by line breaks. - theline = line_arg; - p = vim_strchr(theline, '\n'); - if (p == NULL) { - line_arg += strlen(line_arg); - } else { - *p = NUL; - line_arg = p + 1; - } - } else { - xfree(line_to_free); - if (eap->ea_getline == NULL) { - theline = getcmdline(':', 0, indent, do_concat); - } else { - theline = eap->ea_getline(':', eap->cookie, indent, do_concat); - } - line_to_free = theline; - } - if (KeyTyped) { - lines_left = Rows - 1; - } - if (theline == NULL) { - if (skip_until != NULL) { - semsg(_(e_missing_heredoc_end_marker_str), skip_until); - } else { - emsg(_("E126: Missing :endfunction")); - } - goto erret; - } - if (show_block) { - assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, theline); - } - - // Detect line continuation: SOURCING_LNUM increased more than one. - linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie); - if (SOURCING_LNUM < sourcing_lnum_off) { - sourcing_lnum_off -= SOURCING_LNUM; - } else { - sourcing_lnum_off = 0; - } - - if (skip_until != NULL) { - // Don't check for ":endfunc" between - // * ":append" and "." - // * ":python <cmdlinep". eap->nextcmd = nextcmd; - if (line_to_free != NULL) { + if (*line_to_free != NULL) { xfree(*eap->cmdlinep); - *eap->cmdlinep = line_to_free; - line_to_free = NULL; + *eap->cmdlinep = *line_to_free; + *line_to_free = NULL; } } break; @@ -2705,18 +2471,18 @@ void ex_function(exarg_T *eap) } // Add the line to the function. - ga_grow(&newlines, 1 + (int)sourcing_lnum_off); + ga_grow(newlines, 1 + (int)sourcing_lnum_off); // Copy the line to newly allocated memory. get_one_sourceline() // allocates 250 bytes per line, this saves 80% on average. The cost // is an extra alloc/free. p = xstrdup(theline); - ((char **)(newlines.ga_data))[newlines.ga_len++] = p; + ((char **)(newlines->ga_data))[newlines->ga_len++] = p; // Add NULL lines for continuation lines, so that the line count is // equal to the index in the growarray. while (sourcing_lnum_off-- > 0) { - ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL; + ((char **)(newlines->ga_data))[newlines->ga_len++] = NULL; } // Check for end of eap->arg. @@ -2725,9 +2491,284 @@ void ex_function(exarg_T *eap) } } - // Don't define the function when skipping commands or when an error was - // detected. - if (eap->skip || did_emsg) { + // Return OK when no error was detected. + if (!did_emsg) { + ret = OK; + } + +theend: + xfree(skip_until); + xfree(heredoc_trimmed); + need_wait_return |= saved_wait_return; + return ret; +} + +/// ":function" +void ex_function(exarg_T *eap) +{ + char *line_to_free = NULL; + char *arg; + char *line_arg = NULL; + garray_T newargs; + garray_T default_args; + garray_T newlines; + int varargs = false; + int flags = 0; + ufunc_T *fp; + bool overwrite = false; + funcdict_T fudi; + static int func_nr = 0; // number for nameless function + hashtab_T *ht; + bool show_block = false; + + // ":function" without argument: list functions. + if (ends_excmd(*eap->arg)) { + if (!eap->skip) { + list_functions(NULL); + } + eap->nextcmd = check_nextcmd(eap->arg); + return; + } + + // ":function /pat": list functions matching pattern. + if (*eap->arg == '/') { + char *p = skip_regexp(eap->arg + 1, '/', true); + if (!eap->skip) { + regmatch_T regmatch; + + char c = *p; + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); + *p = c; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + list_functions(®match); + vim_regfree(regmatch.regprog); + } + } + if (*p == '/') { + p++; + } + eap->nextcmd = check_nextcmd(p); + return; + } + + // Get the function name. There are these situations: + // func function name + // "name" == func, "fudi.fd_dict" == NULL + // dict.func new dictionary entry + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func + // dict.func existing dict entry with a Funcref + // "name" == func, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // dict.func existing dict entry that's not a Funcref + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // s:func script-local function name + // g:func global function name, same as "func" + char *p = eap->arg; + char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); + int paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { + // Return on an invalid expression in braces, unless the expression + // evaluation has been cancelled due to an aborting error, an + // interrupt, or an exception. + if (!aborting()) { + if (fudi.fd_newkey != NULL) { + semsg(_(e_dictkey), fudi.fd_newkey); + } + xfree(fudi.fd_newkey); + return; + } + eap->skip = true; + } + + // An error in a function call during evaluation of an expression in magic + // braces should not cause the function not to be defined. + const int saved_did_emsg = did_emsg; + did_emsg = false; + + // + // ":function func" with only function name: list function. + // If bang is given: + // - include "!" in function head + // - exclude line numbers from function body + // + if (!paren) { + if (!ends_excmd(*skipwhite(p))) { + semsg(_(e_trailing_arg), p); + goto ret_free; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) { + *p = NUL; + } + if (!eap->skip && !got_int) { + fp = find_func(name); + if (fp != NULL) { + // Check no function was added or removed from a callback, e.g. at + // the more prompt. "fp" may then be invalid. + const int prev_ht_changed = func_hashtab.ht_changed; + + if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) { + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; + } + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum(j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + if (function_list_modified(prev_ht_changed)) { + break; + } + } + msg_prt_line(FUNCLINE(fp, j), false); + line_breakcheck(); // show multiple lines at a time! + } + if (!got_int) { + msg_putchar('\n'); + if (!function_list_modified(prev_ht_changed)) { + msg_puts(eap->forceit ? "endfunction" : " endfunction"); + } + } + } + } else { + emsg_funcname(N_("E123: Undefined function: %s"), name); + } + } + goto ret_free; + } + + // ":function name(arg1, arg2)" Define function. + p = skipwhite(p); + if (*p != '(') { + if (!eap->skip) { + semsg(_("E124: Missing '(': %s"), eap->arg); + goto ret_free; + } + // attempt to continue by skipping some text + if (vim_strchr(p, '(') != NULL) { + p = vim_strchr(p, '('); + } + } + p = skipwhite(p + 1); + + ga_init(&newargs, (int)sizeof(char *), 3); + ga_init(&newlines, (int)sizeof(char *), 3); + + if (!eap->skip) { + // Check the name of the function. Unless it's a dictionary function + // (that we are overwriting). + if (name != NULL) { + arg = name; + } else { + arg = fudi.fd_newkey; + } + if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { + char *name_base = arg; + if ((uint8_t)(*arg) == K_SPECIAL) { + name_base = vim_strchr(arg, '_'); + if (name_base == NULL) { + name_base = arg + 3; + } else { + name_base++; + } + } + int i; + for (i = 0; name_base[i] != NUL && (i == 0 + ? eval_isnamec1(name_base[i]) + : eval_isnamec(name_base[i])); i++) {} + if (name_base[i] != NUL) { + emsg_funcname(e_invarg2, arg); + goto ret_free; + } + } + // Disallow using the g: dict. + if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) { + emsg(_("E862: Cannot use g: here")); + goto ret_free; + } + } + + if (get_function_args(&p, ')', &newargs, &varargs, + &default_args, eap->skip) == FAIL) { + goto errret_2; + } + + if (KeyTyped && ui_has(kUICmdline)) { + show_block = true; + ui_ext_cmdline_block_append(0, eap->cmd); + } + + // find extra arguments "range", "dict", "abort" and "closure" + while (true) { + p = skipwhite(p); + if (strncmp(p, "range", 5) == 0) { + flags |= FC_RANGE; + p += 5; + } else if (strncmp(p, "dict", 4) == 0) { + flags |= FC_DICT; + p += 4; + } else if (strncmp(p, "abort", 5) == 0) { + flags |= FC_ABORT; + p += 5; + } else if (strncmp(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_("E932: Closure function should not be at top level: %s"), + name == NULL ? "" : name); + goto erret; + } + } else { + break; + } + } + + // When there is a line break use what follows for the function body. + // Makes 'exe "func Test()\n...\nendfunc"' work. + if (*p == '\n') { + line_arg = p + 1; + } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { + semsg(_(e_trailing_arg), p); + } + + // Read the body of the function, until ":endfunction" is found. + if (KeyTyped) { + // Check if the function already exists, don't let the user type the + // whole function before telling them it doesn't work! For a script we + // need to skip the body to be able to find what follows. + if (!eap->skip && !eap->forceit) { + if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) { + emsg(_(e_funcdict)); + } else if (name != NULL && find_func(name) != NULL) { + emsg_funcname(e_funcexts, name); + } + } + + if (!eap->skip && did_emsg) { + goto erret; + } + + if (!ui_has(kUICmdline)) { + msg_putchar('\n'); // don't overwrite the function name + } + cmdline_row = msg_row; + } + + // Save the starting line number. + linenr_T sourcing_lnum_top = SOURCING_LNUM; + + // Do not define the function when getting the body fails and when skipping. + if (get_function_body(eap, &newlines, line_arg, &line_to_free, show_block) == FAIL + || eap->skip) { goto erret; } @@ -2879,13 +2920,10 @@ erret: errret_2: ga_clear_strings(&newlines); ret_free: - xfree(skip_until); - xfree(heredoc_trimmed); xfree(line_to_free); xfree(fudi.fd_newkey); xfree(name); did_emsg |= saved_did_emsg; - need_wait_return |= saved_wait_return; if (show_block) { ui_ext_cmdline_block_leave(); } -- cgit From 0ce0e93bd93d1a203cbb58ab5a029a4260d66798 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 1 Feb 2025 23:43:31 -0800 Subject: refactor(autocmds): remove indirection #32291 Problem: `AucmdExecutable` adds 2 layers of indirection. Although formalizing a `union` is good practice for shared interfaces, this struct is mainly for `autocmd_register` which is internal to this module. Solution: - Store the cmd/fn fields directly on the `AutoCmd` struct. - Drop `AucmdExecutable` and related structures. --- src/nvim/api/autocmd.c | 53 +++++++------------- src/nvim/autocmd.c | 126 ++++++++++++++++-------------------------------- src/nvim/autocmd_defs.h | 3 +- src/nvim/ex_cmds_defs.h | 29 ----------- 4 files changed, 62 insertions(+), 149 deletions(-) diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 1e19e90151..d436dbb7f1 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -290,10 +290,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) PUT_C(autocmd_info, "desc", CSTR_AS_OBJ(ac->desc)); } - if (ac->exec.type == CALLABLE_CB) { + if (ac->handler_cmd) { + PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->handler_cmd)); + } else { PUT_C(autocmd_info, "command", STRING_OBJ(STRING_INIT)); - Callback *cb = &ac->exec.callable.cb; + Callback *cb = &ac->handler_fn; switch (cb->type) { case kCallbackLua: if (nlua_ref_is_function(cb->data.luaref)) { @@ -307,8 +309,6 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) case kCallbackNone: abort(); } - } else { - PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->exec.callable.cmd)); } PUT_C(autocmd_info, "pattern", CSTR_AS_OBJ(ap->pat)); @@ -322,23 +322,6 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) PUT_C(autocmd_info, "buflocal", BOOLEAN_OBJ(false)); } - // TODO(sctx): It would be good to unify script_ctx to actually work with lua - // right now it's just super weird, and never really gives you the info that - // you would expect from this. - // - // I think we should be able to get the line number, filename, etc. from lua - // when we're executing something, and it should be easy to then save that - // info here. - // - // I think it's a big loss not getting line numbers of where options, autocmds, - // etc. are set (just getting "Sourced (lua)" or something is not that helpful. - // - // Once we do that, we can put these into the autocmd_info, but I don't think it's - // useful to do that at this time. - // - // PUT_C(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid)); - // PUT_C(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum)); - kvi_push(autocmd_list, DICT_OBJ(autocmd_info)); } } @@ -411,8 +394,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc { int64_t autocmd_id = -1; char *desc = NULL; - AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; - Callback cb = CALLBACK_NONE; + char *handler_cmd = NULL; + Callback handler_fn = CALLBACK_NONE; Array event_array = unpack_string_or_array(event, "event", true, arena, err); if (ERROR_SET(err)) { @@ -437,13 +420,13 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; }); - cb.type = kCallbackLua; - cb.data.luaref = callback->data.luaref; + handler_fn.type = kCallbackLua; + handler_fn.data.luaref = callback->data.luaref; callback->data.luaref = LUA_NOREF; break; case kObjectTypeString: - cb.type = kCallbackFuncref; - cb.data.funcref = string_to_cstr(callback->data.string); + handler_fn.type = kCallbackFuncref; + handler_fn.data.funcref = string_to_cstr(callback->data.string); break; default: VALIDATE_EXP(false, "callback", "Lua function or Vim function name", @@ -451,12 +434,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; }); } - - aucmd.type = CALLABLE_CB; - aucmd.callable.cb = cb; } else if (HAS_KEY(opts, create_autocmd, command)) { - aucmd.type = CALLABLE_EX; - aucmd.callable.cmd = string_to_cstr(opts->command); + handler_cmd = string_to_cstr(opts->command); } else { VALIDATE(false, "%s", "Required: 'command' or 'callback'", { goto cleanup; @@ -496,7 +475,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc int retval; FOREACH_ITEM(patterns, pat, { - // See: TODO(sctx) WITH_SCRIPT_CONTEXT(channel_id, { retval = autocmd_register(autocmd_id, event_nr, @@ -506,7 +484,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc opts->once, opts->nested, desc, - aucmd); + handler_cmd, + &handler_fn); }); if (retval == FAIL) { @@ -517,7 +496,11 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc }); cleanup: - aucmd_exec_free(&aucmd); + if (handler_cmd) { + XFREE_CLEAR(handler_cmd); + } else { + callback_free(&handler_fn); + } return autocmd_id; } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 6a7cf1c9a3..96da4695de 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -74,7 +74,6 @@ static const char e_autocommand_nesting_too_deep[] // Naming Conventions: // - general autocmd behavior start with au_ // - AutoCmd start with aucmd_ -// - Autocmd.exec stat with aucmd_exec // - AutoPat start with aupat_ // - Groups start with augroup_ // - Events start with event_ @@ -254,24 +253,24 @@ static void au_show_for_event(int group, event_T event, const char *pat) return; } - char *exec_to_string = aucmd_exec_to_string(ac, ac->exec); + char *handler_str = aucmd_handler_to_string(ac); if (ac->desc != NULL) { size_t msglen = 100; char *msg = xmallocz(msglen); - if (ac->exec.type == CALLABLE_CB) { - msg_puts_hl(exec_to_string, HLF_8, false); - snprintf(msg, msglen, " [%s]", ac->desc); + if (ac->handler_cmd) { + snprintf(msg, msglen, "%s [%s]", handler_str, ac->desc); } else { - snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc); + msg_puts_hl(handler_str, HLF_8, false); + snprintf(msg, msglen, " [%s]", ac->desc); } msg_outtrans(msg, 0, false); XFREE_CLEAR(msg); - } else if (ac->exec.type == CALLABLE_CB) { - msg_puts_hl(exec_to_string, HLF_8, false); + } else if (ac->handler_cmd) { + msg_outtrans(handler_str, 0, false); } else { - msg_outtrans(exec_to_string, 0, false); + msg_puts_hl(handler_str, HLF_8, false); } - XFREE_CLEAR(exec_to_string); + XFREE_CLEAR(handler_str); if (p_verbose > 0) { last_set_msg(ac->script_ctx); } @@ -303,7 +302,11 @@ static void aucmd_del(AutoCmd *ac) xfree(ac->pat); } ac->pat = NULL; - aucmd_exec_free(&ac->exec); + if (ac->handler_cmd) { + XFREE_CLEAR(ac->handler_cmd); + } else { + callback_free(&ac->handler_fn); + } XFREE_CLEAR(ac->desc); au_need_clean = true; @@ -899,8 +902,8 @@ void do_all_autocmd_events(const char *pat, bool once, int nested, char *cmd, bo // If *cmd == NUL: show entries. // If forceit == true: delete entries. // If group is not AUGROUP_ALL: only use this group. -int do_autocmd_event(event_T event, const char *pat, bool once, int nested, char *cmd, bool del, - int group) +int do_autocmd_event(event_T event, const char *pat, bool once, int nested, const char *cmd, + bool del, int group) FUNC_ATTR_NONNULL_ALL { // Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events @@ -958,10 +961,8 @@ int do_autocmd_event(event_T event, const char *pat, bool once, int nested, char } if (is_adding_cmd) { - AucmdExecutable exec = AUCMD_EXECUTABLE_INIT; - exec.type = CALLABLE_EX; - exec.callable.cmd = cmd; - autocmd_register(0, event, pat, patlen, group, once, nested, NULL, exec); + Callback handler_fn = CALLBACK_INIT; + autocmd_register(0, event, pat, patlen, group, once, nested, NULL, cmd, &handler_fn); } pat = aucmd_next_pattern(pat, (size_t)patlen); @@ -972,8 +973,13 @@ int do_autocmd_event(event_T event, const char *pat, bool once, int nested, char return OK; } +/// Registers an autocmd. The handler may be a Ex command or callback function, decided by +/// the `handler_cmd` or `handler_fn` args. +/// +/// @param handler_cmd Handler Ex command, or NULL if handler is a function (`handler_fn`). +/// @param handler_fn Handler function, ignored if `handler_cmd` is not NULL. int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int group, bool once, - bool nested, char *desc, AucmdExecutable aucmd) + bool nested, char *desc, const char *handler_cmd, Callback *handler_fn) { // 0 is not a valid group. assert(group != 0); @@ -1081,7 +1087,12 @@ int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int AutoCmd *ac = kv_pushp(autocmds[(int)event]); ac->pat = ap; ac->id = id; - ac->exec = aucmd_exec_copy(aucmd); + if (handler_cmd) { + ac->handler_cmd = xstrdup(handler_cmd); + } else { + ac->handler_cmd = NULL; + callback_copy(&ac->handler_fn, handler_fn); + } ac->script_ctx = current_sctx; ac->script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&ac->script_ctx); @@ -2028,7 +2039,7 @@ static void aucmd_next(AutoPatCmd *apc) /// Executes an autocmd callback function (as opposed to an Ex command). static bool au_callback(const AutoCmd *ac, const AutoPatCmd *apc) { - Callback callback = ac->exec.callable.cb; + Callback callback = ac->handler_fn; if (callback.type == kCallbackLua) { MAXSIZE_TEMP_DICT(data, 7); PUT_C(data, "id", INTEGER_OBJ(ac->id)); @@ -2093,10 +2104,10 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) if (p_verbose >= 9) { verbose_enter_scroll(); - char *exec_to_string = aucmd_exec_to_string(ac, ac->exec); - smsg(0, _("autocommand %s"), exec_to_string); + char *handler_str = aucmd_handler_to_string(ac); + smsg(0, _("autocommand %s"), handler_str); msg_puts("\n"); // don't overwrite this either - XFREE_CLEAR(exec_to_string); + XFREE_CLEAR(handler_str); verbose_leave_scroll(); } @@ -2107,11 +2118,9 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) apc->script_ctx = current_sctx; char *retval; - switch (ac->exec.type) { - case CALLABLE_EX: - retval = xstrdup(ac->exec.callable.cmd); - break; - case CALLABLE_CB: { + if (ac->handler_cmd) { + retval = xstrdup(ac->handler_cmd); + } else { AutoCmd ac_copy = *ac; // Mark oneshot handler as "removed" now, to prevent recursion by e.g. `:doautocmd`. #25526 ac->pat = oneshot ? NULL : ac->pat; @@ -2133,11 +2142,6 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) // and instead we loop over all the matches and just execute one-by-one. // However, my expectation would be that could be expensive. retval = xcalloc(1, 1); - break; - } - case CALLABLE_NONE: - default: - abort(); } // Remove one-shot ("once") autocmd in anticipation of its execution. @@ -2456,60 +2460,14 @@ bool autocmd_delete_id(int64_t id) return success; } -// =========================================================================== -// AucmdExecutable Functions -// =========================================================================== - -/// Generate a string description for the command/callback of an autocmd -char *aucmd_exec_to_string(AutoCmd *ac, AucmdExecutable acc) +/// Gets an (allocated) string representation of an autocmd command/callback. +char *aucmd_handler_to_string(AutoCmd *ac) FUNC_ATTR_PURE { - switch (acc.type) { - case CALLABLE_EX: - return xstrdup(acc.callable.cmd); - case CALLABLE_CB: - return callback_to_string(&acc.callable.cb, NULL); - case CALLABLE_NONE: - return "This is not possible"; + if (ac->handler_cmd) { + return xstrdup(ac->handler_cmd); } - - abort(); -} - -void aucmd_exec_free(AucmdExecutable *acc) -{ - switch (acc->type) { - case CALLABLE_EX: - XFREE_CLEAR(acc->callable.cmd); - break; - case CALLABLE_CB: - callback_free(&acc->callable.cb); - break; - case CALLABLE_NONE: - return; - } - - acc->type = CALLABLE_NONE; -} - -AucmdExecutable aucmd_exec_copy(AucmdExecutable src) -{ - AucmdExecutable dest = AUCMD_EXECUTABLE_INIT; - - switch (src.type) { - case CALLABLE_EX: - dest.type = CALLABLE_EX; - dest.callable.cmd = xstrdup(src.callable.cmd); - return dest; - case CALLABLE_CB: - dest.type = CALLABLE_CB; - callback_copy(&dest.callable.cb, &src.callable.cb); - return dest; - case CALLABLE_NONE: - return dest; - } - - abort(); + return callback_to_string(&ac->handler_fn, NULL); } bool au_event_is_empty(event_T event) diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h index cba947e85f..970aced506 100644 --- a/src/nvim/autocmd_defs.h +++ b/src/nvim/autocmd_defs.h @@ -37,10 +37,11 @@ typedef struct { } AutoPat; typedef struct { - AucmdExecutable exec; ///< Command or callback function AutoPat *pat; ///< Pattern reference (NULL when autocmd was removed) int64_t id; ///< ID used for uniquely tracking an autocmd char *desc; ///< Description for the autocmd + char *handler_cmd; ///< Handler Ex command (NULL if handler is a function). + Callback handler_fn; ///< Handler callback (ignored if `handler_cmd` is not NULL). sctx_T script_ctx; ///< Script context where it is defined bool once; ///< "One shot": removed after execution bool nested; ///< If autocommands nest here diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 4924e86470..9dc922b0d9 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -94,35 +94,6 @@ typedef struct exarg exarg_T; typedef void (*ex_func_T)(exarg_T *eap); typedef int (*ex_preview_func_T)(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr); -// NOTE: These possible could be removed and changed so that -// Callback could take a "command" style string, and simply -// execute that (instead of it being a function). -// -// But it's still a bit weird to do that. -// -// Another option would be that we just make a callback reference to -// "execute($INPUT)" or something like that, so whatever the user -// sends in via autocmds is just executed via this. -// -// However, that would probably have some performance cost (probably -// very marginal, but still some cost either way). -typedef enum { - CALLABLE_NONE, - CALLABLE_EX, - CALLABLE_CB, -} AucmdExecutableType; - -typedef struct aucmd_executable_t AucmdExecutable; -struct aucmd_executable_t { - AucmdExecutableType type; - union { - char *cmd; - Callback cb; - } callable; -}; - -#define AUCMD_EXECUTABLE_INIT { .type = CALLABLE_NONE } - typedef char *(*LineGetter)(int, void *, int, bool); /// Structure for command definition. -- cgit From 718e9ce018572f609a7639865af55476a19847aa Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 17:13:42 +0800 Subject: vim-patch:a5d19aa: runtime(hyprlang): fix string recognition (#32290) fixes: vim/vim#16064 closes: vim/vim#16527 https://github.com/vim/vim/commit/a5d19aa44d97151d572362a24efccbfa09d560ae Co-authored-by: Luca Saccarola --- runtime/syntax/hyprlang.vim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/syntax/hyprlang.vim b/runtime/syntax/hyprlang.vim index f36c58c646..cde504d9ca 100644 --- a/runtime/syntax/hyprlang.vim +++ b/runtime/syntax/hyprlang.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: hyprlang " Maintainer: Luca Saccarola -" Last Change: 2024 nov 15 +" Last Change: 2025 Jan 29 if exists("b:current_syntax") finish @@ -21,7 +21,8 @@ syn region hyprCategory matchgroup=hyprCategoryD start='^\s*\k\+\s*{' end='^\s*} " Variables Types syn match hyprNumber '\%[-+]\<\d\+\>\%[%]' contained syn match hyprFloat '\%[-+]\<\d\+\.\d\+\>\%[%]' contained -syn match hyprString '["\'].*["\']' contained +syn match hyprString "'[^']*'" contained +syn match hyprString '"[^"]*"' contained syn match hyprColor 'rgb(\(\w\|\d\)\{6})' contained syn match hyprColor 'rgba(\(\w\|\d\)\{8})' contained syn match hyprColor '0x\(\w\|\d\)\{8}' contained -- cgit From 4bdabf9b1ae52134f50a0b75dc2c73a40c0c252f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 17:32:51 +0800 Subject: vim-patch:9.1.1068: getchar() can't distinguish between C-I and Tab (#32295) Problem: getchar() can't distinguish between C-I and Tab. Solution: Add {opts} to pass extra flags to getchar() and getcharstr(), with "number" and "simplify" keys. related: vim/vim#10603 closes: vim/vim#16554 https://github.com/vim/vim/commit/e0a2ab397fd13a71efec85b017d5d4d62baf7f63 Cherry-pick tv_dict_has_key() from patch 8.2.4683. --- runtime/doc/builtin.txt | 49 +++++++++++------ runtime/lua/vim/_meta/vimfn.lua | 53 +++++++++++------- src/nvim/eval.lua | 51 +++++++++++------- src/nvim/eval/typval.c | 12 +++++ src/nvim/getchar.c | 63 +++++++++++++--------- test/functional/terminal/buffer_spec.lua | 92 +++++--------------------------- test/old/testdir/test_functions.vim | 71 ++++++++++++++++++++++++ 7 files changed, 233 insertions(+), 158 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index d4a6bbf9d8..3d9010aa2c 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3079,14 +3079,16 @@ getchangelist([{buf}]) *getchangelist()* Return: ~ (`table[]`) -getchar([{expr}]) *getchar()* +getchar([{expr} [, {opts}]]) *getchar()* Get a single character from the user or input stream. - If {expr} is omitted, wait until a character is available. + If {expr} is omitted or is -1, wait until a character is + available. If {expr} is 0, only get a character when one is available. Return zero otherwise. If {expr} is 1, only check if a character is available, it is not consumed. Return zero if no character available. - If you prefer always getting a string use |getcharstr()|. + If you prefer always getting a string use |getcharstr()|, or + specify |FALSE| as "number" in {opts}. Without {expr} and when {expr} is 0 a whole character or special key is returned. If it is a single character, the @@ -3096,7 +3098,8 @@ getchar([{expr}]) *getchar()* starting with 0x80 (decimal: 128). This is the same value as the String "\", e.g., "\". The returned value is also a String when a modifier (shift, control, alt) was used - that is not included in the character. + that is not included in the character. |keytrans()| can also + be used to convert a returned String into a readable form. When {expr} is 0 and Esc is typed, there will be a short delay while Vim waits to see if this is the start of an escape @@ -3108,6 +3111,24 @@ getchar([{expr}]) *getchar()* Use getcharmod() to obtain any additional modifiers. + The optional argument {opts} is a Dict and supports the + following items: + + number If |TRUE|, return a Number when getting + a single character. + If |FALSE|, the return value is always + converted to a String, and an empty + String (instead of 0) is returned when + no character is available. + (default: |TRUE|) + + simplify If |TRUE|, include modifiers in the + character if possible. E.g., return + the same value for CTRL-I and . + If |FALSE|, don't include modifiers in + the character. + (default: |TRUE|) + When the user clicks a mouse button, the mouse event will be returned. The position can then be found in |v:mouse_col|, |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. @@ -3145,10 +3166,11 @@ getchar([{expr}]) *getchar()* < Parameters: ~ - • {expr} (`0|1?`) + • {expr} (`-1|0|1?`) + • {opts} (`table?`) Return: ~ - (`integer`) + (`integer|string`) getcharmod() *getcharmod()* The result is a Number which is the state of the modifiers for @@ -3213,19 +3235,12 @@ getcharsearch() *getcharsearch()* (`table`) getcharstr([{expr}]) *getcharstr()* - Get a single character from the user or input stream as a - string. - If {expr} is omitted, wait until a character is available. - If {expr} is 0 or false, only get a character when one is - available. Return an empty string otherwise. - If {expr} is 1 or true, only check if a character is - available, it is not consumed. Return an empty string - if no character is available. - Otherwise this works like |getchar()|, except that a number - result is converted to a string. + The same as |getchar()|, except that this always returns a + String, and "number" isn't allowed in {opts}. Parameters: ~ - • {expr} (`0|1?`) + • {expr} (`-1|0|1?`) + • {opts} (`table?`) Return: ~ (`string`) diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 4b5b276023..c0be6b089c 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2748,12 +2748,14 @@ function vim.fn.getcellwidths() end function vim.fn.getchangelist(buf) end --- Get a single character from the user or input stream. ---- If {expr} is omitted, wait until a character is available. +--- If {expr} is omitted or is -1, wait until a character is +--- available. --- If {expr} is 0, only get a character when one is available. --- Return zero otherwise. --- If {expr} is 1, only check if a character is available, it is --- not consumed. Return zero if no character available. ---- If you prefer always getting a string use |getcharstr()|. +--- If you prefer always getting a string use |getcharstr()|, or +--- specify |FALSE| as "number" in {opts}. --- --- Without {expr} and when {expr} is 0 a whole character or --- special key is returned. If it is a single character, the @@ -2763,7 +2765,8 @@ function vim.fn.getchangelist(buf) end --- starting with 0x80 (decimal: 128). This is the same value as --- the String "\", e.g., "\". The returned value is --- also a String when a modifier (shift, control, alt) was used ---- that is not included in the character. +--- that is not included in the character. |keytrans()| can also +--- be used to convert a returned String into a readable form. --- --- When {expr} is 0 and Esc is typed, there will be a short delay --- while Vim waits to see if this is the start of an escape @@ -2775,6 +2778,24 @@ function vim.fn.getchangelist(buf) end --- --- Use getcharmod() to obtain any additional modifiers. --- +--- The optional argument {opts} is a Dict and supports the +--- following items: +--- +--- number If |TRUE|, return a Number when getting +--- a single character. +--- If |FALSE|, the return value is always +--- converted to a String, and an empty +--- String (instead of 0) is returned when +--- no character is available. +--- (default: |TRUE|) +--- +--- simplify If |TRUE|, include modifiers in the +--- character if possible. E.g., return +--- the same value for CTRL-I and . +--- If |FALSE|, don't include modifiers in +--- the character. +--- (default: |TRUE|) +--- --- When the user clicks a mouse button, the mouse event will be --- returned. The position can then be found in |v:mouse_col|, --- |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. @@ -2811,9 +2832,10 @@ function vim.fn.getchangelist(buf) end --- endfunction --- < --- ---- @param expr? 0|1 ---- @return integer -function vim.fn.getchar(expr) end +--- @param expr? -1|0|1 +--- @param opts? table +--- @return integer|string +function vim.fn.getchar(expr, opts) end --- The result is a Number which is the state of the modifiers for --- the last obtained character with getchar() or in another way. @@ -2872,20 +2894,13 @@ function vim.fn.getcharpos(expr) end --- @return table function vim.fn.getcharsearch() end ---- Get a single character from the user or input stream as a ---- string. ---- If {expr} is omitted, wait until a character is available. ---- If {expr} is 0 or false, only get a character when one is ---- available. Return an empty string otherwise. ---- If {expr} is 1 or true, only check if a character is ---- available, it is not consumed. Return an empty string ---- if no character is available. ---- Otherwise this works like |getchar()|, except that a number ---- result is converted to a string. ---- ---- @param expr? 0|1 +--- The same as |getchar()|, except that this always returns a +--- String, and "number" isn't allowed in {opts}. +--- +--- @param expr? -1|0|1 +--- @param opts? table --- @return string -function vim.fn.getcharstr(expr) end +function vim.fn.getcharstr(expr, opts) end --- Return completion pattern of the current command-line. --- Only works when the command line is being edited, thus diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index ecb213c811..82e3992287 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -3471,15 +3471,17 @@ M.funcs = { signature = 'getchangelist([{buf}])', }, getchar = { - args = { 0, 1 }, + args = { 0, 2 }, desc = [=[ Get a single character from the user or input stream. - If {expr} is omitted, wait until a character is available. + If {expr} is omitted or is -1, wait until a character is + available. If {expr} is 0, only get a character when one is available. Return zero otherwise. If {expr} is 1, only check if a character is available, it is not consumed. Return zero if no character available. - If you prefer always getting a string use |getcharstr()|. + If you prefer always getting a string use |getcharstr()|, or + specify |FALSE| as "number" in {opts}. Without {expr} and when {expr} is 0 a whole character or special key is returned. If it is a single character, the @@ -3489,7 +3491,8 @@ M.funcs = { starting with 0x80 (decimal: 128). This is the same value as the String "\", e.g., "\". The returned value is also a String when a modifier (shift, control, alt) was used - that is not included in the character. + that is not included in the character. |keytrans()| can also + be used to convert a returned String into a readable form. When {expr} is 0 and Esc is typed, there will be a short delay while Vim waits to see if this is the start of an escape @@ -3501,6 +3504,24 @@ M.funcs = { Use getcharmod() to obtain any additional modifiers. + The optional argument {opts} is a Dict and supports the + following items: + + number If |TRUE|, return a Number when getting + a single character. + If |FALSE|, the return value is always + converted to a String, and an empty + String (instead of 0) is returned when + no character is available. + (default: |TRUE|) + + simplify If |TRUE|, include modifiers in the + character if possible. E.g., return + the same value for CTRL-I and . + If |FALSE|, don't include modifiers in + the character. + (default: |TRUE|) + When the user clicks a mouse button, the mouse event will be returned. The position can then be found in |v:mouse_col|, |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. @@ -3538,9 +3559,9 @@ M.funcs = { < ]=], name = 'getchar', - params = { { 'expr', '0|1' } }, - returns = 'integer', - signature = 'getchar([{expr}])', + params = { { 'expr', '-1|0|1' }, { 'opts', 'table' } }, + returns = 'integer|string', + signature = 'getchar([{expr} [, {opts}]])', }, getcharmod = { desc = [=[ @@ -3613,21 +3634,13 @@ M.funcs = { signature = 'getcharsearch()', }, getcharstr = { - args = { 0, 1 }, + args = { 0, 2 }, desc = [=[ - Get a single character from the user or input stream as a - string. - If {expr} is omitted, wait until a character is available. - If {expr} is 0 or false, only get a character when one is - available. Return an empty string otherwise. - If {expr} is 1 or true, only check if a character is - available, it is not consumed. Return an empty string - if no character is available. - Otherwise this works like |getchar()|, except that a number - result is converted to a string. + The same as |getchar()|, except that this always returns a + String, and "number" isn't allowed in {opts}. ]=], name = 'getcharstr', - params = { { 'expr', '0|1' } }, + params = { { 'expr', '-1|0|1' }, { 'opts', 'table' } }, returns = 'string', signature = 'getcharstr([{expr}])', }, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ed1031577c..48b2e82c0a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2234,6 +2234,18 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, const ptr return TV_DICT_HI2DI(hi); } +/// Check if a key is present in a dictionary. +/// +/// @param[in] d Dictionary to check. +/// @param[in] key Dictionary key. +/// +/// @return whether the key is present in the dictionary. +bool tv_dict_has_key(const dict_T *const d, const char *const key) + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return tv_dict_find(d, key, -1) != NULL; +} + /// Get a typval item from a dictionary and copy it into "rettv". /// /// @param[in] d Dictionary to check. diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 6ec84ff543..c9d2165fb0 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1866,22 +1866,47 @@ bool char_avail(void) return retval != NUL; } +static int no_reduce_keys = 0; ///< Do not apply modifiers to the key. + /// "getchar()" and "getcharstr()" functions -static void getchar_common(typval_T *argvars, typval_T *rettv) +static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number) FUNC_ATTR_NONNULL_ALL { varnumber_T n; bool error = false; + bool simplify = true; + + if (argvars[0].v_type != VAR_UNKNOWN + && tv_check_for_opt_dict_arg(argvars, 1) == FAIL) { + return; + } + + if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type == VAR_DICT) { + dict_T *d = argvars[1].vval.v_dict; + + if (allow_number) { + allow_number = tv_dict_get_bool(d, "number", true); + } else if (tv_dict_has_key(d, "number")) { + semsg(_(e_invarg2), "number"); + error = true; + } + + simplify = tv_dict_get_bool(d, "simplify", true); + } no_mapping++; allow_keys++; - while (true) { + if (!simplify) { + no_reduce_keys++; + } + while (!error) { if (msg_col > 0) { // Position the cursor. Needed after a message that ends in a space. ui_cursor_goto(msg_row, msg_col); } - if (argvars[0].v_type == VAR_UNKNOWN) { + if (argvars[0].v_type == VAR_UNKNOWN + || (argvars[0].v_type == VAR_NUMBER && argvars[0].vval.v_number == -1)) { // getchar(): blocking wait. // TODO(bfredl): deduplicate shared logic with state_enter ? if (!char_avail()) { @@ -1916,14 +1941,16 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) } no_mapping--; allow_keys--; + if (!simplify) { + no_reduce_keys--; + } set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); set_vim_var_nr(VV_MOUSE_COL, 0); - rettv->vval.v_number = n; - if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { + if (n != 0 && (!allow_number || IS_SPECIAL(n) || mod_mask != 0)) { char temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; @@ -1970,35 +1997,23 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) set_vim_var_nr(VV_MOUSE_COL, col + 1); } } + } else if (!allow_number) { + rettv->v_type = VAR_STRING; + } else { + rettv->vval.v_number = n; } } /// "getchar()" function void f_getchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - getchar_common(argvars, rettv); + getchar_common(argvars, rettv, true); } /// "getcharstr()" function void f_getcharstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - getchar_common(argvars, rettv); - - if (rettv->v_type != VAR_NUMBER) { - return; - } - - char temp[7]; // mbyte-char: 6, NUL: 1 - const varnumber_T n = rettv->vval.v_number; - int i = 0; - - if (n != 0) { - i += utf_char2bytes((int)n, temp); - } - assert(i < 7); - temp[i] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmemdupz(temp, (size_t)i); + getchar_common(argvars, rettv, false); } /// "getcharmod()" function @@ -2058,7 +2073,7 @@ static int check_simplify_modifier(int max_offset) { // We want full modifiers in Terminal mode so that the key can be correctly // encoded - if (State & MODE_TERMINAL) { + if ((State & MODE_TERMINAL) || no_reduce_keys > 0) { return 0; } diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index d36dc60a93..4635259e33 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -557,7 +557,7 @@ describe('terminal input', function() '--cmd', 'set notermguicolors', '-c', - 'while 1 | redraw | echo keytrans(getcharstr()) | endwhile', + 'while 1 | redraw | echo keytrans(getcharstr(-1, #{simplify: 0})) | endwhile', }) screen:expect([[ ^ | @@ -566,7 +566,10 @@ describe('terminal input', function() | {3:-- TERMINAL --} | ]]) - for _, key in ipairs({ + local keys = { + '', + '', + '', '', '', '', @@ -632,7 +635,14 @@ describe('terminal input', function() '', '', '', - }) do + } + -- FIXME: The escape sequence to enable kitty keyboard mode doesn't work on Windows + if not is_os('win') then + table.insert(keys, '') + table.insert(keys, '') + table.insert(keys, '') + end + for _, key in ipairs(keys) do feed(key) screen:expect(([[ | @@ -643,82 +653,6 @@ describe('terminal input', function() ]]):format(key:gsub('<%d+,%d+>$', ''))) end end) - - -- TODO(bfredl): getcharstr() erases the distinction between and . - -- If it was enhanced or replaced this could get folded into the test above. - it('can send TAB/C-I and ESC/C-[ separately', function() - if - skip( - is_os('win'), - "The escape sequence to enable kitty keyboard mode doesn't work on Windows" - ) - then - return - end - clear() - local screen = tt.setup_child_nvim({ - '-u', - 'NONE', - '-i', - 'NONE', - '--cmd', - 'colorscheme vim', - '--cmd', - 'set notermguicolors', - '--cmd', - 'noremap echo "Tab!"', - '--cmd', - 'noremap echo "Ctrl-I!"', - '--cmd', - 'noremap echo "Esc!"', - '--cmd', - 'noremap echo "Ctrl-[!"', - }) - - screen:expect([[ - ^ | - {4:~ }|*3 - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | - ]]) - - feed('') - screen:expect([[ - ^ | - {4:~ }|*3 - {5:[No Name] 0,0-1 All}| - Tab! | - {3:-- TERMINAL --} | - ]]) - - feed('') - screen:expect([[ - ^ | - {4:~ }|*3 - {5:[No Name] 0,0-1 All}| - Ctrl-I! | - {3:-- TERMINAL --} | - ]]) - - feed('') - screen:expect([[ - ^ | - {4:~ }|*3 - {5:[No Name] 0,0-1 All}| - Esc! | - {3:-- TERMINAL --} | - ]]) - - feed('') - screen:expect([[ - ^ | - {4:~ }|*3 - {5:[No Name] 0,0-1 All}| - Ctrl-[! | - {3:-- TERMINAL --} | - ]]) - end) end) if is_os('win') then diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index 01e6001dcc..f57743900a 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -2390,6 +2390,77 @@ func Test_getchar() call assert_equal("\", getchar(0)) call assert_equal(0, getchar(0)) + call feedkeys("\", '') + call assert_equal(char2nr("\"), getchar()) + call feedkeys("\", '') + call assert_equal(char2nr("\"), getchar(-1)) + call feedkeys("\", '') + call assert_equal(char2nr("\"), getchar(-1, {})) + call feedkeys("\", '') + call assert_equal(char2nr("\"), getchar(-1, #{number: v:true})) + call assert_equal(0, getchar(0)) + call assert_equal(0, getchar(1)) + call assert_equal(0, getchar(0, #{number: v:true})) + call assert_equal(0, getchar(1, #{number: v:true})) + + call feedkeys("\", '') + call assert_equal("\", getcharstr()) + call feedkeys("\", '') + call assert_equal("\", getcharstr(-1)) + call feedkeys("\", '') + call assert_equal("\", getcharstr(-1, {})) + call feedkeys("\", '') + call assert_equal("\", getchar(-1, #{number: v:false})) + call assert_equal('', getcharstr(0)) + call assert_equal('', getcharstr(1)) + call assert_equal('', getchar(0, #{number: v:false})) + call assert_equal('', getchar(1, #{number: v:false})) + + " Nvim: is never simplified + " for key in ["C-I", "C-X", "M-x"] + for key in ["C-I", "C-X"] + let lines =<< eval trim END + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar()) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1)) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{}})) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{'number': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{'simplify': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<*{key}>", getchar(-1, {{'simplify': v:false}})) + call assert_equal(0, getchar(0)) + call assert_equal(0, getchar(1)) + END + call CheckLegacyAndVim9Success(lines) + + let lines =<< eval trim END + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr()) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1)) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1, {{}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getchar(-1, {{'number': 0}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1, {{'simplify': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<*{key}>", getcharstr(-1, {{'simplify': v:false}})) + call assert_equal('', getcharstr(0)) + call assert_equal('', getcharstr(1)) + END + call CheckLegacyAndVim9Success(lines) + endfor + + call assert_fails('call getchar(1, 1)', 'E1206:') + call assert_fails('call getcharstr(1, 1)', 'E1206:') + call assert_fails('call getcharstr(1, #{number: v:true})', 'E475:') + call assert_fails('call getcharstr(1, #{number: v:false})', 'E475:') + call setline(1, 'xxxx') call Ntest_setmouse(1, 3) let v:mouse_win = 9 -- cgit From a22f2102ceb0b9f33125ce95b4d3cbdcac0f87c3 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 2 Feb 2025 10:30:02 +0100 Subject: build(deps): bump tree-sitter to v0.25.1 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 610b313c03..18d34a7be1 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -50,8 +50,8 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.0.tar.gz -TREESITTER_SHA256 3729e98e54a41a4c03f4f6c0be580d14ed88c113d75acf15397b16e05cf91129 +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.1.tar.gz +TREESITTER_SHA256 99a2446075c2edf60e82755c48415d5f6e40f2d9aacb3423c6ca56809b70fe59 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v29.0.1.tar.gz WASMTIME_SHA256 b94b6c6fd6aebaf05d4c69c1b12b5dc217b0d42c1a95f435b33af63dddfa5304 -- cgit From 5bc948c050ce321ed56ff48b0d41e189b1ea4c87 Mon Sep 17 00:00:00 2001 From: Tristan Knight Date: Sun, 2 Feb 2025 09:56:01 +0000 Subject: fix(diagnostic): improve current_line refresh logic #32275 Problem: The current implementation uses a global augroup for virtual lines in diagnostics, which can lead to conflicts and unintended behavior when multiple namespaces/buffers are involved. Solution: Refactor the code to use a namespace-specific augroup for virtual lines. This ensures that each namespace has its own augroup. Scope the clear commands to only the relevant buffer. --- runtime/lua/vim/diagnostic.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 2538cd3048..6016867046 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -344,7 +344,6 @@ local global_diagnostic_options = { --- @class (private) vim.diagnostic.Handler --- @field show? fun(namespace: integer, bufnr: integer, diagnostics: vim.Diagnostic[], opts?: vim.diagnostic.OptsResolved) --- @field hide? fun(namespace:integer, bufnr:integer) ---- @field _augroup? integer --- @nodoc --- @type table @@ -1841,12 +1840,14 @@ M.handlers.virtual_lines = { ns.user_data.virt_lines_ns = api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_lines', ns.name)) end - if not M.handlers.virtual_lines._augroup then - M.handlers.virtual_lines._augroup = - api.nvim_create_augroup('nvim.lsp.diagnostic.virt_lines', { clear = true }) + if not ns.user_data.virt_lines_augroup then + ns.user_data.virt_lines_augroup = api.nvim_create_augroup( + string.format('nvim.%s.diagnostic.virt_lines', ns.name), + { clear = true } + ) end - api.nvim_clear_autocmds({ group = M.handlers.virtual_lines._augroup }) + api.nvim_clear_autocmds({ group = ns.user_data.virt_lines_augroup, buffer = bufnr }) if opts.virtual_lines.format then diagnostics = reformat_diagnostics(opts.virtual_lines.format, diagnostics) @@ -1855,7 +1856,7 @@ M.handlers.virtual_lines = { if opts.virtual_lines.current_line == true then api.nvim_create_autocmd('CursorMoved', { buffer = bufnr, - group = M.handlers.virtual_lines._augroup, + group = ns.user_data.virt_lines_augroup, callback = function() render_virtual_lines_at_current_line(diagnostics, ns.user_data.virt_lines_ns, bufnr) end, @@ -1875,7 +1876,7 @@ M.handlers.virtual_lines = { if api.nvim_buf_is_valid(bufnr) then api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_lines_ns, 0, -1) end - api.nvim_clear_autocmds({ group = M.handlers.virtual_lines._augroup }) + api.nvim_clear_autocmds({ group = ns.user_data.virt_lines_augroup, buffer = bufnr }) end end, } -- cgit From 3e882bf81c5f0761b98d29a6c986026d5962e5f7 Mon Sep 17 00:00:00 2001 From: glepnir Date: Sun, 2 Feb 2025 18:02:25 +0800 Subject: vim-patch:9.1.1069: preinsert text completions not deleted with / (#32296) Problem: preinsert text completions not deleted with / (ddad431, after v9.1.1059) Solution: handle or specifically and clear the completion (glepnir) fixes: vim/vim#16557 closes: vim/vim#16565 https://github.com/vim/vim/commit/001c26cd6194fba2bfccb06dec30fdc9e1410e62 --- src/nvim/insexpand.c | 4 ++++ test/old/testdir/test_ins_complete.vim | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 44e72b2b01..e21433f5ec 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -2228,6 +2228,10 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) retval = true; } + if ((c == Ctrl_W || c == Ctrl_U) && ins_compl_preinsert_effect()) { + ins_compl_delete(false); + } + auto_format(false, true); // Trigger the CompleteDonePre event to give scripts a chance to diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index f276d1e719..2fb1715634 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -3042,6 +3042,14 @@ function Test_completeopt_preinsert() call assert_equal("fo ", getline('.')) call assert_equal(3, col('.')) + call feedkeys("She\\\", 'tx') + call assert_equal("", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("She\\\", 'tx') + call assert_equal("", getline('.')) + call assert_equal(1, col('.')) + " whole line call feedkeys("Shello hero\\\", 'tx') call assert_equal("hello hero", getline('.')) -- cgit From e08e3d15f66db3fba9f164f0c18b71d72ffb4877 Mon Sep 17 00:00:00 2001 From: Johannes Larsen Date: Sun, 2 Feb 2025 12:00:05 +0100 Subject: fix(man.lua): skip `Attrs.None` highlights #32262 Before the 7121983c45d92349a6532f32dcde9f425e30781e refactoring this loop added highlights from a `buf_hls` list that had filtered out elements with `Attrs.None`. After the refactoring this added highlights from `hls` directly, and those elements would fail with e.g.: $ nvim 'man://math.h(0)' Error detected while processing command line: Error executing Lua callback: /usr/share/nvim/runtime/lua/man.lua:205: Invalid 'hl_group': Expected Lua string stack traceback: [C]: in function 'nvim_buf_add_highlight' /usr/share/nvim/runtime/lua/man.lua:205: in function 'highlight_man_page' /usr/share/nvim/runtime/lua/man.lua:632: in function 'init_pager' /usr/share/nvim/runtime/plugin/man.lua:9: in function --- runtime/lua/man.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 7485f60978..96cfdf523d 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -201,8 +201,10 @@ local function highlight_man_page() api.nvim_buf_set_lines(0, 0, -1, false, lines) for _, hl in ipairs(hls) do - --- @diagnostic disable-next-line: deprecated - api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final) + if hl.attr ~= Attrs.None then + --- @diagnostic disable-next-line: deprecated + api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final) + end end vim.bo.modifiable = mod -- cgit From 48e3ac60c63341761b0f7e21262722f09127d374 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 2 Feb 2025 03:40:43 -0800 Subject: perf(diagnostics): cache line diagnostics when `current_line` is set #32288 Compute the diagnostics per line when `show` is called, allowing for O(1) access for the diagnostics to display when the cursor line or the list of diagnostics haven't changed. --- runtime/lua/vim/diagnostic.lua | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 6016867046..8044767cb0 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1805,20 +1805,26 @@ local function render_virtual_lines(namespace, bufnr, diagnostics) end end ---- @param diagnostics vim.Diagnostic[] +--- @param diagnostics table --- @param namespace integer --- @param bufnr integer local function render_virtual_lines_at_current_line(diagnostics, namespace, bufnr) - local line_diagnostics = {} local lnum = api.nvim_win_get_cursor(0)[1] - 1 + local cursor_diagnostics = {} - for _, diag in ipairs(diagnostics) do - if (lnum == diag.lnum) or (diag.end_lnum and lnum >= diag.lnum and lnum <= diag.end_lnum) then - table.insert(line_diagnostics, diag) + if diagnostics[lnum] ~= nil then + cursor_diagnostics = diagnostics[lnum] + else + for _, line_diags in pairs(diagnostics) do + for _, diag in ipairs(line_diags) do + if diag.end_lnum and lnum >= diag.lnum and lnum <= diag.end_lnum then + table.insert(cursor_diagnostics, diag) + end + end end end - render_virtual_lines(namespace, bufnr, line_diagnostics) + render_virtual_lines(namespace, bufnr, cursor_diagnostics) end M.handlers.virtual_lines = { @@ -1854,15 +1860,18 @@ M.handlers.virtual_lines = { end if opts.virtual_lines.current_line == true then + -- Create a mapping from line -> diagnostics so that we can quickly get the + -- diagnostics we need when the cursor line doesn't change. + local line_diagnostics = diagnostic_lines(diagnostics) api.nvim_create_autocmd('CursorMoved', { buffer = bufnr, group = ns.user_data.virt_lines_augroup, callback = function() - render_virtual_lines_at_current_line(diagnostics, ns.user_data.virt_lines_ns, bufnr) + render_virtual_lines_at_current_line(line_diagnostics, ns.user_data.virt_lines_ns, bufnr) end, }) -- Also show diagnostics for the current line before the first CursorMoved event. - render_virtual_lines_at_current_line(diagnostics, ns.user_data.virt_lines_ns, bufnr) + render_virtual_lines_at_current_line(line_diagnostics, ns.user_data.virt_lines_ns, bufnr) else render_virtual_lines(ns.user_data.virt_lines_ns, bufnr, diagnostics) end -- cgit From 02ea0e77a19b116006dc04848703aaeed3f50ded Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 2 Feb 2025 03:42:47 -0800 Subject: refactor(treesitter): drop `LanguageTree._has_regions` #32274 This simplifies some logic in `languagetree.lua`, removing the need for `_has_regions`, and removing side effects in `:included_regions()`. Before: - Edit is made which sets `_regions = nil` - Upon the next call to `included_regions()` (usually right after we marked `_regions` as `nil` due to an `_iter_regions()` call), if `_regions` is nil, we repopulate the table (as long as the tree actually has regions) After: - Edit is made which resets `_regions` if it exists - `included_regions()` no longer needs to perform this logic itself, and also no longer needs to read a `_has_regions` variable --- runtime/lua/vim/treesitter/languagetree.lua | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4e4da5a5ec..9571a117b8 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -82,7 +82,6 @@ local TSCallbackNames = { ---@field private _ranges_being_parsed table ---Table of callback queues, keyed by each region for which the callbacks should be run ---@field private _cb_queues table)[]> ----@field private _has_regions boolean ---@field private _regions table? ---List of regions this tree should manage and parse. If nil then regions are ---taken from _trees. This is mostly a short-lived cache for included_regions() @@ -132,7 +131,6 @@ function LanguageTree.new(source, lang, opts) _opts = opts, _injection_query = injections[lang] and query.parse(lang, injections[lang]) or query.get(lang, 'injections'), - _has_regions = false, _injections_processed = false, _valid = false, _parser = vim._create_ts_parser(lang), @@ -743,8 +741,6 @@ end ---@private ---@param new_regions (Range4|Range6|TSNode)[][] List of regions this tree should manage and parse. function LanguageTree:set_included_regions(new_regions) - self._has_regions = true - -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(new_regions) do for i, range in ipairs(region) do @@ -788,18 +784,8 @@ function LanguageTree:included_regions() return self._regions end - if not self._has_regions then - -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} (the full range) - return { {} } - end - - local regions = {} ---@type Range6[][] - for i, _ in pairs(self._trees) do - regions[i] = self._trees[i]:included_ranges(true) - end - - self._regions = regions - return regions + -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} (the full range) + return { {} } end ---@param node TSNode @@ -1050,7 +1036,14 @@ function LanguageTree:_edit( end self._parser:reset() - self._regions = nil + + if self._regions then + local regions = {} ---@type table + for i, tree in pairs(self._trees) do + regions[i] = tree:included_ranges(true) + end + self._regions = regions + end local changed_range = { start_row, -- cgit From 77be44563acb64a481d48f45c8dbbfca2d7db415 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 2 Feb 2025 03:46:26 -0800 Subject: refactor(treesitter): always return valid range from parse() #32273 Problem: When running an initial parse, parse() returns an empty table rather than an actual range. In `languagetree.lua`, we manually check if a parse was incremental to determine the changed parse region. Solution: - Always return a range (in the C side) from parse(). - Simplify the language tree code a bit. - Logger no longer shows empty ranges on the initial parse. --- runtime/lua/vim/treesitter/languagetree.lua | 5 +---- src/nvim/lua/treesitter.c | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 9571a117b8..725e95dfc9 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -378,10 +378,7 @@ function LanguageTree:_parse_regions(range, timeout) return changes, no_regions_parsed, total_parse_time, false end - -- Pass ranges if this is an initial parse - local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) - - self:_do_callback('changedtree', cb_changes, tree) + self:_do_callback('changedtree', tree_changes, tree) self._trees[i] = tree vim.list_extend(changes, tree_changes) diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index c7999ac077..3e33fcd142 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -500,7 +500,8 @@ static int parser_parse(lua_State *L) // The new tree will be pushed to the stack, without copy, ownership is now to the lua GC. // Old tree is owned by lua GC since before uint32_t n_ranges = 0; - TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL; + TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) + : ts_tree_included_ranges(new_tree, &n_ranges); push_tree(L, new_tree); // [tree] -- cgit From 9508d6a8146350ffc9f31f4263fa871bab9130bf Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 17 Jan 2025 14:35:05 -0800 Subject: refactor(treesitter): use coroutines for resuming _parse() logic This means that all work previously done by a `_parse()` iteration will be kept in future iterations. This prevents it from running indefinitely in some cases where the file is very large and there are 2+ injections. --- runtime/lua/vim/treesitter/languagetree.lua | 70 ++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 725e95dfc9..f876d9fe7d 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -60,6 +60,8 @@ local default_parse_timeout_ms = 3 ---| 'on_child_added' ---| 'on_child_removed' +---@alias ParserThreadState { timeout: integer? } + --- @type table local TSCallbackNames = { on_changedtree = 'changedtree', @@ -345,12 +347,12 @@ end --- @private --- @param range boolean|Range? ---- @param timeout integer? +--- @param thread_state ParserThreadState --- @return Range6[] changes --- @return integer no_regions_parsed --- @return number total_parse_time --- @return boolean finished whether async parsing still needs time -function LanguageTree:_parse_regions(range, timeout) +function LanguageTree:_parse_regions(range, thread_state) local changes = {} local no_regions_parsed = 0 local total_parse_time = 0 @@ -370,12 +372,18 @@ function LanguageTree:_parse_regions(range, timeout) ) then self._parser:set_included_ranges(ranges) - self._parser:set_timeout(timeout and timeout * 1000 or 0) -- ms -> micros + self._parser:set_timeout(thread_state.timeout and thread_state.timeout * 1000 or 0) -- ms -> micros + local parse_time, tree, tree_changes = tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + while true do + if tree then + break + end + coroutine.yield(changes, no_regions_parsed, total_parse_time, false) - if not tree then - return changes, no_regions_parsed, total_parse_time, false + parse_time, tree, tree_changes = + tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) end self:_do_callback('changedtree', tree_changes, tree) @@ -476,7 +484,11 @@ function LanguageTree:_async_parse(range, on_parse) local ct = is_buffer_parser and buf.changedtick or nil local total_parse_time = 0 local redrawtime = vim.o.redrawtime - local timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil + + local thread_state = {} ---@type ParserThreadState + + ---@type fun(): table, boolean + local parse = coroutine.wrap(self._parse) local function step() if is_buffer_parser then @@ -490,10 +502,12 @@ function LanguageTree:_async_parse(range, on_parse) if buf.changedtick ~= ct then ct = buf.changedtick total_parse_time = 0 + parse = coroutine.wrap(self._parse) end end - local parse_time, trees, finished = tcall(self._parse, self, range, timeout) + thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil + local parse_time, trees, finished = tcall(parse, self, range, thread_state) total_parse_time = total_parse_time + parse_time if finished then @@ -535,16 +549,16 @@ function LanguageTree:parse(range, on_parse) if on_parse then return self:_async_parse(range, on_parse) end - local trees, _ = self:_parse(range) + local trees, _ = self:_parse(range, {}) return trees end --- @private --- @param range boolean|Range|nil ---- @param timeout integer? +--- @param thread_state ParserThreadState --- @return table trees --- @return boolean finished -function LanguageTree:_parse(range, timeout) +function LanguageTree:_parse(range, thread_state) if self:is_valid() then self:_log('valid') return self._trees, true @@ -559,11 +573,18 @@ function LanguageTree:_parse(range, timeout) -- At least 1 region is invalid if not self:is_valid(true) then - local is_finished - changes, no_regions_parsed, total_parse_time, is_finished = self:_parse_regions(range, timeout) - timeout = timeout and math.max(timeout - total_parse_time, 0) - if not is_finished then - return self._trees, false + ---@type fun(self: vim.treesitter.LanguageTree, range: boolean|Range?, thread_state: ParserThreadState): Range6[], integer, number, boolean + local parse_regions = coroutine.wrap(self._parse_regions) + while true do + local is_finished + changes, no_regions_parsed, total_parse_time, is_finished = + parse_regions(self, range, thread_state) + thread_state.timeout = thread_state.timeout + and math.max(thread_state.timeout - total_parse_time, 0) + if is_finished then + break + end + coroutine.yield(self._trees, false) end -- Need to run injections when we parsed something if no_regions_parsed > 0 then @@ -585,13 +606,20 @@ function LanguageTree:_parse(range, timeout) }) for _, child in pairs(self._children) do - if timeout == 0 then - return self._trees, false + if thread_state.timeout == 0 then + coroutine.yield(self._trees, false) end - local ctime, _, child_finished = tcall(child._parse, child, range, timeout) - timeout = timeout and math.max(timeout - ctime, 0) - if not child_finished then - return self._trees, child_finished + + ---@type fun(): table, boolean + local parse = coroutine.wrap(child._parse) + + while true do + local ctime, _, child_finished = tcall(parse, child, range, thread_state) + if child_finished then + thread_state.timeout = thread_state.timeout and math.max(thread_state.timeout - ctime, 0) + break + end + coroutine.yield(self._trees, child_finished) end end -- cgit From 8543aa406c4ae88cc928372b2f8105005cdd0a80 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Wed, 29 Jan 2025 15:53:34 -0800 Subject: feat(treesitter): allow LanguageTree:is_valid() to accept a range When given, only that range will be checked for validity rather than the entire tree. This is used in the highlighter to save CPU cycles since we only need to parse a certain region at a time anyway. --- runtime/doc/news.txt | 2 + runtime/doc/treesitter.txt | 4 +- runtime/lua/vim/treesitter/languagetree.lua | 129 +++++++++++++++------------- test/functional/treesitter/parser_spec.lua | 2 +- 4 files changed, 73 insertions(+), 64 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 2208428f75..e03d6ca512 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -379,6 +379,8 @@ TREESITTER activated by passing the `on_parse` callback parameter. • |vim.treesitter.query.set()| can now inherit and/or extend runtime file queries in addition to overriding. +• |LanguageTree:is_valid()| now accepts a range parameter to narrow the scope + of the validity check. TUI diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 257a6a24e7..b04f13add5 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1581,7 +1581,8 @@ LanguageTree:invalidate({reload}) *LanguageTree:invalidate()* Parameters: ~ • {reload} (`boolean?`) -LanguageTree:is_valid({exclude_children}) *LanguageTree:is_valid()* + *LanguageTree:is_valid()* +LanguageTree:is_valid({exclude_children}, {range}) Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest state of the source. If invalid, user should call |LanguageTree:parse()|. @@ -1589,6 +1590,7 @@ LanguageTree:is_valid({exclude_children}) *LanguageTree:is_valid()* Parameters: ~ • {exclude_children} (`boolean?`) whether to ignore the validity of children (default `false`) + • {range} (`Range?`) range to check for validity Return: ~ (`boolean`) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index f876d9fe7d..ea745c4deb 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -85,6 +85,8 @@ local TSCallbackNames = { ---Table of callback queues, keyed by each region for which the callbacks should be run ---@field private _cb_queues table)[]> ---@field private _regions table? +---The total number of regions. Since _regions can have holes, we cannot simply read this value from #_regions. +---@field private _num_regions integer ---List of regions this tree should manage and parse. If nil then regions are ---taken from _trees. This is mostly a short-lived cache for included_regions() ---@field private _lang string Language name @@ -92,7 +94,8 @@ local TSCallbackNames = { ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees table Reference to parsed tree (one for each language). ---Each key is the index of region, which is synced with _regions and _valid. ----@field private _valid boolean|table If the parsed tree is valid +---@field private _valid_regions table Set of valid region IDs. +---@field private _is_entirely_valid boolean Whether the entire tree (excluding children) is valid. ---@field private _logger? fun(logtype: string, msg: string) ---@field private _logfile? file* local LanguageTree = {} @@ -134,7 +137,9 @@ function LanguageTree.new(source, lang, opts) _injection_query = injections[lang] and query.parse(lang, injections[lang]) or query.get(lang, 'injections'), _injections_processed = false, - _valid = false, + _valid_regions = {}, + _num_regions = 1, + _is_entirely_valid = false, _parser = vim._create_ts_parser(lang), _ranges_being_parsed = {}, _cb_queues = {}, @@ -240,7 +245,8 @@ end --- tree in treesitter. Doesn't clear filesystem cache. Called often, so needs to be fast. ---@param reload boolean|nil function LanguageTree:invalidate(reload) - self._valid = false + self._valid_regions = {} + self._is_entirely_valid = false self._parser:reset() -- buffer was reloaded, reparse all trees @@ -273,16 +279,46 @@ function LanguageTree:lang() return self._lang end +--- @param region Range6[] +--- @param range? boolean|Range +--- @return boolean +local function intercepts_region(region, range) + if #region == 0 then + return true + end + + if range == nil then + return false + end + + if type(range) == 'boolean' then + return range + end + + for _, r in ipairs(region) do + if Range.intercepts(r, range) then + return true + end + end + + return false +end + --- Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest --- state of the source. If invalid, user should call |LanguageTree:parse()|. ----@param exclude_children boolean|nil whether to ignore the validity of children (default `false`) +---@param exclude_children boolean? whether to ignore the validity of children (default `false`) +---@param range Range? range to check for validity ---@return boolean -function LanguageTree:is_valid(exclude_children) - local valid = self._valid +function LanguageTree:is_valid(exclude_children, range) + local valid_regions = self._valid_regions - if type(valid) == 'table' then - for i, _ in pairs(self:included_regions()) do - if not valid[i] then + if not self._is_entirely_valid then + if not range then + return false + end + -- TODO: Efficiently search for possibly intersecting regions using a binary search + for i, region in pairs(self:included_regions()) do + if not valid_regions[i] and intercepts_region(region, range) then return false end end @@ -294,17 +330,12 @@ function LanguageTree:is_valid(exclude_children) end for _, child in pairs(self._children) do - if not child:is_valid(exclude_children) then + if not child:is_valid(exclude_children, range) then return false end end end - if type(valid) == 'boolean' then - return valid - end - - self._valid = true return true end @@ -320,31 +351,6 @@ function LanguageTree:source() return self._source end ---- @param region Range6[] ---- @param range? boolean|Range ---- @return boolean -local function intercepts_region(region, range) - if #region == 0 then - return true - end - - if range == nil then - return false - end - - if type(range) == 'boolean' then - return range - end - - for _, r in ipairs(region) do - if Range.intercepts(r, range) then - return true - end - end - - return false -end - --- @private --- @param range boolean|Range? --- @param thread_state ParserThreadState @@ -357,15 +363,11 @@ function LanguageTree:_parse_regions(range, thread_state) local no_regions_parsed = 0 local total_parse_time = 0 - if type(self._valid) ~= 'table' then - self._valid = {} - end - -- If there are no ranges, set to an empty list -- so the included ranges in the parser are cleared. for i, ranges in pairs(self:included_regions()) do if - not self._valid[i] + not self._valid_regions[i] and ( intercepts_region(ranges, range) or (self._trees[i] and intercepts_region(self._trees[i]:included_ranges(false), range)) @@ -392,7 +394,13 @@ function LanguageTree:_parse_regions(range, thread_state) total_parse_time = total_parse_time + parse_time no_regions_parsed = no_regions_parsed + 1 - self._valid[i] = true + self._valid_regions[i] = true + + -- _valid_regions can have holes, but that is okay because this equality is only true when it + -- has no holes (meaning all regions are valid) + if #self._valid_regions == self._num_regions then + self._is_entirely_valid = true + end end end @@ -559,7 +567,7 @@ end --- @return table trees --- @return boolean finished function LanguageTree:_parse(range, thread_state) - if self:is_valid() then + if self:is_valid(nil, type(range) == 'table' and range or nil) then self:_log('valid') return self._trees, true end @@ -572,7 +580,7 @@ function LanguageTree:_parse(range, thread_state) local total_parse_time = 0 -- At least 1 region is invalid - if not self:is_valid(true) then + if not self:is_valid(true, type(range) == 'table' and range or nil) then ---@type fun(self: vim.treesitter.LanguageTree, range: boolean|Range?, thread_state: ParserThreadState): Range6[], integer, number, boolean local parse_regions = coroutine.wrap(self._parse_regions) while true do @@ -715,38 +723,34 @@ end ---region is valid or not. ---@param fn fun(index: integer, region: Range6[]): boolean function LanguageTree:_iter_regions(fn) - if not self._valid then + if vim.deep_equal(self._valid_regions, {}) then return end - local was_valid = type(self._valid) ~= 'table' - - if was_valid then - self:_log('was valid', self._valid) - self._valid = {} + if self._is_entirely_valid then + self:_log('was valid') end local all_valid = true for i, region in pairs(self:included_regions()) do - if was_valid or self._valid[i] then - self._valid[i] = fn(i, region) - if not self._valid[i] then + if self._valid_regions[i] then + -- Setting this to nil rather than false allows us to determine if all regions were parsed + -- just by checking the length of _valid_regions. + self._valid_regions[i] = fn(i, region) and true or nil + if not self._valid_regions[i] then self:_log(function() return 'invalidating region', i, region_tostr(region) end) end end - if not self._valid[i] then + if not self._valid_regions[i] then all_valid = false end end - -- Compress the valid value to 'true' if there are no invalid regions - if all_valid then - self._valid = all_valid - end + self._is_entirely_valid = all_valid end --- Sets the included regions that should be parsed by this |LanguageTree|. @@ -796,6 +800,7 @@ function LanguageTree:set_included_regions(new_regions) end self._regions = new_regions + self._num_regions = #new_regions end ---Gets the set of included regions managed by this LanguageTree. This can be different from the diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index a6d3a340f7..eb4651a81d 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -633,7 +633,7 @@ int x = INT_MAX; }, get_ranges()) n.feed('7ggI//') - exec_lua([[parser:parse({6, 7})]]) + exec_lua([[parser:parse({5, 6})]]) eq('table', exec_lua('return type(parser:children().c)')) eq(2, exec_lua('return #parser:children().c:trees()')) eq({ -- cgit From 87e806186c721f12c338af86677b6d1e6e2fa44a Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 3 Feb 2025 00:09:43 +0100 Subject: fix(statusline): overwriting stl_items with nvim_eval_statusline() {-item #32265 Problem: When an evaluation {-item calls `nvim_eval_statusline()`, that nested call may overwrite the same memory used for `stl_items`. Solution: Make `curitem` static and use it to compute an offset to avoid overwriting `stl_items` in nested calls to `build_stl_str_hl()`. Move miscellaneous statusline tests into `describe()` block. --- src/nvim/statusline.c | 27 +- test/functional/ui/statusline_spec.lua | 505 ++++++++++++++++----------------- 2 files changed, 267 insertions(+), 265 deletions(-) diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 6947a14a2c..ddae023ad5 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -927,6 +927,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op static stl_hlrec_t *stl_hltab = NULL; static StlClickRecord *stl_tabtab = NULL; static int *stl_separator_locations = NULL; + static int curitem = 0; #define TMPLEN 70 char buf_tmp[TMPLEN]; @@ -1013,7 +1014,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op int groupdepth = 0; int evaldepth = 0; - int curitem = 0; + // nvim_eval_statusline() can be called from inside a {-expression item so + // this may be a recursive call. Keep track of the start index into "stl_items". + // During post-processing only treat items filled in a certain recursion level. + int evalstart = curitem; + bool prevchar_isflag = true; bool prevchar_isitem = false; @@ -1949,7 +1954,9 @@ stcsign: } *out_p = NUL; - int itemcnt = curitem; + // Subtract offset from `itemcnt` and restore `curitem` to previous recursion level. + int itemcnt = curitem - evalstart; + curitem = evalstart; // Free the format buffer if we allocated it internally if (usefmt != fmt) { @@ -1975,7 +1982,7 @@ stcsign: trunc_p = stl_items[0].start; item_idx = 0; - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].type == Trunc) { // Truncate at %< stl_items. trunc_p = stl_items[i].start; @@ -2005,9 +2012,9 @@ stcsign: // Ignore any items in the statusline that occur after // the truncation point - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].start > trunc_p) { - for (int j = i; j < itemcnt; j++) { + for (int j = i; j < itemcnt + evalstart; j++) { if (stl_items[j].type == ClickFunc) { XFREE_CLEAR(stl_items[j].cmd); } @@ -2046,7 +2053,7 @@ stcsign: // the truncation marker `<` is not counted. int item_offset = trunc_len - 1; - for (int i = item_idx; i < itemcnt; i++) { + for (int i = item_idx; i < itemcnt + evalstart; i++) { // Items starting at or after the end of the truncated section need // to be moved backwards. if (stl_items[i].start >= trunc_end_p) { @@ -2079,7 +2086,7 @@ stcsign: // Find how many separators there are, which we will use when // figuring out how many groups there are. int num_separators = 0; - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].type == Separate) { // Create an array of the start location for each separator mark. stl_separator_locations[num_separators] = i; @@ -2104,7 +2111,7 @@ stcsign: } for (int item_idx = stl_separator_locations[l] + 1; - item_idx < itemcnt; + item_idx < itemcnt + evalstart; item_idx++) { stl_items[item_idx].start += dislocation; } @@ -2118,7 +2125,7 @@ stcsign: if (hltab != NULL) { *hltab = stl_hltab; stl_hlrec_t *sp = stl_hltab; - for (int l = 0; l < itemcnt; l++) { + for (int l = evalstart; l < itemcnt + evalstart; l++) { if (stl_items[l].type == Highlight || stl_items[l].type == HighlightFold || stl_items[l].type == HighlightSign) { sp->start = stl_items[l].start; @@ -2139,7 +2146,7 @@ stcsign: if (tabtab != NULL) { *tabtab = stl_tabtab; StlClickRecord *cur_tab_rec = stl_tabtab; - for (int l = 0; l < itemcnt; l++) { + for (int l = evalstart; l < itemcnt + evalstart; l++) { if (stl_items[l].type == TabPage) { cur_tab_rec->start = stl_items[l].start; if (stl_items[l].minwid == 0) { diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 1d0f181244..50e31ac6a9 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -507,268 +507,263 @@ describe('global statusline', function() end) end) -it('statusline does not crash if it has Arabic characters #19447', function() - clear() - api.nvim_set_option_value('statusline', 'غً', {}) - api.nvim_set_option_value('laststatus', 2, {}) - command('redraw!') - assert_alive() -end) +describe('statusline', function() + local screen + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:add_extra_attr_ids { + [100] = { bold = true, reverse = true, foreground = Screen.colors.Blue }, + [101] = { reverse = true, bold = true, foreground = Screen.colors.SlateBlue }, + } + end) -it('statusline is redrawn with :resize from mapping #19629', function() - clear() - local screen = Screen.new(40, 8) - exec([[ - set laststatus=2 - nnoremap resize -1 - nnoremap resize +1 - ]]) - feed('') - screen:expect([[ - ^ | - {1:~ }|*4 - {3:[No Name] }| - |*2 - ]]) - feed('') - screen:expect([[ - ^ | - {1:~ }|*5 - {3:[No Name] }| - | - ]]) -end) + it('does not crash if it has Arabic characters #19447', function() + api.nvim_set_option_value('statusline', 'غً', {}) + api.nvim_set_option_value('laststatus', 2, {}) + command('redraw!') + assert_alive() + end) -it('showcmdloc=statusline does not show if statusline is too narrow', function() - clear() - local screen = Screen.new(40, 8) - command('set showcmd') - command('set showcmdloc=statusline') - command('1vsplit') - screen:expect([[ - ^ │ | - {1:~}│{1:~ }|*5 - {3:< }{2:[No Name] }| - | - ]]) - feed('1234') - screen:expect_unchanged() -end) + it('is redrawn with :resize from mapping #19629', function() + exec([[ + set laststatus=2 + nnoremap resize -1 + nnoremap resize +1 + ]]) + feed('') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + |*2 + ]]) + feed('') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + end) -it('K_EVENT does not trigger a statusline redraw unnecessarily', function() - clear() - local _ = Screen.new(40, 8) - -- does not redraw on vim.schedule (#17937) - command([[ - set laststatus=2 - let g:counter = 0 - func Status() - let g:counter += 1 - lua vim.schedule(function() end) - return g:counter - endfunc - set statusline=%!Status() - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- also in insert mode - feed('i') - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- does not redraw on timer call (#14303) - command([[ - let g:counter = 0 - func Timer(timer) - endfunc - call timer_start(1, 'Timer', {'repeat': 100}) - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) -end) + it('does not contain showmcd with showcmdloc=statusline when too narrow', function() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {1:~}│{1:~ }|*5 + {3:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() + end) -it('statusline is redrawn on various state changes', function() - clear() - local screen = Screen.new(40, 4) - - -- recording state change #22683 - command('set ls=2 stl=%{repeat(reg_recording(),5)}') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - feed('qQ') - screen:expect([[ - ^ | - {1:~ }| - {3:QQQQQ }| - {5:recording @Q} | - ]]) - feed('q') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - - -- Visual mode change #23932 - command('set ls=2 stl=%{mode(1)}') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) - feed('v') - screen:expect([[ - ^ | - {1:~ }| - {3:v }| - {5:-- VISUAL --} | - ]]) - feed('V') - screen:expect([[ - ^ | - {1:~ }| - {3:V }| - {5:-- VISUAL LINE --} | - ]]) - feed('') - screen:expect([[ - ^ | - {1:~ }| - {3:^V }| - {5:-- VISUAL BLOCK --} | - ]]) - feed('') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) -end) + it('does not redraw unnecessarily after K_EVENT', function() + -- does not redraw on vim.schedule (#17937) + command([[ + set laststatus=2 + let g:counter = 0 + func Status() + let g:counter += 1 + lua vim.schedule(function() end) + return g:counter + endfunc + set statusline=%!Status() + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- also in insert mode + feed('i') + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- does not redraw on timer call (#14303) + command([[ + let g:counter = 0 + func Timer(timer) + endfunc + call timer_start(1, 'Timer', {'repeat': 100}) + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + end) -it('ruler is redrawn in cmdline with redrawstatus #22804', function() - clear() - local screen = Screen.new(40, 2) - command([[ - let g:n = 'initial value' - set ls=1 ru ruf=%{g:n} - redraw - let g:n = 'other value' - redrawstatus - ]]) - screen:expect([[ - ^ | - other value | - ]]) -end) + it('is redrawn on various state changes', function() + -- recording state change #22683 + command('set ls=2 stl=%{repeat(reg_recording(),5)}') + local s1 = [[ + ^ | + {1:~ }|*5 + {3: }| + | + ]] + screen:expect(s1) + feed('qQ') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:QQQQQ }| + {5:recording @Q} | + ]]) + feed('q') + screen:expect(s1) + + -- Visual mode change #23932 + command('set ls=2 stl=%{mode(1)}') + local s2 = [[ + ^ | + {1:~ }|*5 + {3:n }| + | + ]] + screen:expect(s2) + feed('v') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:v }| + {5:-- VISUAL --} | + ]]) + feed('V') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:V }| + {5:-- VISUAL LINE --} | + ]]) + feed('') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:^V }| + {5:-- VISUAL BLOCK --} | + ]]) + feed('') + screen:expect(s2) + end) -it('shows correct ruler in cmdline with no statusline', function() - clear() - local screen = Screen.new(30, 8) - -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. - command [[ - set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 - split - wincmd b - vsplit - wincmd t - wincmd | - mode - ]] - -- Window 1 is current. It has a statusline, so cmdline should show the - -- last window's ruler, which has no statusline. - command '1wincmd w' - screen:expect [[ - ^ | - {1:~ }|*2 - {3:[No Name] 1longlonglong }| - │ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] - -- Window 2 is current. It has no statusline, so cmdline should show its - -- ruler instead. - command '2wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - ^ │ | - {1:~ }│{1:~ }|*2 - 2longlonglong | - ]] - -- Window 3 is current. Cmdline should again show its ruler. - command '3wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - │^ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] -end) + it('ruler is redrawn in cmdline with redrawstatus #22804', function() + command([[ + let g:n = 'initial value' + set ls=1 ru ruf=%{g:n} + redraw + let g:n = 'other value' + redrawstatus + ]]) + screen:expect([[ + ^ | + {1:~ }|*6 + other value | + ]]) + end) -it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() - clear() - local screen = Screen.new(53, 4) - command('hi clear StatusLine') - command('hi clear StatusLineNC') - command('vsplit') - screen:expect { - grid = [[ - ^ │ | - {1:~ }│{1:~ }| - [No Name] [No Name] | - | - ]], - } -end) + it('hidden moves ruler to cmdline', function() + -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. + command [[ + set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 + split + wincmd b + vsplit + wincmd t + wincmd | + mode + ]] + -- Window 1 is current. It has a statusline, so cmdline should show the + -- last window's ruler, which has no statusline. + command '1wincmd w' + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] 1longlonglong }| + │ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + -- Window 2 is current. It has no statusline, so cmdline should show its + -- ruler instead. + command '2wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + ^ │ | + {1:~ }│{1:~ }|*2 + 2longlonglong | + ]]) + -- Window 3 is current. Cmdline should again show its ruler. + command '3wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + │^ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + end) -it('showcmdloc=statusline works with vertical splits', function() - clear() - local screen = Screen.new(53, 4) - command('rightbelow vsplit') - command('set showcmd showcmdloc=statusline') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] }{3:[No Name] 1234 }| - | - ]]) - feed('') - command('set laststatus=3') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] 1234 }| - | - ]]) -end) + it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() + command('hi clear StatusLine') + command('hi clear StatusLineNC') + command('vsplit') + screen:expect([[ + ^ │ | + {1:~ }│{1:~ }|*5 + [No Name] [No Name] | + | + ]]) + end) -it('keymap is shown with vertical splits #27269', function() - clear() - local screen = Screen.new(53, 4) - command('setlocal keymap=dvorak') - command('rightbelow vsplit') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] }{3:[No Name] }| - | - ]]) - command('set laststatus=3') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] }| - | - ]]) + it('showcmdloc=statusline works with vertical splits', function() + command('rightbelow vsplit') + command('set showcmd showcmdloc=statusline') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] }{3:[No Name] 1234 }| + | + ]]) + feed('') + command('set laststatus=3') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] 1234 }| + | + ]]) + end) + + it('keymap is shown with vertical splits #27269', function() + command('setlocal keymap=dvorak') + command('rightbelow vsplit') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] }{3:[No Name] }| + | + ]]) + + command('set laststatus=3') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] }| + | + ]]) + end) + + it("nested call from nvim_eval_statusline() doesn't overwrite items #32259", function() + exec_lua('vim.o.laststatus = 2') + exec_lua([[vim.o.statusline = '%#Special#B:%{nvim_eval_statusline("%f", []).str}']]) + screen:expect([[ + ^ | + {1:~ }|*5 + {101:B:[No Name] }| + | + ]]) + end) end) -- cgit From af069c5c05ad99623345071007ad23da51973601 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 08:09:03 +0800 Subject: vim-patch:9.1.1070: Cannot control cursor positioning of getchar() (#32303) Problem: Cannot control cursor positioning of getchar(). Solution: Add "cursor" flag to {opts}, with possible values "hide", "keep" and "msg". related: vim/vim#10603 closes: vim/vim#16569 https://github.com/vim/vim/commit/edf0f7db28f87611368e158210e58ed30f673098 --- runtime/doc/builtin.txt | 8 +++ runtime/doc/news.txt | 5 ++ runtime/lua/vim/_meta/vimfn.lua | 8 +++ src/nvim/eval.lua | 8 +++ src/nvim/getchar.c | 33 +++++++++-- test/functional/vimscript/getchar_spec.lua | 92 ++++++++++++++++++++++++++++++ test/old/testdir/test_functions.vim | 63 +++++++++++++++++++- 7 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 test/functional/vimscript/getchar_spec.lua diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 3d9010aa2c..96574e2899 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3114,6 +3114,14 @@ getchar([{expr} [, {opts}]]) *getchar()* The optional argument {opts} is a Dict and supports the following items: + cursor A String specifying cursor behavior + when waiting for a character. + "hide": hide the cursor. + "keep": keep current cursor unchanged. + "msg": move cursor to message area. + (default: automagically decide + between "keep" and "msg") + number If |TRUE|, return a Number when getting a single character. If |FALSE|, the return value is always diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 2208428f75..eda8d945cb 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -410,6 +410,11 @@ UI • |:checkhealth| can display in a floating window, controlled by the |g:health| variable. +VIMSCRIPT + +• |getchar()| and |getcharstr()| have optional {opts} |Dict| argument to control: + cursor behavior, return type, and whether to simplify the returned key. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index c0be6b089c..098c0e907a 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2781,6 +2781,14 @@ function vim.fn.getchangelist(buf) end --- The optional argument {opts} is a Dict and supports the --- following items: --- +--- cursor A String specifying cursor behavior +--- when waiting for a character. +--- "hide": hide the cursor. +--- "keep": keep current cursor unchanged. +--- "msg": move cursor to message area. +--- (default: automagically decide +--- between "keep" and "msg") +--- --- number If |TRUE|, return a Number when getting --- a single character. --- If |FALSE|, the return value is always diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 82e3992287..9d787c68ea 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -3507,6 +3507,14 @@ M.funcs = { The optional argument {opts} is a Dict and supports the following items: + cursor A String specifying cursor behavior + when waiting for a character. + "hide": hide the cursor. + "keep": keep current cursor unchanged. + "msg": move cursor to message area. + (default: automagically decide + between "keep" and "msg") + number If |TRUE|, return a Number when getting a single character. If |FALSE|, the return value is always diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index c9d2165fb0..5bf89ee5a8 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1872,9 +1872,11 @@ static int no_reduce_keys = 0; ///< Do not apply modifiers to the key. static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number) FUNC_ATTR_NONNULL_ALL { - varnumber_T n; + varnumber_T n = 0; + const int called_emsg_start = called_emsg; bool error = false; bool simplify = true; + char cursor_flag = NUL; if (argvars[0].v_type != VAR_UNKNOWN && tv_check_for_opt_dict_arg(argvars, 1) == FAIL) { @@ -1888,10 +1890,28 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number allow_number = tv_dict_get_bool(d, "number", true); } else if (tv_dict_has_key(d, "number")) { semsg(_(e_invarg2), "number"); - error = true; } simplify = tv_dict_get_bool(d, "simplify", true); + + const char *cursor_str = tv_dict_get_string(d, "cursor", false); + if (cursor_str != NULL) { + if (strcmp(cursor_str, "hide") != 0 + && strcmp(cursor_str, "keep") != 0 + && strcmp(cursor_str, "msg") != 0) { + semsg(_(e_invargNval), "cursor", cursor_str); + } else { + cursor_flag = cursor_str[0]; + } + } + } + + if (called_emsg != called_emsg_start) { + return; + } + + if (cursor_flag == 'h') { + ui_busy_start(); } no_mapping++; @@ -1899,9 +1919,8 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number if (!simplify) { no_reduce_keys++; } - while (!error) { - if (msg_col > 0) { - // Position the cursor. Needed after a message that ends in a space. + while (true) { + if (cursor_flag == 'm' || (cursor_flag == NUL && msg_col > 0)) { ui_cursor_goto(msg_row, msg_col); } @@ -1945,6 +1964,10 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number no_reduce_keys--; } + if (cursor_flag == 'h') { + ui_busy_stop(); + } + set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); diff --git a/test/functional/vimscript/getchar_spec.lua b/test/functional/vimscript/getchar_spec.lua new file mode 100644 index 0000000000..1327d741cf --- /dev/null +++ b/test/functional/vimscript/getchar_spec.lua @@ -0,0 +1,92 @@ +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local clear = n.clear +local exec = n.exec +local feed = n.feed +local async_command = n.async_meths.nvim_command + +describe('getchar()', function() + before_each(clear) + + -- oldtest: Test_getchar_cursor_position() + it('cursor positioning', function() + local screen = Screen.new(40, 6) + exec([[ + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + ]]) + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + | + ]]) + + -- Default: behaves like "msg" when immediately after printing a message, + -- even if :sleep moved cursor elsewhere. + for _, cmd in ipairs({ + 'echo 1234 | call getchar()', + 'echo 1234 | call getchar(-1, {})', + "echo 1234 | call getchar(-1, #{cursor: 'msg'})", + 'echo 1234 | sleep 1m | call getchar()', + 'echo 1234 | sleep 1m | call getchar(-1, {})', + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'msg'})", + }) do + async_command(cmd) + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234^ | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end + + -- Default: behaves like "keep" when not immediately after printing a message. + for _, cmd in ipairs({ + 'call getchar()', + 'call getchar(-1, {})', + "call getchar(-1, #{cursor: 'keep'})", + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'keep'})", + }) do + async_command(cmd) + screen:expect_unchanged() + feed('a') + screen:expect_unchanged() + end + + async_command("call getchar(-1, #{cursor: 'msg'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + ^1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + + async_command("call getchar(-1, #{cursor: 'hide'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end) +end) diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index f57743900a..738a417b86 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -2458,6 +2458,14 @@ func Test_getchar() call assert_fails('call getchar(1, 1)', 'E1206:') call assert_fails('call getcharstr(1, 1)', 'E1206:') + call assert_fails('call getchar(1, #{cursor: "foo"})', 'E475:') + call assert_fails('call getcharstr(1, #{cursor: "foo"})', 'E475:') + call assert_fails('call getchar(1, #{cursor: 0z})', 'E976:') + call assert_fails('call getcharstr(1, #{cursor: 0z})', 'E976:') + call assert_fails('call getchar(1, #{simplify: 0z})', 'E974:') + call assert_fails('call getcharstr(1, #{simplify: 0z})', 'E974:') + call assert_fails('call getchar(1, #{number: []})', 'E745:') + call assert_fails('call getchar(1, #{number: {}})', 'E728:') call assert_fails('call getcharstr(1, #{number: v:true})', 'E475:') call assert_fails('call getcharstr(1, #{number: v:false})', 'E475:') @@ -2476,10 +2484,59 @@ func Test_getchar() enew! endfunc +func Test_getchar_cursor_position() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + nnoremap echo 1234call getchar() + nnoremap call getchar() + nnoremap call getchar(-1, {}) + nnoremap call getchar(-1, #{cursor: 'msg'}) + nnoremap call getchar(-1, #{cursor: 'keep'}) + nnoremap call getchar(-1, #{cursor: 'hide'}) + END + call writefile(lines, 'XgetcharCursorPos', 'D') + let buf = RunVimInTerminal('-S XgetcharCursorPos', {'rows': 6}) + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + + call term_sendkeys(buf, "\") + call WaitForAssert({-> assert_equal([6, 5], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + + for key in ["\", "\", "\"] + call term_sendkeys(buf, key) + call WaitForAssert({-> assert_equal([6, 1], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + endfor + + call term_sendkeys(buf, "\") + call TermWait(buf, 50) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call TermWait(buf, 50) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + call assert_true(term_getcursor(buf)[2].visible) + + call term_sendkeys(buf, "\") + call WaitForAssert({-> assert_false(term_getcursor(buf)[2].visible)}) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_true(term_getcursor(buf)[2].visible)}) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + + call StopVimInTerminal(buf) +endfunc + func Test_libcall_libcallnr() - if !has('libcall') - return - endif + CheckFeature libcall if has('win32') let libc = 'msvcrt.dll' -- cgit From 0309d3fbf0edc5ac958964f85dff76719340c4c7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 11:11:46 +0800 Subject: vim-patch:8.2.0825: def_function() may return pointer that was freed Problem: def_function() may return pointer that was freed. Solution: Set "fp" to NULL after freeing it. https://github.com/vim/vim/commit/a14e6975478adeddcc2161edc1ec611016aa89f3 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 2e549fcf37..da91de4650 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2867,6 +2867,7 @@ void ex_function(exarg_T *eap) if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); xfree(fp); + fp = NULL; goto erret; } } else { @@ -2887,6 +2888,7 @@ void ex_function(exarg_T *eap) hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); + fp = NULL; goto erret; } fp->uf_refcount = 1; -- cgit From cd42740245b5dd25ef9c7e116656d6da630f5db0 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:15:09 +0800 Subject: vim-patch:8.2.1445: Vim9: function expanded name is cleared when sourcing again Problem: Vim9: function expanded name is cleared when sourcing a script again. Solution: Only clear the expanded name when deleting the function. (closes vim/vim#6707) https://github.com/vim/vim/commit/c4ce36d48698669f81ec90f7c9dc9ab8c362e538 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index da91de4650..5156e431db 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -891,7 +891,6 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); - XFREE_CLEAR(fp->uf_name_exp); if (fp->uf_flags & FC_LUAREF) { api_free_luaref(fp->uf_luaref); @@ -930,6 +929,8 @@ static void func_free(ufunc_T *fp) if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { func_remove(fp); } + + XFREE_CLEAR(fp->uf_name_exp); xfree(fp); } -- cgit From 638c6b406bc41d4fed5ef282bae526888de8229a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:21:57 +0800 Subject: vim-patch:8.2.2505: Vim9: crash after defining function with invalid return type Problem: Vim9: crash after defining function with invalid return type. Solution: Clear function growarrays. Fix memory leak. https://github.com/vim/vim/commit/31842cd0772b557eb9584a13740430db29de8a51 Cherry-pick free_fp from patch 8.2.3812. Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 5156e431db..3538493159 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2515,7 +2515,8 @@ void ex_function(exarg_T *eap) garray_T newlines; int varargs = false; int flags = 0; - ufunc_T *fp; + ufunc_T *fp = NULL; + bool free_fp = false; bool overwrite = false; funcdict_T fudi; static int func_nr = 0; // number for nameless function @@ -2888,8 +2889,7 @@ void ex_function(exarg_T *eap) hashitem_T *hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - fp = NULL; + free_fp = true; goto erret; } fp->uf_refcount = 1; @@ -2920,8 +2920,16 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); ga_clear_strings(&default_args); + if (fp != NULL) { + ga_init(&fp->uf_args, (int)sizeof(char *), 1); + ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); + } errret_2: ga_clear_strings(&newlines); + if (free_fp) { + xfree(fp); + fp = NULL; + } ret_free: xfree(line_to_free); xfree(fudi.fd_newkey); -- cgit From 82b029cbb00dbe9649824f7795c177974955d683 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:45:50 +0800 Subject: vim-patch:9.0.1142: crash and/or memory leak when redefining function Problem: Crash and/or memory leak when redefining function after error. Solution: Clear pointer after making a copy. Clear arrays on failure. (closes vim/vim#11774) https://github.com/vim/vim/commit/f057171d8b562c72334fd7c15c89ff787358ce3a Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 3538493159..6eaccbae62 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2918,13 +2918,14 @@ void ex_function(exarg_T *eap) goto ret_free; erret: - ga_clear_strings(&newargs); - ga_clear_strings(&default_args); if (fp != NULL) { + // these were set to "newargs" and "default_args", which are cleared below ga_init(&fp->uf_args, (int)sizeof(char *), 1); ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); } errret_2: + ga_clear_strings(&newargs); + ga_clear_strings(&default_args); ga_clear_strings(&newlines); if (free_fp) { xfree(fp); -- cgit From b853ef770a25fcd91def6c5016d65c336897c2cc Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 08:23:48 +0800 Subject: vim-patch:9.1.1063: too many strlen() calls in userfunc.c Problem: too many strlen() calls in userfunc.c Solution: refactor userfunc.c and remove calls to strlen(), drop set_ufunc_name() and roll it into alloc_ufunc(), check for out-of-memory condition in trans_function_name_ext() (John Marriott) closes: vim/vim#16537 https://github.com/vim/vim/commit/b32800f7c51c866dc0e87244eb4902540982309d Add missing change to call_user_func() from patch 8.1.1007. Consistently use PRIdSCID instead of PRId64 for script IDs. Co-authored-by: John Marriott --- src/nvim/eval/typval.c | 2 +- src/nvim/eval/typval_defs.h | 7 +- src/nvim/eval/userfunc.c | 184 +++++++++++++++++++++++++++----------------- src/nvim/ex_docmd.c | 3 +- src/nvim/keycodes.c | 2 +- 5 files changed, 122 insertions(+), 76 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 48b2e82c0a..f9cf245e50 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2660,7 +2660,7 @@ int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_le dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); item->di_tv.v_type = VAR_FUNC; - item->di_tv.vval.v_string = xstrdup(fp->uf_name); + item->di_tv.vval.v_string = xmemdupz(fp->uf_name, fp->uf_namelen); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h index 1ec7631c4b..90b0d4e4a9 100644 --- a/src/nvim/eval/typval_defs.h +++ b/src/nvim/eval/typval_defs.h @@ -357,9 +357,10 @@ struct ufunc { funccall_T *uf_scoped; ///< l: local variables for closure char *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with ///< "" as a string, otherwise NULL - char uf_name[]; ///< Name of function (actual size equals name); - ///< can start with 123_ - ///< ( is K_SPECIAL KS_EXTRA KE_SNR) + size_t uf_namelen; ///< Length of uf_name (excluding the NUL) + char uf_name[]; ///< Name of function (actual size equals name); + ///< can start with 123_ + ///< ( is K_SPECIAL KS_EXTRA KE_SNR) }; struct partial_S { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 6eaccbae62..6e7d921068 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -264,25 +264,47 @@ static void register_closure(ufunc_T *fp) [current_funccal->fc_ufuncs.ga_len++] = fp; } +static char lambda_name[8 + NUMBUFLEN]; +static size_t lambda_namelen = 0; + /// @return a name for a lambda. Returned in static memory. char *get_lambda_name(void) { - static char name[30]; static int lambda_no = 0; - snprintf(name, sizeof(name), "%d", ++lambda_no); - return name; + int n = snprintf(lambda_name, sizeof(lambda_name), "%d", ++lambda_no); + if (n < 1) { + lambda_namelen = 0; + } else if (n >= (int)sizeof(lambda_name)) { + lambda_namelen = sizeof(lambda_name) - 1; + } else { + lambda_namelen = (size_t)n; + } + + return lambda_name; +} + +/// Get the length of the last lambda name. +size_t get_lambda_name_len(void) +{ + return lambda_namelen; } -static void set_ufunc_name(ufunc_T *fp, char *name) +/// Allocate a "ufunc_T" for a function called "name". +static ufunc_T *alloc_ufunc(const char *name, size_t namelen) { + size_t len = offsetof(ufunc_T, uf_name) + namelen + 1; + ufunc_T *fp = xcalloc(1, len); STRCPY(fp->uf_name, name); + fp->uf_namelen = namelen; if ((uint8_t)name[0] == K_SPECIAL) { - fp->uf_name_exp = xmalloc(strlen(name) + 3); - STRCPY(fp->uf_name_exp, ""); - strcat(fp->uf_name_exp, fp->uf_name + 3); + len = namelen + 3; + fp->uf_name_exp = xmalloc(len); + snprintf(fp->uf_name_exp, len, "%s", fp->uf_name + 3); } + + return fp; } /// Parse a lambda expression and get a Funcref from "*arg". @@ -350,8 +372,9 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) garray_T newlines; char *name = get_lambda_name(); + size_t namelen = get_lambda_name_len(); - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + fp = alloc_ufunc(name, namelen); pt = xcalloc(1, sizeof(partial_T)); ga_init(&newlines, (int)sizeof(char *), 1); @@ -369,7 +392,6 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) } fp->uf_refcount = 1; - set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); @@ -409,7 +431,10 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) errret: ga_clear_strings(&newargs); - xfree(fp); + if (fp != NULL) { + xfree(fp->uf_name_exp); + xfree(fp); + } xfree(pt); if (evalarg != NULL && evalarg->eval_tofree == NULL) { evalarg->eval_tofree = tofree; @@ -627,33 +652,36 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char int *const error) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - const int llen = eval_fname_script(name); - if (llen == 0) { + const char *script_name = name + eval_fname_script(name); + if (script_name == name) { return (char *)name; // no prefix } fname_buf[0] = (char)K_SPECIAL; fname_buf[1] = (char)KS_EXTRA; fname_buf[2] = KE_SNR; - int i = 3; - if (eval_fname_sid(name)) { // "" or "s:" + size_t fname_buflen = 3; + if (!eval_fname_sid(name)) { // "" or "s:" + fname_buf[fname_buflen] = NUL; + } else { if (current_sctx.sc_sid <= 0) { *error = FCERR_SCRIPT; } else { - snprintf(fname_buf + i, (size_t)(FLEN_FIXED + 1 - i), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)strlen(fname_buf); + fname_buflen += (size_t)snprintf(fname_buf + fname_buflen, + FLEN_FIXED + 1 - fname_buflen, + "%" PRIdSCID "_", + current_sctx.sc_sid); } } + size_t fnamelen = fname_buflen + strlen(script_name); char *fname; - if ((size_t)i + strlen(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); + if (fnamelen < FLEN_FIXED) { + STRCPY(fname_buf + fname_buflen, script_name); fname = fname_buf; } else { - fname = xmalloc((size_t)i + strlen(name + llen) + 1); + fname = xmalloc(fnamelen + 1); *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); + snprintf(fname, fnamelen + 1, "%s%s", fname_buf, script_name); } return fname; } @@ -711,20 +739,20 @@ ufunc_T *find_func(const char *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp) +static int cat_func_name(char *buf, size_t bufsize, ufunc_T *fp) { int len = -1; - size_t uflen = strlen(fp->uf_name); + size_t uflen = fp->uf_namelen; assert(uflen > 0); if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) { - len = snprintf(buf, buflen, "%s", fp->uf_name + 3); + len = snprintf(buf, bufsize, "%s", fp->uf_name + 3); } else { - len = snprintf(buf, buflen, "%s", fp->uf_name); + len = snprintf(buf, bufsize, "%s", fp->uf_name); } - (void)len; // Avoid unused warning on release builds assert(len > 0); + return (len >= (int)bufsize) ? (int)bufsize - 1 : len; } /// Add a number variable "name" to dict "dp" with value "nr". @@ -984,6 +1012,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool islambda = false; char numbuf[NUMBUFLEN]; char *name; + size_t namelen; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; proftime_T wait_start; @@ -1105,23 +1134,25 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett break; } } + + namelen = strlen(name); } else { if ((fp->uf_flags & FC_NOARGS) != 0) { // Bail out if no a: arguments used (in lambda). break; } // "..." argument a:1, a:2, etc. - snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; } - if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { + if (fixvar_idx < FIXVAR_CNT && namelen <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + STRCPY(v->di_key, name); } else { - v = xmalloc(sizeof(dictitem_T) + strlen(name)); - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + v = tv_dict_item_alloc_len(name, namelen); + v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; } - STRCPY(v->di_key, name); // Note: the values are copied directly to avoid alloc/free. // "argvars" must have VAR_FIXED for v_lock. @@ -2096,7 +2127,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part len = (int)(end - lv.ll_name); } - size_t sid_buf_len = 0; + size_t sid_buflen = 0; char sid_buf[20]; // Copy the function name to allocated memory. @@ -2106,15 +2137,16 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part lead = 0; // do nothing } else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid(*pp)) { // It's "s:" or "". if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); goto theend; } - sid_buf_len = - (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", current_sctx.sc_sid); - lead += (int)sid_buf_len; + sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", + current_sctx.sc_sid); + lead += (int)sid_buflen; } } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, (int)lv.ll_name_len)) { semsg(_("E128: Function name must start with a capital or \"s:\": %s"), @@ -2136,8 +2168,8 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part name[0] = (char)K_SPECIAL; name[1] = (char)KS_EXTRA; name[2] = KE_SNR; - if (sid_buf_len > 0) { // If it's "" - memcpy(name + 3, sid_buf, sid_buf_len); + if (sid_buflen > 0) { // If it's "" + memcpy(name + 3, sid_buf, sid_buflen); } } memmove(name + lead, lv.ll_name, (size_t)len); @@ -2173,12 +2205,12 @@ char *get_scriptlocal_funcname(char *funcname) char sid_buf[25]; // Expand s: and prefix into nr_ - snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); + size_t sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", + current_sctx.sc_sid); const int off = *funcname == 's' ? 2 : 5; - char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); - STRCPY(newname, sid_buf); - strcat(newname, funcname + off); + size_t newnamesize = sid_buflen + strlen(funcname + off) + 1; + char *newname = xmalloc(newnamesize); + snprintf(newname, newnamesize, "%s%s", sid_buf, funcname + off); return newname; } @@ -2250,6 +2282,7 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in int ret = FAIL; bool is_heredoc = false; char *heredoc_trimmed = NULL; + size_t heredoc_trimmedlen = 0; bool do_concat = true; while (true) { @@ -2313,19 +2346,18 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in // * ":let {var-name} =<< [trim] {marker}" and "{marker}" if (heredoc_trimmed == NULL || (is_heredoc && skipwhite(theline) == theline) - || strncmp(theline, heredoc_trimmed, - strlen(heredoc_trimmed)) == 0) { + || strncmp(theline, heredoc_trimmed, heredoc_trimmedlen) == 0) { if (heredoc_trimmed == NULL) { p = theline; } else if (is_heredoc) { - p = skipwhite(theline) == theline - ? theline : theline + strlen(heredoc_trimmed); + p = skipwhite(theline) == theline ? theline : theline + heredoc_trimmedlen; } else { - p = theline + strlen(heredoc_trimmed); + p = theline + heredoc_trimmedlen; } if (strcmp(p, skip_until) == 0) { XFREE_CLEAR(skip_until); XFREE_CLEAR(heredoc_trimmed); + heredoc_trimmedlen = 0; do_concat = true; is_heredoc = false; } @@ -2402,7 +2434,7 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' && (!ASCII_ISALPHA(p[2]) || (p[2] == 's')))))) { - skip_until = xstrdup("."); + skip_until = xmemdupz(".", 1); } // heredoc: Check for ":python <di_tv.v_type == VAR_FUNC) { @@ -2815,7 +2850,7 @@ void ex_function(exarg_T *eap) } } } else { - char numbuf[20]; + char numbuf[NUMBUFLEN]; fp = NULL; if (fudi.fd_newkey == NULL && !eap->forceit) { @@ -2835,8 +2870,8 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); - name = xstrdup(numbuf); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); + name = xmemdupz(numbuf, namelen); } if (fp == NULL) { @@ -2860,7 +2895,10 @@ void ex_function(exarg_T *eap) } } - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + if (namelen == 0) { + namelen = strlen(name); + } + fp = alloc_ufunc(name, namelen); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -2877,14 +2915,13 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = xstrdup(name); + fudi.fd_di->di_tv.vval.v_string = xmemdupz(name, namelen); // behave like "dict" was used flags |= FC_DICT; } // insert the new function in the function list - set_ufunc_name(fp, name); if (overwrite) { hashitem_T *hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); @@ -2927,6 +2964,9 @@ errret_2: ga_clear_strings(&newargs); ga_clear_strings(&default_args); ga_clear_strings(&newlines); + if (fp != NULL) { + XFREE_CLEAR(fp->uf_name_exp); + } if (free_fp) { xfree(fp); fp = NULL; @@ -3022,15 +3062,16 @@ char *get_user_func_name(expand_T *xp, int idx) return ""; // don't show dict and lambda functions } - if (strlen(fp->uf_name) + 4 >= IOSIZE) { + if (fp->uf_namelen + 4 >= IOSIZE) { return fp->uf_name; // Prevent overflow. } - cat_func_name(IObuff, IOSIZE, fp); + int len = cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - xstrlcat(IObuff, "(", IOSIZE); + xstrlcpy(IObuff + len, "(", IOSIZE - (size_t)len); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - xstrlcat(IObuff, ")", IOSIZE); + len++; + xstrlcpy(IObuff + len, ")", IOSIZE - (size_t)len); } } return IObuff; @@ -3631,22 +3672,27 @@ bool do_return(exarg_T *eap, bool reanimate, bool is_cmd, void *rettv) char *get_return_cmd(void *rettv) { char *s = NULL; - char *tofree = NULL; + size_t slen = 0; if (rettv != NULL) { + char *tofree = NULL; tofree = s = encode_tv2echo((typval_T *)rettv, NULL); + xfree(tofree); } if (s == NULL) { s = ""; + } else { + slen = strlen(s); } xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); - if (strlen(s) + 8 >= IOSIZE) { + size_t IObufflen = 8 + slen; + if (slen + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); + IObufflen += 3; } - xfree(tofree); - return xstrdup(IObuff); + return xstrnsave(IObuff, IObufflen); } /// Get next function line. @@ -4089,7 +4135,8 @@ bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) char *register_luafunc(LuaRef ref) { char *name = get_lambda_name(); - ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + size_t namelen = get_lambda_name_len(); + ufunc_T *fp = alloc_ufunc(name, namelen); fp->uf_refcount = 1; fp->uf_varargs = true; @@ -4098,7 +4145,6 @@ char *register_luafunc(LuaRef ref) fp->uf_script_ctx = current_sctx; fp->uf_luaref = ref; - STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); // coverity[leaked_storage] diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 137226e2ad..ceb17a995d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7400,8 +7400,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum *errormsg = _(e_usingsid); return NULL; } - snprintf(strbuf, sizeof(strbuf), "%" PRIdSCID "_", - current_sctx.sc_sid); + snprintf(strbuf, sizeof(strbuf), "%" PRIdSCID "_", current_sctx.sc_sid); result = strbuf; break; diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index f7215d3d12..a9f8c9222a 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -917,7 +917,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co result[dlen++] = (char)K_SPECIAL; result[dlen++] = (char)KS_EXTRA; result[dlen++] = KE_SNR; - snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)sid); + snprintf(result + dlen, buf_len - dlen, "%" PRIdSCID, sid); dlen += strlen(result + dlen); result[dlen++] = '_'; continue; -- cgit From 82ac8294c22a15899548a1cb408ca114a598f434 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 16:00:04 +0800 Subject: vim-patch:9.1.1066: heap-use-after-free and stack-use-after-scope with :14verbose Problem: heap-use-after-free and stack-use-after-scope with :14verbose when using :return and :try (after 9.1.1063). Solution: Move back the vim_free(tofree) and the scope of numbuf[]. (zeertzjq) closes: vim/vim#16563 https://github.com/vim/vim/commit/2101230f4013860dbafcb0cab3f4e6bc92fb6f35 --- src/nvim/eval/userfunc.c | 8 ++++---- test/old/testdir/test_user_func.vim | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 6e7d921068..402798cafa 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -3672,12 +3672,11 @@ bool do_return(exarg_T *eap, bool reanimate, bool is_cmd, void *rettv) char *get_return_cmd(void *rettv) { char *s = NULL; + char *tofree = NULL; size_t slen = 0; if (rettv != NULL) { - char *tofree = NULL; tofree = s = encode_tv2echo((typval_T *)rettv, NULL); - xfree(tofree); } if (s == NULL) { s = ""; @@ -3688,10 +3687,11 @@ char *get_return_cmd(void *rettv) xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); size_t IObufflen = 8 + slen; - if (slen + 8 >= IOSIZE) { + if (IObufflen >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); - IObufflen += 3; + IObufflen = IOSIZE - 1; } + xfree(tofree); return xstrnsave(IObuff, IObufflen); } diff --git a/test/old/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim index b509b03778..da6a6d8dc4 100644 --- a/test/old/testdir/test_user_func.vim +++ b/test/old/testdir/test_user_func.vim @@ -910,4 +910,36 @@ func Test_func_curly_brace_invalid_name() delfunc Fail endfunc +func Test_func_return_in_try_verbose() + func TryReturnList() + try + return [1, 2, 3] + endtry + endfunc + func TryReturnNumber() + try + return 123 + endtry + endfunc + func TryReturnOverlongString() + try + return repeat('a', 9999) + endtry + endfunc + + " This should not cause heap-use-after-free + call assert_match('\n:return \[1, 2, 3\] made pending\n', + \ execute('14verbose call TryReturnList()')) + " This should not cause stack-use-after-scope + call assert_match('\n:return 123 made pending\n', + \ execute('14verbose call TryReturnNumber()')) + " An overlong string is truncated + call assert_match('\n:return a\{100,}\.\.\.', + \ execute('14verbose call TryReturnOverlongString()')) + + delfunc TryReturnList + delfunc TryReturnNumber + delfunc TryReturnOverlongString +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From db7db783a2d634d5589ebe12605e3989cb30650c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:49:06 +0800 Subject: vim-patch:9.1.1071: args missing after failing to redefine a function Problem: Arguments of a function are missing after failing to redefine it (after 8.2.2505), and heap-use-after-free with script-local function (after 9.1.1063). Solution: Don't clear arguments or free uf_name_exp when failing to redefine an existing function (zeertzjq) closes: vim/vim#16567 https://github.com/vim/vim/commit/04d2a3fdc051d6a419dc0ea4de7a9640cefccd31 --- src/nvim/eval/userfunc.c | 11 +++++----- test/old/testdir/test_user_func.vim | 40 +++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 402798cafa..8022b37f6b 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2825,11 +2825,11 @@ void ex_function(exarg_T *eap) && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { emsg_funcname(e_funcexts, name); - goto erret; + goto errret_keep; } if (fp->uf_calls > 0) { emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); - goto erret; + goto errret_keep; } if (fp->uf_refcount > 1) { // This function is referenced somewhere, don't redefine it but @@ -2961,9 +2961,6 @@ erret: ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); } errret_2: - ga_clear_strings(&newargs); - ga_clear_strings(&default_args); - ga_clear_strings(&newlines); if (fp != NULL) { XFREE_CLEAR(fp->uf_name_exp); } @@ -2971,6 +2968,10 @@ errret_2: xfree(fp); fp = NULL; } +errret_keep: + ga_clear_strings(&newargs); + ga_clear_strings(&default_args); + ga_clear_strings(&newlines); ret_free: xfree(line_to_free); xfree(fudi.fd_newkey); diff --git a/test/old/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim index da6a6d8dc4..b1543c8f24 100644 --- a/test/old/testdir/test_user_func.vim +++ b/test/old/testdir/test_user_func.vim @@ -421,12 +421,48 @@ func Test_func_def_error() call assert_fails('exe l', 'E717:') " Define an autoload function with an incorrect file name - call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript') + call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript', 'D') call assert_fails('source Xscript', 'E746:') - call delete('Xscript') " Try to list functions using an invalid search pattern call assert_fails('function /\%(/', 'E53:') + + " Use a script-local function to cover uf_name_exp. + func s:TestRedefine(arg1 = 1, arg2 = 10) + let caught_E122 = 0 + try + func s:TestRedefine(arg1 = 1, arg2 = 10) + endfunc + catch /E122:/ + let caught_E122 = 1 + endtry + call assert_equal(1, caught_E122) + + let caught_E127 = 0 + try + func! s:TestRedefine(arg1 = 1, arg2 = 10) + endfunc + catch /E127:/ + let caught_E127 = 1 + endtry + call assert_equal(1, caught_E127) + + " The failures above shouldn't cause heap-use-after-free here. + return [a:arg1 + a:arg2, expand('')] + endfunc + + let stacks = [] + " Call the function twice. + " Failing to redefine a function shouldn't clear its argument list. + for i in range(2) + let [val, stack] = s:TestRedefine(1000) + call assert_equal(1010, val) + call assert_match(expand('') .. 'TestRedefine\[20\]$', stack) + call add(stacks, stack) + endfor + call assert_equal(stacks[0], stacks[1]) + + delfunc s:TestRedefine endfunc " Test for deleting a function -- cgit From 0c8890e7a771ca26c75a767b9851aaf7bf2c0f90 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 12:49:33 +0800 Subject: vim-patch:8.2.4948: cannot use Perl heredoc in nested :def function (#32311) Problem: Cannot use Perl heredoc in nested :def function. (Virginia Senioria) Solution: Only concatenate heredoc lines when not in a nested function. (closes vim/vim#10415) https://github.com/vim/vim/commit/d881d1598467d88808bafd2fa86982ebbca7dcc1 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 58 +++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 8022b37f6b..225880d731 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2471,36 +2471,38 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in is_heredoc = true; } - // Check for ":let v =<< [trim] EOF" - // and ":let [a, b] =<< [trim] EOF" - arg = p; - if (checkforcmd(&arg, "let", 2)) { - int var_count = 0; - int semicolon = 0; - arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); - if (arg != NULL) { - arg = skipwhite(arg); - } - if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { - p = skipwhite(arg + 3); - while (true) { - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); - heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); - continue; - } - if (strncmp(p, "eval", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - continue; + if (!is_heredoc) { + // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" + arg = p; + if (checkforcmd(&arg, "let", 2)) { + int var_count = 0; + int semicolon = 0; + arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); + if (arg != NULL) { + arg = skipwhite(arg); + } + if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { + p = skipwhite(arg + 3); + while (true) { + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + continue; + } + if (strncmp(p, "eval", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + continue; + } + break; } - break; + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; } - skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; } } } -- cgit From 3a2893015759396e5345a1a85e0427705e0570b4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 13:20:48 +0800 Subject: vim-patch:9.0.1391: "clear" macros are not always used (#32312) Problem: "clear" macros are not always used. Solution: Use ALLOC_ONE, VIM_CLEAR, CLEAR_POINTER and CLEAR_FIELD in more places. (Yegappan Lakshmanan, closes vim/vim#12104) https://github.com/vim/vim/commit/960dcbd098c761dd623bec9492d5391ff6e8dceb Co-authored-by: Yegappan Lakshmanan --- src/nvim/debugger.c | 6 ++---- src/nvim/eval/userfunc.c | 6 ++---- src/nvim/option.c | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index f3e4ef0698..e60d04fdfd 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -110,13 +110,11 @@ void do_debug(char *cmd) } if (debug_oldval != NULL) { smsg(0, _("Oldval = \"%s\""), debug_oldval); - xfree(debug_oldval); - debug_oldval = NULL; + XFREE_CLEAR(debug_oldval); } if (debug_newval != NULL) { smsg(0, _("Newval = \"%s\""), debug_newval); - xfree(debug_newval); - debug_newval = NULL; + XFREE_CLEAR(debug_newval); } char *sname = estack_sfile(ESTACK_NONE); if (sname != NULL) { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 225880d731..68bbf76043 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2908,8 +2908,7 @@ void ex_function(exarg_T *eap) fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey); if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); - xfree(fp); - fp = NULL; + XFREE_CLEAR(fp); goto erret; } } else { @@ -2967,8 +2966,7 @@ errret_2: XFREE_CLEAR(fp->uf_name_exp); } if (free_fp) { - xfree(fp); - fp = NULL; + XFREE_CLEAR(fp); } errret_keep: ga_clear_strings(&newargs); diff --git a/src/nvim/option.c b/src/nvim/option.c index f9eb67ff83..796165453c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5226,7 +5226,7 @@ void buf_copy_options(buf_T *buf, int flags) // or to a help buffer. if (dont_do_help) { buf->b_p_isk = save_p_isk; - if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) { + if (p_vts && *p_vts != NUL && !buf->b_p_vts_array) { tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; @@ -5239,7 +5239,7 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, kBufOptTabstop); buf->b_p_vts = xstrdup(p_vts); COPY_OPT_SCTX(buf, kBufOptVartabstop); - if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) { + if (p_vts && *p_vts != NUL && !buf->b_p_vts_array) { tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; -- cgit From 445ecca398401ab9cdada163865db6dee374dde3 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 3 Feb 2025 00:54:31 -0800 Subject: feat(diagnostic): format() can filter diagnostics by returning nil #32302 --- runtime/doc/develop.txt | 4 ++++ runtime/doc/diagnostic.txt | 28 +++++++++++++++++----------- runtime/lua/vim/diagnostic.lua | 31 ++++++++++++++++++++----------- test/functional/lua/diagnostic_spec.lua | 26 ++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 1e4889747d..d3170f114f 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -376,6 +376,10 @@ Where possible, these patterns apply to _both_ Lua and the API: - See |vim.lsp.inlay_hint.enable()| and |vim.lsp.inlay_hint.is_enabled()| for a reference implementation of these "best practices". - NOTE: open questions: https://github.com/neovim/neovim/issues/28603 +- Transformation functions should also have a filter functionality when + appropriate. That is, when the function returns a nil value it "filters" its + input, otherwise the transformed value is used. + - Example: |vim.diagnostic.config.format()| API DESIGN GUIDELINES *dev-api* diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 80197670ee..b7bdf2b446 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -532,11 +532,13 @@ Lua module: vim.diagnostic *diagnostic-api* the buffer. Otherwise, any truthy value means to always show the diagnostic source. Overrides the setting from |vim.diagnostic.config()|. - • {format}? (`fun(diagnostic:vim.Diagnostic): string`) A + • {format}? (`fun(diagnostic:vim.Diagnostic): string?`) A function that takes a diagnostic as input and - returns a string. The return value is the text used - to display the diagnostic. Overrides the setting - from |vim.diagnostic.config()|. + returns a string or nil. If the return value is nil, + the diagnostic is not displayed by the handler. Else + the output text is used to display the diagnostic. + Overrides the setting from + |vim.diagnostic.config()|. • {prefix}? (`string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string)`) Prefix each diagnostic in the floating window: • If a `function`, {i} is the index of the @@ -607,10 +609,11 @@ Lua module: vim.diagnostic *diagnostic-api* Fields: ~ • {current_line}? (`boolean`, default: `false`) Only show diagnostics for the current line. - • {format}? (`fun(diagnostic:vim.Diagnostic): string`) A function - that takes a diagnostic as input and returns a - string. The return value is the text used to display - the diagnostic. + • {format}? (`fun(diagnostic:vim.Diagnostic): string?`) A + function that takes a diagnostic as input and returns + a string or nil. If the return value is nil, the + diagnostic is not displayed by the handler. Else the + output text is used to display the diagnostic. *vim.diagnostic.Opts.VirtualText* @@ -635,9 +638,9 @@ Lua module: vim.diagnostic *diagnostic-api* • {suffix}? (`string|(fun(diagnostic:vim.Diagnostic): string)`) Append diagnostic message with suffix. This can be used to render an LSP diagnostic error code. - • {format}? (`fun(diagnostic:vim.Diagnostic): string`) The - return value is the text used to display the - diagnostic. Example: >lua + • {format}? (`fun(diagnostic:vim.Diagnostic): string?`) If + not nil, the return value is the text used to + display the diagnostic. Example: >lua function(diagnostic) if diagnostic.severity == vim.diagnostic.severity.ERROR then return string.format("E: %s", diagnostic.message) @@ -645,6 +648,9 @@ Lua module: vim.diagnostic *diagnostic-api* return diagnostic.message end < + + If the return value is nil, the diagnostic is + not displayed by the handler. • {hl_mode}? (`'replace'|'combine'|'blend'`) See |nvim_buf_set_extmark()|. • {virt_text}? (`[string,any][]`) See |nvim_buf_set_extmark()|. diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 8044767cb0..972a5d1fa6 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -150,10 +150,11 @@ end --- Overrides the setting from |vim.diagnostic.config()|. --- @field source? boolean|'if_many' --- ---- A function that takes a diagnostic as input and returns a string. ---- The return value is the text used to display the diagnostic. +--- A function that takes a diagnostic as input and returns a string or nil. +--- If the return value is nil, the diagnostic is not displayed by the handler. +--- Else the output text is used to display the diagnostic. --- Overrides the setting from |vim.diagnostic.config()|. ---- @field format? fun(diagnostic:vim.Diagnostic): string +--- @field format? fun(diagnostic:vim.Diagnostic): string? --- --- Prefix each diagnostic in the floating window: --- - If a `function`, {i} is the index of the diagnostic being evaluated and @@ -207,7 +208,7 @@ end --- This can be used to render an LSP diagnostic error code. --- @field suffix? string|(fun(diagnostic:vim.Diagnostic): string) --- ---- The return value is the text used to display the diagnostic. Example: +--- If not nil, the return value is the text used to display the diagnostic. Example: --- ```lua --- function(diagnostic) --- if diagnostic.severity == vim.diagnostic.severity.ERROR then @@ -216,7 +217,8 @@ end --- return diagnostic.message --- end --- ``` ---- @field format? fun(diagnostic:vim.Diagnostic): string +--- If the return value is nil, the diagnostic is not displayed by the handler. +--- @field format? fun(diagnostic:vim.Diagnostic): string? --- --- See |nvim_buf_set_extmark()|. --- @field hl_mode? 'replace'|'combine'|'blend' @@ -239,9 +241,10 @@ end --- (default: `false`) --- @field current_line? boolean --- ---- A function that takes a diagnostic as input and returns a string. ---- The return value is the text used to display the diagnostic. ---- @field format? fun(diagnostic:vim.Diagnostic): string +--- A function that takes a diagnostic as input and returns a string or nil. +--- If the return value is nil, the diagnostic is not displayed by the handler. +--- Else the output text is used to display the diagnostic. +--- @field format? fun(diagnostic:vim.Diagnostic): string? --- @class vim.diagnostic.Opts.Signs --- @@ -503,15 +506,21 @@ local function prefix_source(diagnostics) end, diagnostics) end +--- @param format fun(vim.Diagnostic): string? --- @param diagnostics vim.Diagnostic[] --- @return vim.Diagnostic[] local function reformat_diagnostics(format, diagnostics) vim.validate('format', format, 'function') vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics') - local formatted = vim.deepcopy(diagnostics, true) - for _, diagnostic in ipairs(formatted) do - diagnostic.message = format(diagnostic) + local formatted = {} + for _, diagnostic in ipairs(diagnostics) do + local message = format(diagnostic) + if message ~= nil then + local formatted_diagnostic = vim.deepcopy(diagnostic, true) + formatted_diagnostic.message = message + table.insert(formatted, formatted_diagnostic) + end end return formatted end diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 08c287735e..9a982a1c6d 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -2134,6 +2134,32 @@ describe('vim.diagnostic', function() end) ) end) + + it('can filter diagnostics by returning nil when formatting', function() + local result = exec_lua(function() + vim.diagnostic.config { + virtual_text = { + format = function(diagnostic) + if diagnostic.code == 'foo_err' then + return nil + end + return diagnostic.message + end, + }, + } + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('An error here!', 0, 0, 0, 0, 'foo_server', 'foo_err'), + _G.make_error('An error there!', 1, 1, 1, 1, 'bar_server', 'bar_err'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' An error there!', result[1][4].virt_text[3][1]) + end) end) describe('handlers.virtual_lines', function() -- cgit From 720ec5cec2df6aca08c1410647f01584a48bac35 Mon Sep 17 00:00:00 2001 From: uio23 <98493983+uio23@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:09:47 +1300 Subject: fix(tui): cursor color in suckless terminal #32310 Problem: 's 'guicursor' cursor color not working in suckless terminal (ST). Nvim's builtin terminfo for ST lacks a "Cs" entry, even though ST does support the cursor color to be set via termcodes. Solution: - In `augment_terminfo()`, assume that `st` always supports color cursor. - Thomas Dickey will add a "Cs" entry for st to ncurses, from which Nvim's builtin terminfos are generated. Co-authored-by: Justin M. Keyes --- src/nvim/tui/tui.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f4337d5011..31f95a1006 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -2294,6 +2294,7 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in bool putty = terminfo_is_term_family(term, "putty"); bool screen = terminfo_is_term_family(term, "screen"); bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX"); + bool st = terminfo_is_term_family(term, "st"); bool iterm = terminfo_is_term_family(term, "iterm") || terminfo_is_term_family(term, "iterm2") || terminfo_is_term_family(term, "iTerm.app") @@ -2378,9 +2379,10 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in // would use a tmux control sequence and an extra if(screen) test. tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); - } else if ((xterm || hterm || rxvt || tmux || alacritty) + } else if ((xterm || hterm || rxvt || tmux || alacritty || st) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. + // Supported in st, but currently missing in ncurses definitions. #32217 tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", "\033]12;%p1%s\007"); } -- cgit From d7426bc9e99a44e5c79a3645aa74fc2a300e3ae6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 17 Dec 2024 09:06:26 +0800 Subject: vim-patch:9.1.0935: SpotBugs compiler can be improved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: SpotBugs compiler can be improved Solution: runtime(compiler): Improve defaults and error handling for SpotBugs; update test_compiler.vim (Aliaksei Budavei) runtime(compiler): Improve defaults and error handling for SpotBugs * Keep "spotbugs#DefaultPreCompilerTestAction()" defined but do not assign its Funcref to the "PreCompilerTestAction" key of "g:spotbugs_properties": there are no default and there can only be introduced arbitrary "*sourceDirPath" entries; therefore, this assignment is confusing at best, given that the function's implementation delegates to whatever "PreCompilerAction" is. * Allow for the possibility of relative source pathnames passed as arguments to Vim for the Javac default actions, and the necessity to have them properly reconciled when the current working directory is changed. * Do not expect users to remember or know that new source files ‘must be’ ":argadd"'d to be then known to the Javac default actions; so collect the names of Java-file buffers and Java-file Vim arguments; and let users providing the "@sources" file-lists in the "g:javac_makeprg_params" variable update these file-lists themselves. * Strive to not leave behind a fire-once Syntax ":autocmd" for a Java buffer whenever an arbitrary pre-compile action errors out. * Only attempt to run a post-compiler action in the absence of failures for a pre-compiler action. Note that warnings and failures are treated alike (?!) by the Javac compiler, so when previews are tried out with "--enable-preview", remember about passing "-Xlint:-preview" too to also let SpotBugs have a go. * Properly group conditional operators when testing for key entries in a user-defined variable. * Also test whether "javaExternal" is defined when choosing an implementation for source-file parsing. * Two commands are provided to toggle actions for buffer-local autocommands: - SpotBugsRemoveBufferAutocmd; - SpotBugsDefineBufferAutocmd. For example, try this from "~/.vim/after/ftplugin/java.vim": ------------------------------------------------------------ if exists(':SpotBugsDefineBufferAutocmd') == 2 SpotBugsDefineBufferAutocmd BufWritePost SigUSR1 endif ------------------------------------------------------------ And ":doautocmd java_spotbugs User" can be manually used at will. closes: vim/vim#16140 https://github.com/vim/vim/commit/368ef5a48c7a41af7fe2c32a5d5659e23aff63d0 Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com> --- runtime/autoload/spotbugs.vim | 168 ++++++++++++--- runtime/compiler/spotbugs.vim | 153 ++++++++++---- runtime/doc/quickfix.txt | 90 +++++++-- runtime/ftplugin/java.vim | 232 +++++++++++++++------ test/old/testdir/test_compiler.vim | 404 +++++++++++++++++++++++++++++++++++-- 5 files changed, 876 insertions(+), 171 deletions(-) diff --git a/runtime/autoload/spotbugs.vim b/runtime/autoload/spotbugs.vim index 9161395794..6fd822d68e 100644 --- a/runtime/autoload/spotbugs.vim +++ b/runtime/autoload/spotbugs.vim @@ -1,11 +1,52 @@ -" Default pre- and post-compiler actions for SpotBugs +" Default pre- and post-compiler actions and commands for SpotBugs " Maintainers: @konfekt and @zzzyxwvut -" Last Change: 2024 Nov 27 +" Last Change: 2024 Dec 08 let s:save_cpo = &cpo set cpo&vim -if v:version > 900 +" Look for the setting of "g:spotbugs#state" in "ftplugin/java.vim". +let s:state = get(g:, 'spotbugs#state', {}) +let s:commands = get(s:state, 'commands', {}) +let s:compiler = get(s:state, 'compiler', '') +let s:readable = filereadable($VIMRUNTIME . '/compiler/' . s:compiler . '.vim') + +if has_key(s:commands, 'DefaultPreCompilerCommand') + let g:SpotBugsPreCompilerCommand = s:commands.DefaultPreCompilerCommand +else + + function! s:DefaultPreCompilerCommand(arguments) abort + execute 'make ' . a:arguments + cc + endfunction + + let g:SpotBugsPreCompilerCommand = function('s:DefaultPreCompilerCommand') +endif + +if has_key(s:commands, 'DefaultPreCompilerTestCommand') + let g:SpotBugsPreCompilerTestCommand = s:commands.DefaultPreCompilerTestCommand +else + + function! s:DefaultPreCompilerTestCommand(arguments) abort + execute 'make ' . a:arguments + cc + endfunction + + let g:SpotBugsPreCompilerTestCommand = function('s:DefaultPreCompilerTestCommand') +endif + +if has_key(s:commands, 'DefaultPostCompilerCommand') + let g:SpotBugsPostCompilerCommand = s:commands.DefaultPostCompilerCommand +else + + function! s:DefaultPostCompilerCommand(arguments) abort + execute 'make ' . a:arguments + endfunction + + let g:SpotBugsPostCompilerCommand = function('s:DefaultPostCompilerCommand') +endif + +if v:version > 900 || has('nvim') function! spotbugs#DeleteClassFiles() abort if !exists('b:spotbugs_class_files') @@ -24,7 +65,9 @@ if v:version > 900 " Test the magic number and the major version number (45 for v1.0). " Since v9.0.2027. if len(octad) == 8 && octad[0 : 3] == 0zcafe.babe && - \ or((octad[6] << 8), octad[7]) >= 45 + " Nvim: no << operator + "\ or((octad[6] << 8), octad[7]) >= 45 + \ or((octad[6] * 256), octad[7]) >= 45 echomsg printf('Deleting %s: %d', classname, delete(classname)) endif endif @@ -127,25 +170,21 @@ endif function! spotbugs#DefaultPostCompilerAction() abort " Since v7.4.191. - make %:S + call call(g:SpotBugsPostCompilerCommand, ['%:S']) endfunction -" Look for "spotbugs#compiler" in "ftplugin/java.vim". -let s:compiler = exists('spotbugs#compiler') ? spotbugs#compiler : '' -let s:readable = filereadable($VIMRUNTIME . '/compiler/' . s:compiler . '.vim') - if s:readable && s:compiler ==# 'maven' && executable('mvn') function! spotbugs#DefaultPreCompilerAction() abort call spotbugs#DeleteClassFiles() compiler maven - make compile + call call(g:SpotBugsPreCompilerCommand, ['compile']) endfunction function! spotbugs#DefaultPreCompilerTestAction() abort call spotbugs#DeleteClassFiles() compiler maven - make test-compile + call call(g:SpotBugsPreCompilerTestCommand, ['test-compile']) endfunction function! spotbugs#DefaultProperties() abort @@ -156,10 +195,10 @@ if s:readable && s:compiler ==# 'maven' && executable('mvn') \ function('spotbugs#DefaultPreCompilerTestAction'), \ 'PostCompilerAction': \ function('spotbugs#DefaultPostCompilerAction'), - \ 'sourceDirPath': 'src/main/java', - \ 'classDirPath': 'target/classes', - \ 'testSourceDirPath': 'src/test/java', - \ 'testClassDirPath': 'target/test-classes', + \ 'sourceDirPath': ['src/main/java'], + \ 'classDirPath': ['target/classes'], + \ 'testSourceDirPath': ['src/test/java'], + \ 'testClassDirPath': ['target/test-classes'], \ } endfunction @@ -169,13 +208,13 @@ elseif s:readable && s:compiler ==# 'ant' && executable('ant') function! spotbugs#DefaultPreCompilerAction() abort call spotbugs#DeleteClassFiles() compiler ant - make compile + call call(g:SpotBugsPreCompilerCommand, ['compile']) endfunction function! spotbugs#DefaultPreCompilerTestAction() abort call spotbugs#DeleteClassFiles() compiler ant - make compile-test + call call(g:SpotBugsPreCompilerTestCommand, ['compile-test']) endfunction function! spotbugs#DefaultProperties() abort @@ -186,28 +225,55 @@ elseif s:readable && s:compiler ==# 'ant' && executable('ant') \ function('spotbugs#DefaultPreCompilerTestAction'), \ 'PostCompilerAction': \ function('spotbugs#DefaultPostCompilerAction'), - \ 'sourceDirPath': 'src', - \ 'classDirPath': 'build/classes', - \ 'testSourceDirPath': 'test', - \ 'testClassDirPath': 'build/test/classes', + \ 'sourceDirPath': ['src'], + \ 'classDirPath': ['build/classes'], + \ 'testSourceDirPath': ['test'], + \ 'testClassDirPath': ['build/test/classes'], \ } endfunction unlet s:readable s:compiler elseif s:readable && s:compiler ==# 'javac' && executable('javac') + let s:filename = tempname() function! spotbugs#DefaultPreCompilerAction() abort call spotbugs#DeleteClassFiles() compiler javac if get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')) =~ '\s@\S' - " Read options and filenames from @options [@sources ...]. - make + " Only read options and filenames from @options [@sources ...] and do + " not update these files when filelists change. + call call(g:SpotBugsPreCompilerCommand, ['']) else - " Let Javac figure out what files to compile. - execute 'make ' . join(map(filter(copy(v:argv), - \ "v:val =~# '\\.java\\=$'"), - \ 'shellescape(v:val)'), ' ') + " Collect filenames so that Javac can figure out what to compile. + let filelist = [] + + for arg_num in range(argc(-1)) + let arg_name = argv(arg_num) + + if arg_name =~# '\.java\=$' + call add(filelist, fnamemodify(arg_name, ':p:S')) + endif + endfor + + for buf_num in range(1, bufnr('$')) + if !buflisted(buf_num) + continue + endif + + let buf_name = bufname(buf_num) + + if buf_name =~# '\.java\=$' + let buf_name = fnamemodify(buf_name, ':p:S') + + if index(filelist, buf_name) < 0 + call add(filelist, buf_name) + endif + endif + endfor + + noautocmd call writefile(filelist, s:filename) + call call(g:SpotBugsPreCompilerCommand, [shellescape('@' . s:filename)]) endif endfunction @@ -219,14 +285,13 @@ elseif s:readable && s:compiler ==# 'javac' && executable('javac') return { \ 'PreCompilerAction': \ function('spotbugs#DefaultPreCompilerAction'), - \ 'PreCompilerTestAction': - \ function('spotbugs#DefaultPreCompilerTestAction'), \ 'PostCompilerAction': \ function('spotbugs#DefaultPostCompilerAction'), \ } endfunction - unlet s:readable s:compiler + unlet s:readable s:compiler g:SpotBugsPreCompilerTestCommand + delfunction! s:DefaultPreCompilerTestCommand else function! spotbugs#DefaultPreCompilerAction() abort @@ -241,10 +306,49 @@ else return {} endfunction - unlet s:readable + " XXX: Keep "s:compiler" around for "spotbugs#DefaultPreCompilerAction()", + " "s:DefaultPostCompilerCommand" -- "spotbugs#DefaultPostCompilerAction()". + unlet s:readable g:SpotBugsPreCompilerCommand g:SpotBugsPreCompilerTestCommand + delfunction! s:DefaultPreCompilerCommand + delfunction! s:DefaultPreCompilerTestCommand endif +function! s:DefineBufferAutocmd(event, ...) abort + if !exists('#java_spotbugs#User') + return 1 + endif + + for l:event in insert(copy(a:000), a:event) + if l:event != 'User' + execute printf('silent! autocmd! java_spotbugs %s ', l:event) + execute printf('autocmd java_spotbugs %s doautocmd User', l:event) + endif + endfor + + return 0 +endfunction + +function! s:RemoveBufferAutocmd(event, ...) abort + if !exists('#java_spotbugs') + return 1 + endif + + for l:event in insert(copy(a:000), a:event) + if l:event != 'User' + execute printf('silent! autocmd! java_spotbugs %s ', l:event) + endif + endfor + + return 0 +endfunction + +" Documented in ":help compiler-spotbugs". +command! -bar -nargs=+ -complete=event SpotBugsDefineBufferAutocmd + \ call s:DefineBufferAutocmd() +command! -bar -nargs=+ -complete=event SpotBugsRemoveBufferAutocmd + \ call s:RemoveBufferAutocmd() + let &cpo = s:save_cpo -unlet s:save_cpo +unlet s:commands s:state s:save_cpo " vim: set foldmethod=syntax shiftwidth=2 expandtab: diff --git a/runtime/compiler/spotbugs.vim b/runtime/compiler/spotbugs.vim index 72a5084976..10d164b6af 100644 --- a/runtime/compiler/spotbugs.vim +++ b/runtime/compiler/spotbugs.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Spotbugs (Java static checker; needs javac compiled classes) " Maintainer: @konfekt and @zzzyxwvut -" Last Change: 2024 Nov 27 +" Last Change: 2024 Dec 14 if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9 finish @@ -24,8 +24,9 @@ let s:type_names = '\C\<\%(\.\@1 +"b:" or "g:spotbugs_makeprg_params" variable. For example: >vim let b:spotbugs_makeprg_params = "-longBugCodes -effort:max -low" @@ -1345,22 +1345,25 @@ By default, the class files are searched in the directory where the source files are placed. However, typical Java projects use distinct directories for source files and class files. To make both known to SpotBugs, assign their paths (distinct and relative to their common root directory) to the -following properties (using the example of a common Maven project): > +following properties (using the example of a common Maven project): >vim let g:spotbugs_properties = { - \ 'sourceDirPath': 'src/main/java', - \ 'classDirPath': 'target/classes', - \ 'testSourceDirPath': 'src/test/java', - \ 'testClassDirPath': 'target/test-classes', + \ 'sourceDirPath': ['src/main/java'], + \ 'classDirPath': ['target/classes'], + \ 'testSourceDirPath': ['src/test/java'], + \ 'testClassDirPath': ['target/test-classes'], \ } +Note that source and class path entries are expected to come in pairs: define +both "sourceDirPath" and "classDirPath" when you are considering at least one, +and apply the same logic to "testSourceDirPath" and "testClassDirPath". Note that values for the path keys describe only for SpotBugs where to look for files; refer to the documentation for particular compiler plugins for more information. The default pre- and post-compiler actions are provided for Ant, Maven, and Javac compiler plugins and can be selected by assigning the name of a compiler -plugin to the "compiler" key: > +plugin (`ant`, `maven`, or `javac`) to the "compiler" key: >vim let g:spotbugs_properties = { \ 'compiler': 'maven', @@ -1370,7 +1373,7 @@ This single setting is essentially equivalent to all the settings below, with the exception made for the "PreCompilerAction" and "PreCompilerTestAction" values: their listed |Funcref|s will obtain no-op implementations whereas the implicit Funcrefs of the "compiler" key will obtain the requested defaults if -available. > +available. >vim let g:spotbugs_properties = { \ 'PreCompilerAction': @@ -1379,10 +1382,10 @@ available. > \ function('spotbugs#DefaultPreCompilerTestAction'), \ 'PostCompilerAction': \ function('spotbugs#DefaultPostCompilerAction'), - \ 'sourceDirPath': 'src/main/java', - \ 'classDirPath': 'target/classes', - \ 'testSourceDirPath': 'src/test/java', - \ 'testClassDirPath': 'target/test-classes', + \ 'sourceDirPath': ['src/main/java'], + \ 'classDirPath': ['target/classes'], + \ 'testSourceDirPath': ['src/test/java'], + \ 'testClassDirPath': ['target/test-classes'], \ } With default actions, the compiler of choice will attempt to rebuild the class @@ -1390,23 +1393,42 @@ files for the buffer (and possibly for the whole project) as soon as a Java syntax file is loaded; then, `spotbugs` will attempt to analyze the quality of the compilation unit of the buffer. -When default actions are not suited to a desired workflow, consider writing -arbitrary functions yourself and matching their |Funcref|s to the supported +Vim commands proficient in 'makeprg' [0] can be composed with default actions. +Begin by considering which of the supported keys, "DefaultPreCompilerCommand", +"DefaultPreCompilerTestCommand", or "DefaultPostCompilerCommand", you need to +write an implementation for, observing that each of these keys corresponds to +a particular "*Action" key. Follow it by defining a new function that always +declares an only parameter of type string and puts to use a command equivalent +of |:make|, and assigning its |Funcref| to the selected key. For example: +>vim + function! GenericPostCompilerCommand(arguments) abort + execute 'make ' . a:arguments + endfunction + + let g:spotbugs_properties = { + \ 'DefaultPostCompilerCommand': + \ function('GenericPostCompilerCommand'), + \ } + +When default actions are not suited to a desired workflow, proceed by writing +arbitrary functions yourself and matching their Funcrefs to the supported keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction". The next example re-implements the default pre-compiler actions for a Maven -project and requests other default Maven settings with the "compiler" entry: > - +project and requests other default Maven settings with the "compiler" entry: +>vim function! MavenPreCompilerAction() abort call spotbugs#DeleteClassFiles() compiler maven make compile + cc endfunction function! MavenPreCompilerTestAction() abort call spotbugs#DeleteClassFiles() compiler maven make test-compile + cc endfunction let g:spotbugs_properties = { @@ -1419,14 +1441,46 @@ project and requests other default Maven settings with the "compiler" entry: > Note that all entered custom settings will take precedence over the matching default settings in "g:spotbugs_properties". +Note that it is necessary to notify the plugin of the result of a pre-compiler +action before further work can be undertaken. Using |:cc| after |:make| (or +|:ll| after |:lmake|) as the last command of an action is the supported means +of such communication. + +Two commands, "SpotBugsRemoveBufferAutocmd" and "SpotBugsDefineBufferAutocmd", +are provided to toggle actions for buffer-local autocommands. For example, to +also run actions on any |BufWritePost| and |Signal| event, add these lines to +`~/.config/nvim/after/ftplugin/java.vim`: >vim + + if exists(':SpotBugsDefineBufferAutocmd') == 2 + SpotBugsDefineBufferAutocmd BufWritePost Signal + endif + +Otherwise, you can turn to `:doautocmd java_spotbugs User` at any time. The "g:spotbugs_properties" variable is consulted by the Java filetype plugin (|ft-java-plugin|) to arrange for the described automation, and, therefore, it must be defined before |FileType| events can take place for the buffers loaded with Java source files. It could, for example, be set in a project-local -|vimrc| loaded by [0]. +|vimrc| loaded by [1]. + +Both "g:spotbugs_properties" and "b:spotbugs_properties" are recognized and +must be modifiable (|:unlockvar|). The "*Command" entries are always treated +as global functions to be shared among all Java buffers. + +The SpotBugs Java library and, by extension, its distributed shell scripts do +not support in the `-textui` mode listed pathnames with directory filenames +that contain blank characters [2]. To work around this limitation, consider +making a symbolic link to such a directory from a directory that does not have +blank characters in its name and passing this information to SpotBugs: >vim + + let g:spotbugs_alternative_path = { + \ 'fromPath': 'path/to/dir_without_blanks', + \ 'toPath': 'path/to/dir with blanks', + \ } -[0] https://github.com/MarcWeber/vim-addon-local-vimrc/ +[0] https://github.com/Konfekt/vim-compilers +[1] https://github.com/MarcWeber/vim-addon-local-vimrc +[2] https://github.com/spotbugs/spotbugs/issues/909 GNU MAKE *compiler-make* diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim index 6e12fe2fe5..0fa773335d 100644 --- a/runtime/ftplugin/java.vim +++ b/runtime/ftplugin/java.vim @@ -3,7 +3,7 @@ " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Former Maintainer: Dan Sharp " Repository: https://github.com/zzzyxwvut/java-vim.git -" Last Change: 2024 Nov 24 +" Last Change: 2024 Dec 16 " 2024 Jan 14 by Vim Project (browsefilter) " 2024 May 23 by Riley Bruins ('commentstring') @@ -90,63 +90,155 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") endif endif -" The support for pre- and post-compiler actions for SpotBugs. -if exists("g:spotbugs_properties") && has_key(g:spotbugs_properties, 'compiler') - try - let spotbugs#compiler = g:spotbugs_properties.compiler - let g:spotbugs_properties = extend( - \ spotbugs#DefaultProperties(), - \ g:spotbugs_properties, - \ 'force') - catch - echomsg v:errmsg - finally - call remove(g:spotbugs_properties, 'compiler') - endtry -endif +"""" Support pre- and post-compiler actions for SpotBugs. +if (!empty(get(g:, 'spotbugs_properties', {})) || + \ !empty(get(b:, 'spotbugs_properties', {}))) && + \ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim') + + function! s:SpotBugsGetProperty(name, default) abort + return get( + \ {s:spotbugs_properties_scope}spotbugs_properties, + \ a:name, + \ a:default) + endfunction + + function! s:SpotBugsHasProperty(name) abort + return has_key( + \ {s:spotbugs_properties_scope}spotbugs_properties, + \ a:name) + endfunction + + function! s:SpotBugsGetProperties() abort + return {s:spotbugs_properties_scope}spotbugs_properties + endfunction + + " Work around ":bar"s and ":autocmd"s. + function! s:ExecuteActionOnce(cleanup_cmd, action_cmd) abort + try + execute a:cleanup_cmd + finally + execute a:action_cmd + endtry + endfunction + + if exists("b:spotbugs_properties") + let s:spotbugs_properties_scope = 'b:' + + " Merge global entries, if any, in buffer-local entries, favouring + " defined buffer-local ones. + call extend( + \ b:spotbugs_properties, + \ get(g:, 'spotbugs_properties', {}), + \ 'keep') + elseif exists("g:spotbugs_properties") + let s:spotbugs_properties_scope = 'g:' + endif + + let s:commands = {} + + for s:name in ['DefaultPreCompilerCommand', + \ 'DefaultPreCompilerTestCommand', + \ 'DefaultPostCompilerCommand'] + if s:SpotBugsHasProperty(s:name) + let s:commands[s:name] = remove( + \ s:SpotBugsGetProperties(), + \ s:name) + endif + endfor + + if s:SpotBugsHasProperty('compiler') + " XXX: Postpone loading the script until all state, if any, has been + " collected. + if !empty(s:commands) + let g:spotbugs#state = { + \ 'compiler': remove(s:SpotBugsGetProperties(), 'compiler'), + \ 'commands': copy(s:commands), + \ } + else + let g:spotbugs#state = { + \ 'compiler': remove(s:SpotBugsGetProperties(), 'compiler'), + \ } + endif + + " Merge default entries in global (or buffer-local) entries, favouring + " defined global (or buffer-local) ones. + call extend( + \ {s:spotbugs_properties_scope}spotbugs_properties, + \ spotbugs#DefaultProperties(), + \ 'keep') + elseif !empty(s:commands) + " XXX: Postpone loading the script until all state, if any, has been + " collected. + let g:spotbugs#state = {'commands': copy(s:commands)} + endif -if exists("g:spotbugs_properties") && - \ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim') + unlet s:commands s:name let s:request = 0 - if has_key(g:spotbugs_properties, 'PreCompilerAction') - let s:dispatcher = 'call g:spotbugs_properties.PreCompilerAction() | ' - let s:request += 1 + if s:SpotBugsHasProperty('PostCompilerAction') + let s:request += 4 endif - if has_key(g:spotbugs_properties, 'PreCompilerTestAction') - let s:dispatcher = 'call g:spotbugs_properties.PreCompilerTestAction() | ' + if s:SpotBugsHasProperty('PreCompilerTestAction') + let s:dispatcher = printf('call call(%s, [])', + \ string(s:SpotBugsGetProperties().PreCompilerTestAction)) let s:request += 2 endif - if has_key(g:spotbugs_properties, 'PostCompilerAction') - let s:request += 4 + if s:SpotBugsHasProperty('PreCompilerAction') + let s:dispatcher = printf('call call(%s, [])', + \ string(s:SpotBugsGetProperties().PreCompilerAction)) + let s:request += 1 endif + " Adapt the tests for "s:FindClassFiles()" from "compiler/spotbugs.vim". if (s:request == 3 || s:request == 7) && - \ has_key(g:spotbugs_properties, 'sourceDirPath') && - \ has_key(g:spotbugs_properties, 'testSourceDirPath') - function! s:DispatchAction(path_action_pairs) abort + \ (!empty(s:SpotBugsGetProperty('sourceDirPath', [])) && + \ !empty(s:SpotBugsGetProperty('classDirPath', [])) && + \ !empty(s:SpotBugsGetProperty('testSourceDirPath', [])) && + \ !empty(s:SpotBugsGetProperty('testClassDirPath', []))) + function! s:DispatchAction(paths_action_pairs) abort let name = expand('%:p') - for [path, Action] in a:path_action_pairs - if name =~# (path . '.\{-}\.java\=$') - call Action() - break - endif + for [paths, Action] in a:paths_action_pairs + for path in paths + if name =~# (path . '.\{-}\.java\=$') + call Action() + return + endif + endfor endfor endfunction - let s:dispatcher = printf('call s:DispatchAction(%s) | ', - \ string([[g:spotbugs_properties.sourceDirPath, - \ g:spotbugs_properties.PreCompilerAction], - \ [g:spotbugs_properties.testSourceDirPath, - \ g:spotbugs_properties.PreCompilerTestAction]])) + let s:dir_cnt = min([ + \ len(s:SpotBugsGetProperties().sourceDirPath), + \ len(s:SpotBugsGetProperties().classDirPath)]) + let s:test_dir_cnt = min([ + \ len(s:SpotBugsGetProperties().testSourceDirPath), + \ len(s:SpotBugsGetProperties().testClassDirPath)]) + + " Do not break up path pairs with filtering! + let s:dispatcher = printf('call s:DispatchAction(%s)', + \ string([[s:SpotBugsGetProperties().sourceDirPath[0 : s:dir_cnt - 1], + \ s:SpotBugsGetProperties().PreCompilerAction], + \ [s:SpotBugsGetProperties().testSourceDirPath[0 : s:test_dir_cnt - 1], + \ s:SpotBugsGetProperties().PreCompilerTestAction]])) + unlet s:test_dir_cnt s:dir_cnt + endif + + if exists("s:dispatcher") + function! s:ExecuteActions(pre_action, post_action) abort + try + execute a:pre_action + catch /\ + silent! autocmd! java_spotbugs User silent! autocmd! java_spotbugs Syntax for s:action in s:actions - execute printf('autocmd java_spotbugs %s %s', + if has_key(s:action, 'once') + execute printf('autocmd java_spotbugs %s ' . + \ 'call s:ExecuteActionOnce(%s, %s)', \ s:action.event, - \ s:action.cmd . (has_key(s:action, 'once') - \ ? printf(' | autocmd! java_spotbugs %s ', - \ s:action.event) - \ : '')) + \ string(printf('autocmd! java_spotbugs %s ', + \ s:action.event)), + \ string(s:action.cmd)) + else + execute printf('autocmd java_spotbugs %s %s', + \ s:action.event, + \ s:action.cmd) + endif endfor unlet! s:action s:actions s:idx s:dispatcher endif - unlet s:request + delfunction s:SpotBugsGetProperties + delfunction s:SpotBugsHasProperty + delfunction s:SpotBugsGetProperty + unlet! s:request s:spotbugs_properties_scope endif function! JavaFileTypeCleanUp() abort setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr< unlet! b:browsefilter - " The concatenated removals may be misparsed as a BufWritePost autocmd. - silent! autocmd! java_spotbugs BufWritePost + " The concatenated removals may be misparsed as a User autocmd. + silent! autocmd! java_spotbugs User silent! autocmd! java_spotbugs Syntax endfunction @@ -232,14 +338,16 @@ if exists("s:zip_func_upgradable") endif if exists("*s:DispatchAction") - def! s:DispatchAction(path_action_pairs: list>) + def! s:DispatchAction(paths_action_pairs: list>) const name: string = expand('%:p') - for [path: string, Action: func: any] in path_action_pairs - if name =~# (path .. '.\{-}\.java\=$') - Action() - break - endif + for [paths: list, Action: func: any] in paths_action_pairs + for path in paths + if name =~# (path .. '.\{-}\.java\=$') + Action() + return + endif + endfor endfor enddef endif diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim index 3ad6b365de..e6d5e57824 100644 --- a/test/old/testdir/test_compiler.vim +++ b/test/old/testdir/test_compiler.vim @@ -18,6 +18,8 @@ func Test_compiler() endif e Xfoo.pl + " Play nice with other tests. + defer setqflist([]) compiler perl call assert_equal('perl', b:current_compiler) call assert_fails('let g:current_compiler', 'E121:') @@ -91,6 +93,7 @@ func s:SpotBugsParseFilterMakePrg(dirname, makeprg) endif let offset += 1 + strlen('-sourcepath') let result.sourcepath = matchstr(strpart(a:makeprg, offset), '.\{-}\ze[ \t]') + let offset += 1 + strlen(result.sourcepath) " Get the class file arguments, dropping the pathname prefix. let offset = stridx(a:makeprg, a:dirname, offset) @@ -140,7 +143,8 @@ func Test_compiler_spotbugs_makeprg() " THE EXPECTED RESULTS. let results = {} let results['Xspotbugs/src/tests/𐌂1.java'] = { - \ 'sourcepath': '%:p:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/𐌂1.java', + \ ':p:h:S')}, \ 'classfiles': sort([ \ 'Xspotbugs/tests/𐌂1$1.class', \ 'Xspotbugs/tests/𐌂1$1𐌉𐌉1.class', @@ -153,11 +157,12 @@ func Test_compiler_spotbugs_makeprg() \ } " No class file for an empty source file even with "-Xpkginfo:always". let results['Xspotbugs/src/tests/package-info.java'] = { - \ 'sourcepath': '', + \ 'Sourcepath': {-> ''}, \ 'classfiles': [], \ } let results['Xspotbugs/src/tests/α/𐌂1.java'] = { - \ 'sourcepath': '%:p:h:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/𐌂1.java', + \ ':p:h:h:S')}, \ 'classfiles': sort([ \ 'Xspotbugs/tests/α/𐌂1$1.class', \ 'Xspotbugs/tests/α/𐌂1$1𐌉𐌉1.class', @@ -169,11 +174,13 @@ func Test_compiler_spotbugs_makeprg() \ 'Xspotbugs/tests/α/𐌂2.class']), \ } let results['Xspotbugs/src/tests/α/package-info.java'] = { - \ 'sourcepath': '%:p:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/package-info.java', + \ ':p:h:S')}, \ 'classfiles': ['Xspotbugs/tests/α/package-info.class'], \ } let results['Xspotbugs/src/tests/α/β/𐌂1.java'] = { - \ 'sourcepath': '%:p:h:h:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/𐌂1.java', + \ ':p:h:h:h:S')}, \ 'classfiles': sort([ \ 'Xspotbugs/tests/α/β/𐌂1$1.class', \ 'Xspotbugs/tests/α/β/𐌂1$1𐌉𐌉1.class', @@ -185,11 +192,13 @@ func Test_compiler_spotbugs_makeprg() \ 'Xspotbugs/tests/α/β/𐌂2.class']), \ } let results['Xspotbugs/src/tests/α/β/package-info.java'] = { - \ 'sourcepath': '%:p:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/package-info.java', + \ ':p:h:S')}, \ 'classfiles': ['Xspotbugs/tests/α/β/package-info.class'], \ } let results['Xspotbugs/src/tests/α/β/γ/𐌂1.java'] = { - \ 'sourcepath': '%:p:h:h:h:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/𐌂1.java', + \ ':p:h:h:h:h:S')}, \ 'classfiles': sort([ \ 'Xspotbugs/tests/α/β/γ/𐌂1$1.class', \ 'Xspotbugs/tests/α/β/γ/𐌂1$1𐌉𐌉1.class', @@ -201,11 +210,13 @@ func Test_compiler_spotbugs_makeprg() \ 'Xspotbugs/tests/α/β/γ/𐌂2.class']), \ } let results['Xspotbugs/src/tests/α/β/γ/package-info.java'] = { - \ 'sourcepath': '%:p:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/package-info.java', + \ ':p:h:S')}, \ 'classfiles': ['Xspotbugs/tests/α/β/γ/package-info.class'], \ } let results['Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java'] = { - \ 'sourcepath': '%:p:h:h:h:h:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java', + \ ':p:h:h:h:h:h:S')}, \ 'classfiles': sort([ \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1.class', \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1𐌉𐌉1.class', @@ -217,14 +228,15 @@ func Test_compiler_spotbugs_makeprg() \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2.class']), \ } let results['Xspotbugs/src/tests/α/β/γ/δ/package-info.java'] = { - \ 'sourcepath': '%:p:h:S', + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/δ/package-info.java', + \ ':p:h:S')}, \ 'classfiles': ['Xspotbugs/tests/α/β/γ/δ/package-info.class'], \ } " MAKE CLASS FILES DISCOVERABLE! let g:spotbugs_properties = { - \ 'sourceDirPath': 'src/tests', - \ 'classDirPath': 'tests', + \ 'sourceDirPath': ['src/tests'], + \ 'classDirPath': ['tests'], \ } call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'sourcepath')) @@ -249,20 +261,22 @@ func Test_compiler_spotbugs_makeprg() let package_file = src_dir .. 'package-info.java' call writefile([package], src_dir .. 'package-info.java') - for s in ['on', 'off'] + " Note that using "off" for the first _outer_ iteration is preferable + " because only then "hlexists()" may be 0 (see "compiler/spotbugs.vim"). + for s in ['off', 'on'] execute 'syntax ' .. s execute 'edit ' .. type_file compiler spotbugs let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg) - call assert_equal(results[type_file].sourcepath, result.sourcepath) + call assert_equal(results[type_file].Sourcepath(), result.sourcepath) call assert_equal(results[type_file].classfiles, result.classfiles) bwipeout execute 'edit ' .. package_file compiler spotbugs let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg) - call assert_equal(results[package_file].sourcepath, result.sourcepath) + call assert_equal(results[package_file].Sourcepath(), result.sourcepath) call assert_equal(results[package_file].classfiles, result.classfiles) bwipeout endfor @@ -271,4 +285,364 @@ func Test_compiler_spotbugs_makeprg() let &shellslash = save_shellslash endfunc +func s:SpotBugsBeforeFileTypeTryPluginAndClearCache(state) + " Ponder over "extend(spotbugs#DefaultProperties(), g:spotbugs_properties)" + " in "ftplugin/java.vim". + let g:spotbugs#state = a:state + runtime autoload/spotbugs.vim +endfunc + +func Test_compiler_spotbugs_properties() + let save_shellslash = &shellslash + set shellslash + setlocal makeprg= + filetype plugin on + + call assert_true(mkdir('Xspotbugs/src', 'pR')) + call assert_true(mkdir('Xspotbugs/tests', 'pR')) + let type_file = 'Xspotbugs/src/𐌄.java' + let test_file = 'Xspotbugs/tests/𐌄$.java' + call writefile(['enum 𐌄{}'], type_file) + call writefile(['class 𐌄${}'], test_file) + + " TEST INTEGRATION WITH A BOGUS COMPILER PLUGIN. + if !filereadable($VIMRUNTIME .. '/compiler/foo.vim') && !executable('foo') + let g:spotbugs_properties = {'compiler': 'foo'} + " XXX: In case this "if" block is no longer first. + call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({ + \ 'compiler': g:spotbugs_properties.compiler, + \ }) + execute 'edit ' .. type_file + call assert_equal('java', &l:filetype) + " This variable will indefinitely keep the compiler name. + call assert_equal('foo', g:spotbugs#state.compiler) + " The "compiler" entry should be gone after FileType and default entries + " should only appear for a supported compiler. + call assert_false(has_key(g:spotbugs_properties, 'compiler')) + call assert_true(empty(g:spotbugs_properties)) + " Query default implementations. + call assert_true(exists('*spotbugs#DefaultProperties')) + call assert_true(exists('*spotbugs#DefaultPreCompilerAction')) + call assert_true(exists('*spotbugs#DefaultPreCompilerTestAction')) + call assert_true(empty(spotbugs#DefaultProperties())) + " Get a ":message". + redir => out + call spotbugs#DefaultPreCompilerAction() + redir END + call assert_equal('Not supported: "foo"', out[stridx(out, 'Not') :]) + " Get a ":message". + redir => out + call spotbugs#DefaultPreCompilerTestAction() + redir END + call assert_equal('Not supported: "foo"', out[stridx(out, 'Not') :]) + " No ":autocmd"s without one of "PreCompiler*Action", "PostCompilerAction". + call assert_false(exists('#java_spotbugs')) + bwipeout + endif + + let s:spotbugs_results = { + \ 'preActionDone': 0, + \ 'preTestActionDone': 0, + \ 'preTestLocalActionDone': 0, + \ 'postActionDone': 0, + \ 'preCommandArguments': '', + \ 'preTestCommandArguments': '', + \ 'postCommandArguments': '', + \ } + defer execute('unlet s:spotbugs_results') + + func! g:SpotBugsPreAction() abort + let s:spotbugs_results.preActionDone = 1 + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreAction') + + func! g:SpotBugsPreTestAction() abort + let s:spotbugs_results.preTestActionDone = 1 + " XXX: Let see compilation fail. + throw 'Oops' + endfunc + defer execute('delfunction g:SpotBugsPreTestAction') + + func! g:SpotBugsPreTestLocalAction() abort + let s:spotbugs_results.preTestLocalActionDone = 1 + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreTestLocalAction') + + func! g:SpotBugsPostAction() abort + let s:spotbugs_results.postActionDone = 1 + endfunc + defer execute('delfunction g:SpotBugsPostAction') + + func! g:SpotBugsPreCommand(arguments) abort + let s:spotbugs_results.preActionDone = 1 + let s:spotbugs_results.preCommandArguments = a:arguments + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreCommand') + + func! g:SpotBugsPreTestCommand(arguments) abort + let s:spotbugs_results.preTestActionDone = 1 + let s:spotbugs_results.preTestCommandArguments = a:arguments + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreTestCommand') + + func! g:SpotBugsPostCommand(arguments) abort + let s:spotbugs_results.postActionDone = 1 + let s:spotbugs_results.postCommandArguments = a:arguments + endfunc + defer execute('delfunction g:SpotBugsPostCommand') + + " TEST INTEGRATION WITH A SUPPORTED COMPILER PLUGIN. + if filereadable($VIMRUNTIME .. '/compiler/maven.vim') + if !executable('mvn') + if has('win32') + " This is what ":help executable()" suggests. + call writefile([], 'Xspotbugs/mvn.exe') + else + let $PATH = 'Xspotbugs:' .. $PATH + call writefile([], 'Xspotbugs/mvn') + call setfperm('Xspotbugs/mvn', 'rwx------') + endif + endif + + let g:spotbugs_properties = { + \ 'compiler': 'maven', + \ 'PreCompilerAction': function('g:SpotBugsPreAction'), + \ 'PreCompilerTestAction': function('g:SpotBugsPreTestAction'), + \ 'PostCompilerAction': function('g:SpotBugsPostAction'), + \ } + " XXX: In case this is a runner-up ":edit". + call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({ + \ 'compiler': g:spotbugs_properties.compiler, + \ }) + execute 'edit ' .. type_file + call assert_equal('java', &l:filetype) + call assert_equal('maven', g:spotbugs#state.compiler) + call assert_false(has_key(g:spotbugs_properties, 'compiler')) + call assert_false(empty(g:spotbugs_properties)) + " Query default implementations. + call assert_true(exists('*spotbugs#DefaultProperties')) + call assert_equal(sort([ + \ 'PreCompilerAction', + \ 'PreCompilerTestAction', + \ 'PostCompilerAction', + \ 'sourceDirPath', + \ 'classDirPath', + \ 'testSourceDirPath', + \ 'testClassDirPath', + \ ]), + \ sort(keys(spotbugs#DefaultProperties()))) + " Some ":autocmd"s with one of "PreCompiler*Action", "PostCompilerAction". + call assert_true(exists('#java_spotbugs')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + call assert_equal(2, exists(':SpotBugsDefineBufferAutocmd')) + " SpotBugsDefineBufferAutocmd SigUSR1 User SigUSR1 User SigUSR1 User + " call assert_true(exists('#java_spotbugs#SigUSR1')) + SpotBugsDefineBufferAutocmd Signal User Signal User Signal User + call assert_true(exists('#java_spotbugs#Signal')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + call assert_equal(2, exists(':SpotBugsRemoveBufferAutocmd')) + " SpotBugsRemoveBufferAutocmd SigUSR1 User SigUSR1 User UserGettingBored + " call assert_false(exists('#java_spotbugs#SigUSR1')) + SpotBugsRemoveBufferAutocmd Signal User Signal User UserGettingBored + call assert_false(exists('#java_spotbugs#Signal')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + + doautocmd java_spotbugs Syntax + call assert_false(exists('#java_spotbugs#Syntax')) + + " No match: "type_file !~# 'src/main/java'". + call assert_false(s:spotbugs_results.preActionDone) + " No match: "type_file !~# 'src/test/java'". + call assert_false(s:spotbugs_results.preTestActionDone) + " No pre-match, no post-action. + call assert_false(s:spotbugs_results.postActionDone) + " Without a match, confirm that ":compiler spotbugs" has NOT run. + call assert_true(empty(&l:makeprg)) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + " Update path entries. (Note that we cannot use just "src" because there + " is another "src" directory nearer the filesystem root directory, i.e. + " "vim/vim/src/testdir/Xspotbugs/src", and "s:DispatchAction()" (see + " "ftplugin/java.vim") will match "vim/vim/src/testdir/Xspotbugs/tests" + " against "src".) + let g:spotbugs_properties.sourceDirPath = ['Xspotbugs/src'] + let g:spotbugs_properties.classDirPath = ['Xspotbugs/src'] + let g:spotbugs_properties.testSourceDirPath = ['tests'] + let g:spotbugs_properties.testClassDirPath = ['tests'] + + doautocmd java_spotbugs User + " No match: "type_file !~# 'src/main/java'" (with old "*DirPath" values + " cached). + call assert_false(s:spotbugs_results.preActionDone) + " No match: "type_file !~# 'src/test/java'" (with old "*DirPath" values + " cached). + call assert_false(s:spotbugs_results.preTestActionDone) + " No pre-match, no post-action. + call assert_false(s:spotbugs_results.postActionDone) + " Without a match, confirm that ":compiler spotbugs" has NOT run. + call assert_true(empty(&l:makeprg)) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + " XXX: Re-build ":autocmd"s from scratch with new values applied. + doautocmd FileType + + call assert_true(exists('b:spotbugs_syntax_once')) + doautocmd java_spotbugs User + " A match: "type_file =~# 'Xspotbugs/src'" (with new "*DirPath" values + " cached). + call assert_true(s:spotbugs_results.preActionDone) + " No match: "type_file !~# 'tests'" (with new "*DirPath" values cached). + call assert_false(s:spotbugs_results.preTestActionDone) + " For a pre-match, a post-action. + call assert_true(s:spotbugs_results.postActionDone) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + bwipeout + setlocal makeprg= + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + + execute 'edit ' .. test_file + " Prepare a buffer-local, incomplete variant of properties, relying on + " "ftplugin/java.vim" to take care of merging in unique entries, if any, + " from "g:spotbugs_properties". + let b:spotbugs_properties = { + \ 'PreCompilerTestAction': function('g:SpotBugsPreTestLocalAction'), + \ } + call assert_equal('java', &l:filetype) + call assert_true(exists('#java_spotbugs')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + call assert_fails('doautocmd java_spotbugs Syntax', 'Oops') + call assert_false(exists('#java_spotbugs#Syntax')) + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestActionDone) + call assert_false(s:spotbugs_results.preTestLocalActionDone) + " No action after pre-failure (the thrown "Oops" doesn't qualify for ":cc"). + call assert_false(s:spotbugs_results.postActionDone) + " No ":compiler spotbugs" will be run after pre-failure. + call assert_true(empty(&l:makeprg)) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + " XXX: Re-build ":autocmd"s from scratch with buffer-local values applied. + doautocmd FileType + + call assert_true(exists('b:spotbugs_syntax_once')) + doautocmd java_spotbugs User + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestLocalActionDone) + call assert_false(s:spotbugs_results.preTestActionDone) + " For a pre-match, a post-action. + call assert_true(s:spotbugs_results.postActionDone) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + setlocal makeprg= + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + let s:spotbugs_results.preCommandArguments = '' + let s:spotbugs_results.preTestCommandArguments = '' + let s:spotbugs_results.postCommandArguments = '' + " XXX: Compose the assigned "*Command"s with the default Maven "*Action"s. + let b:spotbugs_properties = { + \ 'compiler': 'maven', + \ 'DefaultPreCompilerTestCommand': function('g:SpotBugsPreTestCommand'), + \ 'DefaultPreCompilerCommand': function('g:SpotBugsPreCommand'), + \ 'DefaultPostCompilerCommand': function('g:SpotBugsPostCommand'), + \ 'sourceDirPath': ['Xspotbugs/src'], + \ 'classDirPath': ['Xspotbugs/src'], + \ 'testSourceDirPath': ['tests'], + \ 'testClassDirPath': ['tests'], + \ } + unlet g:spotbugs_properties + " XXX: Re-build ":autocmd"s from scratch with buffer-local values applied. + call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({ + \ 'compiler': b:spotbugs_properties.compiler, + \ 'commands': { + \ 'DefaultPreCompilerTestCommand': + \ b:spotbugs_properties.DefaultPreCompilerTestCommand, + \ 'DefaultPreCompilerCommand': + \ b:spotbugs_properties.DefaultPreCompilerCommand, + \ 'DefaultPostCompilerCommand': + \ b:spotbugs_properties.DefaultPostCompilerCommand, + \ }, + \ }) + doautocmd FileType + + call assert_equal('maven', g:spotbugs#state.compiler) + call assert_equal(sort([ + \ 'DefaultPreCompilerTestCommand', + \ 'DefaultPreCompilerCommand', + \ 'DefaultPostCompilerCommand', + \ ]), + \ sort(keys(g:spotbugs#state.commands))) + call assert_true(exists('b:spotbugs_syntax_once')) + doautocmd java_spotbugs User + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + call assert_true(empty(s:spotbugs_results.preCommandArguments)) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestActionDone) + call assert_equal('test-compile', s:spotbugs_results.preTestCommandArguments) + " For a pre-match, a post-action. + call assert_true(s:spotbugs_results.postActionDone) + call assert_equal('%:S', s:spotbugs_results.postCommandArguments) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + bwipeout + setlocal makeprg= + endif + + filetype plugin off + setlocal makeprg= + let &shellslash = save_shellslash +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 89c294514853ee4849855f880d6d7ff349494c4d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 28 Dec 2024 07:32:08 +0800 Subject: vim-patch:9.1.0967: SpotBugs compiler setup can be further improved Problem: SpotBugs compiler can be further improved Solution: Introduce event-driven primitives for SpotBugs (Aliaksei Budavei) closes: vim/vim#16258 https://github.com/vim/vim/commit/2e252474c4df5018b9819d86ebb70bf3b1b1a1af Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com> --- runtime/compiler/spotbugs.vim | 4 +-- runtime/doc/quickfix.txt | 64 ++++++++++++++++++++++++++++++++++++++ runtime/ftplugin/java.vim | 46 ++++++++++++++++++++++++--- test/old/testdir/test_compiler.vim | 64 +++++++++++++++++++++++++++++++++++++- 4 files changed, 170 insertions(+), 8 deletions(-) diff --git a/runtime/compiler/spotbugs.vim b/runtime/compiler/spotbugs.vim index 10d164b6af..8ed45f8ee0 100644 --- a/runtime/compiler/spotbugs.vim +++ b/runtime/compiler/spotbugs.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Spotbugs (Java static checker; needs javac compiled classes) -" Maintainer: @konfekt and @zzzyxwvut -" Last Change: 2024 Dec 14 +" Maintainers: @konfekt and @zzzyxwvut +" Last Change: 2024 Dec 20 if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9 finish diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index e56de9c1b4..7aeb494437 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -1410,6 +1410,70 @@ of |:make|, and assigning its |Funcref| to the selected key. For example: \ function('GenericPostCompilerCommand'), \ } +When "PostCompilerAction" is available, "PostCompilerActionExecutor" is also +supported. Its value must be a Funcref pointing to a function that always +declares a single parameter of type string and decides whether |:execute| can +be dispatched on its argument, containing a pending post-compiler action, +after ascertaining the current status of |:cc| (or |:ll|): >vim + + function! GenericPostCompilerActionExecutor(action) abort + try + cc + catch /\vim + + function! GenericPreCompilerCommand(arguments) abort + if !exists('g:spotbugs_compilation_done') + doautocmd java_spotbugs_post User + execute 'make ' . a:arguments + " only run doautocmd when :make was synchronous + " see note below + doautocmd java_spotbugs_post ShellCmdPost " XXX: (a) + let g:spotbugs_compilation_done = 1 + else + cc + endif + endfunction + + function! GenericPreCompilerTestCommand(arguments) abort + if !exists('g:spotbugs_test_compilation_done') + doautocmd java_spotbugs_post User + execute 'make ' . a:arguments + " only run doautocmd when :make was synchronous + " see note below + doautocmd java_spotbugs_post ShellCmdPost " XXX: (b) + let g:spotbugs_test_compilation_done = 1 + else + cc + endif + endfunction + + let g:spotbugs_properties = { + \ 'compiler': 'maven', + \ 'DefaultPreCompilerCommand': + \ function('GenericPreCompilerCommand'), + \ 'DefaultPreCompilerTestCommand': + \ function('GenericPreCompilerTestCommand'), + \ 'PostCompilerActionExecutor': + \ function('GenericPostCompilerActionExecutor'), + \ } + +If a command equivalent of `:make` is capable of asynchronous execution and +consuming `ShellCmdPost` events, `:doautocmd java_spotbugs_post ShellCmdPost` +must be removed from such "*Action" (or "*Command") implementations (i.e. the +lines `(a)` and `(b)` in the listed examples) to retain a sequential order for +non-blocking execution, and any notification (see below) must be suppressed. +A `ShellCmdPost` `:autocmd` can be associated with any |:augroup| by assigning +its name to the "augroupForPostCompilerAction" key. + When default actions are not suited to a desired workflow, proceed by writing arbitrary functions yourself and matching their Funcrefs to the supported keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction". diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim index 0fa773335d..cfd25bce6c 100644 --- a/runtime/ftplugin/java.vim +++ b/runtime/ftplugin/java.vim @@ -3,7 +3,7 @@ " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Former Maintainer: Dan Sharp " Repository: https://github.com/zzzyxwvut/java-vim.git -" Last Change: 2024 Dec 16 +" Last Change: 2024 Dec 25 " 2024 Jan 14 by Vim Project (browsefilter) " 2024 May 23 by Riley Bruins ('commentstring') @@ -113,7 +113,7 @@ if (!empty(get(g:, 'spotbugs_properties', {})) || endfunction " Work around ":bar"s and ":autocmd"s. - function! s:ExecuteActionOnce(cleanup_cmd, action_cmd) abort + function! JavaFileTypeExecuteActionOnce(cleanup_cmd, action_cmd) abort try execute a:cleanup_cmd finally @@ -285,7 +285,7 @@ if (!empty(get(g:, 'spotbugs_properties', {})) || for s:action in s:actions if has_key(s:action, 'once') execute printf('autocmd java_spotbugs %s ' . - \ 'call s:ExecuteActionOnce(%s, %s)', + \ 'call JavaFileTypeExecuteActionOnce(%s, %s)', \ s:action.event, \ string(printf('autocmd! java_spotbugs %s ', \ s:action.event)), @@ -297,7 +297,41 @@ if (!empty(get(g:, 'spotbugs_properties', {})) || endif endfor - unlet! s:action s:actions s:idx s:dispatcher + if s:SpotBugsHasProperty('PostCompilerActionExecutor') && + \ (s:request == 7 || s:request == 6 || + \ s:request == 5 || s:request == 4) + let s:augroup = s:SpotBugsGetProperty( + \ 'augroupForPostCompilerAction', + \ 'java_spotbugs_post') + let s:augroup = !empty(s:augroup) ? s:augroup : 'java_spotbugs_post' + + for s:candidate in ['java_spotbugs_post', s:augroup] + if !exists("#" . s:candidate) + execute printf('augroup %s | augroup END', s:candidate) + endif + endfor + + silent! autocmd! java_spotbugs_post User + + " Define a User ":autocmd" to define a once-only ShellCmdPost + " ":autocmd" that will invoke "PostCompilerActionExecutor" and let + " it decide whether to proceed with ":compiler spotbugs" etc.; and + " seek explicit synchronisation with ":doautocmd ShellCmdPost" by + " omitting "nested" for "java_spotbugs_post" and "java_spotbugs". + execute printf('autocmd java_spotbugs_post User ' . + \ 'call JavaFileTypeExecuteActionOnce(%s, %s)', + \ string(printf('autocmd! %s ShellCmdPost ', s:augroup)), + \ string(printf('autocmd %s ShellCmdPost ' . + \ 'call JavaFileTypeExecuteActionOnce(%s, %s)', + \ s:augroup, + \ string(printf('autocmd! %s ShellCmdPost ', s:augroup)), + \ string(printf('call call(%s, [%s])', + \ string(s:SpotBugsGetProperties().PostCompilerActionExecutor), + \ string(printf('compiler spotbugs | call call(%s, [])', + \ string(s:SpotBugsGetProperties().PostCompilerAction)))))))) + endif + + unlet! s:candidate s:augroup s:action s:actions s:idx s:dispatcher endif delfunction s:SpotBugsGetProperties @@ -310,9 +344,11 @@ function! JavaFileTypeCleanUp() abort setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr< unlet! b:browsefilter - " The concatenated removals may be misparsed as a User autocmd. + " The concatenated ":autocmd" removals may be misparsed as an ":autocmd". + " A _once-only_ ShellCmdPost ":autocmd" is always a call-site definition. silent! autocmd! java_spotbugs User silent! autocmd! java_spotbugs Syntax + silent! autocmd! java_spotbugs_post User endfunction " Undo the stuff we changed. diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim index e6d5e57824..f9e7bf042d 100644 --- a/test/old/testdir/test_compiler.vim +++ b/test/old/testdir/test_compiler.vim @@ -399,12 +399,22 @@ func Test_compiler_spotbugs_properties() endfunc defer execute('delfunction g:SpotBugsPostCommand') + func! g:SpotBugsPostCompilerActionExecutor(action) abort + try + " XXX: Notify the spotbugs compiler about success or failure. + cc + catch /\ Date: Tue, 4 Feb 2025 06:45:14 +0800 Subject: vim-patch:9.1.1073: tests: test_compiler fails on Windows without Maven Problem: tests: test_compiler fails on Windows without Maven. Solution: Add Xspotbugs directory to $PATH when mvn is not available (zeertzjq). closes: vim/vim#16576 https://github.com/vim/vim/commit/23da16d3d023a20565dc29128208e6cb095231d9 --- test/old/testdir/test_compiler.vim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim index f9e7bf042d..1310cbadfc 100644 --- a/test/old/testdir/test_compiler.vim +++ b/test/old/testdir/test_compiler.vim @@ -411,8 +411,10 @@ func Test_compiler_spotbugs_properties() " TEST INTEGRATION WITH A SUPPORTED COMPILER PLUGIN. if filereadable($VIMRUNTIME .. '/compiler/maven.vim') + let save_PATH = $PATH if !executable('mvn') if has('win32') + let $PATH = 'Xspotbugs;' .. $PATH " This is what ":help executable()" suggests. call writefile([], 'Xspotbugs/mvn.cmd') else @@ -700,6 +702,7 @@ func Test_compiler_spotbugs_properties() bwipeout setlocal makeprg= + let $PATH = save_PATH endif filetype plugin off -- cgit From 5a7cf85c2c7e452563a4bce9195e9a3426ca3050 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 4 Feb 2025 07:44:41 +0800 Subject: vim-patch:9.1.1074: Strange error when heredoc marker starts with "trim" (#32317) Problem: Strange error when heredoc marker starts with "trim". Solution: Check for whitespace after "trim" or "eval" (zeertzjq) For :python3 etc., a heredoc marker that starts with a lower-case letter is valid, and when it starts with "trim" it works in a script but not in a function, and this PR makes it works in a function. For :let, a heredoc marker that starts with a lower-case letter is not valid, but when it starts with "trim" or "eval" the error can be a bit confusing in a function, and this PR make it less confusing. closes: vim/vim#16574 https://github.com/vim/vim/commit/449c2e5454735fe1cfc8c21b2c6880d6bdf4cd6e --- src/nvim/eval/userfunc.c | 17 ++++++++++++----- test/old/testdir/test_let.vim | 18 ++++++++++++++++++ test/old/testdir/test_perl.vim | 5 ++++- test/old/testdir/test_python3.vim | 5 ++++- test/old/testdir/test_pyx3.vim | 5 ++++- test/old/testdir/test_ruby.vim | 5 ++++- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 68bbf76043..f386dd28b9 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2456,7 +2456,8 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { // ":python <<" continues until a dot, like ":append" p = skipwhite(arg + 2); - if (strncmp(p, "trim", 4) == 0) { + if (strncmp(p, "trim", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { // Ignore leading white space. p = skipwhite(p + 4); heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); @@ -2484,21 +2485,27 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in } if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { p = skipwhite(arg + 3); + bool has_trim = false; while (true) { - if (strncmp(p, "trim", 4) == 0) { + if (strncmp(p, "trim", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { // Ignore leading white space. p = skipwhite(p + 4); - heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); - heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + has_trim = true; continue; } - if (strncmp(p, "eval", 4) == 0) { + if (strncmp(p, "eval", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { // Ignore leading white space. p = skipwhite(p + 4); continue; } break; } + if (has_trim) { + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + } skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); do_concat = false; is_heredoc = true; diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index 44852c1d38..22a3a35f87 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -436,6 +436,24 @@ func Test_let_heredoc_fails() call assert_report('Caught exception: ' .. v:exception) endtry + try + let v =<< trim trimm + trimm + call assert_report('No exception thrown') + catch /E221:/ + catch + call assert_report('Caught exception: ' .. v:exception) + endtry + + try + let v =<< trim trim evall + evall + call assert_report('No exception thrown') + catch /E221:/ + catch + call assert_report('Caught exception: ' .. v:exception) + endtry + let text =<< trim END func WrongSyntax() let v =<< that there diff --git a/test/old/testdir/test_perl.vim b/test/old/testdir/test_perl.vim index c08a042dae..2d7f8fdc10 100644 --- a/test/old/testdir/test_perl.vim +++ b/test/old/testdir/test_perl.vim @@ -316,7 +316,10 @@ VIM::DoCommand('let s ..= "B"') perl << trim eof VIM::DoCommand('let s ..= "E"') eof - call assert_equal('ABCDE', s) + perl << trimm +VIM::DoCommand('let s ..= "F"') +trimm + call assert_equal('ABCDEF', s) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_python3.vim b/test/old/testdir/test_python3.vim index c9dbc0b84e..2436587100 100644 --- a/test/old/testdir/test_python3.vim +++ b/test/old/testdir/test_python3.vim @@ -284,7 +284,10 @@ s+='B' python3 << trim eof s+='E' eof - call assert_equal('ABCDE', pyxeval('s')) + python3 << trimm +s+='F' +trimm + call assert_equal('ABCDEF', pyxeval('s')) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_pyx3.vim b/test/old/testdir/test_pyx3.vim index 89a3cc22ff..8dfeaff807 100644 --- a/test/old/testdir/test_pyx3.vim +++ b/test/old/testdir/test_pyx3.vim @@ -97,7 +97,10 @@ result+='B' pyx << trim eof result+='E' eof - call assert_equal('ABCDE', pyxeval('result')) + pyx << trimm +result+='F' +trimm + call assert_equal('ABCDEF', pyxeval('result')) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim index 4d54d29df7..94941da873 100644 --- a/test/old/testdir/test_ruby.vim +++ b/test/old/testdir/test_ruby.vim @@ -439,7 +439,10 @@ Vim.command('let s ..= "B"') ruby << trim eof Vim.command('let s ..= "E"') eof - call assert_equal('ABCDE', s) +ruby << trimm +Vim.command('let s ..= "F"') +trimm + call assert_equal('ABCDEF', s) endfunc " vim: shiftwidth=2 sts=2 expandtab -- cgit From 290bb4c64bdcc475c29b857dc8626f5c51aa2b8d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 21:52:53 +0800 Subject: vim-patch:9.1.1009: diff feature can be improved Problem: diff feature can be improved Solution: include the linematch diff alignment algorithm (Jonathon) closes: vim/vim#9661 https://github.com/vim/vim/commit/7c7a4e6d1ad50d5b25b42aa2d5a33a8d04a4cc8a Co-authored-by: Jonathon --- runtime/doc/dev_vimpatch.txt | 1 + runtime/doc/options.txt | 102 ++++----- runtime/doc/vim_diff.txt | 2 +- runtime/lua/vim/_meta/options.lua | 102 ++++----- src/nvim/diff.c | 22 +- src/nvim/options.lua | 102 ++++----- test/old/testdir/test_diffmode.vim | 442 +++++++++++++++++++++++++++++++++++++ 7 files changed, 614 insertions(+), 159 deletions(-) diff --git a/runtime/doc/dev_vimpatch.txt b/runtime/doc/dev_vimpatch.txt index 76be24878a..0ddfc75a4c 100644 --- a/runtime/doc/dev_vimpatch.txt +++ b/runtime/doc/dev_vimpatch.txt @@ -184,6 +184,7 @@ information. mch_memmove memmove vim_memset copy_chars copy_spaces memset vim_strbyte strchr + vim_strnchr strnchr vim_strncpy strncpy xstrlcpy/xmemcpyz vim_strcat strncat xstrlcat VIM_ISWHITE ascii_iswhite diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b4ae3cc8fd..4d186bd761 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2037,11 +2037,20 @@ A jump table for the options with a short description can be found at |Q_op|. Option settings for diff mode. It can consist of the following items. All are optional. Items must be separated by a comma. - filler Show filler lines, to keep the text - synchronized with a window that has inserted - lines at the same position. Mostly useful - when windows are side-by-side and 'scrollbind' - is set. + algorithm:{text} Use the specified diff algorithm with the + internal diff engine. Currently supported + algorithms are: + myers the default algorithm + minimal spend extra time to generate the + smallest possible diff + patience patience diff algorithm + histogram histogram diff algorithm + + closeoff When a window is closed where 'diff' is set + and there is only one window remaining in the + same tab page with 'diff' set, execute + `:diffoff` in that window. This undoes a + `:diffsplit` command. context:{n} Use a context of {n} lines between a change and a fold that contains unchanged lines. @@ -2052,6 +2061,23 @@ A jump table for the options with a short description can be found at |Q_op|. value (999999) to disable folding completely. See |fold-diff|. + filler Show filler lines, to keep the text + synchronized with a window that has inserted + lines at the same position. Mostly useful + when windows are side-by-side and 'scrollbind' + is set. + + foldcolumn:{n} Set the 'foldcolumn' option to {n} when + starting diff mode. Without this 2 is used. + + followwrap Follow the 'wrap' option and leave as it is. + + horizontal Start diff mode with horizontal splits (unless + explicitly specified otherwise). + + hiddenoff Do not use diff mode for a buffer when it + becomes hidden. + iblank Ignore changes where lines are all blank. Adds the "-B" flag to the "diff" command if 'diffexpr' is empty. Check the documentation @@ -2065,6 +2091,17 @@ A jump table for the options with a short description can be found at |Q_op|. are considered the same. Adds the "-i" flag to the "diff" command if 'diffexpr' is empty. + indent-heuristic + Use the indent heuristic for the internal + diff library. + + internal Use the internal diff library. This is + ignored when 'diffexpr' is set. *E960* + When running out of memory when writing a + buffer this item will be ignored for diffs + involving that buffer. Set the 'verbose' + option to see when this happens. + iwhite Ignore changes in amount of white space. Adds the "-b" flag to the "diff" command if 'diffexpr' is empty. Check the documentation @@ -2084,56 +2121,19 @@ A jump table for the options with a short description can be found at |Q_op|. of the "diff" command for what this does exactly. - horizontal Start diff mode with horizontal splits (unless - explicitly specified otherwise). + linematch:{n} Align and mark changes between the most + similar lines between the buffers. When the + total number of lines in the diff hunk exceeds + {n}, the lines will not be aligned because for + very large diff hunks there will be a + noticeable lag. A reasonable setting is + "linematch:60", as this will enable alignment + for a 2 buffer diff hunk of 30 lines each, + or a 3 buffer diff hunk of 20 lines each. vertical Start diff mode with vertical splits (unless explicitly specified otherwise). - closeoff When a window is closed where 'diff' is set - and there is only one window remaining in the - same tab page with 'diff' set, execute - `:diffoff` in that window. This undoes a - `:diffsplit` command. - - hiddenoff Do not use diff mode for a buffer when it - becomes hidden. - - foldcolumn:{n} Set the 'foldcolumn' option to {n} when - starting diff mode. Without this 2 is used. - - followwrap Follow the 'wrap' option and leave as it is. - - internal Use the internal diff library. This is - ignored when 'diffexpr' is set. *E960* - When running out of memory when writing a - buffer this item will be ignored for diffs - involving that buffer. Set the 'verbose' - option to see when this happens. - - indent-heuristic - Use the indent heuristic for the internal - diff library. - - linematch:{n} Enable a second stage diff on each generated - hunk in order to align lines. When the total - number of lines in a hunk exceeds {n}, the - second stage diff will not be performed as - very large hunks can cause noticeable lag. A - recommended setting is "linematch:60", as this - will enable alignment for a 2 buffer diff with - hunks of up to 30 lines each, or a 3 buffer - diff with hunks of up to 20 lines each. - - algorithm:{text} Use the specified diff algorithm with the - internal diff engine. Currently supported - algorithms are: - myers the default algorithm - minimal spend extra time to generate the - smallest possible diff - patience patience diff algorithm - histogram histogram diff algorithm - Examples: >vim set diffopt=internal,filler,context:4 set diffopt= diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index c870de00ef..eae341da49 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -351,7 +351,6 @@ Options: - 'autoread' works in the terminal (if it supports "focus" events) - 'background' cannot be set to empty. - 'cpoptions' flags: |cpo-_| -- 'diffopt' "linematch" feature - 'eadirection' cannot be set to empty. - 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is prompted whether to trust the file. @@ -466,6 +465,7 @@ Upstreamed features *nvim-upstreamed* These Nvim features were later integrated into Vim. +- 'diffopt' "linematch" feature - 'fillchars' flags: "eob" - 'jumpoptions' "stack" behavior - 'wildoptions' flags: "pum" enables popupmenu for wildmode completion diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 452959970d..52c556867f 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1631,11 +1631,20 @@ vim.go.dex = vim.go.diffexpr --- Option settings for diff mode. It can consist of the following items. --- All are optional. Items must be separated by a comma. --- ---- filler Show filler lines, to keep the text ---- synchronized with a window that has inserted ---- lines at the same position. Mostly useful ---- when windows are side-by-side and 'scrollbind' ---- is set. +--- algorithm:{text} Use the specified diff algorithm with the +--- internal diff engine. Currently supported +--- algorithms are: +--- myers the default algorithm +--- minimal spend extra time to generate the +--- smallest possible diff +--- patience patience diff algorithm +--- histogram histogram diff algorithm +--- +--- closeoff When a window is closed where 'diff' is set +--- and there is only one window remaining in the +--- same tab page with 'diff' set, execute +--- `:diffoff` in that window. This undoes a +--- `:diffsplit` command. --- --- context:{n} Use a context of {n} lines between a change --- and a fold that contains unchanged lines. @@ -1646,6 +1655,23 @@ vim.go.dex = vim.go.diffexpr --- value (999999) to disable folding completely. --- See `fold-diff`. --- +--- filler Show filler lines, to keep the text +--- synchronized with a window that has inserted +--- lines at the same position. Mostly useful +--- when windows are side-by-side and 'scrollbind' +--- is set. +--- +--- foldcolumn:{n} Set the 'foldcolumn' option to {n} when +--- starting diff mode. Without this 2 is used. +--- +--- followwrap Follow the 'wrap' option and leave as it is. +--- +--- horizontal Start diff mode with horizontal splits (unless +--- explicitly specified otherwise). +--- +--- hiddenoff Do not use diff mode for a buffer when it +--- becomes hidden. +--- --- iblank Ignore changes where lines are all blank. Adds --- the "-B" flag to the "diff" command if --- 'diffexpr' is empty. Check the documentation @@ -1659,6 +1685,17 @@ vim.go.dex = vim.go.diffexpr --- are considered the same. Adds the "-i" flag --- to the "diff" command if 'diffexpr' is empty. --- +--- indent-heuristic +--- Use the indent heuristic for the internal +--- diff library. +--- +--- internal Use the internal diff library. This is +--- ignored when 'diffexpr' is set. *E960* +--- When running out of memory when writing a +--- buffer this item will be ignored for diffs +--- involving that buffer. Set the 'verbose' +--- option to see when this happens. +--- --- iwhite Ignore changes in amount of white space. Adds --- the "-b" flag to the "diff" command if --- 'diffexpr' is empty. Check the documentation @@ -1678,56 +1715,19 @@ vim.go.dex = vim.go.diffexpr --- of the "diff" command for what this does --- exactly. --- ---- horizontal Start diff mode with horizontal splits (unless ---- explicitly specified otherwise). +--- linematch:{n} Align and mark changes between the most +--- similar lines between the buffers. When the +--- total number of lines in the diff hunk exceeds +--- {n}, the lines will not be aligned because for +--- very large diff hunks there will be a +--- noticeable lag. A reasonable setting is +--- "linematch:60", as this will enable alignment +--- for a 2 buffer diff hunk of 30 lines each, +--- or a 3 buffer diff hunk of 20 lines each. --- --- vertical Start diff mode with vertical splits (unless --- explicitly specified otherwise). --- ---- closeoff When a window is closed where 'diff' is set ---- and there is only one window remaining in the ---- same tab page with 'diff' set, execute ---- `:diffoff` in that window. This undoes a ---- `:diffsplit` command. ---- ---- hiddenoff Do not use diff mode for a buffer when it ---- becomes hidden. ---- ---- foldcolumn:{n} Set the 'foldcolumn' option to {n} when ---- starting diff mode. Without this 2 is used. ---- ---- followwrap Follow the 'wrap' option and leave as it is. ---- ---- internal Use the internal diff library. This is ---- ignored when 'diffexpr' is set. *E960* ---- When running out of memory when writing a ---- buffer this item will be ignored for diffs ---- involving that buffer. Set the 'verbose' ---- option to see when this happens. ---- ---- indent-heuristic ---- Use the indent heuristic for the internal ---- diff library. ---- ---- linematch:{n} Enable a second stage diff on each generated ---- hunk in order to align lines. When the total ---- number of lines in a hunk exceeds {n}, the ---- second stage diff will not be performed as ---- very large hunks can cause noticeable lag. A ---- recommended setting is "linematch:60", as this ---- will enable alignment for a 2 buffer diff with ---- hunks of up to 30 lines each, or a 3 buffer ---- diff with hunks of up to 20 lines each. ---- ---- algorithm:{text} Use the specified diff algorithm with the ---- internal diff engine. Currently supported ---- algorithms are: ---- myers the default algorithm ---- minimal spend extra time to generate the ---- smallest possible diff ---- patience patience diff algorithm ---- histogram histogram diff algorithm ---- --- Examples: --- --- ```vim diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 99f70793b3..4ead58c74f 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1820,7 +1820,8 @@ static void find_top_diff_block(diff_T **thistopdiff, diff_T **nextblockblock, i topdiffchange = 0; } - // check if the fromwin topline is matched by the current diff. if so, set it to the top of the diff block + // check if the fromwin topline is matched by the current diff. if so, + // set it to the top of the diff block if (topline >= topdiff->df_lnum[fromidx] && topline <= (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])) { // this line is inside the current diff block, so we will save the @@ -2021,10 +2022,15 @@ static void run_linematch_algorithm(diff_T *dp) size_t ndiffs = 0; for (int i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] != NULL) { - // write the contents of the entire buffer to - // diffbufs_mm[diffbuffers_count] - diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs], - dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1); + if (dp->df_count[i] > 0) { + // write the contents of the entire buffer to + // diffbufs_mm[diffbuffers_count] + diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs], + dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1); + } else { + diffbufs_mm[ndiffs].size = 0; + diffbufs_mm[ndiffs].ptr = NULL; + } diffbufs[ndiffs] = &diffbufs_mm[ndiffs]; @@ -2060,6 +2066,12 @@ static void run_linematch_algorithm(diff_T *dp) /// Returns > 0 for inserting that many filler lines above it (never happens /// when 'diffopt' doesn't contain "filler"). /// This should only be used for windows where 'diff' is set. +/// When diffopt contains linematch, a changed/added/deleted line +/// may also have filler lines above it. In such a case, the possibilities +/// are no longer mutually exclusive. The number of filler lines is +/// returned from diff_check, and the integer 'linestatus' passed by +/// pointer is set to -1 to indicate a changed line, and -2 to indicate an +/// added line /// /// @param wp /// @param lnum diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fdd5799b46..4406ae28a8 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2178,11 +2178,20 @@ local options = { Option settings for diff mode. It can consist of the following items. All are optional. Items must be separated by a comma. - filler Show filler lines, to keep the text - synchronized with a window that has inserted - lines at the same position. Mostly useful - when windows are side-by-side and 'scrollbind' - is set. + algorithm:{text} Use the specified diff algorithm with the + internal diff engine. Currently supported + algorithms are: + myers the default algorithm + minimal spend extra time to generate the + smallest possible diff + patience patience diff algorithm + histogram histogram diff algorithm + + closeoff When a window is closed where 'diff' is set + and there is only one window remaining in the + same tab page with 'diff' set, execute + `:diffoff` in that window. This undoes a + `:diffsplit` command. context:{n} Use a context of {n} lines between a change and a fold that contains unchanged lines. @@ -2193,6 +2202,23 @@ local options = { value (999999) to disable folding completely. See |fold-diff|. + filler Show filler lines, to keep the text + synchronized with a window that has inserted + lines at the same position. Mostly useful + when windows are side-by-side and 'scrollbind' + is set. + + foldcolumn:{n} Set the 'foldcolumn' option to {n} when + starting diff mode. Without this 2 is used. + + followwrap Follow the 'wrap' option and leave as it is. + + horizontal Start diff mode with horizontal splits (unless + explicitly specified otherwise). + + hiddenoff Do not use diff mode for a buffer when it + becomes hidden. + iblank Ignore changes where lines are all blank. Adds the "-B" flag to the "diff" command if 'diffexpr' is empty. Check the documentation @@ -2206,6 +2232,17 @@ local options = { are considered the same. Adds the "-i" flag to the "diff" command if 'diffexpr' is empty. + indent-heuristic + Use the indent heuristic for the internal + diff library. + + internal Use the internal diff library. This is + ignored when 'diffexpr' is set. *E960* + When running out of memory when writing a + buffer this item will be ignored for diffs + involving that buffer. Set the 'verbose' + option to see when this happens. + iwhite Ignore changes in amount of white space. Adds the "-b" flag to the "diff" command if 'diffexpr' is empty. Check the documentation @@ -2225,56 +2262,19 @@ local options = { of the "diff" command for what this does exactly. - horizontal Start diff mode with horizontal splits (unless - explicitly specified otherwise). + linematch:{n} Align and mark changes between the most + similar lines between the buffers. When the + total number of lines in the diff hunk exceeds + {n}, the lines will not be aligned because for + very large diff hunks there will be a + noticeable lag. A reasonable setting is + "linematch:60", as this will enable alignment + for a 2 buffer diff hunk of 30 lines each, + or a 3 buffer diff hunk of 20 lines each. vertical Start diff mode with vertical splits (unless explicitly specified otherwise). - closeoff When a window is closed where 'diff' is set - and there is only one window remaining in the - same tab page with 'diff' set, execute - `:diffoff` in that window. This undoes a - `:diffsplit` command. - - hiddenoff Do not use diff mode for a buffer when it - becomes hidden. - - foldcolumn:{n} Set the 'foldcolumn' option to {n} when - starting diff mode. Without this 2 is used. - - followwrap Follow the 'wrap' option and leave as it is. - - internal Use the internal diff library. This is - ignored when 'diffexpr' is set. *E960* - When running out of memory when writing a - buffer this item will be ignored for diffs - involving that buffer. Set the 'verbose' - option to see when this happens. - - indent-heuristic - Use the indent heuristic for the internal - diff library. - - linematch:{n} Enable a second stage diff on each generated - hunk in order to align lines. When the total - number of lines in a hunk exceeds {n}, the - second stage diff will not be performed as - very large hunks can cause noticeable lag. A - recommended setting is "linematch:60", as this - will enable alignment for a 2 buffer diff with - hunks of up to 30 lines each, or a 3 buffer - diff with hunks of up to 20 lines each. - - algorithm:{text} Use the specified diff algorithm with the - internal diff engine. Currently supported - algorithms are: - myers the default algorithm - minimal spend extra time to generate the - smallest possible diff - patience patience diff algorithm - histogram histogram diff algorithm - Examples: >vim set diffopt=internal,filler,context:4 set diffopt= diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 695aeeac75..b452be5061 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -1017,6 +1017,41 @@ func Test_diff_screen() call WriteDiffFiles(buf, [], [0]) call VerifyBoth(buf, "Test_diff_22", "") + call WriteDiffFiles(buf, ['?a', '?b', '?c'], ['!b']) + call VerifyInternal(buf, 'Test_diff_23', " diffopt+=linematch:30") + + call WriteDiffFiles(buf, ['', + \ 'common line', + \ 'common line', + \ '', + \ 'DEFabc', + \ 'xyz', + \ 'xyz', + \ 'xyz', + \ 'DEFabc', + \ 'DEFabc', + \ 'DEFabc', + \ 'common line', + \ 'common line', + \ 'DEF', + \ 'common line', + \ 'DEF', + \ 'something' ], + \ ['', + \ 'common line', + \ 'common line', + \ '', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'common line', + \ 'common line', + \ 'common line', + \ 'something']) + call VerifyInternal(buf, 'Test_diff_24', " diffopt+=linematch:30") + + " clean up call StopVimInTerminal(buf) call delete('Xdifile1') @@ -2040,4 +2075,411 @@ func Test_diff_topline_noscroll() call StopVimInTerminal(buf) endfunc +func Test_diffget_diffput_linematch() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\\w:set autoread\\w") + + " enable linematch + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call WriteDiffFiles(buf, ['', + \ 'common line', + \ 'common line', + \ '', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'common line', + \ 'common line', + \ 'common line', + \ 'something' ], + \ ['', + \ 'common line', + \ 'common line', + \ '', + \ 'DEFabc', + \ 'xyz', + \ 'xyz', + \ 'xyz', + \ 'DEFabc', + \ 'DEFabc', + \ 'DEFabc', + \ 'common line', + \ 'common line', + \ 'DEF', + \ 'common line', + \ 'DEF', + \ 'something']) + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_1', {}) + + " get from window 1 from line 5 to 9 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, ":5,9diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_2', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 2 from line 5 to 10 + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, ":5,10diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_3', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get all from window 2 + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, ":4,17diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_4', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get all from window 1 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, ":4,12diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_5', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 1 line 5 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "5gg") + call term_sendkeys(buf, ":diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_6', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 6 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "6gg") + call term_sendkeys(buf, ":diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_7', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 7 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "7gg") + call term_sendkeys(buf, ":diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_8', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 11 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "11gg") + call term_sendkeys(buf, ":diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_9', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 12 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "12gg") + call term_sendkeys(buf, ":diffget\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_10', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " put from window 1 using dp 1 line 5 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "5gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_11', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 6 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "6gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_12', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 7 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "7gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_13', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 11 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "11gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_14', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 12 + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "12gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_15', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 6 + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "6gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_16', {}) + + " undo the last diffput + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 8 + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "8gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_17', {}) + + " undo the last diffput + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 9 + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "9gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_18', {}) + + " undo the last diffput + call term_sendkeys(buf, "1\w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 17 + call term_sendkeys(buf, "2\w") + call term_sendkeys(buf, "17gg") + call term_sendkeys(buf, ":diffput\") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_19', {}) + +endfunc + +func Test_linematch_diff() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\\w:set autoread\\w") + + " enable linematch + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call WriteDiffFiles(buf, ['// abc d?', + \ '// d?', + \ '// d?' ], + \ ['!', + \ 'abc d!', + \ 'd!']) + call VerifyScreenDump(buf, 'Test_linematch_diff1', {}) + +endfunc + +func Test_linematch_diff_iwhite() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\\w:set autoread\\w") + + " setup a diff with 2 files and set linematch:30, with ignore white + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call WriteDiffFiles(buf, ['void testFunction () {', + \ ' for (int i = 0; i < 10; i++) {', + \ ' for (int j = 0; j < 10; j++) {', + \ ' }', + \ ' }', + \ '}' ], + \ ['void testFunction () {', + \ ' // for (int j = 0; j < 10; i++) {', + \ ' // }', + \ '}']) + call VerifyScreenDump(buf, 'Test_linematch_diff_iwhite1', {}) + call term_sendkeys(buf, ":set diffopt+=iwhiteall\") + call VerifyScreenDump(buf, 'Test_linematch_diff_iwhite2', {}) + +endfunc + +func Test_linematch_diff_grouping() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\\w:set autoread\\w") + + " a diff that would result in multiple groups before grouping optimization + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call WriteDiffFiles(buf, ['!A', + \ '!B', + \ '!C' ], + \ ['?Z', + \ '?A', + \ '?B', + \ '?C', + \ '?A', + \ '?B', + \ '?B', + \ '?C']) + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping1', {}) + call WriteDiffFiles(buf, ['!A', + \ '!B', + \ '!C' ], + \ ['?A', + \ '?Z', + \ '?B', + \ '?C', + \ '?A', + \ '?B', + \ '?C', + \ '?C']) + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping2', {}) + +endfunc + +func Test_linematch_diff_scroll() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\\w:set autoread\\w") + + " a diff that would result in multiple groups before grouping optimization + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call WriteDiffFiles(buf, ['!A', + \ '!B', + \ '!C' ], + \ ['?A', + \ '?Z', + \ '?B', + \ '?C', + \ '?A', + \ '?B', + \ '?C', + \ '?C']) + " scroll down to show calculation of top fill and scroll to correct line in + " both windows + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll0', {}) + call term_sendkeys(buf, "3\") + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll1', {}) + call term_sendkeys(buf, "3\") + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll2', {}) + +endfunc + + + +func Test_linematch_line_limit_exceeded() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\\w:set autoread\\w") + + call term_sendkeys(buf, ":set diffopt+=linematch:10\") + " a diff block will not be aligned with linematch because it's contents + " exceed 10 lines + call WriteDiffFiles(buf, + \ ['common line', + \ 'HIL', + \ '', + \ 'aABCabc', + \ 'aABCabc', + \ 'aABCabc', + \ 'aABCabc', + \ 'common line', + \ 'HIL', + \ 'common line', + \ 'something'], + \ ['common line', + \ 'DEF', + \ 'GHI', + \ 'something', + \ '', + \ 'aDEFabc', + \ 'xyz', + \ 'xyz', + \ 'xyz', + \ 'aDEFabc', + \ 'aDEFabc', + \ 'aDEFabc', + \ 'common line', + \ 'DEF', + \ 'GHI', + \ 'something else', + \ 'common line', + \ 'something']) + call VerifyScreenDump(buf, 'Test_linematch_line_limit_exceeded1', {}) + " after increasing the count to 30, the limit is not exceeded, and the + " alignment algorithm will run on the largest diff block here + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call VerifyScreenDump(buf, 'Test_linematch_line_limit_exceeded2', {}) + +endfunc + +func Test_linematch_3diffs() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call delete('.Xdifile3.swp') + call WriteDiffFiles3(0, [], [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2 Xdifile3', {}) + call term_sendkeys(buf, "1\w:set autoread\") + call term_sendkeys(buf, "2\w:set autoread\") + call term_sendkeys(buf, "3\w:set autoread\") + call term_sendkeys(buf, ":set diffopt+=linematch:30\") + call WriteDiffFiles3(buf, + \ ["", + \ " common line", + \ " AAA", + \ " AAA", + \ " AAA"], + \ ["", + \ " common line", + \ " <<<<<<< HEAD", + \ " AAA", + \ " AAA", + \ " AAA", + \ " =======", + \ " BBB", + \ " BBB", + \ " BBB", + \ " >>>>>>> branch1"], + \ ["", + \ " common line", + \ " BBB", + \ " BBB", + \ " BBB"]) + call VerifyScreenDump(buf, 'Test_linematch_3diffs1', {}) + +endfunc " vim: shiftwidth=2 sts=2 expandtab -- cgit From 1c3bda7e92f6162800bc013851c8570800026420 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 4 Feb 2025 08:25:10 +0800 Subject: vim-patch:9.1.1022: linematch option value not completed Problem: linematch option value not completed (after v9.1.1009) Solution: Update diffoption completion values related: vim/vim#9661 closes: vim/vim#16437 https://github.com/vim/vim/commit/9162e636b31dcac57876cbdec15a683cedd9760e Co-authored-by: Christian Brabandt --- src/nvim/options.lua | 2 +- test/old/testdir/test_options.vim | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 4406ae28a8..c61c6032c4 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2170,8 +2170,8 @@ local options = { 'followwrap', 'internal', 'indent-heuristic', - 'linematch:', { 'algorithm:', { 'myers', 'minimal', 'patience', 'histogram' } }, + 'linematch:', }, deny_duplicates = true, desc = [=[ diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index dfa140b163..2479f0ca51 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -711,6 +711,10 @@ func Test_set_completion_string_values() " Test empty option set diffopt= call assert_equal([], getcompletion('set diffopt-=', 'cmdline')) + " Test all possible values + call assert_equal(['filler', 'context:', 'iblank', 'icase', 'iwhite', 'iwhiteall', 'iwhiteeol', 'horizontal', + \ 'vertical', 'closeoff', 'hiddenoff', 'foldcolumn:', 'followwrap', 'internal', 'indent-heuristic', 'algorithm:', 'linematch:'], + \ getcompletion('set diffopt=', 'cmdline')) set diffopt& " Test escaping output -- cgit From 4d0c6cae72716eb88d74361c49dc8baefba22c63 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 22:26:05 +0800 Subject: vim-patch:9.1.1023: Coverity complains about dereferencing NULL pointer Problem: Coverity complains about dereferencing NULL pointer Solution: Verify curdiff is not null before dereferencing it closes: vim/vim#16437 https://github.com/vim/vim/commit/a9f77be9223f8b886d89f7fac778d363586beb85 Co-authored-by: Christian Brabandt --- src/nvim/diff.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 4ead58c74f..ee063a330e 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1866,8 +1866,10 @@ static void count_filler_lines_and_topline(int *curlinenum_to, int *linesfiller, } } else { (*linesfiller) = 0; - ch_virtual_lines = get_max_diff_length(curdif); - isfiller = (curdif->df_count[toidx] ? false : true); + if (curdif) { + ch_virtual_lines = get_max_diff_length(curdif); + isfiller = (curdif->df_count[toidx] ? false : true); + } if (isfiller) { while (curdif && curdif->df_next && curdif->df_lnum[toidx] == curdif->df_next->df_lnum[toidx] -- cgit From bd145a6c8398fb7a3fd037bc71c1bacaeba49584 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 22:26:32 +0800 Subject: vim-patch:9.1.1027: no sanitize check when running linematch Problem: no sanitize check when running linematch Solution: add sanitize check before applying the linematch algorithm, similar to diff_find_change() (Jonathon) closes: vim/vim#16446 https://github.com/vim/vim/commit/ca307efe486670b76563a4a287bc94dace57fb74 Co-authored-by: Jonathon --- src/nvim/diff.c | 3 ++- test/functional/ui/linematch_spec.lua | 28 +++++++++++++++++++++ test/old/testdir/test_diffmode.vim | 47 ++++++++++++++++++++++++++++------- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/nvim/diff.c b/src/nvim/diff.c index ee063a330e..68441f7adc 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2127,7 +2127,8 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus) // Useful for scrollbind calculations which need to count all the filler lines // above the screen. if (lnum >= wp->w_topline && lnum < wp->w_botline - && !dp->is_linematched && diff_linematch(dp)) { + && !dp->is_linematched && diff_linematch(dp) + && diff_check_sanity(curtab, dp)) { run_linematch_algorithm(dp); } diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua index b564c01eaa..3593604c49 100644 --- a/test/functional/ui/linematch_spec.lua +++ b/test/functional/ui/linematch_spec.lua @@ -1147,4 +1147,32 @@ describe('regressions', function() }, } end) + + -- oldtest: Test_linematch_3diffs_sanity_check() + it('sanity check with 3 diff buffers', function() + clear() + screen = Screen.new(75, 20) + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'abcd', 'def', 'hij' }) + n.exec('rightbelow vnew') + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'defq', 'hijk', 'nopq' }) + n.exec('rightbelow vnew') + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'hijklm', 'nopqr', 'stuv' }) + n.exec([[ + set diffopt+=linematch:60 + windo diffthis | wincmd t + call feedkeys("Aq\") + call feedkeys("GAklm\") + call feedkeys("o") + ]]) + screen:expect([[ + {7: }{22:abcdq }│{7: }{23:----------------------}│{7: }{23:-----------------------}| + {7: }{4:def }│{7: }{4:def}{27:q}{4: }│{7: }{23:-----------------------}| + {7: }{4:hijk}{27:lm}{4: }│{7: }{4:hijk }│{7: }{4:hijk}{27:lm}{4: }| + {7: }{23:----------------------}│{7: }{4:nopq }│{7: }{4:nopq}{27:r}{4: }| + {7: }{4:^ }│{7: }{23:----------------------}│{7: }{27:stuv}{4: }| + {1:~ }│{1:~ }│{1:~ }|*13 + {3:[No Name] [+] }{2:[No Name] [+] [No Name] [+] }| + {5:-- INSERT --} | + ]]) + end) end) diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index b452be5061..ab64658bd0 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -1247,7 +1247,7 @@ func CloseoffSetup() call setline(1, ['one', 'tow', 'three']) diffthis call assert_equal(1, &diff) - only! + bw! endfunc func Test_diff_closeoff() @@ -2278,7 +2278,8 @@ func Test_diffget_diffput_linematch() call term_sendkeys(buf, "17gg") call term_sendkeys(buf, ":diffput\") call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_19', {}) - + " clean up + call StopVimInTerminal(buf) endfunc func Test_linematch_diff() @@ -2298,7 +2299,8 @@ func Test_linematch_diff() \ 'abc d!', \ 'd!']) call VerifyScreenDump(buf, 'Test_linematch_diff1', {}) - + " clean up + call StopVimInTerminal(buf) endfunc func Test_linematch_diff_iwhite() @@ -2324,7 +2326,8 @@ func Test_linematch_diff_iwhite() call VerifyScreenDump(buf, 'Test_linematch_diff_iwhite1', {}) call term_sendkeys(buf, ":set diffopt+=iwhiteall\") call VerifyScreenDump(buf, 'Test_linematch_diff_iwhite2', {}) - + " clean up + call StopVimInTerminal(buf) endfunc func Test_linematch_diff_grouping() @@ -2361,7 +2364,8 @@ func Test_linematch_diff_grouping() \ '?C', \ '?C']) call VerifyScreenDump(buf, 'Test_linematch_diff_grouping2', {}) - + " clean up + call StopVimInTerminal(buf) endfunc func Test_linematch_diff_scroll() @@ -2392,11 +2396,10 @@ func Test_linematch_diff_scroll() call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll1', {}) call term_sendkeys(buf, "3\") call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll2', {}) - + " clean up + call StopVimInTerminal(buf) endfunc - - func Test_linematch_line_limit_exceeded() CheckScreendump call delete('.Xdifile1.swp') @@ -2443,7 +2446,8 @@ func Test_linematch_line_limit_exceeded() " alignment algorithm will run on the largest diff block here call term_sendkeys(buf, ":set diffopt+=linematch:30\") call VerifyScreenDump(buf, 'Test_linematch_line_limit_exceeded2', {}) - + " clean up + call StopVimInTerminal(buf) endfunc func Test_linematch_3diffs() @@ -2480,6 +2484,31 @@ func Test_linematch_3diffs() \ " BBB", \ " BBB"]) call VerifyScreenDump(buf, 'Test_linematch_3diffs1', {}) + " clean up + call StopVimInTerminal(buf) +endfunc +" this used to access invalid memory +func Test_linematch_3diffs_sanity_check() + CheckScreendump + call delete('.Xfile_linematch1.swp') + call delete('.Xfile_linematch2.swp') + call delete('.Xfile_linematch3.swp') + let lines =<< trim END + set diffopt+=linematch:60 + call feedkeys("Aq\") + call feedkeys("GAklm\") + call feedkeys("o") + END + call writefile(lines, 'Xlinematch_3diffs.vim', 'D') + call writefile(['abcd', 'def', 'hij'], 'Xfile_linematch1', 'D') + call writefile(['defq', 'hijk', 'nopq'], 'Xfile_linematch2', 'D') + call writefile(['hijklm', 'nopqr', 'stuv'], 'Xfile_linematch3', 'D') + call WriteDiffFiles3(0, [], [], []) + let buf = RunVimInTerminal('-d -S Xlinematch_3diffs.vim Xfile_linematch1 Xfile_linematch2 Xfile_linematch3', {}) + call VerifyScreenDump(buf, 'Test_linematch_3diffs2', {}) + + " clean up + call StopVimInTerminal(buf) endfunc " vim: shiftwidth=2 sts=2 expandtab -- cgit From 3a1fe4732d8b6efd5b10803e649d62dc17c2c778 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 4 Feb 2025 08:29:34 +0800 Subject: vim-patch:9.1.1072: 'diffopt' "linematch" cannot be used with {n} less than 10 Problem: 'diffopt' "linematch" cannot be used with {n} less than 10 digits (after v9.1.1022) Solution: Fix off-by-one error when checking for digit (zeertzjq) closes: vim/vim#16577 https://github.com/vim/vim/commit/ccd7f454fcac2f99085d4f50e79c111c02741166 --- test/old/testdir/gen_opt_test.vim | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index f9fcc4ae1b..f12ce8c7c4 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -209,9 +209,11 @@ let test_values = { \ 'icase', 'iwhite', 'iwhiteall', 'horizontal', 'vertical', \ 'closeoff', 'hiddenoff', 'foldcolumn:0', 'foldcolumn:12', \ 'followwrap', 'internal', 'indent-heuristic', 'algorithm:myers', - \ 'algorithm:minimal', 'algorithm:patience', - \ 'algorithm:histogram', 'icase,iwhite'], - \ ['xxx', 'foldcolumn:xxx', 'algorithm:xxx', 'algorithm:']], + \ 'icase,iwhite', 'algorithm:minimal', 'algorithm:patience', + \ 'algorithm:histogram', 'linematch:5'], + \ ['xxx', 'foldcolumn:', 'foldcolumn:x', 'foldcolumn:xxx', + \ 'linematch:', 'linematch:x', 'linematch:xxx', 'algorithm:', + \ 'algorithm:xxx', 'context:', 'context:x', 'context:xxx']], \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'], \ ['xxx']], \ 'eadirection': [['both', 'ver', 'hor'], ['xxx', 'ver,hor']], -- cgit From 3d22293496fc0b8781c3530e8f6a270f1647be93 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 4 Feb 2025 09:31:37 +0800 Subject: test(getchar_spec): fix flakiness (#32320) Problem: getchar_spec may fail when screen:expect_unchanged() doesn't wait long enough. Solution: Add poke_eventloop() before screen:expect_unchanged(). --- test/functional/vimscript/getchar_spec.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/vimscript/getchar_spec.lua b/test/functional/vimscript/getchar_spec.lua index 1327d741cf..4ecf082f97 100644 --- a/test/functional/vimscript/getchar_spec.lua +++ b/test/functional/vimscript/getchar_spec.lua @@ -5,6 +5,7 @@ local clear = n.clear local exec = n.exec local feed = n.feed local async_command = n.async_meths.nvim_command +local poke_eventloop = n.poke_eventloop describe('getchar()', function() before_each(clear) @@ -56,8 +57,10 @@ describe('getchar()', function() "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'keep'})", }) do async_command(cmd) + poke_eventloop() screen:expect_unchanged() feed('a') + poke_eventloop() screen:expect_unchanged() end -- cgit From 8ce2833411534f44dc810b282da283f69e78a28a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 4 Feb 2025 10:07:35 +0800 Subject: test(terminal/cursor_spec): remove unnecessary busy handlers (#32321) They are no longer necessary after #31562, as busy_start and busy_stop are no longer emitted by terminal buffers with visible cursor. --- test/functional/terminal/cursor_spec.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 12a20ddc0d..83408e41b3 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -381,9 +381,6 @@ describe('buffer cursor position is correct in terminal without number column', }, { cols = 70, }) - -- Also check for real cursor position, as it is used for stuff like input methods - screen._handle_busy_start = function() end - screen._handle_busy_stop = function() end screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | @@ -692,9 +689,6 @@ describe('buffer cursor position is correct in terminal with number column', fun }, { cols = 70, }) - -- Also check for real cursor position, as it is used for stuff like input methods - screen._handle_busy_start = function() end - screen._handle_busy_stop = function() end screen:expect([[ {7: 1 } | {7: 2 } | -- cgit From 1103d9fc10d8ffac7e375c3aa9d1bdfe16691776 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 4 Feb 2025 00:56:11 -0800 Subject: build(deps): bump tree-sitter-query to v0.5.0 (#32299) and sync queries from nvim-treesitter (adds support for `MISSING` nodes). --- cmake.deps/deps.txt | 4 ++-- runtime/queries/query/highlights.scm | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 18d34a7be1..38c9d8cff7 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -46,8 +46,8 @@ TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.4.0.tar. TREESITTER_VIM_SHA256 9f856f8b4a10ab43348550fa2d3cb2846ae3d8e60f45887200549c051c66f9d5 TREESITTER_VIMDOC_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v3.0.0.tar.gz TREESITTER_VIMDOC_SHA256 a639bf92bf57bfa1cdc90ca16af27bfaf26a9779064776dd4be34c1ef1453f6c -TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/archive/v0.4.0.tar.gz -TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e +TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/archive/v0.5.0.tar.gz +TREESITTER_QUERY_SHA256 e33907fd334350e32e49b3875c36bcf070defe490357632fac9398e6d4540a80 TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.1.tar.gz diff --git a/runtime/queries/query/highlights.scm b/runtime/queries/query/highlights.scm index f839ec985c..cbd192a8ff 100644 --- a/runtime/queries/query/highlights.scm +++ b/runtime/queries/query/highlights.scm @@ -11,6 +11,9 @@ (named_node name: (identifier) @variable) +(missing_node + name: (identifier) @variable) + (field_definition name: (identifier) @variable.member) @@ -43,8 +46,13 @@ "#" ] @punctuation.special +(predicate + "." @punctuation.special) + "_" @character.special +"MISSING" @keyword + ((parameters (identifier) @number) (#match? @number "^[-+]?[0-9]+(.[0-9]+)?$")) -- cgit From e4a58a7ca03457668492f8f41189ea2f23700172 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Tue, 4 Feb 2025 00:31:41 +0100 Subject: ci(vim-patches): fix failing workflow --- .github/workflows/vim_patches.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/vim_patches.yml b/.github/workflows/vim_patches.yml index 5154565def..05af39f19c 100644 --- a/.github/workflows/vim_patches.yml +++ b/.github/workflows/vim_patches.yml @@ -28,10 +28,10 @@ jobs: - run: sudo apt-get install libfuse2 - run: | - gh release download -R neovim/neovim -p nvim.appimage - chmod a+x nvim.appimage + gh release download -R neovim/neovim -p nvim-linux-x86_64.appimage + chmod a+x nvim-linux-x86_64.appimage mkdir -p $HOME/.local/bin - mv nvim.appimage $HOME/.local/bin/nvim + mv nvim-linux-x86_64.appimage $HOME/.local/bin/nvim printf '%s\n' "$HOME/.local/bin" >> $GITHUB_PATH - name: Set up git config -- cgit From 4317d366691b057ffba4504c1167128a66e4e5c8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 5 Feb 2025 06:48:56 +0800 Subject: fix(event-loop): process input before events in getchar() (#32322) Follow-up to #27358. --- src/nvim/getchar.c | 2 +- test/functional/api/vim_spec.lua | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 5bf89ee5a8..0817d40bb8 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1932,7 +1932,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number // Flush screen updates before blocking. ui_flush(); input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); - if (!multiqueue_empty(main_loop.events)) { + if (!input_available() && !multiqueue_empty(main_loop.events)) { state_handle_k_event(); continue; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index dabe3a2c9a..3aa9ba49d5 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -96,12 +96,19 @@ describe('API', function() assert_alive() end) - it('input is processed first when followed immediately by non-fast events', function() + it('input is processed first if followed immediately by non-fast events', function() api.nvim_set_current_line('ab') async_meths.nvim_input('x') async_meths.nvim_exec_lua('_G.res1 = vim.api.nvim_get_current_line()', {}) async_meths.nvim_exec_lua('_G.res2 = vim.api.nvim_get_current_line()', {}) eq({ 'b', 'b' }, exec_lua('return { _G.res1, _G.res2 }')) + -- Also test with getchar() + async_meths.nvim_command('let g:getchar = 1 | call getchar() | let g:getchar = 0') + eq(1, api.nvim_get_var('getchar')) + async_meths.nvim_input('x') + async_meths.nvim_exec_lua('_G.res1 = vim.g.getchar', {}) + async_meths.nvim_exec_lua('_G.res2 = vim.g.getchar', {}) + eq({ 0, 0 }, exec_lua('return { _G.res1, _G.res2 }')) end) it('does not set CA_COMMAND_BUSY #7254', function() -- cgit From 1deb580977f4144ff3e4704765d13684e84405c5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 5 Feb 2025 07:06:33 +0800 Subject: vim-patch:9.1.1076: vim_strnchr() is strange and unnecessary (#32327) Problem: vim_strnchr() is strange and unnecessary (after v9.1.1009) Solution: Remove vim_strnchr() and use memchr() instead. Also remove a comment referencing an #if that is no longer present. vim_strnchr() is strange in several ways: - It's named like vim_strchr(), but unlike vim_strchr() it doesn't support finding a multibyte char. - Its logic is similar to vim_strbyte(), but unlike vim_strbyte() it uses char instead of char_u. - It takes a pointer as its size argument, which isn't convenient for all its callers. - It allows embedded NULs, unlike other "strn*" functions which stop when encountering a NUL byte. In comparison, memchr() also allows embedded NULs, and it converts bytes in the string to (unsigned char). closes: vim/vim#16579 https://github.com/vim/vim/commit/34e1e8de91ff4a8922d454e3147ea425784aa0a0 --- runtime/doc/dev_vimpatch.txt | 1 - src/nvim/linematch.c | 14 +++++--------- src/nvim/strings.c | 14 -------------- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/runtime/doc/dev_vimpatch.txt b/runtime/doc/dev_vimpatch.txt index 0ddfc75a4c..76be24878a 100644 --- a/runtime/doc/dev_vimpatch.txt +++ b/runtime/doc/dev_vimpatch.txt @@ -184,7 +184,6 @@ information. mch_memmove memmove vim_memset copy_chars copy_spaces memset vim_strbyte strchr - vim_strnchr strnchr vim_strncpy strncpy xstrlcpy/xmemcpyz vim_strcat strncat xstrlcat VIM_ISWHITE ascii_iswhite diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c index 2627489106..79f4c0d692 100644 --- a/src/nvim/linematch.c +++ b/src/nvim/linematch.c @@ -32,12 +32,8 @@ struct diffcmppath_S { static size_t line_len(const mmfile_t *m) { char *s = m->ptr; - size_t n = (size_t)m->size; - char *end = strnchr(s, &n, '\n'); - if (end) { - return (size_t)(end - s); - } - return (size_t)m->size; + char *end = memchr(s, '\n', (size_t)m->size); + return end ? (size_t)(end - s) : (size_t)m->size; } #define MATCH_CHAR_MAX_LEN 800 @@ -148,9 +144,9 @@ static int count_n_matched_chars(mmfile_t **sp, const size_t n, bool iwhite) mmfile_t fastforward_buf_to_lnum(mmfile_t s, linenr_T lnum) { for (int i = 0; i < lnum - 1; i++) { - size_t n = (size_t)s.size; - s.ptr = strnchr(s.ptr, &n, '\n'); - s.size = (int)n; + char *line_end = memchr(s.ptr, '\n', (size_t)s.size); + s.size = line_end ? (int)(s.size - (line_end - s.ptr)) : 0; + s.ptr = line_end; if (!s.ptr) { break; } diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 7505eaba64..818e67b32d 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -498,20 +498,6 @@ char *vim_strchr(const char *const string, const int c) } } -// Sized version of strchr that can handle embedded NULs. -// Adjusts n to the new size. -char *strnchr(const char *p, size_t *n, int c) -{ - while (*n > 0) { - if (*p == c) { - return (char *)p; - } - p++; - (*n)--; - } - return NULL; -} - // Sort an array of strings. static int sort_compare(const void *s1, const void *s2) -- cgit From aa976f0d932738cb4b4f7cf5bef3d5157c68232d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 5 Feb 2025 11:36:01 +0800 Subject: fix(messages): add a trailing space to inputlist() etc. prompts (#32328) Before #31525 the prompts had a trailing space. Also add a test for #7857. --- src/nvim/input.c | 4 +- test/functional/ui/messages_spec.lua | 39 ++++++++++++++++++- test/functional/vimscript/timer_spec.lua | 65 ++++++++++++++++++++++++++------ 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/nvim/input.c b/src/nvim/input.c index ca2a13e016..fb89d86a75 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -159,9 +159,9 @@ int prompt_for_input(char *prompt, int hl_id, bool one_key, bool *mouse_used) if (prompt == NULL) { if (mouse_used != NULL) { - prompt = _("Type number and or click with the mouse (q or empty cancels):"); + prompt = _("Type number and or click with the mouse (q or empty cancels): "); } else { - prompt = _("Type number and (q or empty cancels):"); + prompt = _("Type number and (q or empty cancels): "); } } diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index c996d117f2..5c55dfe910 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1259,7 +1259,7 @@ stack traceback: content = { { '' } }, hl_id = 0, pos = 0, - prompt = 'Type number and or click with the mouse (q or empty cancels):', + prompt = 'Type number and or click with the mouse (q or empty cancels): ', }, }, messages = { @@ -1282,7 +1282,7 @@ stack traceback: content = { { '1' } }, hl_id = 0, pos = 1, - prompt = 'Type number and or click with the mouse (q or empty cancels):', + prompt = 'Type number and or click with the mouse (q or empty cancels): ', }, }, messages = { @@ -1302,6 +1302,41 @@ stack traceback: ]], cmdline = { { abort = false } }, }) + + async_meths.nvim_command("let g:n = inputlist(['input0', 'input1'])") + screen:expect({ + grid = [[ + ^Hello | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 0, + pos = 0, + prompt = 'Type number and or click with the mouse (q or empty cancels): ', + }, + }, + messages = { + { + content = { { 'input0\ninput1\n' } }, + history = false, + kind = 'list_cmd', + }, + }, + }) + + feed('42') + screen:expect({ + grid = [[ + ^Hello | + {1:~ }|*4 + ]], + cmdline = { { + abort = false, + } }, + }) + eq(42, eval('g:n')) end) it('supports nvim_echo messages with multiple attrs', function() diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index d1b8bfe5d9..3e4e6de35c 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -94,12 +94,56 @@ describe('timers', function() assert(0 <= diff and diff <= 4, 'expected (0 <= diff <= 4), got: ' .. tostring(diff)) end) + it('are triggered in inputlist() call #7857', function() + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:n = inputlist(['input0', 'input1']) + ]], + {} + ) + retry(nil, nil, function() + local val = eval('g:val') + ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:n')")) + end) + feed('42') + eq(42, eval('g:n')) + end) + + it('are triggered in confirm() call', function() + api.nvim_ui_attach(80, 24, {}) -- needed for confirm() to work + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:n = confirm('Are you sure?', "&Yes\n&No\n&Cancel") + ]], + {} + ) + retry(nil, nil, function() + local val = eval('g:val') + ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:n')")) + end) + feed('c') + eq(3, eval('g:n')) + end) + it('are triggered in blocking getchar() call', function() - command("call timer_start(5, 'MyHandler', {'repeat': -1})") - async_meths.nvim_command('let g:val = 0 | let g:c = getchar()') + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:c = getchar() + ]], + {} + ) retry(nil, nil, function() local val = eval('g:val') ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:c')")) eq(0, eval('getchar(1)')) end) feed('c') @@ -126,39 +170,36 @@ describe('timers', function() redraw endfunc ]]) - async_meths.nvim_command('let g:c2 = getchar()') + async_meths.nvim_command("let g:c2 = getchar(-1, {'cursor': 'msg'})") async_meths.nvim_command( 'call timer_start(' .. load_adjust(100) .. ", 'AddItem', {'repeat': -1})" ) screen:expect([[ - ^ITEM 1 | + ITEM 1 | ITEM 2 | {1:~ }|*3 - | + ^ | ]]) async_meths.nvim_command('let g:cont = 1') screen:expect([[ - ^ITEM 1 | + ITEM 1 | ITEM 2 | ITEM 3 | {1:~ }|*2 - | + ^ | ]]) feed('3') eq(51, eval('g:c2')) - screen:expect { - grid = [[ + screen:expect([[ ^ITEM 1 | ITEM 2 | ITEM 3 | {1:~ }|*2 | - ]], - unchanged = true, - } + ]]) end) it('can be stopped', function() -- cgit From d769c340b95b731d9a589345741070f7988d7149 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 23 Jan 2025 08:38:57 +0100 Subject: build(deps): bump luv to v1.50.0-1 --- cmake.deps/deps.txt | 4 +- runtime/doc/luvref.txt | 394 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 340 insertions(+), 58 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 38c9d8cff7..c85696edef 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -10,8 +10,8 @@ LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 UNIBILIUM_URL https://github.com/neovim/unibilium/archive/v2.1.2.tar.gz UNIBILIUM_SHA256 370ecb07fbbc20d91d1b350c55f1c806b06bf86797e164081ccc977fc9b3af7a -LUV_URL https://github.com/luvit/luv/releases/download/1.48.0-2/luv-1.48.0-2.tar.gz -LUV_SHA256 2c3a1ddfebb4f6550293a40ee789f7122e97647eede51511f57203de48c03b7a +LUV_URL https://github.com/luvit/luv/archive/1.50.0-1.tar.gz +LUV_SHA256 bb4f0570571e40c1d2a7644f6f9c1309a6ccdb19bf4d397e8d7bfd0c6b88e613 LPEG_URL https://github.com/neovim/deps/raw/d495ee6f79e7962a53ad79670cb92488abe0b9b4/opt/lpeg-1.1.0.tar.gz LPEG_SHA256 4b155d67d2246c1ffa7ad7bc466c1ea899bbc40fef0257cc9c03cecbaed4352a diff --git a/runtime/doc/luvref.txt b/runtime/doc/luvref.txt index 2be73f0412..3cbdb53e01 100644 --- a/runtime/doc/luvref.txt +++ b/runtime/doc/luvref.txt @@ -102,6 +102,7 @@ Low-level implementation details and unexposed C functions and types are not documented here except for when they are relevant to behavior seen in the Lua module. +- |luv-constants| — Constants - |luv-error-handling| — Error handling - |luv-version-checking| — Version checking - |uv_loop_t| — Event loop @@ -130,13 +131,104 @@ module. - |luv-miscellaneous-utilities| — Miscellaneous utilities - |luv-metrics-operations| — Metrics operations +============================================================================== +CONSTANTS *luv-constants* + +As a Lua library, luv supports and encourages the use of lowercase strings to +represent options. For example: +>lua + -- signal start with string input + uv.signal_start("sigterm", function(signame) + print(signame) -- string output: "sigterm" + end) +< +However, luv also superficially exposes libuv constants in a Lua table at +`uv.constants` where its keys are uppercase constant names and their associated +values are integers defined internally by libuv. The values from this table may +be supported as function arguments, but their use may not change the output +type. For example: +>lua + -- signal start with integer input + uv.signal_start(uv.constants.SIGTERM, function(signame) + print(signame) -- string output: "sigterm" + end) +< +The uppercase constants defined in `uv.constants` that have associated +lowercase option strings are listed below. + +Address Families ~ + +- `AF_UNIX`: "unix" +- `AF_INET`: "inet" +- `AF_INET6`: "inet6" +- `AF_IPX`: "ipx" +- `AF_NETLINK`: "netlink" +- `AF_X25`: "x25" +- `AF_AX25`: "as25" +- `AF_ATMPVC`: "atmpvc" +- `AF_APPLETALK`: "appletalk" +- `AF_PACKET`: "packet" + +Signals ~ + +- `SIGHUP`: "sighup" +- `SIGINT`: "sigint" +- `SIGQUIT`: "sigquit" +- `SIGILL`: "sigill" +- `SIGTRAP`: "sigtrap" +- `SIGABRT`: "sigabrt" +- `SIGIOT`: "sigiot" +- `SIGBUS`: "sigbus" +- `SIGFPE`: "sigfpe" +- `SIGKILL`: "sigkill" +- `SIGUSR1`: "sigusr1" +- `SIGSEGV`: "sigsegv" +- `SIGUSR2`: "sigusr2" +- `SIGPIPE`: "sigpipe" +- `SIGALRM`: "sigalrm" +- `SIGTERM`: "sigterm" +- `SIGCHLD`: "sigchld" +- `SIGSTKFLT`: "sigstkflt" +- `SIGCONT`: "sigcont" +- `SIGSTOP`: "sigstop" +- `SIGTSTP`: "sigtstp" +- `SIGBREAK`: "sigbreak" +- `SIGTTIN`: "sigttin" +- `SIGTTOU`: "sigttou" +- `SIGURG`: "sigurg" +- `SIGXCPU`: "sigxcpu" +- `SIGXFSZ`: "sigxfsz" +- `SIGVTALRM`: "sigvtalrm" +- `SIGPROF`: "sigprof" +- `SIGWINCH`: "sigwinch" +- `SIGIO`: "sigio" +- `SIGPOLL`: "sigpoll" +- `SIGLOST`: "siglost" +- `SIGPWR`: "sigpwr" +- `SIGSYS`: "sigsys" + +Socket Types ~ + +- `SOCK_STREAM`: "stream" +- `SOCK_DGRAM`: "dgram" +- `SOCK_SEQPACKET`: "seqpacket" +- `SOCK_RAW`: "raw" +- `SOCK_RDM`: "rdm" + +TTY Modes ~ + +- `TTY_MODE_NORMAL`: "normal" +- `TTY_MODE_RAW`: "raw" +- `TTY_MODE_IO`: "io" + ============================================================================== ERROR HANDLING *luv-error-handling* -In libuv, errors are negative numbered constants; however, while those errors -are exposed through `uv.errno`, the functions used to handle them are not -exposed to luv users. Instead, if an internal error is encountered, the luv -function will return to the caller an assertable `nil, err, name` tuple. +In libuv, errors are represented by negative numbered constants. While these +constants are made available in the `uv.errno` table, they are not returned by +luv funtions and the libuv functions used to handle them are not exposed. +Instead, if an internal error is encountered, the failing luv function will +return to the caller an assertable `nil, err, name` tuple: - `nil` idiomatically indicates failure - `err` is a string with the format `{name}: {message}` @@ -153,9 +245,8 @@ success, or sometimes nothing at all. These cases are documented below. `uv.errno` *uv.errno* -A table value which exposes error constants as a map, where the key is the -error name (without the `UV_` prefix) and its value is a negative number. -See Libuv's "Error constants" page for further details. +Below is a list of known error names and error strings. See Libuv's "Error +constants" page for further details. (https://docs.libuv.org/en/v1.x/errors.html#error-constants) - `E2BIG`: argument list too long. @@ -1154,8 +1245,8 @@ Unix Notes: -- Create a new signal handler local signal = uv.new_signal() -- Define a handler function - uv.signal_start(signal, "sigint", function(signal) - print("got " .. signal .. ", shutting down") + uv.signal_start(signal, "sigint", function(signame) + print("got " .. signame .. ", shutting down") os.exit(1) end) < @@ -1167,34 +1258,40 @@ uv.new_signal() *uv.new_signal()* Returns: `uv_signal_t userdata` or `fail` -uv.signal_start({signal}, {signum}, {callback}) *uv.signal_start()* +uv.signal_start({signal}, {signame}, {callback}) *uv.signal_start()* - > method form `signal:start(signum, callback)` + > method form `signal:start(signame, callback)` Parameters: - `signal`: `uv_signal_t userdata` - - `signum`: `integer` or `string` + - `signame`: `string` or `integer` - `callback`: `callable` - - `signum`: `string` + - `signame`: `string` Start the handle with the given callback, watching for the given signal. + See |luv-constants| for supported `signame` input and output + values. + Returns: `0` or `fail` *uv.signal_start_oneshot()* -uv.signal_start_oneshot({signal}, {signum}, {callback}) +uv.signal_start_oneshot({signal}, {signame}, {callback}) - > method form `signal:start_oneshot(signum, callback)` + > method form `signal:start_oneshot(signame, callback)` Parameters: - `signal`: `uv_signal_t userdata` - - `signum`: `integer` or `string` + - `signame`: `string` or `integer` - `callback`: `callable` - - `signum`: `string` + - `signame`: `string` Same functionality as |uv.signal_start()| but the signal handler is reset the moment the signal is received. + See |luv-constants| for supported `signame` input and output + values. + Returns: `0` or `fail` uv.signal_stop({signal}) *uv.signal_stop()* @@ -1349,30 +1446,36 @@ uv.spawn({path}, {options}, {on_exit}) *uv.spawn()* Returns: `uv_process_t userdata`, `integer` -uv.process_kill({process}, {signum}) *uv.process_kill()* +uv.process_kill({process}, {signame}) *uv.process_kill()* - > method form `process:kill(signum)` + > method form `process:kill(signame)` Parameters: - `process`: `uv_process_t userdata` - - `signum`: `integer` or `string` or `nil` (default: `sigterm`) + - `signame`: `string` or `integer` or `nil` (default: `sigterm`) Sends the specified signal to the given process handle. Check the documentation on |uv_signal_t| for signal support, specially on Windows. + See |luv-constants| for supported `signame` input and output + values. + Returns: `0` or `fail` -uv.kill({pid}, {signum}) *uv.kill()* +uv.kill({pid}, {signame}) *uv.kill()* Parameters: - `pid`: `integer` - - `signum`: `integer` or `string` or `nil` (default: `sigterm`) + - `signame`: `string` or `integer` or `nil` (default: `sigterm`) Sends the specified signal to the given PID. Check the documentation on |uv_signal_t| for signal support, specially on Windows. + See |luv-constants| for supported `signame` input and output + values. + Returns: `0` or `fail` uv.process_get_pid({process}) *uv.process_get_pid()* @@ -1638,12 +1741,13 @@ TCP handles are used to represent both TCP streams and servers. uv.new_tcp([{flags}]) *uv.new_tcp()* Parameters: - - `flags`: `string` or `nil` + - `flags`: `string` or `integer` or `nil` Creates and initializes a new |uv_tcp_t|. Returns the Lua - userdata wrapping it. Flags may be a family string: `"unix"`, - `"inet"`, `"inet6"`, `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, - `"atmpvc"`, `"appletalk"`, or `"packet"`. + userdata wrapping it. + + If set, `flags` must be a valid address family. See + |luv-constants| for supported address family input values. Returns: `uv_tcp_t userdata` or `fail` @@ -1744,6 +1848,8 @@ uv.tcp_getpeername({tcp}) *uv.tcp_getpeername()* Get the address of the peer connected to the handle. + See |luv-constants| for supported address `family` output values. + Returns: `table` or `fail` - `ip` : `string` - `family` : `string` @@ -1758,6 +1864,8 @@ uv.tcp_getsockname({tcp}) *uv.tcp_getsockname()* Get the current address to which the handle is bound. + See |luv-constants| for supported address `family` output values. + Returns: `table` or `fail` - `ip` : `string` - `family` : `string` @@ -1823,8 +1931,7 @@ uv.socketpair([{socktype}, [{protocol}, [{flags1}, [{flags2}]]]]) |uv.tcp_open()|, used with |uv.spawn()|, or for any other purpose. - When specified as a string, `socktype` must be one of - `"stream"`, `"dgram"`, `"raw"`, `"rdm"`, or `"seqpacket"`. + See |luv-constants| for supported `socktype` input values. When `protocol` is set to 0 or nil, it will be automatically chosen based on the socket's domain and type. When `protocol` @@ -2184,17 +2291,11 @@ uv.tty_set_mode({tty}, {mode}) *uv.tty_set_mode()* Parameters: - `tty`: `uv_tty_t userdata` - - `mode`: `integer` + - `mode`: `string` or `integer` Set the TTY using the specified terminal mode. - Parameter `mode` is a C enum with the following values: - - - 0 - UV_TTY_MODE_NORMAL: Initial/normal terminal mode - - 1 - UV_TTY_MODE_RAW: Raw input mode (On Windows, - ENABLE_WINDOW_INPUT is also enabled) - - 2 - UV_TTY_MODE_IO: Binary-safe I/O mode for IPC - (Unix-only) + See |luv-constants| for supported TTY mode input values. Returns: `0` or `fail` @@ -2265,9 +2366,7 @@ uv.new_udp([{flags}]) *uv.new_udp()* Creates and initializes a new |uv_udp_t|. Returns the Lua userdata wrapping it. The actual socket is created lazily. - When specified, `family` must be one of `"unix"`, `"inet"`, - `"inet6"`, `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, - `"atmpvc"`, `"appletalk"`, or `"packet"`. + See |luv-constants| for supported address `family` input values. When specified, `mmsgs` determines the number of messages able to be received at one time via `recvmmsg(2)` (the allocated @@ -2510,6 +2609,51 @@ uv.udp_try_send({udp}, {data}, {host}, {port}) *uv.udp_try_send()* Returns: `integer` or `fail` +uv.udp_try_send2({udp}, {messages}, {flags}) *uv.udp_try_send2()* + + > method form `udp:try_send2(messages, flags)` + + Parameters: + - `udp`: `uv_udp_t userdata` + - `messages`: `table` + - `[1, 2, 3, ..., n]` : `table` + - `data` : `buffer` + - `addr` : `table` + - `ip` : `string` + - `port` : `integer` + - `flags`: `nil` (see below) + - `port`: `integer` + + Like `uv.udp_try_send()`, but can send multiple datagrams. + Lightweight abstraction around `sendmmsg(2)`, with a + `sendmsg(2)` fallback loop for platforms that do not support + the former. The `udp` handle must be fully initialized, either + from a `uv.udp_bind` call, another call that will bind + automatically (`udp_send`, `udp_try_send`, etc), or from + `uv.udp_connect`. `messages` should be an array-like table, + where `addr` must be specified if the `udp` has not been + connected via `udp_connect`. Otherwise, `addr` must be `nil`. + + `flags` is reserved for future extension and must currently be + `nil` or `0` or `{}`. + + Returns the number of messages sent successfully. An error will only be returned + if the first datagram failed to be sent. + + Returns: `integer` or `fail` + >lua + -- If client:connect(...) was not called + local addr = { ip = "127.0.0.1", port = 1234 } + client:try_send2({ + { data = "Message 1", addr = addr }, + { data = "Message 2", addr = addr }, + }) + -- If client:connect(...) was called + client:try_send2({ + { data = "Message 1" }, + { data = "Message 2" }, + }) +< uv.udp_recv_start({udp}, {callback}) *uv.udp_recv_start()* > method form `udp:recv_start(callback)` @@ -2531,6 +2675,8 @@ uv.udp_recv_start({udp}, {callback}) *uv.udp_recv_start()* been bound with |uv.udp_bind()| it is bound to `0.0.0.0` (the "all interfaces" IPv4 address) and a random port number. + See |luv-constants| for supported address `family` output values. + Returns: `0` or `fail` uv.udp_recv_stop({udp}) *uv.udp_recv_stop()* @@ -2743,7 +2889,8 @@ uv.fs_open({path}, {flags}, {mode} [, {callback}]) *uv.fs_open()* Parameters: - `path`: `string` - `flags`: `string` or `integer` - - `mode`: `integer` + - `mode`: `integer` (octal `chmod(1)` mode, e.g. + `tonumber('644', 8)`) - `callback`: `callable` (async version) or `nil` (sync version) - `err`: `nil` or `string` @@ -2829,7 +2976,8 @@ uv.fs_mkdir({path}, {mode} [, {callback}]) *uv.fs_mkdir()* Parameters: - `path`: `string` - - `mode`: `integer` + - `mode`: `integer` (octal representation of `chmod(1)` mode, + e.g. `tonumber('755', 8)`) - `callback`: `callable` (async version) or `nil` (sync version) - `err`: `nil` or `string` @@ -3078,7 +3226,9 @@ uv.fs_access({path}, {mode} [, {callback}]) *uv.fs_access()* Parameters: - `path`: `string` - - `mode`: `integer` + - `mode`: `integer` `string` (a combination of the `'r'`, + `'w'` and `'x'` characters denoting the symbolic mode as per + `chmod(1)`) - `callback`: `callable` (async version) or `nil` (sync version) - `err`: `nil` or `string` @@ -3097,7 +3247,8 @@ uv.fs_chmod({path}, {mode} [, {callback}]) *uv.fs_chmod()* Parameters: - `path`: `string` - - `mode`: `integer` + - `mode`: `integer` (octal representation of `chmod(1)` mode, + e.g. `tonumber('644', 8)`) - `callback`: `callable` (async version) or `nil` (sync version) - `err`: `nil` or `string` @@ -3480,14 +3631,17 @@ uv.getaddrinfo({host}, {service} [, {hints} [, {callback}]]) *uv.getaddrinfo()* Equivalent to `getaddrinfo(3)`. Either `node` or `service` may be `nil` but not both. - Valid hint strings for the keys that take a string: - - `family`: `"unix"`, `"inet"`, `"inet6"`, `"ipx"`, - `"netlink"`, `"x25"`, `"ax25"`, `"atmpvc"`, `"appletalk"`, - or `"packet"` - - `socktype`: `"stream"`, `"dgram"`, `"raw"`, `"rdm"`, or - `"seqpacket"` - - `protocol`: will be looked up using the `getprotobyname(3)` - function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc) + See |luv-constants| for supported address `family` input and + output values. + + See |luv-constants| for supported `socktype` input and + output values. + + When `protocol` is set to `0` or `nil`, it will be + automatically chosen based on the socket's domain and type. + When `protocol` is specified as a string, it will be looked up + using the `getprotobyname(3)` function. Examples: `"ip"`, + `"icmp"`, `"tcp"`, `"udp"`, etc. Returns (sync version): `table` or `fail` - `[1, 2, 3, ..., n]` : `table` @@ -3515,9 +3669,7 @@ uv.getnameinfo({address} [, {callback}]) *uv.getnameinfo()* Equivalent to `getnameinfo(3)`. - When specified, `family` must be one of `"unix"`, `"inet"`, - `"inet6"`, `"ipx"`, `"netlink"`, `"x25"`, `"ax25"`, - `"atmpvc"`, `"appletalk"`, or `"packet"`. + See |luv-constants| for supported address `family` input values. Returns (sync version): `string, string` or `fail` @@ -3526,7 +3678,7 @@ uv.getnameinfo({address} [, {callback}]) *uv.getnameinfo()* ============================================================================== THREADING AND SYNCHRONIZATION UTILITIES *luv-threading-and-synchronization-utilities* -Libuv provides cross-platform implementations for multiple threading an +Libuv provides cross-platform implementations for multiple threading and synchronization primitives. The API largely follows the pthreads API. uv.new_thread([{options}, ] {entry}, {...}) *uv.new_thread()* @@ -3683,6 +3835,43 @@ uv.thread_join({thread}) *uv.thread_join()* Returns: `boolean` or `fail` +uv.thread_detach({thread}) *uv.thread_detach()* + + > method form `thread:detach()` + + Parameters: + - `thread`: `luv_thread_t userdata` + + Detaches a thread. Detached threads automatically release + their resources upon termination, eliminating the need for the + application to call `uv.thread_join`. + + Returns: `boolean` or `fail` + +uv.thread_setname({name}) *uv.thread_setname()* + + Parameters: + - `name`: `string` + + Sets the name of the current thread. Different platforms + define different limits on the max number of characters a + thread name can be: Linux, IBM i (16), macOS (64), Windows + (32767), and NetBSD (32), etc. The name will be truncated if + `name` is larger than the limit of the platform. + + Returns: `0` or `fail` + +uv.thread_getname({thread}) *uv.thread_getname()* + + > method form `thread:getname()` + + Parameters: + - `thread`: `luv_thread_t userdata` + + Gets the name of the thread specified by `thread`. + + Returns: `string` or `fail` + uv.sleep({msec}) *uv.sleep()* Parameters: @@ -3796,6 +3985,36 @@ uv.getrusage() *uv.getrusage()* - `nvcsw` : `integer` (voluntary context switches) - `nivcsw` : `integer` (involuntary context switches) +uv.getrusage_thread() *uv.getrusage_thread()* + Gets the resource usage measures for the calling thread. + + Note: Not supported on all platforms. May return `ENOTSUP`. + + On macOS and Windows not all fields are set (the unsupported + fields are filled with zeroes). + + Returns: `table` or `fail` + - `utime` : `table` (user CPU time used) + - `sec` : `integer` + - `usec` : `integer` + - `stime` : `table` (system CPU time used) + - `sec` : `integer` + - `usec` : `integer` + - `maxrss` : `integer` (maximum resident set size) + - `ixrss` : `integer` (integral shared memory size) + - `idrss` : `integer` (integral unshared data size) + - `isrss` : `integer` (integral unshared stack size) + - `minflt` : `integer` (page reclaims (soft page faults)) + - `majflt` : `integer` (page faults (hard page faults)) + - `nswap` : `integer` (swaps) + - `inblock` : `integer` (block input operations) + - `oublock` : `integer` (block output operations) + - `msgsnd` : `integer` (IPC messages sent) + - `msgrcv` : `integer` (IPC messages received) + - `nsignals` : `integer` (signals received) + - `nvcsw` : `integer` (voluntary context switches) + - `nivcsw` : `integer` (involuntary context switches) + uv.available_parallelism() *uv.available_parallelism()* Returns an estimate of the default amount of parallelism a @@ -3968,6 +4187,8 @@ uv.interface_addresses() *uv.interface_addresses()* information where fields are `ip`, `family`, `netmask`, `internal`, and `mac`. + See |luv-constants| for supported address `family` output values. + Returns: `table` - `[name(s)]` : `table` - `ip` : `string` @@ -4201,6 +4422,67 @@ uv.metrics_info() *uv.metrics_info()* - `events` : `integer` - `events_waiting` : `integer` +============================================================================== +STRING MANIPULATION FUNCTIONS *luv-string-manipulation* + +These string utilities are needed internally for dealing with Windows, and are +exported to allow clients to work uniformly with this data when the libuv API +is not complete. + +Notes: +1. New in luv version 1.49.0. +2. See the WTF-8 spec (https://simonsapin.github.io/wtf-8/) for information + about WTF-8. +3. Luv uses Lua-style strings, which means that all inputs and return values + (UTF-8 or UTF-16 strings) do not include a NUL terminator. + +uv.utf16_length_as_wtf8({utf16}) *uv.utf16_length_as_wtf8()* + + Get the length (in bytes) of a UTF-16 (or UCS-2) string + `utf16` value after converting it to WTF-8. The endianness of + the UTF-16 (or UCS-2) string is assumed to be the same as the + native endianness of the platform. + + Parameters: + - `utf16`: `string` + + Returns: `integer` + +uv.utf16_to_wtf8({utf16}) *uv.utf16_to_wtf8()* + + Convert UTF-16 (or UCS-2) string `utf16` to UTF-8 string. The + endianness of the UTF-16 (or UCS-2) string is assumed to be + the same as the native endianness of the platform. + + Parameters: + - `utf16`: `string` + + Returns: `string` + +uv.wtf8_length_as_utf16({wtf16}) *uv.wtf8_length_as_utf16()* + + Get the length (in UTF-16 code units) of a WTF-8 `wtf8` value + after converting it to UTF-16 (or UCS-2). + + Note: The number of bytes needed for a UTF-16 (or UCS-2) + string is ` * 2`. + + Parameters: + - `wtf8`: `string` + + Returns: `integer` + +uv.wtf8_to_utf16({wtf16}) *uv.wtf8_to_utf16()* + + Convert WTF-8 string in `wtf8` to UTF-16 (or UCS-2) string. + The endianness of the UTF-16 (or UCS-2) string is assumed to + be the same as the native endianness of the platform. + + Parameters: + - `wtf8`: `string` + + Returns: `string` + ============================================================================== CREDITS *luv-credits* @@ -4211,4 +4493,4 @@ https://github.com/luvit/luv/commit/dcd1a1cad5b05634a7691402d6ca2f214fb4ae76. Based on https://github.com/nanotee/luv-vimdocs with kind permission. -vim:tw=78:ts=8:ft=help:norl: +vim:tw=78:ts=8:sw=2:et:ft=help:norl: -- cgit From 09f9f0a94625002f4c70efbdf858fe6918cbc9c6 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 4 Feb 2025 09:25:03 -0800 Subject: feat(treesitter): show which nodes are missing in InspectTree Now `:InspectTree` will show missing nodes as e.g. `(MISSING identifier)` or `(MISSING ";")` rather than just `(identifier)` or `";"`. This is doable because the `MISSING` keyword is now valid query syntax. Co-authored-by: Christian Clason --- runtime/doc/news.txt | 1 + runtime/lua/vim/treesitter/dev.lua | 5 +- test/functional/treesitter/inspect_tree_spec.lua | 59 ++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 4e573197b7..fda37852d6 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -381,6 +381,7 @@ TREESITTER queries in addition to overriding. • |LanguageTree:is_valid()| now accepts a range parameter to narrow the scope of the validity check. +• |:InspectTree| now shows which nodes are missing. TUI diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index ab08e1a527..24dd8243db 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -224,9 +224,12 @@ function TSTreeView:draw(bufnr) local text ---@type string if item.node:named() then - text = string.format('(%s', item.node:type()) + text = string.format('(%s%s', item.node:missing() and 'MISSING ' or '', item.node:type()) else text = string.format('%q', item.node:type()):gsub('\n', 'n') + if item.node:missing() then + text = string.format('(MISSING %s)', text) + end end if item.field then text = string.format('%s: %s', item.field, text) diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua index 47f3421cfe..68622140e4 100644 --- a/test/functional/treesitter/inspect_tree_spec.lua +++ b/test/functional/treesitter/inspect_tree_spec.lua @@ -178,4 +178,63 @@ describe('vim.treesitter.inspect_tree', function() -- close source buffer window and all remaining tree windows n.expect_exit(n.command, 'quit') end) + + it('shows which nodes are missing', function() + insert([[ + int main() { + if (a.) { + // ^ MISSING field_identifier here + if (1) d() + // ^ MISSING ";" here + } + } + ]]) + + exec_lua(function() + vim.treesitter.start(0, 'c') + vim.treesitter.inspect_tree() + end) + feed('a') + + expect_tree [[ + (translation_unit ; [0, 0] - [8, 0] + (function_definition ; [0, 0] - [6, 1] + type: (primitive_type) ; [0, 0] - [0, 3] + declarator: (function_declarator ; [0, 4] - [0, 10] + declarator: (identifier) ; [0, 4] - [0, 8] + parameters: (parameter_list ; [0, 8] - [0, 10] + "(" ; [0, 8] - [0, 9] + ")")) ; [0, 9] - [0, 10] + body: (compound_statement ; [0, 11] - [6, 1] + "{" ; [0, 11] - [0, 12] + (if_statement ; [1, 4] - [5, 5] + "if" ; [1, 4] - [1, 6] + condition: (parenthesized_expression ; [1, 7] - [1, 11] + "(" ; [1, 7] - [1, 8] + (field_expression ; [1, 8] - [1, 10] + argument: (identifier) ; [1, 8] - [1, 9] + operator: "." ; [1, 9] - [1, 10] + field: (MISSING field_identifier)) ; [1, 10] - [1, 10] + ")") ; [1, 10] - [1, 11] + consequence: (compound_statement ; [1, 12] - [5, 5] + "{" ; [1, 12] - [1, 13] + (comment) ; [2, 4] - [2, 41] + (if_statement ; [3, 8] - [4, 36] + "if" ; [3, 8] - [3, 10] + condition: (parenthesized_expression ; [3, 11] - [3, 14] + "(" ; [3, 11] - [3, 12] + (number_literal) ; [3, 12] - [3, 13] + ")") ; [3, 13] - [3, 14] + consequence: (expression_statement ; [3, 15] - [4, 36] + (call_expression ; [3, 15] - [3, 18] + function: (identifier) ; [3, 15] - [3, 16] + arguments: (argument_list ; [3, 16] - [3, 18] + "(" ; [3, 16] - [3, 17] + ")")) ; [3, 17] - [3, 18] + (comment) ; [4, 8] - [4, 36] + (MISSING ";"))) ; [4, 36] - [4, 36] + "}")) ; [5, 4] - [5, 5] + "}"))) ; [6, 0] - [6, 1] + ]] + end) end) -- cgit From 38a52caec09eb15c9ff8b4db6f0cdb7e2a28eb98 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 2 Feb 2025 14:06:05 -0800 Subject: feat(diagnostic): add `current_line` option for `virtual_text` handler --- runtime/doc/diagnostic.txt | 2 + runtime/doc/news.txt | 2 + runtime/lua/vim/diagnostic.lua | 130 ++++++++++++++++++++++---------- test/functional/lua/diagnostic_spec.lua | 19 +++++ 4 files changed, 115 insertions(+), 38 deletions(-) diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index b7bdf2b446..5e1e04ce56 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -621,6 +621,8 @@ Lua module: vim.diagnostic *diagnostic-api* • {severity}? (`vim.diagnostic.SeverityFilter`) Only show virtual text for diagnostics matching the given severity |diagnostic-severity| + • {current_line}? (`boolean`) Only show diagnostics for the + current line. (default `false`) • {source}? (`boolean|"if_many"`) Include the diagnostic source in virtual text. Use `'if_many'` to only show sources if there is more than one diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index fda37852d6..12fac28db8 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -243,6 +243,8 @@ DIAGNOSTICS |vim.diagnostic.jump()|. • A "virtual_lines" diagnostic handler was added to render diagnostics using virtual lines below the respective code. +• The "virtual_text" diagnostic handler accepts a `current_line` option to + only show virtual text at the cursor's line. EDITOR diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 972a5d1fa6..621945aedd 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -190,6 +190,10 @@ end --- severity |diagnostic-severity| --- @field severity? vim.diagnostic.SeverityFilter --- +--- Only show diagnostics for the current line. +--- (default `false`) +--- @field current_line? boolean +--- --- Include the diagnostic source in virtual text. Use `'if_many'` to only --- show sources if there is more than one diagnostic source in the buffer. --- Otherwise, any truthy value means to always show the diagnostic source. @@ -630,6 +634,26 @@ local function diagnostic_lines(diagnostics) return diagnostics_by_line end +--- @param diagnostics table +--- @return vim.Diagnostic[] +local function diagnostics_at_cursor(diagnostics) + local lnum = api.nvim_win_get_cursor(0)[1] - 1 + + if diagnostics[lnum] ~= nil then + return diagnostics[lnum] + end + + local cursor_diagnostics = {} + for _, line_diags in pairs(diagnostics) do + for _, diag in ipairs(line_diags) do + if diag.end_lnum and lnum >= diag.lnum and lnum <= diag.end_lnum then + table.insert(cursor_diagnostics, diag) + end + end + end + return cursor_diagnostics +end + --- @param namespace integer --- @param bufnr integer --- @param diagnostics vim.Diagnostic[] @@ -1570,6 +1594,28 @@ M.handlers.underline = { end, } +--- @param namespace integer +--- @param bufnr integer +--- @param diagnostics table +--- @param opts vim.diagnostic.Opts.VirtualText +local function render_virtual_text(namespace, bufnr, diagnostics, opts) + api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) + + for line, line_diagnostics in pairs(diagnostics) do + local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts) + + if virt_texts then + api.nvim_buf_set_extmark(bufnr, namespace, line, 0, { + hl_mode = opts.hl_mode or 'combine', + virt_text = virt_texts, + virt_text_pos = opts.virt_text_pos, + virt_text_hide = opts.virt_text_hide, + virt_text_win_col = opts.virt_text_win_col, + }) + end + end +end + M.handlers.virtual_text = { show = function(namespace, bufnr, diagnostics, opts) vim.validate('namespace', namespace, 'number') @@ -1601,23 +1647,44 @@ M.handlers.virtual_text = { ns.user_data.virt_text_ns = api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_text', ns.name)) end + if not ns.user_data.virt_text_augroup then + ns.user_data.virt_text_augroup = api.nvim_create_augroup( + string.format('nvim.%s.diagnostic.virt_text', ns.name), + { clear = true } + ) + end - local virt_text_ns = ns.user_data.virt_text_ns - local buffer_line_diagnostics = diagnostic_lines(diagnostics) - for line, line_diagnostics in pairs(buffer_line_diagnostics) do - local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text) + api.nvim_clear_autocmds({ group = ns.user_data.virt_text_augroup, buffer = bufnr }) - if virt_texts then - api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, { - hl_mode = opts.virtual_text.hl_mode or 'combine', - virt_text = virt_texts, - virt_text_pos = opts.virtual_text.virt_text_pos, - virt_text_hide = opts.virtual_text.virt_text_hide, - virt_text_win_col = opts.virtual_text.virt_text_win_col, - }) - end + local line_diagnostics = diagnostic_lines(diagnostics) + + if opts.virtual_text.current_line == true then + api.nvim_create_autocmd('CursorMoved', { + buffer = bufnr, + group = ns.user_data.virt_text_augroup, + callback = function() + local lnum = api.nvim_win_get_cursor(0)[1] - 1 + render_virtual_text( + ns.user_data.virt_text_ns, + bufnr, + { [lnum] = diagnostics_at_cursor(line_diagnostics) }, + opts.virtual_text + ) + end, + }) + -- Also show diagnostics for the current line before the first CursorMoved event. + local lnum = api.nvim_win_get_cursor(0)[1] - 1 + render_virtual_text( + ns.user_data.virt_text_ns, + bufnr, + { [lnum] = diagnostics_at_cursor(line_diagnostics) }, + opts.virtual_text + ) + else + render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text) end - save_extmarks(virt_text_ns, bufnr) + + save_extmarks(ns.user_data.virt_text_ns, bufnr) end, hide = function(namespace, bufnr) local ns = M.get_namespace(namespace) @@ -1626,6 +1693,7 @@ M.handlers.virtual_text = { if api.nvim_buf_is_valid(bufnr) then api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_text_ns, 0, -1) end + api.nvim_clear_autocmds({ group = ns.user_data.virt_text_augroup, buffer = bufnr }) end end, } @@ -1814,28 +1882,6 @@ local function render_virtual_lines(namespace, bufnr, diagnostics) end end ---- @param diagnostics table ---- @param namespace integer ---- @param bufnr integer -local function render_virtual_lines_at_current_line(diagnostics, namespace, bufnr) - local lnum = api.nvim_win_get_cursor(0)[1] - 1 - local cursor_diagnostics = {} - - if diagnostics[lnum] ~= nil then - cursor_diagnostics = diagnostics[lnum] - else - for _, line_diags in pairs(diagnostics) do - for _, diag in ipairs(line_diags) do - if diag.end_lnum and lnum >= diag.lnum and lnum <= diag.end_lnum then - table.insert(cursor_diagnostics, diag) - end - end - end - end - - render_virtual_lines(namespace, bufnr, cursor_diagnostics) -end - M.handlers.virtual_lines = { show = function(namespace, bufnr, diagnostics, opts) vim.validate('namespace', namespace, 'number') @@ -1876,11 +1922,19 @@ M.handlers.virtual_lines = { buffer = bufnr, group = ns.user_data.virt_lines_augroup, callback = function() - render_virtual_lines_at_current_line(line_diagnostics, ns.user_data.virt_lines_ns, bufnr) + render_virtual_lines( + ns.user_data.virt_lines_ns, + bufnr, + diagnostics_at_cursor(line_diagnostics) + ) end, }) -- Also show diagnostics for the current line before the first CursorMoved event. - render_virtual_lines_at_current_line(line_diagnostics, ns.user_data.virt_lines_ns, bufnr) + render_virtual_lines( + ns.user_data.virt_lines_ns, + bufnr, + diagnostics_at_cursor(line_diagnostics) + ) else render_virtual_lines(ns.user_data.virt_lines_ns, bufnr, diagnostics) end diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 9a982a1c6d..a19f558ef2 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -2160,6 +2160,25 @@ describe('vim.diagnostic', function() eq(1, #result) eq(' An error there!', result[1][4].virt_text[3][1]) end) + + it('can only show virtual_text for the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_text = { current_line = true } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' Error here!', result[1][4].virt_text[3][1]) + end) end) describe('handlers.virtual_lines', function() -- cgit From 44740e561fc93afe3ebecfd3618bda2d2abeafb0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 5 Feb 2025 13:23:33 -0800 Subject: fix(log): RPC log format #32337 Problem: RPC log messages show `log_notify` function name, which is not useful: DBG 2025-02-04T22:28:02.419 ui.37862 log_notify:57: RPC -> 3: [notify] nvim_ui_set_focus DBG 2025-02-04T22:28:02.419 nvim.37863.0 log_notify:57: RPC <- 1: [notify] nvim_ui_set_focus Solution: Call logmsg() directly. DBG 2025-02-04T22:42:00.104 ui.40680 RPC: -> 3: [notify] nvim_ui_attach DBG 2025-02-04T22:42:00.104 ui.40680 RPC: -> 3: [notify] nvim_set_client_info --- src/nvim/msgpack_rpc/channel.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index e92039b99f..e38bc1896d 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -44,17 +44,20 @@ static void log_request(char *dir, uint64_t channel_id, uint32_t req_id, const char *name) { - DLOGN("RPC %s %" PRIu64 ": %s id=%u: %s\n", dir, channel_id, REQ, req_id, name); + logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, false, "%s %" PRIu64 ": %s id=%u: %s\n", dir, channel_id, + REQ, req_id, name); } static void log_response(char *dir, uint64_t channel_id, char *kind, uint32_t req_id) { - DLOGN("RPC %s %" PRIu64 ": %s id=%u\n", dir, channel_id, kind, req_id); + logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, false, "%s %" PRIu64 ": %s id=%u\n", dir, channel_id, kind, + req_id); } static void log_notify(char *dir, uint64_t channel_id, const char *name) { - DLOGN("RPC %s %" PRIu64 ": %s %s\n", dir, channel_id, NOT, name); + logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, false, "%s %" PRIu64 ": %s %s\n", dir, channel_id, NOT, + name); } #else -- cgit