diff options
124 files changed, 6059 insertions, 4239 deletions
diff --git a/.github/workflows/linux.yml b/.github/workflows/ci.yml index d693f5e593..70cdc3b4a2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/ci.yml @@ -1,21 +1,31 @@ -name: Linux CI +name: CI on: [push, pull_request] jobs: - linux: - name: ${{ matrix.flavor }} (cc=${{ matrix.cc }}) - runs-on: ubuntu-latest + build: + name: ${{ matrix.os }} ${{ matrix.flavor }} (cc=${{ matrix.cc }}) strategy: matrix: include: - flavor: asan cc: clang-11 + runner: ubuntu-20.04 + os: linux - flavor: lint cc: gcc + runner: ubuntu-20.04 + os: linux - flavor: tsan cc: clang-11 + runner: ubuntu-20.04 + os: linux + - cc: clang + runner: macos-10.15 + os: osx + runs-on: ${{ matrix.runner }} env: CC: ${{ matrix.cc }} + CI_OS_NAME: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -26,17 +36,25 @@ jobs: if: matrix.flavor == 'asan' || matrix.flavor == 'tsan' run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' + sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main' - name: Install apt packages + if: matrix.os == 'linux' run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus cscope gcc-multilib gdb gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python python-pip python-setuptools python3 python3-pip python3-setuptools unzip valgrind xclip + sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus cscope gcc-multilib gdb gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip valgrind xclip - name: Install new clang if: matrix.flavor == 'asan' || matrix.flavor == 'tsan' run: sudo apt-get install -y clang-11 + - name: Install brew packages + if: matrix.os == 'osx' + run: | + brew update >/dev/null + brew install automake ccache cpanminus ninja + brew upgrade + - name: Setup interpreter packages run: | ./ci/before_install.sh diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index 84f26f949e..cc1cef5cc4 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -u -FLAVOR=$1 +FLAVOR=${1:-} cat <<EOF >> "$GITHUB_PATH" $HOME/.local/bin @@ -24,6 +24,7 @@ DEPS_CMAKE_FLAGS=-DUSE_BUNDLED_GPERF=OFF FUNCTIONALTEST=functionaltest CCACHE_COMPRESS=1 CCACHE_SLOPPINESS=time_macros,file_macro +CCACHE_DIR=$HOME/.ccache EOF BUILD_FLAGS="CMAKE_FLAGS=-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$HOME/nvim-install -DBUSTED_OUTPUT_TYPE=nvim -DDEPS_PREFIX=$HOME/nvim-deps/usr -DMIN_LOG_LEVEL=3" diff --git a/.travis.yml b/.travis.yml index 2f4603fa5c..b68f4f1bc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,7 @@ env: - CCACHE_COMPRESS=1 - CCACHE_SLOPPINESS=time_macros,file_macro - CCACHE_BASEDIR="$TRAVIS_BUILD_DIR" + - CI_OS_NAME="$TRAVIS_OS_NAME" anchors: envs: &common-job-env @@ -4,7 +4,7 @@ [Chat](https://gitter.im/neovim/neovim) | [Twitter](https://twitter.com/Neovim) -[](https://github.com/neovim/neovim/actions?query=workflow%3A%22Linux+CI%22) +[](https://github.com/neovim/neovim/actions?query=workflow%3A%22CI%22) [](https://ci.appveyor.com/project/neovim/neovim/branch/master) [](https://codecov.io/gh/neovim/neovim) [](https://scan.coverity.com/projects/2227) diff --git a/ci/before_script.sh b/ci/before_script.sh index 8bab1c4e17..701fe1d9eb 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -7,7 +7,7 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" # Enable ipv6 on Travis. ref: a39c8b7ce30d -if ! test "${TRAVIS_OS_NAME}" = osx ; then +if test -n "${TRAVIS_OS_NAME}" && ! test "${TRAVIS_OS_NAME}" = osx ; then echo "before_script.sh: enable ipv6" sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 fi diff --git a/ci/common/build.sh b/ci/common/build.sh index f0bdec0a0e..0ee4b7493f 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -1,5 +1,5 @@ _stat() { - if test "${TRAVIS_OS_NAME}" = osx ; then + if test "${CI_OS_NAME}" = osx ; then stat -f %Sm "${@}" else stat -c %y "${@}" diff --git a/ci/common/test.sh b/ci/common/test.sh index 4ef6260339..118e181dfa 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -15,7 +15,7 @@ print_core() { return 0 fi echo "======= Core file $core =======" - if test "${TRAVIS_OS_NAME}" = osx ; then + if test "${CI_OS_NAME}" = osx ; then lldb -Q -o "bt all" -f "${app}" -c "${core}" else gdb -n -batch -ex 'thread apply all bt full' "${app}" -c "${core}" @@ -30,7 +30,7 @@ check_core_dumps() { fi local app="${1:-${BUILD_DIR}/bin/nvim}" local cores - if test "${TRAVIS_OS_NAME}" = osx ; then + if test "${CI_OS_NAME}" = osx ; then cores="$(find /cores/ -type f -print)" local _sudo='sudo' else diff --git a/ci/install.sh b/ci/install.sh index ebbd820d9f..1edc1138ee 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -15,8 +15,10 @@ fi # Use default CC to avoid compilation problems when installing Python modules. echo "Install neovim module for Python 3." CC=cc python3 -m pip -q install --user --upgrade pynvim -echo "Install neovim module for Python 2." -CC=cc python2 -m pip -q install --user --upgrade pynvim +if python2 -m pip -c True 2>&1; then + echo "Install neovim module for Python 2." + CC=cc python2 -m pip -q install --user --upgrade pynvim +fi echo "Install neovim RubyGem." gem install --no-document --bindir "$HOME/.local/bin" --user-install --pre neovim diff --git a/man/nvim.1 b/man/nvim.1 index 9e7da629f7..305551c7d4 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -113,9 +113,6 @@ associated with a file. To overwrite a file, add an exclamation mark to the relevant Ex command, such as .Ic :w! . .Ic ":help 'readonly'" -.It Fl Z -Restricted mode. -Disable commands that make use of an external shell. .It Fl m Resets the 'write' option, to disable file modifications. Writing to a file is disabled, but buffers can still be modified. diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 94fd7cf505..112dd4354f 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -487,7 +487,7 @@ endfunction " Resolves Python executable path by invoking and checking `sys.executable`. function! s:python_exepath(invocation) abort - return s:normalize_path(system(a:invocation + return s:normalize_path(system(fnameescape(a:invocation) \ . ' -c "import sys; sys.stdout.write(sys.executable)"')) endfunction diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index dab88fde23..486ed99e3f 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -13,8 +13,6 @@ function! man#init() abort try " Check for -l support. call s:get_page(s:get_path('', 'man')) - catch /E145:/ - " Ignore the error in restricted mode catch /command error .*/ let s:localfile_arg = v:false endtry @@ -436,8 +434,11 @@ function! man#goto_tag(pattern, flags, info) abort let l:structured = [] for l:path in l:paths - let l:n = s:extract_sect_and_name_path(l:path)[1] - let l:structured += [{ 'name': l:n, 'path': l:path }] + let [l:sect, l:name] = s:extract_sect_and_name_path(l:path) + let l:structured += [{ + \ 'name': l:name, + \ 'title': l:name . '(' . l:sect . ')' + \ }] endfor if &cscopetag @@ -448,7 +449,7 @@ function! man#goto_tag(pattern, flags, info) abort return map(l:structured, { \ _, entry -> { \ 'name': entry.name, - \ 'filename': 'man://' . entry.path, + \ 'filename': 'man://' . entry.title, \ 'cmd': '1' \ } \ }) diff --git a/runtime/autoload/xmlformat.vim b/runtime/autoload/xmlformat.vim index ea89401977..712e6f7351 100644 --- a/runtime/autoload/xmlformat.vim +++ b/runtime/autoload/xmlformat.vim @@ -1,6 +1,6 @@ " Vim plugin for formatting XML -" Last Change: Thu, 07 Dec 2018 -" Version: 0.1 +" Last Change: 2020 Jan 06 +" Version: 0.3 " Author: Christian Brabandt <cb@256bit.org> " Repository: https://github.com/chrisbra/vim-xml-ftplugin " License: VIM License @@ -15,51 +15,92 @@ let s:keepcpo = &cpo set cpo&vim " Main function: Format the input {{{1 -func! xmlformat#Format() +func! xmlformat#Format() abort " only allow reformatting through the gq command " (e.g. Vim is in normal mode) if mode() != 'n' " do not fall back to internal formatting return 0 endif + let count_orig = v:count let sw = shiftwidth() let prev = prevnonblank(v:lnum-1) let s:indent = indent(prev)/sw let result = [] let lastitem = prev ? getline(prev) : '' let is_xml_decl = 0 - " split on `<`, but don't split on very first opening < - for item in split(join(getline(v:lnum, (v:lnum + v:count - 1))), '.\@<=[>]\zs') - if s:EndTag(item) - let s:indent = s:DecreaseIndent() - call add(result, s:Indent(item)) - elseif s:EmptyTag(lastitem) - call add(result, s:Indent(item)) - elseif s:StartTag(lastitem) && s:IsTag(item) - let s:indent += 1 - call add(result, s:Indent(item)) - else - if !s:IsTag(item) - " Simply split on '<' - let t=split(item, '.<\@=\zs') - let s:indent+=1 - call add(result, s:Indent(t[0])) - let s:indent = s:DecreaseIndent() - call add(result, s:Indent(t[1])) - else + " go through every line, but don't join all content together and join it + " back. We might lose empty lines + let list = getline(v:lnum, (v:lnum + count_orig - 1)) + let current = 0 + for line in list + " Keep empty input lines? + if empty(line) + call add(result, '') + continue + elseif line !~# '<[/]\?[^>]*>' + let nextmatch = match(list, '<[/]\?[^>]*>', current) + if nextmatch > -1 + let line .= ' '. join(list[(current + 1):(nextmatch-1)], " ") + call remove(list, current+1, nextmatch-1) + endif + endif + " split on `>`, but don't split on very first opening < + " this means, items can be like ['<tag>', 'tag content</tag>'] + for item in split(line, '.\@<=[>]\zs') + if s:EndTag(item) + call s:DecreaseIndent() + call add(result, s:Indent(item)) + elseif s:EmptyTag(lastitem) call add(result, s:Indent(item)) + elseif s:StartTag(lastitem) && s:IsTag(item) + let s:indent += 1 + call add(result, s:Indent(item)) + else + if !s:IsTag(item) + " Simply split on '<', if there is one, + " but reformat according to &textwidth + let t=split(item, '.<\@=\zs') + + " if the content fits well within a single line, add it there + " so that the output looks like this: + " + " <foobar>1</foobar> + if s:TagContent(lastitem) is# s:TagContent(t[1]) && strlen(result[-1]) + strlen(item) <= s:Textwidth() + let result[-1] .= item + let lastitem = t[1] + continue + endif + " t should only contain 2 items, but just be safe here + if s:IsTag(lastitem) + let s:indent+=1 + endif + let result+=s:FormatContent([t[0]]) + if s:EndTag(t[1]) + call s:DecreaseIndent() + endif + "for y in t[1:] + let result+=s:FormatContent(t[1:]) + "endfor + else + call add(result, s:Indent(item)) + endif endif - endif - let lastitem = item - endfor + let lastitem = item + endfor + let current += 1 + endfor - if !empty(result) - exe v:lnum. ",". (v:lnum + v:count - 1). 'd' + if !empty(result) + let lastprevline = getline(v:lnum + count_orig) + let delete_lastline = v:lnum + count_orig - 1 == line('$') + exe v:lnum. ",". (v:lnum + count_orig - 1). 'd' call append(v:lnum - 1, result) " Might need to remove the last line, if it became empty because of the " append() call let last = v:lnum + len(result) - if getline(last) is '' + " do not use empty(), it returns true for `empty(0)` + if getline(last) is '' && lastprevline is '' && delete_lastline exe last. 'd' endif endif @@ -68,15 +109,15 @@ func! xmlformat#Format() return 0 endfunc " Check if given tag is XML Declaration header {{{1 -func! s:IsXMLDecl(tag) +func! s:IsXMLDecl(tag) abort return a:tag =~? '^\s*<?xml\s\?\%(version="[^"]*"\)\?\s\?\%(encoding="[^"]*"\)\? ?>\s*$' endfunc " Return tag indented by current level {{{1 -func! s:Indent(item) +func! s:Indent(item) abort return repeat(' ', shiftwidth()*s:indent). s:Trim(a:item) endfu " Return item trimmed from leading whitespace {{{1 -func! s:Trim(item) +func! s:Trim(item) abort if exists('*trim') return trim(a:item) else @@ -84,30 +125,77 @@ func! s:Trim(item) endif endfunc " Check if tag is a new opening tag <tag> {{{1 -func! s:StartTag(tag) +func! s:StartTag(tag) abort let is_comment = s:IsComment(a:tag) return a:tag =~? '^\s*<[^/?]' && !is_comment endfunc -func! s:IsComment(tag) +" Check if tag is a Comment start {{{1 +func! s:IsComment(tag) abort return a:tag =~? '<!--' endfunc " Remove one level of indentation {{{1 -func! s:DecreaseIndent() - return (s:indent > 0 ? s:indent - 1 : 0) +func! s:DecreaseIndent() abort + let s:indent = (s:indent > 0 ? s:indent - 1 : 0) endfunc " Check if tag is a closing tag </tag> {{{1 -func! s:EndTag(tag) +func! s:EndTag(tag) abort return a:tag =~? '^\s*</' endfunc " Check that the tag is actually a tag and not {{{1 " something like "foobar</foobar>" -func! s:IsTag(tag) +func! s:IsTag(tag) abort return s:Trim(a:tag)[0] == '<' endfunc " Check if tag is empty <tag/> {{{1 -func! s:EmptyTag(tag) +func! s:EmptyTag(tag) abort return a:tag =~ '/>\s*$' endfunc +func! s:TagContent(tag) abort "{{{1 + " Return content of a tag + return substitute(a:tag, '^\s*<[/]\?\([^>]*\)>\s*$', '\1', '') +endfunc +func! s:Textwidth() abort "{{{1 + " return textwidth (or 80 if not set) + return &textwidth == 0 ? 80 : &textwidth +endfunc +" Format input line according to textwidth {{{1 +func! s:FormatContent(list) abort + let result=[] + let limit = s:Textwidth() + let column=0 + let idx = -1 + let add_indent = 0 + let cnt = 0 + for item in a:list + for word in split(item, '\s\+\S\+\zs') + if match(word, '^\s\+$') > -1 + " skip empty words + continue + endif + let column += strdisplaywidth(word, column) + if match(word, "^\\s*\n\\+\\s*$") > -1 + call add(result, '') + let idx += 1 + let column = 0 + let add_indent = 1 + elseif column > limit || cnt == 0 + let add = s:Indent(s:Trim(word)) + call add(result, add) + let column = strdisplaywidth(add) + let idx += 1 + else + if add_indent + let result[idx] = s:Indent(s:Trim(word)) + else + let result[idx] .= ' '. s:Trim(word) + endif + let add_indent = 0 + endif + let cnt += 1 + endfor + endfor + return result +endfunc " Restoration And Modelines: {{{1 let &cpo= s:keepcpo unlet s:keepcpo diff --git a/runtime/compiler/xmllint.vim b/runtime/compiler/xmllint.vim index 96cfa55383..79d38b4d14 100644 --- a/runtime/compiler/xmllint.vim +++ b/runtime/compiler/xmllint.vim @@ -1,7 +1,7 @@ " Vim compiler file -" Compiler: xmllint +" Compiler: Libxml2 Command-Line Tool " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2013 Jul 8 +" Last Change: 2020 Jul 30 if exists("current_compiler") finish @@ -13,14 +13,16 @@ if exists(":CompilerSet") != 2 " older Vim always used :setlocal endif let s:cpo_save = &cpo -set cpo-=C +set cpo&vim CompilerSet makeprg=xmllint\ --valid\ --noout - -CompilerSet errorformat=%+E%f:%l:\ %.%#\ error\ :\ %m, - \%+W%f:%l:\ %.%#\ warning\ :\ %m, - \%-Z%p^, - \%-G%.%# +CompilerSet errorformat=%E%f:%l:\ %.%#\ error\ :\ %m, + \%W%f:%l:\ %.%#\ warning\ :\ %m, + \%-Z%p^, + \%C%.%#, + \%terror:\ %m, + \%tarning:\ %m, + \%-G%.%# let &cpo = s:cpo_save unlet s:cpo_save diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index 20eaa47b26..2a972483ff 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -20,8 +20,7 @@ additionally sets up for viewing the differences between the arguments. > nvim -d file1 file2 [file3 [file4]] -In addition to the |-d| argument, |-R| may be used for readonly mode -respectively. +In addition to the |-d| argument, |-R| may be used for readonly mode. The second and following arguments may also be a directory name. Vim will then append the file name of the first argument to the directory name to find diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 7c6013f1b2..343e35bf66 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2487,6 +2487,7 @@ wait({timeout}, {condition}[, {interval}]) wildmenumode() Number whether 'wildmenu' mode is active win_findbuf({bufnr}) List find windows containing {bufnr} win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab} +win_gettype([{nr}]) String type of window {nr} win_gotoid({expr}) Number go to |window-ID| {expr} win_id2tabwin({expr}) List get tab and window nr from |window-ID| win_id2win({expr}) Number get window nr from |window-ID| @@ -9277,6 +9278,24 @@ win_getid([{win} [, {tab}]]) *win_getid()* number {tab}. The first tab has number one. Return zero if the window cannot be found. +win_gettype([{nr}]) *win_gettype()* + Return the type of the window: + "autocmd" autocommand window. Temporary window + used to execute autocommands. + "popup" popup window |popup| + "preview" preview window |preview-window| + "command" command-line window |cmdwin| + (empty) normal window + "unknown" window {nr} not found + + When {nr} is omitted return the type of the current window. + When {nr} is given return the type of this window by number or + |window-ID|. + + Also see the 'buftype' option. When running a terminal in a + popup window then 'buftype' is "terminal" and win_gettype() + returns "popup". + win_gotoid({expr}) *win_gotoid()* Go to window with ID {expr}. This may also change the current tabpage. diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 4cab716df0..5747ba6044 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -996,6 +996,19 @@ type_definition() *vim.lsp.buf.type_definition()* Jumps to the definition of the type of the symbol under the cursor. +add_workspace_folder({path}) *vim.lsp.buf.add_workspace_folder()* + Add the folder at path to the workspace folders. If {path} is + not provided, the user will be prompted for a path using + |input()|. + +remove_workspace_folder({path}) *vim.lsp.buf.remove_workspace_folder()* + Remove the folder at path from the workspace folders. If + {path} is not provided, the user will be prompted for + a path using |input()|. + +list_workspace_folders() *vim.lsp.buf.list_workspace_folders()* + List all folders in the workspace. + workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* Lists all symbols in the current workspace in the quickfix window. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b83d2c4484..6c42dd6739 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5166,6 +5166,7 @@ A jump table for the options with a short description can be found at |Q_op|. It is allowed to give an argument to the command, e.g. "csh -f". See |option-backslash| about including spaces and backslashes. Environment variables are expanded |:set_env|. + If the name of the shell contains a space, you might need to enclose it in quotes. Example: > :set shell=\"c:\program\ files\unix\sh.exe\"\ -f diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 58cd535e98..ae77b0a35a 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -47,7 +47,7 @@ Whenever you need to access the current syntax tree, parse the buffer: > tstree = parser:parse() -<This will return an immutable tree that represents the current state of the +<This will return a table of immutable trees that represent the current state of the buffer. When the plugin wants to access the state after a (possible) edit it should call `parse()` again. If the buffer wasn't edited, the same tree will be returned again without extra work. If the buffer was parsed before, @@ -59,15 +59,16 @@ shouldn't be done directly in the change callback anyway as they will be very frequent. Rather a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent updates. -tsparser:set_included_ranges({ranges}) *tsparser:set_included_ranges()* - Changes the ranges the parser should consider. This is used for - language injection. {ranges} should be of the form (all zero-based): > +tsparser:set_included_regions({region_list}) *tsparser:set_included_regions()* + Changes the regions the parser should consider. This is used for + language injection. {region_list} should be of the form (all zero-based): > { - {start_node, end_node}, + {node1, node2}, ... } < - NOTE: `start_node` and `end_node` are both inclusive. + `node1` and `node2` are both considered part of the same region and + will be parsed together with the parser in the same context. Tree methods *lua-treesitter-tree* @@ -253,7 +254,7 @@ Here is a list of built-in predicates : `lua-match?` *ts-predicate-lua-match?* This will match the same way than |match?| but using lua regexes. - + `contains?` *ts-predicate-contains?* Will check if any of the following arguments appears in the text corresponding to the node : > diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index ac24f22bc6..0f15aefd17 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -416,8 +416,8 @@ Aliases: gvimdiff (GUI) rgview (GUI) rgvim (GUI) - rview (alias for "nvim -RZ") - rvim (alias for "nvim -Z") + rview + rvim view (alias for "nvim -R") vimdiff (alias for "nvim -d" |diff-mode|) @@ -496,6 +496,7 @@ Startup: --literal (file args are always literal; to expand wildcards on Windows, use |:n| e.g. `nvim +"n *"`) Easy mode: eview, evim, nvim -y + Restricted mode: rview, rvim, nvim -Z Vi mode: nvim -v Test functions: diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 4e617052a9..b9d2a43d5d 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -692,12 +692,15 @@ au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc au BufNewFile,BufRead *.haml setf haml " Hamster Classic | Playground files -au BufNewFile,BufRead *.hsc,*.hsm setf hamster +au BufNewFile,BufRead *.hsm setf hamster " Haskell -au BufNewFile,BufRead *.hs,*.hs-boot setf haskell +au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot setf haskell au BufNewFile,BufRead *.lhs setf lhaskell au BufNewFile,BufRead *.chs setf chaskell +au BufNewFile,BufRead cabal.project setf cabalproject +au BufNewFile,BufRead $HOME/.cabal/config setf cabalconfig +au BufNewFile,BufRead cabal.config setf cabalconfig " Haste au BufNewFile,BufRead *.ht setf haste @@ -2042,12 +2045,14 @@ au BufNewFile,BufRead bzr_log.* setf bzr " Bazel build file if !has("fname_case") - au BufNewFile,BufRead *.BUILD,BUILD setf bzl + au BufNewFile,BufRead *.BUILD,BUILD setf bzl endif " BIND zone au BufNewFile,BufRead */named/db.*,*/bind/db.* call s:StarSetf('bindzone') +au BufNewFile,BufRead cabal.project.* call s:StarSetf('cabalproject') + " Calendar au BufNewFile,BufRead */.calendar/*, \*/share/calendar/*/calendar.*,*/share/calendar/calendar.* diff --git a/runtime/indent/xml.vim b/runtime/indent/xml.vim index 883af98563..413a3ddb53 100644 --- a/runtime/indent/xml.vim +++ b/runtime/indent/xml.vim @@ -1,9 +1,10 @@ -" Language: xml -" Repository: https://github.com/chrisbra/vim-xml-ftplugin -" Last Changed: July 27, 2019 -" Maintainer: Christian Brabandt <cb@256bit.org> -" Previous Maintainer: Johannes Zellner <johannes@zellner.org> +" Language: XML +" Maintainer: Christian Brabandt <cb@256bit.org> +" Repository: https://github.com/chrisbra/vim-xml-ftplugin +" Previous Maintainer: Johannes Zellner <johannes@zellner.org> +" Last Changed: 2019 Dec 02 " Last Change: +" 20191202 - Handle docbk filetype " 20190726 - Correctly handle non-tagged data " 20190204 - correctly handle wrap tags " https://github.com/chrisbra/vim-xml-ftplugin/issues/5 @@ -134,7 +135,7 @@ fun! XmlIndentGet(lnum, use_syntax_check) if syn_name_end =~ 'Comment' && syn_name_start =~ 'Comment' return <SID>XmlIndentComment(a:lnum) - elseif empty(syn_name_start) && empty(syn_name_end) + elseif empty(syn_name_start) && empty(syn_name_end) && a:use_syntax_check " non-xml tag content: use indent from 'autoindent' return pind + shiftwidth() endif @@ -148,7 +149,7 @@ endfun func! <SID>IsXMLContinuation(line) " Checks, whether or not the line matches a start-of-tag - return a:line !~ '^\s*<' + return a:line !~ '^\s*<' && &ft is# 'xml' endfunc func! <SID>HasNoTagEnd(line) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index dacdbcfa17..92f56b2ddf 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -597,7 +597,10 @@ function lsp.start_client(config) -- -- workspace folder in the user interface. -- name -- } - workspaceFolders = nil; + workspaceFolders = {{ + uri = vim.uri_from_fname(config.root_dir); + name = string.format("%s", config.root_dir); + }}; } if config.before_init then -- TODO(ashkan) handle errors here. @@ -610,6 +613,7 @@ function lsp.start_client(config) rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary}) client.initialized = true uninitialized_clients[client_id] = nil + client.workspaceFolders = initialize_params.workspaceFolders client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") -- These are the cleaned up capabilities we use for dynamically deciding -- when to send certain events to clients. diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index fa62905c0a..a70581478b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -238,6 +238,61 @@ function M.outgoing_calls() end) end +--- List workspace folders. +function M.list_workspace_folders() + local workspace_folders = {} + for _, client in ipairs(vim.lsp.buf_get_clients()) do + for _, folder in ipairs(client.workspaceFolders) do + table.insert(workspace_folders, folder.name) + end + end + return workspace_folders +end + +--- Add a workspace folder. +function M.add_workspace_folder(workspace_folder) + workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) + vim.api.nvim_command("redraw") + if not (workspace_folder and #workspace_folder > 0) then return end + if vim.fn.isdirectory(workspace_folder) == 0 then + print(workspace_folder, " is not a valid directory") + return + end + local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}}) + for _, client in ipairs(vim.lsp.buf_get_clients()) do + local found = false + for _, folder in ipairs(client.workspaceFolders) do + if folder.name == workspace_folder then + found = true + print(workspace_folder, "is already part of this workspace") + break + end + end + if not found then + vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + table.insert(client.workspaceFolders, params.event.added[1]) + end + end +end + +--- Remove a workspace folder. +function M.remove_workspace_folder(workspace_folder) + workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) + vim.api.nvim_command("redraw") + if not (workspace_folder and #workspace_folder > 0) then return end + local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}) + for _, client in ipairs(vim.lsp.buf_get_clients()) do + for idx, folder in ipairs(client.workspaceFolders) do + if folder.name == workspace_folder then + vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.workspaceFolders[idx] = nil + return + end + end + end + print(workspace_folder, "is not currently part of the workspace") +end + --- Lists all symbols in the current workspace in the quickfix window. --- --- The list is filtered against {query}; if the argument is omitted from the diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 07b4e8b926..218424fa14 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -716,6 +716,7 @@ function protocol.make_client_capabilities() }; hierarchicalWorkspaceSymbolSupport = true; }; + workspaceFolders = true; applyEdit = true; }; callHierarchy = { @@ -974,6 +975,28 @@ function protocol.resolve_capabilities(server_capabilities) error("The server sent invalid implementationProvider") end + local workspace = server_capabilities.workspace + local workspace_properties = {} + if workspace == nil or workspace.workspaceFolders == nil then + -- Defaults if omitted. + workspace_properties = { + workspace_folder_properties = { + supported = false; + changeNotifications=false; + } + } + elseif type(workspace.workspaceFolders) == 'table' then + workspace_properties = { + workspace_folder_properties = { + supported = if_nil(workspace.workspaceFolders.supported, false); + changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false); + + } + } + else + error("The server sent invalid workspace") + end + local signature_help_properties if server_capabilities.signatureHelpProvider == nil then signature_help_properties = { @@ -993,6 +1016,7 @@ function protocol.resolve_capabilities(server_capabilities) return vim.tbl_extend("error" , text_document_sync_properties , signature_help_properties + , workspace_properties , general_properties ) end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3deec6d74e..f78a36fda2 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1314,6 +1314,9 @@ function M.make_text_document_params() return { uri = vim.uri_from_bufnr(0) } end +function M.make_workspace_params(added, removed) + return { event = { added = added; removed = removed; } } +end --- Returns visual width of tabstop. --- --@see |softtabstop| diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 19ef148afc..6886f0c178 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -1,98 +1,13 @@ local a = vim.api local query = require'vim.treesitter.query' local language = require'vim.treesitter.language' +local LanguageTree = require'vim.treesitter.languagetree' -- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. -- Consider use weak references to release parser if all plugins are done with -- it. local parsers = {} -local Parser = {} -Parser.__index = Parser - ---- Parses the buffer if needed and returns a tree. --- --- Calling this will call the on_changedtree callbacks if the tree has changed. --- --- @returns An up to date tree --- @returns If the tree changed with this call, the changed ranges -function Parser:parse() - if self.valid then - return self._tree_immutable - end - local changes - - self._tree, changes = self._parser:parse(self._tree, self:input_source()) - - self._tree_immutable = self._tree:copy() - - self.valid = true - - if not vim.tbl_isempty(changes) then - for _, cb in ipairs(self.changedtree_cbs) do - cb(changes) - end - end - - return self._tree_immutable, changes -end - -function Parser:input_source() - return self.bufnr or self.str -end - -function Parser:_on_bytes(bufnr, changed_tick, - start_row, start_col, start_byte, - old_row, old_col, old_byte, - new_row, new_col, new_byte) - local old_end_col = old_col + ((old_row == 0) and start_col or 0) - local new_end_col = new_col + ((new_row == 0) and start_col or 0) - self._tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte, - start_row, start_col, - start_row+old_row, old_end_col, - start_row+new_row, new_end_col) - self.valid = false - - for _, cb in ipairs(self.bytes_cbs) do - cb(bufnr, changed_tick, - start_row, start_col, start_byte, - old_row, old_col, old_byte, - new_row, new_col, new_byte) - end -end - ---- Registers callbacks for the parser --- @param cbs An `nvim_buf_attach`-like table argument with the following keys : --- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback. --- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes. --- it will only be passed one argument, that is a table of the ranges (as node ranges) that --- changed. -function Parser:register_cbs(cbs) - if not cbs then return end - - if cbs.on_changedtree then - table.insert(self.changedtree_cbs, cbs.on_changedtree) - end - - if cbs.on_bytes then - table.insert(self.bytes_cbs, cbs.on_bytes) - end -end - ---- Sets the included ranges for the current parser --- --- @param ranges A table of nodes that will be used as the ranges the parser should include. -function Parser:set_included_ranges(ranges) - self._parser:set_included_ranges(ranges) - -- The buffer will need to be parsed again later - self.valid = false -end - ---- Gets the included ranges for the parsers -function Parser:included_ranges() - return self._parser:included_ranges() -end - local M = vim.tbl_extend("error", query, language) setmetatable(M, { @@ -113,9 +28,9 @@ setmetatable(M, { -- It is not recommended to use this, use vim.treesitter.get_parser() instead. -- -- @param bufnr The buffer the parser will be tied to --- @param lang The language of the parser. --- @param id The id the parser will have -function M._create_parser(bufnr, lang, id) +-- @param lang The language of the parser +-- @param opts Options to pass to the language tree +function M._create_parser(bufnr, lang, opts) language.require_language(lang) if bufnr == 0 then bufnr = a.nvim_get_current_buf() @@ -123,25 +38,22 @@ function M._create_parser(bufnr, lang, id) vim.fn.bufload(bufnr) - local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser) - self._parser = vim._create_ts_parser(lang) - self.changedtree_cbs = {} - self.bytes_cbs = {} - self:parse() - -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is - -- using it. + local self = LanguageTree.new(bufnr, lang, opts) + local function bytes_cb(_, ...) - return self:_on_bytes(...) + self:_on_bytes(...) end - local detach_cb = nil - if id ~= nil then - detach_cb = function() - if parsers[id] == self then - parsers[id] = nil - end + + local function detach_cb() + if parsers[bufnr] == self then + parsers[bufnr] = nil end end + a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb}) + + self:parse() + return self end @@ -152,39 +64,36 @@ end -- -- @param bufnr The buffer the parser should be tied to -- @param ft The filetype of this parser --- @param buf_attach_cbs See Parser:register_cbs +-- @param opts Options object to pass to the parser -- -- @returns The parser -function M.get_parser(bufnr, lang, buf_attach_cbs) +function M.get_parser(bufnr, lang, opts) + opts = opts or {} + if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end if lang == nil then lang = a.nvim_buf_get_option(bufnr, "filetype") end - local id = tostring(bufnr)..'_'..lang - if parsers[id] == nil then - parsers[id] = M._create_parser(bufnr, lang, id) + if parsers[bufnr] == nil then + parsers[bufnr] = M._create_parser(bufnr, lang, opts) end - parsers[id]:register_cbs(buf_attach_cbs) + parsers[bufnr]:register_cbs(opts.buf_attach_cbs) - return parsers[id] + return parsers[bufnr] end -function M.get_string_parser(str, lang) +function M.get_string_parser(str, lang, opts) vim.validate { str = { str, 'string' }, lang = { lang, 'string' } } language.require_language(lang) - local self = setmetatable({str=str, lang=lang, valid=false}, Parser) - self._parser = vim._create_ts_parser(lang) - self:parse() - - return self + return LanguageTree.new(str, lang, opts) end return M diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 6714bb6354..275e960e28 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -1,4 +1,5 @@ local a = vim.api +local query = require"vim.treesitter.query" -- support reload for quick experimentation local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} @@ -6,6 +7,9 @@ TSHighlighter.__index = TSHighlighter TSHighlighter.active = TSHighlighter.active or {} +local TSHighlighterQuery = {} +TSHighlighterQuery.__index = TSHighlighterQuery + local ns = a.nvim_create_namespace("treesitter/highlighter") -- These are conventions defined by nvim-treesitter, though it @@ -56,27 +60,83 @@ TSHighlighter.hl_map = { ["include"] = "Include", } -function TSHighlighter.new(parser, query) +local function is_highlight_name(capture_name) + local firstc = string.sub(capture_name, 1, 1) + return firstc ~= string.lower(firstc) +end + +function TSHighlighterQuery.new(lang, query_string) + local self = setmetatable({}, { __index = TSHighlighterQuery }) + + self.hl_cache = setmetatable({}, { + __index = function(table, capture) + local hl = self:get_hl_from_capture(capture) + rawset(table, capture, hl) + + return hl + end + }) + + if query_string then + self._query = query.parse_query(lang, query_string) + else + self._query = query.get_query(lang, "highlights") + end + + return self +end + +function TSHighlighterQuery:query() + return self._query +end + +function TSHighlighterQuery:get_hl_from_capture(capture) + local name = self._query.captures[capture] + + if is_highlight_name(name) then + -- From "Normal.left" only keep "Normal" + return vim.split(name, '.', true)[1] + else + -- Default to false to avoid recomputing + local hl = TSHighlighter.hl_map[name] + return hl and a.nvim_get_hl_id_by_name(hl) or 0 + end +end + +function TSHighlighter.new(tree, opts) local self = setmetatable({}, TSHighlighter) - self.parser = parser - parser:register_cbs { - on_changedtree = function(...) self:on_changedtree(...) end + if type(tree:source()) ~= "number" then + error("TSHighlighter can not be used with a string parser source.") + end + + opts = opts or {} + self.tree = tree + tree:register_cbs { + on_changedtree = function(...) self:on_changedtree(...) end, + on_bytes = function(...) self:on_bytes(...) end } - self:set_query(query) + self.bufnr = tree:source() self.edit_count = 0 self.redraw_count = 0 self.line_count = {} - self.root = self.parser:parse():root() - a.nvim_buf_set_option(self.buf, "syntax", "") - - -- TODO(bfredl): can has multiple highlighters per buffer???? - if not TSHighlighter.active[parser.bufnr] then - TSHighlighter.active[parser.bufnr] = {} + -- A map of highlight states. + -- This state is kept during rendering across each line update. + self._highlight_states = {} + self._queries = {} + + -- Queries for a specific language can be overridden by a custom + -- string query... if one is not provided it will be looked up by file. + if opts.queries then + for lang, query_string in pairs(opts.queries) do + self._queries[lang] = TSHighlighterQuery.new(lang, query_string) + end end - TSHighlighter.active[parser.bufnr][parser.lang] = self + a.nvim_buf_set_option(self.bufnr, "syntax", "") + + TSHighlighter.active[self.bufnr] = self -- Tricky: if syntax hasn't been enabled, we need to reload color scheme -- but use synload.vim rather than syntax.vim to not enable @@ -85,119 +145,113 @@ function TSHighlighter.new(parser, query) if vim.g.syntax_on ~= 1 then vim.api.nvim_command("runtime! syntax/synload.vim") end + + self.tree:parse() + return self end -local function is_highlight_name(capture_name) - local firstc = string.sub(capture_name, 1, 1) - return firstc ~= string.lower(firstc) +function TSHighlighter:destroy() + if TSHighlighter.active[self.bufnr] then + TSHighlighter.active[self.bufnr] = nil + end end -function TSHighlighter:get_hl_from_capture(capture) +function TSHighlighter:get_highlight_state(tstree) + if not self._highlight_states[tstree] then + self._highlight_states[tstree] = { + next_row = 0, + iter = nil + } + end - local name = self.query.captures[capture] + return self._highlight_states[tstree] +end - if is_highlight_name(name) then - -- From "Normal.left" only keep "Normal" - return vim.split(name, '.', true)[1] - else - -- Default to false to avoid recomputing - local hl = TSHighlighter.hl_map[name] - return hl and a.nvim_get_hl_id_by_name(hl) or 0 - end +function TSHighlighter:reset_highlight_state() + self._highlight_states = {} +end + +function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) + a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) end function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes or {}) do - a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1) + a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1) end end -function TSHighlighter:set_query(query) - if type(query) == "string" then - query = vim.treesitter.parse_query(self.parser.lang, query) +function TSHighlighter:get_query(lang) + if not self._queries[lang] then + self._queries[lang] = TSHighlighterQuery.new(lang) end - self.query = query - - self.hl_cache = setmetatable({}, { - __index = function(table, capture) - local hl = self:get_hl_from_capture(capture) - rawset(table, capture, hl) + return self._queries[lang] +end - return hl - end - }) +local function on_line_impl(self, buf, line) + self.tree:for_each_tree(function(tstree, tree) + if not tstree then return end - a.nvim__buf_redraw_range(self.parser.bufnr, 0, a.nvim_buf_line_count(self.parser.bufnr)) -end + local root_node = tstree:root() + local root_start_row, _, root_end_row, _ = root_node:range() -local function iter_active_tshl(buf, fn) - for _, hl in pairs(TSHighlighter.active[buf] or {}) do - fn(hl) - end -end + -- Only worry about trees within the line range + if root_start_row > line or root_end_row < line then return end -local function on_line_impl(self, buf, line) - if self.root == nil then - return -- parser bought the farm already - end + local state = self:get_highlight_state(tstree) + local highlighter_query = self:get_query(tree:lang()) - if self.iter == nil then - self.iter = self.query:iter_captures(self.root,buf,line,self.botline) - end - while line >= self.nextrow do - local capture, node = self.iter() - if capture == nil then - break + if state.iter == nil then + state.iter = highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) end - local start_row, start_col, end_row, end_col = node:range() - local hl = self.hl_cache[capture] - if hl and end_row >= line then - a.nvim_buf_set_extmark(buf, ns, start_row, start_col, - { end_line = end_row, end_col = end_col, - hl_group = hl, - ephemeral = true, - }) - end - if start_row > line then - self.nextrow = start_row + + while line >= state.next_row do + local capture, node = state.iter() + + if capture == nil then break end + + local start_row, start_col, end_row, end_col = node:range() + local hl = highlighter_query.hl_cache[capture] + + if hl and end_row >= line then + a.nvim_buf_set_extmark(buf, ns, start_row, start_col, + { end_line = end_row, end_col = end_col, + hl_group = hl, + ephemeral = true, + priority = 100 -- Low but leaves room below + }) + end + if start_row > line then + state.next_row = start_row + end end - end + end, true) end -function TSHighlighter._on_line(_, _win, buf, line, highlighter) - -- on_line is only called when this is non-nil - if highlighter then - on_line_impl(highlighter, buf, line) - else - iter_active_tshl(buf, function(self) - on_line_impl(self, buf, line) - end) - end +function TSHighlighter._on_line(_, _win, buf, line, _) + local self = TSHighlighter.active[buf] + if not self then return end + + on_line_impl(self, buf, line) end function TSHighlighter._on_buf(_, buf) - iter_active_tshl(buf, function(self) - if self then - local tree = self.parser:parse() - self.root = (tree and tree:root()) or nil - end - end) + local self = TSHighlighter.active[buf] + if self then + self.tree:parse() + end end -function TSHighlighter._on_win(_, _win, buf, _topline, botline) - iter_active_tshl(buf, function(self) - if not self then - return false - end +function TSHighlighter._on_win(_, _win, buf, _topline) + local self = TSHighlighter.active[buf] + if not self then + return false + end - self.iter = nil - self.nextrow = 0 - self.botline = botline - self.redraw_count = self.redraw_count + 1 - return true - end) + self:reset_highlight_state() + self.redraw_count = self.redraw_count + 1 return true end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua new file mode 100644 index 0000000000..a8b62e21b9 --- /dev/null +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -0,0 +1,455 @@ +local a = vim.api +local query = require'vim.treesitter.query' +local language = require'vim.treesitter.language' + +local LanguageTree = {} +LanguageTree.__index = LanguageTree + +-- Represents a single treesitter parser for a language. +-- The language can contain child languages with in it's range, +-- hence the tree. +-- +-- @param source Can be a bufnr or a string of text to parse +-- @param lang The language this tree represents +-- @param opts Options table +-- @param opts.queries A table of language to injection query strings +-- This is useful for overridding the built in runtime file +-- searching for the injection language query per language. +function LanguageTree.new(source, lang, opts) + language.require_language(lang) + opts = opts or {} + + local custom_queries = opts.queries or {} + local self = setmetatable({ + _source=source, + _lang=lang, + _children = {}, + _regions = {}, + _trees = {}, + _opts = opts, + _injection_query = custom_queries[lang] + and query.parse_query(lang, custom_queries[lang]) + or query.get_query(lang, "injections"), + _valid = false, + _parser = vim._create_ts_parser(lang), + _callbacks = { + changedtree = {}, + bytes = {}, + child_added = {}, + child_removed = {} + }, + }, LanguageTree) + + + return self +end + +-- Invalidates this parser and all it's children +function LanguageTree:invalidate() + self._valid = false + + for _, child in ipairs(self._children) do + child:invalidate() + end +end + +-- Returns all trees this language tree contains. +-- Does not include child languages. +function LanguageTree:trees() + return self._trees +end + +-- Gets the language of this tree layer. +function LanguageTree:lang() + return self._lang +end + +-- Determines whether this tree is valid. +-- If the tree is invalid, `parse()` must be called +-- to get the an updated tree. +function LanguageTree:is_valid() + return self._valid +end + +-- Returns a map of language to child tree. +function LanguageTree:children() + return self._children +end + +-- Returns the source content of the language tree (bufnr or string). +function LanguageTree:source() + return self._source +end + +-- Parses all defined regions using a treesitter parser +-- for the language this tree represents. +-- This will run the injection query for this language to +-- determine if any child languages should be created. +function LanguageTree:parse() + if self._valid then + return self._trees + end + + local parser = self._parser + local changes = {} + + local old_trees = self._trees + self._trees = {} + + -- If there are no ranges, set to an empty list + -- so the included ranges in the parser ar cleared. + if self._regions and #self._regions > 0 then + for i, ranges in ipairs(self._regions) do + local old_tree = old_trees[i] + parser:set_included_ranges(ranges) + + local tree, tree_changes = parser:parse(old_tree, self._source) + self:_do_callback('changedtree', tree_changes, tree) + + table.insert(self._trees, tree) + vim.list_extend(changes, tree_changes) + end + else + local tree, tree_changes = parser:parse(old_trees[1], self._source) + self:_do_callback('changedtree', tree_changes, tree) + + table.insert(self._trees, tree) + vim.list_extend(changes, tree_changes) + end + + local injections_by_lang = self:_get_injections() + local seen_langs = {} + + for lang, injection_ranges in pairs(injections_by_lang) do + local child = self._children[lang] + + if not child then + child = self:add_child(lang) + end + + child:set_included_regions(injection_ranges) + + local _, child_changes = child:parse() + + -- Propagate any child changes so they are included in the + -- the change list for the callback. + if child_changes then + vim.list_extend(changes, child_changes) + end + + seen_langs[lang] = true + end + + for lang, _ in pairs(self._children) do + if not seen_langs[lang] then + self:remove_child(lang) + end + end + + self._valid = true + + return self._trees, changes +end + +-- Invokes the callback for each LanguageTree and it's children recursively +-- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string) +-- @param include_self Whether to include the invoking tree in the results. +function LanguageTree:for_each_child(fn, include_self) + if include_self then + fn(self, self._lang) + end + + for _, child in pairs(self._children) do + child:for_each_child(fn, true) + end +end + +-- Invokes the callback for each treesitter trees recursively. +-- Note, this includes the invoking language tree's trees as well. +-- @param fn The callback to invoke. The callback is invoked with arguments +-- (tree: TSTree, languageTree: LanguageTree) +function LanguageTree:for_each_tree(fn) + for _, tree in ipairs(self._trees) do + fn(tree, self) + end + + for _, child in pairs(self._children) do + child:for_each_tree(fn) + end +end + +-- Adds a child language to this tree. +-- If the language already exists as a child, it will first be removed. +-- @param lang The language to add. +function LanguageTree:add_child(lang) + if self._children[lang] then + self:remove_child(lang) + end + + self._children[lang] = LanguageTree.new(self._source, lang, self._opts) + + self:invalidate() + self:_do_callback('child_added', self._children[lang]) + + return self._children[lang] +end + +-- Removes a child language from this tree. +-- @param lang The language to remove. +function LanguageTree:remove_child(lang) + local child = self._children[lang] + + if child then + self._children[lang] = nil + child:destroy() + self:invalidate() + self:_do_callback('child_removed', child) + end +end + +-- Destroys this language tree and all it's children. +-- Any cleanup logic should be performed here. +-- Note, this DOES NOT remove this tree from a parent. +-- `remove_child` must be called on the parent to remove it. +function LanguageTree:destroy() + -- Cleanup here + for _, child in ipairs(self._children) do + child:destroy() + end +end + +-- Sets the included regions that should be parsed by this parser. +-- A region is a set of nodes and/or ranges that will be parsed in the same context. +-- +-- For example, `{ { node1 }, { node2} }` is two separate regions. +-- This will be parsed by the parser in two different contexts... thus resulting +-- in two separate trees. +-- +-- `{ { node1, node2 } }` is a single region consisting of two nodes. +-- This will be parsed by the parser in a single context... thus resulting +-- in a single tree. +-- +-- This allows for embedded languages to be parsed together across different +-- nodes, which is useful for templating languages like ERB and EJS. +-- +-- Note, this call invalidates the tree and requires it to be parsed again. +-- +-- @param regions A list of regions this tree should manange and parse. +function LanguageTree:set_included_regions(regions) + -- Transform the tables from 4 element long to 6 element long (with byte offset) + for _, region in ipairs(regions) do + for i, range in ipairs(region) do + if type(range) == "table" and #range == 4 then + -- TODO(vigoux): I don't think string parsers are useful for now + if type(self._source) == "number" then + local start_row, start_col, end_row, end_col = unpack(range) + -- Easy case, this is a buffer parser + -- TODO(vigoux): proper byte computation here, and account for EOL ? + local start_byte = a.nvim_buf_get_offset(self.bufnr, start_row) + start_col + local end_byte = a.nvim_buf_get_offset(self.bufnr, end_row) + end_col + + region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte } + end + end + end + end + + self._regions = regions + -- Trees are no longer valid now that we have changed regions. + -- TODO(vigoux,steelsojka): Look into doing this smarter so we can use some of the + -- old trees for incremental parsing. Currently, this only + -- effects injected languages. + self._trees = {} + self:invalidate() +end + +-- Gets the set of included regions +function LanguageTree:included_regions() + return self._regions +end + +-- Gets language injection points by language. +-- This is where most of the injection processing occurs. +-- TODO: Allow for an offset predicate to tailor the injection range +-- instead of using the entire nodes range. +-- @private +function LanguageTree:_get_injections() + if not self._injection_query then return {} end + + local injections = {} + + for tree_index, tree in ipairs(self._trees) do + local root_node = tree:root() + local start_line, _, end_line, _ = root_node:range() + + for pattern, match in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do + local lang = nil + local injection_node = nil + local combined = false + + -- You can specify the content and language together + -- using a tag with the language, for example + -- @javascript + for id, node in pairs(match) do + local name = self._injection_query.captures[id] + -- TODO add a way to offset the content passed to the parser. + -- Needed to shave off leading quotes and things of that nature. + + -- Lang should override any other language tag + if name == "language" then + lang = query.get_node_text(node, self._source) + elseif name == "combined" then + combined = true + elseif name == "content" then + injection_node = node + -- Ignore any tags that start with "_" + -- Allows for other tags to be used in matches + elseif string.sub(name, 1, 1) ~= "_" then + if lang == nil then + lang = name + end + + if not injection_node then + injection_node = node + end + end + end + + -- Each tree index should be isolated from the other nodes. + if not injections[tree_index] then + injections[tree_index] = {} + end + + if not injections[tree_index][lang] then + injections[tree_index][lang] = {} + end + + -- Key by pattern so we can either combine each node to parse in the same + -- context or treat each node independently. + if not injections[tree_index][lang][pattern] then + injections[tree_index][lang][pattern] = { combined = combined, nodes = {} } + end + + table.insert(injections[tree_index][lang][pattern].nodes, injection_node) + end + end + + local result = {} + + -- Generate a map by lang of node lists. + -- Each list is a set of ranges that should be parsed + -- together. + for _, lang_map in ipairs(injections) do + for lang, patterns in pairs(lang_map) do + if not result[lang] then + result[lang] = {} + end + + for _, entry in pairs(patterns) do + if entry.combined then + table.insert(result[lang], entry.nodes) + else + for _, node in ipairs(entry.nodes) do + table.insert(result[lang], {node}) + end + end + end + end + end + + return result +end + +function LanguageTree:_do_callback(cb_name, ...) + for _, cb in ipairs(self._callbacks[cb_name]) do + cb(...) + end +end + +function LanguageTree:_on_bytes(bufnr, changed_tick, + start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte) + self:invalidate() + + local old_end_col = old_col + ((old_row == 0) and start_col or 0) + local new_end_col = new_col + ((new_row == 0) and start_col or 0) + + -- Edit all trees recursively, together BEFORE emitting a bytes callback. + -- In most cases this callback should only be called from the root tree. + self:for_each_tree(function(tree) + tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte, + start_row, start_col, + start_row+old_row, old_end_col, + start_row+new_row, new_end_col) + end) + + self:_do_callback('bytes', bufnr, changed_tick, + start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte) +end + +--- Registers callbacks for the parser +-- @param cbs An `nvim_buf_attach`-like table argument with the following keys : +-- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback. +-- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes. +-- it will only be passed one argument, that is a table of the ranges (as node ranges) that +-- changed. +-- `on_child_added` : emitted when a child is added to the tree. +-- `on_child_removed` : emitted when a child is remvoed from the tree. +function LanguageTree:register_cbs(cbs) + if not cbs then return end + + if cbs.on_changedtree then + table.insert(self._callbacks.changedtree, cbs.on_changedtree) + end + + if cbs.on_bytes then + table.insert(self._callbacks.bytes, cbs.on_bytes) + end + + if cbs.on_child_added then + table.insert(self._callbacks.child_added, cbs.on_child_added) + end + + if cbs.on_child_removed then + table.insert(self._callbacks.child_removed, cbs.on_child_removed) + end +end + +local function region_contains(region, range) + for _, node in ipairs(region) do + local start_row, start_col, end_row, end_col = node:range() + local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) + local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) + + if start_fits and end_fits then + return true + end + end + + return false +end + +function LanguageTree:contains(range) + for _, region in pairs(self._regions) do + if region_contains(region, range) then + return true + end + end + + return false +end + +function LanguageTree:language_for_range(range) + for _, child in pairs(self._children) do + if child:contains(range) then + return child:node_for_range(range) + end + end + + return self +end + +return LanguageTree diff --git a/runtime/syntax/gitconfig.vim b/runtime/syntax/gitconfig.vim index c6c56f77b6..e5eaf10f94 100644 --- a/runtime/syntax/gitconfig.vim +++ b/runtime/syntax/gitconfig.vim @@ -2,24 +2,22 @@ " Language: git config file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: gitconfig, .gitconfig, *.git/config -" Last Change: 2010 May 21 +" Last Change: 2019 Dec 05 if exists("b:current_syntax") finish endif -setlocal iskeyword+=- -setlocal iskeyword-=_ syn case ignore syn sync minlines=10 -syn match gitconfigComment "[#;].*" +syn match gitconfigComment "[#;].*" contains=@Spell syn match gitconfigSection "\%(^\s*\)\@<=\[[a-z0-9.-]\+\]" syn match gitconfigSection '\%(^\s*\)\@<=\[[a-z0-9.-]\+ \+\"\%([^\\"]\|\\.\)*"\]' -syn match gitconfigVariable "\%(^\s*\)\@<=\a\k*\%(\s*\%([=#;]\|$\)\)\@=" nextgroup=gitconfigAssignment skipwhite +syn match gitconfigVariable "\%(^\s*\)\@<=\a[a-z0-9-]*\%(\s*\%([=#;]\|$\)\)\@=" nextgroup=gitconfigAssignment skipwhite syn region gitconfigAssignment matchgroup=gitconfigNone start=+=\s*+ skip=+\\+ end=+\s*$+ contained contains=gitconfigBoolean,gitconfigNumber,gitConfigString,gitConfigEscape,gitConfigError,gitconfigComment keepend syn keyword gitconfigBoolean true false yes no contained -syn match gitconfigNumber "\d\+" contained +syn match gitconfigNumber "\<\d\+\>" contained syn region gitconfigString matchgroup=gitconfigDelim start=+"+ skip=+\\+ end=+"+ matchgroup=gitconfigError end=+[^\\"]\%#\@!$+ contained contains=gitconfigEscape,gitconfigEscapeError syn match gitconfigError +\\.+ contained syn match gitconfigEscape +\\[\\"ntb]+ contained @@ -32,7 +30,7 @@ hi def link gitconfigBoolean Boolean hi def link gitconfigNumber Number hi def link gitconfigString String hi def link gitconfigDelim Delimiter -hi def link gitconfigEscape Delimiter +hi def link gitconfigEscape Special hi def link gitconfigError Error let b:current_syntax = "gitconfig" diff --git a/runtime/syntax/xml.vim b/runtime/syntax/xml.vim index 7b503abf49..7c9791a7cc 100644 --- a/runtime/syntax/xml.vim +++ b/runtime/syntax/xml.vim @@ -1,11 +1,15 @@ " Vim syntax file -" Language: XML -" Maintainer: Johannes Zellner <johannes@zellner.org> -" Author and previous maintainer: -" Paul Siegmann <pauls@euronet.nl> -" Last Change: 2013 Jun 07 +" Language: XML +" Maintainer: Christian Brabandt <cb@256bit.org> +" Repository: https://github.com/chrisbra/vim-xml-ftplugin +" Previous Maintainer: Johannes Zellner <johannes@zellner.org> +" Author: Paul Siegmann <pauls@euronet.nl> +" Last Changed: Nov 03, 2019 " Filenames: *.xml -" $Id: xml.vim,v 1.3 2006/04/11 21:32:00 vimboss Exp $ +" Last Change: +" 20190923 - Fix xmlEndTag to match xmlTag (vim/vim#884) +" 20190924 - Fix xmlAttribute property (amadeus/vim-xml@d8ce1c946) +" 20191103 - Enable spell checking globally " CONFIGURATION: " syntax folding can be turned on by @@ -49,6 +53,12 @@ set cpo&vim syn case match +" Allow spell checking in tag values, +" there is no syntax region for that, +" so enable spell checking in top-level elements +" <tag>This text is spell checked</tag> +syn spell toplevel + " mark illegal characters syn match xmlError "[<&]" @@ -81,7 +91,7 @@ syn match xmlEqual +=+ display " ^^^^^^^^^^^^^ " syn match xmlAttrib - \ +[-'"<]\@1<!\<[a-zA-Z:_][-.0-9a-zA-Z:_]*\>\%(['">]\@!\|$\)+ + \ +[-'"<]\@1<!\<[a-zA-Z:_][-.0-9a-zA-Z:_]*\>\%(['"]\@!\|$\)+ \ contained \ contains=xmlAttribPunct,@xmlAttribHook \ display @@ -122,7 +132,7 @@ endif " ^^^ " syn match xmlTagName - \ +<\@1<=[^ /!?<>"']\++ + \ +\%(<\|</\)\@2<=[^ /!?<>"']\++ \ contained \ contains=xmlNamespace,xmlAttribPunct,@xmlTagHook \ display @@ -157,11 +167,11 @@ if exists('g:xml_syntax_folding') " </tag> " ^^^^^^ " - syn match xmlEndTag - \ +</[^ /!?<>"']\+>+ + syn region xmlEndTag + \ matchgroup=xmlTag start=+</[^ /!?<>"']\@=+ + \ matchgroup=xmlTag end=+>+ \ contained - \ contains=xmlNamespace,xmlAttribPunct,@xmlTagHook - + \ contains=xmlTagName,xmlNamespace,xmlAttribPunct,@xmlTagHook " tag elements with syntax-folding. " NOTE: NO HIGHLIGHTING -- highlighting is done by contained elements @@ -181,7 +191,7 @@ if exists('g:xml_syntax_folding') \ start=+<\z([^ /!?<>"']\+\)+ \ skip=+<!--\_.\{-}-->+ \ end=+</\z1\_\s\{-}>+ - \ matchgroup=xmlEndTag end=+/>+ + \ end=+/>+ \ fold \ contains=xmlTag,xmlEndTag,xmlCdata,xmlRegion,xmlComment,xmlEntity,xmlProcessing,@xmlRegionHook,@Spell \ keepend @@ -198,9 +208,10 @@ else \ matchgroup=xmlTag end=+>+ \ contains=xmlError,xmlTagName,xmlAttrib,xmlEqual,xmlString,@xmlStartTagHook - syn match xmlEndTag - \ +</[^ /!?<>"']\+>+ - \ contains=xmlNamespace,xmlAttribPunct,@xmlTagHook + syn region xmlEndTag + \ matchgroup=xmlTag start=+</[^ /!?<>"']\@=+ + \ matchgroup=xmlTag end=+>+ + \ contains=xmlTagName,xmlNamespace,xmlAttribPunct,@xmlTagHook endif diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4fc0ee4fdf..1011f050fd 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1122,6 +1122,8 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) } PUT(dict, "virt_text", ARRAY_OBJ(chunks)); } + + PUT(dict, "priority", INTEGER_OBJ(decor->priority)); } if (dict.size) { @@ -1375,6 +1377,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, uint64_t id = 0; int line2 = -1, hl_id = 0; + DecorPriority priority = DECOR_PRIORITY_BASE; colnr_T col2 = 0; VirtText virt_text = KV_INITIAL_VALUE; for (size_t i = 0; i < opts.size; i++) { @@ -1446,6 +1449,19 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ERROR_SET(err)) { goto error; } + } else if (strequal("priority", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "priority is not a Number of the correct size"); + goto error; + } + + if (v->data.integer < 0 || v->data.integer > UINT16_MAX) { + api_set_error(err, kErrorTypeValidation, + "priority is not a valid value"); + goto error; + } + priority = (DecorPriority)v->data.integer; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; @@ -1479,7 +1495,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, *vt_allocated = virt_text; } decor_add_ephemeral(attr_id, (int)line, (colnr_T)col, - (int)line2, (colnr_T)col2, vt_allocated); + (int)line2, (colnr_T)col2, priority, vt_allocated); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); @@ -1492,6 +1508,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, decor->virt_text = virt_text; } else if (hl_id) { decor = decor_hl(hl_id); + decor->priority = priority; } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index cf822782d8..77002697fe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -212,6 +212,9 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// @param ns_id number of namespace for this highlight /// @param name highlight group name, like ErrorMsg /// @param val highlight definiton map, like |nvim_get_hl_by_name|. +/// in addition the following keys are also recognized: +/// `default`: don't override existing definition, +/// like `hi default` /// @param[out] err Error details, if any /// /// TODO: ns_id = 0, should modify :highlight namespace @@ -249,7 +252,7 @@ void nvim_set_hl_ns(Integer ns_id, Error *err) // event path for redraws caused by "fast" events. This could tie in with // better throttling of async events causing redraws, such as non-batched // nvim_buf_set_extmark calls from async contexts. - if (!updating_screen && !ns_hl_changed) { + if (!provider_active && !ns_hl_changed) { multiqueue_put(main_loop.events, on_redraw_event, 0); } ns_hl_changed = true; @@ -785,10 +788,15 @@ ArrayOf(String) nvim_list_runtime_paths(void) /// /// 'name' can contain wildcards. For example /// nvim_get_runtime_file("colors/*.vim", true) will return all color -/// scheme files. +/// scheme files. Always use forward slashes (/) in the search pattern for +/// subdirectories regardless of platform. /// /// It is not an error to not find any files. An empty array is returned then. /// +/// To find a directory, `name` must end with a forward slash, like +/// "rplugin/python/". Without the slash it would instead look for an ordinary +/// file called "rplugin/python". +/// /// @param name pattern of files to search for /// @param all whether to return all matches or only the first /// @return list of absolute paths to the found files @@ -798,14 +806,13 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err) { Array rv = ARRAY_DICT_INIT; - // TODO(bfredl): - if (name.size == 0) { - api_set_error(err, kErrorTypeValidation, "not yet implemented"); - return rv; + int flags = DIP_START | (all ? DIP_ALL : 0); + + if (name.size == 0 || name.data[name.size-1] == '/') { + flags |= DIP_DIR; } - int flags = DIP_START | (all ? DIP_ALL : 0); - do_in_runtimepath((char_u *)name.data, + do_in_runtimepath((char_u *)(name.size ? name.data : ""), flags, find_runtime_cb, &rv); return rv; } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c new file mode 100644 index 0000000000..53b11c250e --- /dev/null +++ b/src/nvim/autocmd.c @@ -0,0 +1,2092 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// autocmd.c: Autocommand related functions + +#include "nvim/autocmd.h" + +#include "nvim/api/private/handle.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_docmd.h" +#include "nvim/fileio.h" +#include "nvim/getchar.h" +#include "nvim/misc1.h" +#include "nvim/option.h" +#include "nvim/regexp.h" +#include "nvim/search.h" +#include "nvim/state.h" +#include "nvim/ui_compositor.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "auevents_name_map.generated.h" +#include "autocmd.c.generated.h" +#endif + +// +// The autocommands are stored in a list for each event. +// Autocommands for the same pattern, that are consecutive, are joined +// together, to avoid having to match the pattern too often. +// The result is an array of Autopat lists, which point to AutoCmd lists: +// +// last_autopat[0] -----------------------------+ +// V +// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL +// Autopat.cmds Autopat.cmds +// | | +// V V +// AutoCmd.next AutoCmd.next +// | | +// V V +// AutoCmd.next NULL +// | +// V +// NULL +// +// last_autopat[1] --------+ +// V +// first_autopat[1] --> Autopat.next --> NULL +// Autopat.cmds +// | +// V +// AutoCmd.next +// | +// V +// NULL +// etc. +// +// The order of AutoCmds is important, this is the order in which they were +// defined and will have to be executed. +// + +// Code for automatic commands. +static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands + +/// List of autocmd group names +static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL }; +#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i]) +#define BUFLOCAL_PAT_LEN 25 + +// use get_deleted_augroup() to get this +static const char *deleted_augroup = NULL; + +// The ID of the current group. Group 0 is the default one. +static int current_augroup = AUGROUP_DEFAULT; + +static int au_need_clean = false; // need to delete marked patterns + +static event_T last_event; +static int last_group; +static int autocmd_blocked = 0; // block all autocmds + +static bool autocmd_nested = false; +static bool autocmd_include_groups = false; + +static char_u *old_termresponse = NULL; + +static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE +{ + if (deleted_augroup == NULL) { + deleted_augroup = _("--Deleted--"); + } + return deleted_augroup; +} + +// Show the autocommands for one AutoPat. +static void show_autocmd(AutoPat *ap, event_T event) +{ + AutoCmd *ac; + + // Check for "got_int" (here and at various places below), which is set + // when "q" has been hit for the "--more--" prompt + if (got_int) { + return; + } + // pattern has been removed + if (ap->pat == NULL) { + return; + } + + msg_putchar('\n'); + if (got_int) { + return; + } + if (event != last_event || ap->group != last_group) { + if (ap->group != AUGROUP_DEFAULT) { + if (AUGROUP_NAME(ap->group) == NULL) { + msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); + } else { + msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); + } + msg_puts(" "); + } + msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); + last_event = event; + last_group = ap->group; + msg_putchar('\n'); + if (got_int) { + return; + } + } + msg_col = 4; + msg_outtrans(ap->pat); + + for (ac = ap->cmds; ac != NULL; ac = ac->next) { + if (ac->cmd == NULL) { // skip removed commands + continue; + } + if (msg_col >= 14) { + msg_putchar('\n'); + } + msg_col = 14; + if (got_int) { + return; + } + msg_outtrans(ac->cmd); + if (p_verbose > 0) { + last_set_msg(ac->script_ctx); + } + if (got_int) { + return; + } + if (ac->next != NULL) { + msg_putchar('\n'); + if (got_int) { + return; + } + } + } +} + +// Mark an autocommand handler for deletion. +static void au_remove_pat(AutoPat *ap) +{ + XFREE_CLEAR(ap->pat); + ap->buflocal_nr = -1; + au_need_clean = true; +} + +// Mark all commands for a pattern for deletion. +static void au_remove_cmds(AutoPat *ap) +{ + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + XFREE_CLEAR(ac->cmd); + } + au_need_clean = true; +} + +// Delete one command from an autocmd pattern. +static void au_del_cmd(AutoCmd *ac) +{ + XFREE_CLEAR(ac->cmd); + au_need_clean = true; +} + +/// Cleanup autocommands and patterns that have been deleted. +/// This is only done when not executing autocommands. +static void au_cleanup(void) +{ + AutoPat *ap, **prev_ap; + event_T event; + + if (autocmd_busy || !au_need_clean) { + return; + } + + // Loop over all events. + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) { + // Loop over all autocommand patterns. + prev_ap = &(first_autopat[(int)event]); + for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { + bool has_cmd = false; + + // Loop over all commands for this pattern. + AutoCmd **prev_ac = &(ap->cmds); + for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) { + // Remove the command if the pattern is to be deleted or when + // the command has been marked for deletion. + if (ap->pat == NULL || ac->cmd == NULL) { + *prev_ac = ac->next; + xfree(ac->cmd); + xfree(ac); + } else { + has_cmd = true; + prev_ac = &(ac->next); + } + } + + if (ap->pat != NULL && !has_cmd) { + // Pattern was not marked for deletion, but all of its commands were. + // So mark the pattern for deletion. + au_remove_pat(ap); + } + + // Remove the pattern if it has been marked for deletion. + if (ap->pat == NULL) { + if (ap->next == NULL) { + if (prev_ap == &(first_autopat[(int)event])) { + last_autopat[(int)event] = NULL; + } else { + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + } + *prev_ap = ap->next; + vim_regfree(ap->reg_prog); + xfree(ap); + } else { + prev_ap = &(ap->next); + } + } + } + + au_need_clean = false; +} + +// Called when buffer is freed, to remove/invalidate related buffer-local +// autocmds. +void aubuflocal_remove(buf_T *buf) +{ + AutoPat *ap; + event_T event; + AutoPatCmd *apc; + + // invalidate currently executing autocommands + for (apc = active_apc_list; apc; apc = apc->next) { + if (buf->b_fnum == apc->arg_bufnr) { + apc->arg_bufnr = 0; + } + } + + // invalidate buflocals looping through events + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) { + // loop over all autocommand patterns + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + if (ap->buflocal_nr == buf->b_fnum) { + au_remove_pat(ap); + if (p_verbose >= 6) { + verbose_enter(); + smsg(_("auto-removing autocommand: %s <buffer=%d>"), + event_nr2name(event), buf->b_fnum); + verbose_leave(); + } + } + } + } + au_cleanup(); +} + +// Add an autocmd group name. +// Return its ID. Returns AUGROUP_ERROR (< 0) for error. +static int au_new_group(char_u *name) +{ + int i = au_find_group(name); + if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it. + // First try using a free entry. + for (i = 0; i < augroups.ga_len; i++) { + if (AUGROUP_NAME(i) == NULL) { + break; + } + } + if (i == augroups.ga_len) { + ga_grow(&augroups, 1); + } + + AUGROUP_NAME(i) = xstrdup((char *)name); + if (i == augroups.ga_len) { + augroups.ga_len++; + } + } + + return i; +} + +static void au_del_group(char_u *name) +{ + int i = au_find_group(name); + if (i == AUGROUP_ERROR) { // the group doesn't exist + EMSG2(_("E367: No such group: \"%s\""), name); + } else if (i == current_augroup) { + EMSG(_("E936: Cannot delete the current group")); + } else { + event_T event; + AutoPat *ap; + int in_use = false; + + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) { + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + if (ap->group == i && ap->pat != NULL) { + give_warning( + (char_u *)_("W19: Deleting augroup that is still in use"), true); + in_use = true; + event = NUM_EVENTS; + break; + } + } + } + xfree(AUGROUP_NAME(i)); + if (in_use) { + AUGROUP_NAME(i) = (char *)get_deleted_augroup(); + } else { + AUGROUP_NAME(i) = NULL; + } + } +} + +/// Find the ID of an autocmd group name. +/// +/// @param name augroup name +/// +/// @return the ID or AUGROUP_ERROR (< 0) for error. +static int au_find_group(const char_u *name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (int i = 0; i < augroups.ga_len; i++) { + if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup() + && STRCMP(AUGROUP_NAME(i), name) == 0) { + return i; + } + } + return AUGROUP_ERROR; +} + +/// Return true if augroup "name" exists. +/// +/// @param name augroup name +bool au_has_group(const char_u *name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return au_find_group(name) != AUGROUP_ERROR; +} + +/// ":augroup {name}". +void do_augroup(char_u *arg, int del_group) +{ + if (del_group) { + if (*arg == NUL) { + EMSG(_(e_argreq)); + } else { + au_del_group(arg); + } + } else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0 + current_augroup = AUGROUP_DEFAULT; + } else if (*arg) { // ":aug xxx": switch to group xxx + int i = au_new_group(arg); + if (i != AUGROUP_ERROR) { + current_augroup = i; + } + } else { // ":aug": list the group names + msg_start(); + for (int i = 0; i < augroups.ga_len; i++) { + if (AUGROUP_NAME(i) != NULL) { + msg_puts(AUGROUP_NAME(i)); + msg_puts(" "); + } + } + msg_clr_eos(); + msg_end(); + } +} + +#if defined(EXITFREE) +void free_all_autocmds(void) +{ + for (current_augroup = -1; current_augroup < augroups.ga_len; + current_augroup++) { + do_autocmd((char_u *)"", true); + } + + for (int i = 0; i < augroups.ga_len; i++) { + char *const s = ((char **)(augroups.ga_data))[i]; + if ((const char *)s != get_deleted_augroup()) { + xfree(s); + } + } + ga_clear(&augroups); +} +#endif + +// Return the event number for event name "start". +// Return NUM_EVENTS if the event name was not found. +// Return a pointer to the next event name in "end". +static event_T event_name2nr(const char_u *start, char_u **end) +{ + const char_u *p; + int i; + int len; + + // the event name ends with end of line, '|', a blank or a comma + for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) { + } + for (i = 0; event_names[i].name != NULL; i++) { + len = (int)event_names[i].len; + if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) { + break; + } + } + if (*p == ',') { + p++; + } + *end = (char_u *)p; + if (event_names[i].name == NULL) { + return NUM_EVENTS; + } + return event_names[i].event; +} + +/// Return the name for event +/// +/// @param[in] event Event to return name for. +/// +/// @return Event name, static string. Returns "Unknown" for unknown events. +static const char *event_nr2name(event_T event) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST +{ + int i; + + for (i = 0; event_names[i].name != NULL; i++) { + if (event_names[i].event == event) { + return event_names[i].name; + } + } + return "Unknown"; +} + +/// Scan over the events. "*" stands for all events. +/// true when group name was found +static char_u *find_end_event(char_u *arg, int have_group) +{ + char_u *pat; + char_u *p; + + if (*arg == '*') { + if (arg[1] && !ascii_iswhite(arg[1])) { + EMSG2(_("E215: Illegal character after *: %s"), arg); + return NULL; + } + pat = arg + 1; + } else { + for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { + if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) { + if (have_group) { + EMSG2(_("E216: No such event: %s"), pat); + } else { + EMSG2(_("E216: No such group or event: %s"), pat); + } + return NULL; + } + } + } + return pat; +} + +/// Return true if "event" is included in 'eventignore'. +/// +/// @param event event to check +static bool event_ignored(event_T event) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + char_u *p = p_ei; + + while (*p != NUL) { + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { + return true; + } + if (event_name2nr(p, &p) == event) { + return true; + } + } + + return false; +} + +// Return OK when the contents of p_ei is valid, FAIL otherwise. +int check_ei(void) +{ + char_u *p = p_ei; + + while (*p) { + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { + p += 3; + if (*p == ',') { + p++; + } + } else if (event_name2nr(p, &p) == NUM_EVENTS) { + return FAIL; + } + } + + return OK; +} + +// Add "what" to 'eventignore' to skip loading syntax highlighting for every +// buffer loaded into the window. "what" must start with a comma. +// Returns the old value of 'eventignore' in allocated memory. +char_u *au_event_disable(char *what) +{ + char_u *new_ei; + char_u *save_ei; + + save_ei = vim_strsave(p_ei); + new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + if (*what == ',' && *p_ei == NUL) { + STRCPY(new_ei, what + 1); + } else { + STRCAT(new_ei, what); + } + set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE); + xfree(new_ei); + + return save_ei; +} + +void au_event_restore(char_u *old_ei) +{ + if (old_ei != NULL) { + set_string_option_direct((char_u *)"ei", -1, old_ei, OPT_FREE, SID_NONE); + xfree(old_ei); + } +} + +// Implements :autocmd. +// Defines an autocmd (does not execute; cf. apply_autocmds_group). +// +// Can be used in the following ways: +// +// :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that +// will be automatically executed for <event> +// when editing a file matching <pat>, in +// the current group. +// :autocmd <event> <pat> Show the autocommands associated with +// <event> and <pat>. +// :autocmd <event> Show the autocommands associated with +// <event>. +// :autocmd Show all autocommands. +// :autocmd! <event> <pat> <cmd> Remove all autocommands associated with +// <event> and <pat>, and add the command +// <cmd>, for the current group. +// :autocmd! <event> <pat> Remove all autocommands associated with +// <event> and <pat> for the current group. +// :autocmd! <event> Remove all autocommands associated with +// <event> for the current group. +// :autocmd! Remove ALL autocommands for the current +// group. +// +// Multiple events and patterns may be given separated by commas. Here are +// some examples: +// :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic +// :autocmd bufleave * set tw=79 nosmartindent ic infercase +// +// :autocmd * *.c show all autocommands for *.c files. +// +// Mostly a {group} argument can optionally appear before <event>. +void do_autocmd(char_u *arg_in, int forceit) +{ + char_u *arg = arg_in; + char_u *pat; + char_u *envpat = NULL; + char_u *cmd; + int need_free = false; + int nested = false; + bool once = false; + int group; + + if (*arg == '|') { + arg = (char_u *)""; + group = AUGROUP_ALL; // no argument, use all groups + } else { + // Check for a legal group name. If not, use AUGROUP_ALL. + group = au_get_grouparg(&arg); + } + + // Scan over the events. + // If we find an illegal name, return here, don't do anything. + pat = find_end_event(arg, group != AUGROUP_ALL); + if (pat == NULL) { + return; + } + + pat = skipwhite(pat); + if (*pat == '|') { + pat = (char_u *)""; + cmd = (char_u *)""; + } else { + // Scan over the pattern. Put a NUL at the end. + cmd = pat; + while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) { + cmd++; + } + if (*cmd) { + *cmd++ = NUL; + } + + // Expand environment variables in the pattern. Set 'shellslash', we want + // forward slashes here. + if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) { +#ifdef BACKSLASH_IN_FILENAME + int p_ssl_save = p_ssl; + + p_ssl = true; +#endif + envpat = expand_env_save(pat); +#ifdef BACKSLASH_IN_FILENAME + p_ssl = p_ssl_save; +#endif + if (envpat != NULL) { + pat = envpat; + } + } + + cmd = skipwhite(cmd); + for (size_t i = 0; i < 2; i++) { + if (*cmd != NUL) { + // Check for "++once" flag. + if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) { + if (once) { + EMSG2(_(e_duparg2), "++once"); + } + once = true; + cmd = skipwhite(cmd + 6); + } + + // Check for "++nested" flag. + if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) { + if (nested) { + EMSG2(_(e_duparg2), "++nested"); + } + nested = true; + cmd = skipwhite(cmd + 8); + } + + // Check for the old (deprecated) "nested" flag. + if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) { + if (nested) { + EMSG2(_(e_duparg2), "nested"); + } + nested = true; + cmd = skipwhite(cmd + 6); + } + } + } + + // Find the start of the commands. + // Expand <sfile> in it. + if (*cmd != NUL) { + cmd = expand_sfile(cmd); + if (cmd == NULL) { // some error + return; + } + need_free = true; + } + } + + // Print header when showing autocommands. + if (!forceit && *cmd == NUL) { + // Highlight title + MSG_PUTS_TITLE(_("\n--- Autocommands ---")); + } + + // Loop over the events. + last_event = (event_T)-1; // for listing the event name + last_group = AUGROUP_ERROR; // for listing the group name + if (*arg == '*' || *arg == NUL || *arg == '|') { + for (event_T event = (event_T)0; event < (int)NUM_EVENTS; + event = (event_T)(event + 1)) { + if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) + == FAIL) { + break; + } + } + } else { + while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { + event_T event = event_name2nr(arg, &arg); + assert(event < NUM_EVENTS); + if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) + == FAIL) { + break; + } + } + } + + if (need_free) { + xfree(cmd); + } + xfree(envpat); +} + +// Find the group ID in a ":autocmd" or ":doautocmd" argument. +// The "argp" argument is advanced to the following argument. +// +// Returns the group ID or AUGROUP_ALL. +static int au_get_grouparg(char_u **argp) +{ + char_u *group_name; + char_u *p; + char_u *arg = *argp; + int group = AUGROUP_ALL; + + for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { + } + if (p > arg) { + group_name = vim_strnsave(arg, (size_t)(p - arg)); + group = au_find_group(group_name); + if (group == AUGROUP_ERROR) { + group = AUGROUP_ALL; // no match, use all groups + } else { + *argp = skipwhite(p); // match, skip over group name + } + xfree(group_name); + } + return group; +} + +// do_autocmd() for one event. +// Defines an autocmd (does not execute; cf. apply_autocmds_group). +// +// If *pat == NUL: do for all patterns. +// If *cmd == NUL: show entries. +// If forceit == true: delete entries. +// If group is not AUGROUP_ALL: only use this group. +static int do_autocmd_event(event_T event, + char_u *pat, + bool once, + int nested, + char_u *cmd, + int forceit, + int group) +{ + AutoPat *ap; + AutoPat **prev_ap; + AutoCmd *ac; + AutoCmd **prev_ac; + int brace_level; + char_u *endpat; + int findgroup; + int allgroups; + int patlen; + int is_buflocal; + int buflocal_nr; + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + + if (group == AUGROUP_ALL) { + findgroup = current_augroup; + } else { + findgroup = group; + } + allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL); + + // Show or delete all patterns for an event. + if (*pat == NUL) { + for (ap = first_autopat[event]; ap != NULL; ap = ap->next) { + if (forceit) { // delete the AutoPat, if it's in the current group + if (ap->group == findgroup) { + au_remove_pat(ap); + } + } else if (group == AUGROUP_ALL || ap->group == group) { + show_autocmd(ap, event); + } + } + } + + // Loop through all the specified patterns. + for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { + // Find end of the pattern. + // Watch out for a comma in braces, like "*.\{obj,o\}". + endpat = pat; + // ignore single comma + if (*endpat == ',') { + continue; + } + brace_level = 0; + for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); + endpat++) { + if (*endpat == '{') { + brace_level++; + } else if (*endpat == '}') { + brace_level--; + } + } + patlen = (int)(endpat - pat); + + // detect special <buflocal[=X]> buffer-local patterns + is_buflocal = false; + buflocal_nr = 0; + + if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0 + && pat[patlen - 1] == '>') { + // "<buffer...>": Error will be printed only for addition. + // printing and removing will proceed silently. + is_buflocal = true; + if (patlen == 8) { + // "<buffer>" + buflocal_nr = curbuf->b_fnum; + } else if (patlen > 9 && pat[7] == '=') { + if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) { + // "<buffer=abuf>" + buflocal_nr = autocmd_bufnr; + } else if (skipdigits(pat + 8) == pat + patlen - 1) { + // "<buffer=123>" + buflocal_nr = atoi((char *)pat + 8); + } + } + } + + if (is_buflocal) { + // normalize pat into standard "<buffer>#N" form + snprintf( + (char *)buflocal_pat, + BUFLOCAL_PAT_LEN, + "<buffer=%d>", + buflocal_nr); + + pat = buflocal_pat; // can modify pat and patlen + patlen = (int)STRLEN(buflocal_pat); // but not endpat + } + + // Find AutoPat entries with this pattern. When adding a command it + // always goes at or after the last one, so start at the end. + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } + while ((ap = *prev_ap) != NULL) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group, or a group was + // not specified and it's the current group, or a group was + // not specified and we are listing + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if ((allgroups || ap->group == findgroup) && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + // Remove existing autocommands. + // If adding any new autocmd's for this AutoPat, don't + // delete the pattern from the autopat list, append to + // this list. + if (forceit) { + if (*cmd != NUL && ap->next == NULL) { + au_remove_cmds(ap); + break; + } + au_remove_pat(ap); + } else if (*cmd == NUL) { + // Show autocmd's for this autopat, or buflocals <buffer=X> + show_autocmd(ap, event); + + } else if (ap->next == NULL) { + // Add autocmd to this autopat, if it's the last one. + break; + } + } + } + prev_ap = &ap->next; + } + + // Add a new command. + if (*cmd != NUL) { + // If the pattern we want to add a command to does appear at the + // end of the list (or not is not in the list at all), add the + // pattern at the end of the list. + if (ap == NULL) { + // refuse to add buffer-local ap if buffer number is invalid + if (is_buflocal + && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) { + emsgf(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr); + return FAIL; + } + + ap = xmalloc(sizeof(AutoPat)); + ap->pat = vim_strnsave(pat, (size_t)patlen); + ap->patlen = patlen; + + if (is_buflocal) { + ap->buflocal_nr = buflocal_nr; + ap->reg_prog = NULL; + } else { + char_u *reg_pat; + + ap->buflocal_nr = 0; + reg_pat = file_pat_to_reg_pat(pat, endpat, &ap->allow_dirs, true); + if (reg_pat != NULL) { + ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); + } + xfree(reg_pat); + if (reg_pat == NULL || ap->reg_prog == NULL) { + xfree(ap->pat); + xfree(ap); + return FAIL; + } + } + ap->cmds = NULL; + *prev_ap = ap; + last_autopat[(int)event] = ap; + ap->next = NULL; + if (group == AUGROUP_ALL) { + ap->group = current_augroup; + } else { + ap->group = group; + } + } + + // Add the autocmd at the end of the AutoCmd list. + prev_ac = &(ap->cmds); + while ((ac = *prev_ac) != NULL) { + prev_ac = &ac->next; + } + ac = xmalloc(sizeof(AutoCmd)); + ac->cmd = vim_strsave(cmd); + ac->script_ctx = current_sctx; + ac->script_ctx.sc_lnum += sourcing_lnum; + ac->next = NULL; + *prev_ac = ac; + ac->once = once; + ac->nested = nested; + } + } + + au_cleanup(); // may really delete removed patterns/commands now + return OK; +} + +// Implementation of ":doautocmd [group] event [fname]". +// Return OK for success, FAIL for failure; +int do_doautocmd(char_u *arg, + int do_msg, // give message for no matching autocmds? + bool *did_something) +{ + char_u *fname; + int nothing_done = true; + int group; + + if (did_something != NULL) { + *did_something = false; + } + + // Check for a legal group name. If not, use AUGROUP_ALL. + group = au_get_grouparg(&arg); + + if (*arg == '*') { + EMSG(_("E217: Can't execute autocommands for ALL events")); + return FAIL; + } + + // Scan over the events. + // If we find an illegal name, return here, don't do anything. + fname = find_end_event(arg, group != AUGROUP_ALL); + if (fname == NULL) { + return FAIL; + } + + fname = skipwhite(fname); + + // Loop over the events. + while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) { + if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true, group, + curbuf, NULL)) { + nothing_done = false; + } + } + + if (nothing_done && do_msg) { + MSG(_("No matching autocommands")); + } + if (did_something != NULL) { + *did_something = !nothing_done; + } + + return aborting() ? FAIL : OK; +} + +// ":doautoall": execute autocommands for each loaded buffer. +void ex_doautoall(exarg_T *eap) +{ + int retval; + aco_save_T aco; + char_u *arg = eap->arg; + int call_do_modelines = check_nomodeline(&arg); + bufref_T bufref; + + // This is a bit tricky: For some commands curwin->w_buffer needs to be + // equal to curbuf, but for some buffers there may not be a window. + // So we change the buffer for the current window for a moment. This + // gives problems when the autocommands make changes to the list of + // buffers or windows... + FOR_ALL_BUFFERS(buf) { + if (buf->b_ml.ml_mfp == NULL) { + continue; + } + // Find a window for this buffer and save some values. + aucmd_prepbuf(&aco, buf); + set_bufref(&bufref, buf); + + bool did_aucmd; + // execute the autocommands for this buffer + retval = do_doautocmd(arg, false, &did_aucmd); + + if (call_do_modelines && did_aucmd) { + // Execute the modeline settings, but don't set window-local + // options if we are using the current window for another + // buffer. + do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0); + } + + // restore the current window + aucmd_restbuf(&aco); + + // Stop if there is some error or buffer was deleted. + if (retval == FAIL || !bufref_valid(&bufref)) { + break; + } + } + + check_cursor(); // just in case lines got deleted +} + +/// Check *argp for <nomodeline>. When it is present return false, otherwise +/// return true and advance *argp to after it. Thus do_modelines() should be +/// called when true is returned. +/// +/// @param[in,out] argp argument string +bool check_nomodeline(char_u **argp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (STRNCMP(*argp, "<nomodeline>", 12) == 0) { + *argp = skipwhite(*argp + 12); + return false; + } + 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 `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) +{ + win_T *win; + bool need_append = true; // Append `aucmd_win` to the window list. + + // 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 the `aucmd_win` dummy floating window. + if (win == NULL && aucmd_win == NULL) { + win_alloc_aucmd_win(); + need_append = false; + } + if (win == NULL && aucmd_win_used) { + // Strange recursive autocommand, fall back to using the current + // window. Expect a few side effects... + win = curwin; + } + + aco->save_curwin = curwin; + aco->save_prevwin = prevwin; + aco->save_curbuf = curbuf; + 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 = false; + curwin = win; + } else { + // There is no window for "buf", use "aucmd_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 = true; + aucmd_win_used = true; + aucmd_win->w_buffer = buf; + aucmd_win->w_s = &buf->b_s; + buf->b_nwindows++; + win_init_empty(aucmd_win); // set cursor and topline to safe values + + // Make sure w_localdir and globaldir are NULL to avoid a chdir() in + // win_enter_ext(). + XFREE_CLEAR(aucmd_win->w_localdir); + aco->globaldir = globaldir; + globaldir = NULL; + + block_autocmds(); // We don't want BufEnter/WinEnter autocommands. + if (need_append) { + win_append(lastwin, aucmd_win); + handle_register_window(aucmd_win); + win_config_float(aucmd_win, aucmd_win->w_float_config); + } + // Prevent chdir() call in win_enter_ext(), through do_autochdir() + int save_acd = p_acd; + p_acd = false; + win_enter(aucmd_win, false); + p_acd = save_acd; + unblock_autocmds(); + curwin = aucmd_win; + } + curbuf = buf; + aco->new_curwin = curwin; + set_bufref(&aco->new_curbuf, curbuf); +} + +/// Cleanup after executing autocommands for a (hidden) buffer. +/// Restore the window as it was (if possible). +/// +/// @param aco structure holding saved values +void aucmd_restbuf(aco_save_T *aco) +{ + if (aco->use_aucmd_win) { + curbuf->b_nwindows--; + // Find "aucmd_win", it can't be closed, but it may be in another tab page. + // Do not trigger autocommands here. + block_autocmds(); + if (curwin != aucmd_win) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp == aucmd_win) { + if (tp != curtab) { + goto_tabpage_tp(tp, true, true); + } + win_goto(aucmd_win); + goto win_found; + } + } + } + win_found: + + win_remove(curwin, NULL); + handle_unregister_window(curwin); + if (curwin->w_grid.chars != NULL) { + ui_comp_remove_grid(&curwin->w_grid); + ui_call_win_hide(curwin->w_grid.handle); + grid_free(&curwin->w_grid); + } + + aucmd_win_used = false; + last_status(false); // may need to remove last status line + + if (!valid_tabpage_win(curtab)) { + // no valid window in current tabpage + close_tabpage(curtab); + } + + unblock_autocmds(); + + if (win_valid(aco->save_curwin)) { + curwin = aco->save_curwin; + } else { + // Hmm, original window disappeared. Just use the first one. + curwin = firstwin; + } + prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin + : firstwin; // window disappeared? + vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables + hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab + curbuf = curwin->w_buffer; + + xfree(globaldir); + globaldir = aco->globaldir; + + // the buffer contents may have changed + check_cursor(); + if (curwin->w_topline > curbuf->b_ml.ml_line_count) { + curwin->w_topline = curbuf->b_ml.ml_line_count; + curwin->w_topfill = 0; + } + } else { + // restore curwin + if (win_valid(aco->save_curwin)) { + // Restore the buffer which was previously edited by curwin, if it was + // changed, we are still the same window and the buffer is valid. + if (curwin == aco->new_curwin && curbuf != aco->new_curbuf.br_buf + && bufref_valid(&aco->new_curbuf) + && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) { + if (curwin->w_s == &curbuf->b_s) { + curwin->w_s = &aco->new_curbuf.br_buf->b_s; + } + curbuf->b_nwindows--; + curbuf = aco->new_curbuf.br_buf; + curwin->w_buffer = curbuf; + curbuf->b_nwindows++; + } + + curwin = aco->save_curwin; + prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin + : firstwin; // window disappeared? + curbuf = curwin->w_buffer; + // In case the autocommand moves the cursor to a position that does not + // exist in curbuf + check_cursor(); + } + } +} + +/// Execute autocommands for "event" and file name "fname". +/// +/// @param event event that occurred +/// @param fname filename, NULL or empty means use actual file name +/// @param fname_io filename to use for <afile> on cmdline +/// @param force When true, ignore autocmd_busy +/// @param buf Buffer for <abuf> +/// +/// @return true if some commands were executed. +bool apply_autocmds(event_T event, + char_u *fname, + char_u *fname_io, + bool force, + buf_T *buf) +{ + return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, + NULL); +} + +/// Like apply_autocmds(), but with extra "eap" argument. This takes care of +/// setting v:filearg. +/// +/// @param event event that occurred +/// @param fname NULL or empty means use actual file name +/// @param fname_io fname to use for <afile> on cmdline +/// @param force When true, ignore autocmd_busy +/// @param buf Buffer for <abuf> +/// @param exarg Ex command arguments +/// +/// @return true if some commands were executed. +bool apply_autocmds_exarg(event_T event, + char_u *fname, + char_u *fname_io, + bool force, + buf_T *buf, + exarg_T *eap) +{ + return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, + eap); +} + +/// Like apply_autocmds(), but handles the caller's retval. If the script +/// processing is being aborted or if retval is FAIL when inside a try +/// conditional, no autocommands are executed. If otherwise the autocommands +/// cause the script to be aborted, retval is set to FAIL. +/// +/// @param event event that occurred +/// @param fname NULL or empty means use actual file name +/// @param fname_io fname to use for <afile> on cmdline +/// @param force When true, ignore autocmd_busy +/// @param buf Buffer for <abuf> +/// @param[in,out] retval caller's retval +/// +/// @return true if some autocommands were executed +bool apply_autocmds_retval(event_T event, + char_u *fname, + char_u *fname_io, + bool force, + buf_T *buf, + int *retval) +{ + if (should_abort(*retval)) { + return false; + } + + bool did_cmd = apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, NULL); + if (did_cmd && aborting()) { + *retval = FAIL; + } + return did_cmd; +} + +/// Return true if "event" autocommand is defined. +/// +/// @param event the autocommand to check +bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return first_autopat[event] != NULL; +} + +/// Return true when there is a CursorHold/CursorHoldI autocommand defined for +/// the current mode. +bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return has_event( + (get_real_state() == NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)); + // return first_autopat[] != NULL; +} + +/// Return true if the CursorHold/CursorHoldI event can be triggered. +bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + int state; + + if (!did_cursorhold && has_cursorhold() && reg_recording == 0 + && typebuf.tb_len == 0 && !ins_compl_active()) { + state = get_real_state(); + if (state == NORMAL_BUSY || (state & INSERT) != 0) { + return true; + } + } + return false; +} + +/// Execute autocommands for "event" and file name "fname". +/// +/// @param event event that occurred +/// @param fname filename, NULL or empty means use actual file name +/// @param fname_io filename to use for <afile> on cmdline, +/// NULL means use `fname`. +/// @param force When true, ignore autocmd_busy +/// @param group autocmd group ID or AUGROUP_ALL +/// @param buf Buffer for <abuf> +/// @param eap Ex command arguments +/// +/// @return true if some commands were executed. +static bool apply_autocmds_group(event_T event, + char_u *fname, + char_u *fname_io, + bool force, + int group, + buf_T *buf, + exarg_T *eap) +{ + char_u *sfname = NULL; // short file name + char_u *tail; + bool save_changed; + buf_T *old_curbuf; + bool retval = false; + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + char_u *save_autocmd_fname; + int save_autocmd_bufnr; + char_u *save_autocmd_match; + int save_autocmd_busy; + int save_autocmd_nested; + static int nesting = 0; + AutoPatCmd patcmd; + AutoPat *ap; + char_u *save_cmdarg; + long save_cmdbang; + static int filechangeshell_busy = false; + proftime_T wait_time; + bool did_save_redobuff = false; + save_redo_T save_redo; + const bool save_KeyTyped = KeyTyped; + + // Quickly return if there are no autocommands for this event or + // autocommands are blocked. + if (event == NUM_EVENTS || first_autopat[(int)event] == NULL + || is_autocmd_blocked()) { + goto BYPASS_AU; + } + + // When autocommands are busy, new autocommands are only executed when + // explicitly enabled with the "nested" flag. + if (autocmd_busy && !(force || autocmd_nested)) { + goto BYPASS_AU; + } + + // Quickly return when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + if (aborting()) { + goto BYPASS_AU; + } + + // FileChangedShell never nests, because it can create an endless loop. + if (filechangeshell_busy + && (event == EVENT_FILECHANGEDSHELL + || event == EVENT_FILECHANGEDSHELLPOST)) { + goto BYPASS_AU; + } + + // Ignore events in 'eventignore'. + if (event_ignored(event)) { + goto BYPASS_AU; + } + + // Allow nesting of autocommands, but restrict the depth, because it's + // possible to create an endless loop. + if (nesting == 10) { + EMSG(_("E218: autocommand nesting too deep")); + goto BYPASS_AU; + } + + // Check if these autocommands are disabled. Used when doing ":all" or + // ":ball". + if ((autocmd_no_enter && (event == EVENT_WINENTER || event == EVENT_BUFENTER)) + || (autocmd_no_leave + && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) { + goto BYPASS_AU; + } + + // Save the autocmd_* variables and info about the current buffer. + save_autocmd_fname = autocmd_fname; + save_autocmd_bufnr = autocmd_bufnr; + save_autocmd_match = autocmd_match; + save_autocmd_busy = autocmd_busy; + save_autocmd_nested = autocmd_nested; + save_changed = curbuf->b_changed; + old_curbuf = curbuf; + + // Set the file name to be used for <afile>. + // Make a copy to avoid that changing a buffer name or directory makes it + // invalid. + if (fname_io == NULL) { + if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE + || event == EVENT_OPTIONSET) { + autocmd_fname = NULL; + } else if (fname != NULL && !ends_excmd(*fname)) { + autocmd_fname = fname; + } else if (buf != NULL) { + autocmd_fname = buf->b_ffname; + } else { + autocmd_fname = NULL; + } + } else { + autocmd_fname = fname_io; + } + if (autocmd_fname != NULL) { + // Allocate MAXPATHL for when eval_vars() resolves the fullpath. + autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL); + } + + // Set the buffer number to be used for <abuf>. + if (buf == NULL) { + autocmd_bufnr = 0; + } else { + autocmd_bufnr = buf->b_fnum; + } + + // When the file name is NULL or empty, use the file name of buffer "buf". + // Always use the full path of the file name to match with, in case + // "allow_dirs" is set. + if (fname == NULL || *fname == NUL) { + if (buf == NULL) { + fname = NULL; + } else { + if (event == EVENT_SYNTAX) { + fname = buf->b_p_syn; + } else if (event == EVENT_FILETYPE) { + fname = buf->b_p_ft; + } else { + if (buf->b_sfname != NULL) { + sfname = vim_strsave(buf->b_sfname); + } + fname = buf->b_ffname; + } + } + if (fname == NULL) { + fname = (char_u *)""; + } + fname = vim_strsave(fname); // make a copy, so we can change it + } else { + sfname = vim_strsave(fname); + // Don't try expanding the following events. + if (event == EVENT_CMDLINECHANGED || event == EVENT_CMDLINEENTER + || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER + || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED + || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE + || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE + || event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET + || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE + || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING + || event == EVENT_SYNTAX || event == EVENT_SIGNAL + || event == EVENT_TABCLOSED || event == EVENT_WINCLOSED) { + fname = vim_strsave(fname); + } else { + fname = (char_u *)FullName_save((char *)fname, false); + } + } + if (fname == NULL) { // out of memory + xfree(sfname); + retval = false; + goto BYPASS_AU; + } + +#ifdef BACKSLASH_IN_FILENAME + // Replace all backslashes with forward slashes. This makes the + // autocommand patterns portable between Unix and Windows. + if (sfname != NULL) { + forward_slash(sfname); + } + forward_slash(fname); +#endif + + // Set the name to be used for <amatch>. + autocmd_match = fname; + + // Don't redraw while doing autocommands. + RedrawingDisabled++; + save_sourcing_name = sourcing_name; + sourcing_name = NULL; // don't free this one + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 0; // no line number here + + const sctx_T save_current_sctx = current_sctx; + + if (do_profiling == PROF_YES) { + prof_child_enter(&wait_time); // doesn't count for the caller itself + } + + // Don't use local function variables, if called from a function. + funccal_entry_T funccal_entry; + save_funccal(&funccal_entry); + + // When starting to execute autocommands, save the search patterns. + if (!autocmd_busy) { + save_search_patterns(); + if (!ins_compl_active()) { + saveRedobuff(&save_redo); + did_save_redobuff = true; + } + did_filetype = keep_filetype; + } + + // Note that we are applying autocmds. Some commands need to know. + autocmd_busy = true; + filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL); + nesting++; // see matching decrement below + + // Remember that FileType was triggered. Used for did_filetype(). + if (event == EVENT_FILETYPE) { + did_filetype = true; + } + + tail = path_tail(fname); + + // Find first autocommand that matches + patcmd.curpat = first_autopat[(int)event]; + patcmd.nextcmd = NULL; + patcmd.group = group; + patcmd.fname = fname; + patcmd.sfname = sfname; + patcmd.tail = tail; + patcmd.event = event; + patcmd.arg_bufnr = autocmd_bufnr; + patcmd.next = NULL; + auto_next_pat(&patcmd, false); + + // found one, start executing the autocommands + if (patcmd.curpat != NULL) { + // add to active_apc_list + patcmd.next = active_apc_list; + active_apc_list = &patcmd; + + // set v:cmdarg (only when there is a matching pattern) + save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG); + if (eap != NULL) { + save_cmdarg = set_cmdarg(eap, NULL); + set_vim_var_nr(VV_CMDBANG, (long)eap->forceit); + } else { + save_cmdarg = NULL; // avoid gcc warning + } + retval = true; + // mark the last pattern, to avoid an endless loop when more patterns + // are added when executing autocommands + for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) { + ap->last = false; + } + ap->last = true; + check_lnums(true); // make sure cursor and topline are valid + + // Execute the autocmd. The `getnextac` callback handles iteration. + do_cmdline(NULL, getnextac, (void *)&patcmd, + DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT); + + reset_lnums(); // restore cursor and topline, unless they were changed + + if (eap != NULL) { + (void)set_cmdarg(NULL, save_cmdarg); + set_vim_var_nr(VV_CMDBANG, save_cmdbang); + } + // delete from active_apc_list + if (active_apc_list == &patcmd) { // just in case + active_apc_list = patcmd.next; + } + } + + RedrawingDisabled--; + autocmd_busy = save_autocmd_busy; + filechangeshell_busy = false; + autocmd_nested = save_autocmd_nested; + xfree(sourcing_name); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + xfree(autocmd_fname); + autocmd_fname = save_autocmd_fname; + autocmd_bufnr = save_autocmd_bufnr; + autocmd_match = save_autocmd_match; + current_sctx = save_current_sctx; + restore_funccal(); + if (do_profiling == PROF_YES) { + prof_child_exit(&wait_time); + } + KeyTyped = save_KeyTyped; + xfree(fname); + xfree(sfname); + nesting--; // see matching increment above + + // When stopping to execute autocommands, restore the search patterns and + // the redo buffer. Free any buffers in the au_pending_free_buf list and + // free any windows in the au_pending_free_win list. + if (!autocmd_busy) { + restore_search_patterns(); + if (did_save_redobuff) { + restoreRedobuff(&save_redo); + } + did_filetype = false; + while (au_pending_free_buf != NULL) { + buf_T *b = au_pending_free_buf->b_next; + xfree(au_pending_free_buf); + au_pending_free_buf = b; + } + while (au_pending_free_win != NULL) { + win_T *w = au_pending_free_win->w_next; + xfree(au_pending_free_win); + au_pending_free_win = w; + } + } + + // Some events don't set or reset the Changed flag. + // Check if still in the same buffer! + if (curbuf == old_curbuf + && (event == EVENT_BUFREADPOST || event == EVENT_BUFWRITEPOST + || event == EVENT_FILEAPPENDPOST || event == EVENT_VIMLEAVE + || event == EVENT_VIMLEAVEPRE)) { + if (curbuf->b_changed != save_changed) { + need_maketitle = true; + } + curbuf->b_changed = save_changed; + } + + au_cleanup(); // may really delete removed patterns/commands now + +BYPASS_AU: + // When wiping out a buffer make sure all its buffer-local autocommands + // are deleted. + if (event == EVENT_BUFWIPEOUT && buf != NULL) { + aubuflocal_remove(buf); + } + + if (retval == OK && event == EVENT_FILETYPE) { + au_did_filetype = true; + } + + return retval; +} + +// Block triggering autocommands until unblock_autocmd() is called. +// Can be used recursively, so long as it's symmetric. +void block_autocmds(void) +{ + // Remember the value of v:termresponse. + if (is_autocmd_blocked()) { + old_termresponse = get_vim_var_str(VV_TERMRESPONSE); + } + autocmd_blocked++; +} + +void unblock_autocmds(void) +{ + autocmd_blocked--; + + // When v:termresponse was set while autocommands were blocked, trigger + // the autocommands now. Esp. useful when executing a shell command + // during startup (nvim -d). + if (is_autocmd_blocked() + && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) { + apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf); + } +} + +static inline bool is_autocmd_blocked(void) +{ + return autocmd_blocked != 0; +} + +/// Find next autocommand pattern that matches. +/// stop when 'last' flag is set +void auto_next_pat(AutoPatCmd *apc, int stop_at_last) +{ + AutoPat *ap; + AutoCmd *cp; + char *s; + + XFREE_CLEAR(sourcing_name); + + for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { + apc->curpat = NULL; + + // Only use a pattern when it has not been removed, has commands and + // the group matches. For buffer-local autocommands only check the + // buffer number. + if (ap->pat != NULL && ap->cmds != NULL + && (apc->group == AUGROUP_ALL || apc->group == ap->group)) { + // execution-condition + if (ap->buflocal_nr == 0 + ? match_file_pat( + NULL, + &ap->reg_prog, + apc->fname, + apc->sfname, + apc->tail, + ap->allow_dirs) + : ap->buflocal_nr == apc->arg_bufnr) { + const char *const name = event_nr2name(apc->event); + s = _("%s Autocommands for \"%s\""); + + const size_t sourcing_name_len + = (STRLEN(s) + strlen(name) + (size_t)ap->patlen + 1); + + sourcing_name = xmalloc(sourcing_name_len); + snprintf((char *)sourcing_name, sourcing_name_len, s, name, + (char *)ap->pat); + if (p_verbose >= 8) { + verbose_enter(); + smsg(_("Executing %s"), sourcing_name); + verbose_leave(); + } + + apc->curpat = ap; + apc->nextcmd = ap->cmds; + // mark last command + for (cp = ap->cmds; cp->next != NULL; cp = cp->next) { + cp->last = false; + } + cp->last = true; + } + line_breakcheck(); + if (apc->curpat != NULL) { // found a match + break; + } + } + if (stop_at_last && ap->last) { + break; + } + } +} + +/// Get next autocommand command. +/// Called by do_cmdline() to get the next line for ":if". +/// @return allocated string, or NULL for end of autocommands. +char_u *getnextac(int c, void *cookie, int indent, bool do_concat) +{ + AutoPatCmd *acp = (AutoPatCmd *)cookie; + char_u *retval; + AutoCmd *ac; + + // Can be called again after returning the last line. + if (acp->curpat == NULL) { + return NULL; + } + + // repeat until we find an autocommand to execute + for (;;) { + // skip removed commands + while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) { + if (acp->nextcmd->last) { + acp->nextcmd = NULL; + } else { + acp->nextcmd = acp->nextcmd->next; + } + } + + if (acp->nextcmd != NULL) { + break; + } + + // at end of commands, find next pattern that matches + if (acp->curpat->last) { + acp->curpat = NULL; + } else { + acp->curpat = acp->curpat->next; + } + if (acp->curpat != NULL) { + auto_next_pat(acp, true); + } + if (acp->curpat == NULL) { + return NULL; + } + } + + ac = acp->nextcmd; + + if (p_verbose >= 9) { + verbose_enter_scroll(); + smsg(_("autocommand %s"), ac->cmd); + msg_puts("\n"); // don't overwrite this either + verbose_leave_scroll(); + } + retval = vim_strsave(ac->cmd); + // Remove one-shot ("once") autocmd in anticipation of its execution. + if (ac->once) { + au_del_cmd(ac); + } + autocmd_nested = ac->nested; + current_sctx = ac->script_ctx; + if (ac->last) { + acp->nextcmd = NULL; + } else { + acp->nextcmd = ac->next; + } + + return retval; +} + +/// Return true if there is a matching autocommand for "fname". +/// To account for buffer-local autocommands, function needs to know +/// in which buffer the file will be opened. +/// +/// @param event event that occurred. +/// @param sfname filename the event occurred in. +/// @param buf buffer the file is open in +bool has_autocmd(event_T event, + char_u *sfname, + buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT +{ + AutoPat *ap; + char_u *fname; + char_u *tail = path_tail(sfname); + bool retval = false; + + fname = (char_u *)FullName_save((char *)sfname, false); + if (fname == NULL) { + return false; + } + +#ifdef BACKSLASH_IN_FILENAME + // Replace all backslashes with forward slashes. This makes the + // autocommand patterns portable between Unix and Windows. + sfname = vim_strsave(sfname); + forward_slash(sfname); + forward_slash(fname); +#endif + + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + if (ap->pat != NULL && ap->cmds != NULL + && (ap->buflocal_nr == 0 + ? match_file_pat( + NULL, + &ap->reg_prog, + fname, + sfname, + tail, + ap->allow_dirs) + : buf != NULL && ap->buflocal_nr == buf->b_fnum)) { + retval = true; + break; + } + } + + xfree(fname); +#ifdef BACKSLASH_IN_FILENAME + xfree(sfname); +#endif + + return retval; +} + +// Function given to ExpandGeneric() to obtain the list of autocommand group +// names. +char_u *get_augroup_name(expand_T *xp, int idx) +{ + if (idx == augroups.ga_len) { // add "END" add the end + return (char_u *)"END"; + } + if (idx >= augroups.ga_len) { // end of list + return NULL; + } + if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) { + // skip deleted entries + return (char_u *)""; + } + return (char_u *)AUGROUP_NAME(idx); +} + +char_u *set_context_in_autocmd( + expand_T *xp, + char_u *arg, + int doautocmd // true for :doauto*, false for :autocmd +) +{ + char_u *p; + int group; + + // check for a group name, skip it if present + autocmd_include_groups = false; + p = arg; + group = au_get_grouparg(&arg); + + // If there only is a group name that's what we expand. + if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) { + arg = p; + group = AUGROUP_ALL; + } + + // skip over event name + for (p = arg; *p != NUL && !ascii_iswhite(*p); p++) { + if (*p == ',') { + arg = p + 1; + } + } + if (*p == NUL) { + if (group == AUGROUP_ALL) { + autocmd_include_groups = true; + } + xp->xp_context = EXPAND_EVENTS; // expand event name + xp->xp_pattern = arg; + return NULL; + } + + // skip over pattern + arg = skipwhite(p); + while (*arg && (!ascii_iswhite(*arg) || arg[-1] == '\\')) { + arg++; + } + if (*arg) { + return arg; // expand (next) command + } + + if (doautocmd) { + xp->xp_context = EXPAND_FILES; // expand file names + } else { + xp->xp_context = EXPAND_NOTHING; // pattern is not expanded + } + return NULL; +} + +// Function given to ExpandGeneric() to obtain the list of event names. +char_u *get_event_name(expand_T *xp, int idx) +{ + if (idx < augroups.ga_len) { // First list group names, if wanted + if (!autocmd_include_groups || AUGROUP_NAME(idx) == NULL + || AUGROUP_NAME(idx) == get_deleted_augroup()) { + return (char_u *)""; // skip deleted entries + } + return (char_u *)AUGROUP_NAME(idx); + } + return (char_u *)event_names[idx - augroups.ga_len].name; +} + +/// Check whether given autocommand is supported +/// +/// @param[in] event Event to check. +/// +/// @return True if it is, false otherwise. +bool autocmd_supported(const char *const event) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + char_u *p; + return event_name2nr((const char_u *)event, &p) != NUM_EVENTS; +} + +/// Return true if an autocommand is defined for a group, event and +/// pattern: The group can be omitted to accept any group. +/// `event` and `pattern` can be omitted to accept any event and pattern. +/// Buffer-local patterns <buffer> or <buffer=N> are accepted. +/// Used for: +/// exists("#Group") or +/// exists("#Group#Event") or +/// exists("#Group#Event#pat") or +/// exists("#Event") or +/// exists("#Event#pat") +/// +/// @param arg autocommand string +bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT +{ + event_T event; + AutoPat *ap; + buf_T *buflocal_buf = NULL; + int group; + bool retval = false; + + // Make a copy so that we can change the '#' chars to a NUL. + char *const arg_save = xstrdup(arg); + char *p = strchr(arg_save, '#'); + if (p != NULL) { + *p++ = NUL; + } + + // First, look for an autocmd group name. + group = au_find_group((char_u *)arg_save); + char *event_name; + if (group == AUGROUP_ERROR) { + // Didn't match a group name, assume the first argument is an event. + group = AUGROUP_ALL; + event_name = arg_save; + } else { + if (p == NULL) { + // "Group": group name is present and it's recognized + retval = true; + goto theend; + } + + // Must be "Group#Event" or "Group#Event#pat". + event_name = p; + p = strchr(event_name, '#'); + if (p != NULL) { + *p++ = NUL; // "Group#Event#pat" + } + } + + char *pattern = p; // "pattern" is NULL when there is no pattern. + + // Find the index (enum) for the event name. + event = event_name2nr((char_u *)event_name, (char_u **)&p); + + // return false if the event name is not recognized + if (event == NUM_EVENTS) { + goto theend; + } + + // Find the first autocommand for this event. + // If there isn't any, return false; + // If there is one and no pattern given, return true; + ap = first_autopat[(int)event]; + if (ap == NULL) { + goto theend; + } + + // if pattern is "<buffer>", special handling is needed which uses curbuf + // for pattern "<buffer=N>, fnamecmp() will work fine + if (pattern != NULL && STRICMP(pattern, "<buffer>") == 0) { + buflocal_buf = curbuf; + } + + // Check if there is an autocommand with the given pattern. + for (; ap != NULL; ap = ap->next) { + // only use a pattern when it has not been removed and has commands. + // For buffer-local autocommands, fnamecmp() works fine. + if (ap->pat != NULL && ap->cmds != NULL + && (group == AUGROUP_ALL || ap->group == group) + && (pattern == NULL + || (buflocal_buf == NULL + ? fnamecmp(ap->pat, (char_u *)pattern) == 0 + : ap->buflocal_nr == buflocal_buf->b_fnum))) { + retval = true; + break; + } + } + +theend: + xfree(arg_save); + return retval; +} diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h new file mode 100644 index 0000000000..af1eeb0fc4 --- /dev/null +++ b/src/nvim/autocmd.h @@ -0,0 +1,81 @@ +#ifndef NVIM_AUTOCMD_H +#define NVIM_AUTOCMD_H + +#include "nvim/buffer_defs.h" +#include "nvim/ex_cmds_defs.h" + +// Struct to save values in before executing autocommands for a buffer that is +// not the current buffer. +typedef struct { + buf_T *save_curbuf; ///< saved curbuf + int use_aucmd_win; ///< using aucmd_win + win_T *save_curwin; ///< saved curwin + win_T *save_prevwin; ///< saved prevwin + win_T *new_curwin; ///< new curwin + bufref_T new_curbuf; ///< new curbuf + char_u *globaldir; ///< saved value of globaldir +} aco_save_T; + +typedef struct AutoCmd { + char_u *cmd; // Command to be executed (NULL when + // command has been removed) + bool once; // "One shot": removed after execution + bool nested; // If autocommands nest here + bool last; // last command in list + sctx_T script_ctx; // script context where defined + struct AutoCmd *next; // Next AutoCmd in list +} AutoCmd; + +typedef struct AutoPat { + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() +} AutoPat; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "auevents_enum.generated.h" +#endif + +/// +/// Struct used to keep status while executing autocommands for an event. +/// +typedef struct AutoPatCmd { + AutoPat *curpat; // next AutoPat to examine + AutoCmd *nextcmd; // next AutoCmd to execute + int group; // group being used + char_u *fname; // fname to match with + char_u *sfname; // sfname to match with + char_u *tail; // tail of fname + event_T event; // current event + int arg_bufnr; // initially equal to <abuf>, set to zero when + // buf is deleted + struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation +} AutoPatCmd; + + +// Set by the apply_autocmds_group function if the given event is equal to +// EVENT_FILETYPE. Used by the readfile function in order to determine if +// EVENT_BUFREADPOST triggered the EVENT_FILETYPE. +// +// Relying on this value requires one to reset it prior calling +// apply_autocmds_group. +EXTERN bool au_did_filetype INIT(= false); + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "autocmd.h.generated.h" +#endif + +#define AUGROUP_DEFAULT -1 // default autocmd group +#define AUGROUP_ERROR -2 // erroneous autocmd group +#define AUGROUP_ALL -3 // all autocmd groups + +#endif // NVIM_AUTOCMD_H diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 99cdde300d..ffa44c33cd 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3198,7 +3198,7 @@ void maketitle(void) int use_sandbox = false; int save_called_emsg = called_emsg; - use_sandbox = was_set_insecurely((char_u *)"titlestring", 0); + use_sandbox = was_set_insecurely(curwin, (char_u *)"titlestring", 0); called_emsg = false; build_stl_str_hl(curwin, (char_u *)buf, sizeof(buf), p_titlestring, use_sandbox, @@ -3309,7 +3309,7 @@ void maketitle(void) int use_sandbox = false; int save_called_emsg = called_emsg; - use_sandbox = was_set_insecurely((char_u *)"iconstring", 0); + use_sandbox = was_set_insecurely(curwin, (char_u *)"iconstring", 0); called_emsg = false; build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring, use_sandbox, diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 93fe37b585..dba02a67e8 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1132,12 +1132,6 @@ struct VimMenu { vimmenu_T *next; ///< Next item in menu }; -typedef struct { - int wb_startcol; - int wb_endcol; - vimmenu_T *wb_menu; -} winbar_item_T; - /// Structure which contains all information that belongs to a window. /// /// All row numbers are relative to the start of the window, except w_winrow. @@ -1354,10 +1348,6 @@ struct window_S { char_u *w_localdir; /* absolute path of local directory or NULL */ - vimmenu_T *w_winbar; // The root of the WinBar menu hierarchy. - winbar_item_T *w_winbar_items; // list of items in the WinBar - int w_winbar_height; // 1 if there is a window toolbar - // Options local to a window. // They are local because they influence the layout of the window or // depend on the window layout. diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 03ce2a37b5..e6a616c927 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -43,6 +43,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, colnr_T hl_end = 0; Decoration *decor = decor_hl(hl_id); + decor->priority = DECOR_PRIORITY_BASE; // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { int end_off = 0; @@ -84,6 +85,7 @@ Decoration *decor_hl(int hl_id) Decoration *decor = xcalloc(1, sizeof(*decor)); decor->hl_id = hl_id; decor->shared = true; + decor->priority = DECOR_PRIORITY_BASE; *dp = decor; return decor; } @@ -191,12 +193,12 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) HlRange range; if (mark.id&MARKTREE_END_FLAG) { range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, vt, false }; + attr_id, decor->priority, vt, false }; } else { range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, vt, false }; + altpos.col, attr_id, decor->priority, vt, false }; } - kv_push(state->active, range); + hlrange_activate(range, state); next_mark: if (marktree_itr_node_done(state->itr)) { @@ -218,6 +220,34 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) return true; // TODO(bfredl): be more precise } +static void hlrange_activate(HlRange range, DecorState *state) +{ + // Get size before preparing the push, to have the number of elements + size_t s = kv_size(state->active); + + kv_pushp(state->active); + + size_t dest_index = 0; + + // Determine insertion dest_index + while (dest_index < s) { + HlRange item = kv_A(state->active, dest_index); + if (item.priority > range.priority) { + break; + } + + dest_index++; + } + + // Splice + for (size_t index = s; index > dest_index; index--) { + kv_A(state->active, index) = kv_A(state->active, index-1); + } + + // Insert + kv_A(state->active, dest_index) = range; +} + int decor_redraw_col(buf_T *buf, int col, DecorState *state) { if (col <= state->col_until) { @@ -257,9 +287,10 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state) int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - kv_push(state->active, ((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, vt, false })); + hlrange_activate((HlRange){ mark.row, mark.col, + endpos.row, endpos.col, + attr_id, decor->priority, + vt, false }, state); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -321,10 +352,10 @@ VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) } void decor_add_ephemeral(int attr_id, int start_row, int start_col, - int end_row, int end_col, VirtText *virt_text) + int end_row, int end_col, DecorPriority priority, + VirtText *virt_text) { - kv_push(decor_state.active, - ((HlRange){ start_row, start_col, - end_row, end_col, - attr_id, virt_text, virt_text != NULL })); +hlrange_activate(((HlRange){ start_row, start_col, end_row, end_col, attr_id, + priority, virt_text, virt_text != NULL }), + &decor_state); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 90fdc3dc43..2533a641dd 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -15,11 +15,15 @@ typedef struct { typedef kvec_t(VirtTextChunk) VirtText; #define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) +typedef uint16_t DecorPriority; +#define DECOR_PRIORITY_BASE 0x1000 + struct Decoration { int hl_id; // highlight group VirtText virt_text; // TODO(bfredl): style, signs, etc + DecorPriority priority; bool shared; // shared decoration, don't free }; @@ -29,6 +33,7 @@ typedef struct { int end_row; int end_col; int attr_id; + DecorPriority priority; VirtText *virt_text; bool virt_text_owned; } HlRange; @@ -58,6 +63,7 @@ typedef struct { EXTERN kvec_t(DecorProvider) decor_providers INIT(= KV_INITIAL_VALUE); EXTERN DecorState decor_state INIT(= { 0 }); +EXTERN bool provider_active INIT(= false); #define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ { ns_id, false, LUA_NOREF, LUA_NOREF, \ diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 45d2bf7a91..054b788940 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1178,8 +1178,7 @@ int eval_foldexpr(char_u *arg, int *cp) { typval_T tv; varnumber_T retval; - int use_sandbox = was_set_insecurely((char_u *)"foldexpr", - OPT_LOCAL); + int use_sandbox = was_set_insecurely(curwin, (char_u *)"foldexpr", OPT_LOCAL); ++emsg_off; if (use_sandbox) @@ -1971,7 +1970,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, typval_T var2; int empty1 = FALSE; listitem_T *ni; - hashtab_T *ht; + hashtab_T *ht = NULL; int quiet = flags & GLV_QUIET; // Clear everything in "lp". @@ -2441,7 +2440,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, tv_copy(rettv, lp->ll_tv); } else { *lp->ll_tv = *rettv; - lp->ll_tv->v_lock = 0; + lp->ll_tv->v_lock = VAR_UNLOCKED; tv_init(rettv); } @@ -5458,7 +5457,7 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate, } item = tv_dict_item_alloc((const char *)key); item->di_tv = tv; - item->di_tv.v_lock = 0; + item->di_tv.v_lock = VAR_UNLOCKED; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); } @@ -6128,7 +6127,7 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) if (map) { // map(): replace the list item value. tv_clear(tv); - rettv.v_lock = 0; + rettv.v_lock = VAR_UNLOCKED; *tv = rettv; } else { bool error = false; @@ -6433,7 +6432,7 @@ 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("winbar"), wp->w_winbar_height); + tv_dict_add_nr(dict, S_LEN("winbar"), 0); tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); @@ -9078,7 +9077,7 @@ static void set_var_const(const char *name, const size_t name_len, tv_copy(tv, &v->di_tv); } else { v->di_tv = *tv; - v->di_tv.v_lock = 0; + v->di_tv.v_lock = VAR_UNLOCKED; tv_init(tv); } @@ -9275,7 +9274,7 @@ int var_item_copy(const vimconv_T *const conv, tv_copy(from, to); } else { to->v_type = VAR_STRING; - to->v_lock = 0; + to->v_lock = VAR_UNLOCKED; if ((to->vval.v_string = string_convert((vimconv_T *)conv, from->vval.v_string, NULL)) @@ -9286,7 +9285,7 @@ int var_item_copy(const vimconv_T *const conv, break; case VAR_LIST: to->v_type = VAR_LIST; - to->v_lock = 0; + to->v_lock = VAR_UNLOCKED; if (from->vval.v_list == NULL) { to->vval.v_list = NULL; } else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { @@ -9302,7 +9301,7 @@ int var_item_copy(const vimconv_T *const conv, break; case VAR_DICT: to->v_type = VAR_DICT; - to->v_lock = 0; + to->v_lock = VAR_UNLOCKED; if (from->vval.v_dict == NULL) { to->vval.v_dict = NULL; } else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) { @@ -10466,9 +10465,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, provider_call_nesting++; typval_T argvars[3] = { - {.v_type = VAR_STRING, .vval.v_string = (uint8_t *)method, .v_lock = 0}, - {.v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = 0}, - {.v_type = VAR_UNKNOWN} + { .v_type = VAR_STRING, .vval.v_string = (char_u *)method, + .v_lock = VAR_UNLOCKED }, + { .v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = VAR_UNLOCKED }, + { .v_type = VAR_UNKNOWN } }; typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; tv_list_ref(arguments); diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 6c316bb1fe..9f1994e299 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -385,6 +385,7 @@ return { wildmenumode={}, win_findbuf={args=1}, win_getid={args={0,2}}, + win_gettype={args={0,1}}, win_gotoid={args=1}, win_id2tabwin={args=1}, win_id2win={args=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index eee40965e0..901f20bedf 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2987,11 +2987,12 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) // illegal argument or getchar(0) and no char avail: return zero n = 0; } else { - // getchar(0) and char avail: return char + // getchar(0) and char avail() != NUL: get a character. + // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE. n = safe_vgetc(); } - if (n == K_IGNORE) { + if (n == K_IGNORE || n == K_VER_SCROLLBAR || n == K_HOR_SCROLLBAR) { continue; } break; @@ -10991,6 +10992,31 @@ static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = win_getid(argvars); } +/// "win_gettype(nr)" function +static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = curwin; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_string = vim_strsave((char_u *)"unknown"); + return; + } + } + if (wp == aucmd_win) { + rettv->vval.v_string = vim_strsave((char_u *)"autocmd"); + } else if (wp->w_p_pvw) { + rettv->vval.v_string = vim_strsave((char_u *)"preview"); + } else if (wp->w_floating) { + rettv->vval.v_string = vim_strsave((char_u *)"popup"); + } else if (wp == curwin && cmdwin_type != 0) { + rettv->vval.v_string = vim_strsave((char_u *)"command"); + } +} + /// "win_gotoid()" function static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ada6f78f10..b62820fecc 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -725,7 +725,7 @@ int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv) list_T *l; tv->v_type = VAR_LIST; - + tv->v_lock = VAR_UNLOCKED; if (l1 == NULL && l2 == NULL) { l = NULL; } else if (l1 == NULL) { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index dc94bc698d..8daef00985 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -851,7 +851,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; tv_dict_add(&fc->l_vars, v); v->di_tv.v_type = VAR_DICT; - v->di_tv.v_lock = 0; + v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; ++selfdict->dv_refcount; } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index b8a0c3184b..d2ccbe3e6d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1182,6 +1182,7 @@ static void do_filter( char_u *cmd_buf; buf_T *old_curbuf = curbuf; int shell_flags = 0; + const int stmp = p_stmp; if (*cmd == NUL) /* no filter command */ return; @@ -1210,16 +1211,16 @@ static void do_filter( if (do_out) shell_flags |= kShellOptDoOut; - if (!do_in && do_out && !p_stmp) { + if (!do_in && do_out && !stmp) { // Use a pipe to fetch stdout of the command, do not use a temp file. shell_flags |= kShellOptRead; curwin->w_cursor.lnum = line2; - } else if (do_in && !do_out && !p_stmp) { + } else if (do_in && !do_out && !stmp) { // Use a pipe to write stdin of the command, do not use a temp file. shell_flags |= kShellOptWrite; curbuf->b_op_start.lnum = line1; curbuf->b_op_end.lnum = line2; - } else if (do_in && do_out && !p_stmp) { + } else if (do_in && do_out && !stmp) { // Use a pipe to write stdin and fetch stdout of the command, do not // use a temp file. shell_flags |= kShellOptRead | kShellOptWrite; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 42454b7c9a..6b03117ff3 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2389,513 +2389,6 @@ void ex_compiler(exarg_T *eap) } } -/// ":runtime [what] {name}" -void ex_runtime(exarg_T *eap) -{ - char_u *arg = eap->arg; - char_u *p = skiptowhite(arg); - ptrdiff_t len = p - arg; - int flags = eap->forceit ? DIP_ALL : 0; - - if (STRNCMP(arg, "START", len) == 0) { - flags += DIP_START + DIP_NORTP; - arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "OPT", len) == 0) { - flags += DIP_OPT + DIP_NORTP; - arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "PACK", len) == 0) { - flags += DIP_START + DIP_OPT + DIP_NORTP; - arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "ALL", len) == 0) { - flags += DIP_START + DIP_OPT; - arg = skipwhite(arg + len); - } - - source_runtime(arg, flags); -} - - -static void source_callback(char_u *fname, void *cookie) -{ - (void)do_source(fname, false, DOSO_NONE); -} - -/// Find the file "name" in all directories in "path" and invoke -/// "callback(fname, cookie)". -/// "name" can contain wildcards. -/// When "flags" has DIP_ALL: source all files, otherwise only the first one. -/// When "flags" has DIP_DIR: find directories instead of files. -/// When "flags" has DIP_ERR: give an error message if there is no match. -/// -/// return FAIL when no file could be sourced, OK otherwise. -int do_in_path(char_u *path, char_u *name, int flags, - DoInRuntimepathCB callback, void *cookie) -{ - char_u *tail; - int num_files; - char_u **files; - int i; - bool did_one = false; - - // Make a copy of 'runtimepath'. Invoking the callback may change the - // value. - char_u *rtp_copy = vim_strsave(path); - char_u *buf = xmallocz(MAXPATHL); - { - if (p_verbose > 10 && name != NULL) { - verbose_enter(); - smsg(_("Searching for \"%s\" in \"%s\""), - (char *)name, (char *)path); - verbose_leave(); - } - - // Loop over all entries in 'runtimepath'. - char_u *rtp = rtp_copy; - while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { - // Copy the path from 'runtimepath' to buf[]. - copy_option_part(&rtp, buf, MAXPATHL, ","); - size_t buflen = STRLEN(buf); - - // Skip after or non-after directories. - if (flags & (DIP_NOAFTER | DIP_AFTER)) { - bool is_after = buflen >= 5 - && STRCMP(buf + buflen - 5, "after") == 0; - - if ((is_after && (flags & DIP_NOAFTER)) - || (!is_after && (flags & DIP_AFTER))) { - continue; - } - } - - if (name == NULL) { - (*callback)(buf, (void *)&cookie); - if (!did_one) { - did_one = (cookie == NULL); - } - } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { - add_pathsep((char *)buf); - tail = buf + STRLEN(buf); - - // Loop over all patterns in "name" - char_u *np = name; - while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { - // Append the pattern from "name" to buf[]. - assert(MAXPATHL >= (tail - buf)); - copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), - "\t "); - - if (p_verbose > 10) { - verbose_enter(); - smsg(_("Searching for \"%s\""), buf); - verbose_leave(); - } - - // Expand wildcards, invoke the callback for each match. - if (gen_expand_wildcards(1, &buf, &num_files, &files, - (flags & DIP_DIR) ? EW_DIR - : EW_FILE) == OK) { - for (i = 0; i < num_files; i++) { - (*callback)(files[i], cookie); - did_one = true; - if (!(flags & DIP_ALL)) { - break; - } - } - FreeWild(num_files, files); - } - } - } - } - } - xfree(buf); - xfree(rtp_copy); - if (!did_one && name != NULL) { - char *basepath = path == p_rtp ? "runtimepath" : "packpath"; - - if (flags & DIP_ERR) { - EMSG3(_(e_dirnotf), basepath, name); - } else if (p_verbose > 0) { - verbose_enter(); - smsg(_("not found in '%s': \"%s\""), basepath, name); - verbose_leave(); - } - } - - - return did_one ? OK : FAIL; -} - -/// Find "name" in "path". When found, invoke the callback function for -/// it: callback(fname, "cookie") -/// When "flags" has DIP_ALL repeat for all matches, otherwise only the first -/// one is used. -/// Returns OK when at least one match found, FAIL otherwise. -/// If "name" is NULL calls callback for each entry in "path". Cookie is -/// passed by reference in this case, setting it to NULL indicates that callback -/// has done its job. -int do_in_path_and_pp(char_u *path, char_u *name, int flags, - DoInRuntimepathCB callback, void *cookie) -{ - int done = FAIL; - - if ((flags & DIP_NORTP) == 0) { - done = do_in_path(path, name, flags, callback, cookie); - } - - if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { - char *start_dir = "pack/*/start/*/%s"; // NOLINT - size_t len = STRLEN(start_dir) + STRLEN(name); - char_u *s = xmallocz(len); - - vim_snprintf((char *)s, len, start_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - - if (done == FAIL|| (flags & DIP_ALL)) { - start_dir = "start/*/%s"; // NOLINT - len = STRLEN(start_dir) + STRLEN(name); - s = xmallocz(len); - - vim_snprintf((char *)s, len, start_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - } - } - - if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) { - char *opt_dir = "pack/*/opt/*/%s"; // NOLINT - size_t len = STRLEN(opt_dir) + STRLEN(name); - char_u *s = xmallocz(len); - - vim_snprintf((char *)s, len, opt_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - - if (done == FAIL || (flags & DIP_ALL)) { - opt_dir = "opt/*/%s"; // NOLINT - len = STRLEN(opt_dir) + STRLEN(name); - s = xmallocz(len); - - vim_snprintf((char *)s, len, opt_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - } - } - - return done; -} - -/// Just like do_in_path_and_pp(), using 'runtimepath' for "path". -int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) -{ - return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); -} - -/// Source the file "name" from all directories in 'runtimepath'. -/// "name" can contain wildcards. -/// When "flags" has DIP_ALL: source all files, otherwise only the first one. -/// -/// return FAIL when no file could be sourced, OK otherwise. -int source_runtime(char_u *name, int flags) -{ - flags |= (flags & DIP_NORTP) ? 0 : DIP_START; - return source_in_path(p_rtp, name, flags); -} - -/// Just like source_runtime(), but use "path" instead of 'runtimepath'. -int source_in_path(char_u *path, char_u *name, int flags) -{ - return do_in_path_and_pp(path, name, flags, source_callback, NULL); -} - -// Expand wildcards in "pat" and invoke do_source() for each match. -static void source_all_matches(char_u *pat) -{ - int num_files; - char_u **files; - - if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { - for (int i = 0; i < num_files; i++) { - (void)do_source(files[i], false, DOSO_NONE); - } - FreeWild(num_files, files); - } -} - -/// Add the package directory to 'runtimepath' -static int add_pack_dir_to_rtp(char_u *fname) -{ - char_u *p4, *p3, *p2, *p1, *p; - char_u *buf = NULL; - char *afterdir = NULL; - int retval = FAIL; - - p4 = p3 = p2 = p1 = get_past_head(fname); - for (p = p1; *p; MB_PTR_ADV(p)) { - if (vim_ispathsep_nocolon(*p)) { - p4 = p3; p3 = p2; p2 = p1; p1 = p; - } - } - - // now we have: - // rtp/pack/name/start/name - // p4 p3 p2 p1 - // - // find the part up to "pack" in 'runtimepath' - p4++; // append pathsep in order to expand symlink - char_u c = *p4; - *p4 = NUL; - char *const ffname = fix_fname((char *)fname); - *p4 = c; - - if (ffname == NULL) { - return FAIL; - } - - // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences - // Also stop at the first "after" directory - size_t fname_len = strlen(ffname); - buf = try_malloc(MAXPATHL); - if (buf == NULL) { - goto theend; - } - const char *insp = NULL; - const char *after_insp = NULL; - for (const char *entry = (const char *)p_rtp; *entry != NUL; ) { - const char *cur_entry = entry; - - copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); - if (insp == NULL) { - add_pathsep((char *)buf); - char *const rtp_ffname = fix_fname((char *)buf); - if (rtp_ffname == NULL) { - goto theend; - } - bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0; - xfree(rtp_ffname); - if (match) { - // Insert "ffname" after this entry (and comma). - insp = entry; - } - } - - if ((p = (char_u *)strstr((char *)buf, "after")) != NULL - && p > buf - && vim_ispathsep(p[-1]) - && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { - if (insp == NULL) { - // Did not find "ffname" before the first "after" directory, - // insert it before this entry. - insp = cur_entry; - } - after_insp = cur_entry; - break; - } - } - - if (insp == NULL) { - // Both "fname" and "after" not found, append at the end. - insp = (const char *)p_rtp + STRLEN(p_rtp); - } - - // check if rtp/pack/name/start/name/after exists - afterdir = concat_fnames((char *)fname, "after", true); - size_t afterlen = 0; - if (os_isdir((char_u *)afterdir)) { - afterlen = strlen(afterdir) + 1; // add one for comma - } - - const size_t oldlen = STRLEN(p_rtp); - const size_t addlen = STRLEN(fname) + 1; // add one for comma - const size_t new_rtp_capacity = oldlen + addlen + afterlen + 1; - // add one for NUL ------------------------------------------^ - char *const new_rtp = try_malloc(new_rtp_capacity); - if (new_rtp == NULL) { - goto theend; - } - - // We now have 'rtp' parts: {keep}{keep_after}{rest}. - // Create new_rtp, first: {keep},{fname} - size_t keep = (size_t)(insp - (const char *)p_rtp); - memmove(new_rtp, p_rtp, keep); - size_t new_rtp_len = keep; - if (*insp == NUL) { - new_rtp[new_rtp_len++] = ','; // add comma before - } - memmove(new_rtp + new_rtp_len, fname, addlen - 1); - new_rtp_len += addlen - 1; - if (*insp != NUL) { - new_rtp[new_rtp_len++] = ','; // add comma after - } - - if (afterlen > 0 && after_insp != NULL) { - size_t keep_after = (size_t)(after_insp - (const char *)p_rtp); - - // Add to new_rtp: {keep},{fname}{keep_after},{afterdir} - memmove(new_rtp + new_rtp_len, p_rtp + keep, keep_after - keep); - new_rtp_len += keep_after - keep; - memmove(new_rtp + new_rtp_len, afterdir, afterlen - 1); - new_rtp_len += afterlen - 1; - new_rtp[new_rtp_len++] = ','; - keep = keep_after; - } - - if (p_rtp[keep] != NUL) { - // Append rest: {keep},{fname}{keep_after},{afterdir}{rest} - memmove(new_rtp + new_rtp_len, p_rtp + keep, oldlen - keep + 1); - } else { - new_rtp[new_rtp_len] = NUL; - } - - if (afterlen > 0 && after_insp == NULL) { - // Append afterdir when "after" was not found: - // {keep},{fname}{rest},{afterdir} - xstrlcat(new_rtp, ",", new_rtp_capacity); - xstrlcat(new_rtp, afterdir, new_rtp_capacity); - } - - set_option_value("rtp", 0L, new_rtp, 0); - xfree(new_rtp); - retval = OK; - -theend: - xfree(buf); - xfree(ffname); - xfree(afterdir); - return retval; -} - -/// Load scripts in "plugin" and "ftdetect" directories of the package. -static int load_pack_plugin(char_u *fname) -{ - static const char *plugpat = "%s/plugin/**/*.vim"; // NOLINT - static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT - - int retval = FAIL; - char *const ffname = fix_fname((char *)fname); - size_t len = strlen(ffname) + STRLEN(ftpat); - char_u *pat = try_malloc(len + 1); - if (pat == NULL) { - goto theend; - } - vim_snprintf((char *)pat, len, plugpat, ffname); - source_all_matches(pat); - - char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); - - // If runtime/filetype.vim wasn't loaded yet, the scripts will be - // found when it loads. - if (eval_to_number(cmd) > 0) { - do_cmdline_cmd("augroup filetypedetect"); - vim_snprintf((char *)pat, len, ftpat, ffname); - source_all_matches(pat); - do_cmdline_cmd("augroup END"); - } - xfree(cmd); - xfree(pat); - retval = OK; - -theend: - xfree(ffname); - - return retval; -} - -// used for "cookie" of add_pack_plugin() -static int APP_ADD_DIR; -static int APP_LOAD; -static int APP_BOTH; - -static void add_pack_plugin(char_u *fname, void *cookie) -{ - if (cookie != &APP_LOAD) { - char *buf = xmalloc(MAXPATHL); - bool found = false; - - const char *p = (const char *)p_rtp; - while (*p != NUL) { - copy_option_part((char_u **)&p, (char_u *)buf, MAXPATHL, ","); - if (path_fnamecmp(buf, (char *)fname) == 0) { - found = true; - break; - } - } - xfree(buf); - if (!found) { - // directory is not yet in 'runtimepath', add it - if (add_pack_dir_to_rtp(fname) == FAIL) { - return; - } - } - } - - if (cookie != &APP_ADD_DIR) { - load_pack_plugin(fname); - } -} - -/// Add all packages in the "start" directory to 'runtimepath'. -void add_pack_start_dirs(void) -{ - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); - do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); -} - -/// Load plugins from all packages in the "start" directory. -void load_start_packages(void) -{ - did_source_packages = true; - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); - do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); -} - -// ":packloadall" -// Find plugins in the package directories and source them. -void ex_packloadall(exarg_T *eap) -{ - if (!did_source_packages || eap->forceit) { - // First do a round to add all directories to 'runtimepath', then load - // the plugins. This allows for plugins to use an autoload directory - // of another plugin. - add_pack_start_dirs(); - load_start_packages(); - } -} - -/// ":packadd[!] {name}" -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 = STRLEN(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, (char_u *)pat, - DIP_ALL + DIP_DIR - + (round == 2 && res == FAIL ? DIP_ERR : 0), - add_pack_plugin, eap->forceit ? &APP_ADD_DIR : &APP_BOTH); - xfree(pat); - } -} /// ":options" void ex_options(exarg_T *eap) @@ -4232,25 +3725,29 @@ static void script_host_execute(char *name, exarg_T *eap) static void script_host_execute_file(char *name, exarg_T *eap) { - uint8_t buffer[MAXPATHL]; - vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); - - list_T *args = tv_list_alloc(3); - // filename - tv_list_append_string(args, (const char *)buffer, -1); - // current range - tv_list_append_number(args, (int)eap->line1); - tv_list_append_number(args, (int)eap->line2); - (void)eval_call_provider(name, "execute_file", args, true); + if (!eap->skip) { + uint8_t buffer[MAXPATHL]; + vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); + + list_T *args = tv_list_alloc(3); + // filename + tv_list_append_string(args, (const char *)buffer, -1); + // current range + tv_list_append_number(args, (int)eap->line1); + tv_list_append_number(args, (int)eap->line2); + (void)eval_call_provider(name, "execute_file", args, true); + } } static void script_host_do_range(char *name, exarg_T *eap) { - list_T *args = tv_list_alloc(3); - tv_list_append_number(args, (int)eap->line1); - tv_list_append_number(args, (int)eap->line2); - tv_list_append_string(args, (const char *)eap->arg, -1); - (void)eval_call_provider(name, "do_range", args, true); + if (!eap->skip) { + list_T *args = tv_list_alloc(3); + tv_list_append_number(args, (int)eap->line1); + tv_list_append_number(args, (int)eap->line2); + tv_list_append_string(args, (const char *)eap->arg, -1); + (void)eval_call_provider(name, "do_range", args, true); + } } /// ":drop" diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index f85ea94ed6..de4e1429b7 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -4,8 +4,8 @@ #include <stdbool.h> #include "nvim/ex_docmd.h" +#include "nvim/runtime.h" -typedef void (*DoInRuntimepathCB)(char_u *, void *); // // flags for check_changed() @@ -16,10 +16,6 @@ typedef void (*DoInRuntimepathCB)(char_u *, void *); #define CCGD_ALLBUF 8 // may write all buffers #define CCGD_EXCMD 16 // may suggest using ! -// last argument for do_source() -#define DOSO_NONE 0 -#define DOSO_VIMRC 1 // loading vimrc file - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.h.generated.h" #endif diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index fc699e8826..d65387f83b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1873,8 +1873,6 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_python3: case CMD_pythonx: case CMD_pyx: - case CMD_pyxdo: - case CMD_pyxfile: case CMD_return: case CMD_rightbelow: case CMD_ruby: diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 6ee3a3f579..ab8072ae1d 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -67,85 +67,6 @@ #define UV_FS_COPYFILE_FICLONE 0 #endif -// -// The autocommands are stored in a list for each event. -// Autocommands for the same pattern, that are consecutive, are joined -// together, to avoid having to match the pattern too often. -// The result is an array of Autopat lists, which point to AutoCmd lists: -// -// last_autopat[0] -----------------------------+ -// V -// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL -// Autopat.cmds Autopat.cmds -// | | -// V V -// AutoCmd.next AutoCmd.next -// | | -// V V -// AutoCmd.next NULL -// | -// V -// NULL -// -// last_autopat[1] --------+ -// V -// first_autopat[1] --> Autopat.next --> NULL -// Autopat.cmds -// | -// V -// AutoCmd.next -// | -// V -// NULL -// etc. -// -// The order of AutoCmds is important, this is the order in which they were -// defined and will have to be executed. -// -typedef struct AutoCmd { - char_u *cmd; // Command to be executed (NULL when - // command has been removed) - bool once; // "One shot": removed after execution - char nested; // If autocommands nest here - char last; // last command in list - sctx_T script_ctx; // script context where defined - struct AutoCmd *next; // Next AutoCmd in list -} AutoCmd; - -typedef struct AutoPat { - struct AutoPat *next; // next AutoPat in AutoPat list; MUST - // be the first entry - char_u *pat; // pattern as typed (NULL when pattern - // has been removed) - regprog_T *reg_prog; // compiled regprog for pattern - AutoCmd *cmds; // list of commands to do - int group; // group ID - int patlen; // strlen() of pat - int buflocal_nr; // !=0 for buffer-local AutoPat - char allow_dirs; // Pattern may match whole path - char last; // last pattern for apply_autocmds() -} AutoPat; - -/// -/// Struct used to keep status while executing autocommands for an event. -/// -typedef struct AutoPatCmd { - AutoPat *curpat; // next AutoPat to examine - AutoCmd *nextcmd; // next AutoCmd to execute - int group; // group being used - char_u *fname; // fname to match with - char_u *sfname; // sfname to match with - char_u *tail; // tail of fname - event_T event; // current event - int arg_bufnr; // initially equal to <abuf>, set to zero when - // buf is deleted - struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation -} AutoPatCmd; - -#define AUGROUP_DEFAULT -1 /* default autocmd group */ -#define AUGROUP_ERROR -2 /* erroneous autocmd group */ -#define AUGROUP_ALL -3 /* all autocmd groups */ - #define HAS_BW_FLAGS #define FIO_LATIN1 0x01 /* convert Latin1 */ #define FIO_UTF8 0x02 /* convert UTF-8 */ @@ -195,14 +116,6 @@ struct bw_info { static char *e_auchangedbuf = N_( "E812: Autocommands changed buffer or buffer name"); -// Set by the apply_autocmds_group function if the given event is equal to -// EVENT_FILETYPE. Used by the readfile function in order to determine if -// EVENT_BUFREADPOST triggered the EVENT_FILETYPE. -// -// Relying on this value requires one to reset it prior calling -// apply_autocmds_group. -static bool au_did_filetype INIT(= false); - void filemess(buf_T *buf, char_u *name, char_u *s, int attr) { int msg_scroll_save; @@ -231,14 +144,6 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } -static AutoPat *last_autopat[NUM_EVENTS] = { - 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, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; /* * Read lines from file "fname" into the buffer after line "from". @@ -5404,2054 +5309,6 @@ char_u *vim_tempname(void) } -/* - * Code for automatic commands. - */ -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "auevents_name_map.generated.h" -#endif - -static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ - -/// List of autocmd group names -static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL }; -#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i]) - -/* - * The ID of the current group. Group 0 is the default one. - */ -static int current_augroup = AUGROUP_DEFAULT; - -static int au_need_clean = FALSE; /* need to delete marked patterns */ - - - -static event_T last_event; -static int last_group; -static int autocmd_blocked = 0; /* block all autocmds */ - -// use get_deleted_augroup() to get this -static const char *deleted_augroup = NULL; - -static inline const char *get_deleted_augroup(void) - FUNC_ATTR_ALWAYS_INLINE -{ - if (deleted_augroup == NULL) { - deleted_augroup = _("--Deleted--"); - } - return deleted_augroup; -} - -/* - * Show the autocommands for one AutoPat. - */ -static void show_autocmd(AutoPat *ap, event_T event) -{ - AutoCmd *ac; - - /* Check for "got_int" (here and at various places below), which is set - * when "q" has been hit for the "--more--" prompt */ - if (got_int) - return; - if (ap->pat == NULL) /* pattern has been removed */ - return; - - msg_putchar('\n'); - if (got_int) - return; - if (event != last_event || ap->group != last_group) { - if (ap->group != AUGROUP_DEFAULT) { - if (AUGROUP_NAME(ap->group) == NULL) { - msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); - } else { - msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); - } - msg_puts(" "); - } - msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); - last_event = event; - last_group = ap->group; - msg_putchar('\n'); - if (got_int) - return; - } - msg_col = 4; - msg_outtrans(ap->pat); - - for (ac = ap->cmds; ac != NULL; ac = ac->next) { - if (ac->cmd == NULL) { /* skip removed commands */ - continue; - } - if (msg_col >= 14) { - msg_putchar('\n'); - } - msg_col = 14; - if (got_int) { - return; - } - msg_outtrans(ac->cmd); - if (p_verbose > 0) { - last_set_msg(ac->script_ctx); - } - if (got_int) { - return; - } - if (ac->next != NULL) { - msg_putchar('\n'); - if (got_int) { - return; - } - } - } -} - -// Mark an autocommand handler for deletion. -static void au_remove_pat(AutoPat *ap) -{ - XFREE_CLEAR(ap->pat); - ap->buflocal_nr = -1; - au_need_clean = true; -} - -// Mark all commands for a pattern for deletion. -static void au_remove_cmds(AutoPat *ap) -{ - for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { - XFREE_CLEAR(ac->cmd); - } - au_need_clean = true; -} - -// Delete one command from an autocmd pattern. -static void au_del_cmd(AutoCmd *ac) -{ - XFREE_CLEAR(ac->cmd); - au_need_clean = true; -} - -/// Cleanup autocommands and patterns that have been deleted. -/// This is only done when not executing autocommands. -static void au_cleanup(void) -{ - AutoPat *ap, **prev_ap; - event_T event; - - if (autocmd_busy || !au_need_clean) { - return; - } - - // Loop over all events. - for (event = (event_T)0; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)event + 1)) { - // Loop over all autocommand patterns. - prev_ap = &(first_autopat[(int)event]); - for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { - bool has_cmd = false; - - // Loop over all commands for this pattern. - AutoCmd **prev_ac = &(ap->cmds); - for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) { - // Remove the command if the pattern is to be deleted or when - // the command has been marked for deletion. - if (ap->pat == NULL || ac->cmd == NULL) { - *prev_ac = ac->next; - xfree(ac->cmd); - xfree(ac); - } else { - has_cmd = true; - prev_ac = &(ac->next); - } - } - - if (ap->pat != NULL && !has_cmd) { - // Pattern was not marked for deletion, but all of its commands were. - // So mark the pattern for deletion. - au_remove_pat(ap); - } - - // Remove the pattern if it has been marked for deletion. - if (ap->pat == NULL) { - if (ap->next == NULL) { - if (prev_ap == &(first_autopat[(int)event])) { - last_autopat[(int)event] = NULL; - } else { - // this depends on the "next" field being the first in - // the struct - last_autopat[(int)event] = (AutoPat *)prev_ap; - } - } - *prev_ap = ap->next; - vim_regfree(ap->reg_prog); - xfree(ap); - } else { - prev_ap = &(ap->next); - } - } - } - - au_need_clean = false; -} - -/* - * Called when buffer is freed, to remove/invalidate related buffer-local - * autocmds. - */ -void aubuflocal_remove(buf_T *buf) -{ - AutoPat *ap; - event_T event; - AutoPatCmd *apc; - - /* invalidate currently executing autocommands */ - for (apc = active_apc_list; apc; apc = apc->next) - if (buf->b_fnum == apc->arg_bufnr) - apc->arg_bufnr = 0; - - /* invalidate buflocals looping through events */ - for (event = (event_T)0; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)event + 1)) - /* loop over all autocommand patterns */ - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) - if (ap->buflocal_nr == buf->b_fnum) { - au_remove_pat(ap); - if (p_verbose >= 6) { - verbose_enter(); - smsg(_("auto-removing autocommand: %s <buffer=%d>"), - event_nr2name(event), buf->b_fnum); - verbose_leave(); - } - } - au_cleanup(); -} - -// Add an autocmd group name. -// Return its ID. Returns AUGROUP_ERROR (< 0) for error. -static int au_new_group(char_u *name) -{ - int i = au_find_group(name); - if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it. - // First try using a free entry. - for (i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) == NULL) { - break; - } - } - if (i == augroups.ga_len) { - ga_grow(&augroups, 1); - } - - AUGROUP_NAME(i) = xstrdup((char *)name); - if (i == augroups.ga_len) { - augroups.ga_len++; - } - } - - return i; -} - -static void au_del_group(char_u *name) -{ - int i = au_find_group(name); - if (i == AUGROUP_ERROR) { // the group doesn't exist - EMSG2(_("E367: No such group: \"%s\""), name); - } else if (i == current_augroup) { - EMSG(_("E936: Cannot delete the current group")); - } else { - event_T event; - AutoPat *ap; - int in_use = false; - - for (event = (event_T)0; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)event + 1)) { - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { - if (ap->group == i && ap->pat != NULL) { - give_warning((char_u *) - _("W19: Deleting augroup that is still in use"), true); - in_use = true; - event = NUM_EVENTS; - break; - } - } - } - xfree(AUGROUP_NAME(i)); - if (in_use) { - AUGROUP_NAME(i) = (char *)get_deleted_augroup(); - } else { - AUGROUP_NAME(i) = NULL; - } - } -} - -/// Find the ID of an autocmd group name. -/// -/// @param name augroup name -/// -/// @return the ID or AUGROUP_ERROR (< 0) for error. -static int au_find_group(const char_u *name) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - for (int i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup() - && STRCMP(AUGROUP_NAME(i), name) == 0) { - return i; - } - } - return AUGROUP_ERROR; -} - -/// Return true if augroup "name" exists. -/// -/// @param name augroup name -bool au_has_group(const char_u *name) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return au_find_group(name) != AUGROUP_ERROR; -} - -/// ":augroup {name}". -void do_augroup(char_u *arg, int del_group) -{ - if (del_group) { - if (*arg == NUL) { - EMSG(_(e_argreq)); - } else { - au_del_group(arg); - } - } else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0 - current_augroup = AUGROUP_DEFAULT; - } else if (*arg) { // ":aug xxx": switch to group xxx - int i = au_new_group(arg); - if (i != AUGROUP_ERROR) - current_augroup = i; - } else { // ":aug": list the group names - msg_start(); - for (int i = 0; i < augroups.ga_len; ++i) { - if (AUGROUP_NAME(i) != NULL) { - msg_puts(AUGROUP_NAME(i)); - msg_puts(" "); - } - } - msg_clr_eos(); - msg_end(); - } -} - -#if defined(EXITFREE) -void free_all_autocmds(void) -{ - for (current_augroup = -1; current_augroup < augroups.ga_len; - current_augroup++) { - do_autocmd((char_u *)"", true); - } - - for (int i = 0; i < augroups.ga_len; i++) { - char *const s = ((char **)(augroups.ga_data))[i]; - if ((const char *)s != get_deleted_augroup()) { - xfree(s); - } - } - ga_clear(&augroups); -} -#endif - -/* - * Return the event number for event name "start". - * Return NUM_EVENTS if the event name was not found. - * Return a pointer to the next event name in "end". - */ -static event_T event_name2nr(const char_u *start, char_u **end) -{ - const char_u *p; - int i; - int len; - - // the event name ends with end of line, '|', a blank or a comma - for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) { - } - for (i = 0; event_names[i].name != NULL; i++) { - len = (int)event_names[i].len; - if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) { - break; - } - } - if (*p == ',') { - p++; - } - *end = (char_u *)p; - if (event_names[i].name == NULL) { - return NUM_EVENTS; - } - return event_names[i].event; -} - -/// Return the name for event -/// -/// @param[in] event Event to return name for. -/// -/// @return Event name, static string. Returns "Unknown" for unknown events. -static const char *event_nr2name(event_T event) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST -{ - int i; - - for (i = 0; event_names[i].name != NULL; i++) { - if (event_names[i].event == event) { - return event_names[i].name; - } - } - return "Unknown"; -} - -/* - * Scan over the events. "*" stands for all events. - */ -static char_u * -find_end_event ( - char_u *arg, - int have_group /* TRUE when group name was found */ -) -{ - char_u *pat; - char_u *p; - - if (*arg == '*') { - if (arg[1] && !ascii_iswhite(arg[1])) { - EMSG2(_("E215: Illegal character after *: %s"), arg); - return NULL; - } - pat = arg + 1; - } else { - for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { - if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) { - if (have_group) - EMSG2(_("E216: No such event: %s"), pat); - else - EMSG2(_("E216: No such group or event: %s"), pat); - return NULL; - } - } - } - return pat; -} - -/// Return true if "event" is included in 'eventignore'. -/// -/// @param event event to check -static bool event_ignored(event_T event) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - char_u *p = p_ei; - - while (*p != NUL) { - if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { - return true; - } - if (event_name2nr(p, &p) == event) { - return true; - } - } - - return false; -} - -/* - * Return OK when the contents of p_ei is valid, FAIL otherwise. - */ -int check_ei(void) -{ - char_u *p = p_ei; - - while (*p) { - if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { - p += 3; - if (*p == ',') - ++p; - } else if (event_name2nr(p, &p) == NUM_EVENTS) - return FAIL; - } - - return OK; -} - -/* - * Add "what" to 'eventignore' to skip loading syntax highlighting for every - * buffer loaded into the window. "what" must start with a comma. - * Returns the old value of 'eventignore' in allocated memory. - */ -char_u *au_event_disable(char *what) -{ - char_u *new_ei; - char_u *save_ei; - - save_ei = vim_strsave(p_ei); - new_ei = vim_strnsave(p_ei, (int)(STRLEN(p_ei) + STRLEN(what))); - if (*what == ',' && *p_ei == NUL) - STRCPY(new_ei, what + 1); - else - STRCAT(new_ei, what); - set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE); - xfree(new_ei); - - return save_ei; -} - -void au_event_restore(char_u *old_ei) -{ - if (old_ei != NULL) { - set_string_option_direct((char_u *)"ei", -1, old_ei, - OPT_FREE, SID_NONE); - xfree(old_ei); - } -} - -// Implements :autocmd. -// Defines an autocmd (does not execute; cf. apply_autocmds_group). -// -// Can be used in the following ways: -// -// :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that -// will be automatically executed for <event> -// when editing a file matching <pat>, in -// the current group. -// :autocmd <event> <pat> Show the autocommands associated with -// <event> and <pat>. -// :autocmd <event> Show the autocommands associated with -// <event>. -// :autocmd Show all autocommands. -// :autocmd! <event> <pat> <cmd> Remove all autocommands associated with -// <event> and <pat>, and add the command -// <cmd>, for the current group. -// :autocmd! <event> <pat> Remove all autocommands associated with -// <event> and <pat> for the current group. -// :autocmd! <event> Remove all autocommands associated with -// <event> for the current group. -// :autocmd! Remove ALL autocommands for the current -// group. -// -// Multiple events and patterns may be given separated by commas. Here are -// some examples: -// :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic -// :autocmd bufleave * set tw=79 nosmartindent ic infercase -// -// :autocmd * *.c show all autocommands for *.c files. -// -// Mostly a {group} argument can optionally appear before <event>. -void do_autocmd(char_u *arg_in, int forceit) -{ - char_u *arg = arg_in; - char_u *pat; - char_u *envpat = NULL; - char_u *cmd; - int need_free = false; - int nested = false; - bool once = false; - int group; - - if (*arg == '|') { - arg = (char_u *)""; - group = AUGROUP_ALL; // no argument, use all groups - } else { - // Check for a legal group name. If not, use AUGROUP_ALL. - group = au_get_grouparg(&arg); - } - - /* - * Scan over the events. - * If we find an illegal name, return here, don't do anything. - */ - pat = find_end_event(arg, group != AUGROUP_ALL); - if (pat == NULL) - return; - - pat = skipwhite(pat); - if (*pat == '|') { - pat = (char_u *)""; - cmd = (char_u *)""; - } else { - // Scan over the pattern. Put a NUL at the end. - cmd = pat; - while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) { - cmd++; - } - if (*cmd) { - *cmd++ = NUL; - } - - // Expand environment variables in the pattern. Set 'shellslash', we want - // forward slashes here. - if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) { -#ifdef BACKSLASH_IN_FILENAME - int p_ssl_save = p_ssl; - - p_ssl = true; -#endif - envpat = expand_env_save(pat); -#ifdef BACKSLASH_IN_FILENAME - p_ssl = p_ssl_save; -#endif - if (envpat != NULL) { - pat = envpat; - } - } - - cmd = skipwhite(cmd); - for (size_t i = 0; i < 2; i++) { - if (*cmd != NUL) { - // Check for "++once" flag. - if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) { - if (once) { - EMSG2(_(e_duparg2), "++once"); - } - once = true; - cmd = skipwhite(cmd + 6); - } - - // Check for "++nested" flag. - if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) { - if (nested) { - EMSG2(_(e_duparg2), "++nested"); - } - nested = true; - cmd = skipwhite(cmd + 8); - } - - // Check for the old (deprecated) "nested" flag. - if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) { - if (nested) { - EMSG2(_(e_duparg2), "nested"); - } - nested = true; - cmd = skipwhite(cmd + 6); - } - } - } - - // Find the start of the commands. - // Expand <sfile> in it. - if (*cmd != NUL) { - cmd = expand_sfile(cmd); - if (cmd == NULL) { // some error - return; - } - need_free = true; - } - } - - /* - * Print header when showing autocommands. - */ - if (!forceit && *cmd == NUL) { - // Highlight title - MSG_PUTS_TITLE(_("\n--- Autocommands ---")); - } - - /* - * Loop over the events. - */ - last_event = (event_T)-1; // for listing the event name - last_group = AUGROUP_ERROR; // for listing the group name - if (*arg == '*' || *arg == NUL || *arg == '|') { - for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)event + 1)) { - if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) - == FAIL) { - break; - } - } - } else { - while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { - event_T event = event_name2nr(arg, &arg); - assert(event < NUM_EVENTS); - if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) - == FAIL) { - break; - } - } - } - - if (need_free) - xfree(cmd); - xfree(envpat); -} - -/* - * Find the group ID in a ":autocmd" or ":doautocmd" argument. - * The "argp" argument is advanced to the following argument. - * - * Returns the group ID or AUGROUP_ALL. - */ -static int au_get_grouparg(char_u **argp) -{ - char_u *group_name; - char_u *p; - char_u *arg = *argp; - int group = AUGROUP_ALL; - - for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { - } - if (p > arg) { - group_name = vim_strnsave(arg, (int)(p - arg)); - group = au_find_group(group_name); - if (group == AUGROUP_ERROR) - group = AUGROUP_ALL; /* no match, use all groups */ - else - *argp = skipwhite(p); /* match, skip over group name */ - xfree(group_name); - } - return group; -} - -// do_autocmd() for one event. -// Defines an autocmd (does not execute; cf. apply_autocmds_group). -// -// If *pat == NUL: do for all patterns. -// If *cmd == NUL: show entries. -// If forceit == TRUE: delete entries. -// If group is not AUGROUP_ALL: only use this group. -static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, - char_u *cmd, int forceit, int group) -{ - AutoPat *ap; - AutoPat **prev_ap; - AutoCmd *ac; - AutoCmd **prev_ac; - int brace_level; - char_u *endpat; - int findgroup; - int allgroups; - int patlen; - int is_buflocal; - int buflocal_nr; - char_u buflocal_pat[25]; /* for "<buffer=X>" */ - - if (group == AUGROUP_ALL) - findgroup = current_augroup; - else - findgroup = group; - allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL); - - /* - * Show or delete all patterns for an event. - */ - if (*pat == NUL) { - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { - if (forceit) { /* delete the AutoPat, if it's in the current group */ - if (ap->group == findgroup) - au_remove_pat(ap); - } else if (group == AUGROUP_ALL || ap->group == group) - show_autocmd(ap, event); - } - } - - /* - * Loop through all the specified patterns. - */ - for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { - /* - * Find end of the pattern. - * Watch out for a comma in braces, like "*.\{obj,o\}". - */ - endpat = pat; - // ignore single comma - if (*endpat == ',') { - continue; - } - brace_level = 0; - for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); - ++endpat) { - if (*endpat == '{') - brace_level++; - else if (*endpat == '}') - brace_level--; - } - patlen = (int)(endpat - pat); - - /* - * detect special <buflocal[=X]> buffer-local patterns - */ - is_buflocal = FALSE; - buflocal_nr = 0; - - if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0 - && pat[patlen - 1] == '>') { - /* "<buffer...>": Error will be printed only for addition. - * printing and removing will proceed silently. */ - is_buflocal = TRUE; - if (patlen == 8) - /* "<buffer>" */ - buflocal_nr = curbuf->b_fnum; - else if (patlen > 9 && pat[7] == '=') { - if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) - /* "<buffer=abuf>" */ - buflocal_nr = autocmd_bufnr; - else if (skipdigits(pat + 8) == pat + patlen - 1) - /* "<buffer=123>" */ - buflocal_nr = atoi((char *)pat + 8); - } - } - - if (is_buflocal) { - /* normalize pat into standard "<buffer>#N" form */ - sprintf((char *)buflocal_pat, "<buffer=%d>", buflocal_nr); - pat = buflocal_pat; /* can modify pat and patlen */ - patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ - } - - // Find AutoPat entries with this pattern. When adding a command it - // always goes at or after the last one, so start at the end. - if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { - prev_ap = &last_autopat[(int)event]; - } else { - prev_ap = &first_autopat[(int)event]; - } - while ((ap = *prev_ap) != NULL) { - if (ap->pat != NULL) { - /* Accept a pattern when: - * - a group was specified and it's that group, or a group was - * not specified and it's the current group, or a group was - * not specified and we are listing - * - the length of the pattern matches - * - the pattern matches. - * For <buffer[=X]>, this condition works because we normalize - * all buffer-local patterns. - */ - if ((allgroups || ap->group == findgroup) - && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { - /* - * Remove existing autocommands. - * If adding any new autocmd's for this AutoPat, don't - * delete the pattern from the autopat list, append to - * this list. - */ - if (forceit) { - if (*cmd != NUL && ap->next == NULL) { - au_remove_cmds(ap); - break; - } - au_remove_pat(ap); - } - /* - * Show autocmd's for this autopat, or buflocals <buffer=X> - */ - else if (*cmd == NUL) - show_autocmd(ap, event); - - /* - * Add autocmd to this autopat, if it's the last one. - */ - else if (ap->next == NULL) - break; - } - } - prev_ap = &ap->next; - } - - /* - * Add a new command. - */ - if (*cmd != NUL) { - /* - * If the pattern we want to add a command to does appear at the - * end of the list (or not is not in the list at all), add the - * pattern at the end of the list. - */ - if (ap == NULL) { - /* refuse to add buffer-local ap if buffer number is invalid */ - if (is_buflocal && (buflocal_nr == 0 - || buflist_findnr(buflocal_nr) == NULL)) { - emsgf(_("E680: <buffer=%d>: invalid buffer number "), - buflocal_nr); - return FAIL; - } - - ap = xmalloc(sizeof(AutoPat)); - ap->pat = vim_strnsave(pat, patlen); - ap->patlen = patlen; - - if (is_buflocal) { - ap->buflocal_nr = buflocal_nr; - ap->reg_prog = NULL; - } else { - char_u *reg_pat; - - ap->buflocal_nr = 0; - reg_pat = file_pat_to_reg_pat(pat, endpat, - &ap->allow_dirs, TRUE); - if (reg_pat != NULL) - ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); - xfree(reg_pat); - if (reg_pat == NULL || ap->reg_prog == NULL) { - xfree(ap->pat); - xfree(ap); - return FAIL; - } - } - ap->cmds = NULL; - *prev_ap = ap; - last_autopat[(int)event] = ap; - ap->next = NULL; - if (group == AUGROUP_ALL) - ap->group = current_augroup; - else - ap->group = group; - } - - /* - * Add the autocmd at the end of the AutoCmd list. - */ - prev_ac = &(ap->cmds); - while ((ac = *prev_ac) != NULL) - prev_ac = &ac->next; - ac = xmalloc(sizeof(AutoCmd)); - ac->cmd = vim_strsave(cmd); - ac->script_ctx = current_sctx; - ac->script_ctx.sc_lnum += sourcing_lnum; - ac->next = NULL; - *prev_ac = ac; - ac->once = once; - ac->nested = nested; - } - } - - au_cleanup(); /* may really delete removed patterns/commands now */ - return OK; -} - -// Implementation of ":doautocmd [group] event [fname]". -// Return OK for success, FAIL for failure; -int -do_doautocmd( - char_u *arg, - int do_msg, // give message for no matching autocmds? - bool *did_something -) -{ - char_u *fname; - int nothing_done = TRUE; - int group; - - if (did_something != NULL) { - *did_something = false; - } - - /* - * Check for a legal group name. If not, use AUGROUP_ALL. - */ - group = au_get_grouparg(&arg); - - if (*arg == '*') { - EMSG(_("E217: Can't execute autocommands for ALL events")); - return FAIL; - } - - /* - * Scan over the events. - * If we find an illegal name, return here, don't do anything. - */ - fname = find_end_event(arg, group != AUGROUP_ALL); - if (fname == NULL) - return FAIL; - - fname = skipwhite(fname); - - // Loop over the events. - while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) { - if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true, - group, curbuf, NULL)) { - nothing_done = false; - } - } - - if (nothing_done && do_msg) { - MSG(_("No matching autocommands")); - } - if (did_something != NULL) { - *did_something = !nothing_done; - } - - return aborting() ? FAIL : OK; -} - -/* - * ":doautoall": execute autocommands for each loaded buffer. - */ -void ex_doautoall(exarg_T *eap) -{ - int retval; - aco_save_T aco; - char_u *arg = eap->arg; - int call_do_modelines = check_nomodeline(&arg); - bufref_T bufref; - - /* - * This is a bit tricky: For some commands curwin->w_buffer needs to be - * equal to curbuf, but for some buffers there may not be a window. - * So we change the buffer for the current window for a moment. This - * gives problems when the autocommands make changes to the list of - * buffers or windows... - */ - FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp == NULL) { - continue; - } - // Find a window for this buffer and save some values. - aucmd_prepbuf(&aco, buf); - set_bufref(&bufref, buf); - - bool did_aucmd; - // execute the autocommands for this buffer - retval = do_doautocmd(arg, false, &did_aucmd); - - if (call_do_modelines && did_aucmd) { - // Execute the modeline settings, but don't set window-local - // options if we are using the current window for another - // buffer. - do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0); - } - - /* restore the current window */ - aucmd_restbuf(&aco); - - // Stop if there is some error or buffer was deleted. - if (retval == FAIL || !bufref_valid(&bufref)) { - break; - } - } - - check_cursor(); /* just in case lines got deleted */ -} - -/// Check *argp for <nomodeline>. When it is present return false, otherwise -/// return true and advance *argp to after it. Thus do_modelines() should be -/// called when true is returned. -/// -/// @param[in,out] argp argument string -bool check_nomodeline(char_u **argp) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (STRNCMP(*argp, "<nomodeline>", 12) == 0) { - *argp = skipwhite(*argp + 12); - return false; - } - 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 `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) -{ - win_T *win; - bool need_append = true; // Append `aucmd_win` to the window list. - - /* 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 the `aucmd_win` dummy floating window. - if (win == NULL && aucmd_win == NULL) { - win_alloc_aucmd_win(); - need_append = false; - } - if (win == NULL && aucmd_win_used) - /* Strange recursive autocommand, fall back to using the current - * window. Expect a few side effects... */ - win = curwin; - - aco->save_curwin = curwin; - aco->save_prevwin = prevwin; - aco->save_curbuf = curbuf; - 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 = FALSE; - curwin = win; - } else { - /* There is no window for "buf", use "aucmd_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 = TRUE; - aucmd_win_used = TRUE; - aucmd_win->w_buffer = buf; - aucmd_win->w_s = &buf->b_s; - ++buf->b_nwindows; - win_init_empty(aucmd_win); /* set cursor and topline to safe values */ - - /* Make sure w_localdir and globaldir are NULL to avoid a chdir() in - * win_enter_ext(). */ - XFREE_CLEAR(aucmd_win->w_localdir); - aco->globaldir = globaldir; - globaldir = NULL; - - block_autocmds(); // We don't want BufEnter/WinEnter autocommands. - if (need_append) { - win_append(lastwin, aucmd_win); - handle_register_window(aucmd_win); - win_config_float(aucmd_win, aucmd_win->w_float_config); - } - // Prevent chdir() call in win_enter_ext(), through do_autochdir() - int save_acd = p_acd; - p_acd = false; - win_enter(aucmd_win, false); - p_acd = save_acd; - unblock_autocmds(); - curwin = aucmd_win; - } - curbuf = buf; - aco->new_curwin = curwin; - set_bufref(&aco->new_curbuf, curbuf); -} - -/// Cleanup after executing autocommands for a (hidden) buffer. -/// Restore the window as it was (if possible). -/// -/// @param aco structure holding saved values -void aucmd_restbuf(aco_save_T *aco) -{ - if (aco->use_aucmd_win) { - curbuf->b_nwindows--; - // Find "aucmd_win", it can't be closed, but it may be in another tab page. - // Do not trigger autocommands here. - block_autocmds(); - if (curwin != aucmd_win) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { - if (tp != curtab) { - goto_tabpage_tp(tp, true, true); - } - win_goto(aucmd_win); - goto win_found; - } - } - } -win_found: - - win_remove(curwin, NULL); - handle_unregister_window(curwin); - if (curwin->w_grid.chars != NULL) { - ui_comp_remove_grid(&curwin->w_grid); - ui_call_win_hide(curwin->w_grid.handle); - grid_free(&curwin->w_grid); - } - - aucmd_win_used = false; - last_status(false); // may need to remove last status line - - if (!valid_tabpage_win(curtab)) { - // no valid window in current tabpage - close_tabpage(curtab); - } - - unblock_autocmds(); - - if (win_valid(aco->save_curwin)) { - curwin = aco->save_curwin; - } else { - // Hmm, original window disappeared. Just use the first one. - curwin = firstwin; - } - prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin - : firstwin; // window disappeared? - vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables - hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab - curbuf = curwin->w_buffer; - - xfree(globaldir); - globaldir = aco->globaldir; - - // the buffer contents may have changed - check_cursor(); - if (curwin->w_topline > curbuf->b_ml.ml_line_count) { - curwin->w_topline = curbuf->b_ml.ml_line_count; - curwin->w_topfill = 0; - } - } else { - // restore curwin - if (win_valid(aco->save_curwin)) { - // Restore the buffer which was previously edited by curwin, if it was - // changed, we are still the same window and the buffer is valid. - if (curwin == aco->new_curwin - && curbuf != aco->new_curbuf.br_buf - && bufref_valid(&aco->new_curbuf) - && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) { - if (curwin->w_s == &curbuf->b_s) { - curwin->w_s = &aco->new_curbuf.br_buf->b_s; - } - curbuf->b_nwindows--; - curbuf = aco->new_curbuf.br_buf; - curwin->w_buffer = curbuf; - curbuf->b_nwindows++; - } - - curwin = aco->save_curwin; - prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin - : firstwin; // window disappeared? - curbuf = curwin->w_buffer; - // In case the autocommand moves the cursor to a position that does not - // exist in curbuf - check_cursor(); - } - } -} - -static int autocmd_nested = FALSE; - -/// Execute autocommands for "event" and file name "fname". -/// -/// @param event event that occurred -/// @param fname filename, NULL or empty means use actual file name -/// @param fname_io filename to use for <afile> on cmdline -/// @param force When true, ignore autocmd_busy -/// @param buf Buffer for <abuf> -/// -/// @return true if some commands were executed. -bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force, - buf_T *buf) -{ - return apply_autocmds_group(event, fname, fname_io, force, - AUGROUP_ALL, buf, NULL); -} - -/// Like apply_autocmds(), but with extra "eap" argument. This takes care of -/// setting v:filearg. -/// -/// @param event event that occurred -/// @param fname NULL or empty means use actual file name -/// @param fname_io fname to use for <afile> on cmdline -/// @param force When true, ignore autocmd_busy -/// @param buf Buffer for <abuf> -/// @param exarg Ex command arguments -/// -/// @return true if some commands were executed. -static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, - bool force, buf_T *buf, exarg_T *eap) -{ - return apply_autocmds_group(event, fname, fname_io, force, - AUGROUP_ALL, buf, eap); -} - -/// Like apply_autocmds(), but handles the caller's retval. If the script -/// processing is being aborted or if retval is FAIL when inside a try -/// conditional, no autocommands are executed. If otherwise the autocommands -/// cause the script to be aborted, retval is set to FAIL. -/// -/// @param event event that occurred -/// @param fname NULL or empty means use actual file name -/// @param fname_io fname to use for <afile> on cmdline -/// @param force When true, ignore autocmd_busy -/// @param buf Buffer for <abuf> -/// @param[in,out] retval caller's retval -/// -/// @return true if some autocommands were executed -bool apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io, - bool force, buf_T *buf, int *retval) -{ - if (should_abort(*retval)) { - return false; - } - - bool did_cmd = apply_autocmds_group(event, fname, fname_io, force, - AUGROUP_ALL, buf, NULL); - if (did_cmd && aborting()) { - *retval = FAIL; - } - return did_cmd; -} - -/// Return true when there is a CursorHold/CursorHoldI autocommand defined for -/// the current mode. -bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return first_autopat[(int)(get_real_state() == NORMAL_BUSY - ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL; -} - -/// Return true if the CursorHold/CursorHoldI event can be triggered. -bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - int state; - - if (!did_cursorhold - && has_cursorhold() - && reg_recording == 0 - && typebuf.tb_len == 0 - && !ins_compl_active() - ) { - state = get_real_state(); - if (state == NORMAL_BUSY || (state & INSERT) != 0) { - return true; - } - } - return false; -} - -/// Return true if "event" autocommand is defined. -/// -/// @param event the autocommand to check -bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return first_autopat[event] != NULL; -} - -/// Execute autocommands for "event" and file name "fname". -/// -/// @param event event that occurred -/// @param fname filename, NULL or empty means use actual file name -/// @param fname_io filename to use for <afile> on cmdline, -/// NULL means use `fname`. -/// @param force When true, ignore autocmd_busy -/// @param group autocmd group ID or AUGROUP_ALL -/// @param buf Buffer for <abuf> -/// @param eap Ex command arguments -/// -/// @return true if some commands were executed. -static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, - bool force, int group, buf_T *buf, - exarg_T *eap) -{ - char_u *sfname = NULL; /* short file name */ - char_u *tail; - bool save_changed; - buf_T *old_curbuf; - bool retval = false; - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; - char_u *save_autocmd_fname; - int save_autocmd_bufnr; - char_u *save_autocmd_match; - int save_autocmd_busy; - int save_autocmd_nested; - static int nesting = 0; - AutoPatCmd patcmd; - AutoPat *ap; - char_u *save_cmdarg; - long save_cmdbang; - static int filechangeshell_busy = FALSE; - proftime_T wait_time; - bool did_save_redobuff = false; - save_redo_T save_redo; - const bool save_KeyTyped = KeyTyped; - - // Quickly return if there are no autocommands for this event or - // autocommands are blocked. - if (event == NUM_EVENTS || first_autopat[(int)event] == NULL - || autocmd_blocked > 0) { - goto BYPASS_AU; - } - - /* - * When autocommands are busy, new autocommands are only executed when - * explicitly enabled with the "nested" flag. - */ - if (autocmd_busy && !(force || autocmd_nested)) - goto BYPASS_AU; - - /* - * Quickly return when immediately aborting on error, or when an interrupt - * occurred or an exception was thrown but not caught. - */ - if (aborting()) - goto BYPASS_AU; - - /* - * FileChangedShell never nests, because it can create an endless loop. - */ - if (filechangeshell_busy && (event == EVENT_FILECHANGEDSHELL - || event == EVENT_FILECHANGEDSHELLPOST)) - goto BYPASS_AU; - - /* - * Ignore events in 'eventignore'. - */ - if (event_ignored(event)) - goto BYPASS_AU; - - /* - * Allow nesting of autocommands, but restrict the depth, because it's - * possible to create an endless loop. - */ - if (nesting == 10) { - EMSG(_("E218: autocommand nesting too deep")); - goto BYPASS_AU; - } - - /* - * Check if these autocommands are disabled. Used when doing ":all" or - * ":ball". - */ - if ( (autocmd_no_enter - && (event == EVENT_WINENTER || event == EVENT_BUFENTER)) - || (autocmd_no_leave - && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) - goto BYPASS_AU; - - /* - * Save the autocmd_* variables and info about the current buffer. - */ - save_autocmd_fname = autocmd_fname; - save_autocmd_bufnr = autocmd_bufnr; - save_autocmd_match = autocmd_match; - save_autocmd_busy = autocmd_busy; - save_autocmd_nested = autocmd_nested; - save_changed = curbuf->b_changed; - old_curbuf = curbuf; - - /* - * Set the file name to be used for <afile>. - * Make a copy to avoid that changing a buffer name or directory makes it - * invalid. - */ - if (fname_io == NULL) { - if (event == EVENT_COLORSCHEME - || event == EVENT_COLORSCHEMEPRE - || event == EVENT_OPTIONSET) { - autocmd_fname = NULL; - } else if (fname != NULL && !ends_excmd(*fname)) { - autocmd_fname = fname; - } else if (buf != NULL) { - autocmd_fname = buf->b_ffname; - } else { - autocmd_fname = NULL; - } - } else { - autocmd_fname = fname_io; - } - if (autocmd_fname != NULL) { - // Allocate MAXPATHL for when eval_vars() resolves the fullpath. - autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL); - } - - /* - * Set the buffer number to be used for <abuf>. - */ - if (buf == NULL) - autocmd_bufnr = 0; - else - autocmd_bufnr = buf->b_fnum; - - /* - * When the file name is NULL or empty, use the file name of buffer "buf". - * Always use the full path of the file name to match with, in case - * "allow_dirs" is set. - */ - if (fname == NULL || *fname == NUL) { - if (buf == NULL) - fname = NULL; - else { - if (event == EVENT_SYNTAX) - fname = buf->b_p_syn; - else if (event == EVENT_FILETYPE) - fname = buf->b_p_ft; - else { - if (buf->b_sfname != NULL) - sfname = vim_strsave(buf->b_sfname); - fname = buf->b_ffname; - } - } - if (fname == NULL) - fname = (char_u *)""; - fname = vim_strsave(fname); /* make a copy, so we can change it */ - } else { - sfname = vim_strsave(fname); - // Don't try expanding the following events. - if (event == EVENT_CMDLINECHANGED - || event == EVENT_CMDLINEENTER - || event == EVENT_CMDLINELEAVE - || event == EVENT_CMDWINENTER - || event == EVENT_CMDWINLEAVE - || event == EVENT_CMDUNDEFINED - || event == EVENT_COLORSCHEME - || event == EVENT_COLORSCHEMEPRE - || event == EVENT_DIRCHANGED - || event == EVENT_FILETYPE - || event == EVENT_FUNCUNDEFINED - || event == EVENT_OPTIONSET - || event == EVENT_QUICKFIXCMDPOST - || event == EVENT_QUICKFIXCMDPRE - || event == EVENT_REMOTEREPLY - || event == EVENT_SPELLFILEMISSING - || event == EVENT_SYNTAX - || event == EVENT_SIGNAL - || event == EVENT_TABCLOSED - || event == EVENT_WINCLOSED) { - fname = vim_strsave(fname); - } else { - fname = (char_u *)FullName_save((char *)fname, false); - } - } - if (fname == NULL) { /* out of memory */ - xfree(sfname); - retval = false; - goto BYPASS_AU; - } - -#ifdef BACKSLASH_IN_FILENAME - // Replace all backslashes with forward slashes. This makes the - // autocommand patterns portable between Unix and Windows. - if (sfname != NULL) { - forward_slash(sfname); - } - forward_slash(fname); -#endif - - - /* - * Set the name to be used for <amatch>. - */ - autocmd_match = fname; - - - // Don't redraw while doing autocommands. - RedrawingDisabled++; - save_sourcing_name = sourcing_name; - sourcing_name = NULL; /* don't free this one */ - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 0; /* no line number here */ - - const sctx_T save_current_sctx = current_sctx; - - if (do_profiling == PROF_YES) - prof_child_enter(&wait_time); /* doesn't count for the caller itself */ - - // Don't use local function variables, if called from a function. - funccal_entry_T funccal_entry; - save_funccal(&funccal_entry); - - /* - * When starting to execute autocommands, save the search patterns. - */ - if (!autocmd_busy) { - save_search_patterns(); - if (!ins_compl_active()) { - saveRedobuff(&save_redo); - did_save_redobuff = true; - } - did_filetype = keep_filetype; - } - - /* - * Note that we are applying autocmds. Some commands need to know. - */ - autocmd_busy = TRUE; - filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL); - ++nesting; /* see matching decrement below */ - - /* Remember that FileType was triggered. Used for did_filetype(). */ - if (event == EVENT_FILETYPE) - did_filetype = TRUE; - - tail = path_tail(fname); - - /* Find first autocommand that matches */ - patcmd.curpat = first_autopat[(int)event]; - patcmd.nextcmd = NULL; - patcmd.group = group; - patcmd.fname = fname; - patcmd.sfname = sfname; - patcmd.tail = tail; - patcmd.event = event; - patcmd.arg_bufnr = autocmd_bufnr; - patcmd.next = NULL; - auto_next_pat(&patcmd, false); - - /* found one, start executing the autocommands */ - if (patcmd.curpat != NULL) { - /* add to active_apc_list */ - patcmd.next = active_apc_list; - active_apc_list = &patcmd; - - // set v:cmdarg (only when there is a matching pattern) - save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG); - if (eap != NULL) { - save_cmdarg = set_cmdarg(eap, NULL); - set_vim_var_nr(VV_CMDBANG, (long)eap->forceit); - } else { - save_cmdarg = NULL; // avoid gcc warning - } - retval = true; - // mark the last pattern, to avoid an endless loop when more patterns - // are added when executing autocommands - for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) { - ap->last = false; - } - ap->last = true; - check_lnums(true); // make sure cursor and topline are valid - - // Execute the autocmd. The `getnextac` callback handles iteration. - do_cmdline(NULL, getnextac, (void *)&patcmd, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); - - reset_lnums(); // restore cursor and topline, unless they were changed - - if (eap != NULL) { - (void)set_cmdarg(NULL, save_cmdarg); - set_vim_var_nr(VV_CMDBANG, save_cmdbang); - } - /* delete from active_apc_list */ - if (active_apc_list == &patcmd) /* just in case */ - active_apc_list = patcmd.next; - } - - --RedrawingDisabled; - autocmd_busy = save_autocmd_busy; - filechangeshell_busy = FALSE; - autocmd_nested = save_autocmd_nested; - xfree(sourcing_name); - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - xfree(autocmd_fname); - autocmd_fname = save_autocmd_fname; - autocmd_bufnr = save_autocmd_bufnr; - autocmd_match = save_autocmd_match; - current_sctx = save_current_sctx; - restore_funccal(); - if (do_profiling == PROF_YES) { - prof_child_exit(&wait_time); - } - KeyTyped = save_KeyTyped; - xfree(fname); - xfree(sfname); - --nesting; /* see matching increment above */ - - // When stopping to execute autocommands, restore the search patterns and - // the redo buffer. Free any buffers in the au_pending_free_buf list and - // free any windows in the au_pending_free_win list. - if (!autocmd_busy) { - restore_search_patterns(); - if (did_save_redobuff) { - restoreRedobuff(&save_redo); - } - did_filetype = FALSE; - while (au_pending_free_buf != NULL) { - buf_T *b = au_pending_free_buf->b_next; - xfree(au_pending_free_buf); - au_pending_free_buf = b; - } - while (au_pending_free_win != NULL) { - win_T *w = au_pending_free_win->w_next; - xfree(au_pending_free_win); - au_pending_free_win = w; - } - } - - /* - * Some events don't set or reset the Changed flag. - * Check if still in the same buffer! - */ - if (curbuf == old_curbuf - && (event == EVENT_BUFREADPOST - || event == EVENT_BUFWRITEPOST - || event == EVENT_FILEAPPENDPOST - || event == EVENT_VIMLEAVE - || event == EVENT_VIMLEAVEPRE)) { - if (curbuf->b_changed != save_changed) - need_maketitle = TRUE; - curbuf->b_changed = save_changed; - } - - au_cleanup(); /* may really delete removed patterns/commands now */ - -BYPASS_AU: - /* When wiping out a buffer make sure all its buffer-local autocommands - * are deleted. */ - if (event == EVENT_BUFWIPEOUT && buf != NULL) - aubuflocal_remove(buf); - - if (retval == OK && event == EVENT_FILETYPE) { - au_did_filetype = true; - } - - return retval; -} - -static char_u *old_termresponse = NULL; - -/* - * Block triggering autocommands until unblock_autocmd() is called. - * Can be used recursively, so long as it's symmetric. - */ -void block_autocmds(void) -{ - /* Remember the value of v:termresponse. */ - if (autocmd_blocked == 0) - old_termresponse = get_vim_var_str(VV_TERMRESPONSE); - ++autocmd_blocked; -} - -void unblock_autocmds(void) -{ - --autocmd_blocked; - - /* When v:termresponse was set while autocommands were blocked, trigger - * the autocommands now. Esp. useful when executing a shell command - * during startup (nvim -d). */ - if (autocmd_blocked == 0 - && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) - apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf); -} - -// Find next autocommand pattern that matches. -static void -auto_next_pat( - AutoPatCmd *apc, - int stop_at_last /* stop when 'last' flag is set */ -) -{ - AutoPat *ap; - AutoCmd *cp; - char *s; - - XFREE_CLEAR(sourcing_name); - - for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { - apc->curpat = NULL; - - /* Only use a pattern when it has not been removed, has commands and - * the group matches. For buffer-local autocommands only check the - * buffer number. */ - if (ap->pat != NULL && ap->cmds != NULL - && (apc->group == AUGROUP_ALL || apc->group == ap->group)) { - /* execution-condition */ - if (ap->buflocal_nr == 0 - ? match_file_pat(NULL, &ap->reg_prog, apc->fname, apc->sfname, - apc->tail, ap->allow_dirs) - : ap->buflocal_nr == apc->arg_bufnr) { - const char *const name = event_nr2name(apc->event); - s = _("%s Autocommands for \"%s\""); - const size_t sourcing_name_len = (STRLEN(s) + strlen(name) + ap->patlen - + 1); - sourcing_name = xmalloc(sourcing_name_len); - snprintf((char *)sourcing_name, sourcing_name_len, s, name, - (char *)ap->pat); - if (p_verbose >= 8) { - verbose_enter(); - smsg(_("Executing %s"), sourcing_name); - verbose_leave(); - } - - apc->curpat = ap; - apc->nextcmd = ap->cmds; - /* mark last command */ - for (cp = ap->cmds; cp->next != NULL; cp = cp->next) - cp->last = FALSE; - cp->last = TRUE; - } - line_breakcheck(); - if (apc->curpat != NULL) /* found a match */ - break; - } - if (stop_at_last && ap->last) - break; - } -} - -/// Get next autocommand command. -/// Called by do_cmdline() to get the next line for ":if". -/// @return allocated string, or NULL for end of autocommands. -char_u *getnextac(int c, void *cookie, int indent, bool do_concat) -{ - AutoPatCmd *acp = (AutoPatCmd *)cookie; - char_u *retval; - AutoCmd *ac; - - /* Can be called again after returning the last line. */ - if (acp->curpat == NULL) - return NULL; - - /* repeat until we find an autocommand to execute */ - for (;; ) { - /* skip removed commands */ - while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) - if (acp->nextcmd->last) - acp->nextcmd = NULL; - else - acp->nextcmd = acp->nextcmd->next; - - if (acp->nextcmd != NULL) - break; - - /* at end of commands, find next pattern that matches */ - if (acp->curpat->last) - acp->curpat = NULL; - else - acp->curpat = acp->curpat->next; - if (acp->curpat != NULL) - auto_next_pat(acp, TRUE); - if (acp->curpat == NULL) - return NULL; - } - - ac = acp->nextcmd; - - if (p_verbose >= 9) { - verbose_enter_scroll(); - smsg(_("autocommand %s"), ac->cmd); - msg_puts("\n"); // don't overwrite this either - verbose_leave_scroll(); - } - retval = vim_strsave(ac->cmd); - // Remove one-shot ("once") autocmd in anticipation of its execution. - if (ac->once) { - au_del_cmd(ac); - } - autocmd_nested = ac->nested; - current_sctx = ac->script_ctx; - if (ac->last) { - acp->nextcmd = NULL; - } else { - acp->nextcmd = ac->next; - } - - return retval; -} - -/// Return true if there is a matching autocommand for "fname". -/// To account for buffer-local autocommands, function needs to know -/// in which buffer the file will be opened. -/// -/// @param event event that occurred. -/// @param sfname filename the event occurred in. -/// @param buf buffer the file is open in -bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - AutoPat *ap; - char_u *fname; - char_u *tail = path_tail(sfname); - bool retval = false; - - fname = (char_u *)FullName_save((char *)sfname, false); - if (fname == NULL) { - return false; - } - -#ifdef BACKSLASH_IN_FILENAME - // Replace all backslashes with forward slashes. This makes the - // autocommand patterns portable between Unix and Windows. - sfname = vim_strsave(sfname); - forward_slash(sfname); - forward_slash(fname); -#endif - - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { - if (ap->pat != NULL && ap->cmds != NULL - && (ap->buflocal_nr == 0 - ? match_file_pat(NULL, &ap->reg_prog, fname, sfname, tail, - ap->allow_dirs) - : buf != NULL && ap->buflocal_nr == buf->b_fnum)) { - retval = true; - break; - } - } - - xfree(fname); -#ifdef BACKSLASH_IN_FILENAME - xfree(sfname); -#endif - - return retval; -} - -/* - * Function given to ExpandGeneric() to obtain the list of autocommand group - * names. - */ -char_u *get_augroup_name(expand_T *xp, int idx) -{ - if (idx == augroups.ga_len) { // add "END" add the end - return (char_u *)"END"; - } - if (idx >= augroups.ga_len) { // end of list - return NULL; - } - if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) { - // skip deleted entries - return (char_u *)""; - } - return (char_u *)AUGROUP_NAME(idx); -} - -static int include_groups = FALSE; - -char_u * -set_context_in_autocmd ( - expand_T *xp, - char_u *arg, - int doautocmd /* TRUE for :doauto*, FALSE for :autocmd */ -) -{ - char_u *p; - int group; - - /* check for a group name, skip it if present */ - include_groups = FALSE; - p = arg; - group = au_get_grouparg(&arg); - - /* If there only is a group name that's what we expand. */ - if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) { - arg = p; - group = AUGROUP_ALL; - } - - /* skip over event name */ - for (p = arg; *p != NUL && !ascii_iswhite(*p); ++p) - if (*p == ',') - arg = p + 1; - if (*p == NUL) { - if (group == AUGROUP_ALL) - include_groups = TRUE; - xp->xp_context = EXPAND_EVENTS; /* expand event name */ - xp->xp_pattern = arg; - return NULL; - } - - /* skip over pattern */ - arg = skipwhite(p); - while (*arg && (!ascii_iswhite(*arg) || arg[-1] == '\\')) - arg++; - if (*arg) - return arg; /* expand (next) command */ - - if (doautocmd) - xp->xp_context = EXPAND_FILES; /* expand file names */ - else - xp->xp_context = EXPAND_NOTHING; /* pattern is not expanded */ - return NULL; -} - -/* - * Function given to ExpandGeneric() to obtain the list of event names. - */ -char_u *get_event_name(expand_T *xp, int idx) -{ - if (idx < augroups.ga_len) { // First list group names, if wanted - if (!include_groups || AUGROUP_NAME(idx) == NULL - || AUGROUP_NAME(idx) == get_deleted_augroup()) { - return (char_u *)""; // skip deleted entries - } - return (char_u *)AUGROUP_NAME(idx); - } - return (char_u *)event_names[idx - augroups.ga_len].name; -} - - -/// Check whether given autocommand is supported -/// -/// @param[in] event Event to check. -/// -/// @return True if it is, false otherwise. -bool autocmd_supported(const char *const event) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - char_u *p; - return event_name2nr((const char_u *)event, &p) != NUM_EVENTS; -} - -/// Return true if an autocommand is defined for a group, event and -/// pattern: The group can be omitted to accept any group. -/// `event` and `pattern` can be omitted to accept any event and pattern. -/// Buffer-local patterns <buffer> or <buffer=N> are accepted. -/// Used for: -/// exists("#Group") or -/// exists("#Group#Event") or -/// exists("#Group#Event#pat") or -/// exists("#Event") or -/// exists("#Event#pat") -/// -/// @param arg autocommand string -bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT -{ - event_T event; - AutoPat *ap; - buf_T *buflocal_buf = NULL; - int group; - bool retval = false; - - // Make a copy so that we can change the '#' chars to a NUL. - char *const arg_save = xstrdup(arg); - char *p = strchr(arg_save, '#'); - if (p != NULL) { - *p++ = NUL; - } - - // First, look for an autocmd group name. - group = au_find_group((char_u *)arg_save); - char *event_name; - if (group == AUGROUP_ERROR) { - /* Didn't match a group name, assume the first argument is an event. */ - group = AUGROUP_ALL; - event_name = arg_save; - } else { - if (p == NULL) { - // "Group": group name is present and it's recognized - retval = true; - goto theend; - } - - // Must be "Group#Event" or "Group#Event#pat". - event_name = p; - p = strchr(event_name, '#'); - if (p != NULL) { - *p++ = NUL; // "Group#Event#pat" - } - } - - char *pattern = p; // "pattern" is NULL when there is no pattern. - - // Find the index (enum) for the event name. - event = event_name2nr((char_u *)event_name, (char_u **)&p); - - /* return FALSE if the event name is not recognized */ - if (event == NUM_EVENTS) - goto theend; - - /* Find the first autocommand for this event. - * If there isn't any, return FALSE; - * If there is one and no pattern given, return TRUE; */ - ap = first_autopat[(int)event]; - if (ap == NULL) - goto theend; - - /* if pattern is "<buffer>", special handling is needed which uses curbuf */ - /* for pattern "<buffer=N>, fnamecmp() will work fine */ - if (pattern != NULL && STRICMP(pattern, "<buffer>") == 0) - buflocal_buf = curbuf; - - /* Check if there is an autocommand with the given pattern. */ - for (; ap != NULL; ap = ap->next) - /* only use a pattern when it has not been removed and has commands. */ - /* For buffer-local autocommands, fnamecmp() works fine. */ - if (ap->pat != NULL && ap->cmds != NULL - && (group == AUGROUP_ALL || ap->group == group) - && (pattern == NULL - || (buflocal_buf == NULL - ? fnamecmp(ap->pat, (char_u *)pattern) == 0 - : ap->buflocal_nr == buflocal_buf->b_fnum))) { - retval = true; - break; - } - -theend: - xfree(arg_save); - return retval; -} /// Tries matching a filename with a "pattern" ("prog" is NULL), or use the /// precompiled regprog "prog" ("pattern" is NULL). That avoids calling @@ -7467,8 +5324,8 @@ theend: /// @param allow_dirs Allow matching with dir /// /// @return true if there is a match, false otherwise -static bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, - char_u *sfname, char_u *tail, int allow_dirs) +bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, + char_u *sfname, char_u *tail, int allow_dirs) { regmatch_T regmatch; bool result = false; diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index a6011ec414..71149bdcc1 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -3,6 +3,7 @@ #include "nvim/buffer_defs.h" #include "nvim/os/os.h" +#include "nvim/autocmd.h" // Values for readfile() flags #define READ_NEW 0x01 // read a file into a new buffer @@ -15,23 +16,8 @@ #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) -/* - * Struct to save values in before executing autocommands for a buffer that is - * not the current buffer. - */ -typedef struct { - buf_T *save_curbuf; ///< saved curbuf - int use_aucmd_win; ///< using aucmd_win - win_T *save_curwin; ///< saved curwin - win_T *save_prevwin; ///< saved prevwin - win_T *new_curwin; ///< new curwin - bufref_T new_curbuf; ///< new curbuf - char_u *globaldir; ///< saved value of globaldir -} aco_save_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS // Events for autocommands -# include "auevents_enum.generated.h" # include "fileio.h.generated.h" #endif #endif // NVIM_FILEIO_H diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 5e28ca6538..654aa6d5ba 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1835,10 +1835,11 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, curwin = wp; curbuf = wp->w_buffer; - ++emsg_silent; /* handle exceptions, but don't display errors */ - text = eval_to_string_safe(wp->w_p_fdt, NULL, - was_set_insecurely((char_u *)"foldtext", OPT_LOCAL)); - --emsg_silent; + emsg_silent++; // handle exceptions, but don't display errors + text = eval_to_string_safe( + wp->w_p_fdt, NULL, + was_set_insecurely(wp, (char_u *)"foldtext", OPT_LOCAL)); + emsg_silent--; if (text == NULL || did_emsg) got_fdt_error = TRUE; diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua index 98c3254e7a..6ee45a14af 100644 --- a/src/nvim/generators/gen_events.lua +++ b/src/nvim/generators/gen_events.lua @@ -41,22 +41,27 @@ names_tgt:write('\n {0, NULL, (event_T)0},') enum_tgt:write('\n} event_T;\n') names_tgt:write('\n};\n') -names_tgt:write('\nstatic AutoPat *first_autopat[NUM_EVENTS] = {\n ') -local line_len = 1 -for _ = 1,((#events) - 1) do - line_len = line_len + #(' NULL,') - if line_len > 80 then - names_tgt:write('\n ') - line_len = 1 + #(' NULL,') +local gen_autopat_events = function(name) + names_tgt:write(string.format('\nstatic AutoPat *%s[NUM_EVENTS] = {\n ', name)) + local line_len = 1 + for _ = 1,((#events) - 1) do + line_len = line_len + #(' NULL,') + if line_len > 80 then + names_tgt:write('\n ') + line_len = 1 + #(' NULL,') + end + names_tgt:write(' NULL,') end - names_tgt:write(' NULL,') -end -if line_len + #(' NULL') > 80 then - names_tgt:write('\n NULL') -else - names_tgt:write(' NULL') + if line_len + #(' NULL') > 80 then + names_tgt:write('\n NULL') + else + names_tgt:write(' NULL') + end + names_tgt:write('\n};\n') end -names_tgt:write('\n};\n') + +gen_autopat_events("first_autopat") +gen_autopat_events("last_autopat") enum_tgt:close() names_tgt:close() diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 4a64cc31b1..2c954008b3 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -546,7 +546,7 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, curwin->w_botline = lnum + 63; printer_page_num = pagenum; - use_sandbox = was_set_insecurely((char_u *)"printheader", 0); + use_sandbox = was_set_insecurely(curwin, (char_u *)"printheader", 0); build_stl_str_hl(curwin, tbuf, (size_t)width + IOSIZE, p_header, use_sandbox, ' ', width, NULL, NULL); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 898ff4ebfe..b01cdde236 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -149,22 +149,22 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) } } -static ColorKey colored_key(NS ns_id, int syn_id) -{ - return (ColorKey){ .ns_id = (int)ns_id, .syn_id = syn_id }; -} - void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) { DecorProvider *p = get_provider(ns_id, true); + if ((attrs.rgb_ae_attr & HL_DEFAULT) + && map_has(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id))) { + return; + } int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); ColorItem it = { .attr_id = attr_id, .link_id = link_id, - .version = p->hl_valid }; - map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it); + .version = p->hl_valid, + .is_default = (attrs.rgb_ae_attr & HL_DEFAULT) }; + map_put(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id), it); } -int ns_get_hl(NS ns_id, int hl_id, bool link) +int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) { static int recursive = 0; @@ -176,7 +176,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link) } DecorProvider *p = get_provider(ns_id, true); - ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id)); + ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? bool valid_cache = it.version >= p->hl_valid; @@ -218,11 +218,16 @@ int ns_get_hl(NS ns_id, int hl_id, bool link) it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); it.version = p->hl_valid-tmp; - map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it); + it.is_default = attrs.rgb_ae_attr & HL_DEFAULT; + map_put(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id), it); + } + + if (it.is_default && nodefault) { + return -1; } if (link) { - return it.attr_id >= 0 ? -1 : it.link_id; + return it.attr_id >= 0 ? 0 : it.link_id; } else { return it.attr_id; } @@ -307,7 +312,7 @@ void update_window_hl(win_T *wp, bool invalid) // // haha, theme engine go brrr int normality = syn_check_group((const char_u *)S_LEN("Normal")); - int ns_attr = ns_get_hl(-1, normality, false); + int ns_attr = ns_get_hl(-1, normality, false, false); if (ns_attr > 0) { // TODO(bfredl): hantera NormalNC and so on wp->w_hl_attr_normal = ns_attr; @@ -793,6 +798,8 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) { "undercurl", HL_UNDERCURL }, { "italic", HL_ITALIC }, { "reverse", HL_INVERSE }, + { "default", HL_DEFAULT }, + { "global", HL_GLOBAL }, { NULL, 0 }, }; diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 6a5c593ab1..2bda094d8e 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -22,6 +22,8 @@ typedef enum { HL_NOCOMBINE = 0x80, HL_BG_INDEXED = 0x0100, HL_FG_INDEXED = 0x0200, + HL_DEFAULT = 0x0400, + HL_GLOBAL = 0x0800, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes @@ -188,13 +190,16 @@ typedef struct { int ns_id; int syn_id; } ColorKey; +#define ColorKey(n, s) (ColorKey) { .ns_id = (int)(n), .syn_id = (s) } typedef struct { int attr_id; int link_id; int version; + bool is_default; } ColorItem; -#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1 } +#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, \ + .version = -1, .is_default = false } #endif // NVIM_HIGHLIGHT_DEFS_H diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 9e6693afdf..fae971b3b3 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -453,7 +453,8 @@ int get_expr_indent(void) colnr_T save_curswant; int save_set_curswant; int save_State; - int use_sandbox = was_set_insecurely((char_u *)"indentexpr", OPT_LOCAL); + int use_sandbox = was_set_insecurely( + curwin, (char_u *)"indentexpr", OPT_LOCAL); // Save and restore cursor position and curswant, in case it was changed // * via :normal commands. diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a9a57d386b..a640b97d3b 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -22,6 +22,13 @@ #include "nvim/memline.h" #include "nvim/buffer.h" +#define TS_META_PARSER "treesitter_parser" +#define TS_META_TREE "treesitter_tree" +#define TS_META_NODE "treesitter_node" +#define TS_META_QUERY "treesitter_query" +#define TS_META_QUERYCURSOR "treesitter_querycursor" +#define TS_META_TREECURSOR "treesitter_treecursor" + typedef struct { TSQueryCursor *cursor; int predicated_match; @@ -115,12 +122,12 @@ void tslua_init(lua_State *L) langs = pmap_new(cstr_t)(); // type metatables - build_meta(L, "treesitter_parser", parser_meta); - build_meta(L, "treesitter_tree", tree_meta); - build_meta(L, "treesitter_node", node_meta); - build_meta(L, "treesitter_query", query_meta); - build_meta(L, "treesitter_querycursor", querycursor_meta); - build_meta(L, "treesitter_treecursor", treecursor_meta); + build_meta(L, TS_META_PARSER, parser_meta); + build_meta(L, TS_META_TREE, tree_meta); + build_meta(L, TS_META_NODE, node_meta); + build_meta(L, TS_META_QUERY, query_meta); + build_meta(L, TS_META_QUERYCURSOR, querycursor_meta); + build_meta(L, TS_META_TREECURSOR, treecursor_meta); } int tslua_has_language(lua_State *L) @@ -132,12 +139,8 @@ int tslua_has_language(lua_State *L) int tslua_add_language(lua_State *L) { - if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { - return luaL_error(L, "string expected"); - } - - const char *path = lua_tostring(L, 1); - const char *lang_name = lua_tostring(L, 2); + const char *path = luaL_checkstring(L, 1); + const char *lang_name = luaL_checkstring(L, 2); if (pmap_has(cstr_t)(langs, lang_name)) { return 0; @@ -176,8 +179,9 @@ int tslua_add_language(lua_State *L) || lang_version > TREE_SITTER_LANGUAGE_VERSION) { return luaL_error( L, - "ABI version mismatch : expected %d, found %d", - TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, lang_version); + "ABI version mismatch : supported between %d and %d, found %d", + TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, + TREE_SITTER_LANGUAGE_VERSION, lang_version); } pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); @@ -188,10 +192,7 @@ int tslua_add_language(lua_State *L) int tslua_inspect_lang(lua_State *L) { - if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { - return luaL_error(L, "string expected"); - } - const char *lang_name = lua_tostring(L, 1); + const char *lang_name = luaL_checkstring(L, 1); TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { @@ -232,11 +233,9 @@ int tslua_inspect_lang(lua_State *L) int tslua_push_parser(lua_State *L) { - // Gather language - if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { - return luaL_error(L, "string expected"); - } - const char *lang_name = lua_tostring(L, 1); + // Gather language name + const char *lang_name = luaL_checkstring(L, 1); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { return luaL_error(L, "no such language: %s", lang_name); @@ -250,14 +249,14 @@ int tslua_push_parser(lua_State *L) return luaL_error(L, "Failed to load language : %s", lang_name); } - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_PARSER); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } static TSParser ** parser_check(lua_State *L, uint16_t index) { - return luaL_checkudata(L, index, "treesitter_parser"); + return luaL_checkudata(L, index, TS_META_PARSER); } static int parser_gc(lua_State *L) @@ -388,7 +387,7 @@ static int parser_parse(lua_State *L) TSRange *changed = old_tree ? ts_tree_get_changed_ranges( old_tree, new_tree, &n_ranges) : NULL; - tslua_push_tree(L, new_tree, false); // [tree] + push_tree(L, new_tree, false); // [tree] push_ranges(L, changed, n_ranges); // [tree, ranges] @@ -403,7 +402,7 @@ static int tree_copy(lua_State *L) return 0; } - tslua_push_tree(L, *tree, true); // [tree] + push_tree(L, *tree, true); // [tree] return 1; } @@ -435,6 +434,72 @@ static int tree_edit(lua_State *L) return 0; } +// Use the top of the stack (without popping it) to create a TSRange, it can be +// either a lua table or a TSNode +static void range_from_lua(lua_State *L, TSRange *range) +{ + TSNode node; + + if (lua_istable(L, -1)) { + // should be a table of 6 elements + if (lua_objlen(L, -1) != 6) { + goto error; + } + + uint32_t start_row, start_col, start_byte, end_row, end_col, end_byte; + lua_rawgeti(L, -1, 1); // [ range, start_row] + start_row = luaL_checkinteger(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 2); // [ range, start_col] + start_col = luaL_checkinteger(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 3); // [ range, start_byte] + start_byte = luaL_checkinteger(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 4); // [ range, end_row] + end_row = luaL_checkinteger(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 5); // [ range, end_col] + end_col = luaL_checkinteger(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 6); // [ range, end_byte] + end_byte = luaL_checkinteger(L, -1); + lua_pop(L, 1); // [ range ] + + *range = (TSRange) { + .start_point = (TSPoint) { + .row = start_row, + .column = start_col + }, + .end_point = (TSPoint) { + .row = end_row, + .column = end_col + }, + .start_byte = start_byte, + .end_byte = end_byte, + }; + } else if (node_check(L, -1, &node)) { + *range = (TSRange) { + .start_point = ts_node_start_point(node), + .end_point = ts_node_end_point(node), + .start_byte = ts_node_start_byte(node), + .end_byte = ts_node_end_byte(node) + }; + } else { + goto error; + } + return; +error: + luaL_error( + L, + "Ranges can only be made from 6 element long tables or nodes."); +} + static int parser_set_ranges(lua_State *L) { if (lua_gettop(L) < 2) { @@ -461,22 +526,8 @@ static int parser_set_ranges(lua_State *L) // [ parser, ranges ] for (size_t index = 0; index < tbl_len; index++) { lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ] - - TSNode node; - if (!node_check(L, -1, &node)) { - xfree(ranges); - return luaL_error( - L, - "ranges should be tables of nodes."); - } - lua_pop(L, 1); // [ parser, ranges ] - - ranges[index] = (TSRange) { - .start_point = ts_node_start_point(node), - .end_point = ts_node_end_point(node), - .start_byte = ts_node_start_byte(node), - .end_byte = ts_node_end_byte(node) - }; + range_from_lua(L, ranges + index); + lua_pop(L, 1); } // This memcpies ranges, thus we can free it afterwards @@ -506,7 +557,7 @@ static int parser_get_ranges(lua_State *L) /// push tree interface on lua stack. /// /// This makes a copy of the tree, so ownership of the argument is unaffected. -void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy) +void push_tree(lua_State *L, TSTree *tree, bool do_copy) { if (tree == NULL) { lua_pushnil(L); @@ -520,7 +571,7 @@ void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy) *ud = tree; } - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREE); // [udata, meta] lua_setmetatable(L, -2); // [udata] // table used for node wrappers to keep a reference to tree wrapper @@ -534,7 +585,7 @@ void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy) static TSTree **tree_check(lua_State *L, uint16_t index) { - TSTree **ud = luaL_checkudata(L, index, "treesitter_tree"); + TSTree **ud = luaL_checkudata(L, index, TS_META_TREE); return ud; } @@ -582,7 +633,7 @@ static void push_node(lua_State *L, TSNode node, int uindex) } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [udata] *ud = node; - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_NODE); // [udata, meta] lua_setmetatable(L, -2); // [udata] lua_getfenv(L, uindex); // [udata, reftable] lua_setfenv(L, -2); // [udata] @@ -590,7 +641,7 @@ static void push_node(lua_State *L, TSNode node, int uindex) static bool node_check(lua_State *L, int index, TSNode *res) { - TSNode *ud = luaL_checkudata(L, index, "treesitter_node"); + TSNode *ud = luaL_checkudata(L, index, TS_META_NODE); if (ud) { *res = *ud; return true; @@ -618,13 +669,12 @@ static int node_eq(lua_State *L) if (!node_check(L, 1, &node)) { return 0; } - // This should only be called if both x and y in "x == y" has the - // treesitter_node metatable. So it is ok to error out otherwise. - TSNode *ud = luaL_checkudata(L, 2, "treesitter_node"); - if (!ud) { + + TSNode node2; + if (!node_check(L, 2, &node2)) { return 0; } - TSNode node2 = *ud; + lua_pushboolean(L, ts_node_eq(node, node2)); return 1; } @@ -859,7 +909,7 @@ static int node_named_descendant_for_range(lua_State *L) static int node_next_child(lua_State *L) { TSTreeCursor *ud = luaL_checkudata( - L, lua_upvalueindex(1), "treesitter_treecursor"); + L, lua_upvalueindex(1), TS_META_TREECURSOR); if (!ud) { return 0; } @@ -909,7 +959,7 @@ static int node_iter_children(lua_State *L) TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata] *ud = ts_tree_cursor_new(source); - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt] + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREECURSOR); // [udata, mt] lua_setmetatable(L, -2); // [udata] lua_pushvalue(L, 1); // [udata, source_node] lua_pushcclosure(L, node_next_child, 2); @@ -919,7 +969,7 @@ static int node_iter_children(lua_State *L) static int treecursor_gc(lua_State *L) { - TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor"); + TSTreeCursor *ud = luaL_checkudata(L, 1, TS_META_TREECURSOR); ts_tree_cursor_delete(ud); return 0; } @@ -1031,7 +1081,7 @@ static int node_rawquery(lua_State *L) ud->cursor = cursor; ud->predicated_match = -1; - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_querycursor"); + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_QUERYCURSOR); lua_setmetatable(L, -2); // [udata] lua_pushvalue(L, 1); // [udata, node] @@ -1051,7 +1101,7 @@ static int node_rawquery(lua_State *L) static int querycursor_gc(lua_State *L) { - TSLua_cursor *ud = luaL_checkudata(L, 1, "treesitter_querycursor"); + TSLua_cursor *ud = luaL_checkudata(L, 1, TS_META_QUERYCURSOR); ts_query_cursor_delete(ud->cursor); return 0; } @@ -1084,7 +1134,7 @@ int ts_lua_parse_query(lua_State *L) TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata] *ud = query; - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_query"); // [udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_QUERY); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } @@ -1102,7 +1152,7 @@ static const char *query_err_string(TSQueryError err) { static TSQuery *query_check(lua_State *L, int index) { - TSQuery **ud = luaL_checkudata(L, index, "treesitter_query"); + TSQuery **ud = luaL_checkudata(L, index, TS_META_QUERY); return *ud; } diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 4ba2e36656..7094d3be90 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -55,16 +55,10 @@ static bool menu_is_winbar(const char_u *const name) return (STRNCMP(name, "WinBar", 6) == 0); } -int winbar_height(const win_T *const wp) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - return wp->w_winbar != NULL && wp->w_winbar->children != NULL ? 1 : 0; -} - static vimmenu_T **get_root_menu(const char_u *const name) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return menu_is_winbar(name) ? &curwin->w_winbar : &root_menu; + return &root_menu; } /// Do the :menu command and relatives. @@ -191,10 +185,6 @@ ex_menu(exarg_T *eap) } vimmenu_T **root_menu_ptr = get_root_menu(menu_path); - if (root_menu_ptr == &curwin->w_winbar) { - // Assume the window toolbar menu will change. - redraw_later(curwin, NOT_VALID); - } if (enable != kNone) { // Change sensitivity of the menu. @@ -270,19 +260,6 @@ ex_menu(exarg_T *eap) xfree(map_buf); } - if (root_menu_ptr == &curwin->w_winbar) { - const int h = winbar_height(curwin); - - if (h != curwin->w_winbar_height) { - if (h == 0) { - curwin->w_height++; - } else if (curwin->w_height > 0) { - curwin->w_height--; - } - curwin->w_winbar_height = h; - } - } - ui_call_update_menu(); theend: @@ -661,14 +638,6 @@ remove_menu ( return OK; } -// Remove the WinBar menu from window "wp". -void remove_winbar(win_T *wp) - FUNC_ATTR_NONNULL_ALL -{ - remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, true); - xfree(wp->w_winbar_items); -} - /* * Free the given menu structure and remove it from the linked list. */ @@ -937,7 +906,6 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) * Used when expanding menu names. */ static vimmenu_T *expand_menu = NULL; -static vimmenu_T *expand_menu_alt = NULL; static int expand_modes = 0x0; static int expand_emenu; /* TRUE for ":emenu" command */ @@ -992,8 +960,6 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc return NULL; // TODO(vim): check for next command? } if (*p == NUL) { // Complete the menu name - bool try_alt_menu = true; - // With :unmenu, you only want to match menus for the appropriate mode. // With :menu though you might want to add a menu with the same name as // one in another mode, so match menus from other modes too. @@ -1025,10 +991,6 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc break; } menu = menu->next; - if (menu == NULL && try_alt_menu) { - menu = curwin->w_winbar; - try_alt_menu = false; - } } if (menu == NULL) { /* No menu found with the name we were looking for */ @@ -1037,18 +999,12 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc } name = p; menu = menu->children; - try_alt_menu = false; } xfree(path_name); xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS; xp->xp_pattern = after_dot; expand_menu = menu; - if (expand_menu == root_menu) { - expand_menu_alt = curwin->w_winbar; - } else { - expand_menu_alt = NULL; - } } else { // We're in the mapping part xp->xp_context = EXPAND_NOTHING; } @@ -1062,13 +1018,11 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc char_u *get_menu_name(expand_T *xp, int idx) { static vimmenu_T *menu = NULL; - static bool did_alt_menu = false; char_u *str; static int should_advance = FALSE; if (idx == 0) { /* first call: start at first item */ menu = expand_menu; - did_alt_menu = false; should_advance = false; } @@ -1077,10 +1031,6 @@ char_u *get_menu_name(expand_T *xp, int idx) || menu_is_separator(menu->dname) || menu->children == NULL)) { menu = menu->next; - if (menu == NULL && !did_alt_menu) { - menu = expand_menu_alt; - did_alt_menu = true; - } } if (menu == NULL) /* at end of linked list */ @@ -1100,10 +1050,6 @@ char_u *get_menu_name(expand_T *xp, int idx) if (should_advance) { // Advance to next menu entry. menu = menu->next; - if (menu == NULL && !did_alt_menu) { - menu = expand_menu_alt; - did_alt_menu = true; - } } should_advance = !should_advance; @@ -1118,7 +1064,6 @@ char_u *get_menu_name(expand_T *xp, int idx) char_u *get_menu_names(expand_T *xp, int idx) { static vimmenu_T *menu = NULL; - static bool did_alt_menu = false; #define TBUFFER_LEN 256 static char_u tbuffer[TBUFFER_LEN]; /*hack*/ char_u *str; @@ -1126,7 +1071,6 @@ char_u *get_menu_names(expand_T *xp, int idx) if (idx == 0) { /* first call: start at first item */ menu = expand_menu; - did_alt_menu = false; should_advance = false; } @@ -1136,10 +1080,6 @@ char_u *get_menu_names(expand_T *xp, int idx) || (expand_emenu && menu_is_separator(menu->dname)) || menu->dname[STRLEN(menu->dname) - 1] == '.')) { menu = menu->next; - if (menu == NULL && !did_alt_menu) { - menu = expand_menu_alt; - did_alt_menu = true; - } } if (menu == NULL) /* at end of linked list */ @@ -1173,10 +1113,6 @@ char_u *get_menu_names(expand_T *xp, int idx) if (should_advance) { // Advance to next menu entry. menu = menu->next; - if (menu == NULL && !did_alt_menu) { - menu = expand_menu_alt; - did_alt_menu = true; - } } should_advance = !should_advance; @@ -1470,7 +1406,6 @@ static void execute_menu(const exarg_T *eap, vimmenu_T *menu) } } - // For the WinBar menu always use the Normal mode menu. if (idx == -1 || eap == NULL) { mode = (char_u *)"Normal"; idx = MENU_INDEX_NORMAL; @@ -1540,53 +1475,6 @@ void ex_emenu(exarg_T *eap) execute_menu(eap, menu); } -// Handle a click in the window toolbar of "wp" at column "col". -void winbar_click(win_T *wp, int col) - FUNC_ATTR_NONNULL_ALL -{ - if (wp->w_winbar_items == NULL) { - return; - } - for (int idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; idx++) { - winbar_item_T *item = &wp->w_winbar_items[idx]; - - if (col >= item->wb_startcol && col <= item->wb_endcol) { - win_T *save_curwin = NULL; - const pos_T save_visual = VIsual; - const int save_visual_active = VIsual_active; - const int save_visual_select = VIsual_select; - const int save_visual_reselect = VIsual_reselect; - const int save_visual_mode = VIsual_mode; - - if (wp != curwin) { - // Clicking in the window toolbar of a not-current window. - // Make that window the current one and save Visual mode. - save_curwin = curwin; - VIsual_active = false; - curwin = wp; - curbuf = curwin->w_buffer; - check_cursor(); - } - - // Note: the command might close the current window. - execute_menu(NULL, item->wb_menu); - - if (save_curwin != NULL && win_valid(save_curwin)) { - curwin = save_curwin; - curbuf = curwin->w_buffer; - VIsual = save_visual; - VIsual_active = save_visual_active; - VIsual_select = save_visual_select; - VIsual_reselect = save_visual_reselect; - VIsual_mode = save_visual_mode; - } - if (!win_valid(wp)) { - break; - } - } - } -} - /* * Translation of menu names. Just a simple lookup table. */ diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index cff88de00b..f05dade73f 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -60,7 +60,6 @@ int jump_to_mouse(int flags, { static int on_status_line = 0; // #lines below bottom of window static int on_sep_line = 0; // on separator right of window - static bool in_winbar = false; static int prev_row = -1; static int prev_col = -1; static win_T *dragwin = NULL; // window being dragged @@ -101,18 +100,6 @@ retnomove: if (on_sep_line) { return IN_SEP_LINE; } - if (in_winbar) { - // A quick second click may arrive as a double-click, but we use it - // as a second click in the WinBar. - if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED)) { - wp = mouse_find_win(&grid, &row, &col); - if (wp == NULL) { - return IN_UNKNOWN; - } - winbar_click(wp, col); - } - return IN_OTHER_WIN | MOUSE_WINBAR; - } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); redraw_curbuf_later(INVERTED); // delete the inversion @@ -142,13 +129,8 @@ retnomove: dragwin = NULL; if (row == -1) { - // A click in the window toolbar does not enter another window or - // change Visual highlighting. - winbar_click(wp, col); - in_winbar = true; - return IN_OTHER_WIN | MOUSE_WINBAR; + return IN_OTHER_WIN; } - in_winbar = false; // winpos and height may change in win_enter()! if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) { @@ -239,9 +221,6 @@ retnomove: did_drag |= count; } return IN_SEP_LINE; // Cursor didn't move - } else if (in_winbar) { - // After a click on the window toolbar don't start Visual mode. - return IN_OTHER_WIN | MOUSE_WINBAR; } else { // keep_window_focus must be true // before moving the cursor for a left click, stop Visual mode @@ -503,7 +482,6 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) // exist. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp == fp->fr_win) { - *rowp -= wp->w_winbar_height; return wp; } } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 4b26ae259e..0293bb4a73 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1847,13 +1847,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) CancelRedo(); } else { (void)op_delete(oap); - if (oap->motion_type == kMTLineWise && has_format_option(FO_AUTO)) { - // cursor line wasn't saved yet - if (u_save_cursor() == FAIL) { - break; - } + // save cursor line for undo if it wasn't saved yet + if (oap->motion_type == kMTLineWise + && has_format_option(FO_AUTO) + && u_save_cursor() == OK) { + auto_format(false, true); } - auto_format(false, true); } break; @@ -2601,11 +2600,6 @@ do_mouse ( oap == NULL ? NULL : &(oap->inclusive), which_button); - // A click in the window toolbar has no side effects. - if (jump_flags & MOUSE_WINBAR) { - return false; - } - moved = (jump_flags & CURSOR_MOVED); in_status_line = (jump_flags & IN_STATUS_LINE); in_sep_line = (jump_flags & IN_SEP_LINE); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9e7d81fc82..40dd5f0b5c 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4117,8 +4117,8 @@ fex_format( int c /* character to be inserted */ ) { - int use_sandbox = was_set_insecurely((char_u *)"formatexpr", - OPT_LOCAL); + int use_sandbox = was_set_insecurely( + curwin, (char_u *)"formatexpr", OPT_LOCAL); int r; char_u *fex; diff --git a/src/nvim/option.c b/src/nvim/option.c index 0a91687352..77a13b16b1 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -56,6 +56,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" +#include "nvim/runtime.h" #include "nvim/keymap.h" #include "nvim/garray.h" #include "nvim/cursor_shape.h" @@ -337,301 +338,6 @@ static char_u SHM_ALL[] = { # include "option.c.generated.h" #endif -/// Append string with escaped commas -static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - size_t shift = 0; - for (size_t i = 0; i < len; i++) { - if (src[i] == ',') { - dest[i + shift++] = '\\'; - } - dest[i + shift] = src[i]; - } - return &dest[len + shift]; -} - -/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some -/// suffixes -/// -/// @param[in] val ENV_SEPCHAR-separated array value. -/// @param[in] common_suf_len Length of the common suffix which is appended to -/// each item in the array, twice. -/// @param[in] single_suf_len Length of the suffix which is appended to each -/// item in the array once. -/// -/// @return Length of the ENV_SEPCHAR-separated string array that contains each -/// item in the original array twice with suffixes with given length -/// (common_suf is present after each new item, single_suf is present -/// after half of the new items) and with commas after each item, commas -/// inside the values are escaped. -static inline size_t compute_double_env_sep_len(const char *const val, - const size_t common_suf_len, - const size_t single_suf_len) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE -{ - if (val == NULL || *val == NUL) { - return 0; - } - size_t ret = 0; - const void *iter = NULL; - do { - size_t dir_len; - const char *dir; - iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len); - if (dir != NULL && dir_len > 0) { - ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len - + !after_pathsep(dir, dir + dir_len)) * 2 - + single_suf_len); - } - } while (iter != NULL); - return ret; -} - -#define NVIM_SIZE (sizeof("nvim") - 1) - -/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one -/// -/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in -/// addition to suf1 and suf2. -/// -/// @param[in,out] dest Destination comma-separated array. -/// @param[in] val Source ENV_SEPCHAR-separated array. -/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it -/// directory separator is appended. Suffix must not contain -/// commas. -/// @param[in] len1 Length of the suf1. -/// @param[in] suf2 If not NULL, another suffix appended to destination. Again -/// with directory separator behind. Suffix must not contain -/// commas. -/// @param[in] len2 Length of the suf2. -/// @param[in] forward If true, iterate over val in forward direction. -/// Otherwise in reverse. -/// -/// @return (dest + appended_characters_length) -static inline char *add_env_sep_dirs(char *dest, const char *const val, - const char *const suf1, const size_t len1, - const char *const suf2, const size_t len2, - const bool forward) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) -{ - if (val == NULL || *val == NUL) { - return dest; - } - const void *iter = NULL; - do { - size_t dir_len; - const char *dir; - iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter, - &dir, &dir_len); - if (dir != NULL && dir_len > 0) { - dest = strcpy_comma_escaped(dest, dir, dir_len); - if (!after_pathsep(dest - 1, dest)) { - *dest++ = PATHSEP; - } - memmove(dest, "nvim", NVIM_SIZE); - dest += NVIM_SIZE; - if (suf1 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf1, len1); - dest += len1; - if (suf2 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf2, len2); - dest += len2; - } - } - *dest++ = ','; - } - } while (iter != NULL); - return dest; -} - -/// Adds directory `dest` to a comma-separated list of directories. -/// -/// Commas in the added directory are escaped. -/// -/// Windows: Appends "nvim-data" instead of "nvim" if `type` is kXDGDataHome. -/// -/// @see get_xdg_home -/// -/// @param[in,out] dest Destination comma-separated array. -/// @param[in] dir Directory to append. -/// @param[in] type Decides whether to append "nvim" (Win: or "nvim-data"). -/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it -/// directory separator is appended. Suffix must not contain -/// commas. -/// @param[in] len1 Length of the suf1. -/// @param[in] suf2 If not NULL, another suffix appended to destination. Again -/// with directory separator behind. Suffix must not contain -/// commas. -/// @param[in] len2 Length of the suf2. -/// @param[in] forward If true, iterate over val in forward direction. -/// Otherwise in reverse. -/// -/// @return (dest + appended_characters_length) -static inline char *add_dir(char *dest, const char *const dir, - const size_t dir_len, const XDGVarType type, - const char *const suf1, const size_t len1, - const char *const suf2, const size_t len2) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (dir == NULL || dir_len == 0) { - return dest; - } - dest = strcpy_comma_escaped(dest, dir, dir_len); - bool append_nvim = (type == kXDGDataHome || type == kXDGConfigHome); - if (append_nvim) { - if (!after_pathsep(dest - 1, dest)) { - *dest++ = PATHSEP; - } -#if defined(WIN32) - size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE); - memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size); - dest += size; -#else - memmove(dest, "nvim", NVIM_SIZE); - dest += NVIM_SIZE; -#endif - if (suf1 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf1, len1); - dest += len1; - if (suf2 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf2, len2); - dest += len2; - } - } - } - *dest++ = ','; - return dest; -} - -char *get_lib_dir(void) -{ - // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty - // in an appimage build - if (strlen(default_lib_dir) != 0 - && os_isdir((const char_u *)default_lib_dir)) { - return xstrdup(default_lib_dir); - } - - // Find library path relative to the nvim binary: ../lib/nvim/ - char exe_name[MAXPATHL]; - vim_get_prefix_from_exepath(exe_name); - if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) { - return xstrdup(exe_name); - } - return NULL; -} - -/// Sets &runtimepath to default value. -/// -/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing -/// configuration and data files in the same path. #4403 -/// -/// If "clean_arg" is true, Nvim was started with --clean. -static void set_runtimepath_default(bool clean_arg) -{ - size_t rtp_size = 0; - char *const data_home = clean_arg - ? NULL - : stdpaths_get_xdg_var(kXDGDataHome); - char *const config_home = clean_arg - ? NULL - : stdpaths_get_xdg_var(kXDGConfigHome); - char *const vimruntime = vim_getenv("VIMRUNTIME"); - char *const libdir = get_lib_dir(); - char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); - char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); -#define SITE_SIZE (sizeof("site") - 1) -#define AFTER_SIZE (sizeof("after") - 1) - size_t data_len = 0; - size_t config_len = 0; - size_t vimruntime_len = 0; - size_t libdir_len = 0; - if (data_home != NULL) { - data_len = strlen(data_home); - if (data_len != 0) { -#if defined(WIN32) - size_t nvim_size = (sizeof("nvim-data") - 1); -#else - size_t nvim_size = NVIM_SIZE; -#endif - rtp_size += ((data_len + memcnt(data_home, ',', data_len) - + nvim_size + 1 + SITE_SIZE + 1 - + !after_pathsep(data_home, data_home + data_len)) * 2 - + AFTER_SIZE + 1); - } - } - if (config_home != NULL) { - config_len = strlen(config_home); - if (config_len != 0) { - rtp_size += ((config_len + memcnt(config_home, ',', config_len) - + NVIM_SIZE + 1 - + !after_pathsep(config_home, config_home + config_len)) * 2 - + AFTER_SIZE + 1); - } - } - if (vimruntime != NULL) { - vimruntime_len = strlen(vimruntime); - if (vimruntime_len != 0) { - rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1; - } - } - if (libdir != NULL) { - libdir_len = strlen(libdir); - if (libdir_len != 0) { - rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; - } - } - rtp_size += compute_double_env_sep_len(data_dirs, - NVIM_SIZE + 1 + SITE_SIZE + 1, - AFTER_SIZE + 1); - rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1, - AFTER_SIZE + 1); - if (rtp_size == 0) { - return; - } - char *const rtp = xmalloc(rtp_size); - char *rtp_cur = rtp; - rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, - NULL, 0, NULL, 0); - rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true); - rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, - "site", SITE_SIZE, NULL, 0); - rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0, - true); - rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, - NULL, 0, NULL, 0); - rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); - rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, - "after", AFTER_SIZE, false); - rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, - "site", SITE_SIZE, "after", AFTER_SIZE); - rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0, - false); - rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, - "after", AFTER_SIZE, NULL, 0); - // Strip trailing comma. - rtp_cur[-1] = NUL; - assert((size_t) (rtp_cur - rtp) == rtp_size); -#undef SITE_SIZE -#undef AFTER_SIZE - set_string_default("runtimepath", rtp, true); - // Make a copy of 'rtp' for 'packpath' - set_string_default("packpath", rtp, false); - xfree(data_dirs); - xfree(config_dirs); - xfree(data_home); - xfree(config_home); - xfree(vimruntime); - xfree(libdir); -} - -#undef NVIM_SIZE - /// Initialize the options, first part. /// /// Called only once from main(), just after creating the first buffer. @@ -652,7 +358,14 @@ void set_init_1(bool clean_arg) { const char *shell = os_getenv("SHELL"); if (shell != NULL) { - set_string_default("sh", (char *) shell, false); + if (vim_strchr((const char_u *)shell, ' ') != NULL) { + const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL + char *const cmd = xmalloc(len); + snprintf(cmd, len, "\"%s\"", shell); + set_string_default("sh", cmd, true); + } else { + set_string_default("sh", (char *)shell, false); + } } } @@ -779,7 +492,14 @@ void set_init_1(bool clean_arg) true); // Set default for &runtimepath. All necessary expansions are performed in // this function. - set_runtimepath_default(clean_arg); + char *rtp = runtimepath_default(clean_arg); + if (rtp) { + ILOG("startup runtimepart/packpath value: %s", rtp); + set_string_default("runtimepath", rtp, true); + // Make a copy of 'rtp' for 'packpath' + set_string_default("packpath", rtp, false); + rtp = NULL; // ownership taken + } /* * Set all the options (except the terminal options) to their default @@ -945,7 +665,7 @@ set_option_default( } // The default value is not insecure. - uint32_t *flagsp = insecure_flag(opt_idx, opt_flags); + uint32_t *flagsp = insecure_flag(curwin, opt_idx, opt_flags); *flagsp = *flagsp & ~P_INSECURE; } @@ -987,10 +707,9 @@ static void set_string_default(const char *name, char *val, bool allocated) xfree(options[opt_idx].def_val[VI_DEFAULT]); } - options[opt_idx].def_val[VI_DEFAULT] = (char_u *) ( - allocated - ? (char_u *) val - : (char_u *) xstrdup(val)); + options[opt_idx].def_val[VI_DEFAULT] = allocated + ? (char_u *)val + : (char_u *)xstrdup(val); options[opt_idx].flags |= P_DEF_ALLOCED; } } @@ -1880,7 +1599,7 @@ int do_set( saved_newval = (newval != NULL) ? xstrdup((char *)newval) : 0; { - uint32_t *p = insecure_flag(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 @@ -2007,7 +1726,7 @@ static void did_set_option( /* When an option is set in the sandbox, from a modeline or in secure mode * set the P_INSECURE flag. Otherwise, if a new value is stored reset the * flag. */ - uint32_t *p = insecure_flag(opt_idx, opt_flags); + uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { @@ -2346,12 +2065,12 @@ static void check_string_option(char_u **pp) /// Return true when option "opt" was set from a modeline or in secure mode. /// Return false when it wasn't. /// Return -1 for an unknown option. -int was_set_insecurely(char_u *opt, int opt_flags) +int was_set_insecurely(win_T *const wp, char_u *opt, int opt_flags) { int idx = findoption((const char *)opt); if (idx >= 0) { - uint32_t *flagp = insecure_flag(idx, opt_flags); + uint32_t *flagp = insecure_flag(wp, idx, opt_flags); return (*flagp & P_INSECURE) != 0; } internal_error("was_set_insecurely()"); @@ -2360,16 +2079,16 @@ int was_set_insecurely(char_u *opt, int opt_flags) /// Get a pointer to the flags used for the P_INSECURE flag of option /// "opt_idx". For some local options a local flags field is used. -static uint32_t *insecure_flag(int opt_idx, int opt_flags) +static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) { if (opt_flags & OPT_LOCAL) switch ((int)options[opt_idx].indir) { - case PV_STL: return &curwin->w_p_stl_flags; - case PV_FDE: return &curwin->w_p_fde_flags; - case PV_FDT: return &curwin->w_p_fdt_flags; - case PV_INDE: return &curbuf->b_p_inde_flags; - case PV_FEX: return &curbuf->b_p_fex_flags; - case PV_INEX: return &curbuf->b_p_inex_flags; + case PV_STL: return &wp->w_p_stl_flags; + case PV_FDE: return &wp->w_p_fde_flags; + case PV_FDT: return &wp->w_p_fdt_flags; + case PV_INDE: return &wp->w_buffer->b_p_inde_flags; + case PV_FEX: return &wp->w_buffer->b_p_fex_flags; + case PV_INEX: return &wp->w_buffer->b_p_inex_flags; } // Nothing special, return global flags field. @@ -7173,46 +6892,6 @@ int get_sts_value(void) return (int)result; } -/// Check matchpairs option for "*initc". -/// If there is a match set "*initc" to the matching character and "*findc" to -/// the opposite character. Set "*backwards" to the direction. -/// When "switchit" is true swap the direction. -void find_mps_values(int *initc, int *findc, int *backwards, int switchit) -{ - char_u *ptr = curbuf->b_p_mps; - - while (*ptr != NUL) { - if (utf_ptr2char(ptr) == *initc) { - if (switchit) { - *findc = *initc; - *initc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); - *backwards = true; - } else { - *findc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); - *backwards = false; - } - return; - } - char_u *prev = ptr; - ptr += utfc_ptr2len(ptr) + 1; - if (utf_ptr2char(ptr) == *initc) { - if (switchit) { - *findc = *initc; - *initc = utf_ptr2char(prev); - *backwards = false; - } else { - *findc = utf_ptr2char(prev); - *backwards = true; - } - return; - } - ptr += utfc_ptr2len(ptr); - if (*ptr == ',') { - ptr++; - } - } -} - /// This is called when 'breakindentopt' is changed and when a window is /// initialized static bool briopt_check(win_T *wp) diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index f0bc13783c..4d7d9a45df 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -160,8 +160,21 @@ static void init_child(PtyProcess *ptyproc) os_unsetenv("COLUMNS"); os_unsetenv("LINES"); os_unsetenv("TERMCAP"); - os_unsetenv("COLORTERM"); os_unsetenv("COLORFGBG"); + // setting COLORTERM to "truecolor" if termguicolors is set and 256 + // otherwise, but only if it was set in the parent terminal at all + if (os_env_exists("COLORTERM")) { + const char *colorterm = os_getenv("COLORTERM"); + if (colorterm != NULL) { + if (p_tgc) { + os_setenv("COLORTERM", "truecolor", 1); + } else { + os_setenv("COLORTERM", "256", 1); + } + } else { + os_unsetenv("COLORTERM"); + } + } signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); diff --git a/src/nvim/path.c b/src/nvim/path.c index 793f917f06..f52fbbd5c8 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1610,10 +1610,10 @@ void simplify_filename(char_u *filename) static char *eval_includeexpr(const char *const ptr, const size_t len) { - set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t) len); - char *res = (char *) eval_to_string_safe( - curbuf->b_p_inex, NULL, was_set_insecurely((char_u *)"includeexpr", - OPT_LOCAL)); + set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); + char *res = (char *)eval_to_string_safe( + curbuf->b_p_inex, NULL, + was_set_insecurely(curwin, (char_u *)"includeexpr", OPT_LOCAL)); set_vim_var_string(VV_FNAME, NULL, 0); return res; } diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 79048eac39..fa6674469c 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -1348,10 +1348,6 @@ msgstr "E143: Outobevele het nuwe buffer %s onverwags geskrap" msgid "E144: non-numeric argument to :z" msgstr "E144: nie-numeriese parameter vir :z" -#, fuzzy -#~ msgid "E145: Shell commands not allowed in restricted mode" -#~ msgstr "E145: Dop bevele nie toegelaat in rvim" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Patrone kan nie deur letters afgebaken word nie" diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po index 7e83cb08ed..be4206f36e 100644 --- a/src/nvim/po/ca.po +++ b/src/nvim/po/ca.po @@ -1234,10 +1234,6 @@ msgstr "E143: Una auto-ordre ha eliminat el buffer nou %s inesperadament" msgid "E144: non-numeric argument to :z" msgstr "E144: Argument no numric per a :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Les ordres shell no estan permeses en l'rvim" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Les expressions regulars no poden estar delimitades per lletres" diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po index 26bdbe8c45..5b9f3d3a58 100644 --- a/src/nvim/po/cs.cp1250.po +++ b/src/nvim/po/cs.cp1250.po @@ -1249,10 +1249,6 @@ msgstr "E143: Automatick pkazy neoekvan smazaly nov buffer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: neseln argument pro :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim nepovoluje pouit pkaz shellu" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulrn vrazy nesm bt oddleny psmeny" diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po index 986d6753a8..31a90dc514 100644 --- a/src/nvim/po/cs.po +++ b/src/nvim/po/cs.po @@ -1249,10 +1249,6 @@ msgstr "E143: Automatick pkazy neoekvan smazaly nov buffer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: neseln argument pro :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim nepovoluje pouit pkaz shellu" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulrn vrazy nesm bt oddleny psmeny" diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index 58cd19210b..f35272810b 100644 --- a/src/nvim/po/da.po +++ b/src/nvim/po/da.po @@ -980,9 +980,6 @@ msgstr "E143: Autokommandoer slettede uventede ny buffer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: ikke-numerisk argument til :z" -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Skalkommandoer er ikke tilladt i rvim" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulære udtryk kan ikke afgrænses af bogstaver" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index a6ba8476d8..a2e04965e5 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -3,7 +3,7 @@ # Do ":help uganda" in Vim to read copying and usage conditions. # Do ":help credits" in Vim to see a list of people who contributed. # -# Previous-Translator(s): +# Previous-Translator(s): # Johannes Zellner <johannes@zellner.org> # Gerfried Fuchs <alfie@ist.org> msgid "" @@ -659,10 +659,6 @@ msgstr "E143: Autokommandos lschten unerwartet neuen Puffer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: Nicht-numerisches Argument fr :z" -#: ../ex_cmds.c:3398 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Shell-Befehle sind in rvim nicht erlaubt" - #: ../ex_cmds.c:3492 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulre Ausdrcke knnen nicht durch Buchstaben begrenzt werden" diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po index 00a05195b4..7919fc8946 100644 --- a/src/nvim/po/en_GB.po +++ b/src/nvim/po/en_GB.po @@ -1194,10 +1194,6 @@ msgstr "" msgid "E144: non-numeric argument to :z" msgstr "" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regular expressions cannot be delimited by letters" diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index d1ce47d97c..99c46c7275 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -969,9 +969,6 @@ msgstr "E143: Aŭtokomandoj neatendite forviŝis novan bufron %s" msgid "E144: non-numeric argument to :z" msgstr "E144: nenumera argumento de :z" -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Ŝelkomandoj nepermeseblaj en rvim" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Ne eblas limigi regulesprimon per literoj" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index f30e8780f9..eeea27610d 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -1234,10 +1234,6 @@ msgstr "E143: Las auto-órdenes han eliminado al nuevo búfer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: Argumento no numérico para \":z\"" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: No se permiten órdenes de consola en rvim" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Las expresiones regulares no se pueden delimitar con letras" diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index f568a34b3c..4489139cfb 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -1515,10 +1515,6 @@ msgstr "E143: Autocommand poisti uuden puskurin odotuksen vastaisesti %s" msgid "E144: non-numeric argument to :z" msgstr "E144: :z:n argumentti ei ole numero" -#, fuzzy -#~ msgid "E145: Shell commands not allowed in restricted mode" -#~ msgstr "E145: Kuoren komennot eivät toimi rvimissä" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Säännöllistä ilmausta ei voi rajata kirjaimilla" diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index ea6fe302fe..bb60649c91 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -1117,12 +1117,6 @@ msgstr "E143: Une autocommande a effac le nouveau tampon %s" msgid "E144: non-numeric argument to :z" msgstr "E144: L'argument de :z n'est pas numrique" -# AB - La version franaise fera peut-tre mieux passer l'amre pilule. -# La consultation de l'aide donnera l'explication complte ceux qui -# ne comprendraient pas quoi ce message est d. -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Les commandes externes sont indisponibles dans rvim" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "" "E146: Les expressions rgulires ne peuvent pas tre dlimites par des " diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index 617b805eb8..1104b31c32 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -967,9 +967,6 @@ msgstr "E143: Scrios na huathorduithe maoln nua %s go tobann" msgid "E144: non-numeric argument to :z" msgstr "E144: argint neamhuimhriil chun :z" -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: N cheadatear orduithe blaoisce i rvim" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "" "E146: N cheadatear litreacha mar theormharcir ar shloinn ionadaochta" diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index 9d5709e1ab..511f910b71 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -1220,10 +1220,6 @@ msgstr "" msgid "E144: non-numeric argument to :z" msgstr "E144: argomento non-numerico a :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Comandi Shell non permessi in rvim" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Le espressioni regolari non possono essere delimitate da lettere" diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index dc3c4368ab..523e9ca4e3 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -987,9 +987,6 @@ msgstr "E143: autocommandͽХåե %s ޤ" msgid "E144: non-numeric argument to :z" msgstr "E144: ǤϤʤ :z Ϥޤ" -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvimǤϥ륳ޥɤȤޤ" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: ɽʸǶڤ뤳ȤǤޤ" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index 4c5661464a..5a69d0c5bf 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -987,9 +987,6 @@ msgstr "E143: autocommandが予期せず新しいバッファ %s を削除しま msgid "E144: non-numeric argument to :z" msgstr "E144: 数ではない引数が :z に渡されました" -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvimではシェルコマンドを使えません" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: 正規表現は文字で区切ることができません" diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po index e90081bcfd..128b238f8b 100644 --- a/src/nvim/po/ko.UTF-8.po +++ b/src/nvim/po/ko.UTF-8.po @@ -1215,10 +1215,6 @@ msgstr "E143: Autocommand가 뜻 밖에 새 버퍼 %s을(를) 지웠습니다" msgid "E144: non-numeric argument to :z" msgstr "E144: 숫자가 아닌 인자가 :z에 주어졌습니다" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim에서는 쉘 명령을 사용할 수 없습니다" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: 정규표현식은 글자로 구분될 수 없습니다" diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po index b99e8ce465..34617ccf18 100644 --- a/src/nvim/po/nb.po +++ b/src/nvim/po/nb.po @@ -1231,10 +1231,6 @@ msgstr "E143: Autokommandoer slettet uventet den nye bufferen %s" msgid "E144: non-numeric argument to :z" msgstr "E144: Ikke-numerisk parameter til :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Skallkommandoer er ikke tillatt i rvim" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulre uttrykk kan ikke bli adskilt av bokstaver" diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po index 56bcd94e79..30f34508f5 100644 --- a/src/nvim/po/nl.po +++ b/src/nvim/po/nl.po @@ -1217,10 +1217,6 @@ msgstr "E143: 'Autocommands' hebben het nieuwe buffer %s onverwacht verwijderd" msgid "E144: non-numeric argument to :z" msgstr "E144: niet-numeriek argument voor :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: in rvim zijn shell-opdrachten zijn niet toegestaan" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: reguliere expressies kunnen niet begrensd worden door letters" diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po index b99e8ce465..34617ccf18 100644 --- a/src/nvim/po/no.po +++ b/src/nvim/po/no.po @@ -1231,10 +1231,6 @@ msgstr "E143: Autokommandoer slettet uventet den nye bufferen %s" msgid "E144: non-numeric argument to :z" msgstr "E144: Ikke-numerisk parameter til :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Skallkommandoer er ikke tillatt i rvim" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulre uttrykk kan ikke bli adskilt av bokstaver" diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po index a348bf6203..f5c452e924 100644 --- a/src/nvim/po/pl.UTF-8.po +++ b/src/nvim/po/pl.UTF-8.po @@ -1201,10 +1201,6 @@ msgstr "E143: Autokomendy nieoczekiwanie skasowały nowy bufor %s" msgid "E144: non-numeric argument to :z" msgstr "E144: nienumeryczny argument dla :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Komendy powłoki są niedozwolone w rvim" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Wzorce regularne nie mogą być rozgraniczane literami" diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po index 543f0bce27..4f39cb5bdb 100644 --- a/src/nvim/po/pt_BR.po +++ b/src/nvim/po/pt_BR.po @@ -4210,10 +4210,6 @@ msgstr "" msgid "E144: non-numeric argument to :z" msgstr "E144: argumento no-numrico passado a :z" -#: ../ex_cmds.c:3398 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Comandos do shell no so permitidos no rvim" - #: ../ex_cmds.c:3492 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Expresses regulares no podem ser delimitadas por letras" diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index e5be489d72..62f892d257 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -1205,10 +1205,6 @@ msgstr "E143: Автокоманды неожиданно убили новый msgid "E144: non-numeric argument to :z" msgstr "E144: Параметр команды :z должен быть числом" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Использование команд оболочки не допускается в rvim." - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Регулярные выражения не могут разделяться буквами" diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po index 74b8e1039c..ced343bf6b 100644 --- a/src/nvim/po/sk.cp1250.po +++ b/src/nvim/po/sk.cp1250.po @@ -1219,10 +1219,6 @@ msgstr "E143: Automatick prkazy neoakvane zmazali nov buffer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: neseln argument pre :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim nepovouje pouitie prkazov shellu" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulrne vrazy nesm by oddelen psmenami" diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po index d3f954f6d8..66b3d5abb1 100644 --- a/src/nvim/po/sk.po +++ b/src/nvim/po/sk.po @@ -1219,10 +1219,6 @@ msgstr "E143: Automatick prkazy neoakvane zmazali nov buffer %s" msgid "E144: non-numeric argument to :z" msgstr "E144: neseln argument pre :z" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim nepovouje pouitie prkazov shellu" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regulrne vrazy nesm by oddelen psmenami" diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index 88c5d18866..4c157658e5 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -424,7 +424,7 @@ msgstr "Опција 'dictionary' је празна" msgid "'thesaurus' option is empty" msgstr "Опција 'thesaurus' је празна" -#, c-format +#, c-format msgid "Scanning dictionary: %s" msgstr "Скенирање речника: %s" @@ -702,7 +702,7 @@ msgstr "E785: complete() може да се користи само у режи msgid "&Ok" msgstr "&Ок" -#, c-format +#, c-format msgid "+-%s%3ld line: " msgid_plural "+-%s%3ld lines: " msgstr[0] "+-%s%3ld линија: " @@ -810,7 +810,7 @@ msgstr "E677: Грешка при упису temp датотеке" msgid "E921: Invalid callback argument" msgstr "E921: Неисправан callback аргумент" -#, c-format +#, c-format msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s" msgstr "<%s>%s%s %d, Хекс %02x, Окт %03o, Дигр %s" @@ -818,11 +818,11 @@ msgstr "<%s>%s%s %d, Хекс %02x, Окт %03o, Дигр %s" msgid "<%s>%s%s %d, Hex %02x, Octal %03o" msgstr "<%s>%s%s %d, Хекс %02x, Октално %03o" -#, c-format +#, c-format msgid "> %d, Hex %04x, Oct %o, Digr %s" msgstr "> %d, Хекс %04x, Окт %o, Дигр %s" -#, c-format +#, c-format msgid "> %d, Hex %08x, Oct %o, Digr %s" msgstr "> %d, Хекс %08x, Окт %o, Дигр %s" @@ -981,9 +981,6 @@ msgstr "E143: Аутокоманде су неочекивано обрисал msgid "E144: non-numeric argument to :z" msgstr "E144: ненумерички аргумент за :z" -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Shell команде нису дозвољене у rvim" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Регуларни изрази не могу да се раздвајају словима" diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po index 4770db15de..db7bada888 100644 --- a/src/nvim/po/sv.po +++ b/src/nvim/po/sv.po @@ -2622,10 +2622,6 @@ msgstr "E143: Autokommandon tog ovntat bort ny buffert %s" msgid "E144: non-numeric argument to :z" msgstr "E144: ickenumeriskt argument till :z" -#: ../ex_cmds.c:3398 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Skalkommandon inte tilltna i rvim" - #: ../ex_cmds.c:3492 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Reguljra uttryck kan inte vara tskilda av bokstver" diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index f2179f4f1b..604e425bd0 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -1459,10 +1459,6 @@ msgstr "E143: Автокоманди несподівано знищили но msgid "E144: non-numeric argument to :z" msgstr "E144: нечисловий аргумент для :z" -msgid "" -"E145: Shell commands and some functionality not allowed in restricted mode" -msgstr "E145: У обмеженому режимі не дозволені команди оболонки і деяка функіональність" - msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Регулярні вирази не можна розділяти літерами" diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po index 7dbf19c263..a954ea6e34 100644 --- a/src/nvim/po/vi.po +++ b/src/nvim/po/vi.po @@ -1229,10 +1229,6 @@ msgstr "E143: Các lệnh tự động xóa bộ đệm mới ngoài ý muốn % msgid "E144: non-numeric argument to :z" msgstr "E144: Tham số của lệnh :z phải là số" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: Không cho phép sử dụng lệnh shell trong rvim." - #: ../ex_cmds.c:3498 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" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index 76204a43a8..542157002a 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -1222,10 +1222,6 @@ msgstr "E143: 自动命令意外地删除了新缓冲区 %s" msgid "E144: non-numeric argument to :z" msgstr "E144: :z 不接受非数字的参数" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim 中禁止使用 shell 命令" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: 正则表达式不能用字母作分界" diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index 3c1dd463b7..6a11b5e669 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -32,8 +32,8 @@ # So please change [行"] to [行 "] # # Q. How to use UTF8 mode on Win32? -# A. A simple configuration: -# set encoding=utf-8; let $LANG='zh_TW.UTF-8'; +# A. A simple configuration: +# set encoding=utf-8; let $LANG='zh_TW.UTF-8'; # (set langmenu=none or ..) # set fileencodings=ucs-bom,utf-8,japan,taiwan,prc # set fileencoding=taiwan (or utf-8) @@ -1262,10 +1262,6 @@ msgstr "E143: Autocommands 意外地刪除新緩衝區 %s" msgid "E144: non-numeric argument to :z" msgstr "E144: :z 不接受非數字的參數" -#: ../ex_cmds.c:3404 -msgid "E145: Shell commands not allowed in rvim" -msgstr "E145: rvim 中禁止使用 shell 命令" - #: ../ex_cmds.c:3498 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: Regular expression 無法用字母分隔 (?)" diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index d3ca65c53c..3d7d587ed2 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3608,11 +3608,15 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) } if (qf_buf != NULL) { // Use the existing quickfix buffer - (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, - ECMD_HIDE + ECMD_OLDBUF, oldwin); + if (do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, + ECMD_HIDE + ECMD_OLDBUF, oldwin) == FAIL) { + return FAIL; + } } else { // Create a new quickfix buffer - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); + if (do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin) == FAIL) { + return FAIL; + } } // Set the options for the quickfix buffer/window (if not already done) diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c new file mode 100644 index 0000000000..1fb7e3b434 --- /dev/null +++ b/src/nvim/runtime.c @@ -0,0 +1,824 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// @file runtime.c +/// +/// Management of runtime files (including packages) + +#include "nvim/vim.h" +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/option.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds2.h" +#include "nvim/misc1.h" +#include "nvim/os/os.h" +#include "nvim/runtime.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "runtime.c.generated.h" +#endif + + +/// ":runtime [what] {name}" +void ex_runtime(exarg_T *eap) +{ + char_u *arg = eap->arg; + char_u *p = skiptowhite(arg); + ptrdiff_t len = p - arg; + int flags = eap->forceit ? DIP_ALL : 0; + + if (STRNCMP(arg, "START", len) == 0) { + flags += DIP_START + DIP_NORTP; + arg = skipwhite(arg + len); + } else if (STRNCMP(arg, "OPT", len) == 0) { + flags += DIP_OPT + DIP_NORTP; + arg = skipwhite(arg + len); + } else if (STRNCMP(arg, "PACK", len) == 0) { + flags += DIP_START + DIP_OPT + DIP_NORTP; + arg = skipwhite(arg + len); + } else if (STRNCMP(arg, "ALL", len) == 0) { + flags += DIP_START + DIP_OPT; + arg = skipwhite(arg + len); + } + + source_runtime(arg, flags); +} + + +static void source_callback(char_u *fname, void *cookie) +{ + (void)do_source(fname, false, DOSO_NONE); +} + +/// Find the file "name" in all directories in "path" and invoke +/// "callback(fname, cookie)". +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// When "flags" has DIP_DIR: find directories instead of files. +/// When "flags" has DIP_ERR: give an error message if there is no match. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int do_in_path(char_u *path, char_u *name, int flags, + DoInRuntimepathCB callback, void *cookie) +{ + char_u *tail; + int num_files; + char_u **files; + int i; + bool did_one = false; + + // Make a copy of 'runtimepath'. Invoking the callback may change the + // value. + char_u *rtp_copy = vim_strsave(path); + char_u *buf = xmallocz(MAXPATHL); + { + if (p_verbose > 10 && name != NULL) { + verbose_enter(); + smsg(_("Searching for \"%s\" in \"%s\""), + (char *)name, (char *)path); + verbose_leave(); + } + + // Loop over all entries in 'runtimepath'. + char_u *rtp = rtp_copy; + while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { + // Copy the path from 'runtimepath' to buf[]. + copy_option_part(&rtp, buf, MAXPATHL, ","); + size_t buflen = STRLEN(buf); + + // Skip after or non-after directories. + if (flags & (DIP_NOAFTER | DIP_AFTER)) { + bool is_after = buflen >= 5 + && STRCMP(buf + buflen - 5, "after") == 0; + + if ((is_after && (flags & DIP_NOAFTER)) + || (!is_after && (flags & DIP_AFTER))) { + continue; + } + } + + if (name == NULL) { + (*callback)(buf, (void *)&cookie); + if (!did_one) { + did_one = (cookie == NULL); + } + } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { + add_pathsep((char *)buf); + tail = buf + STRLEN(buf); + + // Loop over all patterns in "name" + char_u *np = name; + while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { + // Append the pattern from "name" to buf[]. + assert(MAXPATHL >= (tail - buf)); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), + "\t "); + + if (p_verbose > 10) { + verbose_enter(); + smsg(_("Searching for \"%s\""), buf); + verbose_leave(); + } + + // Expand wildcards, invoke the callback for each match. + if (gen_expand_wildcards(1, &buf, &num_files, &files, + (flags & DIP_DIR) ? EW_DIR : EW_FILE) + == OK) { + for (i = 0; i < num_files; i++) { + (*callback)(files[i], cookie); + did_one = true; + if (!(flags & DIP_ALL)) { + break; + } + } + FreeWild(num_files, files); + } + } + } + } + } + xfree(buf); + xfree(rtp_copy); + if (!did_one && name != NULL) { + char *basepath = path == p_rtp ? "runtimepath" : "packpath"; + + if (flags & DIP_ERR) { + EMSG3(_(e_dirnotf), basepath, name); + } else if (p_verbose > 0) { + verbose_enter(); + smsg(_("not found in '%s': \"%s\""), basepath, name); + verbose_leave(); + } + } + + + return did_one ? OK : FAIL; +} + +/// Find "name" in "path". When found, invoke the callback function for +/// it: callback(fname, "cookie") +/// When "flags" has DIP_ALL repeat for all matches, otherwise only the first +/// one is used. +/// Returns OK when at least one match found, FAIL otherwise. +/// If "name" is NULL calls callback for each entry in "path". Cookie is +/// passed by reference in this case, setting it to NULL indicates that callback +/// has done its job. +int do_in_path_and_pp(char_u *path, char_u *name, int flags, + DoInRuntimepathCB callback, void *cookie) +{ + int done = FAIL; + + if ((flags & DIP_NORTP) == 0) { + done = do_in_path(path, name, flags, callback, cookie); + } + + if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { + char *start_dir = "pack/*/start/*/%s"; // NOLINT + size_t len = STRLEN(start_dir) + STRLEN(name); + char_u *s = xmallocz(len); + + vim_snprintf((char *)s, len, start_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + + if (done == FAIL|| (flags & DIP_ALL)) { + start_dir = "start/*/%s"; // NOLINT + len = STRLEN(start_dir) + STRLEN(name); + s = xmallocz(len); + + vim_snprintf((char *)s, len, start_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + } + } + + if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) { + char *opt_dir = "pack/*/opt/*/%s"; // NOLINT + size_t len = STRLEN(opt_dir) + STRLEN(name); + char_u *s = xmallocz(len); + + vim_snprintf((char *)s, len, opt_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + + if (done == FAIL || (flags & DIP_ALL)) { + opt_dir = "opt/*/%s"; // NOLINT + len = STRLEN(opt_dir) + STRLEN(name); + s = xmallocz(len); + + vim_snprintf((char *)s, len, opt_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + } + } + + return done; +} + +/// Just like do_in_path_and_pp(), using 'runtimepath' for "path". +int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, + void *cookie) +{ + return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); +} + +/// Source the file "name" from all directories in 'runtimepath'. +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int source_runtime(char_u *name, int flags) +{ + flags |= (flags & DIP_NORTP) ? 0 : DIP_START; + return source_in_path(p_rtp, name, flags); +} + +/// Just like source_runtime(), but use "path" instead of 'runtimepath'. +int source_in_path(char_u *path, char_u *name, int flags) +{ + return do_in_path_and_pp(path, name, flags, source_callback, NULL); +} + +// Expand wildcards in "pat" and invoke do_source() for each match. +static void source_all_matches(char_u *pat) +{ + int num_files; + char_u **files; + + if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { + for (int i = 0; i < num_files; i++) { + (void)do_source(files[i], false, DOSO_NONE); + } + FreeWild(num_files, files); + } +} + +/// Add the package directory to 'runtimepath' +static int add_pack_dir_to_rtp(char_u *fname) +{ + char_u *p4, *p3, *p2, *p1, *p; + char_u *buf = NULL; + char *afterdir = NULL; + int retval = FAIL; + + p4 = p3 = p2 = p1 = get_past_head(fname); + for (p = p1; *p; MB_PTR_ADV(p)) { + if (vim_ispathsep_nocolon(*p)) { + p4 = p3; p3 = p2; p2 = p1; p1 = p; + } + } + + // now we have: + // rtp/pack/name/start/name + // p4 p3 p2 p1 + // + // find the part up to "pack" in 'runtimepath' + p4++; // append pathsep in order to expand symlink + char_u c = *p4; + *p4 = NUL; + char *const ffname = fix_fname((char *)fname); + *p4 = c; + + if (ffname == NULL) { + return FAIL; + } + + // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences + // Also stop at the first "after" directory + size_t fname_len = strlen(ffname); + buf = try_malloc(MAXPATHL); + if (buf == NULL) { + goto theend; + } + const char *insp = NULL; + const char *after_insp = NULL; + for (const char *entry = (const char *)p_rtp; *entry != NUL; ) { + const char *cur_entry = entry; + + copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); + if (insp == NULL) { + add_pathsep((char *)buf); + char *const rtp_ffname = fix_fname((char *)buf); + if (rtp_ffname == NULL) { + goto theend; + } + bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0; + xfree(rtp_ffname); + if (match) { + // Insert "ffname" after this entry (and comma). + insp = entry; + } + } + + if ((p = (char_u *)strstr((char *)buf, "after")) != NULL + && p > buf + && vim_ispathsep(p[-1]) + && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { + if (insp == NULL) { + // Did not find "ffname" before the first "after" directory, + // insert it before this entry. + insp = cur_entry; + } + after_insp = cur_entry; + break; + } + } + + if (insp == NULL) { + // Both "fname" and "after" not found, append at the end. + insp = (const char *)p_rtp + STRLEN(p_rtp); + } + + // check if rtp/pack/name/start/name/after exists + afterdir = concat_fnames((char *)fname, "after", true); + size_t afterlen = 0; + if (os_isdir((char_u *)afterdir)) { + afterlen = strlen(afterdir) + 1; // add one for comma + } + + const size_t oldlen = STRLEN(p_rtp); + const size_t addlen = STRLEN(fname) + 1; // add one for comma + const size_t new_rtp_capacity = oldlen + addlen + afterlen + 1; + // add one for NUL ------------------------------------------^ + char *const new_rtp = try_malloc(new_rtp_capacity); + if (new_rtp == NULL) { + goto theend; + } + + // We now have 'rtp' parts: {keep}{keep_after}{rest}. + // Create new_rtp, first: {keep},{fname} + size_t keep = (size_t)(insp - (const char *)p_rtp); + memmove(new_rtp, p_rtp, keep); + size_t new_rtp_len = keep; + if (*insp == NUL) { + new_rtp[new_rtp_len++] = ','; // add comma before + } + memmove(new_rtp + new_rtp_len, fname, addlen - 1); + new_rtp_len += addlen - 1; + if (*insp != NUL) { + new_rtp[new_rtp_len++] = ','; // add comma after + } + + if (afterlen > 0 && after_insp != NULL) { + size_t keep_after = (size_t)(after_insp - (const char *)p_rtp); + + // Add to new_rtp: {keep},{fname}{keep_after},{afterdir} + memmove(new_rtp + new_rtp_len, p_rtp + keep, keep_after - keep); + new_rtp_len += keep_after - keep; + memmove(new_rtp + new_rtp_len, afterdir, afterlen - 1); + new_rtp_len += afterlen - 1; + new_rtp[new_rtp_len++] = ','; + keep = keep_after; + } + + if (p_rtp[keep] != NUL) { + // Append rest: {keep},{fname}{keep_after},{afterdir}{rest} + memmove(new_rtp + new_rtp_len, p_rtp + keep, oldlen - keep + 1); + } else { + new_rtp[new_rtp_len] = NUL; + } + + if (afterlen > 0 && after_insp == NULL) { + // Append afterdir when "after" was not found: + // {keep},{fname}{rest},{afterdir} + xstrlcat(new_rtp, ",", new_rtp_capacity); + xstrlcat(new_rtp, afterdir, new_rtp_capacity); + } + + set_option_value("rtp", 0L, new_rtp, 0); + xfree(new_rtp); + retval = OK; + +theend: + xfree(buf); + xfree(ffname); + xfree(afterdir); + return retval; +} + +/// Load scripts in "plugin" and "ftdetect" directories of the package. +static int load_pack_plugin(char_u *fname) +{ + static const char *plugpat = "%s/plugin/**/*.vim"; // NOLINT + static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT + + int retval = FAIL; + char *const ffname = fix_fname((char *)fname); + size_t len = strlen(ffname) + STRLEN(ftpat); + char_u *pat = try_malloc(len + 1); + if (pat == NULL) { + goto theend; + } + vim_snprintf((char *)pat, len, plugpat, ffname); + source_all_matches(pat); + + char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); + + // If runtime/filetype.vim wasn't loaded yet, the scripts will be + // found when it loads. + if (eval_to_number(cmd) > 0) { + do_cmdline_cmd("augroup filetypedetect"); + vim_snprintf((char *)pat, len, ftpat, ffname); + source_all_matches(pat); + do_cmdline_cmd("augroup END"); + } + xfree(cmd); + xfree(pat); + retval = OK; + +theend: + xfree(ffname); + + return retval; +} + +// used for "cookie" of add_pack_plugin() +static int APP_ADD_DIR; +static int APP_LOAD; +static int APP_BOTH; + +static void add_pack_plugin(char_u *fname, void *cookie) +{ + if (cookie != &APP_LOAD) { + char *buf = xmalloc(MAXPATHL); + bool found = false; + + const char *p = (const char *)p_rtp; + while (*p != NUL) { + copy_option_part((char_u **)&p, (char_u *)buf, MAXPATHL, ","); + if (path_fnamecmp(buf, (char *)fname) == 0) { + found = true; + break; + } + } + xfree(buf); + if (!found) { + // directory is not yet in 'runtimepath', add it + if (add_pack_dir_to_rtp(fname) == FAIL) { + return; + } + } + } + + if (cookie != &APP_ADD_DIR) { + load_pack_plugin(fname); + } +} + +/// Add all packages in the "start" directory to 'runtimepath'. +void add_pack_start_dirs(void) +{ + do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_ADD_DIR); + do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_ADD_DIR); +} + +/// Load plugins from all packages in the "start" directory. +void load_start_packages(void) +{ + did_source_packages = true; + do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_LOAD); + do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_LOAD); +} + +// ":packloadall" +// Find plugins in the package directories and source them. +void ex_packloadall(exarg_T *eap) +{ + if (!did_source_packages || eap->forceit) { + // First do a round to add all directories to 'runtimepath', then load + // the plugins. This allows for plugins to use an autoload directory + // of another plugin. + add_pack_start_dirs(); + load_start_packages(); + } +} + +/// ":packadd[!] {name}" +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 = STRLEN(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, (char_u *)pat, + DIP_ALL + DIP_DIR + + (round == 2 && res == FAIL ? DIP_ERR : 0), + add_pack_plugin, eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + xfree(pat); + } +} + +/// Append string with escaped commas +static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t shift = 0; + for (size_t i = 0; i < len; i++) { + if (src[i] == ',') { + dest[i + shift++] = '\\'; + } + dest[i + shift] = src[i]; + } + return &dest[len + shift]; +} + +/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some +/// suffixes +/// +/// @param[in] val ENV_SEPCHAR-separated array value. +/// @param[in] common_suf_len Length of the common suffix which is appended to +/// each item in the array, twice. +/// @param[in] single_suf_len Length of the suffix which is appended to each +/// item in the array once. +/// +/// @return Length of the ENV_SEPCHAR-separated string array that contains each +/// item in the original array twice with suffixes with given length +/// (common_suf is present after each new item, single_suf is present +/// after half of the new items) and with commas after each item, commas +/// inside the values are escaped. +static inline size_t compute_double_env_sep_len(const char *const val, + const size_t common_suf_len, + const size_t single_suf_len) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (val == NULL || *val == NUL) { + return 0; + } + size_t ret = 0; + const void *iter = NULL; + do { + size_t dir_len; + const char *dir; + iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len + + !after_pathsep(dir, dir + dir_len)) * 2 + + single_suf_len); + } + } while (iter != NULL); + return ret; +} + + +#define NVIM_SIZE (sizeof("nvim") - 1) +/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one +/// +/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in +/// addition to suf1 and suf2. +/// +/// @param[in,out] dest Destination comma-separated array. +/// @param[in] val Source ENV_SEPCHAR-separated array. +/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it +/// directory separator is appended. Suffix must not contain +/// commas. +/// @param[in] len1 Length of the suf1. +/// @param[in] suf2 If not NULL, another suffix appended to destination. Again +/// with directory separator behind. Suffix must not contain +/// commas. +/// @param[in] len2 Length of the suf2. +/// @param[in] forward If true, iterate over val in forward direction. +/// Otherwise in reverse. +/// +/// @return (dest + appended_characters_length) +static inline char *add_env_sep_dirs(char *dest, const char *const val, + const char *const suf1, const size_t len1, + const char *const suf2, const size_t len2, + const bool forward) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) +{ + if (val == NULL || *val == NUL) { + return dest; + } + const void *iter = NULL; + do { + size_t dir_len; + const char *dir; + iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter, + &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + dest = strcpy_comma_escaped(dest, dir, dir_len); + if (!after_pathsep(dest - 1, dest)) { + *dest++ = PATHSEP; + } + memmove(dest, "nvim", NVIM_SIZE); + dest += NVIM_SIZE; + if (suf1 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf1, len1); + dest += len1; + if (suf2 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf2, len2); + dest += len2; + } + } + *dest++ = ','; + } + } while (iter != NULL); + return dest; +} + +/// Adds directory `dest` to a comma-separated list of directories. +/// +/// Commas in the added directory are escaped. +/// +/// Windows: Appends "nvim-data" instead of "nvim" if `type` is kXDGDataHome. +/// +/// @see get_xdg_home +/// +/// @param[in,out] dest Destination comma-separated array. +/// @param[in] dir Directory to append. +/// @param[in] type Decides whether to append "nvim" (Win: or "nvim-data"). +/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it +/// directory separator is appended. Suffix must not contain +/// commas. +/// @param[in] len1 Length of the suf1. +/// @param[in] suf2 If not NULL, another suffix appended to destination. Again +/// with directory separator behind. Suffix must not contain +/// commas. +/// @param[in] len2 Length of the suf2. +/// @param[in] forward If true, iterate over val in forward direction. +/// Otherwise in reverse. +/// +/// @return (dest + appended_characters_length) +static inline char *add_dir(char *dest, const char *const dir, + const size_t dir_len, const XDGVarType type, + const char *const suf1, const size_t len1, + const char *const suf2, const size_t len2) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (dir == NULL || dir_len == 0) { + return dest; + } + dest = strcpy_comma_escaped(dest, dir, dir_len); + bool append_nvim = (type == kXDGDataHome || type == kXDGConfigHome); + if (append_nvim) { + if (!after_pathsep(dest - 1, dest)) { + *dest++ = PATHSEP; + } +#if defined(WIN32) + size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE); + memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size); + dest += size; +#else + memmove(dest, "nvim", NVIM_SIZE); + dest += NVIM_SIZE; +#endif + if (suf1 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf1, len1); + dest += len1; + if (suf2 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf2, len2); + dest += len2; + } + } + } + *dest++ = ','; + return dest; +} + +char *get_lib_dir(void) +{ + // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty + // in an appimage build + if (strlen(default_lib_dir) != 0 + && os_isdir((const char_u *)default_lib_dir)) { + return xstrdup(default_lib_dir); + } + + // Find library path relative to the nvim binary: ../lib/nvim/ + char exe_name[MAXPATHL]; + vim_get_prefix_from_exepath(exe_name); + if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) { + return xstrdup(exe_name); + } + return NULL; +} + +/// Determine the startup value for &runtimepath +/// +/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing +/// configuration and data files in the same path. #4403 +/// +/// @param clean_arg Nvim was started with --clean. +/// @return allocated string with the value +char *runtimepath_default(bool clean_arg) +{ + size_t rtp_size = 0; + char *const data_home = clean_arg + ? NULL + : stdpaths_get_xdg_var(kXDGDataHome); + char *const config_home = clean_arg + ? NULL + : stdpaths_get_xdg_var(kXDGConfigHome); + char *const vimruntime = vim_getenv("VIMRUNTIME"); + char *const libdir = get_lib_dir(); + char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); + char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); +#define SITE_SIZE (sizeof("site") - 1) +#define AFTER_SIZE (sizeof("after") - 1) + size_t data_len = 0; + size_t config_len = 0; + size_t vimruntime_len = 0; + size_t libdir_len = 0; + if (data_home != NULL) { + data_len = strlen(data_home); + if (data_len != 0) { +#if defined(WIN32) + size_t nvim_size = (sizeof("nvim-data") - 1); +#else + size_t nvim_size = NVIM_SIZE; +#endif + rtp_size += ((data_len + memcnt(data_home, ',', data_len) + + nvim_size + 1 + SITE_SIZE + 1 + + !after_pathsep(data_home, data_home + data_len)) * 2 + + AFTER_SIZE + 1); + } + } + if (config_home != NULL) { + config_len = strlen(config_home); + if (config_len != 0) { + rtp_size += ((config_len + memcnt(config_home, ',', config_len) + + NVIM_SIZE + 1 + + !after_pathsep(config_home, config_home + config_len)) * 2 + + AFTER_SIZE + 1); + } + } + if (vimruntime != NULL) { + vimruntime_len = strlen(vimruntime); + if (vimruntime_len != 0) { + rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1; + } + } + if (libdir != NULL) { + libdir_len = strlen(libdir); + if (libdir_len != 0) { + rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; + } + } + rtp_size += compute_double_env_sep_len(data_dirs, + NVIM_SIZE + 1 + SITE_SIZE + 1, + AFTER_SIZE + 1); + rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1, + AFTER_SIZE + 1); + if (rtp_size == 0) { + return NULL; + } + char *const rtp = xmalloc(rtp_size); + char *rtp_cur = rtp; + rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, + NULL, 0, NULL, 0); + rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true); + rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, + "site", SITE_SIZE, NULL, 0); + rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0, + true); + rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, + NULL, 0, NULL, 0); + rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); + rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, + "after", AFTER_SIZE, false); + rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, + "site", SITE_SIZE, "after", AFTER_SIZE); + rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0, + false); + rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, + "after", AFTER_SIZE, NULL, 0); + // Strip trailing comma. + rtp_cur[-1] = NUL; + assert((size_t)(rtp_cur - rtp) == rtp_size); +#undef SITE_SIZE +#undef AFTER_SIZE + xfree(data_dirs); + xfree(config_dirs); + xfree(data_home); + xfree(config_home); + xfree(vimruntime); + xfree(libdir); + + return rtp; +} +#undef NVIM_SIZE diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h new file mode 100644 index 0000000000..b40c2b670e --- /dev/null +++ b/src/nvim/runtime.h @@ -0,0 +1,18 @@ +#ifndef NVIM_RUNTIME_H +#define NVIM_RUNTIME_H + +#include <stdbool.h> + +#include "nvim/ex_docmd.h" + +typedef void (*DoInRuntimepathCB)(char_u *, void *); + +// last argument for do_source() +#define DOSO_NONE 0 +#define DOSO_VIMRC 1 // loading vimrc file + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "runtime.h.generated.h" +#endif +#endif // NVIM_RUNTIME_H diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 8998f9037e..4373d6d5a8 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -173,7 +173,9 @@ static bool provider_invoke(NS ns_id, const char *name, LuaRef ref, Error err = ERROR_INIT; textlock++; + provider_active = true; Object ret = nlua_call_ref(ref, name, args, true, &err); + provider_active = false; textlock--; if (!ERROR_SET(&err) @@ -2194,6 +2196,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } if (wp->w_p_spell + && foldinfo.fi_lines == 0 && *wp->w_s->b_p_spl != NUL && !GA_EMPTY(&wp->w_s->b_langp) && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { @@ -2833,9 +2836,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, * required when 'linebreak' is also set. */ if (tocol == vcol) tocol += n_extra; - /* combine 'showbreak' with 'cursorline' */ + // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { - char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUL)); + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUL), char_attr); } } } @@ -5203,7 +5206,7 @@ win_redr_custom ( fillchar = ' '; attr = HL_ATTR(HLF_TPF); maxwidth = Columns; - use_sandbox = was_set_insecurely((char_u *)"tabline", 0); + use_sandbox = was_set_insecurely(wp, (char_u *)"tabline", 0); } else { row = W_ENDROW(wp); fillchar = fillchar_status(&attr, wp); @@ -5234,14 +5237,14 @@ win_redr_custom ( attr = HL_ATTR(HLF_MSG); } - use_sandbox = was_set_insecurely((char_u *)"rulerformat", 0); + use_sandbox = was_set_insecurely(wp, (char_u *)"rulerformat", 0); } else { if (*wp->w_p_stl != NUL) stl = wp->w_p_stl; else stl = p_stl; - use_sandbox = was_set_insecurely((char_u *)"statusline", - *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); + use_sandbox = was_set_insecurely( + wp, (char_u *)"statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); } col += wp->w_wincol; diff --git a/src/nvim/search.c b/src/nvim/search.c index f979889540..90e1e25de2 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1658,6 +1658,48 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) return found; } +/// Check matchpairs option for "*initc". +/// If there is a match set "*initc" to the matching character and "*findc" to +/// the opposite character. Set "*backwards" to the direction. +/// When "switchit" is true swap the direction. +static void find_mps_values(int *initc, int *findc, bool *backwards, + bool switchit) + FUNC_ATTR_NONNULL_ALL +{ + char_u *ptr = curbuf->b_p_mps; + + while (*ptr != NUL) { + if (utf_ptr2char(ptr) == *initc) { + if (switchit) { + *findc = *initc; + *initc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); + *backwards = true; + } else { + *findc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); + *backwards = false; + } + return; + } + char_u *prev = ptr; + ptr += utfc_ptr2len(ptr) + 1; + if (utf_ptr2char(ptr) == *initc) { + if (switchit) { + *findc = *initc; + *initc = utf_ptr2char(prev); + *backwards = false; + } else { + *findc = utf_ptr2char(prev); + *backwards = true; + } + return; + } + ptr += utfc_ptr2len(ptr); + if (*ptr == ',') { + ptr++; + } + } +} + /* * findmatchlimit -- find the matching paren or brace, if it exists within * maxtravel lines of the cursor. A maxtravel of 0 means search until falling @@ -1684,7 +1726,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) static pos_T pos; // current search position int findc = 0; // matching brace int count = 0; // cumulative number of braces - int backwards = false; // init for gcc + bool backwards = false; // init for gcc bool raw_string = false; // search for raw string bool inquote = false; // true when inside quotes char_u *ptr; @@ -1729,9 +1771,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) raw_string = (initc == 'R'); initc = NUL; } else if (initc != '#' && initc != NUL) { - find_mps_values(&initc, &findc, &backwards, TRUE); - if (findc == NUL) + find_mps_values(&initc, &findc, &backwards, true); + if (findc == NUL) { return NULL; + } } else { /* * Either initc is '#', or no initc was given and we need to look @@ -1759,20 +1802,20 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) else if (linep[pos.col] == '/') { if (linep[pos.col + 1] == '*') { comment_dir = FORWARD; - backwards = FALSE; + backwards = false; pos.col++; } else if (pos.col > 0 && linep[pos.col - 1] == '*') { comment_dir = BACKWARD; - backwards = TRUE; + backwards = true; pos.col--; } } else if (linep[pos.col] == '*') { if (linep[pos.col + 1] == '/') { comment_dir = BACKWARD; - backwards = TRUE; + backwards = true; } else if (pos.col > 0 && linep[pos.col - 1] == '/') { comment_dir = FORWARD; - backwards = FALSE; + backwards = false; } } } @@ -1794,9 +1837,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (initc == NUL) break; - find_mps_values(&initc, &findc, &backwards, FALSE); - if (findc) + find_mps_values(&initc, &findc, &backwards, false); + if (findc) { break; + } pos.col += utfc_ptr2len(linep + pos.col); } if (!findc) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 5e54354ea8..e91d560284 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7563,14 +7563,13 @@ static void syn_unadd_group(void) /// @see syn_attr2entry int syn_id2attr(int hl_id) { - struct hl_group *sgp; - hl_id = syn_get_final_id(hl_id); - int attr = ns_get_hl(-1, hl_id, false); + struct hl_group *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + + int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); if (attr >= 0) { return attr; } - sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one return sgp->sg_attr; } @@ -7583,7 +7582,6 @@ int syn_id2attr(int hl_id) int syn_get_final_id(int hl_id) { int count; - struct hl_group *sgp; if (hl_id > highlight_ga.ga_len || hl_id < 1) return 0; /* Can be called from eval!! */ @@ -7593,19 +7591,20 @@ int syn_get_final_id(int hl_id) * Look out for loops! Break after 100 links. */ for (count = 100; --count >= 0; ) { + struct hl_group *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + // ACHTUNG: when using "tmp" attribute (no link) the function might be // called twice. it needs be smart enough to remember attr only to // syn_id2attr time - int check = ns_get_hl(-1, hl_id, true); + int check = ns_get_hl(-1, hl_id, true, sgp->sg_set); if (check == 0) { - return 0; // how dare! it broke the link! + return hl_id; // how dare! it broke the link! } else if (check > 0) { hl_id = check; continue; } - sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { break; } diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 04a678eeb8..1fa7eeaea0 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -696,6 +696,7 @@ func Test_OptionSet_diffmode_close() call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4']) call assert_fails(':diffthis', 'E788') call assert_equal(1, &diff) + set diffopt-=closeoff bw! call assert_fails(':diffoff!', 'E788') bw! @@ -1856,6 +1857,29 @@ func Test_FileChangedShell_reload() call delete('Xchanged') endfunc +func LogACmd() + call add(g:logged, line('$')) +endfunc + +func Test_TermChanged() + throw 'skipped: Nvim does not support TermChanged event' + CheckNotGui + + enew! + tabnew + call setline(1, ['a', 'b', 'c', 'd']) + $ + au TermChanged * call LogACmd() + let g:logged = [] + let term_save = &term + set term=xterm + call assert_equal([1, 4], g:logged) + + au! TermChanged + let &term = term_save + bwipe! +endfunc + " Test for FileReadCmd autocmd func Test_autocmd_FileReadCmd() func ReadFileCmd() @@ -1910,4 +1934,26 @@ func Test_autocmd_sigusr1() unlet g:sigusr1_passed endfunc +" Test for the temporary internal window used to execute autocmds +func Test_autocmd_window() + %bw! + edit one.txt + tabnew two.txt + let g:blist = [] + augroup aucmd_win_test + au! + au BufEnter * call add(g:blist, [expand('<afile>'), + \ win_gettype(bufwinnr(expand('<afile>')))]) + augroup END + + doautoall BufEnter + call assert_equal([['one.txt', 'autocmd'], ['two.txt', '']], g:blist) + + augroup aucmd_win_test + au! + augroup END + augroup! aucmd_win_test + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index e3c42a4fe3..81f653c393 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -796,6 +796,29 @@ func Test_cmdwin_feedkeys() call feedkeys("q:\<CR>", 'x') endfunc +" Tests for the issues fixed in 7.4.441. +" When 'cedit' is set to Ctrl-C, opening the command window hangs Vim +func Test_cmdwin_cedit() + exe "set cedit=\<C-c>" + normal! : + call assert_equal(1, winnr('$')) + + let g:cmd_wintype = '' + func CmdWinType() + let g:cmd_wintype = getcmdwintype() + let g:wintype = win_gettype() + return '' + endfunc + + call feedkeys("\<C-c>a\<C-R>=CmdWinType()\<CR>\<CR>") + echo input('') + call assert_equal('@', g:cmd_wintype) + call assert_equal('command', g:wintype) + + set cedit&vim + delfunc CmdWinType +endfunc + func Test_buffers_lastused() " check that buffers are sorted by time when wildmode has lastused edit bufc " oldest diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index af8482bdbe..52b5884c8b 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -86,6 +86,8 @@ let s:filename_checks = { \ 'bzr': ['bzr_log.any'], \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'], \ 'cabal': ['file.cabal'], + \ 'cabalconfig': ['cabal.config'], + \ 'cabalproject': ['cabal.project', 'cabal.project.local'], \ 'calendar': ['calendar'], \ 'catalog': ['catalog', 'sgml.catalogfile'], \ 'cdl': ['file.cdl'], @@ -198,8 +200,8 @@ let s:filename_checks = { \ 'gsp': ['file.gsp'], \ 'gtkrc': ['.gtkrc', 'gtkrc'], \ 'haml': ['file.haml'], - \ 'hamster': ['file.hsc', 'file.hsm'], - \ 'haskell': ['file.hs', 'file.hs-boot'], + \ 'hamster': ['file.hsm'], + \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot'], \ 'haste': ['file.ht'], \ 'hastepreproc': ['file.htpp'], \ 'hb': ['file.hb'], diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 00e42733a7..a80a73161f 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -516,7 +516,7 @@ func Test_termguicolors() if !exists('+termguicolors') return endif - if has('vtp') && !has('vcon') + if has('vtp') && !has('vcon') && !has('gui_running') " Win32: 'guicolors' doesn't work without virtual console. call assert_fails('set termguicolors', 'E954:') return diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index fb464d95ea..4ee16558a0 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -735,20 +735,25 @@ func Test_popup_and_preview_autocommand() endfunc func Test_popup_and_previewwindow_dump() - if !CanRunVimInTerminal() - return - endif - call writefile([ - \ 'set previewheight=9', - \ 'silent! pedit', - \ 'call setline(1, map(repeat(["ab"], 10), "v:val. v:key"))', - \ 'exec "norm! G\<C-E>\<C-E>"', - \ ], 'Xscript') + 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\<C-E>\<C-E>" + END + call writefile(lines, 'Xscript') let buf = RunVimInTerminal('-S Xscript', {}) + " wait for the script to finish + call term_wait(buf) + " Test that popup and previewwindow do not overlap. - call term_sendkeys(buf, "o\<C-X>\<C-N>") - sleep 100m + call term_sendkeys(buf, "o") + call term_wait(buf, 100) + call term_sendkeys(buf, "\<C-X>\<C-N>") call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {}) call term_sendkeys(buf, "\<Esc>u") diff --git a/src/nvim/testdir/test_preview.vim b/src/nvim/testdir/test_preview.vim index 91923fb1e9..6c4ae414d3 100644 --- a/src/nvim/testdir/test_preview.vim +++ b/src/nvim/testdir/test_preview.vim @@ -11,3 +11,47 @@ func Test_Psearch() call assert_equal(wincount, winnr('$')) bwipe endfunc + +func Test_window_preview() + " Open a preview window + pedit Xa + 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()) + + " 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_from_help() + filetype on + call writefile(['/* some C code */'], 'Xpreview.c') + help + pedit Xpreview.c + wincmd P + call assert_equal(1, &previewwindow) + call assert_equal('c', &filetype) + wincmd z + + filetype off + close + call delete('Xpreview.c') +endfunc + +func Test_multiple_preview_windows() + new + set previewwindow + new + call assert_fails('set previewwindow', 'E590:') + %bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 3e97cd2dd0..9c5f0777c6 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -4423,4 +4423,39 @@ func Test_add_invalid_entry_with_qf_window() cclose endfunc +" Test for very weird problem: autocommand causes a failure, resulting opening +" the quickfix window to fail. This still splits the window, but otherwise +" should not mess up buffers. +func Test_quickfix_window_fails_to_open() + CheckScreendump + + let lines =<< trim END + anything + try + anything + endtry + END + call writefile(lines, 'XquickfixFails') + + let lines =<< trim END + split XquickfixFails + silent vimgrep anything % + normal o + au BufLeave * ++once source XquickfixFails + " This will trigger the autocommand, which causes an error, what follows + " is aborted but the window was already split. + silent! cwindow + END + call writefile(lines, 'XtestWinFails') + let buf = RunVimInTerminal('-S XtestWinFails', #{rows: 13}) + call VerifyScreenDump(buf, 'Test_quickfix_window_fails', {}) + + " clean up + call term_sendkeys(buf, ":bwipe!\<CR>") + call term_wait(buf) + call StopVimInTerminal(buf) + call delete('XtestWinFails') + call delete('XquickfixFails') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 12bec745a8..6214975ef5 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -2,6 +2,8 @@ source shared.vim source screendump.vim +source term_util.vim +source check.vim " Check that loading startup.vim works. func Test_startup_script() @@ -278,6 +280,68 @@ func Test_V_arg() " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline 1: \" The default vimrc file\..* verbose=15\n", out) endfunc +" Test the '-q [errorfile]' argument. +func Test_q_arg() + CheckFeature quickfix + + let lines =<< trim END + /* some file with an error */ + main() { + functionCall(arg; arg, arg); + return 666 + } + END + call writefile(lines, 'Xbadfile.c') + + let after =<< trim [CODE] + call writefile([&errorfile, string(getpos("."))], "Xtestout") + copen + w >> Xtestout + qall + [CODE] + + " Test with default argument '-q'. + call assert_equal('errors.err', &errorfile) + call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'errors.err') + if RunVim([], after, '-q') + let lines = readfile('Xtestout') + call assert_equal(['errors.err', + \ '[0, 4, 12, 0]', + \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"], + \ lines) + endif + call delete('Xtestout') + call delete('errors.err') + + " Test with explicit argument '-q Xerrors' (with space). + call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'Xerrors') + if RunVim([], after, '-q Xerrors') + let lines = readfile('Xtestout') + call assert_equal(['Xerrors', + \ '[0, 4, 12, 0]', + \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"], + \ lines) + endif + call delete('Xtestout') + + " Test with explicit argument '-qXerrors' (without space). + if RunVim([], after, '-qXerrors') + let lines = readfile('Xtestout') + call assert_equal(['Xerrors', + \ '[0, 4, 12, 0]', + \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"], + \ lines) + endif + + " Test with a non-existing error file (exits with value 3) + let out = system(GetVimCommand() .. ' -q xyz.err') + call assert_equal(3, v:shell_error) + + call delete('Xbadfile.c') + call delete('Xtestout') + call delete('Xerrors') +endfunc + " Test the -V[N]{filename} argument to set the 'verbose' option to N " and set 'verbosefile' to filename. func Test_V_file_arg() @@ -408,13 +472,15 @@ func Test_invalid_args() let out = split(system(GetVimCommand() .. ' - xxx -cq'), "\n") call assert_equal(0, v:shell_error) - " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'. - for opt in ['-t', '-q'] - let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n") - call assert_equal(1, v:shell_error) - call assert_equal('nvim: Too many edit arguments: "' .. opt .. '"', out[0]) - call assert_equal('More info with "nvim -h"', out[1]) - endfor + if has('quickfix') + " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'. + for opt in ['-t', '-q'] + let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n") + call assert_equal(1, v:shell_error) + call assert_equal('nvim: Too many edit arguments: "' .. opt .. '"', out[0]) + call assert_equal('More info with "nvim -h"', out[1]) + endfor + endif for opt in [' -cq', ' --cmd q', ' +', ' -S foo'] let out = split(system(GetVimCommand() .. repeat(opt, 11)), "\n") @@ -515,6 +581,27 @@ func Test_read_stdin() call delete('Xtestout') endfunc +func Test_set_shell() + let after =<< trim [CODE] + call writefile([&shell], "Xtestout") + quit! + [CODE] + + if has('win32') + let $SHELL = 'C:\with space\cmd.exe' + let expected = '"C:\with space\cmd.exe"' + else + let $SHELL = '/bin/with space/sh' + let expected = '"/bin/with space/sh"' + endif + + if RunVimPiped([], after, '', '') + let lines = readfile('Xtestout') + call assert_equal(expected, lines[0]) + endif + call delete('Xtestout') +endfunc + func Test_progpath() " Tests normally run with "./vim" or "../vim", these must have been expanded " to a full path. diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index d3c0594c03..424cb4abd0 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -1,5 +1,8 @@ " Tests for system() and systemlist() +source shared.vim +source check.vim + function! Test_System() if !executable('echo') || !executable('cat') || !executable('wc') return @@ -88,3 +91,54 @@ function! Test_system_exmode() let a = system(v:progpath. cmd) call assert_notequal(0, v:shell_error) endfunc + +func Test_system_with_shell_quote() + throw 'skipped: enable after porting method patches' + CheckMSWindows + + call mkdir('Xdir with spaces', 'p') + call system('copy "%COMSPEC%" "Xdir with spaces\cmd.exe"') + + let shell_save = &shell + let shellxquote_save = &shellxquote + try + " Set 'shell' always needs noshellslash. + let shellslash_save = &shellslash + set noshellslash + let shell_tests = [ + \ expand('$COMSPEC'), + \ '"' . fnamemodify('Xdir with spaces\cmd.exe', ':p') . '"', + \] + let &shellslash = shellslash_save + + let sxq_tests = ['', '(', '"'] + + " Matrix tests: 'shell' * 'shellxquote' + for shell in shell_tests + let &shell = shell + for sxq in sxq_tests + let &shellxquote = sxq + + let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote) + + try + let out = 'echo 123'->system() + catch + call assert_report(printf('%s: %s', msg, v:exception)) + continue + endtry + + " On Windows we may get a trailing space and CR. + if out != "123 \n" + call assert_equal("123\n", out, msg) + endif + + endfor + endfor + + finally + let &shell = shell_save + let &shellxquote = shellxquote_save + call delete('Xdir with spaces', 'rf') + endtry +endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 734f264672..7f50894f66 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -442,4 +442,302 @@ func Test_visual_put_in_block() bwipe! endfunc +" Visual modes (v V CTRL-V) followed by an operator; count; repeating +func Test_visual_mode_op() + new + call append(0, '') + + call setline(1, 'apple banana cherry') + call cursor(1, 1) + normal lvld.l3vd. + call assert_equal('a y', getline(1)) + + call setline(1, ['line 1 line 1', 'line 2 line 2', 'line 3 line 3', + \ 'line 4 line 4', 'line 5 line 5', 'line 6 line 6']) + call cursor(1, 1) + exe "normal Vcnewline\<Esc>j.j2Vd." + call assert_equal(['newline', 'newline'], getline(1, '$')) + + call deletebufline('', 1, '$') + call setline(1, ['xxxxxxxxxxxxx', 'xxxxxxxxxxxxx', 'xxxxxxxxxxxxx', + \ 'xxxxxxxxxxxxx']) + exe "normal \<C-V>jlc \<Esc>l.l2\<C-V>c----\<Esc>l." + call assert_equal([' --------x', + \ ' --------x', + \ 'xxxx--------x', + \ 'xxxx--------x'], getline(1, '$')) + + bwipe! +endfunc + +" Visual mode maps (movement and text object) +" Visual mode maps; count; repeating +" - Simple +" - With an Ex command (custom text object) +func Test_visual_mode_maps() + new + call append(0, '') + + func SelectInCaps() + let [line1, col1] = searchpos('\u', 'bcnW') + let [line2, col2] = searchpos('.\u', 'nW') + call setpos("'<", [0, line1, col1, 0]) + call setpos("'>", [0, line2, col2, 0]) + normal! gv + endfunction + + vnoremap W /\u/s-1<CR> + vnoremap iW :<C-U>call SelectInCaps()<CR> + + call setline(1, 'KiwiRaspberryDateWatermelonPeach') + call cursor(1, 1) + exe "normal vWcNo\<Esc>l.fD2vd." + call assert_equal('NoNoberryach', getline(1)) + + call setline(1, 'JambuRambutanBananaTangerineMango') + call cursor(1, 1) + exe "normal llviWc-\<Esc>l.l2vdl." + call assert_equal('--ago', getline(1)) + + vunmap W + vunmap iW + bwipe! + delfunc SelectInCaps +endfunc + +" Operator-pending mode maps (movement and text object) +" - Simple +" - With Ex command moving the cursor +" - With Ex command and Visual selection (custom text object) +func Test_visual_oper_pending_mode_maps() + new + call append(0, '') + + func MoveToCap() + call search('\u', 'W') + endfunction + + func SelectInCaps() + let [line1, col1] = searchpos('\u', 'bcnW') + let [line2, col2] = searchpos('.\u', 'nW') + call setpos("'<", [0, line1, col1, 0]) + call setpos("'>", [0, line2, col2, 0]) + normal! gv + endfunction + + onoremap W /\u/<CR> + onoremap <Leader>W :<C-U>call MoveToCap()<CR> + onoremap iW :<C-U>call SelectInCaps()<CR> + + call setline(1, 'PineappleQuinceLoganberryOrangeGrapefruitKiwiZ') + call cursor(1, 1) + exe "normal cW-\<Esc>l.l2.l." + call assert_equal('----Z', getline(1)) + + call setline(1, 'JuniperDurianZ') + call cursor(1, 1) + exe "normal g?\WfD." + call assert_equal('WhavcreQhevnaZ', getline(1)) + + call setline(1, 'LemonNectarineZ') + call cursor(1, 1) + exe "normal yiWPlciWNew\<Esc>fr." + call assert_equal('LemonNewNewZ', getline(1)) + + ounmap W + ounmap <Leader>W + ounmap iW + bwipe! + delfunc MoveToCap + delfunc SelectInCaps +endfunc + +" Patch 7.3.879: Properly abort Operator-pending mode for "dv:<Esc>" etc. +func Test_op_pend_mode_abort() + new + call append(0, '') + + call setline(1, ['zzzz', 'zzzz']) + call cursor(1, 1) + + exe "normal dV:\<CR>dv:\<CR>" + call assert_equal(['zzz'], getline(1, 2)) + set nomodifiable + call assert_fails('exe "normal d:\<CR>"', 'E21:') + set modifiable + call feedkeys("dv:\<Esc>dV:\<Esc>", 'xt') + call assert_equal(['zzz'], getline(1, 2)) + set nomodifiable + let v:errmsg = '' + call feedkeys("d:\<Esc>", 'xt') + call assert_true(v:errmsg !~# '^E21:') + set modifiable + + bwipe! +endfunc + +func Test_characterwise_visual_mode() + new + + " characterwise visual mode: replace last line + $put ='a' + let @" = 'x' + normal v$p + call assert_equal('x', getline('$')) + + " characterwise visual mode: delete middle line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + normal G + normal kkv$d + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + " characterwise visual mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + normal Gkkvj$d + call assert_equal(['', 'c'], getline(1, '$')) + + " characterwise visual mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + normal Gv$d + call assert_equal(['', 'a', 'b', ''], getline(1, '$')) + + " characterwise visual mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + normal Gkvj$d + call assert_equal(['', 'a', ''], getline(1, '$')) + + bwipe! +endfunc + +func Test_characterwise_select_mode() + new + + " Select mode maps + snoremap <lt>End> <End> + snoremap <lt>Down> <Down> + snoremap <lt>Del> <Del> + + " characterwise select mode: delete middle line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkkgh\<End>\<Del>" + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + " characterwise select mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkkgh\<Down>\<End>\<Del>" + call assert_equal(['', 'c'], getline(1, '$')) + + " characterwise select mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Ggh\<End>\<Del>" + call assert_equal(['', 'a', 'b', ''], getline(1, '$')) + + " characterwise select mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkgh\<Down>\<End>\<Del>" + call assert_equal(['', 'a', ''], getline(1, '$')) + + sunmap <lt>End> + sunmap <lt>Down> + sunmap <lt>Del> + bwipe! +endfunc + +func Test_linewise_select_mode() + new + + " linewise select mode: delete middle line + call append('$', ['a', 'b', 'c']) + exe "normal GkkgH\<Del>" + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + + " linewise select mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GkkgH\<Down>\<Del>" + call assert_equal(['', 'c'], getline(1, '$')) + + " linewise select mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GgH\<Del>" + call assert_equal(['', 'a', 'b'], getline(1, '$')) + + " linewise select mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GkgH\<Down>\<Del>" + call assert_equal(['', 'a'], getline(1, '$')) + + bwipe! +endfunc + +func Test_visual_mode_put() + new + + " v_p: replace last character with line register at middle line + call append('$', ['aaa', 'bbb', 'ccc']) + normal G + -2yank + normal k$vp + call assert_equal(['', 'aaa', 'bb', 'aaa', '', 'ccc'], getline(1, '$')) + + " v_p: replace last character with line register at middle line selecting + " newline + call deletebufline('', 1, '$') + call append('$', ['aaa', 'bbb', 'ccc']) + normal G + -2yank + normal k$v$p + call assert_equal(['', 'aaa', 'bb', 'aaa', 'ccc'], getline(1, '$')) + + " v_p: replace last character with line register at last line + call deletebufline('', 1, '$') + call append('$', ['aaa', 'bbb', 'ccc']) + normal G + -2yank + normal $vp + call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$')) + + " v_p: replace last character with line register at last line selecting + " newline + call deletebufline('', 1, '$') + call append('$', ['aaa', 'bbb', 'ccc']) + normal G + -2yank + normal $v$p + call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$')) + + bwipe! +endfunc + +func Test_select_mode_gv() + new + + " gv in exclusive select mode after operation + call append('$', ['zzz ', 'äà ']) + set selection=exclusive + normal Gkv3lyjv3lpgvcxxx + call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$')) + + " gv in exclusive select mode without operation + call deletebufline('', 1, '$') + call append('$', 'zzz ') + set selection=exclusive + exe "normal G0v3l\<Esc>gvcxxx" + call assert_equal(['', 'xxx '], getline(1, '$')) + + set selection&vim + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 500e3ff088..c630e678fd 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -172,39 +172,6 @@ func Test_window_split_edit_bufnr() %bw! endfunc -func Test_window_preview() - " Open a preview window - pedit Xa - call assert_equal(2, winnr('$')) - call assert_equal(0, &previewwindow) - - " Go to the preview window - wincmd P - call assert_equal(1, &previewwindow) - - " 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_from_help() - filetype on - call writefile(['/* some C code */'], 'Xpreview.c') - help - pedit Xpreview.c - wincmd P - call assert_equal(1, &previewwindow) - call assert_equal('c', &filetype) - wincmd z - - filetype off - close - call delete('Xpreview.c') -endfunc - func Test_window_exchange() e Xa diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 94c326a5eb..124f96e039 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -22,6 +22,14 @@ #define KEY_BUFFER_SIZE 0xfff +#ifndef UNIT_TESTING +typedef enum { + kIncomplete = -1, + kNotApplicable = 0, + kComplete = 1, +} HandleState; +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/input.c.generated.h" #endif @@ -339,8 +347,14 @@ static void tk_getkeys(TermInput *input, bool force) static void tinput_timer_cb(TimeWatcher *watcher, void *data) { - tk_getkeys(data, true); - tinput_flush(data, true); + TermInput *input = (TermInput *)data; + // If the raw buffer is not empty, process the raw buffer first because it is + // processing an incomplete bracketed paster sequence. + if (rbuffer_size(input->read_stream.buffer)) { + handle_raw_buffer(input, true); + } + tk_getkeys(input, true); + tinput_flush(input, true); } /// Handle focus events. @@ -365,19 +379,22 @@ static bool handle_focus_event(TermInput *input) return false; } -static bool handle_bracketed_paste(TermInput *input) +#define START_PASTE "\x1b[200~" +#define END_PASTE "\x1b[201~" +static HandleState handle_bracketed_paste(TermInput *input) { - if (rbuffer_size(input->read_stream.buffer) > 5 - && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[200~", 6) - || !rbuffer_cmp(input->read_stream.buffer, "\x1b[201~", 6))) { + size_t buf_size = rbuffer_size(input->read_stream.buffer); + if (buf_size > 5 + && (!rbuffer_cmp(input->read_stream.buffer, START_PASTE, 6) + || !rbuffer_cmp(input->read_stream.buffer, END_PASTE, 6))) { bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0'; if (input->paste && enable) { - return false; // Pasting "start paste" code literally. + return kNotApplicable; // Pasting "start paste" code literally. } // Advance past the sequence rbuffer_consumed(input->read_stream.buffer, 6); if (!!input->paste == enable) { - return true; // Spurious "disable paste" code. + return kComplete; // Spurious "disable paste" code. } if (enable) { @@ -392,9 +409,15 @@ static bool handle_bracketed_paste(TermInput *input) // Paste phase: "disabled". input->paste = 0; } - return true; + return kComplete; + } else if (buf_size < 6 + && (!rbuffer_cmp(input->read_stream.buffer, START_PASTE, buf_size) + || !rbuffer_cmp(input->read_stream.buffer, + END_PASTE, buf_size))) { + // Wait for further input, as the sequence may be split. + return kIncomplete; } - return false; + return kNotApplicable; } // ESC NUL => <Esc> @@ -441,39 +464,47 @@ static void set_bg_deferred(void **argv) // ignored in the calculations. // // [1] https://en.wikipedia.org/wiki/Luma_%28video%29 -static bool handle_background_color(TermInput *input) +static HandleState handle_background_color(TermInput *input) { if (input->waiting_for_bg_response <= 0) { - return false; + return kNotApplicable; } size_t count = 0; size_t component = 0; size_t header_size = 0; size_t num_components = 0; + size_t buf_size = rbuffer_size(input->read_stream.buffer); uint16_t rgb[] = { 0, 0, 0 }; uint16_t rgb_max[] = { 0, 0, 0 }; bool eat_backslash = false; bool done = false; bool bad = false; - if (rbuffer_size(input->read_stream.buffer) >= 9 + if (buf_size >= 9 && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) { header_size = 9; num_components = 3; - } else if (rbuffer_size(input->read_stream.buffer) >= 10 + } else if (buf_size >= 10 && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgba:", 10)) { header_size = 10; num_components = 4; + } else if (buf_size < 10 + && !rbuffer_cmp(input->read_stream.buffer, + "\x1b]11;rgba", buf_size)) { + // An incomplete sequence was found, waiting for the next input. + return kIncomplete; } else { input->waiting_for_bg_response--; if (input->waiting_for_bg_response == 0) { DLOG("did not get a response for terminal background query"); } - return false; + return kNotApplicable; } - input->waiting_for_bg_response = 0; - rbuffer_consumed(input->read_stream.buffer, header_size); RBUFFER_EACH(input->read_stream.buffer, c, i) { count = i + 1; + // Skip the header. + if (i < header_size) { + continue; + } if (eat_backslash) { done = true; break; @@ -495,8 +526,8 @@ static bool handle_background_color(TermInput *input) bad = true; } } - rbuffer_consumed(input->read_stream.buffer, count); if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) { + rbuffer_consumed(input->read_stream.buffer, count); double r = (double)rgb[0] / (double)rgb_max[0]; double g = (double)rgb[1] / (double)rgb_max[1]; double b = (double)rgb[2] / (double)rgb_max[2]; @@ -505,34 +536,41 @@ static bool handle_background_color(TermInput *input) DLOG("bg response: %s", bgvalue); loop_schedule_deferred(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); + input->waiting_for_bg_response = 0; + } else if (!done && !bad) { + // An incomplete sequence was found, waiting for the next input. + return kIncomplete; } else { + input->waiting_for_bg_response = 0; + rbuffer_consumed(input->read_stream.buffer, count); DLOG("failed to parse bg response"); - return false; + return kNotApplicable; } - return true; + return kComplete; } #ifdef UNIT_TESTING -bool ut_handle_background_color(TermInput *input) +HandleState ut_handle_background_color(TermInput *input) { return handle_background_color(input); } #endif -static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, - void *data, bool eof) +static void handle_raw_buffer(TermInput *input, bool force) { - TermInput *input = data; - - if (eof) { - loop_schedule_fast(&main_loop, event_create(tinput_done_event, 0)); - return; - } + HandleState is_paste; + HandleState is_bc; do { - if (handle_focus_event(input) - || handle_bracketed_paste(input) - || handle_forced_escape(input) - || handle_background_color(input)) { + if (!force + && (handle_focus_event(input) + || (is_paste = handle_bracketed_paste(input)) != kNotApplicable + || handle_forced_escape(input) + || (is_bc = handle_background_color(input)) != kNotApplicable)) { + if (is_paste == kIncomplete || is_bc == kIncomplete) { + // Wait for the next input, leaving it in the raw buffer due to an + // incomplete sequence. + return; + } continue; } @@ -578,7 +616,34 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, } } } while (rbuffer_size(input->read_stream.buffer)); +} + +static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, + void *data, bool eof) +{ + TermInput *input = data; + + if (eof) { + loop_schedule_fast(&main_loop, event_create(tinput_done_event, 0)); + return; + } + + handle_raw_buffer(input, false); tinput_flush(input, true); + + // An incomplete sequence was found. Leave it in the raw buffer and wait for + // the next input. + if (rbuffer_size(input->read_stream.buffer)) { + // If 'ttimeout' is not set, start the timer with a timeout of 0 to process + // the next input. + long ms = input->ttimeout ? + (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0; + // Stop the current timer if already running + time_watcher_stop(&input->timer_handle); + time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0); + return; + } + // Make sure the next input escape sequence fits into the ring buffer without // wraparound, else it could be misinterpreted (because rbuffer_read_ptr() // exposes the underlying buffer to callers unaware of the wraparound). diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index b30546c815..ed76455189 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -32,7 +32,13 @@ typedef struct term_input { #endif #ifdef UNIT_TESTING -bool ut_handle_background_color(TermInput *input); +typedef enum { + kIncomplete = -1, + kNotApplicable = 0, + kComplete = 1, +} HandleState; + +HandleState ut_handle_background_color(TermInput *input); #endif #endif // NVIM_TUI_INPUT_H diff --git a/src/nvim/types.h b/src/nvim/types.h index 17f7e16740..2dbeecbf6d 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -2,6 +2,7 @@ #define NVIM_TYPES_H #include <stdint.h> +#include <stdbool.h> // dummy to pass an ACL to a function typedef void *vim_acl_T; diff --git a/src/nvim/window.c b/src/nvim/window.c index 17d9b8f483..72ee400e40 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1112,8 +1112,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // add a status line when p_ls == 1 and splitting the first window if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) { - if ((oldwin->w_height + oldwin->w_winbar_height) <= p_wmh - && new_in_layout) { + if (oldwin->w_height <= p_wmh && new_in_layout) { EMSG(_(e_noroom)); return FAIL; } @@ -1210,7 +1209,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) * height. */ // Current window requires at least 1 space. - wmh1 = (p_wmh == 0 ? 1 : p_wmh) + curwin->w_winbar_height; + wmh1 = p_wmh == 0 ? 1 : p_wmh; needed = wmh1 + STATUS_HEIGHT; if (flags & WSP_ROOM) { needed += p_wh - wmh1; @@ -1408,12 +1407,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (flags & (WSP_TOP | WSP_BOT)) { /* set height and row of new window to full height */ wp->w_winrow = tabline_height(); - win_new_height(wp, curfrp->fr_height - (p_ls > 0) - wp->w_winbar_height); + win_new_height(wp, curfrp->fr_height - (p_ls > 0)); wp->w_status_height = (p_ls > 0); } else { /* height and row of new window is same as current window */ wp->w_winrow = oldwin->w_winrow; - win_new_height(wp, oldwin->w_height + oldwin->w_winbar_height); + win_new_height(wp, oldwin->w_height); wp->w_status_height = oldwin->w_status_height; } frp->fr_height = curfrp->fr_height; @@ -1460,7 +1459,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) * one row for the status line */ win_new_height(wp, new_size); if (flags & (WSP_TOP | WSP_BOT)) { - int new_fr_height = curfrp->fr_height - new_size + wp->w_winbar_height; + int new_fr_height = curfrp->fr_height - new_size; if (!((flags & WSP_BOT) && p_ls == 0)) { new_fr_height -= STATUS_HEIGHT; @@ -1474,8 +1473,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) wp->w_status_height = STATUS_HEIGHT; oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; } else { // new window below current one - wp->w_winrow = oldwin->w_winrow + oldwin->w_height - + STATUS_HEIGHT + oldwin->w_winbar_height; + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; wp->w_status_height = oldwin->w_status_height; if (!(flags & WSP_BOT)) { oldwin->w_status_height = STATUS_HEIGHT; @@ -1690,7 +1688,7 @@ make_windows ( - (p_wiw - p_wmw)) / (p_wmw + 1); } else { // Each window needs at least 'winminheight' lines and a status line. - maxcount = (curwin->w_height + curwin->w_winbar_height + maxcount = (curwin->w_height + curwin->w_status_height - (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT); } @@ -3155,9 +3153,7 @@ frame_new_height ( if (topfrp->fr_win != NULL) { // Simple case: just one window. win_new_height(topfrp->fr_win, - height - - topfrp->fr_win->w_status_height - - topfrp->fr_win->w_winbar_height); + height - topfrp->fr_win->w_status_height); } else if (topfrp->fr_layout == FR_ROW) { do { // All frames in this row get the same new height. @@ -3464,8 +3460,7 @@ static void frame_fix_width(win_T *wp) static void frame_fix_height(win_T *wp) FUNC_ATTR_NONNULL_ALL { - wp->w_frame->fr_height = - wp->w_height + wp->w_status_height + wp->w_winbar_height; + wp->w_frame->fr_height = wp->w_height + wp->w_status_height; } /* @@ -3488,11 +3483,10 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) // window: minimal height of the window plus status line m = p_wmh + topfrp->fr_win->w_status_height; if (topfrp->fr_win == curwin && next_curwin == NULL) { - // Current window is minimal one line high and WinBar is visible. + // Current window is minimal one line high. if (p_wmh == 0) { m++; } - m += curwin->w_winbar_height; } } } else if (topfrp->fr_layout == FR_ROW) { @@ -4804,8 +4798,6 @@ win_free ( qf_free_all(wp); - remove_winbar(wp); - xfree(wp->w_p_cc_cols); win_free_grid(wp, false); @@ -5092,8 +5084,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) wp->w_redr_status = true; wp->w_pos_changed = true; } - // WinBar will not show if the window height is zero - const int h = wp->w_height + wp->w_winbar_height + wp->w_status_height; + const int h = wp->w_height + wp->w_status_height; *row += h > topfrp->fr_height ? topfrp->fr_height : h; *col += wp->w_width + wp->w_vsep_width; } else { @@ -5135,7 +5126,6 @@ void win_setheight_win(int height, win_T *win) if (height == 0) { height = 1; } - height += curwin->w_winbar_height; } if (win->w_floating) { @@ -5231,9 +5221,8 @@ static void frame_setheight(frame_T *curfrp, int height) room_cmdline = 0; } else { win_T *wp = lastwin_nofloating(); - room_cmdline = Rows - p_ch - (wp->w_winrow - + wp->w_height + wp->w_winbar_height + - wp->w_status_height); + room_cmdline = Rows - p_ch + - (wp->w_winrow + wp->w_height + wp->w_status_height); if (room_cmdline < 0) { room_cmdline = 0; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 7948f7da09..eb5fd7eca7 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1885,25 +1885,40 @@ describe('API', function() end) describe('nvim_get_runtime_file', function() - it('works', function() + local p = helpers.alter_slashes + it('can find files', function() eq({}, meths.get_runtime_file("bork.borkbork", false)) eq({}, meths.get_runtime_file("bork.borkbork", true)) eq(1, #meths.get_runtime_file("autoload/msgpack.vim", false)) eq(1, #meths.get_runtime_file("autoload/msgpack.vim", true)) local val = meths.get_runtime_file("autoload/remote/*.vim", true) eq(2, #val) - local p = helpers.alter_slashes if endswith(val[1], "define.vim") then - ok(endswith(val[1], p("autoload/remote/define.vim"))) - ok(endswith(val[2], p("autoload/remote/host.vim"))) + ok(endswith(val[1], p"autoload/remote/define.vim")) + ok(endswith(val[2], p"autoload/remote/host.vim")) else - ok(endswith(val[1], p("autoload/remote/host.vim"))) - ok(endswith(val[2], p("autoload/remote/define.vim"))) + ok(endswith(val[1], p"autoload/remote/host.vim")) + ok(endswith(val[2], p"autoload/remote/define.vim")) end val = meths.get_runtime_file("autoload/remote/*.vim", false) eq(1, #val) - ok(endswith(val[1], p("autoload/remote/define.vim")) - or endswith(val[1], p("autoload/remote/host.vim"))) + ok(endswith(val[1], p"autoload/remote/define.vim") + or endswith(val[1], p"autoload/remote/host.vim")) + + eq({}, meths.get_runtime_file("lua", true)) + eq({}, meths.get_runtime_file("lua/vim", true)) + end) + + it('can find directories', function() + local val = meths.get_runtime_file("lua/", true) + eq(1, #val) + ok(endswith(val[1], p"lua/")) + + val = meths.get_runtime_file("lua/vim/", true) + eq(1, #val) + ok(endswith(val[1], p"lua/vim/")) + + eq({}, meths.get_runtime_file("foobarlang/", true)) end) end) end) diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index 4c1083c386..65dc1b3e03 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -50,7 +50,7 @@ describe('treesitter API with C parser', function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse() + tree = parser:parse()[1] root = tree:root() lang = vim.treesitter.inspect_language('c') ]]) @@ -82,7 +82,7 @@ describe('treesitter API with C parser', function() feed("2G7|ay") exec_lua([[ - tree2 = parser:parse() + tree2 = parser:parse()[1] root2 = tree2:root() descendant2 = root2:descendant_for_range(1,2,1,13) ]]) @@ -106,11 +106,8 @@ describe('treesitter API with C parser', function() eq(false, exec_lua("return child:id() == nil")) eq(false, exec_lua("return child:id() == tree")) - -- orginal tree did not change - eq({1,2,1,12}, exec_lua("return {descendant:range()}")) - -- unchanged buffer: return the same tree - eq(true, exec_lua("return parser:parse() == tree2")) + eq(true, exec_lua("return parser:parse()[1] == tree2")) end) local test_text = [[ @@ -142,7 +139,7 @@ void ui_refresh(void) local res = exec_lua([[ parser = vim.treesitter.get_parser(0, "c") - func_node = parser:parse():root():child(0) + func_node = parser:parse()[1]:root():child(0) res = {} for node, field in func_node:iter_children() do @@ -166,7 +163,7 @@ void ui_refresh(void) local res = exec_lua([[ parser = vim.treesitter.get_parser(0, "c") - func_node = parser:parse():root():child(0) + func_node = parser:parse()[1]:root():child(0) local res = {} for _, node in ipairs(func_node:field("type")) do @@ -211,7 +208,7 @@ void ui_refresh(void) local res = exec_lua([[ cquery = vim.treesitter.parse_query("c", ...) parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse() + tree = parser:parse()[1] res = {} for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do -- can't transmit node over RPC. just check the name and range @@ -242,7 +239,7 @@ void ui_refresh(void) local res = exec_lua([[ cquery = vim.treesitter.parse_query("c", ...) parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse() + tree = parser:parse()[1] res = {} for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do -- can't transmit node over RPC. just check the name and range @@ -275,7 +272,7 @@ void ui_refresh(void) local res = exec_lua([[ cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))') parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse() + tree = parser:parse()[1] res = {} for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) do -- can't transmit node over RPC. just check the name and range @@ -321,7 +318,7 @@ void ui_refresh(void) local query = query.parse_query("c", ...) local nodes = {} - for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do + for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do table.insert(nodes, {node:range()}) end @@ -343,10 +340,7 @@ void ui_refresh(void) eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) end) - it('supports highlighting', function() - if not check_parser() then return end - - local hl_text = [[ + local hl_text = [[ /// Schedule Lua callback on main loop's event queue static int nlua_schedule(lua_State *const lstate) { @@ -363,7 +357,7 @@ static int nlua_schedule(lua_State *const lstate) return 0; }]] - local hl_query = [[ +local hl_query = [[ (ERROR) @ErrorMsg "if" @keyword @@ -398,249 +392,319 @@ static int nlua_schedule(lua_State *const lstate) (comment) @comment ]] - local screen = Screen.new(65, 18) - screen:attach() - 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}, - }) - - insert(hl_text) - screen:expect{grid=[[ - /// Schedule Lua callback on main loop's event queue | - static int nlua_schedule(lua_State *const lstate) | - { | - if (lua_type(lstate, 1) != LUA_TFUNCTION | - || lstate != lstate) { | - lua_pushliteral(lstate, "vim.schedule: expected function"); | - return lua_error(lstate); | + describe('when highlighting', function() + local screen + + before_each(function() + screen = Screen.new(65, 18) + screen:attach() + 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}, + }) + end) + + it('supports highlighting', function() + if not check_parser() then return end + + insert(hl_text) + screen:expect{grid=[[ + /// Schedule Lua callback on main loop's event queue | + static int nlua_schedule(lua_State *const lstate) | + { | + if (lua_type(lstate, 1) != LUA_TFUNCTION | + || lstate != lstate) { | + lua_pushliteral(lstate, "vim.schedule: expected function"); | + return lua_error(lstate); | + } | + | + LuaRef cb = nlua_ref(lstate, 1); | + | + multiqueue_put(main_loop.events, nlua_schedule_event, | + 1, (void *)(ptrdiff_t)cb); | + return 0; | + ^} | + {1:~ }| + {1:~ }| + | + ]]} + + exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local highlighter = vim.treesitter.highlighter + local query = ... + test_hl = highlighter.new(parser, {queries = {c = query}}) + ]], hl_query) + 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:~ }| + {1:~ }| + | + ]]} + + feed("5Goc<esc>dd") + + 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:~ }| + {1:~ }| + | + ]]} + + feed('7Go*/<esc>') + 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:~ }| + | + ]]} + + feed('3Go/*<esc>') + 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:}} | + | + ]]} + + feed("gg$") + feed("~") + 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:}} | + | + ]]} + + + feed("re") + 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:}} | + | + ]]} + end) + + it("supports highlighting with custom parser", function() + if not check_parser() then return end + + screen:set_default_attr_ids({ {bold = true, foreground = Screen.colors.SeaGreen4} }) + + insert(test_text) + + screen:expect{ grid= [[ + int width = INT_MAX, height = INT_MAX; | + bool ext_widgets[kUIExtCount]; | + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | } | | - LuaRef cb = nlua_ref(lstate, 1); | - | - multiqueue_put(main_loop.events, nlua_schedule_event, | - 1, (void *)(ptrdiff_t)cb); | - return 0; | - ^} | - {1:~ }| - {1:~ }| - | - ]]} - - exec_lua([[ - local parser = vim.treesitter.get_parser(0, "c") - local highlighter = vim.treesitter.highlighter - local query = ... - test_hl = highlighter.new(parser, query) - ]], hl_query) - 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); | + bool inclusive = ui_override(); | + for (size_t i = 0; i < ui_count; i++) { | + UI *ui = uis[i]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | } | - | - {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:~ }| - {1:~ }| - | - ]]} - - feed("5Goc<esc>dd") - - 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:~ }| - {1:~ }| - | - ]]} - - feed('7Go*/<esc>') - 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:~ }| - | - ]]} - - feed('3Go/*<esc>') - 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:}} | - | - ]]} - - feed("gg$") - feed("~") - 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:}} | - | - ]]} - - - feed("re") - 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:}} | - | - ]]} - end) + ]] } - it("supports highlighting with custom parser", function() - if not check_parser() then return end - - local screen = Screen.new(65, 18) - screen:attach() - screen:set_default_attr_ids({ {bold = true, foreground = Screen.colors.SeaGreen4} }) - - insert(test_text) - - screen:expect{ grid= [[ - int width = INT_MAX, height = INT_MAX; | - bool ext_widgets[kUIExtCount]; | - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - bool inclusive = ui_override(); | - for (size_t i = 0; i < ui_count; i++) { | - UI *ui = uis[i]; | - width = MIN(ui->width, width); | - height = MIN(ui->height, height); | - foo = BAR(ui->bazaar, bazaar); | - for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]] } - - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - query = vim.treesitter.parse_query("c", "(declaration) @decl") + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + query = vim.treesitter.parse_query("c", "(declaration) @decl") - local nodes = {} - for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do - table.insert(nodes, node) - end + local nodes = {} + for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do + table.insert(nodes, node) + end - parser:set_included_ranges(nodes) + parser:set_included_regions({nodes}) - local hl = vim.treesitter.highlighter.new(parser, "(identifier) @type") - ]]) + local hl = vim.treesitter.highlighter.new(parser, {queries = {c = "(identifier) @type"}}) + ]]) - 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); | - } | - } | - ^} | - | - ]] } + 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); | + } | + } | + ^} | + | + ]] } + end) + + it("supports highlighting injected languages", function() + if not check_parser() then return end + + insert([[ + int x = INT_MAX; + #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + #define foo void main() { \ + return 42; \ + } + ]]) + + screen:expect{grid=[[ + int x = INT_MAX; | + #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))| + #define foo void main() { \ | + return 42; \ | + } | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c", { + queries = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"} + }) + local highlighter = vim.treesitter.highlighter + local query = ... + test_hl = highlighter.new(parser, {queries = {c = query}}) + ]], hl_query) + + screen:expect{grid=[[ + {3:int} x = {5:INT_MAX}; | + #define {5:READ_STRING}(x, y) ({3:char_u} *)read_string((x), ({3:size_t})(y))| + #define foo {3:void} main() { \ | + {4:return} {5:42}; \ | + } | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) end) it('inspects language', function() @@ -690,7 +754,7 @@ static int nlua_schedule(lua_State *const lstate) local res = exec_lua [[ parser = vim.treesitter.get_parser(0, "c") - return { parser:parse():root():range() } + return { parser:parse()[1]:root():range() } ]] eq({0, 0, 19, 0}, res) @@ -698,38 +762,51 @@ static int nlua_schedule(lua_State *const lstate) -- The following sets the included ranges for the current parser -- As stated here, this only includes the function (thus the whole buffer, without the last line) local res2 = exec_lua [[ - local root = parser:parse():root() - parser:set_included_ranges({root:child(0)}) - parser.valid = false - return { parser:parse():root():range() } + local root = parser:parse()[1]:root() + parser:set_included_regions({{root:child(0)}}) + parser:invalidate() + return { parser:parse()[1]:root():range() } ]] eq({0, 0, 18, 1}, res2) local range = exec_lua [[ - return parser:included_ranges() + local res = {} + for _, region in ipairs(parser:included_regions()) do + for _, node in ipairs(region) do + table.insert(res, {node:range()}) + end + end + return res ]] eq(range, { { 0, 0, 18, 1 } }) + + local range_tbl = exec_lua [[ + parser:set_included_regions { { { 0, 0, 17, 1 } } } + parser:parse() + return parser:included_regions() + ]] + + eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } }) end) it("allows to set complex ranges", function() if not check_parser() then return end insert(test_text) - local res = exec_lua [[ parser = vim.treesitter.get_parser(0, "c") query = vim.treesitter.parse_query("c", "(declaration) @decl") local nodes = {} - for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do + for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do table.insert(nodes, node) end - parser:set_included_ranges(nodes) + parser:set_included_regions({nodes}) - local root = parser:parse():root() + local root = parser:parse()[1]:root() local res = {} for i=0,(root:named_child_count() - 1) do @@ -740,21 +817,18 @@ static int nlua_schedule(lua_State *const lstate) eq({ { 2, 2, 2, 40 }, - { 3, 3, 3, 32 }, - { 4, 7, 4, 8 }, - { 4, 8, 4, 25 }, - { 8, 2, 8, 6 }, - { 8, 7, 8, 33 }, - { 9, 8, 9, 20 }, - { 10, 4, 10, 5 }, - { 10, 5, 10, 20 }, + { 3, 2, 3, 32 }, + { 4, 7, 4, 25 }, + { 8, 2, 8, 33 }, + { 9, 7, 9, 20 }, + { 10, 4, 10, 20 }, { 14, 9, 14, 27 } }, res) end) it("allows to create string parsers", function() local ret = exec_lua [[ local parser = vim.treesitter.get_string_parser("int foo = 42;", "c") - return { parser:parse():root():range() } + return { parser:parse()[1]:root():range() } ]] eq({ 0, 0, 0, 13 }, ret) @@ -773,7 +847,7 @@ static int nlua_schedule(lua_State *const lstate) local nodes = {} local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') - for _, node in query:iter_captures(parser:parse():root(), str, 0, 2) do + for _, node in query:iter_captures(parser:parse()[1]:root(), str, 0, 2) do table.insert(nodes, { node:range() }) end @@ -781,4 +855,68 @@ static int nlua_schedule(lua_State *const lstate) eq({ {0, 10, 0, 13} }, ret) end) + + describe("when creating a language tree", function() + local function get_ranges() + return exec_lua([[ + local result = {} + parser:for_each_tree(function(tree) table.insert(result, {tree:root():range()}) end) + return result + ]]) + end + + before_each(function() + insert([[ + int x = INT_MAX; + #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + #define READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) + #define VALUE 0 + #define VALUE1 1 + #define VALUE2 2 + ]]) + end) + + describe("when parsing regions independently", function() + it("should inject a language", function() + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c", { + queries = { + c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) + ]]) + + eq("table", exec_lua("return type(parser:children().c)")) + eq(5, exec_lua("return #parser:children().c:trees()")) + eq({ + {0, 2, 7, 0}, -- root tree + {3, 16, 3, 17}, -- VALUE 0 + {4, 17, 4, 18}, -- VALUE1 1 + {5, 17, 5, 18}, -- VALUE2 2 + {1, 28, 1, 67}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + {2, 31, 2, 70} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) + }, get_ranges()) + end) + end) + + describe("when parsing regions combined", function() + it("should inject a language", function() + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c", { + queries = { + c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) + ]]) + + eq("table", exec_lua("return type(parser:children().c)")) + eq(2, exec_lua("return #parser:children().c:trees()")) + eq({ + {0, 2, 7, 0}, -- root tree + {3, 16, 5, 18}, -- VALUE 0 + -- VALUE1 1 + -- VALUE2 2 + {1, 28, 2, 70} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) + }, get_ranges()) + end) + end) + end) + end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5948ab3a64..3ef41fde1d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -727,6 +727,38 @@ describe('TUI', function() ]]) end) + it('paste: split "start paste" code', function() + feed_data('i') + -- Send split "start paste" sequence. + feed_data('\027[2') + feed_data('00~pasted from terminal\027[201~') + screen:expect([[ + pasted from terminal{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + end) + + it('paste: split "stop paste" code', function() + feed_data('i') + -- Send split "stop paste" sequence. + feed_data('\027[200~pasted from terminal\027[20') + feed_data('1~') + screen:expect([[ + pasted from terminal{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + end) + it('allows termguicolors to be set at runtime', function() screen:set_option('rgb', true) screen:set_default_attr_ids({ @@ -1452,15 +1484,108 @@ describe("TUI", function() end) -it('TUI bg color triggers OptionSet event on terminal-response', function() - -- Only single integration test. - -- See test/unit/tui_spec.lua for unit tests. - clear() - local screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile", ' - ..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]') +describe('TUI bg color', function() + local screen + + local function setup() + -- Only single integration test. + -- See test/unit/tui_spec.lua for unit tests. + clear() + screen = thelpers.screen_setup(0, '["'..nvim_prog + ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile", ' + ..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]') + end + + before_each(setup) + + it('triggers OptionSet event on unsplit terminal-response', function() + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + feed_data('\027]11;rgb:ffff/ffff/ffff\007') + screen:expect{any='did OptionSet, yay!'} + + feed_data(':echo "new_bg=".&background\n') + screen:expect{any='new_bg=light'} + + setup() + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + feed_data('\027]11;rgba:ffff/ffff/ffff/8000\027\\') + screen:expect{any='did OptionSet, yay!'} + + feed_data(':echo "new_bg=".&background\n') + screen:expect{any='new_bg=light'} + end) + + it('triggers OptionSet event with split terminal-response', function() + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + -- Send a background response with the OSC command part split. + feed_data('\027]11;rgb') + feed_data(':ffff/ffff/ffff\027\\') + screen:expect{any='did OptionSet, yay!'} + + feed_data(':echo "new_bg=".&background\n') + screen:expect{any='new_bg=light'} + + setup() + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + -- Send a background response with the Pt portion split. + feed_data('\027]11;rgba:ffff/fff') + feed_data('f/ffff/8000\007') + screen:expect{any='did OptionSet, yay!'} + + feed_data(':echo "new_bg=".&background\n') + screen:expect{any='new_bg=light'} + end) + + it('not triggers OptionSet event with invalid terminal-response', function() + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + feed_data('\027]11;rgb:ffff/ffff/ffff/8000\027\\') + screen:expect_unchanged() + + feed_data(':echo "new_bg=".&background\n') + screen:expect{any='new_bg=dark'} - screen:expect([[ + setup() + screen:expect([[ {1: } | {4:~ }| {4:~ }| @@ -1468,10 +1593,11 @@ it('TUI bg color triggers OptionSet event on terminal-response', function() {5:[No Name] 0,0-1 All}| | {3:-- TERMINAL --} | - ]]) - feed_data('\027]11;rgb:ffff/ffff/ffff\007') - screen:expect{any='did OptionSet, yay!'} + ]]) + feed_data('\027]11;rgba:ffff/foo/ffff/8000\007') + screen:expect_unchanged() - feed_data(':echo "new_bg=".&background\n') - screen:expect{any='new_bg=light'} + feed_data(':echo "new_bg=".&background\n') + screen:expect{any='new_bg=dark'} + end) end) diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index d7269d2c29..af709cd521 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -534,6 +534,62 @@ describe('Buffer highlighting', function() ]]} end) + it('respects priority', function() + local set_extmark = curbufmeths.set_extmark + local id = meths.create_namespace('') + insert [[foobar]] + + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 5, + hl_group = "Statement", + priority = 100 + }) + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 6, + hl_group = "String", + priority = 1 + }) + + screen:expect [[ + {3:fooba}{2:^r} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] + + clear_namespace(id, 0, -1) + + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 6, + hl_group = "String", + priority = 1 + }) + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 5, + hl_group = "Statement", + priority = 100 + }) + + screen:expect [[ + {3:fooba}{2:^r} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] + end) + it('works with multibyte text', function() insert([[ Ta båten över sjön!]]) @@ -699,12 +755,12 @@ describe('Buffer highlighting', function() -- TODO: only a virtual text from the same ns curretly overrides -- an existing virtual text. We might add a prioritation system. set_virtual_text(id1, 0, s1, {}) - eq({{1, 0, 0, {virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) + eq({{1, 0, 0, { priority = 0, virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) -- TODO: is this really valid? shouldn't the max be line_count()-1? local lastline = line_count() set_virtual_text(id1, line_count(), s2, {}) - eq({{3, lastline, 0, {virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) + eq({{3, lastline, 0, { priority = 0, virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) eq({}, get_extmarks(id1, {lastline+9000,0}, {lastline+9000, -1}, {})) end) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 4182090732..781fdf7203 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -27,6 +27,7 @@ describe('decorations providers', function() [9] = {reverse = true}; [10] = {italic = true, background = Screen.colors.Magenta}; [11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')}; + [12] = {foreground = tonumber('0x990000')}; } end) @@ -227,4 +228,77 @@ describe('decorations providers', function() ]]} end) + + it('can break an existing link', function() + insert(mulholland) + local ns1 = setup_provider() + + exec [[ + highlight OriginalGroup guifg='#990000' + highlight link LinkGroup OriginalGroup + ]] + + meths.buf_set_virtual_text(0, 0, 2, {{'- not red', 'LinkGroup'}}, {}) + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); {12:- not red} | + bufref_T save_buf; | + switch_buffer(&save_buf, buf); | + posp = getmark(mark, false); | + restore_buffer(&save_buf);^ | + | + ]]} + + meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue'}) + meths.set_hl_ns(ns1) + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); {4:- not red} | + bufref_T save_buf; | + switch_buffer(&save_buf, buf); | + posp = getmark(mark, false); | + restore_buffer(&save_buf);^ | + | + ]]} + end) + + it("with 'default': do not break an existing link", function() + insert(mulholland) + local ns1 = setup_provider() + + exec [[ + highlight OriginalGroup guifg='#990000' + highlight link LinkGroup OriginalGroup + ]] + + meths.buf_set_virtual_text(0, 0, 2, {{'- not red', 'LinkGroup'}}, {}) + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); {12:- not red} | + bufref_T save_buf; | + switch_buffer(&save_buf, buf); | + posp = getmark(mark, false); | + restore_buffer(&save_buf);^ | + | + ]]} + + meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue', default=true}) + meths.set_hl_ns(ns1) + feed 'k' + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); {12:- not red} | + bufref_T save_buf; | + switch_buffer(&save_buf, buf); | + posp = getmark(mark, false^); | + restore_buffer(&save_buf); | + | + ]]} + end) end) diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 669ec5d3ed..018049d2f4 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -117,6 +117,33 @@ describe("folded lines", function() end end) + it("work with spell", function() + command("set spell") + insert([[ + This is a + valid English + sentence composed by + an exhausted developer + in his cave. + ]]) + + feed("gg") + feed("zf3j") + if not multigrid then + -- screen:snapshot_util() + screen:expect{grid=[[ + {5:^+-- 4 lines: This is a······················}| + in his cave. | + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end + end) + it("works with multibyte fillchars", function() insert([[ aa diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 28e4e88326..ef3acd7d2e 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -604,7 +604,7 @@ describe("'listchars' highlight", function() ]]) end) - it("'cursorline' and with 'listchar' option: space, eol, tab, and trail", function() + it("'cursorline' and with 'listchars' option", function() screen:set_default_attr_ids({ [1] = {background=Screen.colors.Grey90}, [2] = { @@ -861,6 +861,57 @@ describe('CursorLine highlight', function() ]]) end) + it("overridden by NonText in 'showbreak' characters", function() + local screen = Screen.new(20,5) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Yellow, background = Screen.colors.Blue}; + [2] = {foreground = Screen.colors.Black, background = Screen.colors.White}; + [3] = {foreground = Screen.colors.Yellow, background = Screen.colors.White}; + [4] = {foreground = Screen.colors.Yellow}; + }) + screen:attach() + + feed_command('set wrap cursorline') + feed_command('set showbreak=>>>') + feed_command('highlight clear NonText') + feed_command('highlight clear CursorLine') + feed_command('highlight NonText guifg=Yellow guibg=Blue gui=NONE') + feed_command('highlight CursorLine guifg=Black guibg=White gui=NONE') + + feed('30iø<esc>o<esc>30ia<esc>') + screen:expect([[ + øøøøøøøøøøøøøøøøøøøø| + {1:>>>}øøøøøøøøøø | + {2:aaaaaaaaaaaaaaaaaaaa}| + {1:>>>}{2:aaaaaaaaa^a }| + | + ]]) + feed('k') + screen:expect([[ + {2:øøøøøøøøøøøøøøøøøøøø}| + {1:>>>}{2:øøøøøøøøø^ø }| + aaaaaaaaaaaaaaaaaaaa| + {1:>>>}aaaaaaaaaa | + | + ]]) + feed_command('highlight NonText guibg=NONE') + screen:expect([[ + {2:øøøøøøøøøøøøøøøøøøøø}| + {3:>>>}{2:øøøøøøøøø^ø }| + aaaaaaaaaaaaaaaaaaaa| + {4:>>>}aaaaaaaaaa | + | + ]]) + feed_command('set nocursorline') + screen:expect([[ + øøøøøøøøøøøøøøøøøøøø| + {4:>>>}øøøøøøøøø^ø | + aaaaaaaaaaaaaaaaaaaa| + {4:>>>}aaaaaaaaaa | + :set nocursorline | + ]]) + end) + it('always updated. vim-patch:8.1.0849', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index d7dde6345f..3826707743 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -232,20 +232,53 @@ describe("shell command :!", function() if has_powershell() then it('powershell supports literal strings', function() set_shell_powershell() - local screen = Screen.new(30, 4) + local screen = Screen.new(45, 4) screen:attach() feed_command([[!'Write-Output $a']]) - screen:expect{any='\nWrite%-Output %$a', timeout=10000} + screen:expect([[ + :!'Write-Output $a' | + Write-Output $a | + | + Press ENTER or type command to continue^ | + ]]) feed_command([[!$a = 1; Write-Output '$a']]) - screen:expect{any='\n%$a', timeout=10000} + screen:expect([[ + :!$a = 1; Write-Output '$a' | + $a | + | + Press ENTER or type command to continue^ | + ]]) feed_command([[!"Write-Output $a"]]) - screen:expect{any='\nWrite%-Output', timeout=10000} + screen:expect([[ + :!"Write-Output $a" | + Write-Output | + | + Press ENTER or type command to continue^ | + ]]) feed_command([[!$a = 1; Write-Output "$a"]]) - screen:expect{any='\n1', timeout=10000} - feed_command(iswin() - and [[!& 'C:\\Windows\\system32\\cmd.exe' /c 'echo $a']] - or [[!& '/bin/sh' -c 'echo ''$a''']]) - screen:expect{any='\n%$a', timeout=10000} + screen:expect([[ + :!$a = 1; Write-Output "$a" | + 1 | + | + Press ENTER or type command to continue^ | + ]]) + if iswin() then + feed_command([[!& 'cmd.exe' /c 'echo $a']]) + screen:expect([[ + :!& 'cmd.exe' /c 'echo $a' | + $a | + | + Press ENTER or type command to continue^ | + ]]) + else + feed_command([[!& '/bin/sh' -c 'echo ''$a''']]) + screen:expect([[ + :!& '/bin/sh' -c 'echo ''$a''' | + $a | + | + Press ENTER or type command to continue^ | + ]]) + end end) end end) diff --git a/test/helpers.lua b/test/helpers.lua index 84148dc1a8..8dbd82cb8c 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -777,11 +777,12 @@ end function module.isCI(name) local any = (name == nil) - assert(any or name == 'appveyor' or name == 'travis' or name == 'sourcehut') + assert(any or name == 'appveyor' or name == 'travis' or name == 'sourcehut' or name == 'github') local av = ((any or name == 'appveyor') and nil ~= os.getenv('APPVEYOR')) local tr = ((any or name == 'travis') and nil ~= os.getenv('TRAVIS')) local sh = ((any or name == 'sourcehut') and nil ~= os.getenv('SOURCEHUT')) - return tr or av or sh + local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS')) + return tr or av or sh or gh end diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua index e6b5c889d7..36ce4a1493 100644 --- a/test/unit/tui_spec.lua +++ b/test/unit/tui_spec.lua @@ -14,10 +14,13 @@ itp('handle_background_color', function() local handle_background_color = cinput.ut_handle_background_color local term_input = ffi.new('TermInput', {}) local events = globals.main_loop.thread_events + local kIncomplete = cinput.kIncomplete + local kNotApplicable = cinput.kNotApplicable + local kComplete = cinput.kComplete -- Short-circuit when not waiting for response. term_input.waiting_for_bg_response = 0 - eq(false, handle_background_color(term_input)) + eq(kNotApplicable, handle_background_color(term_input)) local capacity = 100 local rbuf = ffi.gc(rbuffer.rbuffer_new(capacity), rbuffer.rbuffer_free) @@ -28,7 +31,7 @@ itp('handle_background_color', function() rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) term_input.waiting_for_bg_response = 1 - eq(true, handle_background_color(term_input)) + eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) eq(1, multiqueue.multiqueue_size(events)) @@ -97,14 +100,42 @@ itp('handle_background_color', function() assert_bg('rgba', 'f/f/f/f', 'light') - -- Incomplete sequence: not necessarily correct behavior, but tests it. + -- Incomplete sequence: necessarily correct behavior. local term_response = '\027]11;rgba:f/f/f/f' -- missing '\007 rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) term_input.waiting_for_bg_response = 1 - eq(false, handle_background_color(term_input)) + eq(kIncomplete, handle_background_color(term_input)) + eq(1, term_input.waiting_for_bg_response) + eq(#term_response, rbuf.size) + + term_response = '\007' + rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) + eq(kComplete, handle_background_color(term_input)) + eq(0, term_input.waiting_for_bg_response) + + local event = multiqueue.multiqueue_get(events) + local bg_event = ffi.cast("Event*", event.argv[1]) + eq('light', ffi.string(bg_event.argv[0])) + eq(0, multiqueue.multiqueue_size(events)) + eq(0, rbuf.size) + + term_response = '\027]11;rg' + rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) + + term_input.waiting_for_bg_response = 1 + eq(kIncomplete, handle_background_color(term_input)) + eq(1, term_input.waiting_for_bg_response) + eq(#term_response, rbuf.size) + + term_response = 'ba:f/f/f/f\007' + rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) + eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) + event = multiqueue.multiqueue_get(events) + bg_event = ffi.cast("Event*", event.argv[1]) + eq('light', ffi.string(bg_event.argv[0])) eq(0, multiqueue.multiqueue_size(events)) eq(0, rbuf.size) @@ -114,7 +145,7 @@ itp('handle_background_color', function() rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) term_input.waiting_for_bg_response = 3 - eq(false, handle_background_color(term_input)) + eq(kNotApplicable, handle_background_color(term_input)) eq(2, term_input.waiting_for_bg_response) eq(0, multiqueue.multiqueue_size(events)) @@ -127,7 +158,7 @@ itp('handle_background_color', function() rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response) term_input.waiting_for_bg_response = 1 - eq(true, handle_background_color(term_input)) + eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) eq(1, multiqueue.multiqueue_size(events)) |