diff options
72 files changed, 1429 insertions, 1077 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 7d41c17894..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,3 +0,0 @@ -/.github/ @jamessan -/ci/ @jamessan -/scripts/ @jamessan diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index d1bc412399..acdbc5da1e 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -42,6 +42,7 @@ EOF tsan) cat <<EOF >> "$GITHUB_ENV" TSAN_OPTIONS=log_path=$GITHUB_WORKSPACE/build/log/tsan +CLANG_SANITIZER=TSAN EOF ;; lint) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index f89c83e26f..a2e0566eb1 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -3,6 +3,7 @@ on: pull_request_target: types: [opened] jobs: + triage: runs-on: ubuntu-latest permissions: @@ -12,6 +13,7 @@ jobs: - uses: actions/labeler@main with: repo-token: "${{ secrets.GITHUB_TOKEN }}" + type-scope: runs-on: ubuntu-latest needs: ["triage"] @@ -24,7 +26,81 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} PR_TITLE: ${{ github.event.pull_request.title }} steps: - # Extract type and try to add it as a label - - run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" || true - # Extract scope and try to add it as a label - - run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')" || true + - name: "Extract commit type and add as label" + continue-on-error: true + run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" + - name: "Extract commit scope and add as label" + continue-on-error: true + run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')" + + add-reviewer: + # This job currently doesn't work due to a bug in GitHub CLI. + # + # Issue: https://github.com/cli/cli/issues/4844 + # PR that will fix it: https://github.com/cli/cli/pull/4876 + # + # The current workaround is to temporarily add "continue-on-error" flag so + # the whole workflow doesn't get flagged as a failure. + runs-on: ubuntu-latest + needs: ["triage", "type-scope"] + permissions: + contents: write + pull-requests: write + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + continue-on-error: true + steps: + - name: Get labels + id: labels + run: echo "::set-output name=labels::$(gh pr view $PR_NUMBER --json labels --jq '.labels.[].name' | tr '\n' '%')" + + # The % at the end of the label is a hack to avoid selecting a label that + # is substring of another label. So if there is a label "cinema" then we + # don't accidentally want to interpret that as the label "ci" + + - if: contains(steps.labels.outputs.labels, 'api%') + run: gh pr edit $PR_NUMBER --add-reviewer bfredl,gpanders,muniter + + - if: contains(steps.labels.outputs.labels, 'ci%') + run: gh pr edit $PR_NUMBER --add-reviewer jamessan + + - if: contains(steps.labels.outputs.labels, 'diagnostic%') + run: gh pr edit $PR_NUMBER --add-reviewer gpanders + + - if: contains(steps.labels.outputs.labels, 'distribution%') + run: gh pr edit $PR_NUMBER --add-reviewer jamessan + + - if: contains(steps.labels.outputs.labels, 'documentation%') + run: gh pr edit $PR_NUMBER --add-reviewer clason + + - if: contains(steps.labels.outputs.labels, 'extmarks%') + run: gh pr edit $PR_NUMBER --add-reviewer bfredl + + - if: contains(steps.labels.outputs.labels, 'filetype%') + run: gh pr edit $PR_NUMBER --add-reviewer clason,gpanders + + - if: contains(steps.labels.outputs.labels, 'gui%') + run: gh pr edit $PR_NUMBER --add-reviewer glacambre,smolck + + - if: contains(steps.labels.outputs.labels, 'platform:windows%') + run: gh pr edit $PR_NUMBER --add-reviewer erw7 + + - if: contains(steps.labels.outputs.labels, 'lsp%') + run: gh pr edit $PR_NUMBER --add-reviewer mfussenegger,mjlbach + + - if: contains(steps.labels.outputs.labels, 'terminal%') + run: gh pr edit $PR_NUMBER --add-reviewer erw7 + + - if: contains(steps.labels.outputs.labels, 'treesitter%') + run: gh pr edit $PR_NUMBER --add-reviewer bfredl,vigoux + + - if: contains(steps.labels.outputs.labels, 'typo%') + run: gh pr edit $PR_NUMBER --add-reviewer dundargoc + + - if: contains(steps.labels.outputs.labels, 'ui%') + run: gh pr edit $PR_NUMBER --add-reviewer bfredl + + - if: contains(steps.labels.outputs.labels, 'vim-patch%') + run: gh pr edit $PR_NUMBER --add-reviewer janlazo,seandewar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ad4c90eaf..b72c2ab71d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -212,6 +212,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NVIM_VERSION: ${{ needs.linux.outputs.version }} + DEBUG: api run: | envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md" gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos/* nvim-linux64/* appimage/* nvim-win64/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c43c5c494..e0f05e1205 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -408,6 +408,19 @@ main(void) if(TS_HAS_SET_MATCH_LIMIT) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNVIM_TS_HAS_SET_MATCH_LIMIT") endif() +check_c_source_compiles(" +#include <stdlib.h> +#include <tree_sitter/api.h> +int +main(void) +{ + ts_set_allocator(malloc, calloc, realloc, free); + return 0; +} +" TS_HAS_SET_ALLOCATOR) +if(TS_HAS_SET_ALLOCATOR) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNVIM_TS_HAS_SET_ALLOCATOR") +endif() # Note: The test lib requires LuaJIT; it will be skipped if LuaJIT is missing. option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index 9933ace8c2..342731b272 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -829,6 +829,23 @@ func dist#ft#Dep3patch() endfor endfunc +" This function checks the first 15 lines for appearance of 'FoamFile' +" and then 'object' in a following line. +" In that case, it's probably an OpenFOAM file +func dist#ft#FTfoam() + let ffile = 0 + let lnum = 1 + while lnum <= 15 + if getline(lnum) =~# '^FoamFile' + let ffile = 1 + elseif ffile == 1 && getline(lnum) =~# '^\s*object' + setf foam + return + endif + let lnum = lnum + 1 + endwhile +endfunc + " Restore 'cpoptions' let &cpo = s:cpo_save unlet s:cpo_save diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 90d353f9de..a954e1b1a3 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -7,7 +7,6 @@ let s:loaded_man = 1 let s:find_arg = '-w' let s:localfile_arg = v:true " Always use -l if possible. #6683 -let s:section_arg = '-S' function! man#init() abort try @@ -216,16 +215,38 @@ endfunction function! s:get_path(sect, name) abort " Some man implementations (OpenBSD) return all available paths from the - " search command, so we get() the first one. #8341 - if empty(a:sect) - return substitute(get(split(s:system(['man', s:find_arg, a:name])), 0, ''), '\n\+$', '', '') + " search command. Previously, this function would simply select the first one. + " + " However, some searches will report matches that are incorrect: + " man -w strlen may return string.3 followed by strlen.3, and therefore + " selecting the first would get us the wrong page. Thus, we must find the + " first matching one. + " + " There's yet another special case here. Consider the following: + " If you run man -w strlen and string.3 comes up first, this is a problem. We + " should search for a matching named one in the results list. + " However, if you search for man -w clock_gettime, you will *only* get + " clock_getres.2, which is the right page. Searching the resuls for + " clock_gettime will no longer work. In this case, we should just use the + " first one that was found in the correct section. + " + " Finally, we can avoid relying on -S or -s here since they are very + " inconsistently supported. Instead, call -w with a section and a name. + let results = split(s:system(['man', s:find_arg, a:sect, a:name])) + + if empty(results) + return '' endif - " '-s' flag handles: - " - tokens like 'printf(echo)' - " - sections starting with '-' - " - 3pcap section (found on macOS) - " - commas between sections (for section priority) - return substitute(get(split(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])), 0, ''), '\n\+$', '', '') + + " find any that match the specified name + let namematches = filter(copy(results), 'fnamemodify(v:val, ":t") =~ a:name') + let sectmatches = [] + + if !empty(namematches) && !empty(a:sect) + let sectmatches = filter(copy(namematches), 'fnamemodify(v:val, ":e") == a:sect') + endif + + return substitute(get(sectmatches, 0, get(namematches, 0, results[0])), '\n\+$', '', '') endfunction " s:verify_exists attempts to find the path to a manpage @@ -243,40 +264,72 @@ endfunction " then we don't do it again in step 2. function! s:verify_exists(sect, name) abort let sect = a:sect - if empty(sect) - let sect = get(b:, 'man_default_sects', '') - endif - try - return s:get_path(sect, a:name) - catch /^command error (/ - endtry - - if !empty(get(b:, 'man_default_sects', '')) && sect !=# b:man_default_sects + if empty(sect) + " no section specified, so search with b:man_default_sects + if exists('b:man_default_sects') + let sects = split(b:man_default_sects, ',') + for sec in sects + try + let res = s:get_path(sec, a:name) + if !empty(res) + return res + endif + catch /^command error (/ + endtry + endfor + endif + else + " try with specified section try - return s:get_path(b:man_default_sects, a:name) + let res = s:get_path(sect, a:name) + if !empty(res) + return res + endif catch /^command error (/ endtry - endif - if !empty(sect) - try - return s:get_path('', a:name) - catch /^command error (/ - endtry + " try again with b:man_default_sects + if exists('b:man_default_sects') + let sects = split(b:man_default_sects, ',') + for sec in sects + try + let res = s:get_path(sec, a:name) + if !empty(res) + return res + endif + catch /^command error (/ + endtry + endfor + endif endif + " if none of the above worked, we will try with no section + try + let res = s:get_path('', a:name) + if !empty(res) + return res + endif + catch /^command error (/ + endtry + + " if that still didn't work, we will check for $MANSECT and try again with it + " unset if !empty($MANSECT) try let MANSECT = $MANSECT call setenv('MANSECT', v:null) - return s:get_path('', a:name) + let res = s:get_path('', a:name) + if !empty(res) + return res + endif catch /^command error (/ finally call setenv('MANSECT', MANSECT) endtry endif + " finally, if that didn't work, there is no hope throw 'no manual entry for ' . a:name endfunction diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 7716af25bd..641cd93386 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -697,7 +697,8 @@ Line numbers may be specified with: *:range* *{address}* Each may be followed (several times) by '+' or '-' and an optional number. This number is added or subtracted from the preceding line number. If the -number is omitted, 1 is used. +number is omitted, 1 is used. If there is nothing before the '+' or '-' then +the current line is used. The "/" and "?" after {pattern} are required to separate the pattern from anything that follows. @@ -727,7 +728,7 @@ Some commands allow for a count after the command. This count is used as the number of lines to be used, starting with the line given in the last line specifier (the default is the cursor line). The commands that accept a count are the ones that use a range but do not have a file name argument (because -a file name can also be a number). +a file name can also be a number). The count cannot be negative. Examples: > :s/x/X/g 5 substitute 'x' by 'X' in the current line and four diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index bb36fa46f6..19db3158be 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -334,8 +334,9 @@ config({opts}, {namespace}) *vim.diagnostic.config()* that returns any of the above. Parameters: ~ - {opts} table Configuration table with the following - keys: + {opts} table|nil When omitted or "nil", retrieve the + current configuration. Otherwise, a + configuration table with the following keys: • underline: (default true) Use underline for diagnostics. Options: • severity: Only underline diagnostics diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index c2158f3a1e..fa75ead9a3 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -111,7 +111,7 @@ When mixing Number and Float the Number is converted to Float. Otherwise there is no automatic conversion of Float. You can use str2float() for String to Float, printf() for Float to String and float2nr() for Float to Number. - *E891* *E892* *E893* *E894* + *E362* *E891* *E892* *E893* *E894* *E907* When expecting a Float a Number can also be used, but nothing else. *no-type-checking* @@ -1195,6 +1195,7 @@ When expr8 is a |Funcref| type variable, invoke the function it refers to. expr8->name([args]) method call *method* *->* expr8->{lambda}([args]) + *E260* *E276* For methods that are also available as global functions this is the same as: > name(expr8 [, args]) There can also be methods specifically for the type of "expr8". @@ -4574,8 +4575,10 @@ funcref({name} [, {arglist}] [, {dict}]) function {name} is redefined later. Unlike |function()|, {name} must be an existing user function. - Also for autoloaded functions. {name} cannot be a builtin - function. + It only works for an autoloaded function if it has already + been loaded (to avoid mistakenly loading the autoload script + when only intending to use the function name, use |function()| + instead). {name} cannot be a builtin function. Can also be used as a |method|: > GetFuncname()->funcref([arg]) @@ -6892,7 +6895,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()* GetText()->match('word') GetList()->match('word') < - *matchadd()* *E798* *E799* *E801* *E957* + *matchadd()* *E798* *E799* *E801* *E957* matchadd({group}, {pattern}[, {priority}[, {id} [, {dict}]]]) Defines a pattern to be highlighted in the current window (a "match"). It will be highlighted with {group}. Returns an @@ -11063,7 +11066,7 @@ See |:verbose-cmd| for more information. command, use line breaks instead of |:bar|: > :exe "func Foo()\necho 'foo'\nendfunc" < - *:delf* *:delfunction* *E130* *E131* *E933* + *:delf* *:delfunction* *E131* *E933* :delf[unction][!] {name} Delete function {name}. {name} can also be a |Dictionary| entry that is a @@ -13153,7 +13156,7 @@ code can be used: > unlet scriptnames_output ============================================================================== -The sandbox *eval-sandbox* *sandbox* *E48* +The sandbox *eval-sandbox* *sandbox* The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and 'foldtext' options may be evaluated in a sandbox. This means that you are @@ -13162,6 +13165,7 @@ safety for when these options are set from a modeline. It is also used when the command from a tags file is executed and for CTRL-R = in the command line. The sandbox is also used for the |:sandbox| command. + *E48* These items are not allowed in the sandbox: - changing the buffer text - defining or changing mapping, autocommands, user commands diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 9cc7d063a8..03ec8966d4 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -219,7 +219,7 @@ command: > < *:helpt* *:helptags* - *E154* *E150* *E151* *E152* *E153* *E670* *E856* + *E150* *E151* *E152* *E153* *E154* *E670* *E856* :helpt[ags] [++t] {dir} Generate the help tags file(s) for directory {dir}. When {dir} is ALL then all "doc" directories in diff --git a/runtime/doc/if_cscop.txt b/runtime/doc/if_cscop.txt index f05b3bb8ed..8947aefc1b 100644 --- a/runtime/doc/if_cscop.txt +++ b/runtime/doc/if_cscop.txt @@ -40,7 +40,7 @@ See |cscope-usage| to get started. ============================================================================== Cscope commands *cscope-commands* - *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E561* *E560* + *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E560* *E561* All cscope commands are accessed through suboptions to the cscope commands. `:cscope` or `:cs` is the main command `:scscope` or `:scs` does the same and splits the window diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index 1b42092616..a76f8636f8 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -872,7 +872,7 @@ For example, with N = 1, this will give: *PHP_outdentphpescape* To indent PHP escape tags as the surrounding non-PHP code (only affects the PHP escape tags): > -:let g:PHP_outdentphpescape = 0 + :let g:PHP_outdentphpescape = 0 ------------- *PHP_removeCRwhenUnix* @@ -1199,7 +1199,7 @@ comments will be indented according to the correctly indented code. VIM *ft-vim-indent* - + *g:vim_indent_cont* For indenting Vim scripts there is one variable that specifies the amount of indent for a continuation line, a line that starts with a backslash: > diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index d74d7cafa8..ae2b9c4418 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -830,7 +830,7 @@ space is preferred). Maximum line length is 510 bytes. For an example, imagine the 'thesaurus' file has a line like this: > angry furious mad enraged -<Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would +Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would complete the word "angry"; subsequent presses would change the word to "furious", "mad" etc. @@ -842,7 +842,7 @@ https://github.com/vim/vim/issues/629#issuecomment-443293282 Unpack thesaurus_pkg.zip, put the thesaurus.txt file somewhere, e.g. ~/.vim/thesaurus/english.txt, and the 'thesaurus' option to this file name. - + Completing keywords with 'thesaurusfunc' *compl-thesaurusfunc* If the 'thesaurusfunc' option is set, then the user specified function is diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index f6d9e45d64..e2c274e040 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -82,8 +82,7 @@ modes. map command applies. The mapping may remain defined for other modes where it applies. It also works when {lhs} matches the {rhs} of a - mapping. This is for when when an abbreviation - applied. + mapping. This is for when an abbreviation applied. Note: Trailing spaces are included in the {lhs}. This unmap does NOT work: > :map @@ foo diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index 3bbf36c642..2aa49cee1e 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -489,8 +489,8 @@ Use the RPM or port for your system. window specific to the input method. -USING XIM *multibyte-input* *E284* *E286* *E287* *E288* - *E285* *E289* +USING XIM *multibyte-input* *E284* *E285* *E286* *E287* + *E288* *E289* Note that Display and Input are independent. It is possible to see your language even though you have no input method for it. But when your Display diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 6fbd9ec922..950028d9cc 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -112,7 +112,8 @@ wiped out a buffer which contains a mark or is referenced in another way. *E95* > Buffer with this name already exists -You cannot have two buffers with the same name. +You cannot have two buffers with exactly the same name. This includes the +path leading to the file. *E72* > Close error on swap file @@ -513,10 +514,10 @@ If you type "gq", it will execute this mapping, which will call "gq" again. *E22* > Scripts nested too deep -Scripts can be read with the "-s" command-line argument and with the ":source" -command. The script can then again read another script. This can continue -for about 14 levels. When more nesting is done, Vim assumes that there is a -recursive loop somewhere and stops with this error message. +Scripts can be read with the "-s" command-line argument and with the +`:source!` command. The script can then again read another script. This can +continue for about 14 levels. When more nesting is done, Vim assumes that +there is a recursive loop and stops with this error message. *E300* > Swap file already exists (symlink attack?) @@ -686,6 +687,7 @@ Ex command or function was given an invalid argument. Or |jobstart()| or Trailing characters An argument was given to an Ex command that does not permit one. +Or the argument has invalid characters and has not been recognized. *E477* *E478* > No ! allowed diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c929178f5a..06236741c2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -304,7 +304,7 @@ value to the local value, it doesn't switch back to using the global value This will make the local value of 'path' empty, so that the global value is used. Thus it does the same as: > :setlocal path= -Note: In the future more global options can be made global-local. Using +Note: In the future more global options can be made |global-local|. Using ":setlocal" on a global option might work differently then. @@ -686,10 +686,10 @@ A jump table for the options with a short description can be found at |Q_op|. Write the contents of the file, if it has been modified, on each `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`, `:suspend`, `:tag`, `:!`, `:make`, CTRL-] and CTRL-^ command; and when - a :buffer, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one + a `:buffer`, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one to another file. A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is - set to "hide" and `:next` is used + set to "hide" and `:next` is used. Note that for some commands the 'autowrite' option is not used, see 'autowriteall' for that. Some buffers will not be written, specifically when 'buftype' is @@ -5131,7 +5131,8 @@ A jump table for the options with a short description can be found at |Q_op|. Don't include both "curdir" and "sesdir". When neither is included filenames are stored as absolute paths. - + If you leave out "options" many things won't work well after restoring + the session. *'shada'* *'sd'* *E526* *E527* *E528* 'shada' 'sd' string (Vim default for Win32: !,'100,<50,s10,h,rA:,rB: diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index dfed39dba6..634145da3e 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -304,7 +304,7 @@ the pattern. ============================================================================== 2. The definition of a pattern *search-pattern* *pattern* *[pattern]* *regular-expression* *regexp* *Pattern* - *E76* *E383* *E476* + *E383* *E476* For starters, read chapter 27 of the user manual |usr_27.txt|. @@ -1036,6 +1036,8 @@ match ASCII characters, as indicated by the range. \(\) A pattern enclosed by escaped parentheses. */\(* */\(\)* */\)* E.g., "\(^a\)" matches 'a' at the start of a line. + There can only be ten of these. You can use "\%(" to add more, but + not counting it as a sub-expression. *E51* *E54* *E55* *E872* *E873* \1 Matches the same string that was matched by */\1* *E65* @@ -1058,7 +1060,7 @@ x A single character, with no special meaning, matches itself \x A backslash followed by a single character, with no special meaning, is reserved for future expansions -[] (with 'nomagic': \[]) */[]* */\[]* */\_[]* */collection* +[] (with 'nomagic': \[]) */[]* */\[]* */\_[]* */collection* *E76* \_[] A collection. This is a sequence of characters enclosed in square brackets. It matches any single character in the collection. diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index ba1da209f7..873fbd8971 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -232,7 +232,7 @@ processing a quickfix or location list command, it will be aborted. ":qall!" |:qall|, except that Nvim exits non-zero or [count]. - *:cf* *:cfile* + *:cf* *:cfi* *:cfile* :cf[ile][!] [errorfile] Read the error file and jump to the first error. This is done automatically when Vim is started with the -q option. You can use this command when you diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 0fd481cd83..2c5531411d 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1406,7 +1406,7 @@ add the following line to your startup file: > :let g:filetype_euphoria = "euphoria4" -Elixir and Euphoria share the *.ex file extension. If the filetype is +Elixir and Euphoria share the *.ex file extension. If the filetype is specifically set as Euphoria with the g:filetype_euphoria variable, or the file is determined to be Euphoria based on keywords in the file, then the filetype will be set as Euphoria. Otherwise, the filetype will default to @@ -1437,7 +1437,7 @@ The following file extensions are auto-detected as Elixir file types: *.ex, *.exs, *.eex, *.leex, *.lock -Elixir and Euphoria share the *.ex file extension. If the filetype is +Elixir and Euphoria share the *.ex file extension. If the filetype is specifically set as Euphoria with the g:filetype_euphoria variable, or the file is determined to be Euphoria based on keywords in the file, then the filetype will be set as Euphoria. Otherwise, the filetype will default to diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 8a4468a130..05bffc3038 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -249,7 +249,7 @@ g8 Print the hex values of the bytes used in the To enter |Terminal-mode| automatically: > autocmd TermOpen * startinsert < - *:!cmd* *:!* *E34* + *:!cmd* *:!* :!{cmd} Execute {cmd} with 'shell'. See also |:terminal|. The command runs in a non-interactive shell connected @@ -261,6 +261,7 @@ g8 Print the hex values of the bytes used in the Use |jobstart()| instead. > :call jobstart('foo', {'detach':1}) < + *E34* Any "!" in {cmd} is replaced with the previous external command (see also 'cpoptions'), unless escaped by a backslash. Example: ":!ls" followed by diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index bb31895c96..5b91321c40 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -124,7 +124,7 @@ highlight group (|hl-EndOfBuffer|) can be used to change the highlighting of the filler characters. ============================================================================== -3. Opening and closing a window *opening-window* *E36* +3. Opening and closing a window *opening-window* CTRL-W s *CTRL-W_s* CTRL-W S *CTRL-W_S* @@ -223,6 +223,10 @@ CTRL-W ge *CTRL-W_ge* Note that the 'splitbelow' and 'splitright' options influence where a new window will appear. + *E36* +Creating a window will fail if there is not enough room. Every window needs +at least one screen line and column, sometimes more. Options 'winminheight' +and 'winminwidth' are relevant. *:vert* *:vertical* :vert[ical] {cmd} diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 907f806f31..900bbf778d 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Dec 27 +" Last Change: 2022 Jan 05 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -1210,6 +1210,9 @@ au BufNewFile,BufRead *.xom,*.xin setf omnimark " OPAM au BufNewFile,BufRead opam,*.opam,*.opam.template setf opam +" OpenFOAM +au BufNewFile,BufRead [a-zA-Z0-9]*Dict\(.*\)\=,[a-zA-Z]*Properties\(.*\)\=,*Transport\(.*\),fvSchemes,fvSolution,fvConstrains,fvModels,*/constant/g,*/0\(\.orig\)\=/* call dist#ft#FTfoam() + " OpenROAD au BufNewFile,BufRead *.or setf openroad @@ -2282,6 +2285,9 @@ au BufNewFile,BufRead Kconfig.* call s:StarSetf('kconfig') " Lilo: Linux loader au BufNewFile,BufRead lilo.conf* call s:StarSetf('lilo') +" Libsensors +au BufNewFile,BufRead */etc/sensors.d/[^.]* call s:StarSetf('sensors') + " Logcheck au BufNewFile,BufRead */etc/logcheck/*.d*/* call s:StarSetf('logcheck') diff --git a/runtime/ftplugin/git.vim b/runtime/ftplugin/git.vim deleted file mode 100644 index 75b20f021e..0000000000 --- a/runtime/ftplugin/git.vim +++ /dev/null @@ -1,41 +0,0 @@ -" Vim filetype plugin -" Language: generic git output -" Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 - -" Only do this when not done yet for this buffer -if (exists("b:did_ftplugin")) - finish -endif -let b:did_ftplugin = 1 - -if !exists('b:git_dir') - if expand('%:p') =~# '[\/]\.git[\/]modules[\/]\|:[\/][\/]\|^\a\a\+:' - " Stay out of the way - elseif expand('%:p') =~# '[\/]\.git[\/]worktrees' - let b:git_dir = matchstr(expand('%:p'),'.*\.git[\/]worktrees[\/][^\/]\+\>') - elseif expand('%:p') =~# '\.git\>' - let b:git_dir = matchstr(expand('%:p'),'.*\.git\>') - elseif $GIT_DIR != '' - let b:git_dir = $GIT_DIR - endif - if (has('win32') || has('win64')) && exists('b:git_dir') - let b:git_dir = substitute(b:git_dir,'\\','/','g') - endif -endif - -if exists('*shellescape') && exists('b:git_dir') && b:git_dir != '' - if b:git_dir =~# '/\.git$' " Not a bare repository - let &l:path = escape(fnamemodify(b:git_dir,':h'),'\, ').','.&l:path - endif - let &l:path = escape(b:git_dir,'\, ').','.&l:path - let &l:keywordprg = 'git --git-dir='.shellescape(b:git_dir).' show' -else - setlocal keywordprg=git\ show -endif -if has('gui_running') - let &l:keywordprg = substitute(&l:keywordprg,'^git\>','git --no-pager','') -endif - -setlocal includeexpr=substitute(v:fname,'^[^/]\\+/','','') -let b:undo_ftplugin = "setl keywordprg< path< includeexpr<" diff --git a/runtime/ftplugin/gitcommit.vim b/runtime/ftplugin/gitcommit.vim index 9b1998acaa..9342799b56 100644 --- a/runtime/ftplugin/gitcommit.vim +++ b/runtime/ftplugin/gitcommit.vim @@ -1,66 +1,57 @@ " Vim filetype plugin " Language: git commit file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 " Only do this when not done yet for this buffer if (exists("b:did_ftplugin")) finish endif -runtime! ftplugin/git.vim let b:did_ftplugin = 1 -setlocal comments=:# commentstring=#\ %s setlocal nomodeline tabstop=8 formatoptions+=tl textwidth=72 setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions-=q formatoptions+=n setlocal formatlistpat+=\\\|^\\s*[-*+]\\s\\+ +setlocal include=^+++ +setlocal includeexpr=substitute(v:fname,'^[bi]/','','') -let b:undo_ftplugin = 'setl modeline< tabstop< formatoptions< tw< com< cms< formatlistpat<' +let b:undo_ftplugin = 'setl modeline< tabstop< formatoptions< tw< com< cms< formatlistpat< inc< inex<' -if exists("g:no_gitcommit_commands") || v:version < 700 - finish -endif +let s:l = search('\C\m^[#;@!$%^&|:] -\{24,\} >8 -\{24,\}$', 'cnW', '', 100) +let &l:comments = ':' . (matchstr(getline(s:l ? s:l : '$'), '^[#;@!$%^&|:]\S\@!') . '#')[0] +let &l:commentstring = &l:comments[1] . ' %s' +unlet s:l -if !exists("b:git_dir") - let b:git_dir = expand("%:p:h") +if exists("g:no_gitcommit_commands") + finish endif -command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0,b:git_dir,<f-args>) +command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0, <f-args>) let b:undo_ftplugin = b:undo_ftplugin . "|delc DiffGitCached" -function! s:diffcomplete(A,L,P) +function! s:diffcomplete(A, L, P) abort let args = "" if a:P <= match(a:L." -- "," -- ")+3 let args = args . "-p\n--stat\n--shortstat\n--summary\n--patch-with-stat\n--no-renames\n-B\n-M\n-C\n" end - if exists("b:git_dir") && a:A !~ '^-' - let tree = fnamemodify(b:git_dir,':h') - if strpart(getcwd(),0,strlen(tree)) == tree - let args = args."\n".system("git diff --cached --name-only") - endif + if a:A !~ '^-' && !empty(getftype('.git')) + let args = args."\n".system("git diff --cached --name-only") endif return args endfunction -function! s:gitdiffcached(bang,gitdir,...) - let tree = fnamemodify(a:gitdir,':h') +function! s:gitdiffcached(bang, ...) abort let name = tempname() - let git = "git" - if strpart(getcwd(),0,strlen(tree)) != tree - let git .= " --git-dir=".(exists("*shellescape") ? shellescape(a:gitdir) : '"'.a:gitdir.'"') - endif if a:0 - let extra = join(map(copy(a:000),exists("*shellescape") ? 'shellescape(v:val)' : "'\"'.v:val.'\"'")) + let extra = join(map(copy(a:000), 'shellescape(v:val)')) else let extra = "-p --stat=".&columns endif - call system(git." diff --cached --no-color --no-ext-diff ".extra." > ".(exists("*shellescape") ? shellescape(name) : name)) - exe "pedit ".(exists("*fnameescape") ? fnameescape(name) : name) + call system("git diff --cached --no-color --no-ext-diff ".extra." > ".shellescape(name)) + exe "pedit " . fnameescape(name) wincmd P - let b:git_dir = a:gitdir - command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0,b:git_dir,<f-args>) - nnoremap <buffer> <silent> q :q<CR> + command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0, <f-args>) setlocal buftype=nowrite nobuflisted noswapfile nomodifiable filetype=git endfunction diff --git a/runtime/ftplugin/gitrebase.vim b/runtime/ftplugin/gitrebase.vim index 2fed53c829..143f86a251 100644 --- a/runtime/ftplugin/gitrebase.vim +++ b/runtime/ftplugin/gitrebase.vim @@ -1,22 +1,20 @@ " Vim filetype plugin " Language: git rebase --interactive " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 " Only do this when not done yet for this buffer if (exists("b:did_ftplugin")) finish endif -runtime! ftplugin/git.vim let b:did_ftplugin = 1 -setlocal comments=:# commentstring=#\ %s formatoptions-=t +let &l:comments = ':' . (matchstr(getline('$'), '^[#;@!$%^&|:]\S\@!') . '#')[0] +let &l:commentstring = &l:comments[1] . ' %s' +setlocal formatoptions-=t setlocal nomodeline -if !exists("b:undo_ftplugin") - let b:undo_ftplugin = "" -endif -let b:undo_ftplugin = b:undo_ftplugin."|setl com< cms< fo< ml<" +let b:undo_ftplugin = "setl com< cms< fo< ml<" function! s:choose(word) abort s/^\(\w\+\>\)\=\(\s*\)\ze\x\{4,40\}\>/\=(strlen(submatch(1)) == 1 ? a:word[0] : a:word) . substitute(submatch(2),'^$',' ','')/e @@ -41,8 +39,7 @@ if exists("g:no_plugin_maps") || exists("g:no_gitrebase_maps") finish endif -nnoremap <buffer> <expr> K col('.') < 7 && expand('<Lt>cword>') =~ '\X' && getline('.') =~ '^\w\+\s\+\x\+\>' ? 'wK' : 'K' nnoremap <buffer> <silent> <C-A> :<C-U><C-R>=v:count1<CR>Cycle<CR> nnoremap <buffer> <silent> <C-X> :<C-U><C-R>=v:count1<CR>Cycle!<CR> -let b:undo_ftplugin = b:undo_ftplugin . "|exe 'nunmap <buffer> K'|exe 'nunmap <buffer> <C-A>'|exe 'nunmap <buffer> <C-X>'" +let b:undo_ftplugin = b:undo_ftplugin . "|exe 'nunmap <buffer> <C-A>'|exe 'nunmap <buffer> <C-X>'" diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 417b661155..b4537c2882 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -30,7 +30,7 @@ M.handlers = setmetatable({}, { __newindex = function(t, name, handler) vim.validate { handler = {handler, "t" } } rawset(t, name, handler) - if not global_diagnostic_options[name] then + if global_diagnostic_options[name] == nil then global_diagnostic_options[name] = true end end, @@ -552,7 +552,8 @@ end --- - `table`: Enable this feature with overrides. Use an empty table to use default values. --- - `function`: Function with signature (namespace, bufnr) that returns any of the above. --- ----@param opts table Configuration table with the following keys: +---@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a +--- configuration table with the following keys: --- - underline: (default true) Use underline for diagnostics. Options: --- * severity: Only underline diagnostics matching the given severity --- |diagnostic-severity| @@ -599,7 +600,7 @@ end --- global diagnostic options. function M.config(opts, namespace) vim.validate { - opts = { opts, 't' }, + opts = { opts, 't', true }, namespace = { namespace, 'n', true }, } @@ -611,10 +612,13 @@ function M.config(opts, namespace) t = global_diagnostic_options end - for opt in pairs(global_diagnostic_options) do - if opts[opt] ~= nil then - t[opt] = opts[opt] - end + if not opts then + -- Return current config + return vim.deepcopy(t) + end + + for k, v in pairs(opts) do + t[k] = v end if namespace then @@ -823,6 +827,7 @@ M.handlers.signs = { } bufnr = get_bufnr(bufnr) + opts = opts or {} if opts.signs and opts.signs.severity then diagnostics = filter_by_severity(opts.signs.severity, diagnostics) @@ -890,6 +895,7 @@ M.handlers.underline = { } bufnr = get_bufnr(bufnr) + opts = opts or {} if opts.underline and opts.underline.severity then diagnostics = filter_by_severity(opts.underline.severity, diagnostics) @@ -942,6 +948,7 @@ M.handlers.virtual_text = { } bufnr = get_bufnr(bufnr) + opts = opts or {} local severity if opts.virtual_text then diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 196e782f4e..819587bb3e 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -15,7 +15,7 @@ local function starsetf(ft) end end, { -- Starset matches should always have lowest priority - priority = -1, + priority = -math.huge, }} end @@ -119,7 +119,13 @@ local extension = { tcc = "cpp", hxx = "cpp", hpp = "cpp", - cpp = function() + cpp = function(path, bufnr) + if vim.g.cynlib_syntax_for_cc then + return "cynlib" + end + return "cpp" + end, + cc = function(path, bufnr) if vim.g.cynlib_syntax_for_cc then return "cynlib" end @@ -770,6 +776,14 @@ local extension = { mm = function() vim.fn["dist#ft#FTmm"]() end, mms = function() vim.fn["dist#ft#FTmms"]() end, p = function() vim.fn["dist#ft#FTprogress_pascal"]() end, + patch = function(path, bufnr) + local firstline = getline(bufnr, 1) + if string.find(firstline, "^From " .. string.rep("%x", 40) .. "+ Mon Sep 17 00:00:00 2001$") then + return "gitsendemail" + else + return "diff" + end + end, pl = function() vim.fn["dist#ft#FTpl"]() end, pp = function() vim.fn["dist#ft#FTpp"]() end, pro = function() vim.fn["dist#ft#ProtoCheck"]('idlang') end, @@ -860,6 +874,10 @@ local filename = { ["exim.conf"] = "exim", exports = "exports", [".fetchmailrc"] = "fetchmail", + fvSchemes = function() vim.fn["dist#ft#FTfoam"]() end, + fvSolution = function() vim.fn["dist#ft#FTfoam"]() end, + fvConstraints = function() vim.fn["dist#ft#FTfoam"]() end, + fvModels = function() vim.fn["dist#ft#FTfoam"]() end, fstab = "fstab", mtab = "fstab", [".gdbinit"] = "gdb", @@ -1034,6 +1052,7 @@ local filename = { ["tidy.conf"] = "tidy", tidyrc = "tidy", [".tidyrc"] = "tidy", + [".tmux.conf"] = "tmux", ["/.cargo/config"] = "toml", Pipfile = "toml", ["Gopkg.lock"] = "toml", @@ -1071,6 +1090,7 @@ local filename = { [".alias"] = function() vim.fn["dist#ft#CSH"]() end, [".bashrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, [".cshrc"] = function() vim.fn["dist#ft#CSH"]() end, + [".env"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, [".kshrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end, [".login"] = function() vim.fn["dist#ft#CSH"]() end, [".profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, @@ -1228,6 +1248,8 @@ local pattern = { [".*/%.config/systemd/user/.*%.d/.*%.conf"] = "systemd", [".*/etc/systemd/system/.*%.d/.*%.conf"] = "systemd", [".*%.t%.html"] = "tilde", + ["%.?tmux.*%.conf"] = "tmux", + ["%.?tmux.*%.conf.*"] = { "tmux", { priority = -1 } }, [".*/%.cargo/config"] = "toml", [".*/%.cargo/credentials"] = "toml", [".*/etc/udev/udev%.conf"] = "udevconf", @@ -1334,6 +1356,21 @@ local pattern = { ["zlog.*"] = starsetf('zsh'), ["zsh.*"] = starsetf('zsh'), ["ae%d+%.txt"] = 'mail', + ["[a-zA-Z0-9].*Dict"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z0-9].*Dict%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z].*Properties"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z].*Properties%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*Transport%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/constant/g"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/0/.*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/0%.orig/.*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/etc/sensors%.d/[^.].*"] = starsetf('sensors'), + [".*%.git/.*"] = function(path, bufnr) + local firstline = getline(bufnr, 1) + if firstline:find("^" .. string.rep("%x", 40) .. "+ ") or firstline:sub(1, 5) == "ref: " then + return "git" + end + end, -- END PATTERN } -- luacheck: pop @@ -1361,7 +1398,7 @@ local pattern_sorted = sort_by_priority(pattern) ---@private local function normalize_path(path) - return (path:gsub("\\", "/")) + return (path:gsub("\\", "/"):gsub("^~", vim.env.HOME)) end --- Add new filetype mappings. @@ -1456,6 +1493,25 @@ local function dispatch(ft, path, bufnr, ...) end ---@private +local function match_pattern(name, path, tail, pat) + -- If the pattern contains a / match against the full path, otherwise just the tail + local fullpat = "^" .. pat .. "$" + local matches + if pat:find("/") then + -- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against + -- both the short file name (as typed) and the full file name (after expanding to full path + -- and resolving symlinks) + matches = name:match(fullpat) or path:match(fullpat) + else + matches = tail:match(fullpat) + end + return matches +end + +--- Set the filetype for the given buffer from a file name. +--- +---@param name string File name (can be an absolute or relative path) +---@param bufnr number|nil The buffer to set the filetype for. Defaults to the current buffer. function M.match(name, bufnr) -- When fired from the main filetypedetect autocommand the {bufnr} argument is omitted, so we use -- the current buffer. The {bufnr} argument is provided to allow extensibility in case callers @@ -1476,21 +1532,18 @@ function M.match(name, bufnr) return end - -- Next, check the file path against available patterns - for _, v in ipairs(pattern_sorted) do + -- Next, check the file path against available patterns with non-negative priority + local j = 1 + for i, v in ipairs(pattern_sorted) do local k = next(v) - local ft = v[k][1] - -- If the pattern contains a / match against the full path, otherwise just the tail - local pat = "^" .. k .. "$" - local matches - if k:find("/") then - -- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against - -- both the short file name (as typed) and the full file name (after expanding to full path - -- and resolving symlinks) - matches = name:match(pat) or path:match(pat) - else - matches = tail:match(pat) + local opts = v[k][2] + if opts.priority < 0 then + j = i + break end + + local ft = v[k][1] + local matches = match_pattern(name, path, tail, k) if matches then if dispatch(ft, path, bufnr, matches) then return @@ -1498,11 +1551,25 @@ function M.match(name, bufnr) end end - -- Finally, check file extension + -- Next, check file extension local ext = vim.fn.fnamemodify(name, ":e") if dispatch(extension[ext], path, bufnr) then return end + + -- Finally, check patterns with negative priority + for i = j, #pattern_sorted do + local v = pattern_sorted[i] + local k = next(v) + + local ft = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + if dispatch(ft, path, bufnr, matches) then + return + end + end + end end return M diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fb08e54dfc..8b7eb4ac90 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -312,56 +312,72 @@ do --- client_id → state --- --- state + --- use_incremental_sync: bool + --- buffers: bufnr -> buffer_state + --- + --- buffer_state --- pending_change?: function that the timer starts to trigger didChange --- pending_changes: table (uri -> list of pending changeset tables)); - -- Only set if incremental_sync is used - --- use_incremental_sync: bool - --- buffers?: table (bufnr → lines); for incremental sync only + --- Only set if incremental_sync is used + --- --- timer?: uv_timer + --- lines: table local state_by_client = {} ---@private function changetracking.init(client, bufnr) + local use_incremental_sync = ( + if_nil(client.config.flags.allow_incremental_sync, true) + and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental + ) local state = state_by_client[client.id] if not state then state = { - pending_changes = {}; - last_flush = {}; - use_incremental_sync = ( - if_nil(client.config.flags.allow_incremental_sync, true) - and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental - ); + buffers = {}; + debounce = client.config.flags.debounce_text_changes or 150, + use_incremental_sync = use_incremental_sync; } state_by_client[client.id] = state end - if not state.use_incremental_sync then - return - end - if not state.buffers then - state.buffers = {} + if not state.buffers[bufnr] then + local buf_state = {} + state.buffers[bufnr] = buf_state + if use_incremental_sync then + buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true) + buf_state.pending_changes = {} + end end - state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true) end ---@private function changetracking.reset_buf(client, bufnr) - changetracking.flush(client) + changetracking.flush(client, bufnr) local state = state_by_client[client.id] - if state then - if state.buffers then - state.buffers[bufnr] = nil + if state and state.buffers then + local buf_state = state.buffers[bufnr] + state.buffers[bufnr] = nil + if buf_state and buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil end - state.last_flush = {} end end ---@private function changetracking.reset(client_id) local state = state_by_client[client_id] - if state then - state_by_client[client_id] = nil - changetracking._reset_timer(state) + if not state then + return end + for _, buf_state in pairs(state.buffers) do + if buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil + end + end + state.buffers = {} end ---@private @@ -371,35 +387,27 @@ do -- debounce can be skipped and otherwise maybe reduced. -- -- This turns the debounce into a kind of client rate limiting - local function next_debounce(debounce, state, bufnr) + local function next_debounce(debounce, buf_state) if debounce == 0 then return 0 end local ns_to_ms = 0.000001 - local last_flush = state.last_flush[bufnr] - if not last_flush then + if not buf_state.last_flush then return debounce end local now = uv.hrtime() - local ms_since_last_flush = (now - last_flush) * ns_to_ms - local remaining_debounce = debounce - ms_since_last_flush - if remaining_debounce > 0 then - return remaining_debounce - else - state.last_flush[bufnr] = now - return 0 - end + local ms_since_last_flush = (now - buf_state.last_flush) * ns_to_ms + return math.max(debounce - ms_since_last_flush, 0) end ---@private function changetracking.prepare(bufnr, firstline, lastline, new_lastline) - local incremental_changes = function(client) - local cached_buffers = state_by_client[client.id].buffers + local incremental_changes = function(client, buf_state) local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true) local line_ending = buf_get_line_ending(bufnr) local incremental_change = sync.compute_diff( - cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) - cached_buffers[bufnr] = curr_lines + buf_state.lines, curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) + buf_state.lines = curr_lines return incremental_change end local full_changes = once(function() @@ -413,76 +421,68 @@ do return end local state = state_by_client[client.id] - local debounce = next_debounce(client.config.flags.debounce_text_changes or 150, state, bufnr) - if debounce == 0 then - local changes = state.use_incremental_sync and incremental_changes(client) or full_changes() - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = util.buf_versions[bufnr]; - }; - contentChanges = { changes, } - }) - return - end - changetracking._reset_timer(state) + local buf_state = state.buffers[bufnr] + changetracking._reset_timer(buf_state) + local debounce = next_debounce(state.debounce, buf_state) if state.use_incremental_sync then -- This must be done immediately and cannot be delayed -- The contents would further change and startline/endline may no longer fit - if not state.pending_changes[uri] then - state.pending_changes[uri] = {} - end - table.insert(state.pending_changes[uri], incremental_changes(client)) + table.insert(buf_state.pending_changes, incremental_changes(client, buf_state)) end - state.pending_change = function() - state.pending_change = nil - state.last_flush[bufnr] = uv.hrtime() + buf_state.pending_change = function() + buf_state.pending_change = nil + buf_state.last_flush = uv.hrtime() if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then return end - if state.use_incremental_sync then - for change_uri, content_changes in pairs(state.pending_changes) do - client.notify("textDocument/didChange", { - textDocument = { - uri = change_uri; - version = util.buf_versions[vim.uri_to_bufnr(change_uri)]; - }; - contentChanges = content_changes, - }) - end - state.pending_changes = {} - else - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = util.buf_versions[bufnr]; - }; - contentChanges = { full_changes() }, - }) - end + local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() } + client.notify("textDocument/didChange", { + textDocument = { + uri = uri, + version = util.buf_versions[bufnr], + }, + contentChanges = changes, + }) + buf_state.pending_changes = {} + end + if debounce == 0 then + buf_state.pending_change() + else + local timer = vim.loop.new_timer() + buf_state.timer = timer + -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines + timer:start(debounce, 0, vim.schedule_wrap(buf_state.pending_change)) end - state.timer = vim.loop.new_timer() - -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines - state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change)) end end - function changetracking._reset_timer(state) - if state.timer then - state.timer:stop() - state.timer:close() - state.timer = nil + function changetracking._reset_timer(buf_state) + if buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil end end --- Flushes any outstanding change notification. ---@private - function changetracking.flush(client) + function changetracking.flush(client, bufnr) local state = state_by_client[client.id] - if state then - changetracking._reset_timer(state) - if state.pending_change then - state.pending_change() + if not state then + return + end + if bufnr then + local buf_state = state.buffers[bufnr] or {} + changetracking._reset_timer(buf_state) + if buf_state.pending_change then + buf_state.pending_change() + end + else + for _, buf_state in pairs(state.buffers) do + changetracking._reset_timer(buf_state) + if buf_state.pending_change then + buf_state.pending_change() + end end end end @@ -988,7 +988,7 @@ function lsp.start_client(config) or error(string.format("not found: %q request handler for client %q.", method, client.name)) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state - changetracking.flush(client) + changetracking.flush(client, bufnr) bufnr = resolve_bufnr(bufnr) local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) local success, request_id = rpc.request(method, params, function(err, result) @@ -1045,14 +1045,16 @@ function lsp.start_client(config) ---@private --- Sends a notification to an LSP server. --- - ---@param method (string) LSP method name. - ---@param params (optional, table) LSP request params. - ---@param bufnr (number) Buffer handle, or 0 for current. + ---@param method string LSP method name. + ---@param params table|nil LSP request params. ---@returns {status} (bool) true if the notification was successful. ---If it is false, then it will always be false ---(the client has shutdown). - function client.notify(...) - return rpc.notify(...) + function client.notify(method, params) + if method ~= 'textDocument/didChange' then + changetracking.flush(client) + end + return rpc.notify(method, params) end ---@private diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 543fdb0237..c9b73e4b70 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -184,7 +184,7 @@ function M.formatting_sync(options, timeout_ms) local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr) if result and result.result then - util.apply_text_edits(result.result, bufnr) + util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then vim.notify('vim.lsp.buf.formatting_sync: ' .. err, vim.log.levels.WARN) end @@ -228,7 +228,7 @@ function M.formatting_seq_sync(options, timeout_ms, order) local params = util.make_formatting_params(options) local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf()) if result and result.result then - util.apply_text_edits(result.result, bufnr) + util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN) end @@ -503,7 +503,7 @@ local function on_code_action_results(results, ctx) ---@private local function apply_action(action, client) if action.edit then - util.apply_workspace_edit(action.edit) + util.apply_workspace_edit(action.edit, client.offset_encoding) end if action.command then local command = type(action.command) == 'table' and action.command or action @@ -627,14 +627,19 @@ end --- Executes an LSP server command. --- ----@param command A valid `ExecuteCommandParams` object +---@param command_params table A valid `ExecuteCommandParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand -function M.execute_command(command) +function M.execute_command(command_params) validate { - command = { command.command, 's' }, - arguments = { command.arguments, 't', true } + command = { command_params.command, 's' }, + arguments = { command_params.arguments, 't', true } } - request('workspace/executeCommand', command) + command_params = { + command=command_params.command, + arguments=command_params.arguments, + workDoneToken=command_params.workDoneToken, + } + request('workspace/executeCommand', command_params ) end return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index a48302cc4b..c0843e1577 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -111,13 +111,15 @@ M['client/registerCapability'] = function(_, _, ctx) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -M['workspace/applyEdit'] = function(_, workspace_edit) +M['workspace/applyEdit'] = function(_, workspace_edit, ctx) if not workspace_edit then return end -- TODO(ashkan) Do something more with label? + local client_id = ctx.client_id + local client = vim.lsp.get_client_by_id(client_id) if workspace_edit.label then print("Workspace edit", workspace_edit.label) end - local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit) + local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding) return { applied = status; failureReason = result; @@ -159,6 +161,30 @@ M['textDocument/codeLens'] = function(...) end +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references +M['textDocument/references'] = function(_, result, ctx, config) + if not result or vim.tbl_isempty(result) then + vim.notify('No references found') + else + config = config or {} + if config.loclist then + vim.fn.setloclist(0, {}, ' ', { + title = 'References'; + items = util.locations_to_items(result, ctx.offset_encoding); + context = ctx; + }) + api.nvim_command("lopen") + else + vim.fn.setqflist({}, ' ', { + title = 'References'; + items = util.locations_to_items(result, ctx.offset_encoding); + context = ctx; + }) + api.nvim_command("botright copen") + end + end +end + ---@private --- Return a function that converts LSP responses to list items and opens the list @@ -169,23 +195,26 @@ end --- loclist: (boolean) use the location list (default is to use the quickfix list) --- ---@param map_result function `((resp, bufnr) -> list)` to convert the response ----@param entity name of the resource used in a `not found` error message -local function response_to_list(map_result, entity) - return function(_,result, ctx, config) +---@param entity string name of the resource used in a `not found` error message +---@param title_fn function Function to call to generate list title +local function response_to_list(map_result, entity, title_fn) + return function(_, result, ctx, config) if not result or vim.tbl_isempty(result) then vim.notify('No ' .. entity .. ' found') else config = config or {} if config.loclist then vim.fn.setloclist(0, {}, ' ', { - title = 'Language Server'; + title = title_fn(ctx); items = map_result(result, ctx.bufnr); + context = ctx; }) api.nvim_command("lopen") else vim.fn.setqflist({}, ' ', { - title = 'Language Server'; + title = title_fn(ctx); items = map_result(result, ctx.bufnr); + context = ctx; }) api.nvim_command("botright copen") end @@ -194,31 +223,36 @@ local function response_to_list(map_result, entity) end ---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references -M['textDocument/references'] = response_to_list(util.locations_to_items, 'references') - --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol -M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols') +M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols', function(ctx) + local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ":.") + return string.format('Symbols in %s', fname) +end) --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol -M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols') +M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx) + return string.format("Symbols matching '%s'", ctx.params.query) +end) --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename -M['textDocument/rename'] = function(_, result, _) +M['textDocument/rename'] = function(_, result, ctx, _) if not result then return end - util.apply_workspace_edit(result) + local client = vim.lsp.get_client_by_id(ctx.client_id) + util.apply_workspace_edit(result, client.offset_encoding) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting M['textDocument/rangeFormatting'] = function(_, result, ctx, _) if not result then return end - util.apply_text_edits(result, ctx.bufnr) + local client = vim.lsp.get_client_by_id(ctx.client_id) + util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting M['textDocument/formatting'] = function(_, result, ctx, _) if not result then return end - util.apply_text_edits(result, ctx.bufnr) + local client = vim.lsp.get_client_by_id(ctx.client_id) + util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -277,19 +311,23 @@ local function location_handler(_, result, ctx, _) local _ = log.info() and log.info(ctx.method, 'No location found') return nil end + local client = vim.lsp.get_client_by_id(ctx.client_id) -- textDocument/definition can return Location or Location[] -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition if vim.tbl_islist(result) then - util.jump_to_location(result[1]) + util.jump_to_location(result[1], client.offset_encoding) if #result > 1 then - vim.fn.setqflist({}, ' ', {title = 'LSP locations', items = util.locations_to_items(result)}) + vim.fn.setqflist({}, ' ', { + title = 'LSP locations', + items = util.locations_to_items(result, client.offset_encoding) + }) api.nvim_command("copen") end else - util.jump_to_location(result) + util.jump_to_location(result, client.offset_encoding) end end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 0af4fcb036..8be1683d86 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -193,6 +193,11 @@ end local function get_lines(bufnr, rows) rows = type(rows) == "table" and rows or { rows } + -- This is needed for bufload and bufloaded + if bufnr == 0 then + bufnr = vim.api.nvim_get_current_buf() + end + ---@private local function buf_lines() local lines = {} @@ -272,7 +277,7 @@ end ---@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param offset_encoding string utf-8|utf-16|utf-32 --- 1-indexed local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -281,7 +286,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- When on the first character, we can ignore the difference between byte and -- character if col > 0 then - local line = get_line(bufnr, position.line) + local line = get_line(bufnr, position.line) or '' local ok, result ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) if ok then @@ -355,15 +360,14 @@ end --- Applies a list of text edits to a buffer. ---@param text_edits table list of `TextEdit` objects ---@param bufnr number Buffer id ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to encoding of first client of `bufnr` +---@param offset_encoding string utf-8|utf-16|utf-32 defaults to encoding of first client of `bufnr` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit function M.apply_text_edits(text_edits, bufnr, offset_encoding) validate { text_edits = { text_edits, 't', false }; bufnr = { bufnr, 'number', false }; - offset_encoding = { offset_encoding, 'string', true }; + offset_encoding = { offset_encoding, 'string', false }; } - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -397,25 +401,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end) - -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. - local has_eol_text_edit = false - local max = vim.api.nvim_buf_line_count(bufnr) - local len = _str_utfindex_enc(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '', nil, offset_encoding) - text_edits = vim.tbl_map(function(text_edit) - if max <= text_edit.range.start.line then - text_edit.range.start.line = max - 1 - text_edit.range.start.character = len - text_edit.newText = '\n' .. text_edit.newText - has_eol_text_edit = true - end - if max <= text_edit.range['end'].line then - text_edit.range['end'].line = max - 1 - text_edit.range['end'].character = len - has_eol_text_edit = true - end - return text_edit - end, text_edits) - -- Some LSP servers are depending on the VSCode behavior. -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it. local is_current_buf = vim.api.nvim_get_current_buf() == bufnr @@ -435,16 +420,38 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Apply text edits. local is_cursor_fixed = false + local has_eol_text_edit = false for _, text_edit in ipairs(text_edits) do + -- Normalize line ending + text_edit.newText, _ = string.gsub(text_edit.newText, '\r\n?', '\n') + + -- Convert from LSP style ranges to Neovim style ranges. local e = { start_row = text_edit.range.start.line, - start_col = get_line_byte_from_position(bufnr, text_edit.range.start), + start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding), end_row = text_edit.range['end'].line, - end_col = get_line_byte_from_position(bufnr, text_edit.range['end']), + end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding), text = vim.split(text_edit.newText, '\n', true), } + + -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. + local max = vim.api.nvim_buf_line_count(bufnr) + if max <= e.start_row or max <= e.end_row then + local len = #(get_line(bufnr, max - 1) or '') + if max <= e.start_row then + e.start_row = max - 1 + e.start_col = len + table.insert(e.text, 1, '') + end + if max <= e.end_row then + e.end_row = max - 1 + e.end_col = len + end + has_eol_text_edit = true + end vim.api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) + -- Fix cursor position. local row_count = (e.end_row - e.start_row) + 1 if e.end_row < cursor.row then cursor.row = cursor.row + (#e.text - row_count) @@ -459,10 +466,13 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end + local max = vim.api.nvim_buf_line_count(bufnr) + + -- Apply fixed cursor position. if is_cursor_fixed then local is_valid_cursor = true - is_valid_cursor = is_valid_cursor and cursor.row < vim.api.nvim_buf_line_count(bufnr) - is_valid_cursor = is_valid_cursor and cursor.col <= #(vim.api.nvim_buf_get_lines(bufnr, cursor.row, cursor.row + 1, false)[1] or '') + is_valid_cursor = is_valid_cursor and cursor.row < max + is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, max - 1) or '') if is_valid_cursor then vim.api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col }) end @@ -471,7 +481,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Remove final line if needed local fix_eol = has_eol_text_edit fix_eol = fix_eol and api.nvim_buf_get_option(bufnr, 'fixeol') - fix_eol = fix_eol and (vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') == '' + fix_eol = fix_eol and get_line(bufnr, max - 1) == '' if fix_eol then vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {}) end @@ -509,9 +519,12 @@ end ---@param text_document_edit table: a `TextDocumentEdit` object ---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit, index) +function M.apply_text_document_edit(text_document_edit, index, offset_encoding) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) + if offset_encoding == nil then + vim.notify_once("apply_text_document_edit must be called with valid offset encoding", vim.log.levels.WARN) + end -- For lists of text document edits, -- do not check the version after the first edit. @@ -530,7 +543,7 @@ function M.apply_text_document_edit(text_document_edit, index) return end - M.apply_text_edits(text_document_edit.edits, bufnr) + M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding) end --- Parses snippets in a completion entry. @@ -727,9 +740,13 @@ end --- Applies a `WorkspaceEdit`. --- ----@param workspace_edit (table) `WorkspaceEdit` +---@param workspace_edit table `WorkspaceEdit` +---@param offset_encoding string utf-8|utf-16|utf-32 (required) --see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -function M.apply_workspace_edit(workspace_edit) +function M.apply_workspace_edit(workspace_edit, offset_encoding) + if offset_encoding == nil then + vim.notify_once("apply_workspace_edit must be called with valid offset encoding", vim.log.levels.WARN) + end if workspace_edit.documentChanges then for idx, change in ipairs(workspace_edit.documentChanges) do if change.kind == "rename" then @@ -745,7 +762,7 @@ function M.apply_workspace_edit(workspace_edit) elseif change.kind then error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.apply_text_document_edit(change, idx) + M.apply_text_document_edit(change, idx, offset_encoding) end end return @@ -758,7 +775,7 @@ function M.apply_workspace_edit(workspace_edit) for uri, changes in pairs(all_changes) do local bufnr = vim.uri_to_bufnr(uri) - M.apply_text_edits(changes, bufnr) + M.apply_text_edits(changes, bufnr, offset_encoding) end end @@ -834,7 +851,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers local active_hl local active_signature = signature_help.activeSignature or 0 -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #signature_help.signatures then + -- In 3.15 of the protocol, activeSignature was allowed to be negative + if active_signature >= #signature_help.signatures or active_signature < 0 then active_signature = 0 end local signature = signature_help.signatures[active_signature + 1] @@ -975,12 +993,16 @@ end --- Jumps to a location. --- ----@param location (`Location`|`LocationLink`) +---@param location table (`Location`|`LocationLink`) +---@param offset_encoding string utf-8|utf-16|utf-32 (required) ---@returns `true` if the jump succeeded -function M.jump_to_location(location) +function M.jump_to_location(location, offset_encoding) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return end + if offset_encoding == nil then + vim.notify_once("jump_to_location must be called with valid offset encoding", vim.log.levels.WARN) + end local bufnr = vim.uri_to_bufnr(uri) -- Save position in jumplist vim.cmd "normal! m'" @@ -995,7 +1017,7 @@ function M.jump_to_location(location) api.nvim_buf_set_option(bufnr, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line - local col = get_line_byte_from_position(bufnr, range.start) + local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) api.nvim_win_set_cursor(0, {row + 1, col}) -- Open folds under the cursor vim.cmd("normal! zv") @@ -1504,11 +1526,13 @@ do --[[ References ]] --- ---@param bufnr number Buffer id ---@param references table List of `DocumentHighlight` objects to highlight - ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to `offset_encoding` of first client of `bufnr` + ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32". ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight function M.buf_highlight_references(bufnr, references, offset_encoding) - validate { bufnr = {bufnr, 'n', true} } - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) + validate { + bufnr = {bufnr, 'n', true}, + offset_encoding = { offset_encoding, 'string', false }; + } for _, reference in ipairs(references) do local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"] local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"] @@ -1541,9 +1565,14 @@ end) --- The result can be passed to the {list} argument of |setqflist()| or --- |setloclist()|. --- ----@param locations (table) list of `Location`s or `LocationLink`s +---@param locations table list of `Location`s or `LocationLink`s +---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32 ---@returns (table) list of items -function M.locations_to_items(locations) +function M.locations_to_items(locations, offset_encoding) + if offset_encoding == nil then + vim.notify_once("locations_to_items must be called with valid offset encoding", vim.log.levels.WARN) + end + local items = {} local grouped = setmetatable({}, { __index = function(t, k) @@ -1583,7 +1612,7 @@ function M.locations_to_items(locations) local pos = temp.start local row = pos.line local line = lines[row] or "" - local col = pos.character + local col = M._str_byteindex_enc(line, pos.character, offset_encoding) table.insert(items, { filename = filename, lnum = row + 1, @@ -1767,7 +1796,13 @@ function M._get_offset_encoding(bufnr) local offset_encoding for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do - local this_offset_encoding = client.offset_encoding or "utf-16" + if client.offset_encoding == nil then + vim.notify_once( + string.format("Client (id: %s) offset_encoding is nil. Do not unset offset_encoding.", client.id), + vim.log.levels.ERROR + ) + end + local this_offset_encoding = client.offset_encoding if not offset_encoding then offset_encoding = this_offset_encoding elseif offset_encoding ~= this_offset_encoding then @@ -1814,7 +1849,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) end_pos = {end_pos, 't', true}; offset_encoding = {offset_encoding, 's', true}; } - bufnr = bufnr or 0 + bufnr = bufnr or vim.api.nvim_get_current_buf() offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) local A = list_extend({}, start_pos or api.nvim_buf_get_mark(bufnr, '<')) local B = list_extend({}, end_pos or api.nvim_buf_get_mark(bufnr, '>')) @@ -1896,7 +1931,9 @@ end ---@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col, offset_encoding) local line = get_line(buf, row) - offset_encoding = offset_encoding or M._get_offset_encoding(buf) + if offset_encoding == nil then + vim.notify_once("character_offset must be called with valid offset encoding", vim.log.levels.WARN) + end -- If the col is past the EOL, use the line length. if col > #line then return _str_utfindex_enc(line, nil, offset_encoding) diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index ebed502c92..b3036ea679 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -138,6 +138,13 @@ function M.get_query(lang, query_name) end end +local query_cache = setmetatable({}, { + __index = function(tbl, key) + rawset(tbl, key, {}) + return rawget(tbl, key) + end +}) + --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). --- @@ -151,17 +158,23 @@ end --- -` info.captures` also points to `captures`. --- - `info.patterns` contains information about predicates. --- ----@param lang The language ----@param query A string containing the query (s-expr syntax) +---@param lang string The language +---@param query string A string containing the query (s-expr syntax) --- ---@returns The query function M.parse_query(lang, query) language.require_language(lang) - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, query) - self.info = self.query:inspect() - self.captures = self.info.captures - return self + local cached = query_cache[lang][query] + if cached then + return cached + else + local self = setmetatable({}, Query) + self.query = vim._ts_parse_query(lang, query) + self.info = self.query:inspect() + self.captures = self.info.captures + query_cache[lang][query] = self + return self + end end --- Gets the text corresponding to a given node diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index f9978a6b00..c881133b72 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -153,7 +153,7 @@ func s:StartDebug_internal(dict) if &columns < g:termdebug_wide let s:save_columns = &columns let &columns = g:termdebug_wide - " If we make the Vim window wider, use the whole left halve for the debug + " If we make the Vim window wider, use the whole left half for the debug " windows. let s:allleft = 1 endif @@ -1051,10 +1051,10 @@ func s:GetEvaluationExpression(range, arg) return expr endfunc -" clean up expression that may got in because of range +" clean up expression that may get in because of range " (newlines and surrounding whitespace) " As it can also be specified via ex-command for assignments this function -" may not change the "content" parts (like replacing contained spaces +" may not change the "content" parts (like replacing contained spaces) func s:CleanupExpr(expr) " replace all embedded newlines/tabs/... let expr = substitute(a:expr, '\_s', ' ', 'g') @@ -1084,7 +1084,7 @@ func s:HandleEvaluate(msg) \ ->substitute('.*value="\(.*\)"', '\1', '') \ ->substitute('\\"', '"', 'g') \ ->substitute('\\\\', '\\', 'g') - "\ multi-byte characters arrive in octal form, replace everthing but NULL values + "\ multi-byte characters arrive in octal form, replace everything but NULL values \ ->substitute('\\000', s:NullRepl, 'g') \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') "\ Note: GDB docs also mention hex encodings - the translations below work diff --git a/runtime/scripts.vim b/runtime/scripts.vim index e41405a6c5..dd47f65ba0 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -406,6 +406,12 @@ else elseif s:line1 =~# '^#.*by RouterOS.*$' set ft=routeros + " Sed scripts + " #ncomment is allowed but most likely a false positive so require a space + " before any trailing comment text + elseif s:line1 =~# '^#n\%($\|\s\)' + set ft=sed + " CVS diff else let s:lnum = 1 diff --git a/runtime/syntax/git.vim b/runtime/syntax/git.vim index a8467edd43..bf013ce195 100644 --- a/runtime/syntax/git.vim +++ b/runtime/syntax/git.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: generic git output " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish @@ -12,12 +12,28 @@ syn sync minlines=50 syn include @gitDiff syntax/diff.vim -syn region gitHead start=/\%^/ end=/^$/ -syn region gitHead start=/\%(^commit\%( \x\{40\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^$/ - -" For git reflog and git show ...^{tree}, avoid sync issues -syn match gitHead /^\d\{6\} \%(\w\{4} \)\=\x\{40\}\%( [0-3]\)\=\t.*/ -syn match gitHead /^\x\{40\} \x\{40}\t.*/ +syn region gitHead start=/\%^\%(tag \|tree \|object \)\@=/ end=/^$/ contains=@NoSpell +syn region gitHead start=/\%(^commit\%( \x\{4,\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^$/ contains=@NoSpell +" git log --oneline +" minimize false positives by verifying contents of buffer +if getline(1) =~# '^\x\{7,\} ' && getline('$') =~# '^\x\{7,\} ' + syn match gitHashAbbrev /^\x\{7,\} \@=/ contains=@NoSpell +elseif getline(1) =~# '^[|\/\\_ ]\{-\}\*[|\/\\_ ]\{-\} \x\{7,\} ' + syn match gitHashAbbrev /^[|\/\\_ ]\{-\}\*[|\/\\_ ]\{-\} \zs\x\{7,\} \@=/ contains=@NoSpell +endif +" git log --graph +syn region gitGraph start=/\%(^[|\/\\_ ]*\*[|\/\\_ ]\{-\} commit\%( \x\{4,\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^\%([|\/\\_ ]*$\)\@=/ contains=@NoSpell +" git blame --porcelain +syn region gitHead start=/\%(^\x\{40,\} \d\+ \d\+\%( \d\+\)\=$\)\@=/ end=/^\t\@=/ contains=@NoSpell +" git ls-tree +syn match gitMode /^\d\{6\}\%( \%(blob\|tree\) \x\{4,\}\t\)\@=/ nextgroup=gitType skipwhite contains=@NoSpell +" git ls-files --stage +syn match gitMode /^\d\{6\}\%( \x\{4,\} [0-3]\t\)\@=/ nextgroup=gitHashStage skipwhite contains=@NoSpell +" .git/HEAD, .git/refs/ +syn match gitKeyword /\%^ref: \@=/ nextgroup=gitReference skipwhite contains=@NoSpell +syn match gitHash /\%^\x\{40,}\%$/ skipwhite contains=@NoSpell +" .git/logs/ +syn match gitReflog /^\x\{40,\} \x\{40,\} .\{-\}\d\+\s-\d\{4\}\t.*/ skipwhite contains=@NoSpell,gitReflogOld syn region gitDiff start=/^\%(diff --git \)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff fold syn region gitDiff start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) \|$\)\@=/ contains=@gitDiff @@ -25,35 +41,47 @@ syn region gitDiff start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) syn region gitDiffMerge start=/^\%(diff --\%(cc\|combined\) \)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff syn region gitDiffMerge start=/^\%(@@@@* -\)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff syn match gitDiffAdded "^ \++.*" contained containedin=gitDiffMerge -syn match gitDiffAdded "{+.*+}" contained containedin=gitDiff +syn match gitDiffAdded "{+[^}]*+}" contained containedin=gitDiff syn match gitDiffRemoved "^ \+-.*" contained containedin=gitDiffMerge -syn match gitDiffRemoved "\[-.*-\]" contained containedin=gitDiff +syn match gitDiffRemoved "\[-[^]]*-\]" contained containedin=gitDiff + +syn match gitKeyword /^commit \@=/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitKeyword /^\%(object\|tree\|parent\|encoding\|gpgsig\%(-\w\+\)\=\|previous\) \@=/ contained containedin=gitHead nextgroup=gitHash skipwhite contains=@NoSpell +syn match gitKeyword /^Merge:/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^\%(author\|committer\|tagger\) \@=/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitIdentityHeader /^\%(Author\|Commit\|Tagger\):/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitDateHeader /^\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitHead nextgroup=gitDate skipwhite contains=@NoSpell -syn match gitKeyword /^\%(object\|type\|tag\|commit\|tree\|parent\|encoding\)\>/ contained containedin=gitHead nextgroup=gitHash,gitType skipwhite -syn match gitKeyword /^\%(tag\>\|ref:\)/ contained containedin=gitHead nextgroup=gitReference skipwhite -syn match gitKeyword /^Merge:/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite -syn match gitMode /^\d\{6\}\>/ contained containedin=gitHead nextgroup=gitType,gitHash skipwhite -syn match gitIdentityKeyword /^\%(author\|committer\|tagger\)\>/ contained containedin=gitHead nextgroup=gitIdentity skipwhite -syn match gitIdentityHeader /^\%(Author\|Commit\|Tagger\):/ contained containedin=gitHead nextgroup=gitIdentity skipwhite -syn match gitDateHeader /^\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitHead nextgroup=gitDate skipwhite +syn match gitKeyword /^[*|\/\\_ ]\+\zscommit \@=/ contained containedin=gitGraph nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitKeyword /^[|\/\\_ ]\+\zs\%(object\|tree\|parent\|encoding\|gpgsig\%(-\w\+\)\=\|previous\) \@=/ contained containedin=gitGraph nextgroup=gitHash skipwhite contains=@NoSpell +syn match gitKeyword /^[|\/\\_ ]\+\zsMerge:/ contained containedin=gitGraph nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^[|\/\\_ ]\+\zs\%(author\|committer\|tagger\) \@=/ contained containedin=gitGraph nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitIdentityHeader /^[|\/\\_ ]\+\zs\%(Author\|Commit\|Tagger\):/ contained containedin=gitGraph nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitDateHeader /^[|\/\\_ ]\+\zs\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitGraph nextgroup=gitDate skipwhite contains=@NoSpell -syn match gitReflogHeader /^Reflog:/ contained containedin=gitHead nextgroup=gitReflogMiddle skipwhite -syn match gitReflogHeader /^Reflog message:/ contained containedin=gitHead skipwhite -syn match gitReflogMiddle /\S\+@{\d\+} (/he=e-2 nextgroup=gitIdentity +syn match gitKeyword /^type \@=/ contained containedin=gitHead nextgroup=gitType skipwhite contains=@NoSpell +syn match gitKeyword /^\%(summary\|boundary\|filename\|\%(author\|committer\)-\%(time\|tz\)\) \@=/ contained containedin=gitHead skipwhite contains=@NoSpell +syn match gitKeyword /^tag \@=/ contained containedin=gitHead nextgroup=gitReference skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^\%(author\|committer\)-mail \@=/ contained containedin=gitHead nextgroup=gitEmail skipwhite contains=@NoSpell +syn match gitReflogHeader /^Reflog:/ contained containedin=gitHead nextgroup=gitReflogMiddle skipwhite contains=@NoSpell +syn match gitReflogHeader /^Reflog message:/ contained containedin=gitHead skipwhite contains=@NoSpell +syn match gitReflogMiddle /\S\+@{\d\+} (/he=e-2 nextgroup=gitIdentity contains=@NoSpell -syn match gitDate /\<\u\l\l \u\l\l \d\=\d \d\d:\d\d:\d\d \d\d\d\d [+-]\d\d\d\d/ contained -syn match gitDate /-\=\d\+ [+-]\d\d\d\d\>/ contained -syn match gitDate /\<\d\+ \l\+ ago\>/ contained -syn match gitType /\<\%(tag\|commit\|tree\|blob\)\>/ contained nextgroup=gitHash skipwhite -syn match gitStage /\<\d\t\@=/ contained -syn match gitReference /\S\+\S\@!/ contained -syn match gitHash /\<\x\{40\}\>/ contained nextgroup=gitIdentity,gitStage,gitHash skipwhite -syn match gitHash /^\<\x\{40\}\>/ containedin=gitHead contained nextgroup=gitHash skipwhite -syn match gitHashAbbrev /\<\x\{4,40\}\>/ contained nextgroup=gitHashAbbrev skipwhite -syn match gitHashAbbrev /\<\x\{4,39\}\.\.\./he=e-3 contained nextgroup=gitHashAbbrev skipwhite +syn match gitIdentity /\S.\{-\} <[^>]*>/ contained nextgroup=gitDate skipwhite contains=@NoSpell +syn region gitEmail matchgroup=gitEmailDelimiter start=/</ end=/>/ keepend oneline contained containedin=gitIdentity contains=@NoSpell +syn match gitDate /\<\u\l\l \u\l\l \d\=\d \d\d:\d\d:\d\d \d\d\d\d [+-]\d\d\d\d/ contained contains=@NoSpell +syn match gitDate /-\=\d\+ [+-]\d\d\d\d\>/ contained contains=@NoSpell +syn match gitDate /\<\d\+ \l\+ ago\>/ contained contains=@NoSpell +syn match gitType /\<\%(tag\|commit\|tree\|blob\)\>/ contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitReference /\S\+\S\@!/ contained contains=@NoSpell +syn match gitHash /\<\x\{40,\}\>/ contained nextgroup=gitIdentity,gitHash skipwhite contains=@NoSpell +syn match gitReflogOld /^\x\{40,\} \@=/ contained nextgroup=gitReflogNew skipwhite contains=@NoSpell +syn match gitReflogNew /\<\x\{40,\} \@=/ contained nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitHashAbbrev /\<\x\{4,\}\>/ contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitHashAbbrev /\<\x\{4,39\}\.\.\./he=e-3 contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitHashStage /\<\x\{4,\}\>/ contained nextgroup=gitStage skipwhite contains=@NoSpell +syn match gitStage /\<\d\t\@=/ contained contains=@NoSpell -syn match gitIdentity /\S.\{-\} <[^>]*>/ contained nextgroup=gitDate skipwhite -syn region gitEmail matchgroup=gitEmailDelimiter start=/</ end=/>/ keepend oneline contained containedin=gitIdentity syn match gitNotesHeader /^Notes:\ze\n / @@ -68,7 +96,10 @@ hi def link gitEmailDelimiter Delimiter hi def link gitEmail Special hi def link gitDate Number hi def link gitMode Number +hi def link gitHashStage gitHash hi def link gitHashAbbrev gitHash +hi def link gitReflogOld gitHash +hi def link gitReflogNew gitHash hi def link gitHash Identifier hi def link gitReflogMiddle gitReference hi def link gitReference Function diff --git a/runtime/syntax/gitcommit.vim b/runtime/syntax/gitcommit.vim index 63b1ce920f..42c8d4414f 100644 --- a/runtime/syntax/gitcommit.vim +++ b/runtime/syntax/gitcommit.vim @@ -2,71 +2,87 @@ " Language: git commit file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: *.git/COMMIT_EDITMSG -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish endif +scriptencoding utf-8 + syn case match syn sync minlines=50 +syn sync linebreaks=1 if has("spell") syn spell toplevel endif syn include @gitcommitDiff syntax/diff.vim -syn region gitcommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|#\)\@=/ fold contains=@gitcommitDiff +syn region gitcommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@gitcommitDiff syn match gitcommitSummary "^.*\%<51v." contained containedin=gitcommitFirstLine nextgroup=gitcommitOverflow contains=@Spell syn match gitcommitOverflow ".*" contained contains=@Spell -syn match gitcommitBlank "^[^#].*" contained contains=@Spell +syn match gitcommitBlank "^.\+" contained contains=@Spell +syn match gitcommitFirstLine "\%^.*" nextgroup=gitcommitBlank,gitcommitComment skipnl + +let s:scissors = 0 +let s:l = search('^[#;@!$%^&|:] -\{24,\} >8 -\{24,\}$', 'cnW', '', 100) +if s:l == 0 + let s:l = line('$') +elseif getline(s:l)[0] !=# getline(s:l - 1)[0] + let s:scissors = 1 +endif +let s:comment = escape((matchstr(getline(s:l), '^[#;@!$%^&|:]\S\@!') . '#')[0], '^$.*[]~\"/') -if get(g:, "gitcommit_cleanup") is# "scissors" - syn match gitcommitFirstLine "\%^.*" nextgroup=gitcommitBlank skipnl - syn region gitcommitComment start=/^# -\+ >8 -\+$/ end=/\%$/ contains=gitcommitDiff +if s:scissors + let s:comment .= ' -\{24,\} >8 -\{24,\}$' + exe 'syn region gitcommitComment start="^' . s:comment . '" end="\%$" contains=gitcommitDiff' else - syn match gitcommitFirstLine "\%^[^#].*" nextgroup=gitcommitBlank skipnl - syn match gitcommitComment "^#.*" + exe 'syn match gitcommitComment "^' . s:comment . '.*"' endif +exe 'syn match gitcommitTrailers "\n\@<=\n\%([[:alnum:]-]\+\s*:.*\|(cherry picked from commit .*\)\%(\n\s.*\|\n[[:alnum:]-]\+\s*:.*\|\n(cherry picked from commit .*\)*\%(\n\n*\%(' . s:comment . '\)\|\n*\%$\)\@="' -syn match gitcommitHead "^\%(# .*\n\)\+#$" contained transparent -syn match gitcommitOnBranch "\%(^# \)\@<=On branch" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite -syn match gitcommitOnBranch "\%(^# \)\@<=Your branch .\{-\} '" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite +unlet s:l s:comment s:scissors + +syn match gitcommitTrailerToken "^[[:alnum:]-]\+\s*:" contained containedin=gitcommitTrailers + +syn match gitcommitHash "\<\x\{40,}\>" contains=@NoSpell display +syn match gitcommitOnBranch "\%(^. \)\@<=On branch" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite +syn match gitcommitOnBranch "\%(^. \)\@<=Your branch .\{-\} '" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite syn match gitcommitBranch "[^ ']\+" contained -syn match gitcommitNoBranch "\%(^# \)\@<=Not currently on any branch." contained containedin=gitcommitComment -syn match gitcommitHeader "\%(^# \)\@<=.*:$" contained containedin=gitcommitComment -syn region gitcommitAuthor matchgroup=gitCommitHeader start=/\%(^# \)\@<=\%(Author\|Committer\):/ end=/$/ keepend oneline contained containedin=gitcommitComment transparent -syn match gitcommitNoChanges "\%(^# \)\@<=No changes$" contained containedin=gitcommitComment +syn match gitcommitNoBranch "\%(^. \)\@<=Not currently on any branch." contained containedin=gitcommitComment +syn match gitcommitHeader "\%(^. \)\@<=\S.*[::]\%(\n^$\)\@!$" contained containedin=gitcommitComment +syn region gitcommitAuthor matchgroup=gitCommitHeader start=/\%(^. \)\@<=\%(Author\|Committer\|Date\):/ end=/$/ keepend oneline contained containedin=gitcommitComment transparent +syn match gitcommitHeader "\%(^. \)\@<=commit\%( \x\{40,\}$\)\@=" contained containedin=gitcommitComment nextgroup=gitcommitHash skipwhite +syn match gitcommitNoChanges "\%(^. \)\@<=No changes$" contained containedin=gitcommitComment -syn region gitcommitUntracked start=/^# Untracked files:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitUntrackedFile fold -syn match gitcommitUntrackedFile "\t\@<=.*" contained +syn match gitcommitType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained containedin=gitcommitComment nextgroup=gitcommitFile skipwhite +syn match gitcommitFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitArrow +syn match gitcommitArrow " -> " contained nextgroup=gitcommitFile +syn match gitcommitUntrackedFile "\%(^.\t\)\@<=[^::/]*\%(/.*\)\=$" contained containedin=gitcommitComment -syn region gitcommitDiscarded start=/^# Change\%(s not staged for commit\|d but not updated\):/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitDiscardedType fold -syn region gitcommitSelected start=/^# Changes to be committed:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitSelectedType fold -syn region gitcommitUnmerged start=/^# Unmerged paths:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitUnmergedType fold +syn region gitcommitUntracked start=/^\z(.\) Untracked files:$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitDiscarded start=/^\z(.\) Change\%(s not staged for commit\|d but not updated\):$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader,gitcommitDiscardedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitSelected start=/^\z(.\) Changes to be committed:$/ end=/^\z1$\|^\z1\@!/ contains=gitcommitHeader,gitcommitSelectedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitUnmerged start=/^\z(.\) Unmerged paths:$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader,gitcommitUnmergedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn match gitcommitUntrackedFile "\%(^.\t\)\@<=.*" contained containedin=gitcommitUntracked -syn match gitcommitDiscardedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitDiscardedFile skipwhite -syn match gitcommitSelectedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitSelectedFile skipwhite -syn match gitcommitUnmergedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitUnmergedFile skipwhite -syn match gitcommitDiscardedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitDiscardedArrow -syn match gitcommitSelectedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow -syn match gitcommitUnmergedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow +syn match gitcommitDiscardedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitDiscardedFile skipwhite +syn match gitcommitSelectedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitSelectedFile skipwhite +syn match gitcommitUnmergedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitUnmergedFile skipwhite +syn match gitcommitDiscardedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitDiscardedArrow +syn match gitcommitSelectedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow +syn match gitcommitUnmergedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitUnmergedArrow syn match gitcommitDiscardedArrow " -> " contained nextgroup=gitcommitDiscardedFile syn match gitcommitSelectedArrow " -> " contained nextgroup=gitcommitSelectedFile -syn match gitcommitUnmergedArrow " -> " contained nextgroup=gitcommitSelectedFile - -syn match gitcommitWarning "\%^[^#].*: needs merge$" nextgroup=gitcommitWarning skipnl -syn match gitcommitWarning "^[^#].*: needs merge$" nextgroup=gitcommitWarning skipnl contained -syn match gitcommitWarning "^\%(no changes added to commit\|nothing \%(added \)\=to commit\)\>.*\%$" +syn match gitcommitUnmergedArrow " -> " contained nextgroup=gitcommitUnmergedFile hi def link gitcommitSummary Keyword +hi def link gitcommitTrailerToken Label hi def link gitcommitComment Comment -hi def link gitcommitUntracked gitcommitComment -hi def link gitcommitDiscarded gitcommitComment -hi def link gitcommitSelected gitcommitComment -hi def link gitcommitUnmerged gitcommitComment +hi def link gitcommitHash Identifier hi def link gitcommitOnBranch Comment hi def link gitcommitBranch Special hi def link gitcommitNoBranch gitCommitBranch diff --git a/runtime/syntax/gitrebase.vim b/runtime/syntax/gitrebase.vim index bc6f34d1a7..13f157b005 100644 --- a/runtime/syntax/gitrebase.vim +++ b/runtime/syntax/gitrebase.vim @@ -2,7 +2,7 @@ " Language: git rebase --interactive " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: git-rebase-todo -" Last Change: 2019 Dec 06 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish @@ -10,8 +10,10 @@ endif syn case match -syn match gitrebaseHash "\v<\x{7,}>" contained -syn match gitrebaseCommit "\v<\x{7,}>" nextgroup=gitrebaseSummary skipwhite +let s:c = escape((matchstr(getline('$'), '^[#;@!$%^&|:]\S\@!') . '#')[0], '^$.*[]~\"/') + +syn match gitrebaseHash "\v<\x{7,}>" contained contains=@NoSpell +syn match gitrebaseCommit "\v<\x{7,}>" nextgroup=gitrebaseSummary skipwhite contains=@NoSpell syn match gitrebasePick "\v^p%(ick)=>" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseReword "\v^r%(eword)=>" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseEdit "\v^e%(dit)=>" nextgroup=gitrebaseCommit skipwhite @@ -26,12 +28,15 @@ syn match gitrebaseLabel "\v^l(abel)=>" nextgroup=gitrebaseName skipwhite syn match gitrebaseReset "\v^(t|reset)=>" nextgroup=gitrebaseName skipwhite syn match gitrebaseSummary ".*" contains=gitrebaseHash contained syn match gitrebaseCommand ".*" contained -syn match gitrebaseComment "^\s*#.*" contains=gitrebaseHash +exe 'syn match gitrebaseComment " \@<=' . s:c . ' empty$" containedin=gitrebaseSummary contained' +exe 'syn match gitrebaseComment "^\s*' . s:c . '.*" contains=gitrebaseHash' syn match gitrebaseSquashError "\v%^%(s%(quash)=>|f%(ixup)=>)" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseMergeOption "\v-[Cc]>" nextgroup=gitrebaseMergeCommit skipwhite contained syn match gitrebaseMergeCommit "\v<\x{7,}>" nextgroup=gitrebaseName skipwhite contained syn match gitrebaseName "\v[^[:space:].*?i:^~/-]\S+" nextgroup=gitrebaseMergeComment skipwhite contained -syn match gitrebaseMergeComment "#" nextgroup=gitrebaseSummary skipwhite contained +exe 'syn match gitrebaseMergeComment "' . s:c . '" nextgroup=gitrebaseSummary skipwhite contained' + +unlet s:c hi def link gitrebaseCommit gitrebaseHash hi def link gitrebaseHash Identifier diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim index a8b6637140..f9e15d57e5 100644 --- a/runtime/syntax/i3config.vim +++ b/runtime/syntax/i3config.vim @@ -1,8 +1,9 @@ " Vim syntax file " Language: i3 config file -" Maintainer: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Maintainer: Quentin Hibon (github user hiqua) " Version: 0.4 -" Last Change: 2021 Dec 14 +" Last Change: 2022 Jan 04 " References: " http://i3wm.org/docs/userguide.html#configuring diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 36da6c13a9..e370c0d4d4 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -10,6 +10,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" /// Helper structure for vim_to_object typedef struct { @@ -228,6 +231,13 @@ static inline void typval_encode_dict_end(EncodedData *const edata) /// @return The converted value Object vim_to_object(typval_T *obj) { + if (obj->v_type == VAR_FUNC) { + ufunc_T *fp = find_func(obj->vval.v_string); + if (fp->uf_cb == nlua_CFunction_func_call) { + LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); + return LUAREF_OBJ(ref); + } + } EncodedData edata; kvi_init(edata.stack); const int evo_ret = encode_vim_to_object(&edata, obj, @@ -340,6 +350,16 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv->vval.v_dict = dict; break; } + + case kObjectTypeLuaRef: { + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = api_new_luaref(obj.data.luaref); + char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); + tv->v_type = VAR_FUNC; + tv->vval.v_string = vim_strsave(name); + break; + } + default: abort(); } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 38a82343c3..f9603acbda 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1387,6 +1387,11 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int LuaRef luaref = LUA_NOREF; LuaRef compl_luaref = LUA_NOREF; + if (mb_islower(name.data[0])) { + api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); + goto err; + } + if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive"); goto err; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 5eef4350b7..9efe5a27c4 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1082,6 +1082,8 @@ static int insert_handle_key(InsertState *s) map_execute_lua(); check_pum: + // nvim_select_popupmenu_item() can be called from the handling of + // K_EVENT, K_COMMAND, or K_LUA. // TODO(bfredl): Not entirely sure this indirection is necessary // but doing like this ensures using nvim_select_popupmenu_item is // equivalent to selecting the item with a typed key. @@ -3620,7 +3622,7 @@ static bool ins_compl_prep(int c) // Ignore end of Select mode mapping and mouse scroll buttons. if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT - || c == K_COMMAND) { + || c == K_COMMAND || c == K_LUA) { return retval; } @@ -4986,7 +4988,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func) */ static int ins_compl_key2dir(int c) { - if (c == K_EVENT || c == K_COMMAND) { + if (c == K_EVENT || c == K_COMMAND || c == K_LUA) { return pum_want.item < pum_selected_item ? BACKWARD : FORWARD; } if (c == Ctrl_P || c == Ctrl_L @@ -5016,7 +5018,7 @@ static int ins_compl_key2count(int c) { int h; - if (c == K_EVENT || c == K_COMMAND) { + if (c == K_EVENT || c == K_COMMAND || c == K_LUA) { int offset = pum_want.item - pum_selected_item; return abs(offset); } @@ -5050,6 +5052,7 @@ static bool ins_compl_use_match(int c) return false; case K_EVENT: case K_COMMAND: + case K_LUA: return pum_want.active && pum_want.insert; } return true; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 71c34f98ff..3a285cdf90 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4370,7 +4370,7 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) ++i; } len = (int)STRLEN(p); - new_cmdline = xmalloc(STRLEN(program) + i * (len - 2) + 1); + new_cmdline = xmalloc(STRLEN(program) + (size_t)i * (len - 2) + 1); ptr = new_cmdline; while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) { i = (int)(pos - program); @@ -6028,7 +6028,7 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, break; } - case ct_MODS: { + case ct_MODS: result = quote ? 2 : 0; if (buf != NULL) { if (quote) { @@ -6044,7 +6044,6 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, *buf = '"'; } break; - } case ct_REGISTER: result = eap->regname ? 1 : 0; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 8a702ddd60..f9a2533d4e 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -617,6 +617,13 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) semsg(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; } + if (tv->v_type == VAR_FUNC) { + ufunc_T *fp = find_func(tv->vval.v_string); + if (fp->uf_cb == nlua_CFunction_func_call) { + nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); + return true; + } + } if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) { return false; } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 60a000843f..f4067ad02f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -129,6 +129,10 @@ void tslua_init(lua_State *L) build_meta(L, TS_META_QUERY, query_meta); build_meta(L, TS_META_QUERYCURSOR, querycursor_meta); build_meta(L, TS_META_TREECURSOR, treecursor_meta); + +#ifdef NVIM_TS_HAS_SET_ALLOCATOR + ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree); +#endif } int tslua_has_language(lua_State *L) diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 3397296b3a..2a72d1e6a0 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -247,7 +247,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) } else { // need to allocate memory for this block // If the number of pages matches use the bhdr_T from the free list and // allocate the data. - void *p = xmalloc(mfp->mf_page_size * page_count); + void *p = xmalloc((size_t)mfp->mf_page_size * page_count); hp = mf_rem_free(mfp); hp->bh_data = p; } @@ -269,7 +269,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) // Init the data to all zero, to avoid reading uninitialized data. // This also avoids that the passwd file ends up in the swap file! - (void)memset(hp->bh_data, 0, mfp->mf_page_size * page_count); + (void)memset(hp->bh_data, 0, (size_t)mfp->mf_page_size * page_count); return hp; } @@ -528,7 +528,7 @@ bool mf_release_all(void) static bhdr_T *mf_alloc_bhdr(memfile_T *mfp, unsigned page_count) { bhdr_T *hp = xmalloc(sizeof(bhdr_T)); - hp->bh_data = xmalloc(mfp->mf_page_size * page_count); + hp->bh_data = xmalloc((size_t)mfp->mf_page_size * page_count); hp->bh_page_count = page_count; return hp; } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 08521c0dc3..9925971783 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1032,9 +1032,9 @@ void ml_recover(bool checkext) line_count = 0; idx = 0; // start with first index in block 1 error = 0; - buf->b_ml.ml_stack_top = 0; + buf->b_ml.ml_stack_top = 0; // -V1048 buf->b_ml.ml_stack = NULL; - buf->b_ml.ml_stack_size = 0; // no stack yet + buf->b_ml.ml_stack_size = 0; // -V1048 if (curbuf->b_ffname == NULL) { cannot_open = true; @@ -4139,7 +4139,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) || (offset != 0 && offset > size + buf->b_ml.ml_chunksize[curix].mlcs_totalsize - + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { + + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines; size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize; if (offset && ffdos) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 60bf393085..2b5b47c0b3 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4437,11 +4437,7 @@ static void nv_ident(cmdarg_T *cap) // Start insert mode in terminal buffer restart_edit = 'i'; - add_map((char_u *)"<buffer> <esc> <Cmd>call jobstop(&channel)<CR>", TERM_FOCUS, true); - do_cmdline_cmd("autocmd TermClose <buffer> " - " if !v:event.status |" - " exec 'bdelete! ' .. expand('<abuf>') |" - " endif"); + add_map((char_u *)"<buffer> <esc> <Cmd>bdelete!<CR>", TERM_FOCUS, true); } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 013a78bdac..1a12cb636a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3154,11 +3154,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (ve_flags == VE_ALL && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { - /* Don't need to insert spaces when "p" on the last position of a - * tab or "P" on the first position. */ int viscol = getviscol(); + long ts = curbuf->b_p_ts; + // Don't need to insert spaces when "p" on the last position of a + // tab or "P" on the first position. if (dir == FORWARD - ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1 + ? tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1 : curwin->w_cursor.coladd > 0) { coladvance_force(viscol); } else { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index b1ca8c5805..538604cf79 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -4130,7 +4130,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (((wp->w_p_cuc && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off && (int)wp->w_virtcol < - grid->Columns * (row - startrow + 1) + v + (long)grid->Columns * (row - startrow + 1) + v && lnum != wp->w_cursor.lnum) || draw_color_col || line_attr_lowprio || line_attr || diff_hlf != (hlf_T)0 || has_virttext)) { @@ -6762,7 +6762,7 @@ void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid) void grid_invalidate(ScreenGrid *grid) { - (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); + (void)memset(grid->attrs, -1, sizeof(sattr_T) * grid->Rows * grid->Columns); } bool grid_invalid_row(ScreenGrid *grid, int row) diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 9429a06e92..1296d410f6 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -4082,7 +4082,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // char, e.g., "thes," -> "these". p = fword + sp->ts_fidx; MB_PTR_BACK(fword, p); - if (!spell_iswordp(p, curwin)) { + if (!spell_iswordp(p, curwin) && *preword != NUL) { p = preword + STRLEN(preword); MB_PTR_BACK(preword, p); if (spell_iswordp(p, curwin)) { diff --git a/src/nvim/state.c b/src/nvim/state.c index 68bc76660d..1fe8bb671d 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -180,7 +180,7 @@ char *get_mode(void) buf[1] = 'x'; } } - } else if (State & CMDLINE) { + } else if ((State & CMDLINE) || exmode_active) { buf[0] = 'c'; if (exmode_active) { buf[1] = 'v'; diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index cc767a9bcf..c0ac4393c4 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -27,6 +27,7 @@ source test_join.vim source test_jumps.vim source test_fileformat.vim source test_filetype.vim +source test_filetype_lua.vim source test_lambda.vim source test_menu.vim source test_messages.vim diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 2adea66008..7db05e34d5 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -446,7 +446,7 @@ let s:filename_checks = { \ 'sdc': ['file.sdc'], \ 'sdl': ['file.sdl', 'file.pr'], \ 'sed': ['file.sed'], - \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', 'any/etc/sensors.conf', 'any/etc/sensors3.conf'], + \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'], \ 'services': ['/etc/services', 'any/etc/services'], \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'], \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], @@ -653,7 +653,7 @@ let s:script_checks = { \ ['#!/path/nodejs'], \ ['#!/path/rhino']], \ 'bc': [['#!/path/bc']], - \ 'sed': [['#!/path/sed']], + \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']], \ 'ocaml': [['#!/path/ocaml']], \ 'awk': [['#!/path/awk'], \ ['#!/path/gawk']], @@ -1042,7 +1042,7 @@ func Test_dep3patch_file() call assert_notequal('dep3patch', &filetype) bwipe! - call delete('debian/patches', 'rf') + call delete('debian', 'rf') endfunc func Test_patch_file() @@ -1096,4 +1096,54 @@ func Test_git_file() filetype off endfunc +func Test_foam_file() + filetype on + call assert_true(mkdir('0', 'p')) + call assert_true(mkdir('0.orig', 'p')) + + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict') + split Xfile1Dict + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something') + split Xfile1Dict.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0/Xfile') + split 0/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile') + split 0.orig/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call delete('0', 'rf') + call delete('0.orig', 'rf') + filetype off +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim new file mode 100644 index 0000000000..f73e4ca33f --- /dev/null +++ b/src/nvim/testdir/test_filetype_lua.vim @@ -0,0 +1,2 @@ +let g:do_filetype_lua = 1 +source test_filetype.vim diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index d8d5797dcf..c568805f87 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -590,4 +590,12 @@ func Test_match_char_class_upper() bwipe! endfunc +func Test_match_invalid_byte() + call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid') + new + source Xinvalid + bwipe! + call delete('Xinvalid') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 84a5aca3d5..4371828a72 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -13,6 +13,8 @@ func Test_aaa_empty_reg_test() call assert_fails('normal @!', 'E354:') call assert_fails('normal @:', 'E30:') call assert_fails('normal @.', 'E29:') + call assert_fails('put /', 'E35:') + call assert_fails('put .', 'E29:') endfunc func Test_yank_shows_register() @@ -141,6 +143,14 @@ func Test_last_used_exec_reg() normal @@ call assert_equal('EditEdit', a) + " Test for repeating the last command-line in visual mode + call append(0, 'register') + normal gg + let @r = '' + call feedkeys("v:yank R\<CR>", 'xt') + call feedkeys("v@:", 'xt') + call assert_equal("\nregister\nregister\n", @r) + enew! endfunc @@ -164,6 +174,28 @@ func Test_get_register() call assert_equal('', getregtype('!')) + " Test for clipboard registers (* and +) + if has("clipboard_working") + call append(0, "text for clipboard test") + normal gg"*yiw + call assert_equal('text', getreg('*')) + normal gg2w"+yiw + call assert_equal('clipboard', getreg('+')) + endif + + " Test for inserting an invalid register content + call assert_beeps('exe "normal i\<C-R>!"') + + " Test for inserting a register with multiple lines + call deletebufline('', 1, '$') + call setreg('r', ['a', 'b']) + exe "normal i\<C-R>r" + call assert_equal(['a', 'b', ''], getline(1, '$')) + + " Test for inserting a multi-line register in the command line + call feedkeys(":\<C-R>r\<Esc>", 'xt') + call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + enew! endfunc @@ -187,6 +219,25 @@ func Test_set_register() call setreg('=', 'b', 'a') call assert_equal('regwrite', getreg('=')) + " Test for settting a list of lines to special registers + call setreg('/', []) + call assert_equal('', @/) + call setreg('=', []) + call assert_equal('', @=) + call assert_fails("call setreg('/', ['a', 'b'])", 'E883:') + call assert_fails("call setreg('=', ['a', 'b'])", 'E883:') + call assert_equal(0, setreg('_', ['a', 'b'])) + + " Test for recording to a invalid register + call assert_beeps('normal q$') + + " Appending to a register when recording + call append(0, "text for clipboard test") + normal gg + call feedkeys('qrllq', 'xt') + call feedkeys('qRhhq', 'xt') + call assert_equal('llhh', getreg('r')) + enew! endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index cf0faeee31..1ecb5c8070 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -768,6 +768,14 @@ func Test_spell_screendump() call delete('XtestSpell') endfunc +func Test_spell_single_word() + new + silent! norm 0R00 + spell! ß + silent 0norm 0r$ Dvz= + bwipe! +endfunc + let g:test_data_aff1 = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 8f992f7501..0a218898ed 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -81,6 +81,48 @@ func Test_edit_change() normal Cx call assert_equal('x', getline(1)) bwipe! + set virtualedit= +endfunc + +" Test for pasting before and after a tab character +func Test_paste_in_tab() + new + let @" = 'xyz' + set virtualedit=all + call append(0, "a\tb") + call cursor(1, 2, 6) + normal p + call assert_equal("a\txyzb", getline(1)) + call setline(1, "a\tb") + call cursor(1, 2) + normal P + call assert_equal("axyz\tb", getline(1)) + + " Test for virtual block paste + call setreg('"', 'xyz', 'b') + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal p + call assert_equal("a\txyzb", getline(1)) + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal P + call assert_equal("a xyz b", getline(1)) + + " Test for virtual block paste with gp and gP + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal gp + call assert_equal("a\txyzb", getline(1)) + call assert_equal([0, 1, 6, 0, 12], getcurpos()) + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal gP + call assert_equal("a xyz b", getline(1)) + call assert_equal([0, 1, 12, 0 ,12], getcurpos()) + + bwipe! + set virtualedit= endfunc " Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 8344598486..d58ca92a2f 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -433,6 +433,19 @@ func Test_Visual_Block() close! endfunc +" Test for 'p'ut in visual block mode +func Test_visual_block_put() + enew + + call append(0, ['One', 'Two', 'Three']) + normal gg + yank + call feedkeys("jl\<C-V>ljp", 'xt') + call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$')) + + enew! +endfunc + func Test_visual_put_in_block() new call setline(1, ['xxxx', 'y∞yy', 'zzzz']) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index bc2759ade7..450a76ddac 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -820,6 +820,16 @@ describe('nvim_set_keymap, nvim_del_keymap', function() eq(99, exec_lua[[return SomeValue]]) end) + it('does not reset pum in lua mapping', function() + eq(0, exec_lua [[ + VisibleCount = 0 + vim.api.nvim_set_keymap ('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end}) + return VisibleCount + ]]) + feed('i<C-X><C-V><F2><F2><esc>') + eq(2, exec_lua[[return VisibleCount]]) + end) + it('can overwrite lua mappings', function() eq(0, exec_lua [[ GlobalCount = 0 diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 4220d68ee1..2fa84e8313 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -224,6 +224,23 @@ describe('startup', function() end end) + it('-e sets ex mode', function() + local screen = Screen.new(25, 3) + clear('-e') + screen:attach() + -- Verify we set the proper mode both before and after :vi. + feed("put =mode(1)<CR>vi<CR>:put =mode(1)<CR>") + screen:expect([[ + cv | + ^n | + :put =mode(1) | + ]]) + + eq('cv\n', + funcs.system({nvim_prog, '-n', '-es' }, + { 'put =mode(1)', 'print', '' })) + end) + it('fails on --embed with -es/-Es', function() matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es', funcs.system({nvim_prog, '--embed', '-es' })) @@ -651,11 +668,7 @@ describe('runtime:', function() mkdir_p(ftdetect_folder) write_file(ftdetect_file , [[vim.g.lua_ftdetect = 1]]) - -- TODO(shadmansaleh): Figure out why this test fails without - -- setting VIMRUNTIME - clear{ args_rm={'-u'}, env={XDG_CONFIG_HOME=xconfig, - XDG_DATA_HOME=xdata, - VIMRUNTIME='runtime/'}} + clear{ args_rm={'-u'}, env=xenv } eq(1, eval('g:lua_ftdetect')) rmdir(ftdetect_folder) diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua index 40f36491e4..8ad81ac3d6 100644 --- a/test/functional/editor/K_spec.lua +++ b/test/functional/editor/K_spec.lua @@ -33,6 +33,29 @@ describe('K', function() feed('i'..test_file..'<ESC>K') retry(nil, nil, function() eq(1, eval('filereadable("'..test_file..'")')) end) eq({'fnord'}, eval("readfile('"..test_file.."')")) + -- Confirm that Neovim is still in terminal mode after K is pressed (#16692). + helpers.sleep(500) + eq('t', eval('mode()')) + feed('<space>') -- Any key, not just <space>, can be used here to escape. + eq('n', eval('mode()')) + end) + + it("<esc> kills the buffer for a running 'keywordprg' command", function() + helpers.source('set keywordprg=less') + eval('writefile(["hello", "world"], "' .. test_file .. '")') + feed('i' .. test_file .. '<esc>K') + eq('t', eval('mode()')) + -- Confirm that an arbitrary keypress doesn't escape (i.e., the process is + -- still running). If the process were no longer running, an arbitrary + -- keypress would escape. + helpers.sleep(500) + feed('<space>') + eq('t', eval('mode()')) + -- Confirm that <esc> kills the buffer for the running command. + local bufnr = eval('bufnr()') + feed('<esc>') + eq('n', eval('mode()')) + helpers.neq(bufnr, eval('bufnr()')) end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index f152a487af..1845786c4b 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -451,6 +451,7 @@ function module.new_argv(...) 'GCOV_ERROR_FILE', 'XDG_DATA_DIRS', 'TMPDIR', + 'VIMRUNTIME', }) do if not env_tbl[k] then env_tbl[k] = os.getenv(k) diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index 64d2fc7d84..729ebc601b 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -3,9 +3,6 @@ local exec_lua = helpers.exec_lua local eq = helpers.eq local clear = helpers.clear local pathroot = helpers.pathroot -local meths = helpers.meths -local curbufmeths = helpers.curbufmeths -local funcs = helpers.funcs local root = pathroot() @@ -75,10 +72,10 @@ describe('vim.filetype', function() local root = ... vim.filetype.add({ pattern = { - [root .. '/blog/.*%.txt'] = 'markdown', + ['~/blog/.*%.txt'] = 'markdown', } }) - vim.filetype.match(root .. '/blog/why_neovim_is_awesome.txt') + vim.filetype.match('~/blog/why_neovim_is_awesome.txt') return vim.bo.filetype ]], root)) end) @@ -98,572 +95,4 @@ describe('vim.filetype', function() return vim.bo.filetype ]]) end) - - it('correctly detects filetypes from test_filetype', function() - -- Checks copied from test_filetype.vim - local filename_checks = { - ['8th'] = {'file.8th'}, - ['a2ps'] = {'/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc', 'any/etc/a2ps.cfg', 'any/etc/a2ps/file.cfg'}, - ['a65'] = {'file.a65'}, - ['aap'] = {'file.aap'}, - ['abap'] = {'file.abap'}, - ['abc'] = {'file.abc'}, - ['abel'] = {'file.abl'}, - ['acedb'] = {'file.wrm'}, - ['ada'] = {'file.adb', 'file.ads', 'file.ada', 'file.gpr'}, - ['ahdl'] = {'file.tdf'}, - ['aidl'] = {'file.aidl'}, - ['alsaconf'] = {'.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf', 'any/etc/asound.conf', 'any/usr/share/alsa/alsa.conf'}, - ['aml'] = {'file.aml'}, - ['ampl'] = {'file.run'}, - ['ant'] = {'build.xml'}, - ['apache'] = {'.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file', '/etc/httpd/mods-some/file', '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'}, - ['apachestyle'] = {'/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'}, - ['applescript'] = {'file.scpt'}, - ['aptconf'] = {'apt.conf', '/.aptitude/config', 'any/.aptitude/config'}, - ['arch'] = {'.arch-inventory', '=tagging-method'}, - ['arduino'] = {'file.ino', 'file.pde'}, - ['art'] = {'file.art'}, - ['asciidoc'] = {'file.asciidoc', 'file.adoc'}, - ['asn'] = {'file.asn', 'file.asn1'}, - ['asterisk'] = {'asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'}, - ['atlas'] = {'file.atl', 'file.as'}, - ['autohotkey'] = {'file.ahk'}, - ['autoit'] = {'file.au3'}, - ['automake'] = {'GNUmakefile.am', 'makefile.am', 'Makefile.am'}, - ['ave'] = {'file.ave'}, - ['awk'] = {'file.awk', 'file.gawk'}, - ['b'] = {'file.mch', 'file.ref', 'file.imp'}, - ['bzl'] = {'file.bazel', 'file.bzl', 'WORKSPACE'}, - ['bc'] = {'file.bc'}, - ['bdf'] = {'file.bdf'}, - ['bib'] = {'file.bib'}, - ['beancount'] = {'file.beancount'}, - ['bindzone'] = {'named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'}, - ['blank'] = {'file.bl'}, - ['bsdl'] = {'file.bsd', 'file.bsdl', 'bsd', 'some-bsd'}, - ['bst'] = {'file.bst'}, - ['bzr'] = {'bzr_log.any', 'bzr_log.file'}, - ['c'] = {'enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'}, - ['cabal'] = {'file.cabal'}, - ['cabalconfig'] = {'cabal.config'}, - ['cabalproject'] = {'cabal.project', 'cabal.project.local'}, - ['calendar'] = {'calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'}, - ['catalog'] = {'catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'}, - ['cdl'] = {'file.cdl'}, - ['cdrdaoconf'] = {'/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'}, - ['cdrtoc'] = {'file.toc'}, - ['cf'] = {'file.cfm', 'file.cfi', 'file.cfc'}, - ['cfengine'] = {'cfengine.conf'}, - ['cfg'] = {'file.cfg', 'file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'}, - ['ch'] = {'file.chf'}, - ['chaiscript'] = {'file.chai'}, - ['chaskell'] = {'file.chs'}, - ['chill'] = {'file..ch'}, - ['chordpro'] = {'file.chopro', 'file.crd', 'file.cho', 'file.crdpro', 'file.chordpro'}, - ['cl'] = {'file.eni'}, - ['clean'] = {'file.dcl', 'file.icl'}, - ['clojure'] = {'file.clj', 'file.cljs', 'file.cljx', 'file.cljc'}, - ['cmake'] = {'CMakeLists.txt', 'file.cmake', 'file.cmake.in'}, - ['cmusrc'] = {'any/.cmus/autosave', 'any/.cmus/rc', 'any/.cmus/command-history', 'any/.cmus/file.theme', 'any/cmus/rc', 'any/cmus/file.theme', '/.cmus/autosave', '/.cmus/command-history', '/.cmus/file.theme', '/.cmus/rc', '/cmus/file.theme', '/cmus/rc'}, - ['cobol'] = {'file.cbl', 'file.cob', 'file.lib'}, - ['coco'] = {'file.atg'}, - ['conaryrecipe'] = {'file.recipe'}, - ['conf'] = {'auto.master'}, - ['config'] = {'configure.in', 'configure.ac', '/etc/hostname.file'}, - ['context'] = {'tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'}, - ['cpp'] = {'file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'}, - ['crm'] = {'file.crm'}, - ['crontab'] = {'crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'}, - ['cs'] = {'file.cs', 'file.csx'}, - ['csc'] = {'file.csc'}, - ['csdl'] = {'file.csdl'}, - ['csp'] = {'file.csp', 'file.fdr'}, - ['css'] = {'file.css'}, - ['cterm'] = {'file.con'}, - ['cucumber'] = {'file.feature'}, - ['cuda'] = {'file.cu', 'file.cuh'}, - ['cupl'] = {'file.pld'}, - ['cuplsim'] = {'file.si'}, - ['cvs'] = {'cvs123'}, - ['cvsrc'] = {'.cvsrc'}, - ['cynpp'] = {'file.cyn'}, - ['dart'] = {'file.dart', 'file.drt'}, - ['datascript'] = {'file.ds'}, - ['dcd'] = {'file.dcd'}, - ['debchangelog'] = {'changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'}, - ['debcontrol'] = {'/debian/control', 'any/debian/control'}, - ['debcopyright'] = {'/debian/copyright', 'any/debian/copyright'}, - ['debsources'] = {'/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'}, - ['def'] = {'file.def'}, - ['denyhosts'] = {'denyhosts.conf'}, - ['desc'] = {'file.desc'}, - ['desktop'] = {'file.desktop', '.directory', 'file.directory'}, - ['dictconf'] = {'dict.conf', '.dictrc'}, - ['dictdconf'] = {'dictd.conf', 'dictdfile.conf', 'dictd-file.conf'}, - ['diff'] = {'file.diff', 'file.rej'}, - ['dircolors'] = {'.dir_colors', '.dircolors', '/etc/DIR_COLORS', 'any/etc/DIR_COLORS'}, - ['dnsmasq'] = {'/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'}, - ['dockerfile'] = {'Containerfile', 'Dockerfile', 'file.Dockerfile', 'Dockerfile.debian', 'Containerfile.something'}, - ['dosbatch'] = {'file.bat', 'file.sys'}, - ['dosini'] = {'.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'}, - ['dot'] = {'file.dot', 'file.gv'}, - ['dracula'] = {'file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'}, - ['dtd'] = {'file.dtd'}, - ['dts'] = {'file.dts', 'file.dtsi'}, - ['dune'] = {'jbuild', 'dune', 'dune-project', 'dune-workspace'}, - ['dylan'] = {'file.dylan'}, - ['dylanintr'] = {'file.intr'}, - ['dylanlid'] = {'file.lid'}, - ['ecd'] = {'file.ecd'}, - ['edif'] = {'file.edf', 'file.edif', 'file.edo'}, - ['elinks'] = {'elinks.conf'}, - ['elixir'] = {'file.ex', 'file.exs', 'mix.lock'}, - ['eelixir'] = {'file.eex', 'file.leex'}, - ['elm'] = {'file.elm'}, - ['elmfilt'] = {'filter-rules'}, - ['epuppet'] = {'file.epp'}, - ['erlang'] = {'file.erl', 'file.hrl', 'file.yaws'}, - ['eruby'] = {'file.erb', 'file.rhtml'}, - ['esmtprc'] = {'anyesmtprc', 'esmtprc', 'some-esmtprc'}, - ['esqlc'] = {'file.ec', 'file.EC'}, - ['esterel'] = {'file.strl'}, - ['eterm'] = {'anyEterm/file.cfg', 'Eterm/file.cfg', 'some-Eterm/file.cfg'}, - ['exim'] = {'exim.conf'}, - ['expect'] = {'file.exp'}, - ['exports'] = {'exports'}, - ['factor'] = {'file.factor'}, - ['falcon'] = {'file.fal'}, - ['fan'] = {'file.fan', 'file.fwt'}, - ['fennel'] = {'file.fnl'}, - ['fetchmail'] = {'.fetchmailrc'}, - ['fgl'] = {'file.4gl', 'file.4gh', 'file.m4gl'}, - ['fish'] = {'file.fish'}, - ['focexec'] = {'file.fex', 'file.focexec'}, - ['forth'] = {'file.ft', 'file.fth'}, - ['fortran'] = {'file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'}, - ['fpcmake'] = {'file.fpc'}, - ['framescript'] = {'file.fsl'}, - ['freebasic'] = {'file.fb', 'file.bi'}, - ['fsharp'] = {'file.fs', 'file.fsi', 'file.fsx'}, - ['fstab'] = {'fstab', 'mtab'}, - ['fvwm'] = {'/.fvwm/file', 'any/.fvwm/file'}, - ['gdb'] = {'.gdbinit', 'gdbinit'}, - ['gdmo'] = {'file.mo', 'file.gdmo'}, - ['gedcom'] = {'file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'}, - ['gemtext'] = {'file.gmi', 'file.gemini'}, - ['gift'] = {'file.gift'}, - ['gitcommit'] = {'COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'}, - ['gitconfig'] = {'file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'}, - ['gitolite'] = {'gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'}, - ['gitrebase'] = {'git-rebase-todo'}, - ['gitsendemail'] = {'.gitsendemail.msg.xxxxxx'}, - ['gkrellmrc'] = {'gkrellmrc', 'gkrellmrc_x'}, - ['gnash'] = {'gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'}, - ['gnuplot'] = {'file.gpi'}, - ['go'] = {'file.go'}, - ['gomod'] = {'go.mod'}, - ['gp'] = {'file.gp', '.gprc'}, - ['gpg'] = {'/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'}, - ['grads'] = {'file.gs'}, - ['gretl'] = {'file.gretl'}, - ['groovy'] = {'file.gradle', 'file.groovy'}, - ['group'] = {'any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'}, - ['grub'] = {'/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'}, - ['gsp'] = {'file.gsp'}, - ['gtkrc'] = {'.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'}, - ['haml'] = {'file.haml'}, - ['hamster'] = {'file.hsm'}, - ['haskell'] = {'file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'}, - ['haste'] = {'file.ht'}, - ['hastepreproc'] = {'file.htpp'}, - ['hb'] = {'file.hb'}, - ['hercules'] = {'file.vc', 'file.ev', 'file.sum', 'file.errsum'}, - ['hex'] = {'file.hex', 'file.h32'}, - ['hgcommit'] = {'hg-editor-file.txt'}, - ['hog'] = {'file.hog', 'snort.conf', 'vision.conf'}, - ['hollywood'] = {'file.hws'}, - ['hostconf'] = {'/etc/host.conf', 'any/etc/host.conf'}, - ['hostsaccess'] = {'/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'}, - ['i3config'] = {'/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'}, - ['logcheck'] = {'/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'}, - ['modula3'] = {'file.m3', 'file.mg', 'file.i3', 'file.ig'}, - ['natural'] = {'file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'}, - ['neomuttrc'] = {'Neomuttrc', '.neomuttrc', '.neomuttrc-file', '/.neomutt/neomuttrc', '/.neomutt/neomuttrc-file', 'Neomuttrc', 'Neomuttrc-file', 'any/.neomutt/neomuttrc', 'any/.neomutt/neomuttrc-file', 'neomuttrc', 'neomuttrc-file'}, - ['opl'] = {'file.OPL', 'file.OPl', 'file.OpL', 'file.Opl', 'file.oPL', 'file.oPl', 'file.opL', 'file.opl'}, - ['pcmk'] = {'file.pcmk'}, - ['r'] = {'file.r'}, - ['rhelp'] = {'file.rd'}, - ['rmd'] = {'file.rmd', 'file.smd'}, - ['rnoweb'] = {'file.rnw', 'file.snw'}, - ['rrst'] = {'file.rrst', 'file.srst'}, - ['template'] = {'file.tmpl'}, - ['htmlm4'] = {'file.html.m4'}, - ['httest'] = {'file.htt', 'file.htb'}, - ['ibasic'] = {'file.iba', 'file.ibi'}, - ['icemenu'] = {'/.icewm/menu', 'any/.icewm/menu'}, - ['icon'] = {'file.icn'}, - ['indent'] = {'.indent.pro', 'indentrc'}, - ['inform'] = {'file.inf', 'file.INF'}, - ['initng'] = {'/etc/initng/any/file.i', 'file.ii', 'any/etc/initng/any/file.i'}, - ['inittab'] = {'inittab'}, - ['ipfilter'] = {'ipf.conf', 'ipf6.conf', 'ipf.rules'}, - ['iss'] = {'file.iss'}, - ['ist'] = {'file.ist', 'file.mst'}, - ['j'] = {'file.ijs'}, - ['jal'] = {'file.jal', 'file.JAL'}, - ['jam'] = {'file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'}, - ['java'] = {'file.java', 'file.jav'}, - ['javacc'] = {'file.jj', 'file.jjt'}, - ['javascript'] = {'file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'}, - ['javascriptreact'] = {'file.jsx'}, - ['jess'] = {'file.clp'}, - ['jgraph'] = {'file.jgr'}, - ['jovial'] = {'file.jov', 'file.j73', 'file.jovial'}, - ['jproperties'] = {'file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'}, - ['json'] = {'file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'}, - ['jsonc'] = {'file.jsonc'}, - ['jsp'] = {'file.jsp'}, - ['julia'] = {'file.jl'}, - ['kconfig'] = {'Kconfig', 'Kconfig.debug', 'Kconfig.file'}, - ['kivy'] = {'file.kv'}, - ['kix'] = {'file.kix'}, - ['kotlin'] = {'file.kt', 'file.ktm', 'file.kts'}, - ['kscript'] = {'file.ks'}, - ['kwt'] = {'file.k'}, - ['lace'] = {'file.ace', 'file.ACE'}, - ['latte'] = {'file.latte', 'file.lte'}, - ['ld'] = {'file.ld'}, - ['ldif'] = {'file.ldif'}, - ['less'] = {'file.less'}, - ['lex'] = {'file.lex', 'file.l', 'file.lxx', 'file.l++'}, - ['lftp'] = {'lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'}, - ['lhaskell'] = {'file.lhs'}, - ['libao'] = {'/etc/libao.conf', '/.libao', 'any/.libao', 'any/etc/libao.conf'}, - ['lifelines'] = {'file.ll'}, - ['lilo'] = {'lilo.conf', 'lilo.conf-file'}, - ['limits'] = {'/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'}, - ['liquid'] = {'file.liquid'}, - ['lisp'] = {'file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'}, - ['lite'] = {'file.lite', 'file.lt'}, - ['litestep'] = {'/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'}, - ['loginaccess'] = {'/etc/login.access', 'any/etc/login.access'}, - ['logindefs'] = {'/etc/login.defs', 'any/etc/login.defs'}, - ['logtalk'] = {'file.lgt'}, - ['lotos'] = {'file.lot', 'file.lotos'}, - ['lout'] = {'file.lou', 'file.lout'}, - ['lprolog'] = {'file.sig'}, - ['lsl'] = {'file.lsl'}, - ['lss'] = {'file.lss'}, - ['lua'] = {'file.lua', 'file.rockspec', 'file.nse'}, - ['lynx'] = {'lynx.cfg'}, - ['matlab'] = {'file.m'}, - ['m3build'] = {'m3makefile', 'm3overrides'}, - ['m3quake'] = {'file.quake', 'cm3.cfg'}, - ['m4'] = {'file.at'}, - ['mail'] = {'snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml', 'reportbug-file'}, - ['mailaliases'] = {'/etc/mail/aliases', '/etc/aliases', 'any/etc/aliases', 'any/etc/mail/aliases'}, - ['mailcap'] = {'.mailcap', 'mailcap'}, - ['make'] = {'file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile'}, - ['mallard'] = {'file.page'}, - ['manconf'] = {'/etc/man.conf', 'man.config', 'any/etc/man.conf'}, - ['map'] = {'file.map'}, - ['maple'] = {'file.mv', 'file.mpl', 'file.mws'}, - ['markdown'] = {'file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'}, - ['mason'] = {'file.mason', 'file.mhtml', 'file.comp'}, - ['master'] = {'file.mas', 'file.master'}, - ['mel'] = {'file.mel'}, - ['meson'] = {'meson.build', 'meson_options.txt'}, - ['messages'] = {'/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', - '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', - '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err', - '/log/auth.info', '/log/cron.info', '/log/daemon.info', '/log/debug.info', '/log/kern.info', '/log/lpr.info', '/log/mail.info', '/log/messages.info', '/log/news/news.info', '/log/syslog.info', '/log/user.info', - '/log/auth.warn', '/log/cron.warn', '/log/daemon.warn', '/log/debug.warn', '/log/kern.warn', '/log/lpr.warn', '/log/mail.warn', '/log/messages.warn', '/log/news/news.warn', '/log/syslog.warn', '/log/user.warn', - '/log/auth.crit', '/log/cron.crit', '/log/daemon.crit', '/log/debug.crit', '/log/kern.crit', '/log/lpr.crit', '/log/mail.crit', '/log/messages.crit', '/log/news/news.crit', '/log/syslog.crit', '/log/user.crit', - '/log/auth.notice', '/log/cron.notice', '/log/daemon.notice', '/log/debug.notice', '/log/kern.notice', '/log/lpr.notice', '/log/mail.notice', '/log/messages.notice', '/log/news/news.notice', '/log/syslog.notice', '/log/user.notice'}, - ['mf'] = {'file.mf'}, - ['mgl'] = {'file.mgl'}, - ['mgp'] = {'file.mgp'}, - ['mib'] = {'file.mib', 'file.my'}, - ['mix'] = {'file.mix', 'file.mixal'}, - ['mma'] = {'file.nb'}, - ['mmp'] = {'file.mmp'}, - ['modconf'] = {'/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'}, - ['modula2'] = {'file.m2', 'file.mi'}, - ['monk'] = {'file.isc', 'file.monk', 'file.ssc', 'file.tsc'}, - ['moo'] = {'file.moo'}, - ['mp'] = {'file.mp'}, - ['mplayerconf'] = {'mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'}, - ['mrxvtrc'] = {'mrxvtrc', '.mrxvtrc'}, - ['msidl'] = {'file.odl', 'file.mof'}, - ['msql'] = {'file.msql'}, - ['mupad'] = {'file.mu'}, - ['mush'] = {'file.mush'}, - ['muttrc'] = {'Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'}, - ['mysql'] = {'file.mysql'}, - ['n1ql'] = {'file.n1ql', 'file.nql'}, - ['named'] = {'namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'}, - ['nanorc'] = {'/etc/nanorc', 'file.nanorc', 'any/etc/nanorc'}, - ['ncf'] = {'file.ncf'}, - ['netrc'] = {'.netrc'}, - ['nginx'] = {'file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'}, - ['ninja'] = {'file.ninja'}, - ['nqc'] = {'file.nqc'}, - ['nroff'] = {'file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'}, - ['nsis'] = {'file.nsi', 'file.nsh'}, - ['obj'] = {'file.obj'}, - ['ocaml'] = {'file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit', 'file.mlt', 'file.mlp', 'file.mlip', 'file.mli.cppo', 'file.ml.cppo'}, - ['occam'] = {'file.occ'}, - ['octave'] = {'octaverc', '.octaverc', 'octave.conf'}, - ['omnimark'] = {'file.xom', 'file.xin'}, - ['opam'] = {'opam', 'file.opam', 'file.opam.template'}, - ['openroad'] = {'file.or'}, - ['ora'] = {'file.ora'}, - ['pamconf'] = {'/etc/pam.conf', '/etc/pam.d/file', 'any/etc/pam.conf', 'any/etc/pam.d/file'}, - ['pamenv'] = {'/etc/security/pam_env.conf', '/home/user/.pam_environment', '.pam_environment', 'pam_env.conf'}, - ['papp'] = {'file.papp', 'file.pxml', 'file.pxsl'}, - ['pascal'] = {'file.pas', 'file.dpr', 'file.lpr'}, - ['passwd'] = {'any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak', '/etc/passwd', '/etc/passwd-', '/etc/passwd.edit', '/etc/shadow', '/etc/shadow-', '/etc/shadow.edit', '/var/backups/passwd.bak', '/var/backups/shadow.bak'}, - ['pbtxt'] = {'file.pbtxt'}, - ['pccts'] = {'file.g'}, - ['pdf'] = {'file.pdf'}, - ['perl'] = {'file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'}, - ['pf'] = {'pf.conf'}, - ['pfmain'] = {'main.cf'}, - ['php'] = {'file.php', 'file.php9', 'file.phtml', 'file.ctp'}, - ['lpc'] = {'file.lpc', 'file.ulpc'}, - ['pike'] = {'file.pike', 'file.pmod'}, - ['cmod'] = {'file.cmod'}, - ['pilrc'] = {'file.rcp'}, - ['pine'] = {'.pinerc', 'pinerc', '.pinercex', 'pinercex'}, - ['pinfo'] = {'/etc/pinforc', '/.pinforc', 'any/.pinforc', 'any/etc/pinforc'}, - ['pli'] = {'file.pli', 'file.pl1'}, - ['plm'] = {'file.plm', 'file.p36', 'file.pac'}, - ['plp'] = {'file.plp'}, - ['plsql'] = {'file.pls', 'file.plsql'}, - ['po'] = {'file.po', 'file.pot'}, - ['pod'] = {'file.pod'}, - ['poke'] = {'file.pk'}, - ['postscr'] = {'file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'}, - ['pov'] = {'file.pov'}, - ['povini'] = {'.povrayrc'}, - ['ppd'] = {'file.ppd'}, - ['ppwiz'] = {'file.it', 'file.ih'}, - ['privoxy'] = {'file.action'}, - ['proc'] = {'file.pc'}, - ['procmail'] = {'.procmail', '.procmailrc'}, - ['prolog'] = {'file.pdb'}, - ['promela'] = {'file.pml'}, - ['proto'] = {'file.proto'}, - ['protocols'] = {'/etc/protocols', 'any/etc/protocols'}, - ['ps1'] = {'file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'}, - ['ps1xml'] = {'file.ps1xml'}, - ['psf'] = {'file.psf'}, - ['psl'] = {'file.psl'}, - ['puppet'] = {'file.pp'}, - ['pyret'] = {'file.arr'}, - ['pyrex'] = {'file.pyx', 'file.pxd'}, - ['python'] = {'file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'}, - ['quake'] = {'anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'}, - ['radiance'] = {'file.rad', 'file.mat'}, - ['raku'] = {'file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'}, - ['ratpoison'] = {'.ratpoisonrc', 'ratpoisonrc'}, - ['rbs'] = {'file.rbs'}, - ['rc'] = {'file.rc', 'file.rch'}, - ['rcs'] = {'file,v'}, - ['readline'] = {'.inputrc', 'inputrc'}, - ['remind'] = {'.reminders', 'file.remind', 'file.rem', '.reminders-file'}, - ['rego'] = {'file.rego'}, - ['resolv'] = {'resolv.conf'}, - ['reva'] = {'file.frt'}, - ['rexx'] = {'file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'}, - ['rib'] = {'file.rib'}, - ['rnc'] = {'file.rnc'}, - ['rng'] = {'file.rng'}, - ['robots'] = {'robots.txt'}, - ['routeros'] = {'file.rsc'}, - ['rpcgen'] = {'file.x'}, - ['rpl'] = {'file.rpl'}, - ['rst'] = {'file.rst'}, - ['rtf'] = {'file.rtf'}, - ['ruby'] = {'.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile'}, - ['rust'] = {'file.rs'}, - ['samba'] = {'smb.conf'}, - ['sas'] = {'file.sas'}, - ['sass'] = {'file.sass'}, - ['sather'] = {'file.sa'}, - ['sbt'] = {'file.sbt'}, - ['scala'] = {'file.scala', 'file.sc'}, - ['scheme'] = {'file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'}, - ['scilab'] = {'file.sci', 'file.sce'}, - ['screen'] = {'.screenrc', 'screenrc'}, - ['sexplib'] = {'file.sexp'}, - ['scdoc'] = {'file.scd'}, - ['scss'] = {'file.scss'}, - ['sd'] = {'file.sd'}, - ['sdc'] = {'file.sdc'}, - ['sdl'] = {'file.sdl', 'file.pr'}, - ['sed'] = {'file.sed'}, - ['sensors'] = {'/etc/sensors.conf', '/etc/sensors3.conf', 'any/etc/sensors.conf', 'any/etc/sensors3.conf'}, - ['services'] = {'/etc/services', 'any/etc/services'}, - ['setserial'] = {'/etc/serial.conf', 'any/etc/serial.conf'}, - ['sh'] = {'.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'}, - ['sieve'] = {'file.siv', 'file.sieve'}, - ['simula'] = {'file.sim'}, - ['sinda'] = {'file.sin', 'file.s85'}, - ['sisu'] = {'file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'}, - ['skill'] = {'file.il', 'file.ils', 'file.cdf'}, - ['slang'] = {'file.sl'}, - ['slice'] = {'file.ice'}, - ['solution'] = {'file.sln'}, - ['slpconf'] = {'/etc/slp.conf', 'any/etc/slp.conf'}, - ['slpreg'] = {'/etc/slp.reg', 'any/etc/slp.reg'}, - ['slpspi'] = {'/etc/slp.spi', 'any/etc/slp.spi'}, - ['slrnrc'] = {'.slrnrc'}, - ['slrnsc'] = {'file.score'}, - ['sm'] = {'sendmail.cf'}, - ['svelte'] = {'file.svelte'}, - ['smarty'] = {'file.tpl'}, - ['smcl'] = {'file.hlp', 'file.ihlp', 'file.smcl'}, - ['smith'] = {'file.smt', 'file.smith'}, - ['sml'] = {'file.sml'}, - ['snobol4'] = {'file.sno', 'file.spt'}, - ['sparql'] = {'file.rq', 'file.sparql'}, - ['spec'] = {'file.spec'}, - ['spice'] = {'file.sp', 'file.spice'}, - ['spup'] = {'file.speedup', 'file.spdata', 'file.spd'}, - ['spyce'] = {'file.spy', 'file.spi'}, - ['sql'] = {'file.tyb', 'file.typ', 'file.tyc', 'file.pkb', 'file.pks'}, - ['sqlj'] = {'file.sqlj'}, - ['sqr'] = {'file.sqr', 'file.sqi'}, - ['squid'] = {'squid.conf'}, - ['squirrel'] = {'file.nut'}, - ['srec'] = {'file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'}, - ['sshconfig'] = {'ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'}, - ['sshdconfig'] = {'sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'}, - ['st'] = {'file.st'}, - ['stata'] = {'file.ado', 'file.do', 'file.imata', 'file.mata'}, - ['stp'] = {'file.stp'}, - ['sudoers'] = {'any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'}, - ['svg'] = {'file.svg'}, - ['svn'] = {'svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'}, - ['swift'] = {'file.swift'}, - ['swiftgyb'] = {'file.swift.gyb'}, - ['sil'] = {'file.sil'}, - ['sysctl'] = {'/etc/sysctl.conf', '/etc/sysctl.d/file.conf', 'any/etc/sysctl.conf', 'any/etc/sysctl.d/file.conf'}, - ['systemd'] = {'any/systemd/file.automount', 'any/systemd/file.dnssd', 'any/systemd/file.link', 'any/systemd/file.mount', 'any/systemd/file.netdev', 'any/systemd/file.network', 'any/systemd/file.nspawn', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.slice', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/some.conf.d/file.conf', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile', '/.config/systemd/user/.#', '/.config/systemd/user/.#-file', '/.config/systemd/user/file.d/.#', '/.config/systemd/user/file.d/.#-file', '/.config/systemd/user/file.d/file.conf', '/etc/systemd/file.conf.d/file.conf', '/etc/systemd/system/.#', '/etc/systemd/system/.#-file', '/etc/systemd/system/file.d/.#', '/etc/systemd/system/file.d/.#-file', '/etc/systemd/system/file.d/file.conf', '/systemd/file.automount', '/systemd/file.dnssd', '/systemd/file.link', '/systemd/file.mount', '/systemd/file.netdev', '/systemd/file.network', '/systemd/file.nspawn', '/systemd/file.path', '/systemd/file.service', '/systemd/file.slice', '/systemd/file.socket', '/systemd/file.swap', '/systemd/file.target', '/systemd/file.timer', 'any/.config/systemd/user/.#', 'any/.config/systemd/user/.#-file', 'any/.config/systemd/user/file.d/.#', 'any/.config/systemd/user/file.d/.#-file', 'any/.config/systemd/user/file.d/file.conf', 'any/etc/systemd/file.conf.d/file.conf', 'any/etc/systemd/system/.#', 'any/etc/systemd/system/.#-file', 'any/etc/systemd/system/file.d/.#', 'any/etc/systemd/system/file.d/.#-file', 'any/etc/systemd/system/file.d/file.conf'}, - ['systemverilog'] = {'file.sv', 'file.svh'}, - ['tags'] = {'tags'}, - ['tak'] = {'file.tak'}, - ['taskdata'] = {'pending.data', 'completed.data', 'undo.data'}, - ['taskedit'] = {'file.task'}, - ['tcl'] = {'file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'}, - ['teraterm'] = {'file.ttl'}, - ['terminfo'] = {'file.ti'}, - ['tex'] = {'file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'}, - ['texinfo'] = {'file.texinfo', 'file.texi', 'file.txi'}, - ['texmf'] = {'texmf.cnf'}, - ['text'] = {'file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'}, - ['tf'] = {'file.tf', '.tfrc', 'tfrc'}, - ['tidy'] = {'.tidyrc', 'tidyrc', 'tidy.conf'}, - ['tilde'] = {'file.t.html'}, - ['tli'] = {'file.tli'}, - ['tmux'] = {'tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'}, - ['toml'] = {'file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'}, - ['tpp'] = {'file.tpp'}, - ['treetop'] = {'file.treetop'}, - ['trustees'] = {'trustees.conf'}, - ['tsalt'] = {'file.slt'}, - ['tsscl'] = {'file.tsscl'}, - ['tssgm'] = {'file.tssgm'}, - ['tssop'] = {'file.tssop'}, - ['twig'] = {'file.twig'}, - ['typescriptreact'] = {'file.tsx'}, - ['uc'] = {'file.uc'}, - ['udevconf'] = {'/etc/udev/udev.conf', 'any/etc/udev/udev.conf'}, - ['udevperm'] = {'/etc/udev/permissions.d/file.permissions', 'any/etc/udev/permissions.d/file.permissions'}, - ['udevrules'] = {'/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'}, - ['uil'] = {'file.uit', 'file.uil'}, - ['updatedb'] = {'/etc/updatedb.conf', 'any/etc/updatedb.conf'}, - ['upstart'] = {'/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override', 'any/.config/upstart/file.conf', 'any/.config/upstart/file.override', 'any/.init/file.conf', 'any/.init/file.override', 'any/etc/init/file.conf', 'any/etc/init/file.override', 'any/usr/share/upstart/file.conf', 'any/usr/share/upstart/file.override'}, - ['upstreamdat'] = {'upstream.dat', 'UPSTREAM.DAT', 'upstream.file.dat', 'UPSTREAM.FILE.DAT', 'file.upstream.dat', 'FILE.UPSTREAM.DAT'}, - ['upstreaminstalllog'] = {'upstreaminstall.log', 'UPSTREAMINSTALL.LOG', 'upstreaminstall.file.log', 'UPSTREAMINSTALL.FILE.LOG', 'file.upstreaminstall.log', 'FILE.UPSTREAMINSTALL.LOG'}, - ['upstreamlog'] = {'fdrupstream.log', 'upstream.log', 'UPSTREAM.LOG', 'upstream.file.log', 'UPSTREAM.FILE.LOG', 'file.upstream.log', 'FILE.UPSTREAM.LOG', 'UPSTREAM-file.log', 'UPSTREAM-FILE.LOG'}, - ['usserverlog'] = {'usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'}, - ['usw2kagtlog'] = {'usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'}, - ['vb'] = {'file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'}, - ['vera'] = {'file.vr', 'file.vri', 'file.vrh'}, - ['verilog'] = {'file.v'}, - ['verilogams'] = {'file.va', 'file.vams'}, - ['vgrindefs'] = {'vgrindefs'}, - ['vhdl'] = {'file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'}, - ['vim'] = {'file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'}, - ['viminfo'] = {'.viminfo', '_viminfo'}, - ['vmasm'] = {'file.mar'}, - ['voscm'] = {'file.cm'}, - ['vrml'] = {'file.wrl'}, - ['vroom'] = {'file.vroom'}, - ['vue'] = {'file.vue'}, - ['wast'] = {'file.wast', 'file.wat'}, - ['webmacro'] = {'file.wm'}, - ['wget'] = {'.wgetrc', 'wgetrc'}, - ['winbatch'] = {'file.wbt'}, - ['wml'] = {'file.wml'}, - ['wsh'] = {'file.wsf', 'file.wsc'}, - ['wsml'] = {'file.wsml'}, - ['wvdial'] = {'wvdial.conf', '.wvdialrc'}, - ['xdefaults'] = {'.Xdefaults', '.Xpdefaults', '.Xresources', 'xdm-config', 'file.ad', '/Xresources/file', '/app-defaults/file', 'Xresources', 'Xresources-file', 'any/Xresources/file', 'any/app-defaults/file'}, - ['xhtml'] = {'file.xhtml', 'file.xht'}, - ['xinetd'] = {'/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'}, - ['xmath'] = {'file.msc', 'file.msf'}, - ['xml'] = {'/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd'}, - ['xmodmap'] = {'anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'}, - ['xf86conf'] = {'xorg.conf', 'xorg.conf-4'}, - ['xpm'] = {'file.xpm'}, - ['xpm2'] = {'file.xpm2'}, - ['xquery'] = {'file.xq', 'file.xql', 'file.xqm', 'file.xquery', 'file.xqy'}, - ['xs'] = {'file.xs'}, - ['xsd'] = {'file.xsd'}, - ['xslt'] = {'file.xsl', 'file.xslt'}, - ['yacc'] = {'file.yy', 'file.yxx', 'file.y++'}, - ['yaml'] = {'file.yaml', 'file.yml'}, - ['raml'] = {'file.raml'}, - ['z8a'] = {'file.z8a'}, - ['zig'] = {'file.zig'}, - ['zimbu'] = {'file.zu'}, - ['zimbutempl'] = {'file.zut'}, - ['zsh'] = {'.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'}, - ['help'] = {funcs.getenv('VIMRUNTIME') .. '/doc/help.txt'}, - } - - local filename_case_checks = { - ['modula2'] = {'file.DEF', 'file.MOD'}, - ['bzl'] = {'file.BUILD', 'BUILD'}, - } - - local function check_items(checks) - meths.set_option('swapfile', false) - for ft, names in pairs(checks) do - for _, name in ipairs(names) do - meths.command('new') - meths.command('edit ' .. funcs.fnameescape(name)) - if curbufmeths.get_option('filetype') == '' and curbufmeths.get_option('readonly') then -- luacheck: ignore 542 - -- File exists but not able to edit it (permission denied) - else - eq(ft, curbufmeths.get_option('filetype')) - end - meths.command('bwipe!') - end - end - meths.command('set swapfile&') - end - - meths.set_var('do_filetype_lua', 1) - meths.command('filetype on') - check_items(filename_checks) - if funcs.has('fname_case') == 1 then - check_items(filename_case_checks) - end - meths.command('filetype off') - end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 642dc59bbc..ddcf7687f5 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -987,6 +987,38 @@ describe('lua stdlib', function() matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) + + exec_lua [[ + local counter = 0 + vim.g.AddCounter = function() counter = counter + 1 end + vim.g.GetCounter = function() return counter end + ]] + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + eq(3, exec_lua([[return vim.g.GetCounter()]])) + exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_set_var('AddCounter', function() counter = counter + 1 end) + vim.api.nvim_set_var('GetCounter', function() return counter end) + ]] + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + eq(3, exec_lua([[return vim.g.GetCounter()]])) + exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) end) it('vim.b', function() @@ -1022,6 +1054,38 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.to_delete") exec_lua [[ + local counter = 0 + vim.b.AddCounter = function() counter = counter + 1 end + vim.b.GetCounter = function() return counter end + ]] + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_buf_set_var(0, 'AddCounter', function() counter = counter + 1 end) + vim.api.nvim_buf_set_var(0, 'GetCounter', function() return counter end) + ]] + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1059,6 +1123,38 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.to_delete") exec_lua [[ + local counter = 0 + vim.w.AddCounter = function() counter = counter + 1 end + vim.w.GetCounter = function() return counter end + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_win_set_var(0, 'AddCounter', function() counter = counter + 1 end) + vim.api.nvim_win_set_var(0, 'GetCounter', function() return counter end) + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1091,6 +1187,38 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.to_delete") exec_lua [[ + local counter = 0 + vim.t.AddCounter = function() counter = counter + 1 end + vim.t.GetCounter = function() return counter end + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_tabpage_set_var(0, 'AddCounter', function() counter = counter + 1 end) + vim.api.nvim_tabpage_set_var(0, 'GetCounter', function() return counter end) + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + + exec_lua [[ vim.cmd "tabnew" ]] diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 2fe7eca260..eab520948f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -76,7 +76,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options) end; flags = { allow_incremental_sync = options.allow_incremental_sync or false; - debounce_text_changes = 0; + debounce_text_changes = options.debounce_text_changes or 0; }; on_exit = function(...) vim.rpcnotify(1, "exit", ...) @@ -929,7 +929,60 @@ describe('LSP', function() local client test_rpc_server { test_name = "basic_check_buffer_open_and_change_incremental"; - options = { allow_incremental_sync = true }; + options = { + allow_incremental_sync = true, + }; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_handler = function(err, result, ctx) + if ctx.method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "123boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + client.stop() + end + end; + } + end) + it('should check the body and didChange incremental with debounce', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_incremental"; + options = { + allow_incremental_sync = true, + debounce_text_changes = 5 + }; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -1238,7 +1291,7 @@ describe('LSP', function() make_edit(2, 0, 2, 2, {"3"}); make_edit(3, 2, 3, 4, {""}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ '123First line of text'; '2econd line of text'; @@ -1258,7 +1311,7 @@ describe('LSP', function() make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"}); make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1282,7 +1335,7 @@ describe('LSP', function() make_edit(3, #"Fourth", 3, #'', {"another line of text", "before this"}); make_edit(3, #"Fourth line of text", 3, #'Fourth', {"!"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1299,7 +1352,7 @@ describe('LSP', function() local edits = { make_edit(4, 3, 4, 4, {"ä"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1312,7 +1365,7 @@ describe('LSP', function() local edits = { make_edit(5, 0, 5, 0, "foobar"); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1322,6 +1375,20 @@ describe('LSP', function() 'foobar'; }, buf_lines(1)) end) + it('applies multiple text edits at the end of the document', function() + local edits = { + make_edit(4, 0, 5, 0, ""); + make_edit(5, 0, 5, 0, "foobar"); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'foobar'; + }, buf_lines(1)) + end) describe('cursor position', function() it('don\'t fix the cursor if the range contains the cursor', function() @@ -1329,7 +1396,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 1, 19, 'Second line of text') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1346,7 +1413,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(1, 6, 1, 19, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; ''; @@ -1363,7 +1430,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(0, 18, 5, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; }, buf_lines(1)) @@ -1375,7 +1442,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 2, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1390,7 +1457,7 @@ describe('LSP', function() local edits = { make_edit(1, 7, 1, 11, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second of text'; @@ -1406,7 +1473,7 @@ describe('LSP', function() local edits = { make_edit(0, 11, 1, 12, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1422,21 +1489,21 @@ describe('LSP', function() local edits = { make_edit(0, 0, 5, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) it('applies edits when the end line is 2 larger than vim\'s', function() local edits = { make_edit(0, 0, 6, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) it('applies edits with a column offset', function() local edits = { make_edit(0, 0, 5, 2, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) end) @@ -1464,7 +1531,7 @@ describe('LSP', function() ]] end) it('correctly goes ahead with the edit if all is normal', function() - exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5)) + exec_lua("vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')", text_document_edit(5)) eq({ 'First ↥ 🤦 🦄 line of text'; '2nd line of è¯text'; @@ -1476,7 +1543,7 @@ describe('LSP', function() local bufnr = select(1, ...) local text_edit = select(2, ...) vim.lsp.util.buf_versions[bufnr] = 10 - vim.lsp.util.apply_text_document_edit(text_edit) + vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') ]], target_bufnr, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text'; @@ -1489,7 +1556,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(args[1]) + vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], edit, versionedBuf) end @@ -1515,17 +1582,36 @@ describe('LSP', function() describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() - local expected = { - applied = true; - failureReason = nil; + local expected_handlers = { + {NIL, {}, {method="test", client_id=1}}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client, _) + client.stop() + end; + -- If the program timed out, then code will be nil. + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + -- Note that NIL must be used here. + -- on_handler(err, method, result, client_id) + on_handler = function(...) + local expected = { + applied = true; + failureReason = nil; + } + eq(expected, exec_lua [[ + local apply_edit = { + label = nil; + edit = {}; + } + return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit, {client_id = TEST_RPC_CLIENT_ID}) + ]]) + eq(table.remove(expected_handlers), {...}) + end; } - eq(expected, exec_lua [[ - local apply_edit = { - label = nil; - edit = {}; - } - return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit) - ]]) end) end) @@ -1599,7 +1685,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) @@ -1621,7 +1707,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) @@ -1638,7 +1724,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) end) it('createFile does not touch file if it exists and ignoreIfExists is set', function() @@ -1656,7 +1742,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('Dummy content', read_file(tmpfile)) end) @@ -1676,7 +1762,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('', read_file(tmpfile)) end) @@ -1697,7 +1783,7 @@ describe('LSP', function() } } } - eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')) eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) end) @@ -1858,7 +1944,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -1888,7 +1974,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -2192,7 +2278,7 @@ describe('LSP', function() end local jump = function(msg) - eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg)) + eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg, "utf-16")) eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]]) return { line = exec_lua[[return vim.fn.line('.')]], @@ -2266,6 +2352,27 @@ describe('LSP', function() end) end) + describe('lsp.util.convert_signature_help_to_markdown_lines', function() + it('can handle negative activeSignature', function() + local result = exec_lua[[ + local signature_help = { + activeParameter = 0, + activeSignature = -1, + signatures = { + { + documentation = "", + label = "TestEntity.TestEntity()", + parameters = {} + }, + } + } + return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) + ]] + local expected = {'```cs', 'TestEntity.TestEntity()', '```', ''} + eq(expected, result) + end) + end) + describe('lsp.util.get_effective_tabstop', function() local function test_tabstop(tabsize, softtabstop) exec_lua(string.format([[ diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 1ff451434d..beb43e0271 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -292,10 +292,9 @@ describe(':terminal buffer', function() command('quit') end) - it('does not segfault when pasting empty buffer #13955', function() - feed_command('terminal') + it('does not segfault when pasting empty register #13955', function() feed('<c-\\><c-n>') - feed_command('put a') -- buffer a is empty + feed_command('put a') -- register a is empty helpers.assert_alive() end) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 911fa017ab..44a7f00d8b 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -167,6 +167,27 @@ void ui_refresh(void) eq('variable', ret) end) + it("supports caching queries", function() + local long_query = query:rep(100) + local first_run = exec_lua ([[ + local before = vim.loop.hrtime() + cquery = vim.treesitter.parse_query("c", ...) + local after = vim.loop.hrtime() + return after - before + ]], long_query) + + local subsequent_runs = exec_lua ([[ + local before = vim.loop.hrtime() + for i=1,100,1 do + cquery = vim.treesitter.parse_query("c", ...) + end + local after = vim.loop.hrtime() + return after - before + ]], long_query) + + assert.True(1000 * subsequent_runs < first_run) + end) + it('support query and iter by capture', function() insert(test_text) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 4fc5c389e5..d7f43ca18c 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -10,6 +10,7 @@ local funcs = helpers.funcs local get_pathsep = helpers.get_pathsep local eq = helpers.eq local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua describe('ui/ext_popupmenu', function() local screen @@ -369,6 +370,62 @@ describe('ui/ext_popupmenu', function() {1:~ }| {2:-- INSERT --} | ]]) + + command('iunmap <f1>') + command('iunmap <f2>') + command('iunmap <f3>') + exec_lua([[ + vim.keymap.set('i', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end) + vim.keymap.set('i', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end) + vim.keymap.set('i', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end) + ]]) + feed('<C-r>=TestComplete()<CR>') + screen:expect([[ + | + foo^ | + {6:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f1>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {6:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f2>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f3>') + screen:expect([[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) end) local function source_complete_month() diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 613b39c833..cbb3d6884f 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -203,8 +203,8 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.1.tar.gz) set(TREESITTER_C_SHA256 ffcc2ef0eded59ad1acec9aec4f9b0c7dd209fc1a85d85f8b0e81298e3dddcc2) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.2.tar.gz) -set(TREESITTER_SHA256 2a0445f8172bbf83db005aedb4e893d394e2b7b33251badd3c94c2c5cc37c403) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/bf210f0c9ec7931c1a5f639461495db240aac149.tar.gz) +set(TREESITTER_SHA256 49c1f4d7de932f46bb0a68febb71627c43c110ff6cea6170f475c00270535ad7) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) |