diff options
Diffstat (limited to 'runtime')
30 files changed, 1517 insertions, 158 deletions
diff --git a/runtime/autoload/gzip.vim b/runtime/autoload/gzip.vim index 26b1cda034..a6fbe2c336 100644 --- a/runtime/autoload/gzip.vim +++ b/runtime/autoload/gzip.vim @@ -1,6 +1,6 @@ " Vim autoload file for editing compressed files. " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2023 Aug 10 +" Last Change: 2024 Nov 25 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " These functions are used by the gzip plugin. @@ -148,6 +148,9 @@ fun gzip#read(cmd) else let fname = escape(expand("%:r"), " \t\n*?[{`$\\%#'\"|!<") endif + if filereadable(undofile(expand("%"))) + exe "sil rundo " . fnameescape(undofile(expand("%"))) + endif if &verbose >= 8 execute "doau BufReadPost " . fname else diff --git a/runtime/autoload/spotbugs.vim b/runtime/autoload/spotbugs.vim new file mode 100644 index 0000000000..9161395794 --- /dev/null +++ b/runtime/autoload/spotbugs.vim @@ -0,0 +1,250 @@ +" Default pre- and post-compiler actions for SpotBugs +" Maintainers: @konfekt and @zzzyxwvut +" Last Change: 2024 Nov 27 + +let s:save_cpo = &cpo +set cpo&vim + +if v:version > 900 + + function! spotbugs#DeleteClassFiles() abort + if !exists('b:spotbugs_class_files') + return + endif + + for pathname in b:spotbugs_class_files + let classname = pathname =~# "^'.\\+\\.class'$" + \ ? eval(pathname) + \ : pathname + + if classname =~# '\.class$' && filereadable(classname) + " Since v9.0.0795. + let octad = readblob(classname, 0, 8) + + " Test the magic number and the major version number (45 for v1.0). + " Since v9.0.2027. + if len(octad) == 8 && octad[0 : 3] == 0zcafe.babe && + \ or((octad[6] << 8), octad[7]) >= 45 + echomsg printf('Deleting %s: %d', classname, delete(classname)) + endif + endif + endfor + + let b:spotbugs_class_files = [] + endfunction + +else + + function! s:DeleteClassFilesWithNewLineCodes(classname) abort + " The distribution of "0a"s in class file versions 2560 and 2570: + " + " 0zca.fe.ba.be.00.00.0a.00 0zca.fe.ba.be.00.00.0a.0a + " 0zca.fe.ba.be.00.0a.0a.00 0zca.fe.ba.be.00.0a.0a.0a + " 0zca.fe.ba.be.0a.00.0a.00 0zca.fe.ba.be.0a.00.0a.0a + " 0zca.fe.ba.be.0a.0a.0a.00 0zca.fe.ba.be.0a.0a.0a.0a + let numbers = [0, 0, 0, 0, 0, 0, 0, 0] + let offset = 0 + let lines = readfile(a:classname, 'b', 4) + + " Track NL byte counts to handle files of less than 8 bytes. + let nl_cnt = len(lines) + " Track non-NL byte counts for "0zca.fe.ba.be.0a.0a.0a.0a". + let non_nl_cnt = 0 + + for line in lines + for idx in range(strlen(line)) + " Remap NLs to Nuls. + let numbers[offset] = (line[idx] == "\n") ? 0 : char2nr(line[idx]) % 256 + let non_nl_cnt += 1 + let offset += 1 + + if offset > 7 + break + endif + endfor + + let nl_cnt -= 1 + + if offset > 7 || (nl_cnt < 1 && non_nl_cnt > 4) + break + endif + + " Reclaim NLs. + let numbers[offset] = 10 + let offset += 1 + + if offset > 7 + break + endif + endfor + + " Test the magic number and the major version number (45 for v1.0). + if offset > 7 && numbers[0] == 0xca && numbers[1] == 0xfe && + \ numbers[2] == 0xba && numbers[3] == 0xbe && + \ (numbers[6] * 256 + numbers[7]) >= 45 + echomsg printf('Deleting %s: %d', a:classname, delete(a:classname)) + endif + endfunction + + function! spotbugs#DeleteClassFiles() abort + if !exists('b:spotbugs_class_files') + return + endif + + let encoding = &encoding + + try + set encoding=latin1 + + for pathname in b:spotbugs_class_files + let classname = pathname =~# "^'.\\+\\.class'$" + \ ? eval(pathname) + \ : pathname + + if classname =~# '\.class$' && filereadable(classname) + let line = get(readfile(classname, 'b', 1), 0, '') + let length = strlen(line) + + " Test the magic number and the major version number (45 for v1.0). + if length > 3 && line[0 : 3] == "\xca\xfe\xba\xbe" + if length > 7 && ((line[6] == "\n" ? 0 : char2nr(line[6]) % 256) * 256 + + \ (line[7] == "\n" ? 0 : char2nr(line[7]) % 256)) >= 45 + echomsg printf('Deleting %s: %d', classname, delete(classname)) + else + call s:DeleteClassFilesWithNewLineCodes(classname) + endif + endif + endif + endfor + finally + let &encoding = encoding + endtry + + let b:spotbugs_class_files = [] + endfunction + +endif + +function! spotbugs#DefaultPostCompilerAction() abort + " Since v7.4.191. + make %:S +endfunction + +" Look for "spotbugs#compiler" in "ftplugin/java.vim". +let s:compiler = exists('spotbugs#compiler') ? spotbugs#compiler : '' +let s:readable = filereadable($VIMRUNTIME . '/compiler/' . s:compiler . '.vim') + +if s:readable && s:compiler ==# 'maven' && executable('mvn') + + function! spotbugs#DefaultPreCompilerAction() abort + call spotbugs#DeleteClassFiles() + compiler maven + make compile + endfunction + + function! spotbugs#DefaultPreCompilerTestAction() abort + call spotbugs#DeleteClassFiles() + compiler maven + make test-compile + endfunction + + function! spotbugs#DefaultProperties() abort + return { + \ 'PreCompilerAction': + \ function('spotbugs#DefaultPreCompilerAction'), + \ 'PreCompilerTestAction': + \ function('spotbugs#DefaultPreCompilerTestAction'), + \ 'PostCompilerAction': + \ function('spotbugs#DefaultPostCompilerAction'), + \ 'sourceDirPath': 'src/main/java', + \ 'classDirPath': 'target/classes', + \ 'testSourceDirPath': 'src/test/java', + \ 'testClassDirPath': 'target/test-classes', + \ } + endfunction + + unlet s:readable s:compiler +elseif s:readable && s:compiler ==# 'ant' && executable('ant') + + function! spotbugs#DefaultPreCompilerAction() abort + call spotbugs#DeleteClassFiles() + compiler ant + make compile + endfunction + + function! spotbugs#DefaultPreCompilerTestAction() abort + call spotbugs#DeleteClassFiles() + compiler ant + make compile-test + endfunction + + function! spotbugs#DefaultProperties() abort + return { + \ 'PreCompilerAction': + \ function('spotbugs#DefaultPreCompilerAction'), + \ 'PreCompilerTestAction': + \ function('spotbugs#DefaultPreCompilerTestAction'), + \ 'PostCompilerAction': + \ function('spotbugs#DefaultPostCompilerAction'), + \ 'sourceDirPath': 'src', + \ 'classDirPath': 'build/classes', + \ 'testSourceDirPath': 'test', + \ 'testClassDirPath': 'build/test/classes', + \ } + endfunction + + unlet s:readable s:compiler +elseif s:readable && s:compiler ==# 'javac' && executable('javac') + + function! spotbugs#DefaultPreCompilerAction() abort + call spotbugs#DeleteClassFiles() + compiler javac + + if get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')) =~ '\s@\S' + " Read options and filenames from @options [@sources ...]. + make + else + " Let Javac figure out what files to compile. + execute 'make ' . join(map(filter(copy(v:argv), + \ "v:val =~# '\\.java\\=$'"), + \ 'shellescape(v:val)'), ' ') + endif + endfunction + + function! spotbugs#DefaultPreCompilerTestAction() abort + call spotbugs#DefaultPreCompilerAction() + endfunction + + function! spotbugs#DefaultProperties() abort + return { + \ 'PreCompilerAction': + \ function('spotbugs#DefaultPreCompilerAction'), + \ 'PreCompilerTestAction': + \ function('spotbugs#DefaultPreCompilerTestAction'), + \ 'PostCompilerAction': + \ function('spotbugs#DefaultPostCompilerAction'), + \ } + endfunction + + unlet s:readable s:compiler +else + + function! spotbugs#DefaultPreCompilerAction() abort + echomsg printf('Not supported: "%s"', s:compiler) + endfunction + + function! spotbugs#DefaultPreCompilerTestAction() abort + call spotbugs#DefaultPreCompilerAction() + endfunction + + function! spotbugs#DefaultProperties() abort + return {} + endfunction + + unlet s:readable +endif + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim: set foldmethod=syntax shiftwidth=2 expandtab: diff --git a/runtime/compiler/eslint.vim b/runtime/compiler/eslint.vim index db7a665991..0414817900 100644 --- a/runtime/compiler/eslint.vim +++ b/runtime/compiler/eslint.vim @@ -1,13 +1,12 @@ " Vim compiler file " Compiler: ESLint for JavaScript " Maintainer: Romain Lafourcade <romainlafourcade@gmail.com> -" Last Change: 2020 August 20 -" 2024 Apr 03 by The Vim Project (removed :CompilerSet definition) +" Last Change: 2024 Nov 30 if exists("current_compiler") finish endif let current_compiler = "eslint" -CompilerSet makeprg=npx\ eslint\ --format\ compact -CompilerSet errorformat=%f:\ line\ %l\\,\ col\ %c\\,\ %m,%-G%.%# +CompilerSet makeprg=npx\ eslint\ --format\ stylish +CompilerSet errorformat=%-P%f,\%\\s%#%l:%c\ %#\ %trror\ \ %m,\%\\s%#%l:%c\ %#\ %tarning\ \ %m,\%-Q,\%-G%.%#, diff --git a/runtime/compiler/javac.vim b/runtime/compiler/javac.vim index 9bd4cdf270..53cd772ed8 100644 --- a/runtime/compiler/javac.vim +++ b/runtime/compiler/javac.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Java Development Kit Compiler " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2024 Jun 14 +" Last Change: 2024 Nov 19 (enable local javac_makeprg_params) if exists("current_compiler") finish @@ -11,11 +11,7 @@ let current_compiler = "javac" let s:cpo_save = &cpo set cpo&vim -if exists("g:javac_makeprg_params") - execute $'CompilerSet makeprg=javac\ {escape(g:javac_makeprg_params, ' \|"')}' -else - CompilerSet makeprg=javac -endif +execute $'CompilerSet makeprg=javac\ {escape(get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')), ' \|"')}' CompilerSet errorformat=%E%f:%l:\ error:\ %m, \%W%f:%l:\ warning:\ %m, diff --git a/runtime/compiler/maven.vim b/runtime/compiler/maven.vim index ef8d8a6fb2..72e74e301d 100644 --- a/runtime/compiler/maven.vim +++ b/runtime/compiler/maven.vim @@ -14,7 +14,7 @@ if exists("current_compiler") endif let current_compiler = "maven" -CompilerSet makeprg=mvn\ --batch-mode +execute $'CompilerSet makeprg=mvn\ --batch-mode\ {escape(get(b:, 'maven_makeprg_params', get(g:, 'maven_makeprg_params', '')), ' \|"')}' " Error message for POM CompilerSet errorformat=[FATAL]\ Non-parseable\ POM\ %f:\ %m%\\s%\\+@%.%#line\ %l\\,\ column\ %c%.%#, diff --git a/runtime/compiler/pytest.vim b/runtime/compiler/pytest.vim new file mode 100644 index 0000000000..7fc189932c --- /dev/null +++ b/runtime/compiler/pytest.vim @@ -0,0 +1,103 @@ +" Vim compiler file +" Compiler: Pytest (Python testing framework) +" Maintainer: @Konfekt and @mgedmin +" Last Change: 2024 Nov 28 + +if exists("current_compiler") | finish | endif +let current_compiler = "pytest" + +let s:cpo_save = &cpo +set cpo&vim + +" CompilerSet makeprg=pytest +if has('unix') + execute $'CompilerSet makeprg=/usr/bin/env\ PYTHONWARNINGS=ignore\ pytest\ {escape(get(b:, 'pytest_makeprg_params', get(g:, 'pytest_makeprg_params', '--tb=short --quiet')), ' \|"')}' +elseif has('win32') + execute $'CompilerSet makeprg=set\ PYTHONWARNINGS=ignore\ &&\ pytest\ {escape(get(b:, 'pytest_makeprg_params', get(g:, 'pytest_makeprg_params', '--tb=short --quiet')), ' \|"')}' +else + CompilerSet makeprg=pytest\ --tb=short\ --quiet + execute $'CompilerSet makeprg=pytest\ {escape(get(b:, 'pytest_makeprg_params', get(g:, 'pytest_makeprg_params', '--tb=short --quiet')), ' \|"')}' +endif + +" Pytest syntax errors {{{2 + +" Reset error format so that sourcing .vimrc again and again doesn't grow it +" without bounds +setlocal errorformat& + +" For the record, the default errorformat is this: +" +" %*[^"]"%f"%*\D%l: %m +" "%f"%*\D%l: %m +" %-G%f:%l: (Each undeclared identifier is reported only once +" %-G%f:%l: for each function it appears in.) +" %-GIn file included from %f:%l:%c: +" %-GIn file included from %f:%l:%c\, +" %-GIn file included from %f:%l:%c +" %-GIn file included from %f:%l +" %-G%*[ ]from %f:%l:%c +" %-G%*[ ]from %f:%l: +" %-G%*[ ]from %f:%l\, +" %-G%*[ ]from %f:%l +" %f:%l:%c:%m +" %f(%l):%m +" %f:%l:%m +" "%f"\, line %l%*\D%c%*[^ ] %m +" %D%*\a[%*\d]: Entering directory %*[`']%f' +" %X%*\a[%*\d]: Leaving directory %*[`']%f' +" %D%*\a: Entering directory %*[`']%f' +" %X%*\a: Leaving directory %*[`']%f' +" %DMaking %*\a in %f +" %f|%l| %m +" +" and sometimes it misfires, so let's fix it up a bit +" (TBH I don't even know what compiler produces filename(lineno) so why even +" have it?) +setlocal errorformat-=%f(%l):%m + +" Sometimes Vim gets confused about ISO-8601 timestamps and thinks they're +" filenames; this is a big hammer that ignores anything filename-like on lines +" that start with at least two spaces, possibly preceded by a number and +" optional punctuation +setlocal errorformat^=%+G%\\d%#%.%\\=\ \ %.%# + +" Similar, but when the entire line starts with a date +setlocal errorformat^=%+G\\d\\d\\d\\d-\\d\\d-\\d\\d\ \\d\\d:\\d\\d%.%# + +" make: *** [Makefile:14: target] Error 1 +setlocal errorformat^=%+Gmake:\ ***\ %.%# + +" FAILED tests.py::test_with_params[YYYY-MM-DD:HH:MM:SS] - Exception: bla bla +setlocal errorformat^=%+GFAILED\ %.%# + +" AssertionError: assert ...YYYY-MM-DD:HH:MM:SS... +setlocal errorformat^=%+GAssertionError:\ %.%# + +" --- /path/to/file:before YYYY-MM-DD HH:MM:SS.ssssss +setlocal errorformat^=---%f:%m + +" +++ /path/to/file:before YYYY-MM-DD HH:MM:SS.ssssss +setlocal errorformat^=+++%f:%m + +" Sometimes pytest prepends an 'E' marker at the beginning of a traceback line +setlocal errorformat+=E\ %#File\ \"%f\"\\,\ line\ %l%.%# + +" Python tracebacks (unittest + doctest output) {{{2 + +" This collapses the entire traceback into just the last file+lineno, +" which is convenient when you want to jump to the line that failed (and not +" the top-level entry point), but it makes it impossible to see the full +" traceback, which sucks. +""setlocal errorformat+= +"" \File\ \"%f\"\\,\ line\ %l%.%#, +"" \%C\ %.%#, +"" \%-A\ \ File\ \"unittest%.py\"\\,\ line\ %.%#, +"" \%-A\ \ File\ \"%f\"\\,\ line\ 0%.%#, +"" \%A\ \ File\ \"%f\"\\,\ line\ %l%.%#, +"" \%Z%[%^\ ]%\\@=%m +setlocal errorformat+=File\ \"%f\"\\,\ line\ %l\\,%#%m + +exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"') + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/compiler/spotbugs.vim b/runtime/compiler/spotbugs.vim new file mode 100644 index 0000000000..72a5084976 --- /dev/null +++ b/runtime/compiler/spotbugs.vim @@ -0,0 +1,189 @@ +" Vim compiler file +" Compiler: Spotbugs (Java static checker; needs javac compiled classes) +" Maintainer: @konfekt and @zzzyxwvut +" Last Change: 2024 Nov 27 + +if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9 + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +" Unfortunately Spotbugs does not output absolute paths, so you need to +" pass the directory of the files being checked as `-sourcepath` parameter. +" The regex, auxpath and glob try to include all dependent classes of the +" current buffer. See https://github.com/spotbugs/spotbugs/issues/856 + +" FIXME: When "search()" is used with the "e" flag, it makes no _further_ +" progress after claiming an EOL match (i.e. "\_" or "\n", but not "$"). +" XXX: Omit anonymous class declarations +let s:keywords = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\|package\)\%(\s\|$\)' +let s:type_names = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\)\s*\(\K\k*\)\>' +" Capture ";" for counting a class file directory (see s:package_dir_heads below) +let s:package_names = '\C\<package\s*\(\K\%(\k*\.\=\)\+;\)' +let s:package = '' + +if has('syntax') && exists('g:syntax_on') && exists('b:current_syntax') && + \ b:current_syntax == 'java' && hlexists('javaClassDecl') + + function! s:GetDeclaredTypeNames() abort + if bufname() =~# '\<\%(module\|package\)-info\.java\=$' + return [expand('%:t:r')] + endif + defer execute('silent! normal! g``') + call cursor(1, 1) + let type_names = [] + let lnum = search(s:keywords, 'eW') + while lnum > 0 + let name_attr = synIDattr(synID(lnum, (col('.') - 1), 0), 'name') + if name_attr ==# 'javaClassDecl' + let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:type_names) + if !empty(tokens) | call add(type_names, tokens[1]) | endif + elseif name_attr ==# 'javaExternal' + let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:package_names) + if !empty(tokens) | let s:package = tokens[1] | endif + endif + let lnum = search(s:keywords, 'eW') + endwhile + return type_names + endfunction + +else + function! s:GetDeclaredTypeNames() abort + if bufname() =~# '\<\%(module\|package\)-info\.java\=$' + return [expand('%:t:r')] + endif + " Undo the unsetting of &hls, see below + if &hls + defer execute('set hls') + endif + " Possibly restore the current values for registers '"' and "y", see below + defer call('setreg', ['"', getreg('"'), getregtype('"')]) + defer call('setreg', ['y', getreg('y'), getregtype('y')]) + defer execute('silent bwipeout') + " Copy buffer contents for modification + silent %y y + new + " Apply ":help scratch-buffer" effects and match "$" in Java (generated) + " type names (see s:type_names) + setlocal iskeyword+=$ buftype=nofile bufhidden=hide noswapfile nohls + 0put y + " Discard text blocks and strings + silent keeppatterns %s/\\\@<!"""\_.\{-}\\\@<!"""\|\\"//ge + silent keeppatterns %s/".*"//ge + " Discard comments + silent keeppatterns %s/\/\/.\+$//ge + silent keeppatterns %s/\/\*\_.\{-}\*\///ge + call cursor(1, 1) + let type_names = [] + let lnum = search(s:keywords, 'eW') + while lnum > 0 + let line = getline(lnum) + if line =~# '\<package\>' + let tokens = matchlist(line..getline(lnum + 1), s:package_names) + if !empty(tokens) | let s:package = tokens[1] | endif + else + let tokens = matchlist(line..getline(lnum + 1), s:type_names) + if !empty(tokens) | call add(type_names, tokens[1]) | endif + endif + let lnum = search(s:keywords, 'eW') + endwhile + return type_names + endfunction +endif + +if has('win32') + + function! s:GlobClassFiles(src_type_name) abort + return glob(a:src_type_name..'$*.class', 1, 1) + endfunction + +else + function! s:GlobClassFiles(src_type_name) abort + return glob(a:src_type_name..'\$*.class', 1, 1) + endfunction +endif + +if exists('g:spotbugs_properties') && + \ (has_key(g:spotbugs_properties, 'sourceDirPath') && + \ has_key(g:spotbugs_properties, 'classDirPath')) || + \ (has_key(g:spotbugs_properties, 'testSourceDirPath') && + \ has_key(g:spotbugs_properties, 'testClassDirPath')) + +function! s:FindClassFiles(src_type_name) abort + let class_files = [] + " Match pairwise the components of source and class pathnames + for [src_dir, bin_dir] in filter([ + \ [get(g:spotbugs_properties, 'sourceDirPath', ''), + \ get(g:spotbugs_properties, 'classDirPath', '')], + \ [get(g:spotbugs_properties, 'testSourceDirPath', ''), + \ get(g:spotbugs_properties, 'testClassDirPath', '')]], + \ '!(empty(v:val[0]) || empty(v:val[1]))') + " Since only the rightmost "src" is sought, while there can be any number of + " such filenames, no "fnamemodify(a:src_type_name, ':p:s?src?bin?')" is used + let tail_idx = strridx(a:src_type_name, src_dir) + " No such directory or no such inner type (i.e. without "$") + if tail_idx < 0 | continue | endif + " Substitute "bin_dir" for the rightmost "src_dir" + let candidate_type_name = strpart(a:src_type_name, 0, tail_idx).. + \ bin_dir.. + \ strpart(a:src_type_name, (tail_idx + strlen(src_dir))) + for candidate in insert(s:GlobClassFiles(candidate_type_name), + \ candidate_type_name..'.class') + if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif + endfor + if !empty(class_files) | break | endif + endfor + return class_files +endfunction + +else +function! s:FindClassFiles(src_type_name) abort + let class_files = [] + for candidate in insert(s:GlobClassFiles(a:src_type_name), + \ a:src_type_name..'.class') + if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif + endfor + return class_files +endfunction +endif + +function! s:CollectClassFiles() abort + " Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'" + let pathname = expand('%:p') + let tail_idx = strridx(pathname, expand('%:t')) + let src_pathname = strpart(pathname, 0, tail_idx) + let all_class_files = [] + " Get all type names in the current buffer and let the filename globbing + " discover inner type names from arbitrary type names + for type_name in s:GetDeclaredTypeNames() + call extend(all_class_files, s:FindClassFiles(src_pathname..type_name)) + endfor + return all_class_files +endfunction + +" Expose class files for removal etc. +let b:spotbugs_class_files = s:CollectClassFiles() +let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g')))) +let g:current_compiler = 'spotbugs' +" CompilerSet makeprg=spotbugs +let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '.. + \ get(b:, 'spotbugs_makeprg_params', get(g:, 'spotbugs_makeprg_params', '-workHard -experimental')).. + \ ' -textui -emacs -auxclasspath %:p'..s:package_dir_heads..':S -sourcepath %:p'..s:package_dir_heads..':S '.. + \ join(b:spotbugs_class_files, ' ') +" Emacs expects doubled line numbers +setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m + +" " This compiler is meant to be used for a single buffer only +" exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"') +" exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"') + +delfunction s:CollectClassFiles +delfunction s:FindClassFiles +delfunction s:GlobClassFiles +delfunction s:GetDeclaredTypeNames +let &cpo = s:cpo_save +unlet s:package_dir_heads s:package s:package_names s:type_names s:keywords s:cpo_save + +" vim: set foldmethod=syntax shiftwidth=2 expandtab: diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 9ccc3102b6..eaa3681caa 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -163,6 +163,33 @@ show a sign for the highest severity diagnostic on a given line: >lua } < + *diagnostic-loclist-example* +Whenever the |location-list| is opened, the following `show` handler will show +the most recent diagnostics: >lua + + vim.diagnostic.handlers.loclist = { + show = function(_, _, _, opts) + -- Generally don't want it to open on every update + opts.loclist.open = opts.loclist.open or false + local winid = vim.api.nvim_get_current_win() + vim.diagnostic.setloclist(opts.loclist) + vim.api.nvim_set_current_win(winid) + end + } +< + +The handler accepts the same options as |vim.diagnostic.setloclist()| and can be +configured using |vim.diagnostic.config()|: >lua + + -- Open the location list on every diagnostic change (warnings/errors only). + vim.diagnostic.config({ + loclist = { + open = true, + severity = { min = vim.diagnostic.severity.WARN }, + } + }) +< + ============================================================================== HIGHLIGHTS *diagnostic-highlights* diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 350edc068f..f7157df0f2 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -204,6 +204,7 @@ won't run if your server doesn't support them. - `'textDocument/diagnostic'` - `'textDocument/documentHighlight'` - `'textDocument/documentSymbol'` +- `'textDocument/foldingRange'` - `'textDocument/formatting'` - `'textDocument/hover'` - `'textDocument/implementation'` @@ -697,6 +698,35 @@ commands *vim.lsp.commands* The second argument is the `ctx` of |lsp-handler| +foldclose({kind}, {winid}) *vim.lsp.foldclose()* + Close all {kind} of folds in the the window with {winid}. + + To automatically fold imports when opening a file, you can use an autocmd: >lua + vim.api.nvim_create_autocmd('LspNotify', { + callback = function(args) + if args.data.method == 'textDocument/didOpen' then + vim.lsp.foldclose('imports', vim.fn.bufwinid(args.buf)) + end + end, + }) +< + + Parameters: ~ + • {kind} (`lsp.FoldingRangeKind`) Kind to close, one of "comment", + "imports" or "region". + • {winid} (`integer?`) Defaults to the current window. + +foldexpr({lnum}) *vim.lsp.foldexpr()* + Provides an interface between the built-in client and a `foldexpr` + function. + + Parameters: ~ + • {lnum} (`integer`) line number + +foldtext() *vim.lsp.foldtext()* + Provides a `foldtext` function that shows the `collapsedText` retrieved, + defaults to the first folded line if `collapsedText` is not provided. + formatexpr({opts}) *vim.lsp.formatexpr()* Provides an interface between the built-in client and a `formatexpr` function. @@ -975,7 +1005,7 @@ Lua module: vim.lsp.client *lsp-client* • {dynamic_capabilities} (`lsp.DynamicCapabilities`) • {request} (`fun(self: vim.lsp.Client, method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer?`) See |Client:request()|. - • {request_sync} (`fun(self: vim.lsp.Client, method: string, params: table, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError?, result:any}?, string?`) + • {request_sync} (`fun(self: vim.lsp.Client, method: string, params: table, timeout_ms: integer?, bufnr: integer?): {err: lsp.ResponseError?, result:any}?, string?`) See |Client:request_sync()|. • {notify} (`fun(self: vim.lsp.Client, method: string, params: table?): boolean`) See |Client:notify()|. @@ -1179,7 +1209,7 @@ Client:request({method}, {params}, {handler}, {bufnr}) • {method} (`string`) LSP method name. • {params} (`table?`) LSP request params. • {handler} (`lsp.Handler?`) Response |lsp-handler| for this method. - • {bufnr} (`integer?`) Buffer handle. 0 for current (default). + • {bufnr} (`integer?`) (default: 0) Buffer handle, or 0 for current. Return (multiple): ~ (`boolean`) status indicates whether the request was successful. If it @@ -1201,7 +1231,8 @@ Client:request_sync({method}, {params}, {timeout_ms}, {bufnr}) • {params} (`table`) LSP request params. • {timeout_ms} (`integer?`) Maximum time in milliseconds to wait for a result. Defaults to 1000 - • {bufnr} (`integer`) Buffer handle (0 for current). + • {bufnr} (`integer?`) (default: 0) Buffer handle, or 0 for + current. Return (multiple): ~ (`{err: lsp.ResponseError?, result:any}?`) `result` and `err` from the @@ -1385,7 +1416,7 @@ format({opts}) *vim.lsp.buf.format()* predicate are included. Example: >lua -- Never request typescript-language-server for formatting vim.lsp.buf.format { - filter = function(client) return client.name ~= "tsserver" end + filter = function(client) return client.name ~= "ts_ls" end } < • {async}? (`boolean`, default: false) If true the method @@ -1448,7 +1479,7 @@ references({context}, {opts}) *vim.lsp.buf.references()* window. Parameters: ~ - • {context} (`table?`) Context for the request + • {context} (`lsp.ReferenceContext?`) Context for the request • {opts} (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|. See also: ~ diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 4d4a51872a..c3dddaf888 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2485,22 +2485,24 @@ vim.validate({name}, {value}, {validator}, {optional}, {message}) ============================================================================== Lua module: vim.loader *vim.loader* -vim.loader.disable() *vim.loader.disable()* +vim.loader.enable({enable}) *vim.loader.enable()* WARNING: This feature is experimental/unstable. - Disables the experimental Lua module loader: - • removes the loaders - • adds the default Nvim loader - -vim.loader.enable() *vim.loader.enable()* - WARNING: This feature is experimental/unstable. + Enables or disables the experimental Lua module loader: - Enables the experimental Lua module loader: - • overrides loadfile + Enable (`enable=true`): + • overrides |loadfile()| • adds the Lua loader using the byte-compilation cache • adds the libs loader • removes the default Nvim loader + Disable (`enable=false`): + • removes the loaders + • adds the default Nvim loader + + Parameters: ~ + • {enable} (`boolean?`) true/nil to enable, false to disable + vim.loader.find({modname}, {opts}) *vim.loader.find()* WARNING: This feature is experimental/unstable. @@ -2924,6 +2926,17 @@ vim.keymap.set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* ============================================================================== Lua module: vim.fs *vim.fs* + + *vim.fs.exists()* +Use |uv.fs_stat()| to check a file's type, and whether it exists. + +Example: >lua + if vim.uv.fs_stat(file) then + vim.print("file exists") + end +< + + vim.fs.basename({file}) *vim.fs.basename()* Return the basename of the given path diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 58ab7ef44c..ad3f2c0a6a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -11,13 +11,17 @@ For changes in the previous release, see |news-0.10|. Type |gO| to see the table of contents. ============================================================================== -BREAKING CHANGES IN HEAD *news-breaking-dev* +BREAKING CHANGES IN HEAD OR EXPERIMENTAL *news-breaking-dev* ====== Remove this section before release. ====== The following changes to UNRELEASED features were made during the development cycle (Nvim HEAD, the "master" branch). +EXPERIMENTS + +• Removed `vim.loader.disable()`. Use `vim.loader.enable(false)` instead. + OPTIONS • 'jumpoptions' flag "unload" has been renamed to "clean". @@ -226,6 +230,8 @@ LSP • |vim.lsp.buf.hover()| now highlights hover ranges using the |hl-LspReferenceTarget| highlight group. • Functions in |vim.lsp.Client| can now be called as methods. +• Implemented LSP folding: |vim.lsp.foldexpr()| + https://microsoft.github.io/language-server-protocol/specification/#textDocument_foldingRange LUA @@ -280,6 +286,8 @@ TUI :lua =vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan) • |log| messages written by the builtin UI client (TUI, |--remote-ui|) are now prefixed with "ui" instead of "?". +• The TUI will re-query the terminal's background color when a theme update + notification is received and Nvim will update 'background' accordingly. UI diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ea56633c77..c5f21c64a2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5917,6 +5917,7 @@ A jump table for the options with a short description can be found at |Q_op|. All fields except the {item} are optional. A single percent sign can be given as "%%". + *stl-%!* When the option starts with "%!" then it is used as an expression, evaluated and the result is used as the option value. Example: >vim set statusline=%!MyStatusLine() diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index b3399b2766..a291c0277d 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -1317,9 +1317,117 @@ g:compiler_gcc_ignore_unmatched_lines JAVAC *compiler-javac* Commonly used compiler options can be added to 'makeprg' by setting the -g:javac_makeprg_params variable. For example: > +b/g:javac_makeprg_params variable. For example: > + let g:javac_makeprg_params = "-Xlint:all -encoding utf-8" -< + +MAVEN *compiler-maven* + +Commonly used compiler options can be added to 'makeprg' by setting the +b/g:maven_makeprg_params variable. For example: > + + let g:maven_makeprg_params = "-DskipTests -U -X" + +SPOTBUGS *compiler-spotbugs* + +SpotBugs is a static analysis tool that can be used to find bugs in Java. +It scans the Java bytecode of all classes in the currently open buffer. +(Therefore, `:compiler! spotbugs` is not supported.) + +Commonly used compiler options can be added to 'makeprg' by setting the +"b:" or "g:spotbugs_makeprg_params" variable. For example: > + + let b:spotbugs_makeprg_params = "-longBugCodes -effort:max -low" + +The global default is "-workHard -experimental". + +By default, the class files are searched in the directory where the source +files are placed. However, typical Java projects use distinct directories +for source files and class files. To make both known to SpotBugs, assign +their paths (distinct and relative to their common root directory) to the +following properties (using the example of a common Maven project): > + + let g:spotbugs_properties = { + \ 'sourceDirPath': 'src/main/java', + \ 'classDirPath': 'target/classes', + \ 'testSourceDirPath': 'src/test/java', + \ 'testClassDirPath': 'target/test-classes', + \ } + +Note that values for the path keys describe only for SpotBugs where to look +for files; refer to the documentation for particular compiler plugins for more +information. + +The default pre- and post-compiler actions are provided for Ant, Maven, and +Javac compiler plugins and can be selected by assigning the name of a compiler +plugin to the "compiler" key: > + + let g:spotbugs_properties = { + \ 'compiler': 'maven', + \ } + +This single setting is essentially equivalent to all the settings below, with +the exception made for the "PreCompilerAction" and "PreCompilerTestAction" +values: their listed |Funcref|s will obtain no-op implementations whereas the +implicit Funcrefs of the "compiler" key will obtain the requested defaults if +available. > + + let g:spotbugs_properties = { + \ 'PreCompilerAction': + \ function('spotbugs#DefaultPreCompilerAction'), + \ 'PreCompilerTestAction': + \ function('spotbugs#DefaultPreCompilerTestAction'), + \ 'PostCompilerAction': + \ function('spotbugs#DefaultPostCompilerAction'), + \ 'sourceDirPath': 'src/main/java', + \ 'classDirPath': 'target/classes', + \ 'testSourceDirPath': 'src/test/java', + \ 'testClassDirPath': 'target/test-classes', + \ } + +With default actions, the compiler of choice will attempt to rebuild the class +files for the buffer (and possibly for the whole project) as soon as a Java +syntax file is loaded; then, `spotbugs` will attempt to analyze the quality of +the compilation unit of the buffer. + +When default actions are not suited to a desired workflow, consider writing +arbitrary functions yourself and matching their |Funcref|s to the supported +keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction". + +The next example re-implements the default pre-compiler actions for a Maven +project and requests other default Maven settings with the "compiler" entry: > + + function! MavenPreCompilerAction() abort + call spotbugs#DeleteClassFiles() + compiler maven + make compile + endfunction + + function! MavenPreCompilerTestAction() abort + call spotbugs#DeleteClassFiles() + compiler maven + make test-compile + endfunction + + let g:spotbugs_properties = { + \ 'compiler': 'maven', + \ 'PreCompilerAction': + \ function('MavenPreCompilerAction'), + \ 'PreCompilerTestAction': + \ function('MavenPreCompilerTestAction'), + \ } + +Note that all entered custom settings will take precedence over the matching +default settings in "g:spotbugs_properties". + +The "g:spotbugs_properties" variable is consulted by the Java filetype plugin +(|ft-java-plugin|) to arrange for the described automation, and, therefore, it +must be defined before |FileType| events can take place for the buffers loaded +with Java source files. It could, for example, be set in a project-local +|vimrc| loaded by [0]. + +[0] https://github.com/MarcWeber/vim-addon-local-vimrc/ + GNU MAKE *compiler-make* Since the default make program is "make", the compiler plugin for make, @@ -1409,6 +1517,13 @@ Useful values for the 'makeprg' options therefore are: setlocal makeprg=./alltests.py " Run a testsuite setlocal makeprg=python\ %:S " Run a single testcase +PYTEST COMPILER *compiler-pytest* +Commonly used compiler options can be added to 'makeprg' by setting the +b/g:pytest_makeprg_params variable. For example: > + + let b:pytest_makeprg_params = "--verbose --no-summary --disable-warnings" + +The global default is "--tb=short --quiet"; Python warnings are suppressed. TEX COMPILER *compiler-tex* diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim index 55b358374f..6e12fe2fe5 100644 --- a/runtime/ftplugin/java.vim +++ b/runtime/ftplugin/java.vim @@ -3,7 +3,7 @@ " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Former Maintainer: Dan Sharp " Repository: https://github.com/zzzyxwvut/java-vim.git -" Last Change: 2024 Sep 26 +" Last Change: 2024 Nov 24 " 2024 Jan 14 by Vim Project (browsefilter) " 2024 May 23 by Riley Bruins <ribru17@gmail.com> ('commentstring') @@ -90,10 +90,127 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") endif endif +" The support for pre- and post-compiler actions for SpotBugs. +if exists("g:spotbugs_properties") && has_key(g:spotbugs_properties, 'compiler') + try + let spotbugs#compiler = g:spotbugs_properties.compiler + let g:spotbugs_properties = extend( + \ spotbugs#DefaultProperties(), + \ g:spotbugs_properties, + \ 'force') + catch + echomsg v:errmsg + finally + call remove(g:spotbugs_properties, 'compiler') + endtry +endif + +if exists("g:spotbugs_properties") && + \ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim') + let s:request = 0 + + if has_key(g:spotbugs_properties, 'PreCompilerAction') + let s:dispatcher = 'call g:spotbugs_properties.PreCompilerAction() | ' + let s:request += 1 + endif + + if has_key(g:spotbugs_properties, 'PreCompilerTestAction') + let s:dispatcher = 'call g:spotbugs_properties.PreCompilerTestAction() | ' + let s:request += 2 + endif + + if has_key(g:spotbugs_properties, 'PostCompilerAction') + let s:request += 4 + endif + + if (s:request == 3 || s:request == 7) && + \ has_key(g:spotbugs_properties, 'sourceDirPath') && + \ has_key(g:spotbugs_properties, 'testSourceDirPath') + function! s:DispatchAction(path_action_pairs) abort + let name = expand('%:p') + + for [path, Action] in a:path_action_pairs + if name =~# (path . '.\{-}\.java\=$') + call Action() + break + endif + endfor + endfunction + + let s:dispatcher = printf('call s:DispatchAction(%s) | ', + \ string([[g:spotbugs_properties.sourceDirPath, + \ g:spotbugs_properties.PreCompilerAction], + \ [g:spotbugs_properties.testSourceDirPath, + \ g:spotbugs_properties.PreCompilerTestAction]])) + endif + + if s:request + if exists("b:spotbugs_syntax_once") + let s:actions = [{'event': 'BufWritePost'}] + else + " XXX: Handle multiple FileType events when vimrc contains more + " than one filetype setting for the language, e.g.: + " :filetype plugin indent on + " :autocmd BufRead,BufNewFile *.java setlocal filetype=java ... + " XXX: DO NOT ADD b:spotbugs_syntax_once TO b:undo_ftplugin ! + let b:spotbugs_syntax_once = 1 + let s:actions = [{ + \ 'event': 'Syntax', + \ 'once': 1, + \ }, { + \ 'event': 'BufWritePost', + \ }] + endif + + for s:idx in range(len(s:actions)) + if s:request == 7 || s:request == 6 || s:request == 5 + let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs | ' . + \ 'call g:spotbugs_properties.PostCompilerAction()' + elseif s:request == 4 + let s:actions[s:idx].cmd = 'compiler spotbugs | ' . + \ 'call g:spotbugs_properties.PostCompilerAction()' + elseif s:request == 3 || s:request == 2 || s:request == 1 + let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs' + else + let s:actions[s:idx].cmd = '' + endif + endfor + + if !exists("#java_spotbugs") + augroup java_spotbugs + augroup END + endif + + " The events are defined in s:actions. + silent! autocmd! java_spotbugs BufWritePost <buffer> + silent! autocmd! java_spotbugs Syntax <buffer> + + for s:action in s:actions + execute printf('autocmd java_spotbugs %s <buffer> %s', + \ s:action.event, + \ s:action.cmd . (has_key(s:action, 'once') + \ ? printf(' | autocmd! java_spotbugs %s <buffer>', + \ s:action.event) + \ : '')) + endfor + + unlet! s:action s:actions s:idx s:dispatcher + endif + + unlet s:request +endif + +function! JavaFileTypeCleanUp() abort + setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr< + unlet! b:browsefilter + + " The concatenated removals may be misparsed as a BufWritePost autocmd. + silent! autocmd! java_spotbugs BufWritePost <buffer> + silent! autocmd! java_spotbugs Syntax <buffer> +endfunction + " Undo the stuff we changed. -let b:undo_ftplugin = "setlocal suffixes< suffixesadd<" . - \ " formatoptions< comments< commentstring< path< includeexpr<" . - \ " | unlet! b:browsefilter" +let b:undo_ftplugin = 'call JavaFileTypeCleanUp() | delfunction JavaFileTypeCleanUp' " See ":help vim9-mix". if !has("vim9script") @@ -114,6 +231,19 @@ if exists("s:zip_func_upgradable") setlocal suffixesadd< endif +if exists("*s:DispatchAction") + def! s:DispatchAction(path_action_pairs: list<list<any>>) + const name: string = expand('%:p') + + for [path: string, Action: func: any] in path_action_pairs + if name =~# (path .. '.\{-}\.java\=$') + Action() + break + endif + endfor + enddef +endif + " Restore the saved compatibility options. let &cpo = s:save_cpo unlet s:save_cpo diff --git a/runtime/ftplugin/python.vim b/runtime/ftplugin/python.vim index c000296726..6f20468896 100644 --- a/runtime/ftplugin/python.vim +++ b/runtime/ftplugin/python.vim @@ -3,8 +3,9 @@ " Maintainer: Tom Picton <tom@tompicton.com> " Previous Maintainer: James Sully <sullyj3@gmail.com> " Previous Maintainer: Johannes Zellner <johannes@zellner.org> -" Last Change: 2024/05/13 -" https://github.com/tpict/vim-ftplugin-python +" Repository: https://github.com/tpict/vim-ftplugin-python +" Last Change: 2024/05/13 +" 2024 Nov 30 use pytest compiler (#16130) if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 @@ -134,6 +135,11 @@ elseif executable('python') setlocal keywordprg=python\ -m\ pydoc endif +if expand('%:t') =~# '\v^test_.*\.py$|_test\.py$' && executable('pytest') + compiler pytest + let &l:makeprg .= ' %:S' +endif + " Script for filetype switching to undo the local stuff we may have changed let b:undo_ftplugin = 'setlocal cinkeys<' \ . '|setlocal comments<' @@ -148,6 +154,7 @@ let b:undo_ftplugin = 'setlocal cinkeys<' \ . '|setlocal softtabstop<' \ . '|setlocal suffixesadd<' \ . '|setlocal tabstop<' + \ . '|setlocal makeprg<' \ . '|silent! nunmap <buffer> [M' \ . '|silent! nunmap <buffer> [[' \ . '|silent! nunmap <buffer> []' diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 2687f34302..6583cf48b3 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -546,8 +546,9 @@ do --- --- @param option string Option name --- @param value any Option value - local function setoption(option, value) - if vim.api.nvim_get_option_info2(option, {}).was_set then + --- @param force boolean? Always set the value, even if already set + local function setoption(option, value, force) + if not force and vim.api.nvim_get_option_info2(option, {}).was_set then -- Don't do anything if option is already set return end @@ -563,7 +564,7 @@ do once = true, nested = true, callback = function() - setoption(option, value) + setoption(option, value, force) end, }) end @@ -645,11 +646,15 @@ do return nil, nil, nil end - local timer = assert(vim.uv.new_timer()) - + -- This autocommand updates the value of 'background' anytime we receive + -- an OSC 11 response from the terminal emulator. If the user has set + -- 'background' explictly then we will delete this autocommand, + -- effectively disabling automatic background setting. + local force = false local id = vim.api.nvim_create_autocmd('TermResponse', { group = group, nested = true, + desc = "Update the value of 'background' automatically based on the terminal emulator's background color", callback = function(args) local resp = args.data ---@type string local r, g, b = parseosc11(resp) @@ -661,27 +666,33 @@ do if rr and gg and bb then local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) local bg = luminance < 0.5 and 'dark' or 'light' - setoption('background', bg) + setoption('background', bg, force) + + -- On the first query response, don't force setting the option in + -- case the user has already set it manually. If they have, then + -- this autocommand will be deleted. If they haven't, then we do + -- want to force setting the option to override the value set by + -- this autocommand. + if not force then + force = true + end end + end + end, + }) - return true + vim.api.nvim_create_autocmd('VimEnter', { + group = group, + nested = true, + once = true, + callback = function() + if vim.api.nvim_get_option_info2('background', {}).was_set then + vim.api.nvim_del_autocmd(id) end end, }) io.stdout:write('\027]11;?\007') - - timer:start(1000, 0, function() - -- Delete the autocommand if no response was received - vim.schedule(function() - -- Suppress error if autocommand has already been deleted - pcall(vim.api.nvim_del_autocmd, id) - end) - - if not timer:is_closing() then - timer:close() - end - end) end --- If the TUI (term_has_truecolor) was able to determine that the host diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index c635d9bd3b..247b464a70 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6311,6 +6311,7 @@ vim.wo.stc = vim.wo.statuscolumn --- All fields except the {item} are optional. A single percent sign can --- be given as "%%". --- +--- *stl-%!* --- When the option starts with "%!" then it is used as an expression, --- evaluated and the result is used as the option value. Example: --- diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index aa566973b6..5d771c30e9 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -962,7 +962,9 @@ local extension = { purs = 'purescript', arr = 'pyret', pxd = 'pyrex', + pxi = 'pyrex', pyx = 'pyrex', + ['pyx+'] = 'pyrex', pyw = 'python', py = 'python', pyi = 'python', diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index d91eeaf02f..2f007d97c3 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,3 +1,15 @@ +--- @brief <pre>help +--- *vim.fs.exists()* +--- Use |uv.fs_stat()| to check a file's type, and whether it exists. +--- +--- Example: +--- +--- >lua +--- if vim.uv.fs_stat(file) then +--- vim.print("file exists") +--- end +--- < + local uv = vim.uv local M = {} diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 0cce0ab21d..71d0188128 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -399,50 +399,51 @@ function M.reset(path) end end ---- Enables the experimental Lua module loader: ---- * overrides loadfile +--- Enables or disables the experimental Lua module loader: +--- +--- Enable (`enable=true`): +--- * overrides |loadfile()| --- * adds the Lua loader using the byte-compilation cache --- * adds the libs loader --- * removes the default Nvim loader --- ---- @since 0 -function M.enable() - if M.enabled then - return - end - M.enabled = true - vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') - _G.loadfile = loadfile_cached - -- add Lua loader - table.insert(loaders, 2, loader_cached) - -- add libs loader - table.insert(loaders, 3, loader_lib_cached) - -- remove Nvim loader - for l, loader in ipairs(loaders) do - if loader == vim._load_package then - table.remove(loaders, l) - break - end - end -end - ---- Disables the experimental Lua module loader: +--- Disable (`enable=false`): --- * removes the loaders --- * adds the default Nvim loader --- --- @since 0 -function M.disable() - if not M.enabled then +--- +--- @param enable? (boolean) true/nil to enable, false to disable +function M.enable(enable) + enable = enable == nil and true or enable + if enable == M.enabled then return end - M.enabled = false - _G.loadfile = _loadfile - for l, loader in ipairs(loaders) do - if loader == loader_cached or loader == loader_lib_cached then - table.remove(loaders, l) + M.enabled = enable + + if enable then + vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') + _G.loadfile = loadfile_cached + -- add Lua loader + table.insert(loaders, 2, loader_cached) + -- add libs loader + table.insert(loaders, 3, loader_lib_cached) + -- remove Nvim loader + for l, loader in ipairs(loaders) do + if loader == vim._load_package then + table.remove(loaders, l) + break + end + end + else + _G.loadfile = _loadfile + for l, loader in ipairs(loaders) do + if loader == loader_cached or loader == loader_lib_cached then + table.remove(loaders, l) + end end + table.insert(loaders, 2, vim._load_package) end - table.insert(loaders, 2, vim._load_package) end --- Tracks the time spent in a function diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 6d29c9e4df..a3791e15c3 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -3,6 +3,7 @@ local validate = vim.validate local lsp = vim._defer_require('vim.lsp', { _changetracking = ..., --- @module 'vim.lsp._changetracking' + _folding_range = ..., --- @module 'vim.lsp._folding_range' _snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar' _tagfunc = ..., --- @module 'vim.lsp._tagfunc' _watchfiles = ..., --- @module 'vim.lsp._watchfiles' @@ -57,6 +58,7 @@ lsp._request_name_to_capability = { [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' }, [ms.textDocument_documentLink] = { 'documentLinkProvider' }, [ms.textDocument_documentSymbol] = { 'documentSymbolProvider' }, + [ms.textDocument_foldingRange] = { 'foldingRangeProvider' }, [ms.textDocument_formatting] = { 'documentFormattingProvider' }, [ms.textDocument_hover] = { 'hoverProvider' }, [ms.textDocument_implementation] = { 'implementationProvider' }, @@ -1094,6 +1096,38 @@ function lsp.tagfunc(pattern, flags) return vim.lsp._tagfunc(pattern, flags) end +--- Provides an interface between the built-in client and a `foldexpr` function. +---@param lnum integer line number +function lsp.foldexpr(lnum) + return vim.lsp._folding_range.foldexpr(lnum) +end + +--- Close all {kind} of folds in the the window with {winid}. +--- +--- To automatically fold imports when opening a file, you can use an autocmd: +--- +--- ```lua +--- vim.api.nvim_create_autocmd('LspNotify', { +--- callback = function(args) +--- if args.data.method == 'textDocument/didOpen' then +--- vim.lsp.foldclose('imports', vim.fn.bufwinid(args.buf)) +--- end +--- end, +--- }) +--- ``` +--- +---@param kind lsp.FoldingRangeKind Kind to close, one of "comment", "imports" or "region". +---@param winid? integer Defaults to the current window. +function lsp.foldclose(kind, winid) + return vim.lsp._folding_range.foldclose(kind, winid) +end + +--- Provides a `foldtext` function that shows the `collapsedText` retrieved, +--- defaults to the first folded line if `collapsedText` is not provided. +function lsp.foldtext() + return vim.lsp._folding_range.foldtext() +end + ---Checks whether a client is stopped. --- ---@param client_id (integer) diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua new file mode 100644 index 0000000000..6a445017a3 --- /dev/null +++ b/runtime/lua/vim/lsp/_folding_range.lua @@ -0,0 +1,371 @@ +local util = require('vim.lsp.util') +local log = require('vim.lsp.log') +local ms = require('vim.lsp.protocol').Methods +local api = vim.api + +local M = {} + +---@class (private) vim.lsp.folding_range.BufState +--- +---@field version? integer +--- +--- Never use this directly, `renew()` the cached foldinfo +--- then use on demand via `row_*` fields. +--- +--- Index In the form of client_id -> ranges +---@field client_ranges table<integer, lsp.FoldingRange[]?> +--- +--- Index in the form of row -> [foldlevel, mark] +---@field row_level table<integer, [integer, ">" | "<"?]?> +--- +--- Index in the form of start_row -> kinds +---@field row_kinds table<integer, table<lsp.FoldingRangeKind, true?>?>> +--- +--- Index in the form of start_row -> collapsed_text +---@field row_text table<integer, string?> + +---@type table<integer, vim.lsp.folding_range.BufState?> +local bufstates = {} + +--- Renew the cached foldinfo in the buffer. +---@param bufnr integer +local function renew(bufnr) + local bufstate = assert(bufstates[bufnr]) + + ---@type table<integer, [integer, ">" | "<"?]?> + local row_level = {} + ---@type table<integer, table<lsp.FoldingRangeKind, true?>?>> + local row_kinds = {} + ---@type table<integer, string?> + local row_text = {} + + for _, ranges in pairs(bufstate.client_ranges) do + for _, range in ipairs(ranges) do + local start_row = range.startLine + local end_row = range.endLine + -- Adding folds within a single line is not supported by Nvim. + if start_row ~= end_row then + row_text[start_row] = range.collapsedText + + local kind = range.kind + if kind then + local kinds = row_kinds[start_row] or {} + kinds[kind] = true + row_kinds[start_row] = kinds + end + + for row = start_row, end_row do + local level = row_level[row] or { 0 } + level[1] = level[1] + 1 + row_level[row] = level + end + row_level[start_row][2] = '>' + row_level[end_row][2] = '<' + end + end + end + + bufstate.row_level = row_level + bufstate.row_kinds = row_kinds + bufstate.row_text = row_text +end + +--- Renew the cached foldinfo then force `foldexpr()` to be re-evaluated, +--- without opening folds. +---@param bufnr integer +local function foldupdate(bufnr) + renew(bufnr) + for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do + local wininfo = vim.fn.getwininfo(winid)[1] + if wininfo and wininfo.tabnr == vim.fn.tabpagenr() then + if vim.wo[winid].foldmethod == 'expr' then + vim._foldupdate(winid, 0, api.nvim_buf_line_count(bufnr)) + end + end + end +end + +--- Whether `foldupdate()` is scheduled for the buffer with `bufnr`. +--- +--- Index in the form of bufnr -> true? +---@type table<integer, true?> +local scheduled_foldupdate = {} + +--- Schedule `foldupdate()` after leaving insert mode. +---@param bufnr integer +local function schedule_foldupdate(bufnr) + if not scheduled_foldupdate[bufnr] then + scheduled_foldupdate[bufnr] = true + api.nvim_create_autocmd('InsertLeave', { + buffer = bufnr, + once = true, + callback = function() + foldupdate(bufnr) + scheduled_foldupdate[bufnr] = nil + end, + }) + end +end + +---@param results table<integer,{err: lsp.ResponseError?, result: lsp.FoldingRange[]?}> +---@type lsp.MultiHandler +local function multi_handler(results, ctx) + local bufnr = assert(ctx.bufnr) + -- Handling responses from outdated buffer only causes performance overhead. + if util.buf_versions[bufnr] ~= ctx.version then + return + end + + local bufstate = assert(bufstates[bufnr]) + for client_id, result in pairs(results) do + if result.err then + log.error(result.err) + else + bufstate.client_ranges[client_id] = result.result + end + end + bufstate.version = ctx.version + + if api.nvim_get_mode().mode:match('^i') then + -- `foldUpdate()` is guarded in insert mode. + schedule_foldupdate(bufnr) + else + foldupdate(bufnr) + end +end + +---@param result lsp.FoldingRange[]? +---@type lsp.Handler +local function handler(err, result, ctx) + multi_handler({ [ctx.client_id] = { err = err, result = result } }, ctx) +end + +--- Request `textDocument/foldingRange` from the server. +--- `foldupdate()` is scheduled once after the request is completed. +---@param bufnr integer +---@param client? vim.lsp.Client The client whose server supports `foldingRange`. +local function request(bufnr, client) + ---@type lsp.FoldingRangeParams + local params = { textDocument = util.make_text_document_params(bufnr) } + + if client then + client:request(ms.textDocument_foldingRange, params, handler, bufnr) + return + end + + if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then + return + end + + vim.lsp.buf_request_all(bufnr, ms.textDocument_foldingRange, params, multi_handler) +end + +-- NOTE: +-- `bufstate` and event hooks are interdependent: +-- * `bufstate` needs event hooks for correctness. +-- * event hooks require the previous `bufstate` for updates. +-- Since they are manually created and destroyed, +-- we ensure their lifecycles are always synchronized. +-- +-- TODO(ofseed): +-- 1. Implement clearing `bufstate` and event hooks +-- when no clients in the buffer support the corresponding method. +-- 2. Then generalize this state management to other LSP modules. +local augroup_setup = api.nvim_create_augroup('vim_lsp_folding_range/setup', {}) + +--- Initialize `bufstate` and event hooks, then request folding ranges. +--- Manage their lifecycle within this function. +---@param bufnr integer +---@return vim.lsp.folding_range.BufState? +local function setup(bufnr) + if not api.nvim_buf_is_loaded(bufnr) then + return + end + + -- Register the new `bufstate`. + bufstates[bufnr] = { + client_ranges = {}, + row_level = {}, + row_kinds = {}, + row_text = {}, + } + + -- Event hooks from `buf_attach` can't be removed externally. + -- Hooks and `bufstate` share the same lifecycle; + -- they should self-destroy if `bufstate == nil`. + api.nvim_buf_attach(bufnr, false, { + -- `on_detach` also runs on buffer reload (`:e`). + -- Ensure `bufstate` and hooks are cleared to avoid duplication or leftover states. + on_detach = function() + bufstates[bufnr] = nil + api.nvim_clear_autocmds({ buffer = bufnr, group = augroup_setup }) + end, + -- Reset `bufstate` and request folding ranges. + on_reload = function() + bufstates[bufnr] = { + client_ranges = {}, + row_level = {}, + row_kinds = {}, + row_text = {}, + } + request(bufnr) + end, + --- Sync changed rows with their previous foldlevels before applying new ones. + on_bytes = function(_, _, _, start_row, _, _, old_row, _, _, new_row, _, _) + if bufstates[bufnr] == nil then + return true + end + local row_level = bufstates[bufnr].row_level + if next(row_level) == nil then + return + end + local row = new_row - old_row + if row > 0 then + vim._list_insert(row_level, start_row, start_row + math.abs(row) - 1, { -1 }) + -- If the previous row ends a fold, + -- Nvim treats the first row after consecutive `-1`s as a new fold start, + -- which is not the desired behavior. + local prev_level = row_level[start_row - 1] + if prev_level and prev_level[2] == '<' then + row_level[start_row] = { prev_level[1] - 1 } + end + elseif row < 0 then + vim._list_remove(row_level, start_row, start_row + math.abs(row) - 1) + end + end, + }) + api.nvim_create_autocmd('LspDetach', { + group = augroup_setup, + buffer = bufnr, + callback = function(args) + if not api.nvim_buf_is_loaded(bufnr) then + return + end + + ---@type integer + local client_id = args.data.client_id + bufstates[bufnr].client_ranges[client_id] = nil + + ---@type vim.lsp.Client[] + local clients = vim + .iter(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) + ---@param client vim.lsp.Client + :filter(function(client) + return client.id ~= client_id + end) + :totable() + if #clients == 0 then + bufstates[bufnr] = { + client_ranges = {}, + row_level = {}, + row_kinds = {}, + row_text = {}, + } + end + + foldupdate(bufnr) + end, + }) + api.nvim_create_autocmd('LspAttach', { + group = augroup_setup, + buffer = bufnr, + callback = function(args) + local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) + request(bufnr, client) + end, + }) + api.nvim_create_autocmd('LspNotify', { + group = augroup_setup, + buffer = bufnr, + callback = function(args) + local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) + if + client:supports_method(ms.textDocument_foldingRange, bufnr) + and ( + args.data.method == ms.textDocument_didChange + or args.data.method == ms.textDocument_didOpen + ) + then + request(bufnr, client) + end + end, + }) + + request(bufnr) + + return bufstates[bufnr] +end + +---@param kind lsp.FoldingRangeKind +---@param winid integer +local function foldclose(kind, winid) + vim._with({ win = winid }, function() + local bufnr = api.nvim_win_get_buf(winid) + local row_kinds = bufstates[bufnr].row_kinds + -- Reverse traverse to ensure that the smallest ranges are closed first. + for row = api.nvim_buf_line_count(bufnr) - 1, 0, -1 do + local kinds = row_kinds[row] + if kinds and kinds[kind] then + vim.cmd(row + 1 .. 'foldclose') + end + end + end) +end + +---@param kind lsp.FoldingRangeKind +---@param winid? integer +function M.foldclose(kind, winid) + vim.validate('kind', kind, 'string') + vim.validate('winid', winid, 'number', true) + + winid = winid or api.nvim_get_current_win() + local bufnr = api.nvim_win_get_buf(winid) + local bufstate = bufstates[bufnr] + if not bufstate then + return + end + + if bufstate.version == util.buf_versions[bufnr] then + foldclose(kind, winid) + return + end + -- Schedule `foldclose()` if the buffer is not up-to-date. + + if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then + return + end + ---@type lsp.FoldingRangeParams + local params = { textDocument = util.make_text_document_params(bufnr) } + vim.lsp.buf_request_all(bufnr, ms.textDocument_foldingRange, params, function(...) + multi_handler(...) + foldclose(kind, winid) + end) +end + +---@return string +function M.foldtext() + local bufnr = api.nvim_get_current_buf() + local lnum = vim.v.foldstart + local row = lnum - 1 + local bufstate = bufstates[bufnr] + if bufstate and bufstate.row_text[row] then + return bufstate.row_text[row] + end + return vim.fn.getline(lnum) +end + +---@param lnum? integer +---@return string level +function M.foldexpr(lnum) + local bufnr = api.nvim_get_current_buf() + local bufstate = bufstates[bufnr] or setup(bufnr) + if not bufstate then + return '0' + end + + local row = (lnum or vim.v.lnum) - 1 + local level = bufstate.row_level[row] + return level and (level[2] or '') .. (level[1] or '0') or '0' +end + +return M diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index aca6bf27f4..10479807a2 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -487,7 +487,7 @@ end --- ```lua --- -- Never request typescript-language-server for formatting --- vim.lsp.buf.format { ---- filter = function(client) return client.name ~= "tsserver" end +--- filter = function(client) return client.name ~= "ts_ls" end --- } --- ``` --- @field filter? fun(client: vim.lsp.Client): boolean? @@ -736,7 +736,7 @@ end --- Lists all the references to the symbol under the cursor in the quickfix window. --- ----@param context (table|nil) Context for the request +---@param context lsp.ReferenceContext? Context for the request ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references ---@param opts? vim.lsp.ListOpts function M.references(context, opts) diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 7c2b7192f5..a14b6ccda6 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -639,7 +639,7 @@ end --- @param method string LSP method name. --- @param params? table LSP request params. --- @param handler? lsp.Handler Response |lsp-handler| for this method. ---- @param bufnr? integer Buffer handle. 0 for current (default). +--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current. --- @return boolean status indicates whether the request was successful. --- If it is `false`, then it will always be `false` (the client has shutdown). --- @return integer? request_id Can be used with |Client:cancel_request()|. @@ -718,7 +718,7 @@ end --- @param params table LSP request params. --- @param timeout_ms integer? Maximum time in milliseconds to wait for --- a result. Defaults to 1000 ---- @param bufnr integer Buffer handle (0 for current). +--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current. --- @return {err: lsp.ResponseError?, result:any}? `result` and `err` from the |lsp-handler|. --- `nil` is the request was unsuccessful --- @return string? err On timeout, cancel or error, where `err` is a diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 3d29dad90a..cfd47d8f7c 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -440,6 +440,13 @@ function protocol.make_client_capabilities() properties = { 'command' }, }, }, + foldingRange = { + dynamicRegistration = false, + lineFoldingOnly = true, + foldingRange = { + collapsedText = true, + }, + }, formatting = { dynamicRegistration = true, }, diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3de76f93a6..ced8aa5745 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1894,7 +1894,7 @@ function M.make_position_params(window, position_encoding) local buf = api.nvim_win_get_buf(window) if position_encoding == nil then vim.notify_once( - 'warning: position_encoding is required, using the offset_encoding from the first client', + 'position_encoding param is required in vim.lsp.util.make_position_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) position_encoding = M._get_offset_encoding(buf) @@ -1950,7 +1950,7 @@ function M.make_range_params(window, position_encoding) local buf = api.nvim_win_get_buf(window or 0) if position_encoding == nil then vim.notify_once( - 'warning: position_encoding is required, using the offset_encoding from the first client', + 'position_encoding param is required in vim.lsp.util.make_range_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) position_encoding = M._get_offset_encoding(buf) @@ -1979,7 +1979,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding) bufnr = bufnr or api.nvim_get_current_buf() if position_encoding == nil then vim.notify_once( - 'warning: position_encoding is required, using the offset_encoding from the first client', + 'position_encoding param is required in vim.lsp.util.make_given_range_params. Defaulting to position encoding of the first client.', vim.log.levels.WARN ) position_encoding = M._get_offset_encoding(bufnr) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 4f2373b182..2e8edea22a 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -737,6 +737,51 @@ function vim.list_slice(list, start, finish) return new_list end +--- Efficiently insert items into the middle of a list. +--- +--- Calling table.insert() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +---@param v any +function vim._list_insert(t, first, last, v) + local n = #t + + -- Shift table forward + for i = n - first, 0, -1 do + t[last + 1 + i] = t[first + i] + end + + -- Fill in new values + for i = first, last do + t[i] = v + end +end + +--- Efficiently remove items from middle of a list. +--- +--- Calling table.remove() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +function vim._list_remove(t, first, last) + local n = #t + for i = 0, n - first do + t[first + i] = t[last + 1 + i] + t[last + 1 + i] = nil + end +end + --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- ---@see |lua-patterns| diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua index d45c8021c6..f910ab3a1d 100644 --- a/runtime/lua/vim/text.lua +++ b/runtime/lua/vim/text.lua @@ -2,6 +2,18 @@ local M = {} +local alphabet = '0123456789ABCDEF' +local atoi = {} ---@type table<string, integer> +local itoa = {} ---@type table<integer, string> +do + for i = 1, #alphabet do + local char = alphabet:sub(i, i) + itoa[i - 1] = char + atoi[char] = i - 1 + atoi[char:lower()] = i - 1 + end +end + --- Hex encode a string. --- --- @param str string String to encode @@ -9,7 +21,9 @@ local M = {} function M.hexencode(str) local enc = {} ---@type string[] for i = 1, #str do - enc[i] = string.format('%02X', str:byte(i, i + 1)) + local byte = str:byte(i) + enc[2 * i - 1] = itoa[math.floor(byte / 16)] + enc[2 * i] = itoa[byte % 16] end return table.concat(enc) end @@ -26,8 +40,12 @@ function M.hexdecode(enc) local str = {} ---@type string[] for i = 1, #enc, 2 do - local n = assert(tonumber(enc:sub(i, i + 1), 16)) - str[#str + 1] = string.char(n) + local u = atoi[enc:sub(i, i)] + local l = atoi[enc:sub(i + 1, i + 1)] + if not u or not l then + return nil, 'string must contain only hex characters' + end + str[(i + 1) / 2] = string.char(u * 16 + l) end return table.concat(str), nil end diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7237d2e7d4..0cb5b497c7 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -30,65 +30,20 @@ function FoldInfo.new() }, FoldInfo) end ---- Efficiently remove items from middle of a list a list. ---- ---- Calling table.remove() in a loop will re-index the tail of the table on ---- every iteration, instead this function will re-index the table exactly ---- once. ---- ---- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 ---- ----@param t any[] ----@param first integer ----@param last integer -local function list_remove(t, first, last) - local n = #t - for i = 0, n - first do - t[first + i] = t[last + 1 + i] - t[last + 1 + i] = nil - end -end - ---@package ---@param srow integer ---@param erow integer 0-indexed, exclusive function FoldInfo:remove_range(srow, erow) - list_remove(self.levels, srow + 1, erow) - list_remove(self.levels0, srow + 1, erow) -end - ---- Efficiently insert items into the middle of a list. ---- ---- Calling table.insert() in a loop will re-index the tail of the table on ---- every iteration, instead this function will re-index the table exactly ---- once. ---- ---- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 ---- ----@param t any[] ----@param first integer ----@param last integer ----@param v any -local function list_insert(t, first, last, v) - local n = #t - - -- Shift table forward - for i = n - first, 0, -1 do - t[last + 1 + i] = t[first + i] - end - - -- Fill in new values - for i = first, last do - t[i] = v - end + vim._list_remove(self.levels, srow + 1, erow) + vim._list_remove(self.levels0, srow + 1, erow) end ---@package ---@param srow integer ---@param erow integer 0-indexed, exclusive function FoldInfo:add_range(srow, erow) - list_insert(self.levels, srow + 1, erow, -1) - list_insert(self.levels0, srow + 1, erow, -1) + vim._list_insert(self.levels, srow + 1, erow, -1) + vim._list_insert(self.levels0, srow + 1, erow, -1) end ---@param range Range2 diff --git a/runtime/syntax/po.vim b/runtime/syntax/po.vim index 08d6baec27..6da27f639d 100644 --- a/runtime/syntax/po.vim +++ b/runtime/syntax/po.vim @@ -1,10 +1,11 @@ " Vim syntax file " Language: po (gettext) " Maintainer: Dwayne Bailey <dwayne@translate.org.za> -" Last Change: 2015 Jun 07 +" Last Change: 2024 Nov 28 " Contributors: Dwayne Bailey (Most advanced syntax highlighting) " Leonardo Fontenelle (Spell checking) " Nam SungHyun <namsh@kldp.org> (Original maintainer) +" Eisuke Kawashima (add format-flags: #16132) " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -32,9 +33,9 @@ syn region poMsgCTxt matchgroup=poStatementMsgCTxt start=+^msgctxt "+rs=e-1 syn region poMsgID matchgroup=poStatementMsgid start=+^msgid "+rs=e-1 matchgroup=poStringID end=+^msgstr\(\|\[[\]0\[]\]\) "+me=s-1 contains=poStringID,poStatementMsgidplural,poStatementMsgid syn region poMsgSTR matchgroup=poStatementMsgstr start=+^msgstr\(\|\[[\]0\[]\]\) "+rs=e-1 matchgroup=poStringSTR end=+\n\n+me=s-1 contains=poStringSTR,poStatementMsgstr syn region poStringCTxt start=+"+ skip=+\\\\\|\\"+ end=+"+ -syn region poStringID start=+"+ skip=+\\\\\|\\"+ end=+"+ contained +syn region poStringID start=+"+ skip=+\\\\\|\\"+ end=+"+ contained \ contains=poSpecial,poFormat,poCommentKDE,poPluralKDE,poKDEdesktopFile,poHtml,poAcceleratorId,poHtmlNot,poVariable -syn region poStringSTR start=+"+ skip=+\\\\\|\\"+ end=+"+ contained +syn region poStringSTR start=+"+ skip=+\\\\\|\\"+ end=+"+ contained \ contains=@Spell,poSpecial,poFormat,poHeaderItem,poCommentKDEError,poHeaderUndefined,poPluralKDEError,poMsguniqError,poKDEdesktopFile,poHtml,poAcceleratorStr,poHtmlNot,poVariable " Header and Copyright @@ -45,13 +46,43 @@ syn match poCopyrightUnset "SOME DESCRIPTIVE TITLE\|FIRST AUTHOR <EMAIL@ADDR " Translation comment block including: translator comment, automatic comments, flags and locations syn match poComment "^#.*$" syn keyword poFlagFuzzy fuzzy contained + +syn match poFlagFormat /\<\%(no-\)\?awk-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?boost-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?c++-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?c-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?csharp-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?elisp-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?gcc-internal-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?gfc-internal-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?java-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?java-printf-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?javascript-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?kde-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?librep-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?lisp-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?lua-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?objc-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?object-pascal-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?perl-brace-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?perl-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?php-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?python-brace-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?python-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?qt-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?qt-plural-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?ruby-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?scheme-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?sh-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?smalltalk-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?tcl-format\>/ contained +syn match poFlagFormat /\<\%(no-\)\?ycp-format\>/ contained + syn match poCommentTranslator "^# .*$" contains=poCopyrightUnset -syn match poCommentAutomatic "^#\..*$" +syn match poCommentAutomatic "^#\..*$" syn match poCommentSources "^#:.*$" -syn match poCommentFlags "^#,.*$" contains=poFlagFuzzy -syn match poDiffOld '\(^#| "[^{]*+}\|{+[^}]*+}\|{+[^}]*\|"$\)' contained -syn match poDiffNew '\(^#| "[^{]*-}\|{-[^}]*-}\|{-[^}]*\|"$\)' contained -syn match poCommentDiff "^#|.*$" contains=poDiffOld,poDiffNew +syn match poCommentFlags "^#,.*$" contains=poFlagFuzzy,poFlagFormat +syn match poCommentPrevious "^#|.*$" " Translations (also includes header fields as they appear in a translation msgstr) syn region poCommentKDE start=+"_: +ms=s+1 end="\\n" end="\"\n^msgstr"me=s-1 contained @@ -66,13 +97,13 @@ syn match poFormat "%%" contained syn region poMsguniqError matchgroup=poMsguniqErrorMarkers start="#-#-#-#-#" end='#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)-\("\n"\|\)#\("\n"\|\)\\n' contained " Obsolete messages -syn match poObsolete "^#\~.*$" +syn match poObsolete "^#\~.*$" " KDE Name= handling syn match poKDEdesktopFile "\"\(Name\|Comment\|GenericName\|Description\|Keywords\|About\)="ms=s+1,me=e-1 " Accelerator keys - this messes up if the preceding or following char is a multibyte unicode char -syn match poAcceleratorId contained "[^&_~][&_~]\(\a\|\d\)[^:]"ms=s+1,me=e-1 +syn match poAcceleratorId contained "[^&_~][&_~]\(\a\|\d\)[^:]"ms=s+1,me=e-1 syn match poAcceleratorStr contained "[^&_~][&_~]\(\a\|\d\)[^:]"ms=s+1,me=e-1 contains=@Spell " Variables simple @@ -86,11 +117,10 @@ hi def link poComment Comment hi def link poCommentAutomatic Comment hi def link poCommentTranslator Comment hi def link poCommentFlags Special -hi def link poCommentDiff Comment +hi def link poCommentPrevious Comment hi def link poCopyrightUnset Todo hi def link poFlagFuzzy Todo -hi def link poDiffOld Todo -hi def link poDiffNew Special +hi def link poFlagFormat Todo hi def link poObsolete Comment hi def link poStatementMsgid Statement |