aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/autoload/health/provider.vim119
-rw-r--r--runtime/autoload/man.vim18
-rw-r--r--runtime/autoload/netrw.vim5
-rw-r--r--runtime/autoload/provider/clipboard.vim9
-rw-r--r--runtime/doc/api.txt16
-rw-r--r--runtime/doc/autocmd.txt22
-rw-r--r--runtime/doc/eval.txt163
-rw-r--r--runtime/doc/filetype.txt2
-rw-r--r--runtime/doc/fold.txt3
-rw-r--r--runtime/doc/insert.txt3
-rw-r--r--runtime/doc/lsp.txt143
-rw-r--r--runtime/doc/lua.txt42
-rw-r--r--runtime/doc/mlang.txt4
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt24
-rw-r--r--runtime/doc/options.txt37
-rw-r--r--runtime/doc/sign.txt10
-rw-r--r--runtime/doc/spell.txt18
-rw-r--r--runtime/doc/starting.txt13
-rw-r--r--runtime/doc/syntax.txt4
-rw-r--r--runtime/doc/ui.txt10
-rw-r--r--runtime/doc/usr_21.txt15
-rw-r--r--runtime/doc/usr_41.txt7
-rw-r--r--runtime/doc/vim_diff.txt1
-rw-r--r--runtime/doc/windows.txt3
-rw-r--r--runtime/filetype.vim25
-rw-r--r--runtime/indent/testdir/runtest.vim1
-rw-r--r--runtime/lua/vim/lsp.lua72
-rw-r--r--runtime/lua/vim/lsp/buf.lua45
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua157
-rw-r--r--runtime/lua/vim/lsp/log.lua1
-rw-r--r--runtime/lua/vim/lsp/protocol.lua55
-rw-r--r--runtime/lua/vim/lsp/rpc.lua68
-rw-r--r--runtime/lua/vim/lsp/util.lua403
-rw-r--r--runtime/lua/vim/shared.lua74
-rw-r--r--runtime/lua/vim/treesitter.lua86
-rw-r--r--runtime/lua/vim/tshighlighter.lua28
-rw-r--r--runtime/lua/vim/uri.lua18
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim241
-rw-r--r--runtime/scripts.vim4
-rw-r--r--runtime/syntax/tutor.vim12
40 files changed, 1515 insertions, 466 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index cc7d86d0c1..6d481e9f49 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -163,7 +163,7 @@ function! s:check_clipboard() abort
endif
endfunction
-" Get the latest Neovim Python client (pynvim) version from PyPI.
+" Get the latest Nvim Python client (pynvim) version from PyPI.
function! s:latest_pypi_version() abort
let pypi_version = 'unable to get pypi response'
let pypi_response = s:download('https://pypi.python.org/pypi/pynvim/json')
@@ -180,7 +180,7 @@ endfunction
" Get version information using the specified interpreter. The interpreter is
" used directly in case breaking changes were introduced since the last time
-" Neovim's Python client was updated.
+" Nvim's Python client was updated.
"
" Returns: [
" {python executable version},
@@ -224,7 +224,7 @@ function! s:version_info(python) abort
\ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'],
\ '', 1, 1)
if empty(nvim_version)
- let nvim_version = 'unable to find neovim Python module version'
+ let nvim_version = 'unable to find pynvim module version'
let base = fnamemodify(nvim_path, ':h')
let metas = glob(base.'-*/METADATA', 1, 1)
\ + glob(base.'-*/PKG-INFO', 1, 1)
@@ -363,7 +363,7 @@ function! s:check_python(version) abort
\ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/'
call health#report_warn('pyenv is not set up optimally.', [
\ printf('Create a virtualenv specifically '
- \ . 'for Neovim using pyenv, and set `g:%s`. This will avoid '
+ \ . 'for Nvim using pyenv, and set `g:%s`. This will avoid '
\ . 'the need to install the pynvim module in each '
\ . 'version/virtualenv.', host_prog_var)
\ ])
@@ -377,7 +377,7 @@ function! s:check_python(version) abort
if resolve(python_exe) !~# '^'.venv_root.'/'
call health#report_warn('Your virtualenv is not set up optimally.', [
\ printf('Create a virtualenv specifically '
- \ . 'for Neovim and use `g:%s`. This will avoid '
+ \ . 'for Nvim and use `g:%s`. This will avoid '
\ . 'the need to install the pynvim module in each '
\ . 'virtualenv.', host_prog_var)
\ ])
@@ -392,18 +392,6 @@ function! s:check_python(version) abort
let python_exe = ''
endif
- " Check if $VIRTUAL_ENV is valid.
- if exists('$VIRTUAL_ENV') && !empty(python_exe)
- if $VIRTUAL_ENV ==# matchstr(python_exe, '^\V'.$VIRTUAL_ENV)
- call health#report_info('$VIRTUAL_ENV matches executable')
- else
- call health#report_warn(
- \ '$VIRTUAL_ENV exists but appears to be inactive. '
- \ . 'This could lead to unexpected results.',
- \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654' ])
- endif
- endif
-
" Diagnostic output
call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe))
if len(python_multiple)
@@ -497,6 +485,76 @@ function! s:check_for_pyenv() abort
return [pyenv_path, pyenv_root]
endfunction
+" Resolves Python executable path by invoking and checking `sys.executable`.
+function! s:python_exepath(invocation) abort
+ return s:normalize_path(system(a:invocation
+ \ . ' -c "import sys; sys.stdout.write(sys.executable)"'))
+endfunction
+
+" Checks that $VIRTUAL_ENV Python executables are found at front of $PATH in
+" Nvim and subshells.
+function! s:check_virtualenv() abort
+ call health#report_start('Python virtualenv')
+ if !exists('$VIRTUAL_ENV')
+ call health#report_ok('no $VIRTUAL_ENV')
+ return
+ endif
+ let errors = []
+ " Keep hints as dict keys in order to discard duplicates.
+ let hints = {}
+ " The virtualenv should contain some Python executables, and those
+ " executables should be first both on Nvim's $PATH and the $PATH of
+ " subshells launched from Nvim.
+ let bin_dir = has('win32') ? '/Scripts' : '/bin'
+ let venv_bins = glob($VIRTUAL_ENV . bin_dir . '/python*', v:true, v:true)
+ " XXX: Remove irrelevant executables found in bin/.
+ let venv_bins = filter(venv_bins, 'v:val !~# "python-config"')
+ if len(venv_bins)
+ for venv_bin in venv_bins
+ let venv_bin = s:normalize_path(venv_bin)
+ let py_bin_basename = fnamemodify(venv_bin, ':t')
+ let nvim_py_bin = s:python_exepath(exepath(py_bin_basename))
+ let subshell_py_bin = s:python_exepath(py_bin_basename)
+ if venv_bin !=# nvim_py_bin
+ call add(errors, '$PATH yields this '.py_bin_basename.' executable: '.nvim_py_bin)
+ let hint = '$PATH ambiguities arise if the virtualenv is not '
+ \.'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, '
+ \.'check that invoking Python from the command line launches the correct one, '
+ \.'then relaunch Nvim.'
+ let hints[hint] = v:true
+ endif
+ if venv_bin !=# subshell_py_bin
+ call add(errors, '$PATH in subshells yields this '
+ \.py_bin_basename . ' executable: '.subshell_py_bin)
+ let hint = '$PATH ambiguities in subshells typically are '
+ \.'caused by your shell config overriding the $PATH previously set by the '
+ \.'virtualenv. Either prevent them from doing so, or use this workaround: '
+ \.'https://vi.stackexchange.com/a/7654'
+ let hints[hint] = v:true
+ endif
+ endfor
+ else
+ call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.')
+ endif
+
+ if len(errors)
+ let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV
+ if len(venv_bins)
+ let msg .= "\nAnd its ".bin_dir.' directory contains: '
+ \.join(map(venv_bins, "fnamemodify(v:val, ':t')"), ', ')
+ endif
+ let conj = "\nBut "
+ for error in errors
+ let msg .= conj.error
+ let conj = "\nAnd "
+ endfor
+ let msg .= "\nSo invoking Python may lead to unexpected results."
+ call health#report_warn(msg, keys(hints))
+ else
+ call health#report_ok('$VIRTUAL_ENV provides :python, :python3, et al.')
+ endif
+endfunction
+
function! s:check_ruby() abort
call health#report_start('Ruby provider (optional)')
@@ -531,7 +589,7 @@ function! s:check_ruby() abort
\ 'Are you behind a firewall or proxy?'])
return
endif
- let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 1, 'not found')
+ let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found')
let current_gem_cmd = host .' --version'
let current_gem = s:system(current_gem_cmd)
@@ -567,7 +625,7 @@ function! s:check_node() abort
let node_v = get(split(s:system('node -v'), "\n"), 0, '')
call health#report_info('Node.js: '. node_v)
if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0
- call health#report_warn('Neovim node.js host does not support '.node_v)
+ call health#report_warn('Nvim node.js host does not support '.node_v)
" Skip further checks, they are nonsense if nodejs is too old.
return
endif
@@ -582,7 +640,7 @@ function! s:check_node() abort
\ 'Run in shell (if you use yarn): yarn global add neovim'])
return
endif
- call health#report_info('Neovim node.js host: '. host)
+ call health#report_info('Nvim node.js host: '. host)
let manager = executable('npm') ? 'npm' : 'yarn'
let latest_npm_cmd = has('win32') ?
@@ -637,7 +695,7 @@ function! s:check_perl() abort
let perl_v = get(split(s:system(['perl', '-W', '-e', 'print $^V']), "\n"), 0, '')
call health#report_info('Perl: '. perl_v)
if s:shell_error
- call health#report_warn('Neovim perl host does not support '.perl_v)
+ call health#report_warn('Nvim perl host does not support '.perl_v)
" Skip further checks, they are nonsense if perl is too old.
return
endif
@@ -648,17 +706,31 @@ function! s:check_perl() abort
\ ['Run in shell: cpanm Neovim::Ext'])
return
endif
- call health#report_info('Neovim perl host: '. host)
+ call health#report_info('Nvim perl host: '. host)
- let latest_cpan_cmd = 'cpanm --info Neovim::Ext'
+ let latest_cpan_cmd = 'cpanm --info -q Neovim::Ext'
let latest_cpan = s:system(latest_cpan_cmd)
if s:shell_error || empty(latest_cpan)
call health#report_error('Failed to run: '. latest_cpan_cmd,
\ ["Make sure you're connected to the internet.",
\ 'Are you behind a firewall or proxy?'])
return
+ elseif latest_cpan[0] ==# '!'
+ let cpanm_errs = split(latest_cpan, '!')
+ if cpanm_errs[0] =~# "Can't write to "
+ call health#report_warn(cpanm_errs[0], cpanm_errs[1:-2])
+ " Last line is the package info
+ let latest_cpan = cpanm_errs[-1]
+ else
+ call health#report_error('Unknown warning from command: ' . latest_cpan_cmd, cpanm_errs)
+ return
+ endif
endif
let latest_cpan = matchstr(latest_cpan, '\(\.\?\d\)\+')
+ if empty(latest_cpan)
+ call health#report_error('Cannot parse version number from cpanm output: ' . latest_cpan)
+ return
+ endif
let current_cpan_cmd = [host, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION']
let current_cpan = s:system(current_cpan_cmd)
@@ -682,6 +754,7 @@ function! health#provider#check() abort
call s:check_clipboard()
call s:check_python(2)
call s:check_python(3)
+ call s:check_virtualenv()
call s:check_ruby()
call s:check_node()
call s:check_perl()
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim
index 5feab0ce70..122ae357bc 100644
--- a/runtime/autoload/man.vim
+++ b/runtime/autoload/man.vim
@@ -328,18 +328,24 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort
return s:complete(sect, sect, name)
endfunction
-function! s:get_paths(sect, name) abort
+function! s:get_paths(sect, name, do_fallback) abort
+ " callers must try-catch this, as some `man` implementations don't support `s:find_arg`
try
let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',')
+ return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1)
catch
- call s:error(v:exception)
- return
+ if !a:do_fallback
+ throw v:exception
+ endif
+
+ " fallback to a single path, with the page we're trying to find
+ let [l:sect, l:name, l:path] = s:verify_exists(a:sect, a:name)
+ return [l:path]
endtry
- return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1)
endfunction
function! s:complete(sect, psect, name) abort
- let pages = s:get_paths(a:sect, a:name)
+ let pages = s:get_paths(a:sect, a:name, v:false)
" We remove duplicates in case the same manpage in different languages was found.
return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i'))
endfunction
@@ -387,7 +393,7 @@ endfunction
function! man#goto_tag(pattern, flags, info) abort
let [l:sect, l:name] = man#extract_sect_and_name_ref(a:pattern)
- let l:paths = s:get_paths(l:sect, l:name)
+ let l:paths = s:get_paths(l:sect, l:name, v:true)
let l:structured = []
for l:path in l:paths
diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index fa86223d53..b69ad7187a 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -5460,6 +5460,11 @@ fun! netrw#CheckIfRemote(...)
else
let curfile= expand("%")
endif
+
+ " Ignore terminal buffers
+ if &buftype ==# 'terminal'
+ return 0
+ endif
" call Decho("curfile<".curfile.">")
if curfile =~ '^\a\{3,}://'
" call Dret("netrw#CheckIfRemote 1")
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index c86f7d0c2f..a96a0a61b7 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -113,8 +113,13 @@ function! provider#clipboard#Executable() abort
let s:paste['*'] = s:paste['+']
return 'doitclient'
elseif executable('win32yank.exe')
- let s:copy['+'] = 'win32yank.exe -i --crlf'
- let s:paste['+'] = 'win32yank.exe -o --lf'
+ if has('wsl') && getftype(exepath('win32yank.exe')) == 'link'
+ let win32yank = resolve(exepath('win32yank.exe'))
+ else
+ let win32yank = 'win32yank.exe'
+ endif
+ let s:copy['+'] = win32yank.' -i --crlf'
+ let s:paste['+'] = win32yank.' -o --lf'
let s:copy['*'] = s:copy['+']
let s:paste['*'] = s:paste['+']
return 'win32yank'
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 39b6c6417d..a2e0c56f85 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -2341,6 +2341,22 @@ nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()*
Parameters: ~
{height} Popupmenu height, must be greater than zero.
+ *nvim_ui_pum_set_bounds()*
+nvim_ui_pum_set_bounds({width}, {height}, {row}, {col})
+
+ Tells Nvim the geometry of the popumenu, to align floating
+ windows with an external popup menu. Note that this method
+ is not to be confused with |nvim_ui_pum_set_height()|, which
+ sets the number of visible items in the popup menu, while
+ this function sets the bounding box of the popup menu,
+ including visual decorations such as boarders and sliders.
+
+ Parameters: ~
+ {width} Popupmenu width.
+ {height} Popupmenu height.
+ {row} Popupmenu row.
+ {height} Popupmenu height.
+
nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()*
TODO: Documentation
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 8006a45899..64ca7b6a45 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -358,7 +358,10 @@ Name triggered by ~
|MenuPopup| just before showing the popup menu
|CompleteChanged| after popup menu changed, not fired on popup menu hide
-|CompleteDone| after Insert mode completion is done
+|CompleteDonePre| after Insert mode completion is done, before clearing
+ info
+|CompleteDone| after Insert mode completion is done, after clearing
+ info
|User| to be used in combination with ":doautocmd"
|Signal| after Nvim receives a signal
@@ -577,7 +580,8 @@ ColorSchemePre Before loading a color scheme. |:colorscheme|
CompleteChanged *CompleteChanged*
After each time the Insert mode completion
menu changed. Not fired on popup menu hide,
- use |CompleteDone| for that.
+ use |CompleteDonePre| or |CompleteDone| for
+ that.
Sets these |v:event| keys:
completed_item See |complete-items|.
@@ -594,12 +598,22 @@ CompleteChanged *CompleteChanged*
The size and position of the popup are also
available by calling |pum_getpos()|.
+ *CompleteDonePre*
+CompleteDonePre After Insert mode completion is done. Either
+ when something was completed or abandoning
+ completion. |ins-completion|
+ |complete_info()| can be used, the info is
+ cleared after triggering CompleteDonePre.
+ The |v:completed_item| variable contains
+ information about the completed item.
+
*CompleteDone*
CompleteDone After Insert mode completion is done. Either
when something was completed or abandoning
completion. |ins-completion|
- |complete_info()| can be used, the info is
- cleared after triggering CompleteDone.
+ |complete_info()| cannot be used, the info is
+ cleared before triggering CompleteDone. Use
+ CompleteDonePre if you need it.
|v:completed_item| gives the completed item.
*CursorHold*
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 69b8b3cf39..6f13b34876 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2084,6 +2084,7 @@ ctxsize() Number return |context-stack| size
cursor({lnum}, {col} [, {off}])
Number move cursor to {lnum}, {col}, {off}
cursor({list}) Number move cursor to position in {list}
+debugbreak({pid}) Number interrupt process being debugged
deepcopy({expr} [, {noref}]) any make a full copy of {expr}
delete({fname} [, {flags}]) Number delete the file or directory {fname}
deletebufline({expr}, {first}[, {last}])
@@ -2109,6 +2110,7 @@ extend({expr1}, {expr2} [, {expr3}])
exp({expr}) Float exponential of {expr}
expand({expr} [, {nosuf} [, {list}]])
any expand special keywords in {expr}
+expandcmd({expr}) String expand {expr} like with `:edit`
feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer
filereadable({file}) Number |TRUE| if {file} is a readable file
filewritable({file}) Number |TRUE| if {file} is a writable file
@@ -2239,6 +2241,7 @@ libcallnr({lib}, {func}, {arg}) Number idem, but return a Number
line({expr}) Number line nr of cursor, last line or mark
line2byte({lnum}) Number byte count of line {lnum}
lispindent({lnum}) Number Lisp indent for line {lnum}
+list2str({list} [, {utf8}]) String turn numbers in {list} into a String
localtime() Number current time
log({expr}) Float natural logarithm (base e) of {expr}
log10({expr}) Float logarithm of Float {expr} to base 10
@@ -2280,6 +2283,10 @@ pathshorten({expr}) String shorten directory names in a path
pow({x}, {y}) Float {x} to the power of {y}
prevnonblank({lnum}) Number line nr of non-blank line <= {lnum}
printf({fmt}, {expr1}...) String format text
+prompt_addtext({buf}, {expr}) none add text to a prompt buffer
+prompt_setcallback({buf}, {expr}) none set prompt callback function
+prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
+prompt_setprompt({buf}, {text}) none set prompt text
pum_getpos() Dict position and size of pum if visible
pumvisible() Number whether popup menu is visible
pyeval({expr}) any evaluate |Python| expression
@@ -2290,7 +2297,7 @@ range({expr} [, {max} [, {stride}]])
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {binary} [, {max}]])
List get list of lines from file {fname}
-reg_executing() Number get the executing register name
+reg_executing() String get the executing register name
reg_recording() String get the recording register name
reltime([{start} [, {end}]]) List get time value
reltimefloat({time}) Float turn the time value into a Float
@@ -2389,6 +2396,8 @@ sqrt({expr}) Float square root of {expr}
stdioopen({dict}) Number open stdio in a headless instance.
stdpath({what}) String/List returns the standard path(s) for {what}
str2float({expr}) Float convert String to Float
+str2list({expr} [, {utf8}]) List convert each character of {expr} to
+ ASCII/UTF8 value
str2nr({expr} [, {base}]) Number convert String to Number
strchars({expr} [, {skipcc}]) Number character length of the String {expr}
strcharpart({str}, {start} [, {len}])
@@ -3637,6 +3646,11 @@ exp({expr}) *exp()*
:echo exp(-1)
< 0.367879
+debugbreak({pid}) *debugbreak()*
+ Specifically used to interrupt a program being debugged. It
+ will cause process {pid} to get a SIGTRAP. Behavior for other
+ processes is undefined. See |terminal-debugger|.
+ {Sends a SIGINT to a process {pid} other than MS-Windows}
expand({expr} [, {nosuf} [, {list}]]) *expand()*
Expand wildcards and the following special keywords in {expr}.
@@ -3720,6 +3734,14 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()*
See |glob()| for finding existing files. See |system()| for
getting the raw output of an external command.
+expandcmd({expr}) *expandcmd()*
+ Expand special items in {expr} like what is done for an Ex
+ command such as `:edit`. This expands special keywords, like
+ with |expand()|, and environment variables, anywhere in
+ {expr}. Returns the expanded string.
+ Example: >
+ :echo expandcmd('make %<.o')
+<
extend({expr1}, {expr2} [, {expr3}]) *extend()*
{expr1} and {expr2} must be both |Lists| or both
|Dictionaries|.
@@ -4142,6 +4164,9 @@ getbufinfo([{dict}])
changed TRUE if the buffer is modified.
changedtick number of changes made to the buffer.
hidden TRUE if the buffer is hidden.
+ lastused timestamp in seconds, like
+ |localtime()|, when the buffer was
+ last used.
listed TRUE if the buffer is listed.
lnum current line number in buffer.
linecount number of lines in the buffer (only
@@ -4541,7 +4566,7 @@ getline({lnum} [, {end}])
from the current buffer. Example: >
getline(1)
< When {lnum} is a String that doesn't start with a
- digit, line() is called to translate the String into a Number.
+ digit, |line()| is called to translate the String into a Number.
To get the line under the cursor: >
getline(".")
< When {lnum} is smaller than 1 or bigger than the number of
@@ -5677,6 +5702,20 @@ lispindent({lnum}) *lispindent()*
When {lnum} is invalid or Vim was not compiled the
|+lispindent| feature, -1 is returned.
+list2str({list} [, {utf8}]) *list2str()*
+ Convert each number in {list} to a character string can
+ concatenate them all. Examples: >
+ list2str([32]) returns " "
+ list2str([65, 66, 67]) returns "ABC"
+< The same can be done (slowly) with: >
+ join(map(list, {nr, val -> nr2char(val)}), '')
+< |str2list()| does the opposite.
+
+ When {utf8} is omitted or zero, the current 'encoding' is used.
+ With {utf8} is 1, always return utf-8 characters.
+ With utf-8 composing characters work as expected: >
+ list2str([97, 769]) returns "á"
+<
localtime() *localtime()*
Return the current time, measured as seconds since 1st Jan
1970. See also |strftime()| and |getftime()|.
@@ -6541,6 +6580,50 @@ printf({fmt}, {expr1} ...) *printf()*
of "%" items. If there are not sufficient or too many
arguments an error is given. Up to 18 arguments can be used.
+prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
+ Set prompt callback for buffer {buf} to {expr}. When {expr}
+ is an empty string the callback is removed. This has only
+ effect if {buf} has 'buftype' set to "prompt".
+
+ The callback is invoked when pressing Enter. The current
+ buffer will always be the prompt buffer. A new line for a
+ prompt is added before invoking the callback, thus the prompt
+ for which the callback was invoked will be in the last but one
+ line.
+ If the callback wants to add text to the buffer, it must
+ insert it above the last line, since that is where the current
+ prompt is. This can also be done asynchronously.
+ The callback is invoked with one argument, which is the text
+ that was entered at the prompt. This can be an empty string
+ if the user only typed Enter.
+ Example: >
+ call prompt_setcallback(bufnr(''), function('s:TextEntered'))
+ func s:TextEntered(text)
+ if a:text == 'exit' || a:text == 'quit'
+ stopinsert
+ close
+ else
+ call append(line('$') - 1, 'Entered: "' . a:text . '"')
+ " Reset 'modified' to allow the buffer to be closed.
+ set nomodified
+ endif
+ endfunc
+
+prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()*
+ Set a callback for buffer {buf} to {expr}. When {expr} is an
+ empty string the callback is removed. This has only effect if
+ {buf} has 'buftype' set to "prompt".
+
+ This callback will be invoked when pressing CTRL-C in Insert
+ mode. Without setting a callback Vim will exit Insert mode,
+ as in any buffer.
+
+prompt_setprompt({buf}, {text}) *prompt_setprompt()*
+ Set prompt for buffer {buf} to {text}. You most likely want
+ {text} to end in a space.
+ The result is only visible if {buf} has 'buftype' set to
+ "prompt". Example: >
+ call prompt_setprompt(bufnr(''), 'command: ')
pum_getpos() *pum_getpos()*
If the popup menu (see |ins-completion-menu|) is not visible,
@@ -6606,6 +6689,33 @@ range({expr} [, {max} [, {stride}]]) *range()*
range(0) " []
range(2, 0) " error!
<
+ *readdir()*
+readdir({directory} [, {expr}])
+ Return a list with file and directory names in {directory}.
+
+ When {expr} is omitted all entries are included.
+ When {expr} is given, it is evaluated to check what to do:
+ If {expr} results in -1 then no further entries will
+ be handled.
+ If {expr} results in 0 then this entry will not be
+ added to the list.
+ If {expr} results in 1 then this entry will be added
+ to the list.
+ Each time {expr} is evaluated |v:val| is set to the entry name.
+ When {expr} is a function the name is passed as the argument.
+ For example, to get a list of files ending in ".txt": >
+ readdir(dirname, {n -> n =~ '.txt$'})
+< To skip hidden and backup files: >
+ readdir(dirname, {n -> n !~ '^\.\|\~$'})
+
+< If you want to get a directory tree: >
+ function! s:tree(dir)
+ return {a:dir : map(readdir(a:dir),
+ \ {_, x -> isdirectory(x) ?
+ \ {x : s:tree(a:dir . '/' . x)} : x})}
+ endfunction
+ echo s:tree(".")
+<
*readfile()*
readfile({fname} [, {binary} [, {max}]])
Read file {fname} and return a |List|, each line of the file
@@ -6637,17 +6747,6 @@ readfile({fname} [, {binary} [, {max}]])
the result is an empty list.
Also see |writefile()|.
- *readdir()*
-readdir({directory} [, {expr}])
- Return a list with file and directory names in {directory}.
- You can also use |glob()| if you don't need to do complicated
- things, such as limiting the number of matches.
-
- When {expr} is omitted all entries are included.
- When {expr} is given, it is evaluated to check what to do:
- If {expr} results in -1 then no further entries will
- be handled.
-
reg_executing() *reg_executing()*
Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed.
@@ -7550,11 +7649,21 @@ settagstack({nr}, {dict} [, {action}]) *settagstack()*
{nr} can be the window number or the |window-ID|.
For a list of supported items in {dict}, refer to
- |gettagstack()|
+ |gettagstack()|. "curidx" takes effect before changing the tag
+ stack.
*E962*
- If {action} is not present or is set to 'r', then the tag
- stack is replaced. If {action} is set to 'a', then new entries
- from {dict} are pushed onto the tag stack.
+ How the tag stack is modified depends on the {action}
+ argument:
+ - If {action} is not present or is set to 'r', then the tag
+ stack is replaced.
+ - If {action} is set to 'a', then new entries from {dict} are
+ pushed (added) onto the tag stack.
+ - If {action} is set to 't', then all the entries from the
+ current entry in the tag stack or "curidx" in {dict} are
+ removed and then new entries are pushed to the stack.
+
+ The current index is set to one after the length of the tag
+ stack after the modification.
Returns zero for success, -1 for failure.
@@ -7728,7 +7837,7 @@ sign_getplaced([{expr} [, {dict}]]) *sign_getplaced()*
priority sign priority
The returned signs in a buffer are ordered by their line
- number.
+ number and priority.
Returns an empty list on failure or if there are no placed
signs.
@@ -8133,6 +8242,18 @@ str2float({expr}) *str2float()*
|substitute()|: >
let f = str2float(substitute(text, ',', '', 'g'))
+str2list({expr} [, {utf8}]) *str2list()*
+ Return a list containing the number values which represent
+ each character in String {expr}. Examples: >
+ str2list(" ") returns [32]
+ str2list("ABC") returns [65, 66, 67]
+< |list2str()| does the opposite.
+
+ When {utf8} is omitted or zero, the current 'encoding' is used.
+ With {utf8} set to 1, always treat the String as utf-8
+ characters. With utf-8 composing characters are handled
+ properly: >
+ str2list("á") returns [97, 769]
str2nr({expr} [, {base}]) *str2nr()*
Convert string {expr} to a number.
@@ -10277,8 +10398,8 @@ text...
The parsing works slightly different from |:echo|,
more like |:execute|. All the expressions are first
evaluated and concatenated before echoing anything.
- The expressions must evaluate to a Number or String, a
- Dictionary or List causes an error.
+ If expressions does not evaluate to a Number or
+ String, string() is used to turn it into a string.
Uses the highlighting set by the |:echohl| command.
Example: >
:echomsg "It's a Zizzer Zazzer Zuzz, as you can plainly see."
@@ -10289,7 +10410,7 @@ text...
message in the |message-history|. When used in a
script or function the line number will be added.
Spaces are placed between the arguments as with the
- :echo command. When used inside a try conditional,
+ |:echomsg| command. When used inside a try conditional,
the message is raised as an error exception instead
(see |try-echoerr|).
Example: >
diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt
index c649688d99..1fce37c594 100644
--- a/runtime/doc/filetype.txt
+++ b/runtime/doc/filetype.txt
@@ -275,7 +275,7 @@ Note that the last one is the value of $VIMRUNTIME which has been expanded.
Note that when using a plugin manager or |packages| many directories will be
added to 'runtimepath'. These plugins each require their own directory, don't
-put them directly in ~/.vim/plugin.
+put them directly in ~/.config/nvim/plugin.
What if it looks like your plugin is not being loaded? You can find out what
happens when Vim starts up by using the |-V| argument: >
diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt
index f2f6c70b0c..8e2cb2f728 100644
--- a/runtime/doc/fold.txt
+++ b/runtime/doc/fold.txt
@@ -527,8 +527,7 @@ FOLDCOLUMN *fold-foldcolumn*
'foldcolumn' is a number, which sets the width for a column on the side of the
window to indicate folds. When it is zero, there is no foldcolumn. A normal
-value is 4 or 5. The minimal useful value is 2, although 1 still provides
-some information. The maximum is 12.
+value is auto:9. The maximum is 9.
An open fold is indicated with a column that has a '-' at the top and '|'
characters below it. This column stops where the open fold stops. When folds
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index fce4d8628e..e53af5074b 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -1083,7 +1083,8 @@ items:
empty when non-zero this match will be added even when it is
an empty string
user_data custom data which is associated with the item and
- available in |v:completed_item|
+ available in |v:completed_item|; it can be any type;
+ defaults to an empty string
All of these except "icase", "equal", "dup" and "empty" must be a string. If
an item does not meet these requirements then an error message is given and
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 016a8be7e6..249136f32f 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -48,6 +48,8 @@ go-to-definition, hover, etc. Example config: >
nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR>
nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR>
nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR>
+ nnoremap <silent> g0 <cmd>lua vim.lsp.buf.document_symbol()<CR>
+ nnoremap <silent> gW <cmd>lua vim.lsp.buf.workspace_symbol()<CR>
Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows
|i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of
@@ -63,7 +65,7 @@ FAQ *lsp-faq*
- Q: How to force-reload LSP?
A: Stop all clients, then reload the buffer. >
- :lua vim.lsp.stop_all_clients()
+ :lua vim.lsp.stop_client(vim.lsp.get_active_clients())
:edit
- Q: Why isn't completion working?
@@ -77,21 +79,6 @@ FAQ *lsp-faq*
"after/ftplugin/python.vim".
================================================================================
-LSP HIGHLIGHT *lsp-highlight*
-
-When LSP is activated these highlight groups are defined:
-
- LspDiagnosticsError
- LspDiagnosticsHint
- LspDiagnosticsInformation
- LspDiagnosticsUnderline
- LspDiagnosticsUnderlineError
- LspDiagnosticsUnderlineHint
- LspDiagnosticsUnderlineInformation
- LspDiagnosticsUnderlineWarning
- LspDiagnosticsWarning
-
-================================================================================
LSP API *lsp-api*
The `vim.lsp` Lua module is a framework for building LSP plugins.
@@ -174,6 +161,33 @@ name: >
vim.lsp.protocol.TextDocumentSyncKind[1] == "Full"
================================================================================
+LSP HIGHLIGHT *lsp-highlight*
+
+ *hl-LspDiagnosticsError*
+LspDiagnosticsError used for "Error" diagnostic virtual text
+ *hl-LspDiagnosticsErrorSign*
+LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column
+ *hl-LspDiagnosticsWarning*
+LspDiagnosticsWarning used for "Warning" diagnostic virtual text
+ *hl-LspDiagnosticsWarningSign*
+LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column
+ *hl-LspDiagnosticsInformation*
+LspDiagnosticInformation used for "Information" diagnostic virtual text
+ *hl-LspDiagnosticsInformationSign*
+LspDiagnosticInformationSign used for "Information" signs in sign column
+ *hl-LspDiagnosticsHint*
+LspDiagnosticHint used for "Hint" diagnostic virtual text
+ *hl-LspDiagnosticsHintSign*
+LspDiagnosticHintSign used for "Hint" diagnostic signs in sign column
+ *hl-LspReferenceText*
+LspReferenceText used for highlighting "text" references
+ *hl-LspReferenceRead*
+LspReferenceRead used for highlighting "read" references
+ *hl-LspReferenceWrite*
+LspReferenceWrite used for highlighting "write" references
+
+
+================================================================================
LSP EXAMPLE *lsp-extension-example*
This example is for plugin authors or users who want a lot of control. If you
@@ -290,6 +304,12 @@ The example will:
<
+==============================================================================
+AUTOCOMMANDS *lsp-autocommands*
+
+ *LspDiagnosticsChanged*
+LspDiagnosticsChanged After receiving publishDiagnostics server response
+
==============================================================================
Lua module: vim.lsp *lsp-core*
@@ -333,7 +353,7 @@ buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()*
{params} (string) Parameters to send to the server
Return: ~
- nil
+ true if any client returns true; false otherwise
*vim.lsp.buf_request()*
buf_request({bufnr}, {method}, {params}, {callback})
@@ -543,7 +563,7 @@ start_client({config}) *vim.lsp.start_client()*
{root_dir} (required, string) Directory where the
LSP server will base its rootUri on
initialization.
- {cmd} (required, string or list treated like
+ {cmd} (required, list treated like
|jobstart()|) Base command that
initiates the LSP client.
{cmd_cwd} (string, default=|getcwd()|) Directory
@@ -720,6 +740,12 @@ declaration() *vim.lsp.buf.declaration()*
definition() *vim.lsp.buf.definition()*
TODO: Documentation
+document_highlight() *vim.lsp.buf.document_highlight()*
+ TODO: Documentation
+
+document_symbol() *vim.lsp.buf.document_symbol()*
+ TODO: Documentation
+
formatting({options}) *vim.lsp.buf.formatting()*
TODO: Documentation
@@ -735,9 +761,6 @@ npcall({fn}, {...}) *vim.lsp.buf.npcall()*
ok_or_nil({status}, {...}) *vim.lsp.buf.ok_or_nil()*
TODO: Documentation
-peek_definition() *vim.lsp.buf.peek_definition()*
- TODO: Documentation
-
*vim.lsp.buf.range_formatting()*
range_formatting({options}, {start_pos}, {end_pos})
TODO: Documentation
@@ -751,12 +774,23 @@ rename({new_name}) *vim.lsp.buf.rename()*
request({method}, {params}, {callback}) *vim.lsp.buf.request()*
TODO: Documentation
+server_ready() *vim.lsp.buf.server_ready()*
+ Sends a notification through all clients associated with current
+ buffer and returns `true` if server responds.
+
signature_help() *vim.lsp.buf.signature_help()*
TODO: Documentation
type_definition() *vim.lsp.buf.type_definition()*
TODO: Documentation
+workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()*
+ Lists all symbols in the current workspace in the quickfix
+ window. The list is filtered against the optional argument
+ {query}; if the argument is omitted from the call, the user
+ is prompted to enter a string on the command line. An empty
+ string means no filtering is done.
+
==============================================================================
Lua module: vim.lsp.callbacks *lsp-callbacks*
@@ -894,13 +928,60 @@ apply_text_edits({text_edits}, {bufnr})
apply_workspace_edit({workspace_edit})
TODO: Documentation
+ *vim.lsp.util.diagnostics_by_buf*
+diagnostics_by_buf
+ A table containing diagnostics grouped by buf.
+
+ {<bufnr>: {diagnostics}}
+
+ {diagnostics} is an array of diagnostics.
+
+ By default this is populated by the
+ `textDocument/publishDiagnostics` callback via
+ |vim.lsp.util.buf_diagnostics_save_positions|.
+
+ It contains entries for active buffers. Once a buffer is
+ detached the entries for it are discarded.
+
buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()*
TODO: Documentation
+
+ *vim.lsp.util.buf_diagnostics_count()*
+buf_diagnostics_count({kind})
+ Returns the number of diagnostics of given kind for current buffer.
+ Useful for showing diagnostics counts in statusline. eg:
- *vim.lsp.util.buf_diagnostics_save_positions()*
-buf_diagnostics_save_positions({bufnr}, {diagnostics})
+>
+ function! LspStatus() abort
+ let sl = ''
+ if luaeval('vim.lsp.buf.server_ready()')
+ let sl.='%#MyStatuslineLSP#E:'
+ let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Error\")")}'
+ let sl.='%#MyStatuslineLSP# W:'
+ let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count(\"Warning\")")}'
+ else
+ let sl.='%#MyStatuslineLSPErrors#off'
+ endif
+ return sl
+ endfunction
+ let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
+<
+
+ Parameters: ~
+ {kind} Diagnostic severity kind: Error, Warning, Information or Hint.
+
+buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()*
TODO: Documentation
+ *vim.lsp.util.buf_diagnostics_save()*
+buf_diagnostics_save_positions({bufnr}, {diagnostics})
+ Stores the diagnostics into |vim.lsp.util.diagnostics_by_buf|
+
+ Parameters: ~
+ {bufr} bufnr for which the diagnostics are for.
+ {diagnostics} Diagnostics[] received from the
+ langauge server.
+
*vim.lsp.util.buf_diagnostics_underline()*
buf_diagnostics_underline({bufnr}, {diagnostics})
TODO: Documentation
@@ -909,6 +990,18 @@ buf_diagnostics_underline({bufnr}, {diagnostics})
buf_diagnostics_virtual_text({bufnr}, {diagnostics})
TODO: Documentation
+ *vim.lsp.util.buf_diagnostics_signs()*
+buf_diagnostics_signs({bufnr}, {diagnostics})
+ Place signs for each diagnostic in the sign column.
+ Sign characters can be customized with the following commands:
+>
+sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl=
+sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl=
+sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl=
+sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl=
+<
+
+
character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()*
TODO: Documentation
@@ -973,10 +1066,6 @@ npcall({fn}, {...}) *vim.lsp.util.npcall()*
ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()*
TODO: Documentation
- *vim.lsp.util.open_floating_peek_preview()*
-open_floating_peek_preview({bufnr}, {start}, {finish}, {opts})
- TODO: Documentation
-
*vim.lsp.util.open_floating_preview()*
open_floating_preview({contents}, {filetype}, {opts})
TODO: Documentation
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index af1f4a8c1f..7f376cbbf0 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -494,10 +494,12 @@ VIM.TREESITTER *lua-treesitter*
Nvim integrates the tree-sitter library for incremental parsing of buffers.
Currently Nvim does not provide the tree-sitter parsers, instead these must
-be built separately, for instance using the tree-sitter utility.
-The parser is loaded into nvim using >
+be built separately, for instance using the tree-sitter utility. The only
+exception is a C parser being included in official builds for testing
+purposes. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath'
+directory. A parser can also be loaded manually using a full path: >
- vim.treesitter.add_language("/path/to/c_parser.so", "c")
+ vim.treesitter.require_language("python", "/path/to/python.so")
<Create a parser for a buffer and a given language (if another plugin uses the
same buffer/language combination, it will be safely reused). Use >
@@ -691,6 +693,35 @@ identical identifiers, highlighting both as |hl-WarningMsg|: >
(eq? @WarningMsg.left @WarningMsg.right))
------------------------------------------------------------------------------
+VIM.REGEX *lua-regex*
+
+Vim regexes can be used directly from lua. Currently they only allow
+matching within a single line.
+
+vim.regex({re}) *vim.regex()*
+
+ Parse the regex {re} and return a regex object. 'magic' and
+ 'ignorecase' options are ignored, lua regexes always defaults to magic
+ and ignoring case. The behavior can be changed with flags in
+ the beginning of the string |/magic|.
+
+Regex objects support the following methods:
+
+regex:match_str({str}) *regex:match_str()*
+ Match the string against the regex. If the string should match the
+ regex precisely, surround the regex with `^` and `$`.
+ If the was a match, the byte indices for the beginning and end of
+ the match is returned. When there is no match, `nil` is returned.
+ As any integer is truth-y, `regex:match()` can be directly used
+ as a condition in an if-statement.
+
+regex:match_line({bufnr}, {line_idx}[, {start}, {end}]) *regex:match_line()*
+ Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and
+ {end} are supplied, match only this byte index range. Otherwise see
+ |regex:match_str()|. If {start} is used, then the returned byte
+ indices will be relative {start}.
+
+------------------------------------------------------------------------------
VIM *lua-builtin*
vim.api.{func}({...}) *vim.api*
@@ -877,7 +908,10 @@ deep_equal({a}, {b}) *vim.deep_equal()*
deepcopy({orig}) *vim.deepcopy()*
Returns a deep copy of the given object. Non-table objects are
copied as in a typical Lua assignment, whereas table objects
- are copied recursively.
+ are copied recursively. Functions are naively copied, so
+ functions in the copied table point to the same functions as
+ those in the input table. Userdata and threads are not copied
+ and will throw an error.
Parameters: ~
{orig} Table to copy
diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt
index 03c48b962d..2a10a7051d 100644
--- a/runtime/doc/mlang.txt
+++ b/runtime/doc/mlang.txt
@@ -185,8 +185,8 @@ you can do it without restarting Vim: >
:source $VIMRUNTIME/menu.vim
Each part of a menu path is translated separately. The result is that when
-"Help" is translated to "Hilfe" and "Overview" to "berblick" then
-"Help.Overview" will be translated to "Hilfe.berblick".
+"Help" is translated to "Hilfe" and "Overview" to "Überblick" then
+"Help.Overview" will be translated to "Hilfe.Überblick".
==============================================================================
3. Scripts *multilang-scripts*
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index 1a5e6421e7..a96d118667 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -50,6 +50,13 @@ mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys
except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return
to normal-mode. |CTRL-\_CTRL-N|
+Terminal-mode forces these local options:
+
+ 'nocursorline'
+ 'nocursorcolumn'
+ 'scrolloff' = 0
+ 'sidescrolloff' = 0
+
Terminal-mode has its own |:tnoremap| namespace for mappings, this can be used
to automate any terminal interaction.
@@ -307,6 +314,23 @@ Other commands ~
isn't one
+Prompt mode ~
+ *termdebug-prompt*
+When on MS-Windows, gdb will run in a buffer with 'buftype' set to "prompt".
+This works slightly differently:
+- The gdb window will be in Insert mode while typing commands. Go to Normal
+ mode with <Esc>, then you can move around in the buffer, copy/paste, etc.
+ Go back to editing the gdb command with any command that starts Insert mode,
+ such as `a` or `i`.
+- The program being debugged will run in a separate window. On MS-Windows
+ this is a new console window. On Unix, if the |+terminal| feature is
+ available a Terminal window will be opened to run the debugged program in.
+
+ *termdebug_use_prompt*
+Prompt mode can be used even when the |+terminal| feature is present with: >
+ let g:termdebug_use_prompt = 1
+
+
Communication ~
*termdebug-communication*
There is another, hidden, buffer, which is used for Vim to communicate with
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 70af23ee29..8d4f76d3dd 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1093,6 +1093,8 @@ A jump table for the options with a short description can be found at |Q_op|.
nowrite buffer will not be written
quickfix list of errors |:cwindow| or locations |:lwindow|
terminal |terminal-emulator| buffer
+ prompt buffer where only the last line can be edited, meant
+ to be used by a plugin, see |prompt-buffer|
This option is used together with 'bufhidden' and 'swapfile' to
specify special kinds of buffers. See |special-buffers|.
@@ -2419,11 +2421,14 @@ A jump table for the options with a short description can be found at |Q_op|.
automatically close when moving out of them.
*'foldcolumn'* *'fdc'*
-'foldcolumn' 'fdc' number (default 0)
+'foldcolumn' 'fdc' string (default "0")
local to window
- When non-zero, a column with the specified width is shown at the side
- of the window which indicates open and closed folds. The maximum
- value is 12.
+ When and how to draw the foldcolumn. Valid values are:
+ "auto": resize to the maximum amount of folds to display.
+ "auto:[1-9]": resize to accommodate multiple folds up to the
+ selected level
+ 0: to disable foldcolumn
+ "[1-9]": to display a fixed number of columns
See |folding|.
*'foldenable'* *'fen'* *'nofoldenable'* *'nofen'*
@@ -4895,13 +4900,17 @@ A jump table for the options with a short description can be found at |Q_op|.
*'scrolloff'* *'so'*
'scrolloff' 'so' number (default 0)
- global
+ global or local to window |global-local|
Minimal number of screen lines to keep above and below the cursor.
This will make some context visible around where you are working. If
you set it to a very large value (999) the cursor line will always be
in the middle of the window (except at the start or end of the file or
when long lines wrap).
- For scrolling horizontally see 'sidescrolloff'.
+ After using the local value, go back the global value with one of
+ these two: >
+ setlocal scrolloff<
+ setlocal scrolloff=-1
+< For scrolling horizontally see 'sidescrolloff'.
*'scrollopt'* *'sbo'*
'scrollopt' 'sbo' string (default "ver,jump")
@@ -5510,7 +5519,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'sidescrolloff'* *'siso'*
'sidescrolloff' 'siso' number (default 0)
- global
+ global or local to window |global-local|
The minimal number of screen columns to keep to the left and to the
right of the cursor if 'nowrap' is set. Setting this option to a
value greater than 0 while having |'sidescroll'| also at a non-zero
@@ -5519,7 +5528,11 @@ A jump table for the options with a short description can be found at |Q_op|.
to a large value (like 999) has the effect of keeping the cursor
horizontally centered in the window, as long as one does not come too
close to the beginning of the line.
-
+ After using the local value, go back the global value with one of
+ these two: >
+ setlocal sidescrolloff<
+ setlocal sidescrolloff=-1
+<
Example: Try this together with 'sidescroll' and 'listchars' as
in the following example to never allow the cursor to move
onto the "extends" character: >
@@ -6503,9 +6516,11 @@ A jump table for the options with a short description can be found at |Q_op|.
>= 1 When the shada file is read or written.
>= 2 When a file is ":source"'ed.
>= 3 UI info, terminal capabilities
+ >= 4 Shell commands.
>= 5 Every searched tags file and include file.
>= 8 Files for which a group of autocommands is executed.
>= 9 Every executed autocommand.
+ >= 11 Finding items in a path
>= 12 Every executed function.
>= 13 When an exception is thrown, caught, finished, or discarded.
>= 14 Anything pending in a ":finally" clause.
@@ -6688,6 +6703,10 @@ A jump table for the options with a short description can be found at |Q_op|.
While the menu is active these keys have special meanings:
+ CTRL-Y - accept the currently selected match and stop
+ completion.
+ CTRL-E - end completion, go back to what was there before
+ selecting a match.
<Left> <Right> - select previous/next match (like CTRL-P/CTRL-N)
<Down> - in filename/menu name completion: move into a
subdirectory or submenu.
@@ -6725,6 +6744,8 @@ A jump table for the options with a short description can be found at |Q_op|.
complete first match.
"list:longest" When more than one match, list all matches and
complete till longest common string.
+ "list:lastused" When more than one buffer matches, sort buffers
+ by time last used (other than the current buffer).
When there is only a single match, it is fully completed in all cases.
Examples: >
diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt
index 4e0d91dae0..e3ba4ba181 100644
--- a/runtime/doc/sign.txt
+++ b/runtime/doc/sign.txt
@@ -176,9 +176,9 @@ See |sign_place()| for the equivalent Vim script function.
By default, the sign is assigned a default priority of 10. To
assign a different priority value, use "priority={prio}" to
- specify a value. The priority is used to determine the
- highlight group used when multiple signs are placed on the
- same line.
+ specify a value. The priority is used to determine the sign
+ that is displayed when multiple signs are placed on the same
+ line.
Examples: >
:sign place 5 line=3 name=sign1 file=a.py
@@ -198,7 +198,9 @@ See |sign_place()| for the equivalent Vim script function.
it (e.g., when the debugger has stopped at a breakpoint).
The optional "group={group}" attribute can be used before
- "file=" to select a sign in a particular group.
+ "file=" to select a sign in a particular group. The optional
+ "priority={prio}" attribute can be used to change the priority
+ of an existing sign.
:sign place {id} name={name} [buffer={nr}]
Same, but use buffer {nr}. If the buffer argument is not
diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt
index 110c6ef221..b88e26cdff 100644
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -504,7 +504,7 @@ then Vim will try to guess.
Multiple {inname} arguments can be given to combine
regions into one Vim spell file. Example: >
- :mkspell ~/.vim/spell/en /tmp/en_US /tmp/en_CA /tmp/en_AU
+ :mkspell ~/.config/nvim/spell/en /tmp/en_US /tmp/en_CA /tmp/en_AU
< This combines the English word lists for US, CA and AU
into one en.spl file.
Up to eight regions can be combined. *E754* *E755*
@@ -888,9 +888,9 @@ when using "cp1250" on Unix.
*spell-LOW* *spell-UPP*
Three lines in the affix file are needed. Simplistic example:
- FOL ~
- LOW ~
- UPP ~
+ FOL áëñ ~
+ LOW áëñ ~
+ UPP ÁËÑ ~
All three lines must have exactly the same number of characters.
@@ -905,9 +905,9 @@ The "UPP" line specifies the characters with upper-case. That is, a character
is upper-case where it's different from the character at the same position in
"FOL".
-An exception is made for the German sharp s . The upper-case version is
+An exception is made for the German sharp s ß. The upper-case version is
"SS". In the FOL/LOW/UPP lines it should be included, so that it's recognized
-as a word character, but use the character in all three.
+as a word character, but use the ß character in all three.
ASCII characters should be omitted, Vim always handles these in the same way.
When the encoding is UTF-8 no word characters need to be specified.
@@ -1377,7 +1377,7 @@ suggestions would spend most time trying all kind of weird compound words.
*spell-SYLLABLE*
The SYLLABLE item defines characters or character sequences that are used to
count the number of syllables in a word. Example:
- SYLLABLE aeiouy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui ~
+ SYLLABLE aáeéiíoóöõuúüûy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui ~
Before the first slash is the set of characters that are counted for one
syllable, also when repeated and mixed, until the next character that is not
@@ -1458,8 +1458,8 @@ alike. This is mostly used for a letter with different accents. This is used
to prefer suggestions with these letters substituted. Example:
MAP 2 ~
- MAP e ~
- MAP u ~
+ MAP eéëêè ~
+ MAP uüùúû ~
The first line specifies the number of MAP lines following. Vim ignores the
number, but the line must be there.
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index e3f0d593a7..af7d233619 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -184,12 +184,17 @@ argument.
the 'modifiable' and 'write' options can be set to enable
changes and writing.
- *-Z* *restricted-mode* *E145*
+ *-Z* *restricted-mode* *E145* *E981*
-Z Restricted mode. All commands that make use of an external
shell are disabled. This includes suspending with CTRL-Z,
- ":sh", filtering, the system() function, backtick expansion,
- delete(), rename(), mkdir(), writefile(), libcall(),
- jobstart(), etc.
+ ":sh", filtering, the system() function, backtick expansion
+ and libcall().
+ Also disallowed are delete(), rename(), mkdir(), jobstart(),
+ etc.
+ Interfaces, such as Python, Ruby and Lua, are also disabled,
+ since they could be used to execute shell commands.
+ Note that the user may still find a loophole to execute a
+ shell command, it has only been made difficult.
-e *-e* *-E*
-E Start Nvim in Ex mode |gQ|.
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 30ccb699cd..57337aeac2 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -4647,8 +4647,8 @@ in their own color.
":colorscheme" in a color scheme script.
To customize a colorscheme use another name, e.g.
- "~/.vim/colors/mine.vim", and use `:runtime` to load
- the original colorscheme: >
+ "~/.config/nvim/colors/mine.vim", and use `:runtime` to
+ load the original colorscheme: >
runtime colors/evening.vim
hi Statement ctermfg=Blue guifg=Blue
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index de54ce59b6..2817c1015b 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -161,7 +161,9 @@ the editor.
`cursor_shape`: "block", "horizontal", "vertical"
`cell_percentage`: Cell % occupied by the cursor.
`blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|.
- `attr_id`: Cursor attribute id (defined by `hl_attr_define`)
+ `attr_id`: Cursor attribute id (defined by `hl_attr_define`).
+ When attr_id is 0, the background and foreground
+ colors should be swapped.
`attr_id_lm`: Cursor attribute id for when 'langmap' is active.
`short_name`: Mode code name, see 'guicursor'.
`name`: Mode descriptive name.
@@ -592,6 +594,12 @@ tabs.
When |ext_messages| is active, no message grid is used, and this event
will not be sent.
+["win_viewport", grid, win, topline, botline, curline, curcol]
+ Indicates the range of buffer text displayed in the window, as well
+ as the cursor position in the buffer. All positions are zero-based.
+ `botline` is set to one more than the line count of the buffer, if
+ there are filler lines past the end.
+
==============================================================================
Popupmenu Events *ui-popupmenu*
diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt
index 99a78e7b05..44d653a1a7 100644
--- a/runtime/doc/usr_21.txt
+++ b/runtime/doc/usr_21.txt
@@ -339,21 +339,6 @@ window, move the cursor to the filename and press "O". Double clicking with
the mouse will also do this.
-UNIX AND MS-WINDOWS
-
-Some people have to do work on MS-Windows systems one day and on Unix another
-day. If you are one of them, consider adding "slash" and "unix" to
-'sessionoptions'. The session files will then be written in a format that can
-be used on both systems. This is the command to put in your |init.vim| file:
->
- :set sessionoptions+=unix,slash
-
-Vim will use the Unix format then, because the MS-Windows Vim can read and
-write Unix files, but Unix Vim can't read MS-Windows format session files.
-Similarly, MS-Windows Vim understands file names with / to separate names, but
-Unix Vim doesn't understand \.
-
-
SESSIONS AND SHADA
Sessions store many things, but not the position of marks, contents of
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index c770950a96..234f7801ab 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -578,8 +578,10 @@ used for. You can find an alphabetical list here: |functions|. Use CTRL-] on
the function name to jump to detailed help on it.
String manipulation: *string-functions*
- nr2char() get a character by its ASCII value
- char2nr() get ASCII value of a character
+ nr2char() get a character by its number value
+ list2str() get a character string from a list of numbers
+ char2nr() get number value of a character
+ str2list() get list of numbers from a string
str2nr() convert a string to a Number
str2float() convert a string to a Float
printf() format a string according to % items
@@ -607,6 +609,7 @@ String manipulation: *string-functions*
strcharpart() get part of a string using char index
strgetchar() get character from a string using char index
expand() expand special keywords
+ expandcmd() expand a command like done for `:edit`
iconv() convert text from one encoding to another
byteidx() byte index of a character in a string
byteidxcomp() like byteidx() but count composing characters
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 5835c7f314..376375e4ef 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -201,6 +201,7 @@ Options:
'guicursor' works in the terminal
'fillchars' flags: "msgsep" (see 'display'), "eob" for |hl-EndOfBuffer|
marker, "foldopen", "foldsep", "foldclose"
+ 'foldcolumn' supports up to 9 dynamic/fixed columns
'inccommand' shows interactive results for |:substitute|-like commands
'listchars' local to window
'pumblend' pseudo-transparent popupmenu
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 977e0daef7..b5623f4ea4 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -1021,6 +1021,9 @@ list of buffers. |unlisted-buffer|
x buffers with a read error
% current buffer
# alternate buffer
+ R terminal buffers with a running job
+ F terminal buffers with a finished job
+ t show time last used and sort buffers
Combining flags means they are "and"ed together, e.g.:
h+ hidden buffers which are modified
a+ active buffers which are modified
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 656ee36484..0b5003dc44 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: 2019 Nov 26
+" Last Change: 2020 Apr 29
" Listen very carefully, I will say this only once
if exists("did_load_filetypes")
@@ -229,11 +229,14 @@ au BufNewFile,BufRead *.bl setf blank
" Blkid cache file
au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml
+" BSDL
+au BufNewFile,BufRead *bsd,*.bsdl setf bsdl
+
" Bazel (http://bazel.io)
autocmd BufRead,BufNewFile *.bzl,WORKSPACE,BUILD.bazel setf bzl
if has("fname_case")
" There is another check for BUILD further below.
- autocmd BufRead,BufNewFile BUILD setf bzl
+ autocmd BufRead,BufNewFile BUILD setf bzl
endif
" C or lpc
@@ -487,7 +490,7 @@ au BufNewFile,BufRead *.rul
au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl')
" DOT
-au BufNewFile,BufRead *.dot setf dot
+au BufNewFile,BufRead *.dot,*.gv setf dot
" Dylan - lid files
au BufNewFile,BufRead *.lid setf dylanlid
@@ -829,6 +832,9 @@ au BufNewFile,BufRead *.k setf kwt
" Kivy
au BufNewFile,BufRead *.kv setf kivy
+" Kotlin
+au BufNewFile,BufRead *.kt,*.ktm,*.kts setf kotlin
+
" KDE script
au BufNewFile,BufRead *.ks setf kscript
@@ -1123,6 +1129,9 @@ au BufNewFile,BufRead pf.conf setf pf
" Pam conf
au BufNewFile,BufRead */etc/pam.conf setf pamconf
+" Pam environment
+au BufNewFile,BufRead pam_env.conf,.pam_environment setf pamenv
+
" PApp
au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp
@@ -1636,9 +1645,13 @@ au BufNewFile,BufRead */etc/sysctl.conf,*/etc/sysctl.d/*.conf setf sysctl
" Systemd unit files
au BufNewFile,BufRead */systemd/*.{automount,mount,path,service,socket,swap,target,timer} setf systemd
" Systemd overrides
-au BufNewFile,BufRead /etc/systemd/system/*.d/*.conf setf systemd
+au BufNewFile,BufRead */etc/systemd/system/*.d/*.conf setf systemd
+au BufNewFile,BufRead */.config/systemd/user/*.d/*.conf setf systemd
" Systemd temp files
-au BufNewFile,BufRead /etc/systemd/system/*.d/.#* setf systemd
+au BufNewFile,BufRead */etc/systemd/system/*.d/.#* setf systemd
+au BufNewFile,BufRead */etc/systemd/system/.#* setf systemd
+au BufNewFile,BufRead */.config/systemd/user/*.d/.#* setf systemd
+au BufNewFile,BufRead */.config/systemd/user/.#* setf systemd
" Synopsys Design Constraints
au BufNewFile,BufRead *.sdc setf sdc
@@ -1769,7 +1782,7 @@ au BufNewFile,BufRead *.va,*.vams setf verilogams
au BufNewFile,BufRead *.sv,*.svh setf systemverilog
" VHDL
-au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst setf vhdl
+au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst,*.vho setf vhdl
" Vim script
au BufNewFile,BufRead *.vim,*.vba,.exrc,_exrc setf vim
diff --git a/runtime/indent/testdir/runtest.vim b/runtime/indent/testdir/runtest.vim
index 9502c42f3e..945c2753e9 100644
--- a/runtime/indent/testdir/runtest.vim
+++ b/runtime/indent/testdir/runtest.vim
@@ -10,6 +10,7 @@ filetype indent on
syn on
set nowrapscan
set report=9999
+set modeline
au! SwapExists * call HandleSwapExists()
func HandleSwapExists()
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index cfa208f21c..61da2130c8 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -101,7 +101,7 @@ local function for_each_buffer_client(bufnr, callback)
for client_id in pairs(client_ids) do
local client = active_clients[client_id]
if client then
- callback(client, client_id)
+ callback(client, client_id, bufnr)
end
end
end
@@ -121,13 +121,9 @@ local function validate_encoding(encoding)
or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding))
end
-local function validate_command(input)
+function lsp._cmd_parts(input)
local cmd, cmd_args
- if type(input) == 'string' then
- -- Use a shell to execute the command if it is a string.
- cmd = vim.api.nvim_get_option('shell')
- cmd_args = {vim.api.nvim_get_option('shellcmdflag'), input}
- elseif vim.tbl_islist(input) then
+ if vim.tbl_islist(input) then
cmd = input[1]
cmd_args = {}
-- Don't mutate our input.
@@ -138,7 +134,7 @@ local function validate_command(input)
end
end
else
- error("cmd type must be string or list.")
+ error("cmd type must be list.")
end
return cmd, cmd_args
end
@@ -158,7 +154,7 @@ local function validate_client_config(config)
callbacks = { config.callbacks, "t", true };
capabilities = { config.capabilities, "t", true };
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
- cmd_env = { config.cmd_env, "f", true };
+ cmd_env = { config.cmd_env, "t", true };
name = { config.name, 's', true };
on_error = { config.on_error, "f", true };
on_exit = { config.on_exit, "f", true };
@@ -166,7 +162,7 @@ local function validate_client_config(config)
before_init = { config.before_init, "f", true };
offset_encoding = { config.offset_encoding, "s", true };
}
- local cmd, cmd_args = validate_command(config.cmd)
+ local cmd, cmd_args = lsp._cmd_parts(config.cmd)
local offset_encoding = valid_encodings.UTF16
if config.offset_encoding then
offset_encoding = validate_encoding(config.offset_encoding)
@@ -202,6 +198,7 @@ local function text_document_did_open_handler(bufnr, client)
}
}
client.notify('textDocument/didOpen', params)
+ util.buf_versions[bufnr] = params.textDocument.version
end
--- LSP client object.
@@ -452,7 +449,7 @@ function lsp.start_client(config)
-- The rootPath of the workspace. Is null if no folder is open.
--
-- @deprecated in favour of rootUri.
- rootPath = nil;
+ rootPath = config.root_dir;
-- The rootUri of the workspace. Is null if no folder is open. If both
-- `rootPath` and `rootUri` are set `rootUri` wins.
rootUri = vim.uri_from_fname(config.root_dir);
@@ -524,23 +521,25 @@ function lsp.start_client(config)
end
--- Checks capabilities before rpc.request-ing.
- function client.request(method, params, callback)
+ function client.request(method, params, callback, bufnr)
if not callback then
callback = resolve_callback(method)
or error("not found: request callback for client "..client.name)
end
- local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback)
+ local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
-- TODO keep these checks or just let it go anyway?
if (not client.resolved_capabilities.hover and method == 'textDocument/hover')
or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp')
or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition')
or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
+ or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
+ or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol')
then
- callback(unsupported_method(method), method, nil, client_id)
+ callback(unsupported_method(method), method, nil, client_id, bufnr)
return
end
return rpc.request(method, params, function(err, result)
- callback(err, method, result, client_id)
+ callback(err, method, result, client_id, bufnr)
end)
end
@@ -617,6 +616,8 @@ do
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return
end
+
+ util.buf_versions[bufnr] = changedtick
-- Lazy initialize these because clients may not even need them.
local incremental_changes = once(function(client)
local size_index = encoding_index[client.offset_encoding]
@@ -723,6 +724,7 @@ function lsp.buf_attach_client(bufnr, client_id)
client.notify('textDocument/didClose', params)
end
end)
+ util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
end;
-- TODO if we know all of the potential clients ahead of time, then we
@@ -840,8 +842,8 @@ function lsp.buf_request(bufnr, method, params, callback)
callback = { callback, 'f', true };
}
local client_request_ids = {}
- for_each_buffer_client(bufnr, function(client, client_id)
- local request_success, request_id = client.request(method, params, callback)
+ for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
+ local request_success, request_id = client.request(method, params, callback, resolved_bufnr)
-- This could only fail if the client shut down in the time since we looked
-- it up and we did the request, which should be rare.
@@ -897,21 +899,22 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
return request_results
end
---- Sends a notification to all servers attached to the buffer.
----
---@param bufnr (optional, number) Buffer handle, or 0 for current
---@param method (string) LSP method name
---@param params (string) Parameters to send to the server
----
---@returns nil
+--- Send a notification to a server
+-- @param bufnr [number] (optional): The number of the buffer
+-- @param method [string]: Name of the request method
+-- @param params [string]: Arguments to send to the server
+--
+-- @returns true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
validate {
bufnr = { bufnr, 'n', true };
method = { method, 's' };
}
- for_each_buffer_client(bufnr, function(client, _client_id)
- client.rpc.notify(method, params)
+ local resp = false
+ for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
+ if client.rpc.notify(method, params) then resp = true end
end)
+ return resp
end
--- Implements 'omnifunc' compatible LSP completion.
@@ -949,12 +952,14 @@ function lsp.omnifunc(findstart, base)
-- Get the start position of the current keyword
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
+ local prefix = line_to_cursor:sub(textMatch+1)
+
local params = util.make_position_params()
local items = {}
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, _, result)
if err or not result then return end
- local matches = util.text_document_completion_list_to_complete_items(result)
+ local matches = util.text_document_completion_list_to_complete_items(result, prefix)
-- TODO(ashkan): is this the best way to do this?
vim.list_extend(items, matches)
vim.fn.complete(textMatch+1, items)
@@ -1013,5 +1018,18 @@ function lsp.get_log_path()
return log.get_filename()
end
+-- Define the LspDiagnostics signs if they're not defined already.
+do
+ local function define_default_sign(name, properties)
+ if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then
+ vim.fn.sign_define(name, properties)
+ end
+ end
+ define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''})
+ define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''})
+ define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''})
+ define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''})
+end
+
return lsp
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index a6a05fb095..0b45951a56 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -23,17 +23,15 @@ local function request(method, params, callback)
return vim.lsp.buf_request(0, method, params, callback)
end
-function M.hover()
- local params = util.make_position_params()
- request('textDocument/hover', params)
+function M.server_ready()
+ return not not vim.lsp.buf_notify(0, "window/progress", {})
end
-function M.peek_definition()
+function M.hover()
local params = util.make_position_params()
- request('textDocument/peekDefinition', params)
+ request('textDocument/hover', params)
end
-
function M.declaration()
local params = util.make_position_params()
request('textDocument/declaration', params)
@@ -68,8 +66,9 @@ end
function M.formatting(options)
validate { options = {options, 't', true} }
+ local sts = vim.bo.softtabstop;
options = vim.tbl_extend('keep', options or {}, {
- tabSize = vim.bo.tabstop;
+ tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
insertSpaces = vim.bo.expandtab;
})
local params = {
@@ -85,8 +84,9 @@ function M.range_formatting(options, start_pos, end_pos)
start_pos = {start_pos, 't', true};
end_pos = {end_pos, 't', true};
}
+ local sts = vim.bo.softtabstop;
options = vim.tbl_extend('keep', options or {}, {
- tabSize = vim.bo.tabstop;
+ tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
insertSpaces = vim.bo.expandtab;
})
local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<'))
@@ -132,5 +132,34 @@ function M.references(context)
request('textDocument/references', params)
end
+function M.document_symbol()
+ local params = { textDocument = util.make_text_document_params() }
+ request('textDocument/documentSymbol', params)
+end
+
+function M.workspace_symbol(query)
+ query = query or npcall(vfn.input, "Query: ")
+ local params = {query = query}
+ request('workspace/symbol', params)
+end
+
+--- Send request to server to resolve document highlights for the
+--- current text document position. This request can be associated
+--- to key mapping or to events such as `CursorHold`, eg:
+---
+--- <pre>
+--- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]]
+--- </pre>
+function M.document_highlight()
+ local params = util.make_position_params()
+ request('textDocument/documentHighlight', params)
+end
+
+function M.clear_references()
+ util.buf_clear_references()
+end
+
return M
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
index 794140ee2e..09ca4b61e4 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -17,7 +17,11 @@ M['workspace/applyEdit'] = function(_, _, workspace_edit)
if workspace_edit.label then
print("Workspace edit", workspace_edit.label)
end
- util.apply_workspace_edit(workspace_edit.edit)
+ local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
+ return {
+ applied = status;
+ failureReason = result;
+ }
end
M['textDocument/publishDiagnostics'] = function(_, _, result)
@@ -29,18 +33,40 @@ M['textDocument/publishDiagnostics'] = function(_, _, result)
return
end
util.buf_clear_diagnostics(bufnr)
+
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
+ -- The diagnostic's severity. Can be omitted. If omitted it is up to the
+ -- client to interpret diagnostics as error, warning, info or hint.
+ -- TODO: Replace this with server-specific heuristics to infer severity.
+ for _, diagnostic in ipairs(result.diagnostics) do
+ if diagnostic.severity == nil then
+ diagnostic.severity = protocol.DiagnosticSeverity.Error
+ end
+ end
+
util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
util.buf_diagnostics_underline(bufnr, result.diagnostics)
util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
- -- util.set_loclist(result.diagnostics)
+ util.buf_diagnostics_signs(bufnr, result.diagnostics)
+ vim.api.nvim_command("doautocmd User LspDiagnosticsChanged")
end
M['textDocument/references'] = function(_, _, result)
if not result then return end
- util.set_qflist(result)
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+
+local symbol_callback = function(_, _, result, _, bufnr)
+ if not result or vim.tbl_isempty(result) then return end
+
+ util.set_qflist(util.symbols_to_items(result, bufnr))
api.nvim_command("copen")
api.nvim_command("wincmd p")
end
+M['textDocument/documentSymbol'] = symbol_callback
+M['workspace/symbol'] = symbol_callback
M['textDocument/rename'] = function(_, _, result)
if not result then return end
@@ -63,8 +89,9 @@ M['textDocument/completion'] = function(_, _, result)
local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
local line_to_cursor = line:sub(col+1)
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
+ local prefix = line_to_cursor:sub(textMatch+1)
- local matches = util.text_document_completion_list_to_complete_items(result)
+ local matches = util.text_document_completion_list_to_complete_items(result, prefix)
vim.fn.complete(textMatch+1, matches)
end
@@ -93,11 +120,20 @@ local function location_callback(_, method, result)
local _ = log.info() and log.info(method, 'No location found')
return nil
end
- util.jump_to_location(result[1])
- if #result > 1 then
- util.set_qflist(result)
- api.nvim_command("copen")
- api.nvim_command("wincmd p")
+
+ -- 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])
+
+ if #result > 1 then
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+ else
+ util.jump_to_location(result)
end
end
@@ -106,72 +142,13 @@ M['textDocument/definition'] = location_callback
M['textDocument/typeDefinition'] = location_callback
M['textDocument/implementation'] = location_callback
---- Convert SignatureHelp response to preview contents.
--- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp
-local function signature_help_to_preview_contents(input)
- if not input.signatures then
- return
- end
- --The active signature. If omitted or the value lies outside the range of
- --`signatures` the value defaults to zero or is ignored if `signatures.length
- --=== 0`. Whenever possible implementors should make an active decision about
- --the active signature and shouldn't rely on a default value.
- local contents = {}
- local active_signature = input.activeSignature or 0
- -- If the activeSignature is not inside the valid range, then clip it.
- if active_signature >= #input.signatures then
- active_signature = 0
- end
- local signature = input.signatures[active_signature + 1]
- if not signature then
- return
- end
- vim.list_extend(contents, vim.split(signature.label, '\n', true))
- if signature.documentation then
- util.convert_input_to_markdown_lines(signature.documentation, contents)
- end
- if input.parameters then
- local active_parameter = input.activeParameter or 0
- -- If the activeParameter is not inside the valid range, then clip it.
- if active_parameter >= #input.parameters then
- active_parameter = 0
- end
- local parameter = signature.parameters and signature.parameters[active_parameter]
- if parameter then
- --[=[
- --Represents a parameter of a callable-signature. A parameter can
- --have a label and a doc-comment.
- interface ParameterInformation {
- --The label of this parameter information.
- --
- --Either a string or an inclusive start and exclusive end offsets within its containing
- --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
- --string representation as `Position` and `Range` does.
- --
- --*Note*: a label of type string should be a substring of its containing signature label.
- --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
- label: string | [number, number];
- --The human-readable doc-comment of this parameter. Will be shown
- --in the UI but can be omitted.
- documentation?: string | MarkupContent;
- }
- --]=]
- -- TODO highlight parameter
- if parameter.documentation then
- util.convert_input_to_markdown_lines(parameter.documentation, contents)
- end
- end
- end
- return contents
-end
-
M['textDocument/signatureHelp'] = function(_, method, result)
util.focusable_preview(method, function()
if not (result and result.signatures and result.signatures[1]) then
return { 'No signature available' }
end
-- TODO show popup when signatures is empty?
- local lines = signature_help_to_preview_contents(result)
+ local lines = util.convert_signature_help_to_markdown_lines(result)
lines = util.trim_empty_lines(lines)
if vim.tbl_isempty(lines) then
return { 'No signature available' }
@@ -180,22 +157,33 @@ M['textDocument/signatureHelp'] = function(_, method, result)
end)
end
-M['textDocument/peekDefinition'] = function(_, _, result, _)
- if not (result and result[1]) then return end
- local loc = result[1]
- local bufnr = vim.uri_to_bufnr(loc.uri) or error("not found: "..tostring(loc.uri))
- local start = loc.range.start
- local finish = loc.range["end"]
- util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 })
- local headbuf = util.open_floating_preview({"Peek:"}, nil, {
- offset_y = -(finish.line - start.line);
- width = finish.character - start.character + 2;
- })
- -- TODO(ashkan) change highlight group?
- api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1)
+M['textDocument/documentHighlight'] = function(_, _, result, _)
+ if not result then return end
+ local bufnr = api.nvim_get_current_buf()
+ util.buf_highlight_references(bufnr, result)
+end
+
+M['window/logMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ local client = vim.lsp.get_client_by_id(client_id)
+ local client_name = client and client.name or string.format("id=%d", client_id)
+ if not client then
+ err_message("LSP[", client_name, "] client has shut down after sending the message")
+ end
+ if message_type == protocol.MessageType.Error then
+ log.error(message)
+ elseif message_type == protocol.MessageType.Warning then
+ log.warn(message)
+ elseif message_type == protocol.MessageType.Info then
+ log.info(message)
+ else
+ log.debug(message)
+ end
+ return result
end
-local function log_message(_, _, result, client_id)
+M['window/showMessage'] = function(_, _, result, client_id)
local message_type = result.type
local message = result.message
local client = vim.lsp.get_client_by_id(client_id)
@@ -212,9 +200,6 @@ local function log_message(_, _, result, client_id)
return result
end
-M['window/showMessage'] = log_message
-M['window/logMessage'] = log_message
-
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, method, params, client_id)
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 974eaae38c..c0db5e5485 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -13,7 +13,6 @@ log.levels = {
INFO = 2;
WARN = 3;
ERROR = 4;
- -- FATAL = 4;
}
-- Default log level is warn.
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index f64b0b50e7..ee6e29bac0 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -644,6 +644,18 @@ function protocol.make_client_capabilities()
-- TODO(tjdevries): Implement this
contextSupport = false;
};
+ declaration = {
+ linkSupport = true;
+ };
+ definition = {
+ linkSupport = true;
+ };
+ implementation = {
+ linkSupport = true;
+ };
+ typeDefinition = {
+ linkSupport = true;
+ };
hover = {
dynamicRegistration = false;
contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
@@ -663,21 +675,36 @@ function protocol.make_client_capabilities()
documentHighlight = {
dynamicRegistration = false
};
- -- documentSymbol = {
- -- dynamicRegistration = false;
- -- symbolKind = {
- -- valueSet = (function()
- -- local res = {}
- -- for k in pairs(protocol.SymbolKind) do
- -- if type(k) == 'string' then table.insert(res, k) end
- -- end
- -- return res
- -- end)();
- -- };
- -- hierarchicalDocumentSymbolSupport = false;
- -- };
+ documentSymbol = {
+ dynamicRegistration = false;
+ symbolKind = {
+ valueSet = (function()
+ local res = {}
+ for k in pairs(protocol.SymbolKind) do
+ if type(k) == 'number' then table.insert(res, k) end
+ end
+ return res
+ end)();
+ };
+ hierarchicalDocumentSymbolSupport = true;
+ };
+ };
+ workspace = {
+ symbol = {
+ dynamicRegistration = false;
+ symbolKind = {
+ valueSet = (function()
+ local res = {}
+ for k in pairs(protocol.SymbolKind) do
+ if type(k) == 'number' then table.insert(res, k) end
+ end
+ return res
+ end)();
+ };
+ hierarchicalWorkspaceSymbolSupport = true;
+ };
+ applyEdit = true;
};
- workspace = nil;
experimental = nil;
}
end
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index e13b05610b..dad1dc11f1 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -33,38 +33,25 @@ local function convert_NIL(v)
return v
end
--- If a dictionary is passed in, turn it into a list of string of "k=v"
--- Accepts a table which can be composed of k=v strings or map-like
--- specification, such as:
---
--- ```
--- {
--- "PRODUCTION=false";
--- "PATH=/usr/bin/";
--- PORT = 123;
--- HOST = "0.0.0.0";
--- }
--- ```
---
--- Non-string values will be cast with `tostring`
-local function force_env_list(final_env)
- if final_env then
- local env = final_env
- final_env = {}
- for k,v in pairs(env) do
- -- If it's passed in as a dict, then convert to list of "k=v"
- if type(k) == "string" then
- table.insert(final_env, k..'='..tostring(v))
- elseif type(v) == 'string' then
- table.insert(final_env, v)
- else
- -- TODO is this right or should I exception here?
- -- Try to coerce other values to string.
- table.insert(final_env, tostring(v))
- end
- end
- return final_env
+--- Merges current process env with the given env and returns the result as
+--- a list of "k=v" strings.
+---
+--- Example:
+---
+--- { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", }
+--- => { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", }
+local function env_merge(env)
+ if env == nil then
+ return env
end
+ -- Merge.
+ env = vim.tbl_extend('force', vim.fn.environ(), env)
+ local final_env = {}
+ for k,v in pairs(env) do
+ assert(type(k) == 'string', 'env must be a dict')
+ table.insert(final_env, k..'='..tostring(v))
+ end
+ return final_env
end
local function format_message_with_content_length(encoded_message)
@@ -153,14 +140,23 @@ local function format_rpc_error(err)
validate {
err = { err, 't' };
}
- local code_name = assert(protocol.ErrorCodes[err.code], "err.code is invalid")
- local message_parts = {"RPC", code_name}
+
+ -- There is ErrorCodes in the LSP specification,
+ -- but in ResponseError.code it is not used and the actual type is number.
+ local code
+ if protocol.ErrorCodes[err.code] then
+ code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
+ else
+ code = string.format("code_name = unknown, code = %s,", err.code)
+ end
+
+ local message_parts = {"RPC[Error]", code}
if err.message then
- table.insert(message_parts, "message = ")
+ table.insert(message_parts, "message =")
table.insert(message_parts, string.format("%q", err.message))
end
if err.data then
- table.insert(message_parts, "data = ")
+ table.insert(message_parts, "data =")
table.insert(message_parts, vim.inspect(err.data))
end
return table.concat(message_parts, ' ')
@@ -262,7 +258,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para
if spawn_params.cwd then
assert(is_dir(spawn_params.cwd), "cwd must be a directory")
end
- spawn_params.env = force_env_list(extra_spawn_params.env)
+ spawn_params.env = env_merge(extra_spawn_params.env)
end
handle, pid = uv.spawn(cmd, spawn_params, onexit)
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index df82e2d412..099a77099b 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -6,6 +6,31 @@ local list_extend = vim.list_extend
local M = {}
+--- Diagnostics received from the server via `textDocument/publishDiagnostics`
+-- by buffer.
+--
+-- {<bufnr>: {diagnostics}}
+--
+-- This contains only entries for active buffers. Entries for detached buffers
+-- are discarded.
+--
+-- If you override the `textDocument/publishDiagnostic` callback,
+-- this will be empty unless you call `buf_diagnostics_save_positions`.
+--
+--
+-- Diagnostic is:
+--
+-- {
+-- range: Range
+-- message: string
+-- severity?: DiagnosticSeverity
+-- code?: number | string
+-- source?: string
+-- tags?: DiagnosticTag[]
+-- relatedInformation?: DiagnosticRelatedInformation[]
+-- }
+M.diagnostics_by_buf = {}
+
local split = vim.split
local function split_lines(value)
return split(value, '\n', true)
@@ -71,16 +96,28 @@ end)
function M.apply_text_edits(text_edits, bufnr)
if not next(text_edits) then return end
+ if not api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
local start_line, finish_line = math.huge, -1
local cleaned = {}
for i, e in ipairs(text_edits) do
+ -- adjust start and end column for UTF-16 encoding of non-ASCII characters
+ local start_row = e.range.start.line
+ local start_col = e.range.start.character
+ local start_bline = api.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1]
+ start_col = vim.str_byteindex(start_bline, start_col)
+ local end_row = e.range["end"].line
+ local end_col = e.range["end"].character
+ local end_bline = api.nvim_buf_get_lines(bufnr, end_row, end_row+1, true)[1]
+ end_col = vim.str_byteindex(end_bline, end_col)
start_line = math.min(e.range.start.line, start_line)
finish_line = math.max(e.range["end"].line, finish_line)
-- TODO(ashkan) sanity check ranges for overlap.
table.insert(cleaned, {
i = i;
- A = {e.range.start.line; e.range.start.character};
- B = {e.range["end"].line; e.range["end"].character};
+ A = {start_row; start_col};
+ B = {end_row; end_col};
lines = vim.split(e.newText, '\n', true);
})
end
@@ -131,10 +168,12 @@ end
function M.apply_text_document_edit(text_document_edit)
local text_document = text_document_edit.textDocument
local bufnr = vim.uri_to_bufnr(text_document.uri)
- -- TODO(ashkan) check this is correct.
- if api.nvim_buf_get_changedtick(bufnr) > text_document.version then
- print("Buffer ", text_document.uri, " newer than edits.")
- return
+ if text_document.version then
+ -- `VersionedTextDocumentIdentifier`s version may be null https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier
+ if text_document.version ~= vim.NIL and M.buf_versions[bufnr] ~= nil and M.buf_versions[bufnr] > text_document.version then
+ print("Buffer ", text_document.uri, " newer than edits.")
+ return
+ end
end
M.apply_text_edits(text_document_edit.edits, bufnr)
end
@@ -145,15 +184,55 @@ function M.get_current_line_to_cursor()
return line:sub(pos[2]+1)
end
+-- Sort by CompletionItem.sortText
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+local function sort_completion_items(items)
+ if items[1] and items[1].sortText then
+ table.sort(items, function(a, b) return a.sortText < b.sortText
+ end)
+ end
+end
+
+-- Returns text that should be inserted when selecting completion item. The precedence is as follows:
+-- textEdit.newText > insertText > label
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+local function get_completion_word(item)
+ if item.textEdit ~= nil and item.textEdit.newText ~= nil then
+ return item.textEdit.newText
+ elseif item.insertText ~= nil then
+ return item.insertText
+ end
+ return item.label
+end
+
+-- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned.
+-- So we exclude completion candidates whose prefix does not match.
+local function remove_unmatch_completion_items(items, prefix)
+ return vim.tbl_filter(function(item)
+ local word = get_completion_word(item)
+ return vim.startswith(word, prefix)
+ end, items)
+end
+
+-- Acording to LSP spec, if the client set "completionItemKind.valueSet",
+-- the client must handle it properly even if it receives a value outside the specification.
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+function M._get_completion_item_kind_name(completion_item_kind)
+ return protocol.CompletionItemKind[completion_item_kind] or "Unknown"
+end
+
--- Getting vim complete-items with incomplete flag.
-- @params CompletionItem[], CompletionList or nil (https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
-- @return { matches = complete-items table, incomplete = boolean }
-function M.text_document_completion_list_to_complete_items(result)
+function M.text_document_completion_list_to_complete_items(result, prefix)
local items = M.extract_completion_items(result)
if vim.tbl_isempty(items) then
return {}
end
+ items = remove_unmatch_completion_items(items, prefix)
+ sort_completion_items(items)
+
local matches = {}
for _, completion_item in ipairs(items) do
@@ -169,16 +248,23 @@ function M.text_document_completion_list_to_complete_items(result)
end
end
- local word = completion_item.insertText or completion_item.label
+ local word = get_completion_word(completion_item)
table.insert(matches, {
word = word,
abbr = completion_item.label,
- kind = protocol.CompletionItemKind[completion_item.kind] or '',
+ kind = M._get_completion_item_kind_name(completion_item.kind),
menu = completion_item.detail or '',
info = info,
icase = 1,
- dup = 0,
+ dup = 1,
empty = 1,
+ user_data = {
+ nvim = {
+ lsp = {
+ completion_item = completion_item
+ }
+ }
+ },
})
end
@@ -245,12 +331,71 @@ function M.convert_input_to_markdown_lines(input, contents)
end
end
end
- if contents[1] == '' or contents[1] == nil then
+ if (contents[1] == '' or contents[1] == nil) and #contents == 1 then
return {}
end
return contents
end
+--- Convert SignatureHelp response to markdown lines.
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp
+function M.convert_signature_help_to_markdown_lines(signature_help)
+ if not signature_help.signatures then
+ return
+ end
+ --The active signature. If omitted or the value lies outside the range of
+ --`signatures` the value defaults to zero or is ignored if `signatures.length
+ --=== 0`. Whenever possible implementors should make an active decision about
+ --the active signature and shouldn't rely on a default value.
+ local contents = {}
+ 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
+ active_signature = 0
+ end
+ local signature = signature_help.signatures[active_signature + 1]
+ if not signature then
+ return
+ end
+ vim.list_extend(contents, vim.split(signature.label, '\n', true))
+ if signature.documentation then
+ M.convert_input_to_markdown_lines(signature.documentation, contents)
+ end
+ if signature_help.parameters then
+ local active_parameter = signature_help.activeParameter or 0
+ -- If the activeParameter is not inside the valid range, then clip it.
+ if active_parameter >= #signature_help.parameters then
+ active_parameter = 0
+ end
+ local parameter = signature.parameters and signature.parameters[active_parameter]
+ if parameter then
+ --[=[
+ --Represents a parameter of a callable-signature. A parameter can
+ --have a label and a doc-comment.
+ interface ParameterInformation {
+ --The label of this parameter information.
+ --
+ --Either a string or an inclusive start and exclusive end offsets within its containing
+ --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
+ --string representation as `Position` and `Range` does.
+ --
+ --*Note*: a label of type string should be a substring of its containing signature label.
+ --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
+ label: string | [number, number];
+ --The human-readable doc-comment of this parameter. Will be shown
+ --in the UI but can be omitted.
+ documentation?: string | MarkupContent;
+ }
+ --]=]
+ -- TODO highlight parameter
+ if parameter.documentation then
+ M.convert_input_help_to_markdown_lines(parameter.documentation, contents)
+ end
+ end
+ end
+ return contents
+end
+
function M.make_floating_popup_options(width, height, opts)
validate {
opts = { opts, 't', true };
@@ -297,14 +442,24 @@ function M.make_floating_popup_options(width, height, opts)
end
function M.jump_to_location(location)
- if location.uri == nil then return end
- local bufnr = vim.uri_to_bufnr(location.uri)
+ -- location may be Location or LocationLink
+ local uri = location.uri or location.targetUri
+ if uri == nil then return end
+ local bufnr = vim.uri_to_bufnr(uri)
-- Save position in jumplist
vim.cmd "normal! m'"
- -- TODO(ashkan) use tagfunc here to update tagstack.
+
+ -- Push a new item into tagstack
+ local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0}
+ local items = {{tagname=vim.fn.expand('<cword>'), from=from}}
+ vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't')
+
+ --- Jump to new location (adjusting for UTF-16 encoding of characters)
api.nvim_set_current_buf(bufnr)
- local row = location.range.start.line
- local col = location.range.start.character
+ api.nvim_buf_set_option(0, 'buflisted', true)
+ local range = location.range or location.targetSelectionRange
+ local row = range.start.line
+ local col = range.start.character
local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
col = vim.str_byteindex(line, col)
api.nvim_win_set_cursor(0, {row + 1, col})
@@ -498,36 +653,10 @@ function M.open_floating_preview(contents, filetype, opts)
end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
- -- TODO make InsertCharPre disappearing optional?
- api.nvim_command("autocmd CursorMoved,BufHidden,InsertCharPre <buffer> ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)")
+ M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden"}, floating_winnr)
return floating_bufnr, floating_winnr
end
-local function validate_lsp_position(pos)
- validate { pos = {pos, 't'} }
- validate {
- line = {pos.line, 'n'};
- character = {pos.character, 'n'};
- }
- return true
-end
-
-function M.open_floating_peek_preview(bufnr, start, finish, opts)
- validate {
- bufnr = {bufnr, 'n'};
- start = {start, validate_lsp_position, 'valid start Position'};
- finish = {finish, validate_lsp_position, 'valid finish Position'};
- opts = { opts, 't', true };
- }
- local width = math.max(finish.character - start.character + 1, 1)
- local height = math.max(finish.line - start.line + 1, 1)
- local floating_winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
- api.nvim_win_set_cursor(floating_winnr, {start.line+1, start.character})
- api.nvim_command("autocmd CursorMoved * ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)")
- return floating_winnr
-end
-
-
local function highlight_range(bufnr, ns, hiname, start, finish)
if start[1] == finish[1] then
-- TODO care about encoding here since this is in byte index?
@@ -542,10 +671,9 @@ local function highlight_range(bufnr, ns, hiname, start, finish)
end
do
- local all_buffer_diagnostics = {}
-
local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics")
-
+ local reference_ns = api.nvim_create_namespace("vim_lsp_references")
+ local sign_ns = 'vim_lsp_signs'
local underline_highlight_name = "LspDiagnosticsUnderline"
vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name))
for kind, _ in pairs(protocol.DiagnosticSeverity) do
@@ -573,12 +701,18 @@ do
table.insert(cmd_parts, k.."="..v)
end
api.nvim_command(table.concat(cmd_parts, ' '))
+ api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name)
severity_highlights[severity] = highlight_name
end
function M.buf_clear_diagnostics(bufnr)
validate { bufnr = {bufnr, 'n', true} }
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
+
+ -- clear sign group
+ vim.fn.sign_unplace(sign_ns, {buffer=bufnr})
+
+ -- clear virtual text namespace
api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1)
end
@@ -593,13 +727,12 @@ do
-- if #marks == 0 then
-- return
-- end
- -- local buffer_diagnostics = all_buffer_diagnostics[bufnr]
local lines = {"Diagnostics:"}
local highlights = {{0, "Bold"}}
- local buffer_diagnostics = all_buffer_diagnostics[bufnr]
+ local buffer_diagnostics = M.diagnostics_by_buf[bufnr]
if not buffer_diagnostics then return end
- local line_diagnostics = buffer_diagnostics[line]
+ local line_diagnostics = M.diagnostics_group_by_line(buffer_diagnostics)[line]
if not line_diagnostics then return end
for i, diagnostic in ipairs(line_diagnostics) do
@@ -610,6 +743,7 @@ do
-- TODO(ashkan) make format configurable?
local prefix = string.format("%d. ", i)
local hiname = severity_highlights[diagnostic.severity]
+ assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
local message_lines = split_lines(diagnostic.message)
table.insert(lines, prefix..message_lines[1])
table.insert(highlights, {#prefix + 1, hiname})
@@ -627,6 +761,8 @@ do
return popup_bufnr, winnr
end
+ --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf
+ --
function M.buf_diagnostics_save_positions(bufnr, diagnostics)
validate {
bufnr = {bufnr, 'n', true};
@@ -635,31 +771,17 @@ do
if not diagnostics then return end
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
- if not all_buffer_diagnostics[bufnr] then
+ if not M.diagnostics_by_buf[bufnr] then
-- Clean up our data when the buffer unloads.
api.nvim_buf_attach(bufnr, false, {
on_detach = function(b)
- all_buffer_diagnostics[b] = nil
+ M.diagnostics_by_buf[b] = nil
end
})
end
- all_buffer_diagnostics[bufnr] = {}
- local buffer_diagnostics = all_buffer_diagnostics[bufnr]
-
- for _, diagnostic in ipairs(diagnostics) do
- local start = diagnostic.range.start
- -- local mark_id = api.nvim_buf_set_extmark(bufnr, diagnostic_ns, 0, start.line, 0, {})
- -- buffer_diagnostics[mark_id] = diagnostic
- local line_diagnostics = buffer_diagnostics[start.line]
- if not line_diagnostics then
- line_diagnostics = {}
- buffer_diagnostics[start.line] = line_diagnostics
- end
- table.insert(line_diagnostics, diagnostic)
- end
+ M.diagnostics_by_buf[bufnr] = diagnostics
end
-
function M.buf_diagnostics_underline(bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
local start = diagnostic.range["start"]
@@ -681,15 +803,46 @@ do
end
end
- function M.buf_diagnostics_virtual_text(bufnr, diagnostics)
- local buffer_line_diagnostics = all_buffer_diagnostics[bufnr]
- if not buffer_line_diagnostics then
- M.buf_diagnostics_save_positions(bufnr, diagnostics)
+ function M.buf_clear_references(bufnr)
+ validate { bufnr = {bufnr, 'n', true} }
+ api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1)
+ end
+
+ function M.buf_highlight_references(bufnr, references)
+ validate { bufnr = {bufnr, 'n', true} }
+ for _, reference in ipairs(references) do
+ local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]}
+ local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]}
+ local document_highlight_kind = {
+ [protocol.DocumentHighlightKind.Text] = "LspReferenceText";
+ [protocol.DocumentHighlightKind.Read] = "LspReferenceRead";
+ [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite";
+ }
+ local kind = reference["kind"] or protocol.DocumentHighlightKind.Text
+ highlight_range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
end
- buffer_line_diagnostics = all_buffer_diagnostics[bufnr]
- if not buffer_line_diagnostics then
+ end
+
+ function M.diagnostics_group_by_line(diagnostics)
+ if not diagnostics then return end
+ local diagnostics_by_line = {}
+ for _, diagnostic in ipairs(diagnostics) do
+ local start = diagnostic.range.start
+ local line_diagnostics = diagnostics_by_line[start.line]
+ if not line_diagnostics then
+ line_diagnostics = {}
+ diagnostics_by_line[start.line] = line_diagnostics
+ end
+ table.insert(line_diagnostics, diagnostic)
+ end
+ return diagnostics_by_line
+ end
+
+ function M.buf_diagnostics_virtual_text(bufnr, diagnostics)
+ if not diagnostics then
return
end
+ local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics)
for line, line_diags in pairs(buffer_line_diagnostics) do
local virt_texts = {}
for i = 1, #line_diags - 1 do
@@ -701,10 +854,36 @@ do
api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {})
end
end
+
+ function M.buf_diagnostics_count(kind)
+ local bufnr = vim.api.nvim_get_current_buf()
+ local diagnostics = M.diagnostics_by_buf[bufnr]
+ if not diagnostics then return end
+ local count = 0
+ for _, diagnostic in pairs(diagnostics) do
+ if protocol.DiagnosticSeverity[kind] == diagnostic.severity then
+ count = count + 1
+ end
+ end
+ return count
+ end
+
+ local diagnostic_severity_map = {
+ [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign";
+ [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign";
+ [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign";
+ [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign";
+ }
+
+ function M.buf_diagnostics_signs(bufnr, diagnostics)
+ for _, diagnostic in ipairs(diagnostics) do
+ vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)})
+ end
+ end
end
local position_sort = sort_by_key(function(v)
- return {v.line, v.character}
+ return {v.start.line, v.start.character}
end)
-- Returns the items with the byte position calculated correctly and in sorted
@@ -719,19 +898,25 @@ function M.locations_to_items(locations)
end;
})
for _, d in ipairs(locations) do
- local start = d.range.start
- local fname = assert(vim.uri_to_fname(d.uri))
- table.insert(grouped[fname], start)
+ -- locations may be Location or LocationLink
+ local uri = d.uri or d.targetUri
+ local fname = assert(vim.uri_to_fname(uri))
+ local range = d.range or d.targetSelectionRange
+ table.insert(grouped[fname], {start = range.start})
end
+
+
local keys = vim.tbl_keys(grouped)
table.sort(keys)
-- TODO(ashkan) I wish we could do this lazily.
for _, fname in ipairs(keys) do
local rows = grouped[fname]
+
table.sort(rows, position_sort)
local i = 0
for line in io.lines(fname) do
- for _, pos in ipairs(rows) do
+ for _, temp in ipairs(rows) do
+ local pos = temp.start
local row = pos.line
if i == row then
local col
@@ -754,23 +939,65 @@ function M.locations_to_items(locations)
return items
end
--- locations is Location[]
--- Only sets for the current window.
-function M.set_loclist(locations)
+function M.set_loclist(items)
vim.fn.setloclist(0, {}, ' ', {
title = 'Language Server';
- items = M.locations_to_items(locations);
+ items = items;
})
end
--- locations is Location[]
-function M.set_qflist(locations)
+function M.set_qflist(items)
vim.fn.setqflist({}, ' ', {
title = 'Language Server';
- items = M.locations_to_items(locations);
+ items = items;
})
end
+-- Acording to LSP spec, if the client set "symbolKind.valueSet",
+-- the client must handle it properly even if it receives a value outside the specification.
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
+function M._get_symbol_kind_name(symbol_kind)
+ return protocol.SymbolKind[symbol_kind] or "Unknown"
+end
+
+--- Convert symbols to quickfix list items
+---
+--@symbols DocumentSymbol[] or SymbolInformation[]
+function M.symbols_to_items(symbols, bufnr)
+ local function _symbols_to_items(_symbols, _items, _bufnr)
+ for _, symbol in ipairs(_symbols) do
+ if symbol.location then -- SymbolInformation type
+ local range = symbol.location.range
+ local kind = M._get_symbol_kind_name(symbol.kind)
+ table.insert(_items, {
+ filename = vim.uri_to_fname(symbol.location.uri),
+ lnum = range.start.line + 1,
+ col = range.start.character + 1,
+ kind = kind,
+ text = '['..kind..'] '..symbol.name,
+ })
+ elseif symbol.range then -- DocumentSymbole type
+ local kind = M._get_symbol_kind_name(symbol.kind)
+ table.insert(_items, {
+ -- bufnr = _bufnr,
+ filename = vim.api.nvim_buf_get_name(_bufnr),
+ lnum = symbol.range.start.line + 1,
+ col = symbol.range.start.character + 1,
+ kind = kind,
+ text = '['..kind..'] '..symbol.name
+ })
+ if symbol.children then
+ for _, v in ipairs(_symbols_to_items(symbol.children, _items, _bufnr)) do
+ vim.list_extend(_items, v)
+ end
+ end
+ end
+ end
+ return _items
+ end
+ return _symbols_to_items(symbols, {}, bufnr)
+end
+
-- Remove empty lines from the beginning and end.
function M.trim_empty_lines(lines)
local start = 1
@@ -823,11 +1050,15 @@ function M.make_position_params()
local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
col = str_utfindex(line, col)
return {
- textDocument = { uri = vim.uri_from_bufnr(0) };
+ textDocument = M.make_text_document_params();
position = { line = row; character = col; }
}
end
+function M.make_text_document_params()
+ return { uri = vim.uri_from_bufnr(0) }
+end
+
-- @param buf buffer handle or 0 for current.
-- @param row 0-indexed line
-- @param col 0-indexed byte offset in line
@@ -840,5 +1071,7 @@ function M.character_offset(buf, row, col)
return str_utfindex(line, col)
end
+M.buf_versions = {}
+
return M
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index ea1117a906..d18fcfaf95 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -8,6 +8,9 @@ local vim = vim or {}
--- Returns a deep copy of the given object. Non-table objects are copied as
--- in a typical Lua assignment, whereas table objects are copied recursively.
+--- Functions are naively copied, so functions in the copied table point to the
+--- same functions as those in the input table. Userdata and threads are not
+--- copied and will throw an error.
---
--@param orig Table to copy
--@returns New table of copied keys and (nested) values.
@@ -20,6 +23,11 @@ vim.deepcopy = (function()
local deepcopy_funcs = {
table = function(orig)
local copy = {}
+
+ if vim._empty_dict_mt ~= nil and getmetatable(orig) == vim._empty_dict_mt then
+ copy = vim.empty_dict()
+ end
+
for k, v in pairs(orig) do
copy[vim.deepcopy(k)] = vim.deepcopy(v)
end
@@ -29,10 +37,16 @@ vim.deepcopy = (function()
string = _id,
['nil'] = _id,
boolean = _id,
+ ['function'] = _id,
}
return function(orig)
- return deepcopy_funcs[type(orig)](orig)
+ local f = deepcopy_funcs[type(orig)]
+ if f then
+ return f(orig)
+ else
+ error("Cannot deepcopy object of type "..type(orig))
+ end
end
end)()
@@ -130,6 +144,36 @@ function vim.tbl_values(t)
return values
end
+--- Apply a function to all values of a table.
+---
+--@param func function or callable table
+--@param t table
+function vim.tbl_map(func, t)
+ vim.validate{func={func,'c'},t={t,'t'}}
+
+ local rettab = {}
+ for k, v in pairs(t) do
+ rettab[k] = func(v)
+ end
+ return rettab
+end
+
+--- Filter a table using a predicate function
+---
+--@param func function or callable table
+--@param t table
+function vim.tbl_filter(func, t)
+ vim.validate{func={func,'c'},t={t,'t'}}
+
+ local rettab = {}
+ for _, entry in pairs(t) do
+ if func(entry) then
+ table.insert(rettab, entry)
+ end
+ end
+ return rettab
+end
+
--- Checks if a list-like (vector) table contains `value`.
---
--@param t Table to check
@@ -169,9 +213,19 @@ function vim.tbl_extend(behavior, ...)
if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then
error('invalid "behavior": '..tostring(behavior))
end
+
+ if select('#', ...) < 2 then
+ error('wrong number of arguments (given '..tostring(1 + select('#', ...))..', expected at least 3)')
+ end
+
local ret = {}
+ if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then
+ ret = vim.empty_dict()
+ end
+
for i = 1, select('#', ...) do
local tbl = select(i, ...)
+ vim.validate{["after the second argument"] = {tbl,'t'}}
if tbl then
for k, v in pairs(tbl) do
if behavior ~= 'force' and ret[k] ~= nil then
@@ -311,6 +365,24 @@ function vim.tbl_islist(t)
end
end
+--- Counts the number of non-nil values in table `t`.
+---
+--- <pre>
+--- vim.tbl_count({ a=1, b=2 }) => 2
+--- vim.tbl_count({ 1, 2 }) => 2
+--- </pre>
+---
+--@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
+--@param Table
+--@returns Number that is the number of the value in table
+function vim.tbl_count(t)
+ vim.validate{t={t,'t'}}
+
+ local count = 0
+ for _ in pairs(t) do count = count + 1 end
+ return count
+end
+
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
--@see https://www.lua.org/pil/20.2.html
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index aa8b8fcdd1..d3b78a7f73 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -31,8 +31,6 @@ function Parser:_on_lines(bufnr, _, start_row, old_stop_row, stop_row, old_byte_
end
local M = {
- add_language=vim._ts_add_language,
- inspect_language=vim._ts_inspect_language,
parse_query = vim._ts_parse_query,
}
@@ -45,12 +43,34 @@ setmetatable(M, {
end
})
-function M.create_parser(bufnr, ft, id)
+function M.require_language(lang, path)
+ if vim._ts_has_language(lang) then
+ return true
+ end
+ if path == nil then
+ local fname = 'parser/' .. lang .. '.*'
+ local paths = a.nvim_get_runtime_file(fname, false)
+ if #paths == 0 then
+ -- TODO(bfredl): help tag?
+ error("no parser for '"..lang.."' language")
+ end
+ path = paths[1]
+ end
+ vim._ts_add_language(path, lang)
+end
+
+function M.inspect_language(lang)
+ M.require_language(lang)
+ return vim._ts_inspect_language(lang)
+end
+
+function M.create_parser(bufnr, lang, id)
+ M.require_language(lang)
if bufnr == 0 then
bufnr = a.nvim_get_current_buf()
end
- local self = setmetatable({bufnr=bufnr, lang=ft, valid=false}, Parser)
- self._parser = vim._create_ts_parser(ft)
+ local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser)
+ self._parser = vim._create_ts_parser(lang)
self.change_cbs = {}
self:parse()
-- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
@@ -93,11 +113,33 @@ end
local Query = {}
Query.__index = Query
+local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+local function check_magic(str)
+ if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
+ return str
+ end
+ return '\\v'..str
+end
+
function M.parse_query(lang, query)
+ M.require_language(lang)
local self = setmetatable({}, Query)
- self.query = vim._ts_parse_query(lang, query)
+ self.query = vim._ts_parse_query(lang, vim.fn.escape(query,'\\'))
self.info = self.query:inspect()
self.captures = self.info.captures
+ self.regexes = {}
+ for id,preds in pairs(self.info.patterns) do
+ local regexes = {}
+ for i, pred in ipairs(preds) do
+ if (pred[1] == "match?" and type(pred[2]) == "number"
+ and type(pred[3]) == "string") then
+ regexes[i] = vim.regex(check_magic(pred[3]))
+ end
+ end
+ if next(regexes) then
+ self.regexes[id] = regexes
+ end
+ end
return self
end
@@ -110,8 +152,16 @@ local function get_node_text(node, bufnr)
return string.sub(line, start_col+1, end_col)
end
-local function match_preds(match, preds, bufnr)
- for _, pred in pairs(preds) do
+function Query:match_preds(match, pattern, bufnr)
+ local preds = self.info.patterns[pattern]
+ if not preds then
+ return true
+ end
+ local regexes = self.regexes[pattern]
+ for i, pred in pairs(preds) do
+ -- Here we only want to return if a predicate DOES NOT match, and
+ -- continue on the other case. This way unknown predicates will not be considered,
+ -- which allows some testing and easier user extensibility (#12173).
if pred[1] == "eq?" then
local node = match[pred[2]]
local node_text = get_node_text(node, bufnr)
@@ -128,8 +178,18 @@ local function match_preds(match, preds, bufnr)
if node_text ~= str or str == nil then
return false
end
- else
- return false
+ elseif pred[1] == "match?" then
+ if not regexes or not regexes[i] then
+ return false
+ end
+ local node = match[pred[2]]
+ local start_row, start_col, end_row, end_col = node:range()
+ if start_row ~= end_row then
+ return false
+ end
+ if not regexes[i]:match_line(bufnr, start_row, start_col, end_col) then
+ return false
+ end
end
end
return true
@@ -143,8 +203,7 @@ function Query:iter_captures(node, bufnr, start, stop)
local function iter()
local capture, captured_node, match = raw_iter()
if match ~= nil then
- local preds = self.info.patterns[match.pattern]
- local active = match_preds(match, preds, bufnr)
+ local active = self:match_preds(match, match.pattern, bufnr)
match.active = active
if not active then
return iter() -- tail call: try next match
@@ -163,8 +222,7 @@ function Query:iter_matches(node, bufnr, start, stop)
local function iter()
local pattern, match = raw_iter()
if match ~= nil then
- local preds = self.info.patterns[pattern]
- local active = (not preds) or match_preds(match, preds, bufnr)
+ local active = self:match_preds(match, pattern, bufnr)
if not active then
return iter() -- tail call: try next match
end
diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua
index 1544ecbf49..1440acf0d0 100644
--- a/runtime/lua/vim/tshighlighter.lua
+++ b/runtime/lua/vim/tshighlighter.lua
@@ -69,6 +69,8 @@ function TSHighlighter:set_query(query)
end
self.id_map[i] = hl
end
+
+ a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf))
end
function TSHighlighter:on_change(changes)
@@ -85,7 +87,6 @@ end
function TSHighlighter:on_window(_, _win, _buf, _topline, botline)
self.iter = nil
- self.active_nodes = {}
self.nextrow = 0
self.botline = botline
self.redraw_count = self.redraw_count + 1
@@ -107,34 +108,13 @@ function TSHighlighter:on_line(_, _win, buf, line)
end
local start_row, start_col, end_row, end_col = node:range()
local hl = self.id_map[capture]
- if hl > 0 and active then
- if start_row == line and end_row == line then
- a.nvim__put_attr(hl, start_col, end_col)
- elseif end_row >= line then
- -- TODO(bfredl): this is quite messy. Togheter with multiline bufhl we should support
- -- luahl generating multiline highlights (and other kinds of annotations)
- self.active_nodes[{hl=hl, start_row=start_row, start_col=start_col, end_row=end_row, end_col=end_col}] = true
- end
+ if hl > 0 and active and end_row >= line then
+ a.nvim__put_attr(hl, start_row, start_col, end_row, end_col)
end
if start_row > line then
self.nextrow = start_row
end
end
- for node,_ in pairs(self.active_nodes) do
- if node.start_row <= line and node.end_row >= line then
- local start_col, end_col = node.start_col, node.end_col
- if node.start_row < line then
- start_col = 0
- end
- if node.end_row > line then
- end_col = 9000
- end
- a.nvim__put_attr(node.hl, start_col, end_col)
- end
- if node.end_row <= line then
- self.active_nodes[node] = nil
- end
- end
self.line_count[line] = (self.line_count[line] or 0) + 1
--return tostring(self.line_count[line])
end
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index 1065f84f4c..e28cc9e20f 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -66,10 +66,17 @@ local function uri_from_fname(path)
end
local function uri_from_bufnr(bufnr)
- return uri_from_fname(vim.api.nvim_buf_get_name(bufnr))
+ local fname = vim.api.nvim_buf_get_name(bufnr)
+ local scheme = fname:match("^([a-z]+)://.*")
+ if scheme then
+ return fname
+ else
+ return uri_from_fname(fname)
+ end
end
local function uri_to_fname(uri)
+ uri = uri_decode(uri)
-- TODO improve this.
if is_windows_file_uri(uri) then
uri = uri:gsub('^file:///', '')
@@ -77,12 +84,17 @@ local function uri_to_fname(uri)
else
uri = uri:gsub('^file://', '')
end
- return uri_decode(uri)
+ return uri
end
-- Return or create a buffer for a uri.
local function uri_to_bufnr(uri)
- return vim.fn.bufadd((uri_to_fname(uri)))
+ local scheme = assert(uri:match("^([a-z]+)://.*"), 'Uri must contain a scheme: ' .. uri)
+ if scheme == 'file' then
+ return vim.fn.bufadd(uri_to_fname(uri))
+ else
+ return vim.fn.bufadd(uri)
+ end
end
return {
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 7a757ef7d6..aa2b69ad97 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -37,7 +37,7 @@
" For neovim compatibility, the vim specific calls were replaced with neovim
" specific calls:
" term_start -> term_open
-" term_sendkeys -> jobsend
+" term_sendkeys -> chansend
" term_getline -> getbufline
" job_info && term_getjob -> using linux command ps to get the tty
" balloon -> nvim floating window
@@ -47,8 +47,6 @@
" https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304
"
" Neovim terminal also works seamlessly on windows, which is why the ability
-" to use the prompt buffer was removed.
-"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
@@ -57,6 +55,12 @@ if exists(':Termdebug')
finish
endif
+" The terminal feature does not work with gdb on win32.
+if !has('win32')
+ let s:way = 'terminal'
+else
+ let s:way = 'prompt'
+endif
let s:keepcpo = &cpo
set cpo&vim
@@ -138,7 +142,19 @@ func s:StartDebug_internal(dict)
let s:vertical = 0
endif
- call s:StartDebug_term(a:dict)
+ " Override using a terminal window by setting g:termdebug_use_prompt to 1.
+ let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt
+ if !has('win32') && !use_prompt
+ let s:way = 'terminal'
+ else
+ let s:way = 'prompt'
+ endif
+
+ if s:way == 'prompt'
+ call s:StartDebug_prompt(a:dict)
+ else
+ call s:StartDebug_term(a:dict)
+ endif
endfunc
" Use when debugger didn't start or ended.
@@ -214,11 +230,11 @@ func s:StartDebug_term(dict)
" Set arguments to be run
if len(proc_args)
- call jobsend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
+ call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
endif
" Connect gdb to the communication pty, using the GDB/MI interface
- call jobsend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r")
+ call chansend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r")
" Wait for the response to show up, users may not notice the error and wonder
" why the debugger doesn't work.
@@ -275,6 +291,100 @@ func s:StartDebug_term(dict)
call s:StartDebugCommon(a:dict)
endfunc
+func s:StartDebug_prompt(dict)
+ " Open a window with a prompt buffer to run gdb in.
+ if s:vertical
+ vertical new
+ else
+ new
+ endif
+ let s:gdbwin = win_getid(winnr())
+ let s:promptbuf = bufnr('')
+ call prompt_setprompt(s:promptbuf, 'gdb> ')
+ set buftype=prompt
+ file gdb
+ call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
+ call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
+
+ if s:vertical
+ " Assuming the source code window will get a signcolumn, use two more
+ " columns for that, thus one less for the terminal window.
+ exe (&columns / 2 - 1) . "wincmd |"
+ endif
+
+ " Add -quiet to avoid the intro message causing a hit-enter prompt.
+ let gdb_args = get(a:dict, 'gdb_args', [])
+ let proc_args = get(a:dict, 'proc_args', [])
+
+ let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
+ "call ch_log('executing "' . join(cmd) . '"')
+
+ let s:gdbjob = jobstart(cmd, {
+ \ 'on_exit': function('s:EndPromptDebug'),
+ \ 'on_stdout': function('s:GdbOutCallback'),
+ \ })
+ if s:gdbjob == 0
+ echoerr 'invalid argument (or job table is full) while starting gdb job'
+ exe 'bwipe! ' . s:ptybuf
+ return
+ elseif s:gdbjob == -1
+ echoerr 'Failed to start the gdb job'
+ call s:CloseBuffers()
+ return
+ endif
+
+ " Interpret commands while the target is running. This should usualy only
+ " be exec-interrupt, since many commands don't work properly while the
+ " target is running.
+ call s:SendCommand('-gdb-set mi-async on')
+ " Older gdb uses a different command.
+ call s:SendCommand('-gdb-set target-async on')
+
+ let s:ptybuf = 0
+ if has('win32')
+ " MS-Windows: run in a new console window for maximum compatibility
+ call s:SendCommand('set new-console on')
+ else
+ " Unix: Run the debugged program in a terminal window. Open it below the
+ " gdb window.
+ execute 'new'
+ wincmd x | wincmd j
+ belowright let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
+ if s:pty_job_id == 0
+ echoerr 'invalid argument (or job table is full) while opening terminal window'
+ return
+ elseif s:pty_job_id == -1
+ echoerr 'Failed to open the program terminal window'
+ return
+ endif
+ let pty_job_info = nvim_get_chan_info(s:pty_job_id)
+ let s:ptybuf = pty_job_info['buffer']
+ let pty = pty_job_info['pty']
+ let s:ptywin = win_getid(winnr())
+ call s:SendCommand('tty ' . pty)
+
+ " Since GDB runs in a prompt window, the environment has not been set to
+ " match a terminal window, need to do that now.
+ call s:SendCommand('set env TERM = xterm-color')
+ call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
+ call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
+ call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
+ call s:SendCommand('set env COLORS = ' . &t_Co)
+ call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
+ endif
+ call s:SendCommand('set print pretty on')
+ call s:SendCommand('set breakpoint pending on')
+ " Disable pagination, it causes everything to stop at the gdb
+ call s:SendCommand('set pagination off')
+
+ " Set arguments to be run
+ if len(proc_args)
+ call s:SendCommand('set args ' . join(proc_args))
+ endif
+
+ call s:StartDebugCommon(a:dict)
+ startinsert
+endfunc
func s:StartDebugCommon(dict)
" Sign used to highlight the line where the program has stopped.
@@ -316,23 +426,99 @@ endfunc
" Send a command to gdb. "cmd" is the string without line terminator.
func s:SendCommand(cmd)
"call ch_log('sending to gdb: ' . a:cmd)
- call jobsend(s:comm_job_id, a:cmd . "\r")
+ if s:way == 'prompt'
+ call chansend(s:gdbjob, a:cmd . "\n")
+ else
+ call chansend(s:comm_job_id, a:cmd . "\r")
+ endif
endfunc
" This is global so that a user can create their mappings with this.
func TermDebugSendCommand(cmd)
- let do_continue = 0
- if !s:stopped
- let do_continue = 1
- call s:SendCommand('-exec-interrupt')
- sleep 10m
+ if s:way == 'prompt'
+ call chansend(s:gdbjob, a:cmd . "\n")
+ else
+ let do_continue = 0
+ if !s:stopped
+ let do_continue = 1
+ if s:way == 'prompt'
+ " Need to send a signal to get the UI to listen. Strangely this is only
+ " needed once.
+ call jobstop(s:gdbjob)
+ else
+ call s:SendCommand('-exec-interrupt')
+ endif
+ sleep 10m
+ endif
+ call chansend(s:gdb_job_id, a:cmd . "\r")
+ if do_continue
+ Continue
+ endif
endif
- call jobsend(s:gdb_job_id, a:cmd . "\r")
- if do_continue
- Continue
+endfunc
+
+" Function called when entering a line in the prompt buffer.
+func s:PromptCallback(text)
+ call s:SendCommand(a:text)
+endfunc
+
+" Function called when pressing CTRL-C in the prompt buffer and when placing a
+" breakpoint.
+func s:PromptInterrupt()
+ if s:pid == 0
+ echoerr 'Cannot interrupt gdb, did not find a process ID'
+ else
+ "call ch_log('Interrupting gdb')
+ " Using job_stop(s:gdbjob, 'int') does not work.
+ call debugbreak(s:pid)
endif
endfunc
+" Function called when gdb outputs text.
+func s:GdbOutCallback(job_id, msgs, event)
+ "call ch_log('received from gdb: ' . a:text)
+
+ " Drop the gdb prompt, we have our own.
+ " Drop status and echo'd commands.
+ call filter(a:msgs, { index, val ->
+ \ val !=# '(gdb)' && val !=# '^done' && val[0] !=# '&'})
+
+ let lines = []
+ let index = 0
+
+ for msg in a:msgs
+ if msg =~ '^^error,msg='
+ if exists('s:evalexpr')
+ \ && s:DecodeMessage(msg[11:])
+ \ =~ 'A syntax error in expression, near\|No symbol .* in current context'
+ " Silently drop evaluation errors.
+ call remove(a:msgs, index)
+ unlet s:evalexpr
+ continue
+ endif
+ elseif msg[0] == '~'
+ call add(lines, s:DecodeMessage(msg[1:]))
+ call remove(a:msgs, index)
+ continue
+ endif
+ let index += 1
+ endfor
+
+ let curwinid = win_getid(winnr())
+ call win_gotoid(s:gdbwin)
+
+ " Add the output above the current prompt.
+ for line in lines
+ call append(line('$') - 1, line)
+ endfor
+ if !empty(lines)
+ set modified
+ endif
+
+ call win_gotoid(curwinid)
+ call s:CommOutput(a:job_id, a:msgs, a:event)
+endfunc
+
" Decode a message from gdb. quotedText starts with a ", return the text up
" to the next ", unescaping characters.
func s:DecodeMessage(quotedText)
@@ -396,6 +582,19 @@ func s:EndDebugCommon()
au! TermDebug
endfunc
+func s:EndPromptDebug(job_id, exit_code, event)
+ let curwinid = win_getid(winnr())
+ call win_gotoid(s:gdbwin)
+ close
+ if curwinid != s:gdbwin
+ call win_gotoid(curwinid)
+ endif
+
+ call s:EndDebugCommon()
+ unlet s:gdbwin
+ "call ch_log("Returning from EndPromptDebug()")
+endfunc
+
func s:CommOutput(job_id, msgs, event)
for msg in a:msgs
@@ -436,7 +635,11 @@ func s:InstallCommands()
command Stop call s:SendCommand('-exec-interrupt')
" using -exec-continue results in CTRL-C in gdb window not working
- command Continue call jobsend(s:gdb_job_id, "continue\r")
+ if s:way == 'prompt'
+ command Continue call s:SendCommand('continue')
+ else
+ command Continue call chansend(s:gdb_job_id, "continue\r")
+ endif
command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
command Gdb call win_gotoid(s:gdbwin)
@@ -494,7 +697,11 @@ func s:SetBreakpoint()
let do_continue = 0
if !s:stopped
let do_continue = 1
- call s:SendCommand('-exec-interrupt')
+ if s:way == 'prompt'
+ call s:PromptInterrupt()
+ else
+ call s:SendCommand('-exec-interrupt')
+ endif
sleep 10m
endif
" Use the fname:lnum format, older gdb can't handle --source.
diff --git a/runtime/scripts.vim b/runtime/scripts.vim
index a690431014..c552f0202f 100644
--- a/runtime/scripts.vim
+++ b/runtime/scripts.vim
@@ -376,6 +376,10 @@ else
elseif s:line1 =~? '-\*-.*erlang.*-\*-'
set ft=erlang
+ " YAML
+ elseif s:line1 =~# '^%YAML'
+ set ft=yaml
+
" CVS diff
else
let s:lnum = 1
diff --git a/runtime/syntax/tutor.vim b/runtime/syntax/tutor.vim
index 6305eef734..83ca547fdd 100644
--- a/runtime/syntax/tutor.vim
+++ b/runtime/syntax/tutor.vim
@@ -33,16 +33,16 @@ syn keyword tutorMarks Todo Note Tip Excersise
syn region tutorCodeblock matchgroup=Delimiter start=/^\~\{3}.*$/ end=/^\~\{3}/
-syn region tutorShell matchgroup=Delimiter start=/^\~\{3} sh\s*$/ end=/^\~\{3}/ keepend contains=@TUTORSHELL
+syn region tutorShell matchgroup=Delimiter start=/^\~\{3} sh\s*$/ end=/^\~\{3}/ keepend contains=@TUTORSHELL concealends
syn match tutorShellPrompt /\(^\s*\)\@<=[$#]/ contained containedin=tutorShell
-syn region tutorInlineCode matchgroup=Delimiter start=/\\\@<!`/ end=/\\\@<!\(`{\@!\|`\s\)/
+syn region tutorInlineCode matchgroup=Delimiter start=/\\\@<!`/ end=/\\\@<!\(`{\@!\|`\s\)/ concealends
-syn region tutorCommand matchgroup=Delimiter start=/^\~\{3} cmd\( :\)\?\s*$/ end=/^\~\{3}/ keepend contains=@VIM
-syn region tutorInlineCommand matchgroup=Delimiter start=/\\\@<!`\(.*{vim}\)\@=/ end=/\\\@<!`\({vim}\)\@=/ nextgroup=tutorInlineType contains=@VIM
+syn region tutorCommand matchgroup=Delimiter start=/^\~\{3} cmd\( :\)\?\s*$/ end=/^\~\{3}/ keepend contains=@VIM concealends
+syn region tutorInlineCommand matchgroup=Delimiter start=/\\\@<!`\(.*`{vim}\)\@=/ end=/\\\@<!`\({vim}\)\@=/ nextgroup=tutorInlineType contains=@VIM concealends keepend
-syn region tutorNormal matchgroup=Delimiter start=/^\~\{3} norm\(al\?\)\?\s*$/ end=/^\~\{3}/ contains=@VIMNORMAL
-syn region tutorInlineNormal matchgroup=Delimiter start=/\\\@<!`\(\S*{normal}\)\@=/ end=/\\\@<!`\({normal}\)\@=/ nextgroup=tutorInlineType contains=@VIMNORMAL
+syn region tutorNormal matchgroup=Delimiter start=/^\~\{3} norm\(al\?\)\?\s*$/ end=/^\~\{3}/ contains=@VIMNORMAL concealends
+syn region tutorInlineNormal matchgroup=Delimiter start=/\\\@<!`\(\S*`{normal}\)\@=/ end=/\\\@<!`\({normal}\)\@=/ nextgroup=tutorInlineType contains=@VIMNORMAL concealends keepend
syn match tutorInlineType /{\(normal\|vim\)}/ contained conceal