diff options
124 files changed, 2701 insertions, 2998 deletions
diff --git a/.github/scripts/build_universal_macos.sh b/.github/scripts/build_universal_macos.sh index 392eec62ac..4dfe0d0cf8 100755 --- a/.github/scripts/build_universal_macos.sh +++ b/.github/scripts/build_universal_macos.sh @@ -23,9 +23,17 @@ echo "Build release" cd "$GITHUB_WORKSPACE" MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -f1 -d.)" export MACOSX_DEPLOYMENT_TARGET -cmake -S cmake.deps -B .deps -G Ninja -D CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} -D CMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} -D CMAKE_OSX_ARCHITECTURES=arm64\;x86_64 +cmake -S cmake.deps -B .deps -G Ninja \ + -D CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} \ + -D CMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \ + -D CMAKE_OSX_ARCHITECTURES=arm64\;x86_64 \ + -D CMAKE_FIND_FRAMEWORK=NEVER cmake --build .deps -cmake -B build -G Ninja -D CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} -D CMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} -D CMAKE_OSX_ARCHITECTURES=arm64\;x86_64 +cmake -B build -G Ninja \ + -D CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} \ + -D CMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \ + -D CMAKE_OSX_ARCHITECTURES=arm64\;x86_64 \ + -D CMAKE_FIND_FRAMEWORK=NEVER cmake --build build cmake --install build --prefix build/release/nvim-macos cd build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 219ad0288f..c6d0c39402 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -225,13 +225,15 @@ jobs: identifier: Neovim.Neovim release-tag: ${{ github.event.inputs.tag_name || github.ref_name }} token: ${{ secrets.WINGET_TOKEN }} + - name: Fetch nightly build msi from previous job + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') + uses: actions/download-artifact@v3 - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') - name: Get nightly version + name: Get version from nightly build msi id: get-version run: | - Invoke-WebRequest https://github.com/neovim/neovim/releases/download/nightly/nvim-win64.msi -OutFile setup.msi Install-Module -Name 'Carbon.Windows.Installer' -Force - $VERSION = (Get-CMsi (Resolve-Path .\setup.msi).Path).ProductVersion + $VERSION = (Get-CMsi (Resolve-Path .\nvim-win64\nvim-win64.msi).Path).ProductVersion "version=$VERSION" >> $env:GITHUB_OUTPUT - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') name: Publish nightly diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86561e468b..8215a79b35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -143,6 +143,8 @@ jobs: flags: -D UNSIGNED_CHAR=ON - cc: clang runner: macos-12 + flags: -D CMAKE_FIND_FRAMEWORK=NEVER + deps_flags: -D CMAKE_FIND_FRAMEWORK=NEVER # functionaltest-lua is our dumping ground for non-mainline configurations. # 1. Check that the tests pass with PUC Lua instead of LuaJIT. diff --git a/CMakeLists.txt b/CMakeLists.txt index bfc8a2283f..dad3815373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,14 +114,7 @@ set(NVIM_API_LEVEL 11) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. set(NVIM_API_PRERELEASE false) -# Default to -O2 on release builds. -if(CMAKE_C_FLAGS_RELEASE MATCHES "-O3") - message(STATUS "Replacing -O3 in CMAKE_C_FLAGS_RELEASE with -O2") - string(REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") -endif() - # Build-type: RelWithDebInfo - # /Og means something different in MSVC if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Og -g") diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index e9314ffe02..5ac34cbf26 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -17,7 +17,8 @@ set(DEPS_CMAKE_ARGS -D CMAKE_C_STANDARD=99 -D CMAKE_GENERATOR=${CMAKE_GENERATOR} -D CMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - -D CMAKE_POSITION_INDEPENDENT_CODE=ON) + -D CMAKE_POSITION_INDEPENDENT_CODE=ON + -D CMAKE_FIND_FRAMEWORK=${CMAKE_FIND_FRAMEWORK}) set(DEPS_CMAKE_CACHE_ARGS -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}) diff --git a/cmake.deps/cmake/BuildLuarocks.cmake b/cmake.deps/cmake/BuildLuarocks.cmake index 901d1f17da..674a7eaee1 100644 --- a/cmake.deps/cmake/BuildLuarocks.cmake +++ b/cmake.deps/cmake/BuildLuarocks.cmake @@ -104,34 +104,29 @@ if(MSVC) set(PATH PATH=${DEPS_INSTALL_DIR}/luarocks/tools;$ENV{PATH}) endif() -# mpack -add_custom_command(OUTPUT ${ROCKS_DIR}/mpack - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build mpack 1.0.10-0 ${LUAROCKS_BUILDARGS} - DEPENDS luarocks) -add_custom_target(mpack ALL DEPENDS ${ROCKS_DIR}/mpack) - -# lpeg -add_custom_command(OUTPUT ${ROCKS_DIR}/lpeg - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build lpeg 1.0.2-1 ${LUAROCKS_BUILDARGS} - DEPENDS mpack) -add_custom_target(lpeg ALL DEPENDS ${ROCKS_DIR}/lpeg) +set(CURRENT_DEP luarocks) + +function(Download ROCK VER) + if(ARGV2) + set(OUTPUT ${ARGV2}) + else() + set(OUTPUT ${ROCKS_DIR}/${ROCK}) + endif() + add_custom_command(OUTPUT ${OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build ${ROCK} ${VER} ${LUAROCKS_BUILDARGS} + DEPENDS ${CURRENT_DEP}) + add_custom_target(${ROCK} ALL DEPENDS ${OUTPUT}) + set(CURRENT_DEP ${ROCK} PARENT_SCOPE) +endfunction() + +Download(mpack 1.0.10-0) +Download(lpeg 1.0.2-1) if((NOT USE_BUNDLED_LUAJIT) AND USE_BUNDLED_LUA) - # luabitop - add_custom_command(OUTPUT ${ROCKS_DIR}/luabitop - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build luabitop 1.0.2-3 ${LUAROCKS_BUILDARGS} - DEPENDS lpeg) - add_custom_target(luabitop ALL DEPENDS ${ROCKS_DIR}/luabitop) + Download(luabitop 1.0.2-3) endif() if(USE_BUNDLED_BUSTED) - if((NOT USE_BUNDLED_LUAJIT) AND USE_BUNDLED_LUA) - set(BUSTED_DEPENDS luabitop) - else() - set(BUSTED_DEPENDS lpeg) - endif() - - # busted if(WIN32) set(BUSTED_EXE "${DEPS_BIN_DIR}/busted.bat") set(LUACHECK_EXE "${DEPS_BIN_DIR}/luacheck.bat") @@ -139,22 +134,11 @@ if(USE_BUNDLED_BUSTED) set(BUSTED_EXE "${DEPS_BIN_DIR}/busted") set(LUACHECK_EXE "${DEPS_BIN_DIR}/luacheck") endif() - add_custom_command(OUTPUT ${BUSTED_EXE} - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build busted 2.1.1 ${LUAROCKS_BUILDARGS} - DEPENDS ${BUSTED_DEPENDS}) - add_custom_target(busted ALL DEPENDS ${BUSTED_EXE}) - # luacheck - add_custom_command(OUTPUT ${LUACHECK_EXE} - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build luacheck 1.1.0-1 ${LUAROCKS_BUILDARGS} - DEPENDS busted) - add_custom_target(luacheck ALL DEPENDS ${LUACHECK_EXE}) + Download(busted 2.1.1 ${BUSTED_EXE}) + Download(luacheck 1.1.0-1 ${LUACHECK_EXE}) if (USE_BUNDLED_LUA OR NOT USE_BUNDLED_LUAJIT) - # coxpcall - add_custom_command(OUTPUT ${ROCKS_DIR}/coxpcall - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build coxpcall 1.17.0-1 ${LUAROCKS_BUILDARGS} - DEPENDS luarocks) - add_custom_target(coxpcall ALL DEPENDS ${ROCKS_DIR}/coxpcall) + Download(coxpcall 1.17.0-1) endif() endif() diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim deleted file mode 100644 index 70f525156c..0000000000 --- a/runtime/autoload/health/provider.vim +++ /dev/null @@ -1,755 +0,0 @@ -let s:shell_error = 0 - -function! s:is_bad_response(s) abort - return a:s =~? '\v(^unable)|(^error)|(^outdated)' -endfunction - -function! s:trim(s) abort - return substitute(a:s, '^\_s*\|\_s*$', '', 'g') -endfunction - -" Convert '\' to '/'. Collapse '//' and '/./'. -function! s:normalize_path(s) abort - return substitute(substitute(a:s, '\', '/', 'g'), '/\./\|/\+', '/', 'g') -endfunction - -" Returns TRUE if `cmd` exits with success, else FALSE. -function! s:cmd_ok(cmd) abort - call system(a:cmd) - return v:shell_error == 0 -endfunction - -" Handler for s:system() function. -function! s:system_handler(jobid, data, event) dict abort - if a:event ==# 'stderr' - if self.add_stderr_to_output - let self.output .= join(a:data, '') - else - let self.stderr .= join(a:data, '') - endif - elseif a:event ==# 'stdout' - let self.output .= join(a:data, '') - elseif a:event ==# 'exit' - let s:shell_error = a:data - endif -endfunction - -" Attempts to construct a shell command from an args list. -" Only for display, to help users debug a failed command. -function! s:shellify(cmd) abort - if type(a:cmd) != type([]) - return a:cmd - endif - return join(map(copy(a:cmd), - \'v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') -endfunction - -" Run a system command and timeout after 30 seconds. -function! s:system(cmd, ...) abort - let stdin = a:0 ? a:1 : '' - let ignore_error = a:0 > 2 ? a:3 : 0 - let opts = { - \ 'add_stderr_to_output': a:0 > 1 ? a:2 : 0, - \ 'output': '', - \ 'stderr': '', - \ 'on_stdout': function('s:system_handler'), - \ 'on_stderr': function('s:system_handler'), - \ 'on_exit': function('s:system_handler'), - \ } - let jobid = jobstart(a:cmd, opts) - - if jobid < 1 - call health#report_error(printf('Command error (job=%d): `%s` (in %s)', - \ jobid, s:shellify(a:cmd), string(getcwd()))) - let s:shell_error = 1 - return opts.output - endif - - if !empty(stdin) - call jobsend(jobid, stdin) - endif - - let res = jobwait([jobid], 30000) - if res[0] == -1 - call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) - call jobstop(jobid) - elseif s:shell_error != 0 && !ignore_error - let emsg = printf("Command error (job=%d, exit code %d): `%s` (in %s)", - \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd())) - if !empty(opts.output) - let emsg .= "\noutput: " . opts.output - end - if !empty(opts.stderr) - let emsg .= "\nstderr: " . opts.stderr - end - call health#report_error(emsg) - endif - - return opts.output -endfunction - -function! s:systemlist(cmd, ...) abort - let stdout = split(s:system(a:cmd, a:0 ? a:1 : ''), "\n") - if a:0 > 1 && !empty(a:2) - return filter(stdout, '!empty(v:val)') - endif - return stdout -endfunction - -" Fetch the contents of a URL. -function! s:download(url) abort - let has_curl = executable('curl') - if has_curl && system(['curl', '-V']) =~# 'Protocols:.*https' - let rv = s:system(['curl', '-sL', a:url], '', 1, 1) - return s:shell_error ? 'curl error with '.a:url.': '.s:shell_error : rv - elseif executable('python') - let script = " - \try:\n - \ from urllib.request import urlopen\n - \except ImportError:\n - \ from urllib2 import urlopen\n - \\n - \response = urlopen('".a:url."')\n - \print(response.read().decode('utf8'))\n - \" - let rv = s:system(['python', '-c', script]) - return empty(rv) && s:shell_error - \ ? 'python urllib.request error: '.s:shell_error - \ : rv - endif - return 'missing `curl` ' - \ .(has_curl ? '(with HTTPS support) ' : '') - \ .'and `python`, cannot make web request' -endfunction - -" Check for clipboard tools. -function! s:check_clipboard() abort - call health#report_start('Clipboard (optional)') - - if !empty($TMUX) && executable('tmux') && executable('pbpaste') && !s:cmd_ok('pbpaste') - let tmux_version = matchstr(system('tmux -V'), '\d\+\.\d\+') - call health#report_error('pbcopy does not work with tmux version: '.tmux_version, - \ ['Install tmux 2.6+. https://superuser.com/q/231130', - \ 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233']) - endif - - let clipboard_tool = provider#clipboard#Executable() - if exists('g:clipboard') && empty(clipboard_tool) - call health#report_error( - \ provider#clipboard#Error(), - \ ["Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."]) - elseif empty(clipboard_tool) - call health#report_warn( - \ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', - \ [':help clipboard']) - else - call health#report_ok('Clipboard tool found: '. clipboard_tool) - endif -endfunction - -" 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') - if !empty(pypi_response) - try - let pypi_data = json_decode(pypi_response) - catch /E474/ - return 'error: '.pypi_response - endtry - let pypi_version = get(get(pypi_data, 'info', {}), 'version', 'unable to parse') - endif - return pypi_version -endfunction - -" Get version information using the specified interpreter. The interpreter is -" used directly in case breaking changes were introduced since the last time -" Nvim's Python client was updated. -" -" Returns: [ -" {python executable version}, -" {current nvim version}, -" {current pypi nvim status}, -" {installed version status} -" ] -function! s:version_info(python) abort - let pypi_version = s:latest_pypi_version() - let python_version = s:trim(s:system([ - \ a:python, - \ '-c', - \ 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', - \ ])) - - if empty(python_version) - let python_version = 'unable to parse '.a:python.' response' - endif - - let nvim_path = s:trim(s:system([ - \ a:python, '-c', - \ 'import sys; ' . - \ 'sys.path = [p for p in sys.path if p != ""]; ' . - \ 'import neovim; print(neovim.__file__)'])) - if s:shell_error || empty(nvim_path) - return [python_version, 'unable to load neovim Python module', pypi_version, - \ nvim_path] - endif - - " Assuming that multiple versions of a package are installed, sort them - " numerically in descending order. - function! s:compare(metapath1, metapath2) abort - let a = matchstr(fnamemodify(a:metapath1, ':p:h:t'), '[0-9.]\+') - let b = matchstr(fnamemodify(a:metapath2, ':p:h:t'), '[0-9.]\+') - return a == b ? 0 : a > b ? 1 : -1 - endfunction - - " Try to get neovim.VERSION (added in 0.1.11dev). - let nvim_version = s:system([a:python, '-c', - \ 'from neovim import VERSION as v; '. - \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'], - \ '', 1, 1) - if empty(nvim_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) - \ + glob(base.'.egg-info/PKG-INFO', 1, 1) - let metas = sort(metas, 's:compare') - - if !empty(metas) - for meta_line in readfile(metas[0]) - if meta_line =~# '^Version:' - let nvim_version = matchstr(meta_line, '^Version: \zs\S\+') - break - endif - endfor - endif - endif - - let nvim_path_base = fnamemodify(nvim_path, ':~:h') - let version_status = 'unknown; '.nvim_path_base - if !s:is_bad_response(nvim_version) && !s:is_bad_response(pypi_version) - if v:lua.vim.version.lt(nvim_version, pypi_version) - let version_status = 'outdated; from '.nvim_path_base - else - let version_status = 'up to date' - endif - endif - - return [python_version, nvim_version, pypi_version, version_status] -endfunction - -" Check the Python interpreter's usability. -function! s:check_bin(bin) abort - if !filereadable(a:bin) && (!has('win32') || !filereadable(a:bin.'.exe')) - call health#report_error(printf('"%s" was not found.', a:bin)) - return 0 - elseif executable(a:bin) != 1 - call health#report_error(printf('"%s" is not executable.', a:bin)) - return 0 - endif - return 1 -endfunction - -" Check "loaded" var for given a:provider. -" Returns 1 if the caller should return (skip checks). -function! s:disabled_via_loaded_var(provider) abort - let loaded_var = 'g:loaded_'.a:provider.'_provider' - if exists(loaded_var) && !exists('*provider#'.a:provider.'#Call') - let v = eval(loaded_var) - if 0 is v - call health#report_info('Disabled ('.loaded_var.'='.v.').') - return 1 - else - call health#report_info('Disabled ('.loaded_var.'='.v.'). This might be due to some previous error.') - endif - endif - return 0 -endfunction - -function! s:check_python() abort - call health#report_start('Python 3 provider (optional)') - - let pyname = 'python3' - let python_exe = '' - let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : '' - let host_prog_var = pyname.'_host_prog' - let python_multiple = [] - - if s:disabled_via_loaded_var(pyname) - return - endif - - let [pyenv, pyenv_root] = s:check_for_pyenv() - - if exists('g:'.host_prog_var) - call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var))) - endif - - let [pyname, pythonx_warnings] = provider#pythonx#Detect(3) - - if empty(pyname) - call health#report_warn('No Python executable found that can `import neovim`. ' - \ . 'Using the first available executable for diagnostics.') - elseif exists('g:'.host_prog_var) - let python_exe = pyname - endif - - " No Python executable could `import neovim`, or host_prog_var was used. - if !empty(pythonx_warnings) - call health#report_warn(pythonx_warnings, ['See :help provider-python for more information.', - \ 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim']) - - elseif !empty(pyname) && empty(python_exe) - if !exists('g:'.host_prog_var) - call health#report_info(printf('`g:%s` is not set. Searching for ' - \ . '%s in the environment.', host_prog_var, pyname)) - endif - - if !empty(pyenv) - let python_exe = s:trim(s:system([pyenv, 'which', pyname], '', 1)) - - if empty(python_exe) - call health#report_warn(printf('pyenv could not find %s.', pyname)) - endif - endif - - if empty(python_exe) - let python_exe = exepath(pyname) - - if exists('$PATH') - for path in split($PATH, has('win32') ? ';' : ':') - let path_bin = s:normalize_path(path.'/'.pyname) - if path_bin != s:normalize_path(python_exe) - \ && index(python_multiple, path_bin) == -1 - \ && executable(path_bin) - call add(python_multiple, path_bin) - endif - endfor - - if len(python_multiple) - " This is worth noting since the user may install something - " that changes $PATH, like homebrew. - call health#report_info(printf('Multiple %s executables found. ' - \ . 'Set `g:%s` to avoid surprises.', pyname, host_prog_var)) - endif - - if python_exe =~# '\<shims\>' - call health#report_warn(printf('`%s` appears to be a pyenv shim.', python_exe), [ - \ '`pyenv` is not in $PATH, your pyenv installation is broken. ' - \ .'Set `g:'.host_prog_var.'` to avoid surprises.', - \ ]) - endif - endif - endif - endif - - if !empty(python_exe) && !exists('g:'.host_prog_var) - if empty(venv) && !empty(pyenv) - \ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/' - call health#report_warn('pyenv is not set up optimally.', [ - \ printf('Create a virtualenv specifically ' - \ . '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) - \ ]) - elseif !empty(venv) - if !empty(pyenv_root) - let venv_root = pyenv_root - else - let venv_root = fnamemodify(venv, ':h') - endif - - if resolve(python_exe) !~# '^'.venv_root.'/' - call health#report_warn('Your virtualenv is not set up optimally.', [ - \ printf('Create a virtualenv specifically ' - \ . 'for Nvim and use `g:%s`. This will avoid ' - \ . 'the need to install the pynvim module in each ' - \ . 'virtualenv.', host_prog_var) - \ ]) - endif - endif - endif - - if empty(python_exe) && !empty(pyname) - " An error message should have already printed. - call health#report_error(printf('`%s` was not found.', pyname)) - elseif !empty(python_exe) && !s:check_bin(python_exe) - let python_exe = '' - endif - - " Diagnostic output - call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe)) - if len(python_multiple) - for path_bin in python_multiple - call health#report_info('Other python executable: ' . path_bin) - endfor - endif - - if empty(python_exe) - " No Python executable can import 'neovim'. Check if any Python executable - " can import 'pynvim'. If so, that Python failed to import 'neovim' as - " well, which is most probably due to a failed pip upgrade: - " https://github.com/neovim/neovim/wiki/Following-HEAD#20181118 - let [pynvim_exe, errors] = provider#pythonx#DetectByModule('pynvim', 3) - if !empty(pynvim_exe) - call health#report_error( - \ 'Detected pip upgrade failure: Python executable can import "pynvim" but ' - \ . 'not "neovim": '. pynvim_exe, - \ "Use that Python version to reinstall \"pynvim\" and optionally \"neovim\".\n" - \ . pynvim_exe ." -m pip uninstall pynvim neovim\n" - \ . pynvim_exe ." -m pip install pynvim\n" - \ . pynvim_exe ." -m pip install neovim # only if needed by third-party software") - endif - else - let [majorpyversion, current, latest, status] = s:version_info(python_exe) - - if 3 != str2nr(majorpyversion) - call health#report_warn('Unexpected Python version.' . - \ ' This could lead to confusing error messages.') - endif - - call health#report_info('Python version: ' . majorpyversion) - - if s:is_bad_response(status) - call health#report_info(printf('pynvim version: %s (%s)', current, status)) - else - call health#report_info(printf('pynvim version: %s', current)) - endif - - if s:is_bad_response(current) - call health#report_error( - \ "pynvim is not installed.\nError: ".current, - \ ['Run in shell: '. python_exe .' -m pip install pynvim']) - endif - - if s:is_bad_response(latest) - call health#report_warn('Could not contact PyPI to get latest version.') - call health#report_error('HTTP request failed: '.latest) - elseif s:is_bad_response(status) - call health#report_warn(printf('Latest pynvim is NOT installed: %s', latest)) - elseif !s:is_bad_response(current) - call health#report_ok(printf('Latest pynvim is installed.')) - endif - endif -endfunction - -" Check if pyenv is available and a valid pyenv root can be found, then return -" their respective paths. If either of those is invalid, return two empty -" strings, effectively ignoring pyenv. -function! s:check_for_pyenv() abort - let pyenv_path = resolve(exepath('pyenv')) - - if empty(pyenv_path) - return ['', ''] - endif - - call health#report_info('pyenv: Path: '. pyenv_path) - - let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : '' - - if empty(pyenv_root) - let pyenv_root = s:trim(s:system([pyenv_path, 'root'])) - call health#report_info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.') - endif - - if !isdirectory(pyenv_root) - call health#report_warn( - \ printf('pyenv: Root does not exist: %s. ' - \ . 'Ignoring pyenv for all following checks.', pyenv_root)) - return ['', ''] - endif - - call health#report_info('pyenv: Root: '.pyenv_root) - - 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(fnameescape(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/34996' - let hints[hint] = v:true - endif - endfor - else - call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.') - endif - - let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV - if len(errors) - 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_info(msg) - call health#report_info('Python version: ' - \.system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"')) - call health#report_ok('$VIRTUAL_ENV provides :!python.') - endif -endfunction - -function! s:check_ruby() abort - call health#report_start('Ruby provider (optional)') - - if s:disabled_via_loaded_var('ruby') - return - endif - - if !executable('ruby') || !executable('gem') - call health#report_warn( - \ '`ruby` and `gem` must be in $PATH.', - \ ['Install Ruby and verify that `ruby` and `gem` commands work.']) - return - endif - call health#report_info('Ruby: '. s:system(['ruby', '-v'])) - - let [host, err] = provider#ruby#Detect() - if empty(host) - call health#report_warn('`neovim-ruby-host` not found.', - \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.', - \ 'Run `gem environment` to ensure the gem bin directory is in $PATH.', - \ 'If you are using rvm/rbenv/chruby, try "rehashing".', - \ 'See :help g:ruby_host_prog for non-standard gem installations.', - \ 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim']) - return - endif - call health#report_info('Host: '. host) - - let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra "^^neovim$"' : 'gem list -ra ^neovim$' - let latest_gem = s:system(split(latest_gem_cmd)) - if s:shell_error || empty(latest_gem) - call health#report_error('Failed to run: '. latest_gem_cmd, - \ ["Make sure you're connected to the internet.", - \ 'Are you behind a firewall or proxy?']) - return - endif - 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) - if s:shell_error - call health#report_error('Failed to run: '. join(current_gem_cmd), - \ ['Report this issue with the output of: ', join(current_gem_cmd)]) - return - endif - - if v:lua.vim.version.lt(current_gem, latest_gem) - call health#report_warn( - \ printf('Gem "neovim" is out-of-date. Installed: %s, latest: %s', - \ current_gem, latest_gem), - \ ['Run in shell: gem update neovim']) - else - call health#report_ok('Latest "neovim" gem is installed: '. current_gem) - endif -endfunction - -function! s:check_node() abort - call health#report_start('Node.js provider (optional)') - - if s:disabled_via_loaded_var('node') - return - endif - - if !executable('node') || (!executable('npm') && !executable('yarn') && !executable('pnpm')) - call health#report_warn( - \ '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.', - \ ['Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.']) - return - endif - let node_v = get(split(s:system(['node', '-v']), "\n"), 0, '') - call health#report_info('Node.js: '. node_v) - if s:shell_error || v:lua.vim.version.lt(node_v[1:], '6.0.0') - call health#report_warn('Nvim node.js host does not support Node '.node_v) - " Skip further checks, they are nonsense if nodejs is too old. - return - endif - if !provider#node#can_inspect() - call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.') - endif - - let [host, err] = provider#node#Detect() - if empty(host) - call health#report_warn('Missing "neovim" npm (or yarn, pnpm) package.', - \ ['Run in shell: npm install -g neovim', - \ 'Run in shell (if you use yarn): yarn global add neovim', - \ 'Run in shell (if you use pnpm): pnpm install -g neovim', - \ 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim']) - return - endif - call health#report_info('Nvim node.js host: '. host) - - let manager = 'npm' - if executable('yarn') - let manager = 'yarn' - elseif executable('pnpm') - let manager = 'pnpm' - endif - - let latest_npm_cmd = has('win32') ? - \ 'cmd /c '. manager .' info neovim --json' : - \ manager .' info neovim --json' - let latest_npm = s:system(split(latest_npm_cmd)) - if s:shell_error || empty(latest_npm) - call health#report_error('Failed to run: '. latest_npm_cmd, - \ ["Make sure you're connected to the internet.", - \ 'Are you behind a firewall or proxy?']) - return - endif - try - let pkg_data = json_decode(latest_npm) - catch /E474/ - return 'error: '.latest_npm - endtry - let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse') - - let current_npm_cmd = ['node', host, '--version'] - let current_npm = s:system(current_npm_cmd) - if s:shell_error - call health#report_error('Failed to run: '. join(current_npm_cmd), - \ ['Report this issue with the output of: ', join(current_npm_cmd)]) - return - endif - - if latest_npm !=# 'unable to parse' && v:lua.vim.version.lt(current_npm, latest_npm) - call health#report_warn( - \ printf('Package "neovim" is out-of-date. Installed: %s, latest: %s', - \ current_npm, latest_npm), - \ ['Run in shell: npm install -g neovim', - \ 'Run in shell (if you use yarn): yarn global add neovim', - \ 'Run in shell (if you use pnpm): pnpm install -g neovim']) - else - call health#report_ok('Latest "neovim" npm/yarn/pnpm package is installed: '. current_npm) - endif -endfunction - -function! s:check_perl() abort - call health#report_start('Perl provider (optional)') - - if s:disabled_via_loaded_var('perl') - return - endif - - let [perl_exec, perl_warnings] = provider#perl#Detect() - if empty(perl_exec) - if !empty(perl_warnings) - call health#report_warn(perl_warnings, ['See :help provider-perl for more information.', - \ 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim']) - else - call health#report_warn('No usable perl executable found') - endif - return - endif - - call health#report_info('perl executable: '. perl_exec) - - " we cannot use cpanm that is on the path, as it may not be for the perl - " set with g:perl_host_prog - call s:system([perl_exec, '-W', '-MApp::cpanminus', '-e', '']) - if s:shell_error - return [perl_exec, '"App::cpanminus" module is not installed'] - endif - - let latest_cpan_cmd = [perl_exec, - \ '-MApp::cpanminus::fatscript', '-e', - \ 'my $app = App::cpanminus::script->new; - \ $app->parse_options ("--info", "-q", "Neovim::Ext"); - \ exit $app->doit'] - - let latest_cpan = s:system(latest_cpan_cmd) - if s:shell_error || empty(latest_cpan) - call health#report_error('Failed to run: '. join(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 = [perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] - let current_cpan = s:system(current_cpan_cmd) - if s:shell_error - call health#report_error('Failed to run: '. join(current_cpan_cmd), - \ ['Report this issue with the output of: ', join(current_cpan_cmd)]) - return - endif - - if v:lua.vim.version.lt(current_cpan, latest_cpan) - call health#report_warn( - \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s', - \ current_cpan, latest_cpan), - \ ['Run in shell: cpanm -n Neovim::Ext']) - else - call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan) - endif -endfunction - -function! health#provider#check() abort - call s:check_clipboard() - call s:check_python() - call s:check_virtualenv() - call s:check_ruby() - call s:check_node() - call s:check_perl() -endfunction diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim index e495e8262a..d7d1d8399e 100644 --- a/runtime/autoload/tar.vim +++ b/runtime/autoload/tar.vim @@ -735,82 +735,6 @@ fun! s:Rmdir(fname) " call Dret("Rmdir") endfun -" --------------------------------------------------------------------- -" tar#Vimuntar: installs a tarball in the user's .vim / vimfiles directory {{{2 -fun! tar#Vimuntar(...) -" call Dfunc("tar#Vimuntar() a:0=".a:0." a:1<".(exists("a:1")? a:1 : "-n/a-").">") - let tarball = expand("%") -" call Decho("tarball<".tarball.">") - let tarbase = substitute(tarball,'\..*$','','') -" call Decho("tarbase<".tarbase.">") - let tarhome = expand("%:p") - if has("win32") || has("win95") || has("win64") || has("win16") - let tarhome= substitute(tarhome,'\\','/','g') - endif - let tarhome= substitute(tarhome,'/[^/]*$','','') -" call Decho("tarhome<".tarhome.">") - let tartail = expand("%:t") -" call Decho("tartail<".tartail.">") - let curdir = getcwd() -" call Decho("curdir <".curdir.">") - " set up vimhome - if a:0 > 0 && a:1 != "" - let vimhome= a:1 - else - let vimhome= vimball#VimballHome() - endif -" call Decho("vimhome<".vimhome.">") - -" call Decho("curdir<".curdir."> vimhome<".vimhome.">") - if simplify(curdir) != simplify(vimhome) - " copy (possibly compressed) tarball to .vim/vimfiles -" call Decho(netrw#WinPath(g:tar_copycmd)." ".shellescape(tartail)." ".shellescape(vimhome)) - call system(netrw#WinPath(g:tar_copycmd)." ".shellescape(tartail)." ".shellescape(vimhome)) -" call Decho("exe cd ".fnameescape(vimhome)) - exe "cd ".fnameescape(vimhome) - endif -" call Decho("getcwd<".getcwd().">") - - " if necessary, decompress the tarball; then, extract it - if tartail =~ '\.tgz' - if executable("gunzip") - silent exe "!gunzip ".shellescape(tartail) - elseif executable("gzip") - silent exe "!gzip -d ".shellescape(tartail) - else - echoerr "unable to decompress<".tartail."> on this system" - if simplify(curdir) != simplify(tarhome) - " remove decompressed tarball, restore directory -" call Decho("delete(".tartail.".tar)") - call delete(tartail.".tar") -" call Decho("exe cd ".fnameescape(curdir)) - exe "cd ".fnameescape(curdir) - endif -" call Dret("tar#Vimuntar") - return - endif - else - call vimball#Decompress(tartail,0) - endif - let extractcmd= netrw#WinPath(g:tar_extractcmd) -" call Decho("system(".extractcmd." ".shellescape(tarbase.".tar").")") - call system(extractcmd." ".shellescape(tarbase.".tar")) - - " set up help - if filereadable("doc/".tarbase.".txt") -" call Decho("exe helptags ".getcwd()."/doc") - exe "helptags ".getcwd()."/doc" - endif - - if simplify(tarhome) != simplify(vimhome) - " remove decompressed tarball, restore directory - call delete(vimhome."/".tarbase.".tar") - exe "cd ".fnameescape(curdir) - endif - -" call Dret("tar#Vimuntar") -endfun - " ===================================================================== " Modelines And Restoration: {{{1 let &cpo= s:keepcpo diff --git a/runtime/bugreport.vim b/runtime/bugreport.vim deleted file mode 100644 index 27761ca011..0000000000 --- a/runtime/bugreport.vim +++ /dev/null @@ -1,87 +0,0 @@ -:" Use this script to create the file "bugreport.txt", which contains -:" information about the environment of a possible bug in Vim. -:" -:" Maintainer: Bram Moolenaar <Bram@vim.org> -:" Last change: 2019 Jan 27 -:" -:" To use inside Vim: -:" :so $VIMRUNTIME/bugreport.vim -:" Or, from the command line: -:" vim -s $VIMRUNTIME/bugreport.vim -:" -:" The "if 1" lines are to avoid error messages when expression evaluation is -:" not compiled in. -:" -:if 1 -: let more_save = &more -:endif -:set nomore -:if has("unix") -: !echo "uname -a" >bugreport.txt -: !uname -a >>bugreport.txt -:endif -:redir >>bugreport.txt -:version -:if 1 -: func <SID>CheckDir(n) -: if isdirectory(a:n) -: echo 'directory "' . a:n . '" exists' -: else -: echo 'directory "' . a:n . '" does NOT exist' -: endif -: endfun -: func <SID>CheckFile(n) -: if filereadable(a:n) -: echo '"' . a:n . '" is readable' -: else -: echo '"' . a:n . '" is NOT readable' -: endif -: endfun -: echo "--- Directories and Files ---" -: echo '$VIM = "' . $VIM . '"' -: call <SID>CheckDir($VIM) -: echo '$VIMRUNTIME = "' . $VIMRUNTIME . '"' -: call <SID>CheckDir($VIMRUNTIME) -: call <SID>CheckFile(&helpfile) -: call <SID>CheckFile(fnamemodify(&helpfile, ":h") . "/tags") -: call <SID>CheckFile($VIMRUNTIME . "/menu.vim") -: call <SID>CheckFile($VIMRUNTIME . "/filetype.vim") -: call <SID>CheckFile($VIMRUNTIME . "/syntax/synload.vim") -: delfun <SID>CheckDir -: delfun <SID>CheckFile -: echo "--- Scripts sourced ---" -: scriptnames -:endif -:set all -:if has("autocmd") -: au -:endif -:if 1 -: echo "--- Normal/Visual mode mappings ---" -:endif -:map -:if 1 -: echo "--- Insert/Command-line mode mappings ---" -:endif -:map! -:if 1 -: echo "--- Abbreviations ---" -:endif -:ab -:if 1 -: echo "--- Highlighting ---" -:endif -:highlight -:if 1 -: echo "--- Variables ---" -:endif -:if 1 -: let -:endif -:redir END -:set more& -:if 1 -: let &more = more_save -: unlet more_save -:endif -:e bugreport.txt diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 09d260e0cd..d63563cc05 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3068,8 +3068,8 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* In general, values below 100 are recommended, unless there is a good reason to overshadow builtin elements. - • style: Configure the appearance of the window. Currently - only takes one non-empty value: + • style: (optional) Configure the appearance of the window. + Currently only supports one value: • "minimal" Nvim will display the window with many UI options disabled. This is useful when displaying a temporary float where the text should not be edited. diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 0e04e9035b..3c940ccfa2 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3003,7 +3003,6 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* arglist file names in argument list augroup autocmd groups buffer buffer names - behave |:behave| suboptions breakpoint |:breakadd| and |:breakdel| suboptions cmdline |cmdline-completion| result color color schemes diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 3735073867..171d285950 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -120,6 +120,8 @@ LSP FUNCTIONS {buffer = bufnr} instead. - *vim.lsp.buf.formatting()* Use |vim.lsp.buf.format()| with {async = true} instead. +- *vim.lsp.buf.formatting_sync()* Use |vim.lsp.buf.format()| with + {async = false} instead. - *vim.lsp.buf.range_formatting()* Use |vim.lsp.formatexpr()| or |vim.lsp.buf.format()| instead. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index fe15ba6115..351690f4df 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -93,7 +93,27 @@ non-zero number it means TRUE: > :" executed To test for a non-empty string, use empty(): > :if !empty("foo") -< + +< *falsy* *truthy* +An expression can be used as a condition, ignoring the type and only using +whether the value is "sort of true" or "sort of false". Falsy is: + the number zero + empty string, blob, list or dictionary +Other values are truthy. Examples: + 0 falsy + 1 truthy + -1 truthy + 0.0 falsy + 0.1 truthy + '' falsy + 'x' truthy + [] falsy + [0] truthy + {} falsy + #{x: 1} truthy + 0z falsy + 0z00 truthy + *non-zero-arg* Function arguments often behave slightly different from |TRUE|: If the argument is present and it evaluates to a non-zero Number, |v:true| or a @@ -841,9 +861,12 @@ All expressions within one level are parsed from left to right. ------------------------------------------------------------------------------ -expr1 *expr1* *ternary* *E109* +expr1 *expr1* *ternary* *falsy-operator* *??* *E109* + +The ternary operator: expr2 ? expr1 : expr1 +The falsy operator: expr2 ?? expr1 -expr2 ? expr1 : expr1 +Ternary operator ~ The expression before the '?' is evaluated to a number. If it evaluates to |TRUE|, the result is the value of the expression between the '?' and ':', @@ -866,6 +889,23 @@ To keep this readable, using |line-continuation| is suggested: > You should always put a space before the ':', otherwise it can be mistaken for use in a variable such as "a:1". +Falsy operator ~ + +This is also known as the "null coalescing operator", but that's too +complicated, thus we just call it the falsy operator. + +The expression before the '??' is evaluated. If it evaluates to +|truthy|, this is used as the result. Otherwise the expression after the '??' +is evaluated and used as the result. This is most useful to have a default +value for an expression that may result in zero or empty: > + echo theList ?? 'list is empty' + echo GetName() ?? 'unknown' + +These are similar, but not equal: > + expr2 ?? expr1 + expr2 ? expr2 : expr1 +In the second line "expr2" is evaluated twice. + ------------------------------------------------------------------------------ expr2 and expr3 *expr2* *expr3* @@ -2499,7 +2539,7 @@ This does NOT work: > |List| item. *:let=<<* *:let-heredoc* - *E990* *E991* *E172* *E221* + *E990* *E991* *E172* *E221* *E1145* :let {var-name} =<< [trim] {endmarker} text... text... diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 174683a8c3..fbd3fccec0 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1159,7 +1159,6 @@ tag command action ~ |:badd| :bad[d] add buffer to the buffer list |:balt| :balt like ":badd" but also set the alternate file |:bdelete| :bd[elete] remove a buffer from the buffer list -|:behave| :be[have] set mouse and selection behavior |:belowright| :bel[owright] make split window appear right or below |:bfirst| :bf[irst] go to first buffer in the buffer list |:blast| :bl[ast] go to last buffer in the buffer list diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6084a625ba..6fdf3775f6 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1516,9 +1516,10 @@ region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()* Parameters: ~ • {bufnr} (integer) number of buffer - • {pos1} integer[] (line, column) tuple marking beginning of - region - • {pos2} integer[] (line, column) tuple marking end of region + • {pos1} integer[]|string start of region as a (line, column) + tuple or string accepted by |getpos()| + • {pos2} integer[]|string end of region as a (line, column) tuple + or string accepted by |getpos()| • {regtype} (string) type of selection, see |setreg()| • {inclusive} (boolean) indicating whether column of pos2 is inclusive @@ -1697,6 +1698,19 @@ is_callable({f}) *vim.is_callable()* Return: ~ (boolean) `true` if `f` is callable, else `false` +list_contains({t}, {value}) *vim.list_contains()* + Checks if a list-like table (integer keys without gaps) contains `value`. + + Parameters: ~ + • {t} (table) Table to check (must be list-like, not validated) + • {value} any Value to compare + + Return: ~ + (boolean) `true` if `t` contains `value` + + See also: ~ + • |vim.tbl_contains()| for checking values in general tables + list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* Extends a list-like table with the values of another list-like table. @@ -1796,16 +1810,31 @@ tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()* Return: ~ (table) o -tbl_contains({t}, {value}) *vim.tbl_contains()* - Checks if a list-like (vector) table contains `value`. +tbl_contains({t}, {value}, {opts}) *vim.tbl_contains()* + Checks if a table contains a given value, specified either directly or via + a predicate that is checked for each value. + + Example: >lua + + vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) + return vim.deep_equal(v, { 'b', 'c' }) + end, { predicate = true }) + -- true +< Parameters: ~ • {t} (table) Table to check - • {value} any Value to compare + • {value} any Value to compare or predicate function reference + • {opts} (table|nil) Keyword arguments |kwargs|: + • predicate: (boolean) `value` is a function reference to be + checked (default false) Return: ~ (boolean) `true` if `t` contains `value` + See also: ~ + • |vim.list_contains()| for checking values in list-like tables + tbl_count({t}) *vim.tbl_count()* Counts the number of non-nil values in table `t`. @@ -1899,6 +1928,20 @@ tbl_get({o}, {...}) *vim.tbl_get()* Return: ~ any Nested value indexed by key (if it exists), else nil +tbl_isarray({t}) *vim.tbl_isarray()* + Tests if a Lua table can be treated as an array (a table indexed by + integers). + + Empty table `{}` is assumed to be an array, unless it was created by + |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, + for example from |rpcrequest()| or |vim.fn|. + + Parameters: ~ + • {t} (table) + + Return: ~ + (boolean) `true` if array-like table, else `false`. + tbl_isempty({t}) *vim.tbl_isempty()* Checks if a table is empty. @@ -1912,17 +1955,18 @@ tbl_isempty({t}) *vim.tbl_isempty()* • https://github.com/premake/premake-core/blob/master/src/base/table.lua tbl_islist({t}) *vim.tbl_islist()* - Tests if a Lua table can be treated as an array. + Tests if a Lua table can be treated as a list (a table indexed by + consecutive integers starting from 1). - Empty table `{}` is assumed to be an array, unless it was created by + Empty table `{}` is assumed to be an list, unless it was created by |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|. Parameters: ~ - • {t} (table) Table + • {t} (table) Return: ~ - (boolean) `true` if array-like table, else `false` + (boolean) `true` if list-like table, else `false`. tbl_keys({t}) *vim.tbl_keys()* Return a list of all keys used in a table. However, the order of the diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 6e2a1b1d3f..6697b3018a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -15,9 +15,21 @@ BREAKING CHANGES *news-breaking* The following changes may require adaptations in user config or plugins. +• |vim.tbl_islist()| now checks whether a table is actually list-like (i.e., + has integer keys without gaps and starting from 1). For the previous + behavior (only check for integer keys, allow gaps or not starting with 1), + use |vim.tbl_isarray()|. + • "#" followed by a digit no longer stands for a function key at the start of the lhs of a mapping. +• `:behave` was removed. if you used `:behave mswin`, the following is equivalent: >vim + + set selection=exclusive + set selectmode=mouse,key + set mousemodel=popup + set keymodel=startsel,stopsel + ============================================================================== ADDED FEATURES *news-added* @@ -30,14 +42,19 @@ CHANGED FEATURES *news-changed* The following changes to existing APIs or features add new behavior. -• ... +• |vim.tbl_contains()| now works for general tables and allows specifying a + predicate function that is checked for each value. (Use |vim.list_contains()| + for checking list-like tables (integer keys without gaps) for literal values.) + +• |vim.region()| can use a string accepted by |getpos()| as position. ============================================================================== REMOVED FEATURES *news-removed* The following deprecated functions or APIs were removed. -• ... +• Vimball support is removed. + - :Vimuntar command removed. ============================================================================== DEPRECATIONS *news-deprecations* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d22a78700f..b4cad51990 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3589,7 +3589,6 @@ A jump table for the options with a short description can be found at |Q_op|. stopsel Using a not-shifted special key stops selection. Special keys in this context are the cursor keys, <End>, <Home>, <PageUp> and <PageDown>. - The 'keymodel' option is set by the |:behave| command. *'keywordprg'* *'kp'* 'keywordprg' 'kp' string (default ":Man", Windows: ":help") @@ -4168,21 +4167,6 @@ A jump table for the options with a short description can be found at |Q_op|. 'mousehide' hide mouse pointer while typing text 'selectmode' whether to start Select mode or Visual mode - The :behave command provides some "profiles" for mouse behavior. - *:behave* *:be* - :be[have] {model} Set behavior for mouse and selection. Valid - arguments are: - mswin MS-Windows behavior - xterm Xterm behavior - - Using ":behave" changes these options: - option mswin xterm ~ - 'selectmode' "mouse,key" "" - 'mousemodel' "popup" "extend" - 'keymodel' "startsel,stopsel" "" - 'selection' "exclusive" "inclusive" - - *'mousefocus'* *'mousef'* *'nomousefocus'* *'nomousef'* 'mousefocus' 'mousef' boolean (default off) global @@ -4250,8 +4234,6 @@ A jump table for the options with a short description can be found at |Q_op|. "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click) "g<RightMouse>" is "<C-RightMouse> ("CTRL-T") - The 'mousemodel' option is set by the |:behave| command. - *'mousemoveevent'* *'mousemev'* *'nomousemoveevent'* *'nomousemev'* 'mousemoveevent' 'mousemev' boolean (default off) global @@ -5013,8 +4995,6 @@ A jump table for the options with a short description can be found at |Q_op|. backwards, you cannot include the last character of a line, when starting in Normal mode and 'virtualedit' empty. - The 'selection' option is set by the |:behave| command. - *'selectmode'* *'slm'* 'selectmode' 'slm' string (default "") global @@ -5025,7 +5005,6 @@ A jump table for the options with a short description can be found at |Q_op|. key when using shifted special keys cmd when using "v", "V" or CTRL-V See |Select-mode|. - The 'selectmode' option is set by the |:behave| command. *'sessionoptions'* *'ssop'* 'sessionoptions' 'ssop' string (default: "blank,buffers,curdir,folds, diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt index 2ae93b098a..a0e06a7d37 100644 --- a/runtime/doc/pi_health.txt +++ b/runtime/doc/pi_health.txt @@ -21,7 +21,7 @@ Plugin authors are encouraged to write new healthchecks. |health-dev| ============================================================================== Commands *health-commands* - *:che* *:checkhealth* *:CheckHealth* + *:che* *:checkhealth* :che[ckhealth] Run all healthchecks. *E5009* Nvim depends on |$VIMRUNTIME|, 'runtimepath' and 'packpath' to diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index 2230b82dec..e664b98086 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -33,23 +33,6 @@ Copyright 2005-2017: *tar-copyright* also write to the file. Currently, one may not make a new file in tar archives via the plugin. - *:Vimuntar* - VIMUNTAR~ - - :Vimuntar [vimhome] - - This command copies, if necessary, the tarball to the .vim or vimfiles - directory using the first writable directory in the |'runtimepath'| - when no [vimhome] is specified. Otherwise, the [vimhome] argument - allows the user to specify that directory, instead. - - The copy is done using the command in *g:tar_copycmd* , which is > - cp for cygwin, unix, macunix - copy for windows (32, 95, 64, 16) -< The extraction is done with the command specified with - *g:tar_extractcmd* , which by default is > - "tar -xf" -< *:TarDiff* DIFFERENCING SUPPORT~ diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index 00b4f9eed4..8f7e393c02 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -190,26 +190,21 @@ The ":map" command (with no arguments) lists your current mappings. At least the ones for Normal mode. More about mappings in section |40.1|. ============================================================================== -*05.4* Adding a package *add-package* *vimball-install* +*05.4* Adding a package *add-package* -A package is a set of files that you can add to Vim. There are two kinds of -packages: optional and automatically loaded on startup. - -The Vim distribution comes with a few packages that you can optionally use. -For example, the vimball plugin. This plugin supports creating and using -vimballs (self-installing Vim plugin archives). - -To start using the vimball plugin, add one line to your vimrc file: > - packadd vimball +You may use |:packadd| to enable packages on demand. This is useful for plugins +you want to enable only sometimes. To enable `example_package`, use the +following command: > + packadd example_package -That's all! You can also type the command to try it out. Now you can find -help about this plugin: > - :help vimball +That's all! Now you can find help about this plugin: > + :help example_package This works, because when `:packadd` loaded the plugin it also added the -package directory in 'runtimepath', so that the help file can be found. The -tags for vimball's help are already created. If you need to generate the help -tags for a package, see the `:helptags` command. +package directory in 'runtimepath', so that the help file can be found. + +A package is a set of files that you can add to Vim. There are two kinds of +packages: optional and automatically loaded on startup. You can find packages on the Internet in various places. It usually comes as an archive or as a repository. For an archive you can follow these steps: diff --git a/runtime/doc/usr_09.txt b/runtime/doc/usr_09.txt index 8084d13b5d..ea16010dc2 100644 --- a/runtime/doc/usr_09.txt +++ b/runtime/doc/usr_09.txt @@ -124,41 +124,13 @@ This adds the 'l' flag to 'guioptions'. Standards are wonderful. In Microsoft Windows, you can use the mouse to select text in a standard manner. The X Window system also has a standard system for using the mouse. Unfortunately, these two standards are not the -same. - Fortunately, you can customize Vim. You can make the behavior of the mouse -work like an X Window system mouse or a Microsoft Windows mouse. The following -command makes the mouse behave like an X Window mouse: > +same. Fortunately, you can customize Vim. - :behave xterm - -The following command makes the mouse work like a Microsoft Windows mouse: > - - :behave mswin - -The default behavior of the mouse on Unix systems is xterm. The default -behavior on Windows systems is selected during the installation process. For -details about what the two behaviors are, see |:behave|. Here follows a -summary. - - -XTERM MOUSE BEHAVIOR - -Left mouse click position the cursor -Left mouse drag select text in Visual mode -Middle mouse click paste text from the clipboard -Right mouse click extend the selected text until the mouse - pointer - - -MSWIN MOUSE BEHAVIOR - -Left mouse click position the cursor -Left mouse drag select text in Select mode (see |09.4|) -Left mouse click, with Shift extend the selected text until the mouse - pointer -Middle mouse click paste text from the clipboard -Right mouse click display a pop-up menu +The following commands makes the mouse work more like a Microsoft Windows mouse: > + set selection=exclusive + set selectmode=mouse,key + set keymodel=startsel,stopsel The mouse can be further tuned. Check out these options if you want to change the way how the mouse works: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7228473676..58f6d6f6f9 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -556,6 +556,7 @@ Aliases: vimdiff (alias for "nvim -d" |diff-mode|) Commands: + :behave :fixdel :hardcopy :helpfind @@ -573,6 +574,7 @@ Commands: :cscope :lcscope :scscope + :Vimuntar Compile-time features: Emacs tags support @@ -764,5 +766,8 @@ Hardcopy: `:hardcopy` was removed. Instead, use `:TOhtml` and print the resulting HTML using a web browser or some other HTML viewer. +Bundled plugins: + vimball *vimball* + ============================================================================== vim:tw=78:ts=8:sw=2:et:ft=help:norl: diff --git a/runtime/ftplugin/cs.lua b/runtime/ftplugin/cs.lua new file mode 100644 index 0000000000..b4e68148f5 --- /dev/null +++ b/runtime/ftplugin/cs.lua @@ -0,0 +1 @@ +vim.bo.commentstring = '/*%s*/' diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index 5b09126788..5188c13284 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -26,7 +26,7 @@ end function M.properties.charset(bufnr, val) assert( - vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val), + vim.list_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val), 'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"' ) if val == 'utf-8' or val == 'utf-8-bom' then diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index cca9434e9c..1158d80941 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -64,6 +64,7 @@ local function system(cmd_, silent, env) local cmd_str = table.concat(cmd, ' ') man_error(string.format('command error: %s', cmd_str)) end + return '' end vim.wait(30000, function() diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua index b6d84404ec..6f544e9407 100644 --- a/runtime/lua/nvim/health.lua +++ b/runtime/lua/nvim/health.lua @@ -325,7 +325,7 @@ local function check_tmux() -- check for RGB capabilities local info = vim.fn.system({ 'tmux', 'display-message', '-p', '#{client_termfeatures}' }) info = vim.split(vim.trim(info), ',', { trimempty = true }) - if not vim.tbl_contains(info, 'RGB') then + if not vim.list_contains(info, 'RGB') then local has_rgb = false if #info == 0 then -- client_termfeatures may not be supported; fallback to checking show-messages diff --git a/runtime/lua/provider/health.lua b/runtime/lua/provider/health.lua new file mode 100644 index 0000000000..a5fe14732c --- /dev/null +++ b/runtime/lua/provider/health.lua @@ -0,0 +1,916 @@ +local M = {} + +local start = vim.health.report_start +local ok = vim.health.report_ok +local info = vim.health.report_info +local warn = vim.health.report_warn +local error = vim.health.report_error +local iswin = vim.loop.os_uname().sysname == 'Windows_NT' + +local shell_error_code = 0 +local function shell_error() + return shell_error_code ~= 0 +end + +-- Returns true if `cmd` exits with success, else false. +local function cmd_ok(cmd) + vim.fn.system(cmd) + return vim.v.shell_error == 0 +end + +local function executable(exe) + return vim.fn.executable(exe) == 1 +end + +local function is_blank(s) + return s:find('^%s*$') ~= nil +end + +local function isdir(path) + if not path then + return false + end + local stat = vim.loop.fs_stat(path) + if not stat then + return false + end + return stat.type == 'directory' +end + +local function isfile(path) + if not path then + return false + end + local stat = vim.loop.fs_stat(path) + if not stat then + return false + end + return stat.type == 'file' +end + +-- Handler for s:system() function. +local function system_handler(self, _, data, event) + if event == 'stderr' then + if self.add_stderr_to_output then + self.output = self.output .. vim.fn.join(data, '') + else + self.stderr = self.stderr .. vim.fn.join(data, '') + end + elseif event == 'stdout' then + self.output = self.output .. vim.fn.join(data, '') + elseif event == 'exit' then + shell_error_code = data + end +end + +-- Attempts to construct a shell command from an args list. +-- Only for display, to help users debug a failed command. +local function shellify(cmd) + if type(cmd) ~= 'table' then + return cmd + end + return vim.fn.join( + vim.fn.map(vim.fn.copy(cmd), [[v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val]]), + ' ' + ) +end + +-- Run a system command and timeout after 30 seconds. +local function system(cmd, ...) + local args = { ... } + local args_count = vim.tbl_count(args) + + local stdin = (args_count > 0 and args[1] or '') + local stderr = (args_count > 1 and args[2] or false) + local ignore_error = (args_count > 2 and args[3] or false) + + local opts = { + add_stderr_to_output = stderr, + output = '', + stderr = '', + on_stdout = system_handler, + on_stderr = system_handler, + on_exit = system_handler, + } + local jobid = vim.fn.jobstart(cmd, opts) + + if jobid < 1 then + local message = 'Command error (job=' + .. jobid + .. '): `' + .. shellify(cmd) + .. '` (in ' + .. vim.fn.string(vim.fn.getcwd()) + .. ')' + + error(message) + shell_error_code = 1 + return opts.output + end + + if not is_blank(stdin) then + vim.cmd([[call jobsend(jobid, stdin)]]) + end + + local res = vim.fn.jobwait({ jobid }, 30000) + if res[1] == -1 then + error('Command timed out: ' .. shellify(cmd)) + vim.cmd([[call jobstop(jobid)]]) + elseif shell_error() and not ignore_error then + local emsg = 'Command error (job=' + .. jobid + .. ', exit code ' + .. shell_error_code + .. '): `' + .. shellify(cmd) + .. '` (in ' + .. vim.fn.string(vim.fn.getcwd()) + .. ')' + if not is_blank(opts.output) then + emsg = emsg .. '\noutput: ' .. opts.output + end + if not is_blank(opts.stderr) then + emsg = emsg .. '\nstderr: ' .. opts.stderr + end + error(emsg) + end + + -- return opts.output + local _ = ... + return vim.trim(vim.fn.system(cmd)) +end + +local function clipboard() + start('Clipboard (optional)') + + if + os.getenv('TMUX') + and executable('tmux') + and executable('pbpaste') + and not cmd_ok('pbpaste') + then + local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') + local advice = { + 'Install tmux 2.6+. https://superuser.com/q/231130', + 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233', + } + error('pbcopy does not work with tmux version: ' .. tmux_version, advice) + end + + local clipboard_tool = vim.fn['provider#clipboard#Executable']() + if vim.g.clipboard and is_blank(clipboard_tool) then + local error_message = vim.fn['provider#clipboard#Error']() + error( + error_message, + "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all." + ) + elseif is_blank(clipboard_tool) then + warn( + 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', + ':help clipboard' + ) + else + ok('Clipboard tool found: ' .. clipboard_tool) + end +end + +local function disabled_via_loaded_var(provider) + local loaded_var = 'loaded_' .. provider .. '_provider' + local v = vim.g[loaded_var] + if v == 0 then + info('Disabled (' .. loaded_var .. '=' .. v .. ').') + return true + end + return false +end + +-- Check if pyenv is available and a valid pyenv root can be found, then return +-- their respective paths. If either of those is invalid, return two empty +-- strings, effectively ignoring pyenv. +local function check_for_pyenv() + local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv')) + + if is_blank(pyenv_path) then + return { '', '' } + end + + info('pyenv: Path: ' .. pyenv_path) + + local pyenv_root = os.getenv('PYENV_ROOT') and vim.fn.resolve('$PYENV_ROOT') or '' + + if is_blank(pyenv_root) then + pyenv_root = vim.trim(system({ pyenv_path, 'root' })) + info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.') + end + + if not isdir(pyenv_root) then + local message = 'pyenv: Root does not exist: ' + .. pyenv_root + .. '. Ignoring pyenv for all following checks.' + warn(message) + return { '', '' } + end + + info('pyenv: Root: ' .. pyenv_root) + + return { pyenv_path, pyenv_root } +end + +-- Check the Python interpreter's usability. +local function check_bin(bin) + if not isfile(bin) and (not iswin or not isfile(bin .. '.exe')) then + error('"' .. bin .. '" was not found.') + return false + elseif not executable(bin) then + error('"' .. bin .. '" is not executable.') + return false + end + return true +end + +-- Fetch the contents of a URL. +local function download(url) + local has_curl = executable('curl') + if has_curl and vim.fn.system({ 'curl', '-V' }):find('Protocols:.*https') then + local rv = system({ 'curl', '-sL', url }, '', 1, 1) + if shell_error() then + return 'curl error with ' .. url .. ': ' .. shell_error_code + else + return rv + end + elseif executable('python') then + local script = "try:\n\ + from urllib.request import urlopen\n\ + except ImportError:\n\ + from urllib2 import urlopen\n\ + response = urlopen('" .. url .. "')\n\ + print(response.read().decode('utf8'))\n" + local rv = system({ 'python', '-c', script }) + if is_blank(rv) and shell_error() then + return 'python urllib.request error: ' .. shell_error_code + else + return rv + end + end + + local message = 'missing `curl` ' + + if has_curl then + message = message .. '(with HTTPS support) ' + end + message = message .. 'and `python`, cannot make web request' + + return message +end + +-- Get the latest Nvim Python client (pynvim) version from PyPI. +local function latest_pypi_version() + local pypi_version = 'unable to get pypi response' + local pypi_response = download('https://pypi.python.org/pypi/pynvim/json') + if not is_blank(pypi_response) then + local pcall_ok, output = pcall(vim.fn.json_decode, pypi_response) + local pypi_data + if pcall_ok then + pypi_data = output + else + return 'error: ' .. pypi_response + end + + local pypi_element = pypi_data['info'] or {} + pypi_version = pypi_element['version'] or 'unable to parse' + end + return pypi_version +end + +local function is_bad_response(s) + local lower = s:lower() + return vim.startswith(lower, 'unable') + or vim.startswith(lower, 'error') + or vim.startswith(lower, 'outdated') +end + +-- Get version information using the specified interpreter. The interpreter is +-- used directly in case breaking changes were introduced since the last time +-- Nvim's Python client was updated. +-- +-- Returns: { +-- {python executable version}, +-- {current nvim version}, +-- {current pypi nvim status}, +-- {installed version status} +-- } +local function version_info(python) + local pypi_version = latest_pypi_version() + + local python_version = vim.trim(system({ + python, + '-c', + 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', + })) + + if is_blank(python_version) then + python_version = 'unable to parse ' .. python .. ' response' + end + + local nvim_path = vim.trim(system({ + python, + '-c', + 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)', + })) + if shell_error() or is_blank(nvim_path) then + return { python_version, 'unable to load neovim Python module', pypi_version, nvim_path } + end + + -- Assuming that multiple versions of a package are installed, sort them + -- numerically in descending order. + local function compare(metapath1, metapath2) + local a = vim.fn.matchstr(vim.fn.fnamemodify(metapath1, ':p:h:t'), [[[0-9.]\+]]) + local b = vim.fn.matchstr(vim.fn.fnamemodify(metapath2, ':p:h:t'), [[[0-9.]\+]]) + if a == b then + return 0 + elseif a > b then + return 1 + else + return -1 + end + end + + -- Try to get neovim.VERSION (added in 0.1.11dev). + local nvim_version = system({ + python, + '-c', + 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))', + }, '', 1, 1) + if is_blank(nvim_version) then + nvim_version = 'unable to find pynvim module version' + local base = vim.fs.basename(nvim_path, ':h') + local metas = vim.fn.glob(base .. '-*/METADATA', 1, 1) + vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', 1, 1)) + vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', 1, 1)) + metas = table.sort(metas, compare) + + if metas and next(metas) ~= nil then + for _, meta_line in ipairs(vim.fn.readfile(metas[1])) do + if vim.startswith(meta_line, 'Version:') then + nvim_version = vim.fn.matchstr(meta_line, [[^Version: \zs\S\+]]) + break + end + end + end + end + + local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]]) + local version_status = 'unknown; ' .. nvim_path_base + if is_bad_response(nvim_version) and is_bad_response(pypi_version) then + if vim.version.lt(nvim_version, pypi_version) then + version_status = 'outdated; from ' .. nvim_path_base + else + version_status = 'up to date' + end + end + + return { python_version, nvim_version, pypi_version, version_status } +end + +-- Resolves Python executable path by invoking and checking `sys.executable`. +local function python_exepath(invocation) + return vim.fs.normalize( + system(vim.fn.fnameescape(invocation) .. ' -c "import sys; sys.stdout.write(sys.executable)"') + ) +end + +local function python() + start('Python 3 provider (optional)') + + local pyname = 'python3' + local python_exe = '' + local virtual_env = os.getenv('VIRTUAL_ENV') + local venv = virtual_env and vim.fn.resolve(virtual_env) or '' + local host_prog_var = pyname .. '_host_prog' + local python_multiple = {} + + if disabled_via_loaded_var(pyname) then + return + end + + local pyenv_table = check_for_pyenv() + local pyenv = pyenv_table[1] + local pyenv_root = pyenv_table[2] + + if vim.g[host_prog_var] then + local message = 'Using: g:' .. host_prog_var .. ' = "' .. vim.g[host_prog_var] .. '"' + info(message) + end + + local python_table = vim.fn['provider#pythonx#Detect'](3) + pyname = python_table[1] + local pythonx_warnings = python_table[2] + + if is_blank(pyname) then + warn( + 'No Python executable found that can `import neovim`. ' + .. 'Using the first available executable for diagnostics.' + ) + elseif vim.g[host_prog_var] then + python_exe = pyname + end + + -- No Python executable could `import neovim`, or host_prog_var was used. + if not is_blank(pythonx_warnings) then + warn(pythonx_warnings, { + 'See :help provider-python for more information.', + 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', + }) + elseif not is_blank(pyname) and is_blank(python_exe) then + if not vim.g[host_prog_var] then + local message = '`g:' + .. host_prog_var + .. '` is not set. Searching for ' + .. pyname + .. ' in the environment.' + info(message) + end + + if not is_blank(pyenv) then + python_exe = vim.trim(system({ pyenv, 'which', pyname }, '', 1)) + if is_blank(python_exe) then + warn('pyenv could not find ' .. pyname .. '.') + end + end + + if is_blank(python_exe) then + python_exe = vim.fn.exepath(pyname) + + if os.getenv('PATH') then + local path_sep = iswin and ';' or ':' + local paths = vim.split(os.getenv('PATH') or '', path_sep) + + for _, path in ipairs(paths) do + local path_bin = vim.fs.normalize(path .. '/' .. pyname) + if + path_bin ~= vim.fs.normalize(python_exe) + and vim.list_contains(python_multiple, path_bin) + and executable(path_bin) + then + python_multiple[#python_multiple + 1] = path_bin + end + end + + if vim.tbl_count(python_multiple) > 0 then + -- This is worth noting since the user may install something + -- that changes $PATH, like homebrew. + local message = 'Multiple ' + .. pyname + .. ' executables found. ' + .. 'Set `g:' + .. host_prog_var + .. '` to avoid surprises.' + info(message) + end + + if python_exe:find('shims') then + local message = '`' .. python_exe .. '` appears to be a pyenv shim.' + local advice = '`pyenv` is not in $PATH, your pyenv installation is broken. Set `g:' + .. host_prog_var + .. '` to avoid surprises.' + + warn(message, advice) + end + end + end + end + + if not is_blank(python_exe) and not vim.g[host_prog_var] then + if + is_blank(venv) + and not is_blank(pyenv) + and not is_blank(pyenv_root) + and vim.startswith(vim.fn.resolve(python_exe), pyenv_root .. '/') + then + local advice = 'Create a virtualenv specifically for Nvim using pyenv, and set `g:' + .. host_prog_var + .. '`. This will avoid the need to install the pynvim module in each version/virtualenv.' + warn('pyenv is not set up optimally.', advice) + elseif not is_blank(venv) then + local venv_root + if not is_blank(pyenv_root) then + venv_root = pyenv_root + else + venv_root = vim.fs.dirname(venv) + end + + if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then + local advice = 'Create a virtualenv specifically for Nvim and use `g:' + .. host_prog_var + .. '`. This will avoid the need to install the pynvim module in each virtualenv.' + warn('Your virtualenv is not set up optimally.', advice) + end + end + end + + if is_blank(python_exe) and not is_blank(pyname) then + -- An error message should have already printed. + error('`' .. pyname .. '` was not found.') + elseif not is_blank(python_exe) and not check_bin(python_exe) then + python_exe = '' + end + + -- Diagnostic output + info('Executable: ' .. (is_blank(python_exe) and 'Not found' or python_exe)) + if vim.tbl_count(python_multiple) > 0 then + for _, path_bin in ipairs(python_multiple) do + info('Other python executable: ' .. path_bin) + end + end + + if is_blank(python_exe) then + -- No Python executable can import 'neovim'. Check if any Python executable + -- can import 'pynvim'. If so, that Python failed to import 'neovim' as + -- well, which is most probably due to a failed pip upgrade: + -- https://github.com/neovim/neovim/wiki/Following-HEAD#20181118 + local pynvim_table = vim.fn['provider#pythonx#DetectByModule']('pynvim', 3) + local pynvim_exe = pynvim_table[1] + if not is_blank(pynvim_exe) then + local message = 'Detected pip upgrade failure: Python executable can import "pynvim" but not "neovim": ' + .. pynvim_exe + local advice = { + 'Use that Python version to reinstall "pynvim" and optionally "neovim".', + pynvim_exe .. ' -m pip uninstall pynvim neovim', + pynvim_exe .. ' -m pip install pynvim', + pynvim_exe .. ' -m pip install neovim # only if needed by third-party software', + } + error(message, advice) + end + else + local version_info_table = version_info(python_exe) + local majorpyversion = version_info_table[1] + local current = version_info_table[2] + local latest = version_info_table[3] + local status = version_info_table[4] + + if vim.fn.str2nr(majorpyversion) ~= 3 then + warn('Unexpected Python version. This could lead to confusing error messages.') + end + + info('Python version: ' .. majorpyversion) + + if is_bad_response(status) then + info('pynvim version: ' .. current .. ' (' .. status .. ')') + else + info('pynvim version: ' .. current) + end + + if is_bad_response(current) then + error( + 'pynvim is not installed.\nError: ' .. current, + 'Run in shell: ' .. python_exe .. ' -m pip install pynvim' + ) + end + + if is_bad_response(latest) then + warn('Could not contact PyPI to get latest version.') + error('HTTP request failed: ' .. latest) + elseif is_bad_response(status) then + warn('Latest pynvim is NOT installed: ' .. latest) + elseif not is_bad_response(current) then + ok('Latest pynvim is installed.') + end + end + + start('Python virtualenv') + if not virtual_env then + ok('no $VIRTUAL_ENV') + return + end + local errors = {} + -- Keep hints as dict keys in order to discard duplicates. + local 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. + local bin_dir = iswin and 'Scripts' or 'bin' + local venv_bins = vim.tbl_filter(function(v) + -- XXX: Remove irrelevant executables found in bin/. + return not v:match('python%-config') + end, vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true)) + if vim.tbl_count(venv_bins) > 0 then + for _, venv_bin in pairs(venv_bins) do + venv_bin = vim.fs.normalize(venv_bin) + local py_bin_basename = vim.fs.basename(venv_bin) + local nvim_py_bin = python_exepath(vim.fn.exepath(py_bin_basename)) + local subshell_py_bin = python_exepath(py_bin_basename) + if venv_bin ~= nvim_py_bin then + errors[#errors + 1] = '$PATH yields this ' + .. py_bin_basename + .. ' executable: ' + .. nvim_py_bin + local 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.' + hints[hint] = true + end + if venv_bin ~= subshell_py_bin then + errors[#errors + 1] = '$PATH in subshells yields this ' + .. py_bin_basename + .. ' executable: ' + .. subshell_py_bin + local 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/34996' + hints[hint] = true + end + end + else + errors[#errors + 1] = 'no Python executables found in the virtualenv ' + .. bin_dir + .. ' directory.' + end + + local msg = '$VIRTUAL_ENV is set to: ' .. virtual_env + if vim.tbl_count(errors) > 0 then + if vim.tbl_count(venv_bins) > 0 then + msg = msg + .. '\nAnd its ' + .. bin_dir + .. ' directory contains: ' + .. vim.fn.join(vim.fn.map(venv_bins, [[fnamemodify(v:val, ':t')]]), ', ') + end + local conj = '\nBut ' + for _, err in ipairs(errors) do + msg = msg .. conj .. err + conj = '\nAnd ' + end + msg = msg .. '\nSo invoking Python may lead to unexpected results.' + warn(msg, vim.fn.keys(hints)) + else + info(msg) + info( + 'Python version: ' + .. system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"') + ) + ok('$VIRTUAL_ENV provides :!python.') + end +end + +local function ruby() + start('Ruby provider (optional)') + + if disabled_via_loaded_var('ruby') then + return + end + + if not executable('ruby') or not executable('gem') then + warn( + '`ruby` and `gem` must be in $PATH.', + 'Install Ruby and verify that `ruby` and `gem` commands work.' + ) + return + end + info('Ruby: ' .. system({ 'ruby', '-v' })) + + local ruby_detect_table = vim.fn['provider#ruby#Detect']() + local host = ruby_detect_table[1] + if is_blank(host) then + warn('`neovim-ruby-host` not found.', { + 'Run `gem install neovim` to ensure the neovim RubyGem is installed.', + 'Run `gem environment` to ensure the gem bin directory is in $PATH.', + 'If you are using rvm/rbenv/chruby, try "rehashing".', + 'See :help g:ruby_host_prog for non-standard gem installations.', + 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', + }) + return + end + info('Host: ' .. host) + + local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$') + local latest_gem = system(vim.fn.split(latest_gem_cmd)) + if shell_error() or is_blank(latest_gem) then + error( + 'Failed to run: ' .. latest_gem_cmd, + { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } + ) + return + end + local gem_split = vim.split(latest_gem, [[neovim (\|, \|)$]]) + latest_gem = gem_split[1] or 'not found' + + local current_gem_cmd = { host, '--version' } + local current_gem = system(current_gem_cmd) + if shell_error() then + error( + 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), + { 'Report this issue with the output of: ', table.concat(current_gem_cmd, ' ') } + ) + return + end + + if vim.version.lt(current_gem, latest_gem) then + local message = 'Gem "neovim" is out-of-date. Installed: ' + .. current_gem + .. ', latest: ' + .. latest_gem + warn(message, 'Run in shell: gem update neovim') + else + ok('Latest "neovim" gem is installed: ' .. current_gem) + end +end + +local function node() + start('Node.js provider (optional)') + + if disabled_via_loaded_var('node') then + return + end + + if + not executable('node') + or (not executable('npm') and not executable('yarn') and not executable('pnpm')) + then + warn( + '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.', + 'Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.' + ) + return + end + + -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' + local node_v = system({ 'node', '-v' }) + info('Node.js: ' .. node_v) + if shell_error() or vim.version.lt(node_v, '6.0.0') then + warn('Nvim node.js host does not support Node ' .. node_v) + -- Skip further checks, they are nonsense if nodejs is too old. + return + end + if vim.fn['provider#node#can_inspect']() == 0 then + warn( + 'node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.' + ) + end + + local node_detect_table = vim.fn['provider#node#Detect']() + local host = node_detect_table[1] + if is_blank(host) then + warn('Missing "neovim" npm (or yarn, pnpm) package.', { + 'Run in shell: npm install -g neovim', + 'Run in shell (if you use yarn): yarn global add neovim', + 'Run in shell (if you use pnpm): pnpm install -g neovim', + 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim', + }) + return + end + info('Nvim node.js host: ' .. host) + + local manager = 'npm' + if executable('yarn') then + manager = 'yarn' + elseif executable('pnpm') then + manager = 'pnpm' + end + + local latest_npm_cmd = ( + iswin and 'cmd /c ' .. manager .. ' info neovim --json' or manager .. ' info neovim --json' + ) + local latest_npm = system(vim.fn.split(latest_npm_cmd)) + if shell_error() or is_blank(latest_npm) then + error( + 'Failed to run: ' .. latest_npm_cmd, + { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } + ) + return + end + + local pcall_ok, output = pcall(vim.fn.json_decode, latest_npm) + local pkg_data + if pcall_ok then + pkg_data = output + else + return 'error: ' .. latest_npm + end + local latest_npm_subtable = pkg_data['dist-tags'] or {} + latest_npm = latest_npm_subtable['latest'] or 'unable to parse' + + local current_npm_cmd = { 'node', host, '--version' } + local current_npm = system(current_npm_cmd) + if shell_error() then + error( + 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), + { 'Report this issue with the output of: ', table.concat(current_npm_cmd, ' ') } + ) + return + end + + if latest_npm ~= 'unable to parse' and vim.version.lt(current_npm, latest_npm) then + local message = 'Package "neovim" is out-of-date. Installed: ' + .. current_npm + .. ' latest: ' + .. latest_npm + warn(message({ + 'Run in shell: npm install -g neovim', + 'Run in shell (if you use yarn): yarn global add neovim', + 'Run in shell (if you use pnpm): pnpm install -g neovim', + })) + else + ok('Latest "neovim" npm/yarn/pnpm package is installed: ' .. current_npm) + end +end + +local function perl() + start('Perl provider (optional)') + + if disabled_via_loaded_var('perl') then + return + end + + local perl_detect_table = vim.fn['provider#perl#Detect']() + local perl_exec = perl_detect_table[1] + local perl_warnings = perl_detect_table[2] + + if is_blank(perl_exec) then + if not is_blank(perl_warnings) then + warn(perl_warnings, { + 'See :help provider-perl for more information.', + 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', + }) + else + warn('No usable perl executable found') + end + return + end + + info('perl executable: ' .. perl_exec) + + -- we cannot use cpanm that is on the path, as it may not be for the perl + -- set with g:perl_host_prog + system({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' }) + if shell_error() then + return { perl_exec, '"App::cpanminus" module is not installed' } + end + + local latest_cpan_cmd = { + perl_exec, + '-MApp::cpanminus::fatscript', + '-e', + 'my $app = App::cpanminus::script->new; $app->parse_options ("--info", "-q", "Neovim::Ext"); exit $app->doit', + } + + local latest_cpan = system(latest_cpan_cmd) + if shell_error() or is_blank(latest_cpan) then + error( + 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '), + { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } + ) + return + elseif latest_cpan[1] == '!' then + local cpanm_errs = vim.split(latest_cpan, '!') + if cpanm_errs[1]:find("Can't write to ") then + local advice = {} + for i = 2, #cpanm_errs do + advice[#advice + 1] = cpanm_errs[i] + end + + warn(cpanm_errs[1], advice) + -- Last line is the package info + latest_cpan = cpanm_errs[#cpanm_errs] + else + error('Unknown warning from command: ' .. latest_cpan_cmd, cpanm_errs) + return + end + end + latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]]) + if is_blank(latest_cpan) then + error('Cannot parse version number from cpanm output: ' .. latest_cpan) + return + end + + local current_cpan_cmd = { perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION' } + local current_cpan = system(current_cpan_cmd) + if shell_error then + error( + 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), + { 'Report this issue with the output of: ', table.concat(current_cpan_cmd, ' ') } + ) + return + end + + if vim.version.lt(current_cpan, latest_cpan) then + local message = 'Module "Neovim::Ext" is out-of-date. Installed: ' + .. current_cpan + .. ', latest: ' + .. latest_cpan + warn(message, 'Run in shell: cpanm -n Neovim::Ext') + else + ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan) + end +end + +function M.check() + clipboard() + python() + ruby() + node() + perl() +end + +return M diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index fa0980563a..c922ec93db 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -402,8 +402,8 @@ end --- Input and output positions are (0,0)-indexed and indicate byte positions. --- ---@param bufnr integer number of buffer ----@param pos1 integer[] (line, column) tuple marking beginning of region ----@param pos2 integer[] (line, column) tuple marking end of region +---@param pos1 integer[]|string start of region as a (line, column) tuple or string accepted by |getpos()| +---@param pos2 integer[]|string end of region as a (line, column) tuple or string accepted by |getpos()| ---@param regtype string type of selection, see |setreg()| ---@param inclusive boolean indicating whether column of pos2 is inclusive ---@return table region Table of the form `{linenr = {startcol,endcol}}`. @@ -414,6 +414,24 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) vim.fn.bufload(bufnr) end + if type(pos1) == 'string' then + local pos = vim.fn.getpos(pos1) + pos1 = { pos[2] - 1, pos[3] - 1 + pos[4] } + end + if type(pos2) == 'string' then + local pos = vim.fn.getpos(pos2) + pos2 = { pos[2] - 1, pos[3] - 1 + pos[4] } + end + + if pos1[1] > pos2[1] or (pos1[1] == pos2[1] and pos1[2] > pos2[2]) then + pos1, pos2 = pos2, pos1 + end + + -- getpos() may return {0,0,0,0} + if pos1[1] < 0 or pos1[2] < 0 then + return {} + end + -- check that region falls within current buffer local buf_line_count = vim.api.nvim_buf_line_count(bufnr) pos1[1] = math.min(pos1[1], buf_line_count - 1) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 714038f8e4..d1b50304c7 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -744,7 +744,7 @@ function M.get_namespaces() end ---@class Diagnostic ----@field buffer integer +---@field bufnr integer ---@field lnum integer 0-indexed ---@field end_lnum nil|integer 0-indexed ---@field col integer 0-indexed diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index d71a806ea8..a6cfcb730f 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -15,8 +15,8 @@ M.priorities = { ---@param bufnr integer Buffer number to apply highlighting to ---@param ns integer Namespace to add highlight to ---@param higroup string Highlight group to use for highlighting ----@param start { [1]: integer, [2]: integer } Start position {line, col} ----@param finish { [1]: integer, [2]: integer } Finish position {line, col} +---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()| +---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()| ---@param opts table|nil Optional parameters -- - regtype type of range (see |setreg()|, default charwise) -- - inclusive boolean indicating whether the range is end-inclusive (default false) @@ -27,11 +27,6 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local inclusive = opts.inclusive or false local priority = opts.priority or M.priorities.user - -- sanity check - if start[2] < 0 or finish[1] < start[1] then - return - end - local region = vim.region(bufnr, start, finish, regtype, inclusive) for linenr, cols in pairs(region) do local end_row @@ -104,18 +99,12 @@ function M.on_yank(opts) yank_timer:close() end - local pos1 = vim.fn.getpos("'[") - local pos2 = vim.fn.getpos("']") - - pos1 = { pos1[2] - 1, pos1[3] - 1 + pos1[4] } - pos2 = { pos2[2] - 1, pos2[3] - 1 + pos2[4] } - M.range( bufnr, yank_ns, higroup, - pos1, - pos2, + "'[", + "']", { regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user } ) diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 38b1e9fc0f..66627fe4e7 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -5,7 +5,7 @@ local loaders = package.loaders local M = {} ----@alias CacheHash {mtime: {sec:number, nsec:number}, size:number, type: string} +---@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: uv.aliases.fs_stat_types} ---@alias CacheEntry {hash:CacheHash, chunk:string} ---@class ModuleFindOpts @@ -28,12 +28,11 @@ M.enabled = false ---@field _rtp string[] ---@field _rtp_pure string[] ---@field _rtp_key string +---@field _hashes? table<string, CacheHash> local Loader = { VERSION = 3, ---@type table<string, table<string,ModuleInfo>> _indexed = {}, - ---@type table<string, CacheHash> - _hashes = {}, ---@type table<string, string[]> _topmods = {}, _loadfile = loadfile, @@ -44,9 +43,13 @@ local Loader = { } --- @param path string ---- @return uv.fs_stat.result +--- @return CacheHash --- @private function Loader.get_hash(path) + if not Loader._hashes then + return uv.fs_stat(path) --[[@as CacheHash]] + end + if not Loader._hashes[path] then -- Note we must never save a stat for a non-existent path. -- For non-existent paths fs_stat() will return nil. @@ -163,13 +166,16 @@ end ---@return string|function ---@private function Loader.loader(modname) + Loader._hashes = {} local ret = M.find(modname)[1] if ret then -- Make sure to call the global loadfile so we respect any augmentations done elsewhere. -- E.g. profiling local chunk, err = loadfile(ret.modpath) + Loader._hashes = nil return chunk or error(err) end + Loader._hashes = nil return '\ncache_loader: module ' .. modname .. ' not found' end @@ -373,7 +379,9 @@ function M.reset(path) end -- Path could be a directory so just clear all the hashes. - Loader._hashes = {} + if Loader._hashes then + Loader._hashes = {} + end end --- Enables the experimental Lua module loader: @@ -441,7 +449,7 @@ function Loader.lsmod(path) if topname then Loader._indexed[path][topname] = { modpath = modpath, modname = topname } Loader._topmods[topname] = Loader._topmods[topname] or {} - if not vim.tbl_contains(Loader._topmods[topname], path) then + if not vim.list_contains(Loader._topmods[topname], path) then table.insert(Loader._topmods[topname], path) end end @@ -515,7 +523,7 @@ function M._inspect(opts) { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' }, }) for k, v in pairs(Loader._stats[stat]) do - if not vim.tbl_contains({ 'time', 'total' }, k) then + if not vim.list_contains({ 'time', 'total' }, k) then chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) } chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' } end diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 2d39f2d45d..5c78bd7580 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -171,7 +171,7 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids) if restrict_client_ids and #restrict_client_ids > 0 then local filtered_client_ids = {} for client_id in pairs(client_ids) do - if vim.tbl_contains(restrict_client_ids, client_id) then + if vim.list_contains(restrict_client_ids, client_id) then filtered_client_ids[client_id] = true end end @@ -2186,7 +2186,7 @@ function lsp.formatexpr(opts) opts = opts or {} local timeout_ms = opts.timeout_ms or 500 - if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then + if vim.list_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then -- `formatexpr` is also called when exceeding `textwidth` in insert mode -- fall back to internal formatting return 1 @@ -2384,4 +2384,3 @@ lsp.commands = setmetatable({}, { }) return lsp --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua index 797d8960d5..e7ada5415f 100644 --- a/runtime/lua/vim/lsp/_snippet.lua +++ b/runtime/lua/vim/lsp/_snippet.lua @@ -17,14 +17,14 @@ P.take_until = function(targets, specials) table.insert(raw, '\\') new_pos = new_pos + 1 c = string.sub(input, new_pos, new_pos) - if not vim.tbl_contains(targets, c) and not vim.tbl_contains(specials, c) then + if not vim.list_contains(targets, c) and not vim.list_contains(specials, c) then table.insert(esc, '\\') end table.insert(raw, c) table.insert(esc, c) new_pos = new_pos + 1 else - if vim.tbl_contains(targets, c) then + if vim.list_contains(targets, c) then break end table.insert(raw, c) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8bf3764f5e..3d9011656f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -806,4 +806,3 @@ function M.execute_command(command_params) end return M --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 81cac6a511..005a0047fa 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -42,7 +42,7 @@ local function execute_lens(lens, bufnr, client_id) -- Need to use the client that returned the lens → must not use buf_request local command_provider = client.server_capabilities.executeCommandProvider local commands = type(command_provider) == 'table' and command_provider.commands or {} - if not vim.tbl_contains(commands, command.command) then + if not vim.list_contains(commands, command.command) then vim.notify( string.format( 'Language server does not support command `%s`. This command may require a client extension.', diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index d01f8e6159..71bef43bc1 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -644,4 +644,3 @@ for k, fn in pairs(M) do end return M --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 51dcb7d21d..3d5bc06c3f 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -174,4 +174,3 @@ function log.should_log(level) end return log --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index f4489ad17d..2cb8fc7955 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -894,4 +894,3 @@ function protocol.resolve_capabilities(server_capabilities) end return protocol --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 30b61d01d6..af3190c9bd 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -753,4 +753,3 @@ return { client_errors = client_errors, create_read_loop = create_read_loop, } --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ebde7af16c..31af2afb0b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1476,7 +1476,7 @@ end local function close_preview_window(winnr, bufnrs) vim.schedule(function() -- exit if we are in one of ignored buffers - if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then + if bufnrs and vim.list_contains(bufnrs, api.nvim_get_current_buf()) then return end @@ -2154,4 +2154,3 @@ M._get_line_byte_from_position = get_line_byte_from_position 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 eb734fb512..f700f4a6b3 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -252,12 +252,53 @@ function vim.tbl_filter(func, t) return rettab end ---- Checks if a list-like (vector) table contains `value`. +--- Checks if a table contains a given value, specified either directly or via +--- a predicate that is checked for each value. +--- +--- Example: +--- <pre>lua +--- vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) +--- return vim.deep_equal(v, { 'b', 'c' }) +--- end, { predicate = true }) +--- -- true +--- </pre> +--- +---@see |vim.list_contains()| for checking values in list-like tables --- ---@param t table Table to check +---@param value any Value to compare or predicate function reference +---@param opts (table|nil) Keyword arguments |kwargs|: +--- - predicate: (boolean) `value` is a function reference to be checked (default false) +---@return boolean `true` if `t` contains `value` +function vim.tbl_contains(t, value, opts) + vim.validate({ t = { t, 't' }, opts = { opts, 't', true } }) + + local pred + if opts and opts.predicate then + vim.validate({ value = { value, 'c' } }) + pred = value + else + pred = function(v) + return v == value + end + end + + for _, v in pairs(t) do + if pred(v) then + return true + end + end + return false +end + +--- Checks if a list-like table (integer keys without gaps) contains `value`. +--- +---@see |vim.tbl_contains()| for checking values in general tables +--- +---@param t table Table to check (must be list-like, not validated) ---@param value any Value to compare ---@return boolean `true` if `t` contains `value` -function vim.tbl_contains(t, value) +function vim.list_contains(t, value) vim.validate({ t = { t, 't' } }) for _, v in ipairs(t) do @@ -279,10 +320,10 @@ function vim.tbl_isempty(t) return next(t) == nil end ---- We only merge empty tables or tables that are not a list +--- We only merge empty tables or tables that are not an array (indexed by integers) ---@private local function can_merge(v) - return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_islist(v)) + return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_isarray(v)) end local function tbl_extend(behavior, deep_extend, ...) @@ -513,15 +554,15 @@ function vim.spairs(t) end end ---- Tests if a Lua table can be treated as an array. +--- Tests if a Lua table can be treated as an array (a table indexed by integers). --- --- Empty table `{}` is assumed to be an array, unless it was created by --- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, --- for example from |rpcrequest()| or |vim.fn|. --- ----@param t table Table ----@return boolean `true` if array-like table, else `false` -function vim.tbl_islist(t) +---@param t table +---@return boolean `true` if array-like table, else `false`. +function vim.tbl_isarray(t) if type(t) ~= 'table' then return false end @@ -529,7 +570,8 @@ function vim.tbl_islist(t) local count = 0 for k, _ in pairs(t) do - if type(k) == 'number' then + --- Check if the number k is an integer + if type(k) == 'number' and k == math.floor(k) then count = count + 1 else return false @@ -548,6 +590,38 @@ function vim.tbl_islist(t) end end +--- Tests if a Lua table can be treated as a list (a table indexed by consecutive integers starting from 1). +--- +--- Empty table `{}` is assumed to be an list, unless it was created by +--- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, +--- for example from |rpcrequest()| or |vim.fn|. +--- +---@param t table +---@return boolean `true` if list-like table, else `false`. +function vim.tbl_islist(t) + if type(t) ~= 'table' then + return false + end + + local num_elem = vim.tbl_count(t) + + if num_elem == 0 then + -- TODO(bfredl): in the future, we will always be inside nvim + -- then this check can be deleted. + if vim._empty_dict_mt == nil then + return nil + end + return getmetatable(t) ~= vim._empty_dict_mt + else + for i = 1, num_elem do + if t[i] == nil then + return false + end + end + return true + end +end + --- Counts the number of non-nil values in table `t`. --- --- <pre>lua @@ -811,4 +885,3 @@ function vim.defaulttable(create) end return vim --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5b87e6ac31..8a747ba14c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -458,7 +458,7 @@ local directive_handlers = { metadata[id] = {} end - local pattern, replacement = pred[3], pred[3] + local pattern, replacement = pred[3], pred[4] assert(type(pattern) == 'string') assert(type(replacement) == 'string') diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 38759fcdc0..08ed829114 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -134,4 +134,3 @@ return { uri_to_fname = uri_to_fname, uri_to_bufnr = uri_to_bufnr, } --- vim:sw=2 ts=2 et diff --git a/runtime/mswin.vim b/runtime/mswin.vim index 2b04c1aea3..482b720c53 100644 --- a/runtime/mswin.vim +++ b/runtime/mswin.vim @@ -15,7 +15,10 @@ endif set cpo&vim " set 'selection', 'selectmode', 'mousemodel' and 'keymodel' for MS-Windows -behave mswin +set selection=exclusive +set selectmode=mouse,key +set mousemodel=popup +set keymodel=startsel,stopsel " backspace and cursor keys wrap to previous/next line set backspace=indent,eol,start whichwrap+=<,>,[,] diff --git a/runtime/pack/dist/opt/vimball/autoload/vimball.vim b/runtime/pack/dist/opt/vimball/autoload/vimball.vim deleted file mode 100644 index 9c7dcbda0f..0000000000 --- a/runtime/pack/dist/opt/vimball/autoload/vimball.vim +++ /dev/null @@ -1,775 +0,0 @@ -" vimball.vim : construct a file containing both paths and files -" Author: Charles E. Campbell -" Date: Apr 11, 2016 -" Version: 37 -" GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim -" Copyright: (c) 2004-2011 by Charles E. Campbell -" The VIM LICENSE applies to Vimball.vim, and Vimball.txt -" (see |copyright|) except use "Vimball" instead of "Vim". -" No warranty, express or implied. -" *** *** Use At-Your-Own-Risk! *** *** - -" --------------------------------------------------------------------- -" Load Once: {{{1 -if &cp || exists("g:loaded_vimball") - finish -endif -let g:loaded_vimball = "v37" -if v:version < 702 - echohl WarningMsg - echo "***warning*** this version of vimball needs vim 7.2" - echohl Normal - finish -endif -let s:keepcpo= &cpo -set cpo&vim -"DechoTabOn - -" ===================================================================== -" Constants: {{{1 -if !exists("s:USAGE") - let s:USAGE = 0 - let s:WARNING = 1 - let s:ERROR = 2 - - " determine if cygwin is in use or not - if !exists("g:netrw_cygwin") - if has("win32") || has("win95") || has("win64") || has("win16") - if &shell =~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$' - let g:netrw_cygwin= 1 - else - let g:netrw_cygwin= 0 - endif - else - let g:netrw_cygwin= 0 - endif - endif - - " set up g:vimball_mkdir if the mkdir() call isn't defined - if !exists("*mkdir") - if exists("g:netrw_local_mkdir") - let g:vimball_mkdir= g:netrw_local_mkdir - elseif executable("mkdir") - let g:vimball_mkdir= "mkdir" - elseif executable("makedir") - let g:vimball_mkdir= "makedir" - endif - if !exists(g:vimball_mkdir) - call vimball#ShowMesg(s:WARNING,"(vimball) g:vimball_mkdir undefined") - endif - endif -endif - -" ===================================================================== -" Functions: {{{1 - -" --------------------------------------------------------------------- -" vimball#MkVimball: creates a vimball given a list of paths to files {{{2 -" Input: -" line1,line2: a range of lines containing paths to files to be included in the vimball -" writelevel : if true, force a write to filename.vmb, even if it exists -" (usually accomplished with :MkVimball! ... -" filename : base name of file to be created (ie. filename.vmb) -" Output: a filename.vmb using vimball format: -" path -" filesize -" [file] -" path -" filesize -" [file] -fun! vimball#MkVimball(line1,line2,writelevel,...) range -" call Dfunc("MkVimball(line1=".a:line1." line2=".a:line2." writelevel=".a:writelevel." vimballname<".a:1.">) a:0=".a:0) - if a:1 =~ '\.vim$' || a:1 =~ '\.txt$' - let vbname= substitute(a:1,'\.\a\{3}$','.vmb','') - else - let vbname= a:1 - endif - if vbname !~ '\.vmb$' - let vbname= vbname.'.vmb' - endif -" call Decho("vbname<".vbname.">") - if !a:writelevel && a:1 =~ '[\/]' - call vimball#ShowMesg(s:ERROR,"(MkVimball) vimball name<".a:1."> should not include slashes; use ! to insist") -" call Dret("MkVimball : vimball name<".a:1."> should not include slashes") - return - endif - if !a:writelevel && filereadable(vbname) - call vimball#ShowMesg(s:ERROR,"(MkVimball) file<".vbname."> exists; use ! to insist") -" call Dret("MkVimball : file<".vbname."> already exists; use ! to insist") - return - endif - - " user option bypass - call vimball#SaveSettings() - - if a:0 >= 2 - " allow user to specify where to get the files - let home= expand(a:2) - else - " use first existing directory from rtp - let home= vimball#VimballHome() - endif - - " save current directory - let curdir = getcwd() - call s:ChgDir(home) - - " record current tab, initialize while loop index - let curtabnr = tabpagenr() - let linenr = a:line1 -" call Decho("curtabnr=".curtabnr) - - while linenr <= a:line2 - let svfile = getline(linenr) -" call Decho("svfile<".svfile.">") - - if !filereadable(svfile) - call vimball#ShowMesg(s:ERROR,"unable to read file<".svfile.">") - call s:ChgDir(curdir) - call vimball#RestoreSettings() -" call Dret("MkVimball") - return - endif - - " create/switch to mkvimball tab - if !exists("vbtabnr") - tabnew - sil! file Vimball - let vbtabnr= tabpagenr() - else - exe "tabn ".vbtabnr - endif - - let lastline= line("$") + 1 - if lastline == 2 && getline("$") == "" - call setline(1,'" Vimball Archiver by Charles E. Campbell') - call setline(2,'UseVimball') - call setline(3,'finish') - let lastline= line("$") + 1 - endif - call setline(lastline ,substitute(svfile,'$',' [[[1','')) - call setline(lastline+1,0) - - " write the file from the tab -" call Decho("exe $r ".fnameescape(svfile)) - exe "$r ".fnameescape(svfile) - - call setline(lastline+1,line("$") - lastline - 1) -" call Decho("lastline=".lastline." line$=".line("$")) - - " restore to normal tab - exe "tabn ".curtabnr - let linenr= linenr + 1 - endwhile - - " write the vimball - exe "tabn ".vbtabnr - call s:ChgDir(curdir) - setlocal ff=unix - if a:writelevel -" call Decho("exe w! ".fnameescape(vbname)) - exe "w! ".fnameescape(vbname) - else -" call Decho("exe w ".fnameescape(vbname)) - exe "w ".fnameescape(vbname) - endif -" call Decho("Vimball<".vbname."> created") - echo "Vimball<".vbname."> created" - - " remove the evidence - setlocal nomod bh=wipe - exe "tabn ".curtabnr - exe "tabc! ".vbtabnr - - " restore options - call vimball#RestoreSettings() - -" call Dret("MkVimball") -endfun - -" --------------------------------------------------------------------- -" vimball#Vimball: extract and distribute contents from a vimball {{{2 -" (invoked the the UseVimball command embedded in -" vimballs' prologue) -fun! vimball#Vimball(really,...) -" call Dfunc("vimball#Vimball(really=".a:really.") a:0=".a:0) - - if v:version < 701 || (v:version == 701 && !exists('*fnameescape')) - echoerr "your vim is missing the fnameescape() function (pls upgrade to vim 7.2 or later)" -" call Dret("vimball#Vimball : needs 7.1 with patch 299 or later") - return - endif - - if getline(1) !~ '^" Vimball Archiver' - echoerr "(Vimball) The current file does not appear to be a Vimball!" -" call Dret("vimball#Vimball") - return - endif - - " set up standard settings - call vimball#SaveSettings() - let curtabnr = tabpagenr() - let vimballfile = expand("%:tr") - - " set up vimball tab -" call Decho("setting up vimball tab") - tabnew - sil! file Vimball - let vbtabnr= tabpagenr() - let didhelp= "" - - " go to vim plugin home - if a:0 > 0 - " let user specify the directory where the vimball is to be unpacked. - " If, however, the user did not specify a full path, set the home to be below the current directory - let home= expand(a:1) - if has("win32") || has("win95") || has("win64") || has("win16") - if home !~ '^\a:[/\\]' - let home= getcwd().'/'.a:1 - endif - elseif home !~ '^/' - let home= getcwd().'/'.a:1 - endif - else - let home= vimball#VimballHome() - endif -" call Decho("home<".home.">") - - " save current directory and remove older same-named vimball, if any - let curdir = getcwd() -" call Decho("home<".home.">") -" call Decho("curdir<".curdir.">") - - call s:ChgDir(home) - let s:ok_unablefind= 1 - call vimball#RmVimball(vimballfile) - unlet s:ok_unablefind - - let linenr = 4 - let filecnt = 0 - - " give title to listing of (extracted) files from Vimball Archive - if a:really - echohl Title | echomsg "Vimball Archive" | echohl None - else - echohl Title | echomsg "Vimball Archive Listing" | echohl None - echohl Statement | echomsg "files would be placed under: ".home | echohl None - endif - - " apportion vimball contents to various files -" call Decho("exe tabn ".curtabnr) - exe "tabn ".curtabnr -" call Decho("linenr=".linenr." line$=".line("$")) - while 1 < linenr && linenr < line("$") - let fname = substitute(getline(linenr),'\t\[\[\[1$','','') - let fname = substitute(fname,'\\','/','g') - let fsize = substitute(getline(linenr+1),'^\(\d\+\).\{-}$','\1','')+0 - let fenc = substitute(getline(linenr+1),'^\d\+\s*\(\S\{-}\)$','\1','') - let filecnt = filecnt + 1 -" call Decho("fname<".fname."> fsize=".fsize." filecnt=".filecnt. " fenc=".fenc) - - if a:really - echomsg "extracted <".fname.">: ".fsize." lines" - else - echomsg "would extract <".fname.">: ".fsize." lines" - endif -" call Decho("using L#".linenr.": will extract file<".fname.">") -" call Decho("using L#".(linenr+1).": fsize=".fsize) - - " Allow AsNeeded/ directory to take place of plugin/ directory - " when AsNeeded/filename is filereadable or was present in VimballRecord - if fname =~ '\<plugin/' - let anfname= substitute(fname,'\<plugin/','AsNeeded/','') - if filereadable(anfname) || (exists("s:VBRstring") && s:VBRstring =~# anfname) -" call Decho("using anfname<".anfname."> instead of <".fname.">") - let fname= anfname - endif - endif - - " make directories if they don't exist yet - if a:really -" call Decho("making directories if they don't exist yet (fname<".fname.">)") - let fnamebuf= substitute(fname,'\\','/','g') - let dirpath = substitute(home,'\\','/','g') -" call Decho("init: fnamebuf<".fnamebuf.">") -" call Decho("init: dirpath <".dirpath.">") - while fnamebuf =~ '/' - let dirname = dirpath."/".substitute(fnamebuf,'/.*$','','') - let dirpath = dirname - let fnamebuf = substitute(fnamebuf,'^.\{-}/\(.*\)$','\1','') -" call Decho("dirname<".dirname.">") -" call Decho("dirpath<".dirpath.">") - if !isdirectory(dirname) -" call Decho("making <".dirname.">") - if exists("g:vimball_mkdir") - call system(g:vimball_mkdir." ".shellescape(dirname)) - else - call mkdir(dirname) - endif - call s:RecordInVar(home,"rmdir('".dirname."')") - endif - endwhile - endif - call s:ChgDir(home) - - " grab specified qty of lines and place into "a" buffer - " (skip over path/filename and qty-lines) - let linenr = linenr + 2 - let lastline = linenr + fsize - 1 -" call Decho("exe ".linenr.",".lastline."yank a") - " no point in handling a zero-length file - if lastline >= linenr - exe "silent ".linenr.",".lastline."yank a" - - " copy "a" buffer into tab -" call Decho('copy "a buffer into tab#'.vbtabnr) - exe "tabn ".vbtabnr - setlocal ma - sil! %d - silent put a - 1 - sil! d - - " write tab to file - if a:really - let fnamepath= home."/".fname -" call Decho("exe w! ".fnameescape(fnamepath)) - if fenc != "" - exe "silent w! ++enc=".fnameescape(fenc)." ".fnameescape(fnamepath) - else - exe "silent w! ".fnameescape(fnamepath) - endif - echo "wrote ".fnameescape(fnamepath) - call s:RecordInVar(home,"call delete('".fnamepath."')") - endif - - " return to tab with vimball -" call Decho("exe tabn ".curtabnr) - exe "tabn ".curtabnr - - " set up help if it's a doc/*.txt file -" call Decho("didhelp<".didhelp."> fname<".fname.">") - if a:really && didhelp == "" && fname =~ 'doc/[^/]\+\.\(txt\|..x\)$' - let didhelp= substitute(fname,'^\(.*\<doc\)[/\\][^.]*\.\(txt\|..x\)$','\1','') -" call Decho("didhelp<".didhelp.">") - endif - endif - - " update for next file -" call Decho("update linenr= [linenr=".linenr."] + [fsize=".fsize."] = ".(linenr+fsize)) - let linenr= linenr + fsize - endwhile - - " set up help -" call Decho("about to set up help: didhelp<".didhelp.">") - if didhelp != "" - let htpath= home."/".didhelp -" call Decho("exe helptags ".htpath) - exe "helptags ".fnameescape(htpath) - echo "did helptags" - endif - - " make sure a "Press ENTER..." prompt appears to keep the messages showing! - while filecnt <= &ch - echomsg " " - let filecnt= filecnt + 1 - endwhile - - " record actions in <.VimballRecord> - call s:RecordInFile(home) - - " restore events, delete tab and buffer - exe "sil! tabn ".vbtabnr - setlocal nomod bh=wipe - exe "sil! tabn ".curtabnr - exe "sil! tabc! ".vbtabnr - call vimball#RestoreSettings() - call s:ChgDir(curdir) - -" call Dret("vimball#Vimball") -endfun - -" --------------------------------------------------------------------- -" vimball#RmVimball: remove any files, remove any directories made by any {{{2 -" previous vimball extraction based on a file of the current -" name. -" Usage: RmVimball (assume current file is a vimball; remove) -" RmVimball vimballname -fun! vimball#RmVimball(...) -" call Dfunc("vimball#RmVimball() a:0=".a:0) - if exists("g:vimball_norecord") -" call Dret("vimball#RmVimball : (g:vimball_norecord)") - return - endif - - if a:0 == 0 - let curfile= expand("%:tr") -" call Decho("case a:0=0: curfile<".curfile."> (used expand(%:tr))") - else - if a:1 =~ '[\/]' - call vimball#ShowMesg(s:USAGE,"RmVimball vimballname [path]") -" call Dret("vimball#RmVimball : suspect a:1<".a:1.">") - return - endif - let curfile= a:1 -" call Decho("case a:0=".a:0.": curfile<".curfile.">") - endif - if curfile =~ '\.vmb$' - let curfile= substitute(curfile,'\.vmb','','') - elseif curfile =~ '\.vba$' - let curfile= substitute(curfile,'\.vba','','') - endif - if a:0 >= 2 - let home= expand(a:2) - else - let home= vimball#VimballHome() - endif - let curdir = getcwd() -" call Decho("home <".home.">") -" call Decho("curfile<".curfile.">") -" call Decho("curdir <".curdir.">") - - call s:ChgDir(home) - if filereadable(".VimballRecord") -" call Decho(".VimballRecord is readable") -" call Decho("curfile<".curfile.">") - keepalt keepjumps 1split - sil! keepalt keepjumps e .VimballRecord - let keepsrch= @/ -" call Decho('search for ^\M'.curfile.'.\m: ') -" call Decho('search for ^\M'.curfile.'.\m{vba|vmb}: ') -" call Decho('search for ^\M'.curfile.'\m[-0-9.]*\.{vba|vmb}: ') - if search('^\M'.curfile."\m: ".'cw') - let foundit= 1 - elseif search('^\M'.curfile.".\mvmb: ",'cw') - let foundit= 2 - elseif search('^\M'.curfile.'\m[-0-9.]*\.vmb: ','cw') - let foundit= 2 - elseif search('^\M'.curfile.".\mvba: ",'cw') - let foundit= 1 - elseif search('^\M'.curfile.'\m[-0-9.]*\.vba: ','cw') - let foundit= 1 - else - let foundit = 0 - endif - if foundit - if foundit == 1 - let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vba: ','','') - else - let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vmb: ','','') - endif - let s:VBRstring= substitute(exestring,'call delete(','','g') - let s:VBRstring= substitute(s:VBRstring,"[')]",'','g') -" call Decho("exe ".exestring) - sil! keepalt keepjumps exe exestring - sil! keepalt keepjumps d - let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g")) -" call Decho("exestring<".exestring.">") - echomsg "removed ".exestring." files" - else - let s:VBRstring= '' - let curfile = substitute(curfile,'\.vmb','','') -" call Decho("unable to find <".curfile."> in .VimballRecord") - if !exists("s:ok_unablefind") - call vimball#ShowMesg(s:WARNING,"(RmVimball) unable to find <".curfile."> in .VimballRecord") - endif - endif - sil! keepalt keepjumps g/^\s*$/d - sil! keepalt keepjumps wq! - let @/= keepsrch - endif - call s:ChgDir(curdir) - -" call Dret("vimball#RmVimball") -endfun - -" --------------------------------------------------------------------- -" vimball#Decompress: attempts to automatically decompress vimballs {{{2 -fun! vimball#Decompress(fname,...) -" call Dfunc("Decompress(fname<".a:fname.">) a:0=".a:0) - - " decompression: - if expand("%") =~ '.*\.gz' && executable("gunzip") - " handle *.gz with gunzip - silent exe "!gunzip ".shellescape(a:fname) - if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">") - endif - let fname= substitute(a:fname,'\.gz$','','') - exe "e ".escape(fname,' \') - if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif - - elseif expand("%") =~ '.*\.gz' && executable("gzip") - " handle *.gz with gzip -d - silent exe "!gzip -d ".shellescape(a:fname) - if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">") - endif - let fname= substitute(a:fname,'\.gz$','','') - exe "e ".escape(fname,' \') - if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif - - elseif expand("%") =~ '.*\.bz2' && executable("bunzip2") - " handle *.bz2 with bunzip2 - silent exe "!bunzip2 ".shellescape(a:fname) - if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">") - endif - let fname= substitute(a:fname,'\.bz2$','','') - exe "e ".escape(fname,' \') - if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif - - elseif expand("%") =~ '.*\.bz2' && executable("bzip2") - " handle *.bz2 with bzip2 -d - silent exe "!bzip2 -d ".shellescape(a:fname) - if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">") - endif - let fname= substitute(a:fname,'\.bz2$','','') - exe "e ".escape(fname,' \') - if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif - - elseif expand("%") =~ '.*\.zip' && executable("unzip") - " handle *.zip with unzip - silent exe "!unzip ".shellescape(a:fname) - if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">") - endif - let fname= substitute(a:fname,'\.zip$','','') - exe "e ".escape(fname,' \') - if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif - endif - - if a:0 == 0| setlocal noma bt=nofile fmr=[[[,]]] fdm=marker | endif - -" call Dret("Decompress") -endfun - -" --------------------------------------------------------------------- -" vimball#ShowMesg: {{{2 -fun! vimball#ShowMesg(level,msg) -" call Dfunc("vimball#ShowMesg(level=".a:level." msg<".a:msg.">)") - - let rulerkeep = &ruler - let showcmdkeep = &showcmd - set noruler noshowcmd - redraw! - - if &fo =~# '[ta]' - echomsg "***vimball*** ".a:msg - else - if a:level == s:WARNING || a:level == s:USAGE - echohl WarningMsg - elseif a:level == s:ERROR - echohl Error - endif - echomsg "***vimball*** ".a:msg - echohl None - endif - - if a:level != s:USAGE - call inputsave()|let ok= input("Press <cr> to continue")|call inputrestore() - endif - - let &ruler = rulerkeep - let &showcmd = showcmdkeep - -" call Dret("vimball#ShowMesg") -endfun -" ===================================================================== -" s:ChgDir: change directory (in spite of Windoze) {{{2 -fun! s:ChgDir(newdir) -" call Dfunc("ChgDir(newdir<".a:newdir.">)") - if (has("win32") || has("win95") || has("win64") || has("win16")) - try - exe 'silent cd '.fnameescape(substitute(a:newdir,'/','\\','g')) - catch /^Vim\%((\a\+)\)\=:E/ - call mkdir(fnameescape(substitute(a:newdir,'/','\\','g'))) - exe 'silent cd '.fnameescape(substitute(a:newdir,'/','\\','g')) - endtry - else - try - exe 'silent cd '.fnameescape(a:newdir) - catch /^Vim\%((\a\+)\)\=:E/ - call mkdir(fnameescape(a:newdir)) - exe 'silent cd '.fnameescape(a:newdir) - endtry - endif -" call Dret("ChgDir : curdir<".getcwd().">") -endfun - -" --------------------------------------------------------------------- -" s:RecordInVar: record a un-vimball command in the .VimballRecord file {{{2 -fun! s:RecordInVar(home,cmd) -" call Dfunc("RecordInVar(home<".a:home."> cmd<".a:cmd.">)") - if a:cmd =~ '^rmdir' -" if !exists("s:recorddir") -" let s:recorddir= substitute(a:cmd,'^rmdir',"call s:Rmdir",'') -" else -" let s:recorddir= s:recorddir."|".substitute(a:cmd,'^rmdir',"call s:Rmdir",'') -" endif - elseif !exists("s:recordfile") - let s:recordfile= a:cmd - else - let s:recordfile= s:recordfile."|".a:cmd - endif -" call Dret("RecordInVar : s:recordfile<".(exists("s:recordfile")? s:recordfile : "")."> s:recorddir<".(exists("s:recorddir")? s:recorddir : "").">") -endfun - -" --------------------------------------------------------------------- -" s:RecordInFile: {{{2 -fun! s:RecordInFile(home) -" call Dfunc("s:RecordInFile()") - if exists("g:vimball_norecord") -" call Dret("s:RecordInFile : g:vimball_norecord") - return - endif - - if exists("s:recordfile") || exists("s:recorddir") - let curdir= getcwd() - call s:ChgDir(a:home) - keepalt keepjumps 1split - - let cmd= expand("%:tr").": " -" call Decho("cmd<".cmd.">") - - sil! keepalt keepjumps e .VimballRecord - setlocal ma - $ - if exists("s:recordfile") && exists("s:recorddir") - let cmd= cmd.s:recordfile."|".s:recorddir - elseif exists("s:recorddir") - let cmd= cmd.s:recorddir - elseif exists("s:recordfile") - let cmd= cmd.s:recordfile - else -" call Dret("s:RecordInFile : neither recordfile nor recorddir exist") - return - endif -" call Decho("cmd<".cmd.">") - - " put command into buffer, write .VimballRecord `file - keepalt keepjumps put=cmd - sil! keepalt keepjumps g/^\s*$/d - sil! keepalt keepjumps wq! - call s:ChgDir(curdir) - - if exists("s:recorddir") -" call Decho("unlet s:recorddir<".s:recorddir.">") - unlet s:recorddir - endif - if exists("s:recordfile") -" call Decho("unlet s:recordfile<".s:recordfile.">") - unlet s:recordfile - endif - else -" call Decho("s:record[file|dir] doesn't exist") - endif - -" call Dret("s:RecordInFile") -endfun - -" --------------------------------------------------------------------- -" vimball#VimballHome: determine/get home directory path (usually from rtp) {{{2 -fun! vimball#VimballHome() -" call Dfunc("vimball#VimballHome()") - if exists("g:vimball_home") - let home= g:vimball_home - else - " go to vim plugin home - for home in split(&rtp,',') + [''] - if isdirectory(home) && filewritable(home) | break | endif - let basehome= substitute(home,'[/\\]\.vim$','','') - if isdirectory(basehome) && filewritable(basehome) - let home= basehome."/.vim" - break - endif - endfor - if home == "" - " just pick the first directory - let home= substitute(&rtp,',.*$','','') - endif - if (has("win32") || has("win95") || has("win64") || has("win16")) - let home= substitute(home,'/','\\','g') - endif - endif - " insure that the home directory exists -" call Decho("picked home<".home.">") - if !isdirectory(home) - if exists("g:vimball_mkdir") -" call Decho("home<".home."> isn't a directory -- making it now with g:vimball_mkdir<".g:vimball_mkdir.">") -" call Decho("system(".g:vimball_mkdir." ".shellescape(home).")") - call system(g:vimball_mkdir." ".shellescape(home)) - else -" call Decho("home<".home."> isn't a directory -- making it now with mkdir()") - call mkdir(home) - endif - endif -" call Dret("vimball#VimballHome <".home.">") - return home -endfun - -" --------------------------------------------------------------------- -" vimball#SaveSettings: {{{2 -fun! vimball#SaveSettings() -" call Dfunc("SaveSettings()") - let s:makeep = getpos("'a") - let s:regakeep= @a - if exists("+acd") - let s:acdkeep = &acd - endif - let s:eikeep = &ei - let s:fenkeep = &l:fen - let s:hidkeep = &hidden - let s:ickeep = &ic - let s:lzkeep = &lz - let s:pmkeep = &pm - let s:repkeep = &report - let s:vekeep = &ve - let s:ffkeep = &l:ff - let s:swfkeep = &l:swf - if exists("+acd") - setlocal ei=all ve=all noacd nofen noic report=999 nohid bt= ma lz pm= ff=unix noswf - else - setlocal ei=all ve=all nofen noic report=999 nohid bt= ma lz pm= ff=unix noswf - endif - " vimballs should be in unix format - setlocal ff=unix -" call Dret("SaveSettings") -endfun - -" --------------------------------------------------------------------- -" vimball#RestoreSettings: {{{2 -fun! vimball#RestoreSettings() -" call Dfunc("RestoreSettings()") - let @a = s:regakeep - if exists("+acd") - let &acd = s:acdkeep - endif - let &l:fen = s:fenkeep - let &hidden = s:hidkeep - let &ic = s:ickeep - let &lz = s:lzkeep - let &pm = s:pmkeep - let &report = s:repkeep - let &ve = s:vekeep - let &ei = s:eikeep - let &l:ff = s:ffkeep - if s:makeep[0] != 0 - " restore mark a -" call Decho("restore mark-a: makeep=".string(makeep)) - call setpos("'a",s:makeep) - endif - if exists("+acd") - unlet s:acdkeep - endif - unlet s:regakeep s:eikeep s:fenkeep s:hidkeep s:ickeep s:repkeep s:vekeep s:makeep s:lzkeep s:pmkeep s:ffkeep -" call Dret("RestoreSettings") -endfun - -let &cpo = s:keepcpo -unlet s:keepcpo - -" --------------------------------------------------------------------- -" Modelines: {{{1 -" vim: fdm=marker diff --git a/runtime/pack/dist/opt/vimball/doc/vimball.txt b/runtime/pack/dist/opt/vimball/doc/vimball.txt deleted file mode 100644 index 602fe85954..0000000000 --- a/runtime/pack/dist/opt/vimball/doc/vimball.txt +++ /dev/null @@ -1,273 +0,0 @@ -*vimball.txt* For Vim version 7.4. Last change: 2012 Jan 17 - - ---------------- - Vimball Archiver - ---------------- - -Author: Charles E. Campbell, Jr. <NdrOchip@ScampbellPfamily.AbizM> - (remove NOSPAM from Campbell's email first) -Copyright: (c) 2004-2012 by Charles E. Campbell, Jr. *Vimball-copyright* - The VIM LICENSE (see |copyright|) applies to the files in this - package, including vimballPlugin.vim, vimball.vim, and pi_vimball.txt. - except use "vimball" instead of "VIM". Like anything else that's free, - vimball.vim and its associated files are provided *as is* and comes with - no warranty of any kind, either expressed or implied. No guarantees - of merchantability. No guarantees of suitability for any purpose. By - using this plugin, you agree that in no event will the copyright - holder be liable for any damages resulting from the use of this - software. Use at your own risk! - -============================================================================== -1. Contents *vba* *vimball* *vimball-contents* - - 1. Contents......................................: |vimball-contents| - 2. Vimball Introduction..........................: |vimball-intro| - 3. Vimball Manual................................: |vimball-manual| - MkVimball.....................................: |:MkVimball| - UseVimball....................................: |:UseVimball| - RmVimball.....................................: |:RmVimball| - 4. Vimball History...............................: |vimball-history| - - -============================================================================== -2. Vimball Introduction *vimball-intro* - - Vimball is intended to make life simpler for users of plugins. All - a user needs to do with a vimball is: > - vim someplugin.vba - :so % - :q -< and the plugin and all its components will be installed into their - appropriate directories. Note that one doesn't need to be in any - particular directory when one does this. Plus, any help for the - plugin will also be automatically installed. - - If a user has decided to use the AsNeeded plugin, vimball is smart - enough to put scripts nominally intended for .vim/plugin/ into - .vim/AsNeeded/ instead. - - Removing a plugin that was installed with vimball is really easy: > - vim - :RmVimball someplugin -< This operation is not at all easy for zips and tarballs, for example. - - Vimball examines the user's |'runtimepath'| to determine where to put - the scripts. The first directory mentioned on the runtimepath is - usually used if possible. Use > - :echo &rtp -< to see that directory. - - -============================================================================== -3. Vimball Manual *vimball-manual* - -MAKING A VIMBALL *:MkVimball* - :[range]MkVimball[!] filename [path] - - The range is composed of lines holding paths to files to be included - in your new vimball, omitting the portion of the paths that is - normally specified by the runtimepath (|'rtp'|). As an example: > - plugin/something.vim - doc/something.txt -< using > - :[range]MkVimball filename -< - on this range of lines will create a file called "filename.vba" which - can be used by Vimball.vim to re-create these files. If the - "filename.vba" file already exists, then MkVimball will issue a - warning and not create the file. Note that these paths are relative - to your .vim (vimfiles) directory, and the files should be in that - directory. The vimball plugin normally uses the first |'runtimepath'| - directory that exists as a prefix; don't use absolute paths, unless - the user has specified such a path. - - If you use the exclamation point (!), then MkVimball will create the - "filename.vba" file, overwriting it if it already exists. This - behavior resembles that for |:w|. - - If you wish to force slashes into the filename, that can also be done - by using the exclamation mark (ie. :MkVimball! path/filename). - - The tip at https://vim.wikia.com/wiki/Using_VimBall_with_%27Make%27 - has a good idea on how to automate the production of vimballs using - make. - - -MAKING DIRECTORIES VIA VIMBALLS *g:vimball_mkdir* - - First, the |mkdir()| command is tried (not all systems support it). - - If it doesn't exist, then if g:vimball_mkdir doesn't exist, it is set - as follows: > - |g:netrw_local_mkdir|, if it exists - "mkdir" , if it is executable - "makedir" , if it is executable - Otherwise , it is undefined. -< One may explicitly specify the directory making command using - g:vimball_mkdir. This command is used to make directories that - are needed as indicated by the vimball. - - -CONTROLLING THE VIMBALL EXTRACTION DIRECTORY *g:vimball_home* - - You may override the use of the |'runtimepath'| by specifying a - variable, g:vimball_home. - - *vimball-extract* - vim filename.vba - - Simply editing a Vimball will cause Vimball.vim to tell the user to - source the file to extract its contents. - - Extraction will only proceed if the first line of a putative vimball - file holds the "Vimball Archiver by Charles E. Campbell, Jr., Ph.D." - line. - -LISTING FILES IN A VIMBALL *:VimballList* - - :VimballList - - This command will tell Vimball to list the files in the archive, along - with their lengths in lines. - -MANUALLY INVOKING VIMBALL EXTRACTION *:UseVimball* - - :UseVimball [path] - - This command is contained within the vimball itself; it invokes the - vimball#Vimball() routine which is responsible for unpacking the - vimball. One may choose to execute it by hand instead of sourcing - the vimball; one may also choose to specify a path for the - installation, thereby overriding the automatic choice of the first - existing directory on the |'runtimepath'|. - -REMOVING A VIMBALL *:RmVimball* - - :RmVimball vimballfile [path] - - This command removes all files generated by the specified vimball - (but not any directories it may have made). One may choose a path - for de-installation, too (see |'runtimepath'|); otherwise, the - default is the first existing directory on the |'runtimepath'|. - To implement this, a file (.VimballRecord) is made in that directory - containing a record of what files need to be removed for all vimballs - used thus far. - -PREVENTING LOADING - - If for some reason you don't want to be able to extract plugins - using vimballs: you may prevent the loading of vimball.vim by - putting the following two variables in your <.vimrc>: > - - let g:loaded_vimballPlugin= 1 - let g:loaded_vimball = 1 -< -WINDOWS *vimball-windows* - - Many vimball files are compressed with gzip. Windows, unfortunately, - does not come provided with a tool to decompress gzip'ped files. - Fortunately, there are a number of tools available for Windows users - to un-gzip files: -> - Item Tool/Suite Free Website - ---- ---------- ---- ------- - 7zip tool y https://www.7-zip.org/ - Winzip tool n https://www.winzip.com/downwz.htm - unxutils suite y https://unxutils.sourceforge.net/ - cygwin suite y https://www.cygwin.com/ - GnuWin32 suite y https://gnuwin32.sourceforge.net/ - MinGW suite y https://www.mingw.org/ -< - -============================================================================== -4. Vimball History *vimball-history* {{{1 - - 34 : Sep 22, 2011 * "UseVimball path" now supports a non-full path by - prepending the current directory to it. - 33 : Apr 02, 2011 * Gave priority to *.vmb over *.vba - * Changed silent! to sil! (shorter) - * Safed |'swf'| setting (during vimball extraction, - its now turned off) - 32 : May 19, 2010 * (Christian Brabandt) :so someplugin.vba and - :so someplugin.vba.gz (and the other supported - compression types) now works - * (Jan Steffens) added support for xz compression - * fenc extraction was erroneously picking up the - end of the line number when no file encoding - was present. Fixed. - * By request, beginning the switchover from the vba - extension to vmb. Currently both are supported; - MkVimball, however, now will create *.vmb files. - Feb 11, 2011 * motoyakurotsu reported an error with vimball's - handling of zero-length files - 30 : Dec 08, 2008 * fnameescape() inserted to protect error - messaging using corrupted filenames from - causing problems - * RmVimball supports filenames that would - otherwise be considered to have "magic" - characters (ie. Abc[1].vba) - Feb 18, 2009 * s:Escape(), g:vimball_shq, and g:netrw_shq - removed (shellescape() used directly) - Oct 05, 2009 * (Nikolai Weibull) suggested that MkVimball - be allowed to use slashes in the filename. - 26 : May 27, 2008 * g:vimball_mkdir usage installed. Makes the - $HOME/.vim (or $HOME\vimfiles) directory if - necessary. - May 30, 2008 * (tnx to Bill McCarthy) found and fixed a bug: - vimball wasn't updating plugins to AsNeeded/ - when it should - 25 : Mar 24, 2008 * changed vimball#Vimball() to recognize doc/*.??x - files as help files, too. - Apr 18, 2008 * RmVimball command is now protected by saving and - restoring settings -- in particular, acd was - causing problems as reported by Zhang Shuhan - 24 : Nov 15, 2007 * g:vimball_path_escape used by s:Path() to - prevent certain characters from causing trouble - (defunct: |fnameescape()| and |shellescape()| - now used instead) - 22 : Mar 21, 2007 * uses setlocal instead of set during BufEnter - 21 : Nov 27, 2006 * (tnx to Bill McCarthy) vimball had a header - handling problem and it now changes \s to /s - 20 : Nov 20, 2006 * substitute() calls have all had the 'e' flag - removed. - 18 : Aug 01, 2006 * vimballs now use folding to easily display their - contents. - * if a user has AsNeeded/somefile, then vimball - will extract plugin/somefile to the AsNeeded/ - directory - 17 : Jun 28, 2006 * changes all \s to /s internally for Windows - 16 : Jun 15, 2006 * A. Mechelynck's idea to allow users to specify - installation root paths implemented for - UseVimball, MkVimball, and RmVimball. - * RmVimball implemented - 15 : Jun 13, 2006 * bugfix - 14 : May 26, 2006 * bugfixes - 13 : May 01, 2006 * exists("&acd") used to determine if the acd - option exists - 12 : May 01, 2006 * bugfix - the |'acd'| option is not always defined - 11 : Apr 27, 2006 * VimballList would create missing subdirectories that - the vimball specified were needed. Fixed. - 10 : Apr 27, 2006 * moved all setting saving/restoration to a pair of - functions. Included some more settings in them - which frequently cause trouble. - 9 : Apr 26, 2006 * various changes to support Windows' predilection - for backslashes and spaces in file and directory - names. - 7 : Apr 25, 2006 * bypasses foldenable - * uses more exe and less norm! (:yank :put etc) - * does better at insuring a "Press ENTER" prompt - appears to keep its messages visible - 4 : Mar 31, 2006 * BufReadPost seems to fire twice; BufReadEnter - only fires once, so the "Source this file..." - message is now issued only once. - 3 : Mar 20, 2006 * removed query, now requires sourcing to be - extracted (:so %). Message to that effect - included. - * :VimballList now shows files that would be - extracted. - 2 : Mar 20, 2006 * query, :UseVimball included - 1 : Mar 20, 2006 * initial release - - -============================================================================== -vim:tw=78:ts=8:ft=help:fdm=marker diff --git a/runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim b/runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim deleted file mode 100644 index d7473a0296..0000000000 --- a/runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim +++ /dev/null @@ -1,43 +0,0 @@ -" vimballPlugin : construct a file containing both paths and files -" Author: Charles E. Campbell -" Copyright: (c) 2004-2014 by Charles E. Campbell -" The VIM LICENSE applies to Vimball.vim, and Vimball.txt -" (see |copyright|) except use "Vimball" instead of "Vim". -" No warranty, express or implied. -" *** *** Use At-Your-Own-Risk! *** *** -" -" (Rom 2:1 WEB) Therefore you are without excuse, O man, whoever you are who -" judge. For in that which you judge another, you condemn yourself. For -" you who judge practice the same things. -" GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim - -" --------------------------------------------------------------------- -" Load Once: {{{1 -if &cp || exists("g:loaded_vimballPlugin") - finish -endif -let g:loaded_vimballPlugin = "v37" -let s:keepcpo = &cpo -set cpo&vim - -" ------------------------------------------------------------------------------ -" Public Interface: {{{1 -com! -range -complete=file -nargs=+ -bang MkVimball call vimball#MkVimball(<line1>,<line2>,<bang>0,<f-args>) -com! -nargs=? -complete=dir UseVimball call vimball#Vimball(1,<f-args>) -com! -nargs=0 VimballList call vimball#Vimball(0) -com! -nargs=* -complete=dir RmVimball call vimball#SaveSettings()|call vimball#RmVimball(<f-args>)|call vimball#RestoreSettings() -augroup Vimball - au! - au BufEnter *.vba,*.vba.gz,*.vba.bz2,*.vba.zip,*.vba.xz setlocal bt=nofile fmr=[[[,]]] fdm=marker|if &ff != 'unix'|setlocal ma ff=unix noma|endif|if line('$') > 1|call vimball#ShowMesg(0,"Source this file to extract it! (:so %)")|endif - au SourceCmd *.vba.gz,*.vba.bz2,*.vba.zip,*.vba.xz let s:origfile=expand("%")|if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|endif|call vimball#Decompress(expand("<amatch>"))|so %|if s:origfile!=expand("<afile>")|close|endif - au SourceCmd *.vba if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|call vimball#Vimball(1)|close|else|call vimball#Vimball(1)|endif - au BufEnter *.vmb,*.vmb.gz,*.vmb.bz2,*.vmb.zip,*.vmb.xz setlocal bt=nofile fmr=[[[,]]] fdm=marker|if &ff != 'unix'|setlocal ma ff=unix noma|endif|if line('$') > 1|call vimball#ShowMesg(0,"Source this file to extract it! (:so %)")|endif - au SourceCmd *.vmb.gz,*.vmb.bz2,*.vmb.zip,*.vmb.xz let s:origfile=expand("%")|if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|endif|call vimball#Decompress(expand("<amatch>"))|so %|if s:origfile!=expand("<afile>")|close|endif - au SourceCmd *.vmb if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|call vimball#Vimball(1)|close|else|call vimball#Vimball(1)|endif -augroup END - -" ===================================================================== -" Restoration And Modelines: {{{1 -" vim: fdm=marker -let &cpo= s:keepcpo -unlet s:keepcpo diff --git a/runtime/plugin/health.vim b/runtime/plugin/health.vim deleted file mode 100644 index 66ae8fb239..0000000000 --- a/runtime/plugin/health.vim +++ /dev/null @@ -1 +0,0 @@ -autocmd CmdUndefined CheckHealth checkhealth diff --git a/runtime/plugin/tarPlugin.vim b/runtime/plugin/tarPlugin.vim index d55492a93e..384a3ed823 100644 --- a/runtime/plugin/tarPlugin.vim +++ b/runtime/plugin/tarPlugin.vim @@ -47,7 +47,6 @@ augroup tar au BufReadCmd *.tar.zst call tar#Browse(expand("<amatch>")) au BufReadCmd *.tzs call tar#Browse(expand("<amatch>")) augroup END -com! -nargs=? -complete=file Vimuntar call tar#Vimuntar(<q-args>) " --------------------------------------------------------------------- " Restoration And Modelines: {{{1 diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 367ce60765..e2ab70eca2 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -491,7 +491,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) return '' -- Discard common "noise" lines. end -- XXX: Avoid newlines (too much whitespace) after block elements in old (preformatted) layout. - local div = opt.old and root:child(0) and vim.tbl_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type()) + local div = opt.old and root:child(0) and vim.list_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type()) return string.format('%s%s', div and trim(text) or text, div and '' or '\n') elseif node_name == 'line_li' then local sib = root:prev_sibling() @@ -522,7 +522,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) s = fix_tab_after_conceal(s, node_text(root:next_sibling())) end return s - elseif vim.tbl_contains({'codespan', 'keycode'}, node_name) then + elseif vim.list_contains({'codespan', 'keycode'}, node_name) then if root:has_error() then return text end @@ -554,7 +554,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) if root:has_error() then return text end - local in_heading = vim.tbl_contains({'h1', 'h2', 'h3'}, parent) + local in_heading = vim.list_contains({'h1', 'h2', 'h3'}, parent) local cssclass = (not in_heading and get_indent(node_text()) > 8) and 'help-tag-right' or 'help-tag' local tagname = node_text(root:child(1), false) if vim.tbl_count(stats.first_tags) < 2 then @@ -601,7 +601,7 @@ local function get_helpfiles(include) for f, type in vim.fs.dir(dir) do if (vim.endswith(f, '.txt') and type == 'file' - and (not include or vim.tbl_contains(include, f))) then + and (not include or vim.list_contains(include, f))) then local fullpath = vim.fn.fnamemodify(('%s/%s'):format(dir, f), ':p') table.insert(rv, fullpath) end diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index 92b07c4f73..f9a4631b7e 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -51,7 +51,7 @@ local function validate_commit(commit_message) return nil end - local commit_split = vim.split(commit_message, ":") + local commit_split = vim.split(commit_message, ":", {plain = true}) -- Return nil if the type is vim-patch since most of the normal rules don't -- apply. if commit_split[1] == "vim-patch" then @@ -63,13 +63,22 @@ local function validate_commit(commit_message) return [[Commit message is too long, a maximum of 80 characters is allowed.]] end + local before_colon = commit_split[1] - if vim.tbl_count(commit_split) < 2 then + local after_idx = 2 + if before_colon:match('^[^%(]*%([^%)]*$') then + -- Need to find the end of commit scope when commit scope contains colons. + while after_idx <= vim.tbl_count(commit_split) do + after_idx = after_idx + 1 + if commit_split[after_idx - 1]:find(')') then + break + end + end + end + if after_idx > vim.tbl_count(commit_split) then return [[Commit message does not include colons.]] end - - local before_colon = commit_split[1] - local after_colon = commit_split[#commit_split] + local after_colon = commit_split[after_idx] -- Check if commit introduces a breaking change. if vim.endswith(before_colon, "!") then @@ -77,7 +86,7 @@ local function validate_commit(commit_message) end -- Check if type is correct - local type = vim.split(before_colon, "%(")[1] + local type = vim.split(before_colon, "(", {plain = true})[1] local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'vim-patch'} if not vim.tbl_contains(allowed_types, type) then return string.format( @@ -234,7 +243,16 @@ function M._test() ['ci: Capitalized first word'] = false, ['ci: UPPER_CASE First Word'] = true, ['unknown: using unknown type'] = false, + ['feat: foo:bar'] = true, + ['feat(something): foo:bar'] = true, ['feat(:grep): read from pipe'] = true, + ['feat(:grep/:make): read from pipe'] = true, + ['feat(:grep): foo:bar'] = true, + ['feat(:grep/:make): foo:bar'] = true, + ['feat(:grep)'] = false, + ['feat(:grep/:make)'] = false, + ['feat(:grep'] = false, + ['feat(:grep/:make'] = false, ['ci: you\'re saying this commit message just goes on and on and on and on and on and on for way too long?'] = false, } diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 20e337afcb..385005a442 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -212,7 +212,7 @@ preprocess_patch() { 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove testdir/test_*.vim files - local na_src_testdir='balloon.*\|channel.*\|crypt\.vim\|cscope\.vim\|gui.*\|hardcopy\.vim\|job_fails\.vim\|json\.vim\|mzscheme\.vim\|netbeans.*\|paste\.vim\|popupwin.*\|restricted\.vim\|shortpathname\.vim\|tcl\.vim\|terminal.*\|xxd\.vim' + local na_src_testdir='balloon.*\|behave\.vim\|channel.*\|crypt\.vim\|cscope\.vim\|gui.*\|hardcopy\.vim\|job_fails\.vim\|json\.vim\|mzscheme\.vim\|netbeans.*\|paste\.vim\|popupwin.*\|restricted\.vim\|shortpathname\.vim\|tcl\.vim\|terminal.*\|xxd\.vim' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<test_\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove version.c #7555 diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 2438a5cf1d..208aa165c9 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -172,7 +172,8 @@ Object nvim_eval(String expr, Error *err) int ok; TRY_WRAP(err, { - ok = eval0(expr.data, &rettv, NULL, true); + ok = eval0(expr.data, &rettv, NULL, &EVALARG_EVALUATE); + clear_evalarg(&EVALARG_EVALUATE, NULL); }); if (!ERROR_SET(err)) { @@ -290,10 +291,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) switch (dict.type) { case kObjectTypeString: try_start(); - if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) { + if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to evaluate dict expression"); } + clear_evalarg(&EVALARG_EVALUATE, NULL); if (try_end(err)) { return rv; } diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 23bd5d2f48..c267fee39a 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -108,8 +108,8 @@ /// The default value for floats are 50. In general, values below 100 are /// recommended, unless there is a good reason to overshadow builtin /// elements. -/// - style: Configure the appearance of the window. Currently only takes -/// one non-empty value: +/// - style: (optional) Configure the appearance of the window. Currently +/// only supports one value: /// - "minimal" Nvim will display the window with many UI options /// disabled. This is useful when displaying a temporary /// float where the text should not be edited. Disables @@ -222,9 +222,11 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) win_config_float(win, fconfig); win->w_pos_changed = true; } - if (fconfig.style == kWinStyleMinimal) { - win_set_minimal_style(win); - didset_window_options(win, true); + if (HAS_KEY(config->style)) { + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(win); + didset_window_options(win, true); + } } } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index f1ce919942..726344a42b 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1421,6 +1421,8 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) aco->save_curwin_handle = curwin->handle; aco->save_curbuf = curbuf; aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle; + aco->save_State = State; + if (win != NULL) { // There is a window for "buf" in the current tab page, make it the // curwin. This is preferred, it has the least side effects (esp. if @@ -1497,6 +1499,10 @@ void aucmd_restbuf(aco_save_T *aco) win_found: // May need to stop Insert mode if we were in a prompt buffer. leaving_window(curwin); + // Do not stop Insert mode when already in Insert mode before. + if (aco->save_State & MODE_INSERT) { + stop_insert_mode = false; + } // Remove the window. win_remove(curwin, NULL); pmap_del(handle_T)(&window_handles, curwin->handle); diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index 791b589167..6dbd18ba7c 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -32,6 +32,7 @@ typedef struct { bufref_T new_curbuf; ///< new curbuf char *globaldir; ///< saved value of globaldir bool save_VIsual_active; ///< saved VIsual_active + int save_State; ///< saved State } aco_save_T; typedef struct AutoCmd_S AutoCmd; diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 774e16f73d..f2776191b7 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -2104,10 +2104,6 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa xp->xp_context = EXPAND_CHECKHEALTH; xp->xp_pattern = (char *)arg; break; - case CMD_behave: - xp->xp_context = EXPAND_BEHAVE; - xp->xp_pattern = (char *)arg; - break; case CMD_messages: xp->xp_context = EXPAND_MESSAGES; @@ -2479,19 +2475,6 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int * } /// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":behave {mswin,xterm}" command. -static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx == 0) { - return "mswin"; - } - if (idx == 1) { - return "xterm"; - } - return NULL; -} - -/// Function given to ExpandGeneric() to obtain the possible arguments of the /// ":breakadd {expr, file, func, here}" command. /// ":breakdel {func, file, here}" command. static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) @@ -2585,7 +2568,6 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches int escaped; } tab[] = { { EXPAND_COMMANDS, get_command_name, false, true }, - { EXPAND_BEHAVE, get_behave_arg, true, true }, { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, { EXPAND_MESSAGES, get_messages_arg, true, true }, { EXPAND_HISTORY, get_history_arg, true, true }, diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 3c9a63f5a3..90723372a9 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -494,7 +494,7 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp) { // Disable error messages, a bad expression would make Vim unusable. emsg_off++; - typval_T *const tv = eval_expr(bp->dbg_name); + typval_T *const tv = eval_expr(bp->dbg_name, NULL); emsg_off--; return tv; } diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 7d6349e552..de3808e4f5 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -2203,7 +2203,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) curwin = wp; STRCPY(buf, "b:keymap_name"); // must be writable emsg_skip++; - s = p = eval_to_string(buf, NULL, false); + s = p = eval_to_string(buf, false); emsg_skip--; curbuf = old_curbuf; curwin = old_curwin; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index a05fb4e08e..2268a7cd3e 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -656,7 +656,15 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int virtnum, statuscol_T wp->w_statuscol_line_count = wp->w_nrwidth_line_count; set_vim_var_nr(VV_VIRTNUM, 0); build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp); - stcp->width += stcp->truncate; + if (stcp->truncate > 0) { + // Add truncated width to avoid unnecessary redraws + int addwidth = MIN(stcp->truncate, MAX_NUMBERWIDTH - wp->w_nrwidth); + stcp->truncate = 0; + stcp->width += addwidth; + wp->w_nrwidth += addwidth; + wp->w_nrwidth_width = wp->w_nrwidth; + wp->w_valid &= ~VALID_WCOL; + } } set_vim_var_nr(VV_VIRTNUM, virtnum); @@ -2668,7 +2676,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { col_attr = cuc_attr; } else if (draw_color_col && VCOL_HLC == *color_cols) { - col_attr = mc_attr; + col_attr = hl_combine_attr(wlv.line_attr_lowprio, mc_attr); } col_attr = hl_combine_attr(col_attr, wlv.line_attr); @@ -2967,16 +2975,17 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, wlv.need_showbreak = true; } if (statuscol.draw) { - if (wlv.row == startrow + wlv.filler_lines + 1 - || wlv.row == startrow + wlv.filler_lines) { - // Re-evaluate 'statuscolumn' for the first wrapped row and non filler line - statuscol.textp = NULL; - } else if (statuscol.textp) { + if (wlv.row == startrow + wlv.filler_lines) { + statuscol.textp = NULL; // re-evaluate for first non-filler line + } else if (vim_strchr(p_cpo, CPO_NUMCOL) && wlv.row > startrow + wlv.filler_lines) { + statuscol.draw = false; // don't draw status column if "n" is in 'cpo' + } else if (wlv.row == startrow + wlv.filler_lines + 1) { + statuscol.textp = NULL; // re-evaluate for first wrapped line + } else { // Draw the already built 'statuscolumn' on the next wrapped or filler line statuscol.textp = statuscol.text; statuscol.hlrecp = statuscol.hlrec; - } // Fall back to default columns if the 'n' flag isn't in 'cpo' - statuscol.draw = vim_strchr(p_cpo, CPO_NUMCOL) == NULL; + } } wlv.filler_todo--; virt_line_offset = -1; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ff33fdeccb..6a8b24ceed 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -692,21 +692,35 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch set_vim_var_string(VV_FNAME_OUT, NULL, -1); } +void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip) +{ + *evalarg = (evalarg_T){ .eval_flags = skip ? 0 : EVAL_EVALUATE }; + if (eap != NULL) { + if (getline_equal(eap->getline, eap->cookie, getsourceline)) { + evalarg->eval_getline = eap->getline; + evalarg->eval_cookie = eap->cookie; + } + } +} + /// Top level evaluation function, returning a boolean. /// Sets "error" to true if there was an error. /// /// @param skip only parse, don't execute /// /// @return true or false. -int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip) +int eval_to_bool(char *arg, bool *error, exarg_T *eap, int skip) { typval_T tv; bool retval = false; + evalarg_T evalarg; + + fill_evalarg_from_eap(&evalarg, eap, skip); if (skip) { emsg_skip++; } - if (eval0(arg, &tv, nextcmd, !skip) == FAIL) { + if (eval0(arg, &tv, eap, &evalarg) == FAIL) { *error = true; } else { *error = false; @@ -718,19 +732,23 @@ int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip) if (skip) { emsg_skip--; } + clear_evalarg(&evalarg, eap); return retval; } /// Call eval1() and give an error message if not done at a lower level. -static int eval1_emsg(char **arg, typval_T *rettv, bool evaluate) +static int eval1_emsg(char **arg, typval_T *rettv, exarg_T *eap) FUNC_ATTR_NONNULL_ARG(1, 2) { const char *const start = *arg; const int did_emsg_before = did_emsg; const int called_emsg_before = called_emsg; + evalarg_T evalarg; + + fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip); - const int ret = eval1(arg, rettv, evaluate); + const int ret = eval1(arg, rettv, &evalarg); if (ret == FAIL) { // Report the invalid expression unless the expression evaluation has // been cancelled due to an aborting error, an interrupt, or an @@ -742,6 +760,7 @@ static int eval1_emsg(char **arg, typval_T *rettv, bool evaluate) semsg(_(e_invexpr2), start); } } + clear_evalarg(&evalarg, eap); return ret; } @@ -786,7 +805,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *r return FAIL; } s = skipwhite(s); - if (eval1_emsg(&s, rettv, true) == FAIL) { + if (eval1_emsg(&s, rettv, NULL) == FAIL) { return FAIL; } if (*skipwhite(s) != NUL) { // check for trailing chars after expr @@ -817,22 +836,23 @@ bool eval_expr_to_bool(const typval_T *expr, bool *error) /// Top level evaluation function, returning a string /// /// @param[in] arg String to evaluate. -/// @param nextcmd Pointer to the start of the next Ex command. /// @param[in] skip If true, only do parsing to nextcmd without reporting /// errors or actually evaluating anything. /// /// @return [allocated] string result of evaluation or NULL in case of error or /// when skipping. -char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip) +char *eval_to_string_skip(char *arg, exarg_T *eap, const bool skip) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { typval_T tv; char *retval; + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, skip); if (skip) { emsg_skip++; } - if (eval0((char *)arg, &tv, (char **)nextcmd, !skip) == FAIL || skip) { + if (eval0(arg, &tv, eap, &evalarg) == FAIL || skip) { retval = NULL; } else { retval = xstrdup(tv_get_string(&tv)); @@ -841,6 +861,7 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip if (skip) { emsg_skip--; } + clear_evalarg(&evalarg, eap); return retval; } @@ -848,12 +869,24 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip /// Skip over an expression at "*pp". /// /// @return FAIL for an error, OK otherwise. -int skip_expr(char **pp) +int skip_expr(char **pp, evalarg_T *const evalarg) { - typval_T rettv; + const int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags; + + // Don't evaluate the expression. + if (evalarg != NULL) { + evalarg->eval_flags &= ~EVAL_EVALUATE; + } *pp = skipwhite(*pp); - return eval1(pp, &rettv, false); + typval_T rettv; + int res = eval1(pp, &rettv, NULL); + + if (evalarg != NULL) { + evalarg->eval_flags = save_flags; + } + + return res; } /// Top level evaluation function, returning a string. @@ -862,13 +895,13 @@ int skip_expr(char **pp) /// a Float to a String. /// /// @return pointer to allocated memory, or NULL for failure. -char *eval_to_string(char *arg, char **nextcmd, bool convert) +char *eval_to_string(char *arg, bool convert) { typval_T tv; char *retval; garray_T ga; - if (eval0(arg, &tv, nextcmd, true) == FAIL) { + if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { retval = NULL; } else { if (convert && tv.v_type == VAR_LIST) { @@ -890,6 +923,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert) } tv_clear(&tv); } + clear_evalarg(&EVALARG_EVALUATE, NULL); return retval; } @@ -898,7 +932,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert) /// textlock. /// /// @param use_sandbox when true, use the sandbox. -char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox) +char *eval_to_string_safe(char *arg, int use_sandbox) { char *retval; funccal_entry_T funccal_entry; @@ -908,7 +942,7 @@ char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox) sandbox++; } textlock++; - retval = eval_to_string(arg, nextcmd, false); + retval = eval_to_string(arg, false); if (use_sandbox) { sandbox--; } @@ -929,7 +963,7 @@ varnumber_T eval_to_number(char *expr) emsg_off++; - if (eval1(&p, &rettv, true) == FAIL) { + if (eval1(&p, &rettv, &EVALARG_EVALUATE) == FAIL) { retval = -1; } else { retval = tv_get_number_chk(&rettv, NULL); @@ -944,12 +978,18 @@ varnumber_T eval_to_number(char *expr) /// /// @return an allocated typval_T with the result or /// NULL when there is an error. -typval_T *eval_expr(char *arg) +typval_T *eval_expr(char *arg, exarg_T *eap) { typval_T *tv = xmalloc(sizeof(*tv)); - if (eval0(arg, tv, NULL, true) == FAIL) { + evalarg_T evalarg; + + fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip); + + if (eval0(arg, tv, eap, &evalarg) == FAIL) { XFREE_CLEAR(tv); } + + clear_evalarg(&evalarg, eap); return tv; } @@ -1024,7 +1064,7 @@ list_T *eval_spell_expr(char *badword, char *expr) emsg_off++; } - if (eval1(&p, &rettv, true) == OK) { + if (eval1(&p, &rettv, &EVALARG_EVALUATE) == OK) { if (rettv.v_type != VAR_LIST) { tv_clear(&rettv); } else { @@ -1171,7 +1211,7 @@ int eval_foldexpr(char *arg, int *cp) } textlock++; *cp = NUL; - if (eval0(arg, &tv, NULL, true) == FAIL) { + if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { retval = 0; } else { // If the result is a number, just return the number. @@ -1195,6 +1235,7 @@ int eval_foldexpr(char *arg, int *cp) sandbox--; } textlock--; + clear_evalarg(&EVALARG_EVALUATE, NULL); return (int)retval; } @@ -1346,7 +1387,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const empty1 = true; } else { empty1 = false; - if (eval1(&p, &var1, true) == FAIL) { // Recursive! + if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) { // Recursive! return NULL; } if (!tv_check_str(&var1)) { @@ -1380,7 +1421,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const lp->ll_empty2 = true; } else { lp->ll_empty2 = false; - if (eval1(&p, &var2, true) == FAIL) { // Recursive! + // Recursive! + if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) { tv_clear(&var1); return NULL; } @@ -1632,8 +1674,8 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // handle +=, -=, *=, /=, %= and .= di = NULL; - if (get_var_tv(lp->ll_name, (int)strlen(lp->ll_name), - &tv, &di, true, false) == OK) { + if (eval_variable(lp->ll_name, (int)strlen(lp->ll_name), + &tv, &di, true, false) == OK) { if ((di == NULL || (!var_check_ro(di->di_flags, lp->ll_name, TV_CSTRING) && !tv_check_lock(&di->di_tv, lp->ll_name, TV_CSTRING))) @@ -1776,12 +1818,13 @@ notify: /// @param[out] *errp set to true for an error, false otherwise; /// /// @return a pointer that holds the info. Null when there is an error. -void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) +void *eval_for_line(const char *arg, bool *errp, exarg_T *eap, evalarg_T *const evalarg) { forinfo_T *fi = xcalloc(1, sizeof(forinfo_T)); const char *expr; typval_T tv; list_T *l; + const bool skip = !(evalarg->eval_flags & EVAL_EVALUATE); *errp = true; // Default: there is an error. @@ -1791,7 +1834,8 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } expr = skipwhite(expr); - if (expr[0] != 'i' || expr[1] != 'n' || !ascii_iswhite(expr[2])) { + if (expr[0] != 'i' || expr[1] != 'n' + || !(expr[2] == NUL || ascii_iswhite(expr[2]))) { emsg(_("E690: Missing \"in\" after :for")); return fi; } @@ -1799,7 +1843,8 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) if (skip) { emsg_skip++; } - if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) { + expr = skipwhite(expr + 2); + if (eval0((char *)expr, &tv, eap, evalarg) == OK) { *errp = false; if (!skip) { if (tv.v_type == VAR_LIST) { @@ -2163,10 +2208,12 @@ int pattern_match(const char *pat, const char *text, bool ic) /// @param basetv "expr" for "expr->name(arg)" /// /// @return OK or FAIL. -static int eval_func(char **const arg, char *const name, const int name_len, typval_T *const rettv, - const bool evaluate, typval_T *const basetv) - FUNC_ATTR_NONNULL_ARG(1, 2, 4) +static int eval_func(char **const arg, evalarg_T *const evalarg, char *const name, + const int name_len, typval_T *const rettv, const int flags, + typval_T *const basetv) + FUNC_ATTR_NONNULL_ARG(1, 3, 5) { + const bool evaluate = flags & EVAL_EVALUATE; char *s = name; int len = name_len; @@ -2190,7 +2237,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ funcexe.fe_evaluate = evaluate; funcexe.fe_partial = partial; funcexe.fe_basetv = basetv; - int ret = get_func_tv(s, len, rettv, arg, &funcexe); + int ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); xfree(s); @@ -2214,6 +2261,26 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ return ret; } +/// After using "evalarg" filled from "eap": free the memory. +void clear_evalarg(evalarg_T *evalarg, exarg_T *eap) +{ + if (evalarg != NULL) { + if (evalarg->eval_tofree != NULL) { + if (eap != NULL) { + // We may need to keep the original command line, e.g. for + // ":let" it has the variable names. But we may also need the + // new one, "nextcmd" points into it. Keep both. + xfree(eap->cmdline_tofree); + eap->cmdline_tofree = *eap->cmdlinep; + *eap->cmdlinep = evalarg->eval_tofree; + } else { + xfree(evalarg->eval_tofree); + } + evalarg->eval_tofree = NULL; + } + } +} + /// The "evaluate" argument: When false, the argument is only parsed but not /// executed. The function may return OK, but the rettv will be of type /// VAR_UNKNOWN. The function still returns FAIL for a syntax error. @@ -2223,8 +2290,10 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ /// Put the result in "rettv" when returning OK and "evaluate" is true. /// Note: "rettv.v_lock" is not set. /// +/// @param evalarg can be NULL, &EVALARG_EVALUATE or a pointer. +/// /// @return OK or FAIL. -int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) +int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg) { int ret; char *p; @@ -2233,7 +2302,7 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) bool end_error = false; p = skipwhite(arg); - ret = eval1(&p, rettv, evaluate); + ret = eval1(&p, rettv, evalarg); if (ret != FAIL) { end_error = !ends_excmd(*p); @@ -2246,7 +2315,8 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) // been cancelled due to an aborting error, an interrupt, or an // exception, or we already gave a more specific error. // Also check called_emsg for when using assert_fails(). - if (!aborting() && did_emsg == did_emsg_before + if (!aborting() + && did_emsg == did_emsg_before && called_emsg == called_emsg_before) { if (end_error) { semsg(_(e_trailing_arg), p); @@ -2256,8 +2326,9 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) } ret = FAIL; } - if (nextcmd != NULL) { - *nextcmd = check_nextcmd(p); + + if (eap != NULL) { + eap->nextcmd = check_nextcmd(p); } return ret; @@ -2265,6 +2336,7 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) /// Handle top level expression: /// expr2 ? expr1 : expr1 +/// expr2 ?? expr1 /// /// "arg" must point to the first non-white of the expression. /// "arg" is advanced to the next non-white after the recognized expression. @@ -2272,54 +2344,89 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) /// Note: "rettv.v_lock" is not set. /// /// @return OK or FAIL. -int eval1(char **arg, typval_T *rettv, int evaluate) +int eval1(char **arg, typval_T *rettv, evalarg_T *const evalarg) { - typval_T var2; - // Get the first variable. - if (eval2(arg, rettv, evaluate) == FAIL) { + if (eval2(arg, rettv, evalarg) == FAIL) { return FAIL; } - if ((*arg)[0] == '?') { + char *p = *arg; + if (*p == '?') { + const bool op_falsy = p[1] == '?'; + evalarg_T *evalarg_used = evalarg; + evalarg_T local_evalarg; + if (evalarg == NULL) { + local_evalarg = (evalarg_T){ .eval_flags = 0 }; + evalarg_used = &local_evalarg; + } + const int orig_flags = evalarg_used->eval_flags; + const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE; + bool result = false; if (evaluate) { bool error = false; - if (tv_get_number_chk(rettv, &error) != 0) { + if (op_falsy) { + result = tv2bool(rettv); + } else if (tv_get_number_chk(rettv, &error) != 0) { result = true; } - tv_clear(rettv); + if (error || !op_falsy || !result) { + tv_clear(rettv); + } if (error) { return FAIL; } } - // Get the second variable. + // Get the second variable. Recursive! + if (op_falsy) { + (*arg)++; + } *arg = skipwhite(*arg + 1); - if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive! + evalarg_used->eval_flags = (op_falsy ? !result : result) + ? orig_flags : (orig_flags & ~EVAL_EVALUATE); + typval_T var2; + if (eval1(arg, &var2, evalarg_used) == FAIL) { + evalarg_used->eval_flags = orig_flags; return FAIL; } + if (!op_falsy || !result) { + *rettv = var2; + } - // Check for the ":". - if ((*arg)[0] != ':') { - emsg(_("E109: Missing ':' after '?'")); - if (evaluate && result) { - tv_clear(rettv); + if (!op_falsy) { + // Check for the ":". + p = *arg; + if (*p != ':') { + emsg(_("E109: Missing ':' after '?'")); + if (evaluate && result) { + tv_clear(rettv); + } + evalarg_used->eval_flags = orig_flags; + return FAIL; } - return FAIL; - } - // Get the third variable. - *arg = skipwhite(*arg + 1); - if (eval1(arg, &var2, evaluate && !result) == FAIL) { // Recursive! - if (evaluate && result) { - tv_clear(rettv); + // Get the third variable. Recursive! + *arg = skipwhite(*arg + 1); + evalarg_used->eval_flags = !result ? orig_flags : (orig_flags & ~EVAL_EVALUATE); + if (eval1(arg, &var2, evalarg_used) == FAIL) { + if (evaluate && result) { + tv_clear(rettv); + } + evalarg_used->eval_flags = orig_flags; + return FAIL; + } + if (evaluate && !result) { + *rettv = var2; } - return FAIL; } - if (evaluate && !result) { - *rettv = var2; + + if (evalarg == NULL) { + clear_evalarg(&local_evalarg, NULL); + } else { + evalarg->eval_flags = orig_flags; } } @@ -2333,21 +2440,29 @@ int eval1(char **arg, typval_T *rettv, int evaluate) /// "arg" is advanced to the next non-white after the recognized expression. /// /// @return OK or FAIL. -static int eval2(char **arg, typval_T *rettv, int evaluate) +static int eval2(char **arg, typval_T *rettv, evalarg_T *const evalarg) { - typval_T var2; - bool error = false; - // Get the first variable. - if (eval3(arg, rettv, evaluate) == FAIL) { + if (eval3(arg, rettv, evalarg) == FAIL) { return FAIL; } - // Repeat until there is no following "||". - bool first = true; - bool result = false; - while ((*arg)[0] == '|' && (*arg)[1] == '|') { - if (evaluate && first) { + // Handle the "||" operator. + char *p = *arg; + if (p[0] == '|' && p[1] == '|') { + evalarg_T *evalarg_used = evalarg; + evalarg_T local_evalarg; + if (evalarg == NULL) { + local_evalarg = (evalarg_T){ .eval_flags = 0 }; + evalarg_used = &local_evalarg; + } + const int orig_flags = evalarg_used->eval_flags; + const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE; + + bool result = false; + + if (evaluate) { + bool error = false; if (tv_get_number_chk(rettv, &error) != 0) { result = true; } @@ -2355,28 +2470,41 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) if (error) { return FAIL; } - first = false; } - // Get the second variable. - *arg = skipwhite(*arg + 2); - if (eval3(arg, &var2, evaluate && !result) == FAIL) { - return FAIL; - } + // Repeat until there is no following "||". + while (p[0] == '|' && p[1] == '|') { + // Get the second variable. + *arg = skipwhite(*arg + 2); + evalarg_used->eval_flags = !result ? orig_flags : (orig_flags & ~EVAL_EVALUATE); + typval_T var2; + if (eval3(arg, &var2, evalarg_used) == FAIL) { + return FAIL; + } - // Compute the result. - if (evaluate && !result) { - if (tv_get_number_chk(&var2, &error) != 0) { - result = true; + // Compute the result. + if (evaluate && !result) { + bool error = false; + if (tv_get_number_chk(&var2, &error) != 0) { + result = true; + } + tv_clear(&var2); + if (error) { + return FAIL; + } } - tv_clear(&var2); - if (error) { - return FAIL; + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = result; } + + p = *arg; } - if (evaluate) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = result; + + if (evalarg == NULL) { + clear_evalarg(&local_evalarg, NULL); + } else { + evalarg->eval_flags = orig_flags; } } @@ -2390,21 +2518,29 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) /// `arg` is advanced to the next non-white after the recognized expression. /// /// @return OK or FAIL. -static int eval3(char **arg, typval_T *rettv, int evaluate) +static int eval3(char **arg, typval_T *rettv, evalarg_T *const evalarg) { - typval_T var2; - bool error = false; - // Get the first variable. - if (eval4(arg, rettv, evaluate) == FAIL) { + if (eval4(arg, rettv, evalarg) == FAIL) { return FAIL; } - // Repeat until there is no following "&&". - bool first = true; - bool result = true; - while ((*arg)[0] == '&' && (*arg)[1] == '&') { - if (evaluate && first) { + char *p = *arg; + // Handle the "&&" operator. + if (p[0] == '&' && p[1] == '&') { + evalarg_T *evalarg_used = evalarg; + evalarg_T local_evalarg; + if (evalarg == NULL) { + local_evalarg = (evalarg_T){ .eval_flags = 0 }; + evalarg_used = &local_evalarg; + } + const int orig_flags = evalarg_used->eval_flags; + const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE; + + bool result = true; + + if (evaluate) { + bool error = false; if (tv_get_number_chk(rettv, &error) == 0) { result = false; } @@ -2412,28 +2548,41 @@ static int eval3(char **arg, typval_T *rettv, int evaluate) if (error) { return FAIL; } - first = false; } - // Get the second variable. - *arg = skipwhite(*arg + 2); - if (eval4(arg, &var2, evaluate && result) == FAIL) { - return FAIL; - } + // Repeat until there is no following "&&". + while (p[0] == '&' && p[1] == '&') { + // Get the second variable. + *arg = skipwhite(*arg + 2); + evalarg_used->eval_flags = result ? orig_flags : (orig_flags & ~EVAL_EVALUATE); + typval_T var2; + if (eval4(arg, &var2, evalarg_used) == FAIL) { + return FAIL; + } - // Compute the result. - if (evaluate && result) { - if (tv_get_number_chk(&var2, &error) == 0) { - result = false; + // Compute the result. + if (evaluate && result) { + bool error = false; + if (tv_get_number_chk(&var2, &error) == 0) { + result = false; + } + tv_clear(&var2); + if (error) { + return FAIL; + } } - tv_clear(&var2); - if (error) { - return FAIL; + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = result; } + + p = *arg; } - if (evaluate) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = result; + + if (evalarg == NULL) { + clear_evalarg(&local_evalarg, NULL); + } else { + evalarg->eval_flags = orig_flags; } } @@ -2456,19 +2605,18 @@ static int eval3(char **arg, typval_T *rettv, int evaluate) /// "arg" is advanced to the next non-white after the recognized expression. /// /// @return OK or FAIL. -static int eval4(char **arg, typval_T *rettv, int evaluate) +static int eval4(char **arg, typval_T *rettv, evalarg_T *const evalarg) { typval_T var2; - char *p; exprtype_T type = EXPR_UNKNOWN; int len = 2; // Get the first variable. - if (eval5(arg, rettv, evaluate) == FAIL) { + if (eval5(arg, rettv, evalarg) == FAIL) { return FAIL; } - p = *arg; + char *p = *arg; switch (p[0]) { case '=': if (p[1] == '=') { @@ -2528,11 +2676,11 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) // Get the second variable. *arg = skipwhite(p + len); - if (eval5(arg, &var2, evaluate) == FAIL) { + if (eval5(arg, &var2, evalarg) == FAIL) { tv_clear(rettv); return FAIL; } - if (evaluate) { + if (evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE)) { const int ret = typval_compare(rettv, &var2, type, ic); tv_clear(&var2); @@ -2586,25 +2734,22 @@ static int eval_addlist(typval_T *tv1, typval_T *tv2) /// `arg` is advanced to the next non-white after the recognized expression. /// /// @return OK or FAIL. -static int eval5(char **arg, typval_T *rettv, int evaluate) +static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg) { - typval_T var2; - varnumber_T n1, n2; - float_T f1 = 0, f2 = 0; - char *p; - // Get the first variable. - if (eval6(arg, rettv, evaluate, false) == FAIL) { + if (eval6(arg, rettv, evalarg, false) == FAIL) { return FAIL; } // Repeat computing, until no '+', '-' or '.' is following. for (;;) { int op = (uint8_t)(**arg); - if (op != '+' && op != '-' && op != '.') { + bool concat = op == '.'; + if (op != '+' && op != '-' && !concat) { break; } + const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE); if ((op != '+' || (rettv->v_type != VAR_LIST && rettv->v_type != VAR_BLOB)) && (op == '.' || rettv->v_type != VAR_FLOAT) && evaluate) { // For "list + ...", an illegal use of the first operand as @@ -2625,7 +2770,8 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) (*arg)++; } *arg = skipwhite(*arg + 1); - if (eval6(arg, &var2, evaluate, op == '.') == FAIL) { + typval_T var2; + if (eval6(arg, &var2, evalarg, op == '.') == FAIL) { tv_clear(rettv); return FAIL; } @@ -2643,7 +2789,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) tv_clear(&var2); return FAIL; } - p = concat_str(s1, s2); + char *p = concat_str(s1, s2); tv_clear(rettv); rettv->v_type = VAR_STRING; rettv->vval.v_string = p; @@ -2655,6 +2801,8 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) } } else { bool error = false; + varnumber_T n1, n2; + float_T f1 = 0, f2 = 0; if (rettv->v_type == VAR_FLOAT) { f1 = rettv->vval.v_float; @@ -2724,32 +2872,30 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) /// expression. Is advanced to the next non-whitespace /// character after the recognized expression. /// @param[out] rettv Location where result is saved. -/// @param[in] evaluate If not true, rettv is not populated. /// @param[in] want_string True if "." is string_concatenation, otherwise /// float /// @return OK or FAIL. -static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) +static int eval6(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string) FUNC_ATTR_NO_SANITIZE_UNDEFINED { - typval_T var2; - int op; - varnumber_T n1, n2; bool use_float = false; - float_T f1 = 0, f2 = 0; - bool error = false; // Get the first variable. - if (eval7(arg, rettv, evaluate, want_string) == FAIL) { + if (eval7(arg, rettv, evalarg, want_string) == FAIL) { return FAIL; } // Repeat computing, until no '*', '/' or '%' is following. for (;;) { - op = (uint8_t)(**arg); + int op = (uint8_t)(**arg); if (op != '*' && op != '/' && op != '%') { break; } + varnumber_T n1, n2; + float_T f1 = 0, f2 = 0; + bool error = false; + const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE); if (evaluate) { if (rettv->v_type == VAR_FLOAT) { f1 = rettv->vval.v_float; @@ -2768,7 +2914,8 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) // Get the second variable. *arg = skipwhite(*arg + 1); - if (eval7(arg, &var2, evaluate, false) == FAIL) { + typval_T var2; + if (eval7(arg, &var2, evalarg, false) == FAIL) { return FAIL; } @@ -2859,8 +3006,9 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) /// @param want_string after "." operator /// /// @return OK or FAIL. -static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) +static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); int ret = OK; static int recurse = 0; @@ -2901,7 +3049,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '7': case '8': case '9': - ret = get_number_tv(arg, rettv, evaluate, want_string); + ret = eval_number(arg, rettv, evaluate, want_string); // Apply prefixed "-" and "+" now. Matters especially when // "->" follows. @@ -2912,24 +3060,24 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // String constant: "string". case '"': - ret = get_string_tv(arg, rettv, evaluate); + ret = eval_string(arg, rettv, evaluate); break; // Literal string constant: 'str''ing'. case '\'': - ret = get_lit_string_tv(arg, rettv, evaluate); + ret = eval_lit_string(arg, rettv, evaluate); break; // List: [expr, expr] case '[': - ret = get_list_tv(arg, rettv, evaluate); + ret = eval_list(arg, rettv, evalarg); break; // Dictionary: #{key: val, key: val} case '#': if ((*arg)[1] == '{') { (*arg)++; - ret = eval_dict(arg, rettv, evaluate, true); + ret = eval_dict(arg, rettv, evalarg, true); } else { ret = NOTDONE; } @@ -2938,19 +3086,19 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Lambda: {arg, arg -> expr} // Dictionary: {'key': val, 'key': val} case '{': - ret = get_lambda_tv(arg, rettv, evaluate); + ret = get_lambda_tv(arg, rettv, evalarg); if (ret == NOTDONE) { - ret = eval_dict(arg, rettv, evaluate, false); + ret = eval_dict(arg, rettv, evalarg, false); } break; // Option value: &name case '&': - ret = get_option_tv((const char **)arg, rettv, evaluate); + ret = eval_option((const char **)arg, rettv, evaluate); break; // Environment variable: $VAR. case '$': - ret = get_env_tv(arg, rettv, evaluate); + ret = eval_env_var(arg, rettv, evaluate); break; // Register contents: @r. @@ -2968,7 +3116,8 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // nested expression: (expression). case '(': *arg = skipwhite(*arg + 1); - ret = eval1(arg, rettv, evaluate); // recursive! + + ret = eval1(arg, rettv, evalarg); // recursive! if (**arg == ')') { (*arg)++; } else if (ret == OK) { @@ -2996,11 +3145,15 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) if (len <= 0) { ret = FAIL; } else { - if (**arg == '(') { // recursive! - ret = eval_func(arg, s, len, rettv, evaluate, NULL); + const int flags = evalarg == NULL ? 0 : evalarg->eval_flags; + if (**arg == '(') { + // "name(..." recursive! + ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL); } else if (evaluate) { - ret = get_var_tv(s, len, rettv, NULL, true, false); + // get value of variable + ret = eval_variable(s, len, rettv, NULL, true, false); } else { + // skip the name check_vars(s, (size_t)len); ret = OK; } @@ -3013,7 +3166,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Handle following '[', '(' and '.' for expr[expr], expr.name, // expr(expr), expr->name(expr) if (ret == OK) { - ret = handle_subscript((const char **)arg, rettv, evaluate, true); + ret = handle_subscript((const char **)arg, rettv, evalarg, true); } // Apply logical NOT and unary '-', from right to left, ignore '+'. @@ -3089,10 +3242,10 @@ static int eval7_leader(typval_T *const rettv, const bool numeric_only, /// to the name of the Lua function to call (after the /// "v:lua." prefix). /// @return OK on success, FAIL on failure. -static int call_func_rettv(char **const arg, typval_T *const rettv, const bool evaluate, - dict_T *const selfdict, typval_T *const basetv, +static int call_func_rettv(char **const arg, evalarg_T *const evalarg, typval_T *const rettv, + const bool evaluate, dict_T *const selfdict, typval_T *const basetv, const char *const lua_funcname) - FUNC_ATTR_NONNULL_ARG(1, 2) + FUNC_ATTR_NONNULL_ARG(1, 3) { partial_T *pt = NULL; typval_T functv; @@ -3124,7 +3277,7 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e funcexe.fe_selfdict = selfdict; funcexe.fe_basetv = basetv; const int ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, - arg, &funcexe); + arg, evalarg, &funcexe); // Clear the funcref afterwards, so that deleting it while // evaluating the arguments is possible (see test55). @@ -3143,16 +3296,17 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e /// @return FAIL or OK. /// /// @note "*arg" is advanced to after the ')'. -static int eval_lambda(char **const arg, typval_T *const rettv, const bool evaluate, +static int eval_lambda(char **const arg, typval_T *const rettv, evalarg_T *const evalarg, const bool verbose) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1, 2) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); // Skip over the ->. *arg += 2; typval_T base = *rettv; rettv->v_type = VAR_UNKNOWN; - int ret = get_lambda_tv(arg, rettv, evaluate); + int ret = get_lambda_tv(arg, rettv, evalarg); if (ret != OK) { return FAIL; } else if (**arg != '(') { @@ -3166,7 +3320,7 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu tv_clear(rettv); ret = FAIL; } else { - ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, NULL); + ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, NULL); } // Clear the funcref afterwards, so that deleting it while @@ -3183,10 +3337,12 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu /// @param *arg points to the '-'. /// /// @return FAIL or OK. "*arg" is advanced to after the ')'. -static int eval_method(char **const arg, typval_T *const rettv, const bool evaluate, +static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const evalarg, const bool verbose) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1, 2) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); + // Skip over the ->. *arg += 2; typval_T base = *rettv; @@ -3236,9 +3392,9 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu rettv->vval.v_partial = vvlua_partial; rettv->vval.v_partial->pt_refcount++; } - ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, lua_funcname); + ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname); } else { - ret = eval_func(arg, name, len, rettv, evaluate, &base); + ret = eval_func(arg, evalarg, name, len, rettv, evaluate ? EVAL_EVALUATE : 0, &base); } } @@ -3257,8 +3413,9 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu /// @param verbose give error messages /// /// @returns FAIL or OK. "*arg" is advanced to after the ']'. -static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) +static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool verbose) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); bool empty1 = false; bool empty2 = false; ptrdiff_t len = -1; @@ -3313,7 +3470,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) *arg = skipwhite(*arg + 1); if (**arg == ':') { empty1 = true; - } else if (eval1(arg, &var1, evaluate) == FAIL) { // Recursive! + } else if (eval1(arg, &var1, evalarg) == FAIL) { // Recursive! return FAIL; } else if (evaluate && !tv_check_str(&var1)) { // Not a number or string. @@ -3327,7 +3484,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) *arg = skipwhite(*arg + 1); if (**arg == ']') { empty2 = true; - } else if (eval1(arg, &var2, evaluate) == FAIL) { // Recursive! + } else if (eval1(arg, &var2, evalarg) == FAIL) { // Recursive! if (!empty1) { tv_clear(&var1); } @@ -3557,7 +3714,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) /// @param[in] evaluate If not true, rettv is not populated. /// /// @return OK or FAIL. -int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate) +int eval_option(const char **const arg, typval_T *const rettv, const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { const bool working = (**arg == '+'); // has("+option") @@ -3620,7 +3777,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval /// Allocate a variable for a number constant. Also deals with "0z" for blob. /// /// @return OK or FAIL. -static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_string) +static int eval_number(char **arg, typval_T *rettv, bool evaluate, bool want_string) { char *p = skipdigits(*arg + 1); bool get_float = false; @@ -3705,7 +3862,7 @@ static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_s /// Allocate a variable for a string constant. /// /// @return OK or FAIL. -static int get_string_tv(char **arg, typval_T *rettv, int evaluate) +static int eval_string(char **arg, typval_T *rettv, int evaluate) { char *p; unsigned int extra = 0; @@ -3818,7 +3975,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) if (extra != 0) { name += extra; if (name >= rettv->vval.v_string + len) { - iemsg("get_string_tv() used more space than allocated"); + iemsg("eval_string() used more space than allocated"); } break; } @@ -3845,7 +4002,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) /// Allocate a variable for a 'str''ing' constant. /// /// @return OK or FAIL. -static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) +static int eval_lit_string(char **arg, typval_T *rettv, int evaluate) { char *p; int reduce = 0; @@ -3929,9 +4086,11 @@ void partial_unref(partial_T *pt) /// Allocate a variable for a List and fill it from "*arg". /// +/// @param arg "*arg" points to the "[". /// @return OK or FAIL. -static int get_list_tv(char **arg, typval_T *rettv, int evaluate) +static int eval_list(char **arg, typval_T *rettv, evalarg_T *const evalarg) { + const bool evaluate = evalarg == NULL ? false : evalarg->eval_flags & EVAL_EVALUATE; list_T *l = NULL; if (evaluate) { @@ -3941,7 +4100,7 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate) *arg = skipwhite(*arg + 1); while (**arg != ']' && **arg != NUL) { typval_T tv; - if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive! + if (eval1(arg, &tv, evalarg) == FAIL) { // Recursive! goto failret; } if (evaluate) { @@ -3949,14 +4108,20 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate) tv_list_append_owned_tv(l, tv); } + // the comma must come after the value + bool had_comma = **arg == ','; + if (had_comma) { + *arg = skipwhite(*arg + 1); + } + if (**arg == ']') { break; } - if (**arg != ',') { + + if (!had_comma) { semsg(_("E696: Missing comma in List: %s"), *arg); goto failret; } - *arg = skipwhite(*arg + 1); } if (**arg != ']') { @@ -4573,11 +4738,14 @@ static int get_literal_key(char **arg, typval_T *tv) } /// Allocate a variable for a Dictionary and fill it from "*arg". -/// "literal" is true for #{key: val} +/// +/// @param arg "*arg" points to the "{". +/// @param literal true for #{key: val} /// /// @return OK or FAIL. Returns NOTDONE for {expr}. -static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) +static int eval_dict(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool literal) { + const bool evaluate = evalarg == NULL ? false : evalarg->eval_flags & EVAL_EVALUATE; typval_T tv; char *key = NULL; char *curly_expr = skipwhite(*arg + 1); @@ -4591,7 +4759,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) // "#{abc}" is never a curly-braces expression. if (*curly_expr != '}' && !literal - && eval1(&curly_expr, &tv, false) == OK + && eval1(&curly_expr, &tv, NULL) == OK && *skipwhite(curly_expr) == '}') { return NOTDONE; } @@ -4608,7 +4776,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) while (**arg != '}' && **arg != NUL) { if ((literal ? get_literal_key(arg, &tvkey) - : eval1(arg, &tvkey, evaluate)) == FAIL) { // recursive! + : eval1(arg, &tvkey, evalarg)) == FAIL) { // recursive! goto failret; } if (**arg != ':') { @@ -4626,7 +4794,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) } *arg = skipwhite(*arg + 1); - if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive! + if (eval1(arg, &tv, evalarg) == FAIL) { // Recursive! if (evaluate) { tv_clear(&tvkey); } @@ -4649,14 +4817,19 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) } tv_clear(&tvkey); + // the comma must come after the value + bool had_comma = **arg == ','; + if (had_comma) { + *arg = skipwhite(*arg + 1); + } + if (**arg == '}') { break; } - if (**arg != ',') { + if (!had_comma) { semsg(_("E722: Missing comma in Dictionary: %s"), *arg); goto failret; } - *arg = skipwhite(*arg + 1); } if (**arg != '}') { @@ -4713,7 +4886,7 @@ size_t string2float(const char *const text, float_T *const ret_value) /// @param arg Points to the '$'. It is advanced to after the name. /// /// @return FAIL if the name is invalid. -static int get_env_tv(char **arg, typval_T *rettv, int evaluate) +static int eval_env_var(char **arg, typval_T *rettv, int evaluate) { (*arg)++; char *name = *arg; @@ -6501,15 +6674,14 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex } char *retval = NULL; - char *nextcmd = NULL; *expr_start = NUL; *expr_end = NUL; char c1 = *in_end; *in_end = NUL; - char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false); - if (temp_result != NULL && nextcmd == NULL) { + char *temp_result = eval_to_string(expr_start + 1, false); + if (temp_result != NULL) { retval = xmalloc(strlen(temp_result) + (size_t)(expr_start - in_start) + (size_t)(in_end - expr_end) + 1); STRCPY(retval, in_start); @@ -6937,12 +7109,13 @@ int check_luafunc_name(const char *const str, const bool paren) /// /// Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len() /// -/// @param evaluate do more than finding the end /// @param verbose give error messages /// @param start_leader start of '!' and '-' prefixes /// @param end_leaderp end of '!' and '-' prefixes -int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int verbose) +int handle_subscript(const char **const arg, typval_T *rettv, evalarg_T *const evalarg, + bool verbose) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); int ret = OK; dict_T *selfdict = NULL; const char *lua_funcname = NULL; @@ -6971,7 +7144,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int && !ascii_iswhite(*(*arg - 1))) || (**arg == '-' && (*arg)[1] == '>'))) { if (**arg == '(') { - ret = call_func_rettv((char **)arg, rettv, evaluate, selfdict, NULL, lua_funcname); + ret = call_func_rettv((char **)arg, evalarg, rettv, evaluate, selfdict, NULL, lua_funcname); // Stop the expression evaluation when immediately aborting on // error, or when an interrupt occurred or an exception was thrown @@ -6987,10 +7160,10 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int } else if (**arg == '-') { if ((*arg)[2] == '{') { // expr->{lambda}() - ret = eval_lambda((char **)arg, rettv, evaluate, verbose); + ret = eval_lambda((char **)arg, rettv, evalarg, verbose); } else { // expr->name() - ret = eval_method((char **)arg, rettv, evaluate, verbose); + ret = eval_method((char **)arg, rettv, evalarg, verbose); } } else { // **arg == '[' || **arg == '.' tv_dict_unref(selfdict); @@ -7002,7 +7175,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int } else { selfdict = NULL; } - if (eval_index((char **)arg, rettv, evaluate, verbose) == FAIL) { + if (eval_index((char **)arg, rettv, evalarg, verbose) == FAIL) { tv_clear(rettv); ret = FAIL; } @@ -7374,6 +7547,9 @@ void ex_echo(exarg_T *eap) bool need_clear = true; const int did_emsg_before = did_emsg; const int called_emsg_before = called_emsg; + evalarg_T evalarg; + + fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) { emsg_skip++; @@ -7385,7 +7561,7 @@ void ex_echo(exarg_T *eap) { char *p = arg; - if (eval1(&arg, &rettv, !eap->skip) == FAIL) { + if (eval1(&arg, &rettv, &evalarg) == FAIL) { // Report the invalid expression unless the expression evaluation // has been cancelled due to an aborting error, an interrupt, or an // exception. @@ -7425,6 +7601,7 @@ void ex_echo(exarg_T *eap) arg = skipwhite(arg); } eap->nextcmd = check_nextcmd(arg); + clear_evalarg(&evalarg, eap); if (eap->skip) { emsg_skip--; @@ -7463,7 +7640,7 @@ void ex_execute(exarg_T *eap) emsg_skip++; } while (*arg != NUL && *arg != '|' && *arg != '\n') { - ret = eval1_emsg(&arg, &rettv, !eap->skip); + ret = eval1_emsg(&arg, &rettv, eap); if (ret == FAIL) { break; } @@ -8246,14 +8423,14 @@ bool eval_has_provider(const char *feat) typval_T tv; // Get the g:loaded_xx_provider variable. int len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); - if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { + if (eval_variable(buf, len, &tv, NULL, false, true) == FAIL) { // Trigger autoload once. len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name); script_autoload(buf, (size_t)len, false); // Retry the (non-autoload-style) variable. len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); - if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { + if (eval_variable(buf, len, &tv, NULL, false, true) == FAIL) { // Show a hint if Call() is defined but g:loaded_xx_provider is missing. snprintf(buf, sizeof(buf), "provider#%s#Call", name); if (!!find_func(buf) && p_lpl) { diff --git a/src/nvim/eval.h b/src/nvim/eval.h index aa034cb2b3..e9cdb108a8 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -266,6 +266,27 @@ typedef int (*ex_unletlock_callback)(lval_T *, char *, exarg_T *, int); // Used for checking if local variables or arguments used in a lambda. extern bool *eval_lavars_used; +/// Struct passed through eval() functions. +/// See EVALARG_EVALUATE for a fixed value with eval_flags set to EVAL_EVALUATE. +typedef struct { + int eval_flags; ///< EVAL_ flag values below + + /// copied from exarg_T when "getline" is "getsourceline". Can be NULL. + LineGetter eval_getline; + void *eval_cookie; ///< argument for eval_getline() + + /// pointer to the last line obtained with getsourceline() + char *eval_tofree; +} evalarg_T; + +/// Flag for expression evaluation. +enum { + EVAL_EVALUATE = 1, ///< when missing don't actually evaluate +}; + +/// Passed to an eval() function to enable evaluation. +EXTERN evalarg_T EVALARG_EVALUATE INIT(= { EVAL_EVALUATE, NULL, NULL, NULL }); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.h.generated.h" #endif diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 7f224f371c..23d0242ce4 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1550,7 +1550,7 @@ static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } const char *const expr_start = s; - if (s == NULL || eval1((char **)&s, rettv, true) == FAIL) { + if (s == NULL || eval1((char **)&s, rettv, &EVALARG_EVALUATE) == FAIL) { if (expr_start != NULL && !aborting()) { semsg(_(e_invexpr2), expr_start); } @@ -1722,7 +1722,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(exp); } } else if (*p == '&' || *p == '+') { // Option. - n = (get_option_tv(&p, NULL, true) == OK); + n = (eval_option(&p, NULL, true) == OK); if (*skipwhite(p) != NUL) { n = false; // Trailing garbage. } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 3e67571053..91be41751e 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -4201,3 +4201,34 @@ const char *tv_get_string_buf(const typval_T *const tv, char *const buf) return res != NULL ? res : ""; } + +/// Return true when "tv" is not falsy: non-zero, non-empty string, non-empty +/// list, etc. Mostly like what JavaScript does, except that empty list and +/// empty dictionary are false. +bool tv2bool(const typval_T *const tv) +{ + switch (tv->v_type) { + case VAR_NUMBER: + return tv->vval.v_number != 0; + case VAR_FLOAT: + return tv->vval.v_float != 0.0; + case VAR_PARTIAL: + return tv->vval.v_partial != NULL; + case VAR_FUNC: + case VAR_STRING: + return tv->vval.v_string != NULL && *tv->vval.v_string != NUL; + case VAR_LIST: + return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0; + case VAR_DICT: + return tv->vval.v_dict != NULL && tv->vval.v_dict->dv_hashtab.ht_used > 0; + case VAR_BOOL: + return tv->vval.v_bool == kBoolVarTrue; + case VAR_SPECIAL: + return tv->vval.v_special == kSpecialVarNull; + case VAR_BLOB: + return tv->vval.v_blob != NULL && tv->vval.v_blob->bv_ga.ga_len > 0; + case VAR_UNKNOWN: + break; + } + return false; +} diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index f09da6b79b..178f9fd6b6 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -71,6 +71,8 @@ static const char *e_funcref = N_("E718: Funcref required"); static const char *e_nofunc = N_("E130: Unknown function: %s"); static const char e_no_white_space_allowed_before_str_str[] = N_("E1068: No white space allowed before '%s': %s"); +static const char e_missing_heredoc_end_marker_str[] + = N_("E1145: Missing heredoc end marker: %s"); void func_init(void) { @@ -152,7 +154,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * p = skipwhite(p) + 1; p = skipwhite(p); char *expr = p; - if (eval1(&p, &rettv, false) != FAIL) { + if (eval1(&p, &rettv, NULL) != FAIL) { ga_grow(default_args, 1); // trim trailing whitespace @@ -252,22 +254,23 @@ static void set_ufunc_name(ufunc_T *fp, char *name) /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) +int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; ufunc_T *fp = NULL; partial_T *pt = NULL; int varargs; int ret; - char *start = skipwhite(*arg + 1); - char *s, *e; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; + char *tofree = NULL; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, NULL, true); - if (ret == FAIL || *start != '>') { + char *s = skipwhite(*arg + 1); + ret = get_function_args(&s, '-', NULL, NULL, NULL, true); + if (ret == FAIL || *s != '>') { return NOTDONE; } @@ -290,12 +293,18 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) // Get the start and the end of the expression. *arg = skipwhite((*arg) + 1); - s = *arg; - ret = skip_expr(arg); + char *start = *arg; + ret = skip_expr(arg, evalarg); + char *end = *arg; if (ret == FAIL) { goto errret; } - e = *arg; + if (evalarg != NULL) { + // avoid that the expression gets freed when another line break follows + tofree = evalarg->eval_tofree; + evalarg->eval_tofree = NULL; + } + *arg = skipwhite(*arg); if (**arg != '}') { semsg(_("E451: Expected }: %s"), *arg); @@ -317,11 +326,11 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) ga_grow(&newlines, 1); // Add "return " before the expression. - size_t len = (size_t)(7 + e - s + 1); + size_t len = (size_t)(7 + end - start + 1); p = xmalloc(len); ((char **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - xstrlcpy(p + 7, s, (size_t)(e - s) + 1); + xstrlcpy(p + 7, start, (size_t)(end - start) + 1); if (strstr(p + 7, "a:") == NULL) { // No a: variables are used for sure. flags |= FC_NOARGS; @@ -359,12 +368,22 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) } eval_lavars_used = old_eval_lavars; + if (evalarg != NULL && evalarg->eval_tofree == NULL) { + evalarg->eval_tofree = tofree; + } else { + xfree(tofree); + } return OK; errret: ga_clear_strings(&newargs); xfree(fp); xfree(pt); + if (evalarg != NULL && evalarg->eval_tofree == NULL) { + evalarg->eval_tofree = tofree; + } else { + xfree(tofree); + } eval_lavars_used = old_eval_lavars; return FAIL; } @@ -448,7 +467,8 @@ void emsg_funcname(const char *errmsg, const char *name) /// @param funcexe various values /// /// @return OK or FAIL. -int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) +int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg, + funcexe_T *funcexe) { char *argp; int ret = OK; @@ -463,7 +483,7 @@ int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_ if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1(&argp, &argvars[argcount], funcexe->fe_evaluate) == FAIL) { + if (eval1(&argp, &argvars[argcount], evalarg) == FAIL) { ret = FAIL; break; } @@ -972,7 +992,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett default_expr = ((char **)(fp->uf_def_args.ga_data)) [ai + fp->uf_def_args.ga_len]; - if (eval1(&default_expr, &def_rettv, true) == FAIL) { + if (eval1(&default_expr, &def_rettv, &EVALARG_EVALUATE) == FAIL) { default_arg_err = true; break; } @@ -1109,7 +1129,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // A Lambda always has the command "return {expr}". It is much faster // to evaluate {expr} directly. ex_nesting_level++; - (void)eval1(&p, rettv, true); + (void)eval1(&p, rettv, &EVALARG_EVALUATE); ex_nesting_level--; } else { // call do_cmdline() to execute the lines @@ -2301,7 +2321,11 @@ void ex_function(exarg_T *eap) lines_left = Rows - 1; } if (theline == NULL) { - emsg(_("E126: Missing :endfunction")); + if (skip_until != NULL) { + semsg(_(e_missing_heredoc_end_marker_str), skip_until); + } else { + emsg(_("E126: Missing :endfunction")); + } goto erret; } if (show_block) { @@ -2947,13 +2971,15 @@ void ex_return(exarg_T *eap) return; } + evalarg_T evalarg = { .eval_flags = eap->skip ? 0 : EVAL_EVALUATE }; + if (eap->skip) { emsg_skip++; } eap->nextcmd = NULL; if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + && eval0(arg, &rettv, eap, &evalarg) != FAIL) { if (!eap->skip) { returning = do_return(eap, false, true, &rettv); } else { @@ -2982,6 +3008,7 @@ void ex_return(exarg_T *eap) if (eap->skip) { emsg_skip--; } + clear_evalarg(&evalarg, eap); } /// ":1,25call func(arg1, arg2)" function call. @@ -2998,16 +3025,19 @@ void ex_call(exarg_T *eap) bool failed = false; funcdict_T fudi; partial_T *partial = NULL; + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) { // trans_function_name() doesn't work well when skipping, use eval0() // instead to skip to any following command, e.g. for: // :if 0 | call dict.foo().bar() | endif. emsg_skip++; - if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { + if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) { tv_clear(&rettv); } emsg_skip--; + clear_evalarg(&evalarg, eap); return; } @@ -3065,14 +3095,13 @@ void ex_call(exarg_T *eap) funcexe.fe_evaluate = true; funcexe.fe_partial = partial; funcexe.fe_selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { + if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) { failed = true; break; } // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true) - == FAIL) { + if (handle_subscript((const char **)&arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) { failed = true; break; } @@ -3104,6 +3133,7 @@ void ex_call(exarg_T *eap) eap->nextcmd = check_nextcmd(arg); } } + clear_evalarg(&evalarg, eap); end: tv_dict_unref(fudi.fd_dict); diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index c8583f232c..f0e1f5dca0 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include <stddef.h> +#include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 701f190a06..b8a8f39437 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -175,25 +175,16 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) /// ":let var ..= expr" assignment command. /// ":let [var1, var2] = expr" unpack list. /// ":let [name, ..., ; lastname] = expr" unpack list. -void ex_let(exarg_T *eap) -{ - ex_let_const(eap, false); -} - +/// /// ":cons[t] var = expr1" define constant /// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list /// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list -void ex_const(exarg_T *eap) -{ - ex_let_const(eap, true); -} - -static void ex_let_const(exarg_T *eap, const bool is_const) +void ex_let(exarg_T *eap) { + const bool is_const = eap->cmdidx == CMD_const; char *arg = eap->arg; char *expr = NULL; typval_T rettv; - int i; int var_count = 0; int semicolon = 0; char op[2]; @@ -208,8 +199,10 @@ static void ex_let_const(exarg_T *eap, const bool is_const) argend--; } expr = skipwhite(argend); - if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL - && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) { + bool concat = strncmp(expr, "..=", 3) == 0; + bool has_assign = *expr == '=' || (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL + && expr[1] == '='); + if (!has_assign && !concat) { // ":let" without "=": list variables if (*arg == '[') { emsg(_(e_invarg)); @@ -227,7 +220,10 @@ static void ex_let_const(exarg_T *eap, const bool is_const) list_vim_vars(&first); } eap->nextcmd = check_nextcmd(arg); - } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { + return; + } + + if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { // HERE document list_T *l = heredoc_get(eap, expr + 3); if (l != NULL) { @@ -239,36 +235,43 @@ static void ex_let_const(exarg_T *eap, const bool is_const) } tv_clear(&rettv); } - } else { - op[0] = '='; - op[1] = NUL; - if (*expr != '=') { - if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) { - op[0] = *expr; // +=, -=, *=, /=, %= or .= - if (expr[0] == '.' && expr[1] == '.') { // ..= - expr++; - } - } - expr += 2; - } else { - expr += 1; - } + return; + } - expr = skipwhite(expr); + rettv.v_type = VAR_UNKNOWN; - if (eap->skip) { - emsg_skip++; - } - i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip); - if (eap->skip) { - if (i != FAIL) { - tv_clear(&rettv); + op[0] = '='; + op[1] = NUL; + if (*expr != '=') { + if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) { + op[0] = *expr; // +=, -=, *=, /=, %= or .= + if (expr[0] == '.' && expr[1] == '.') { // ..= + expr++; } - emsg_skip--; - } else if (i != FAIL) { - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op); - tv_clear(&rettv); } + expr += 2; + } else { + expr += 1; + } + + expr = skipwhite(expr); + + if (eap->skip) { + emsg_skip++; + } + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, eap->skip); + int eval_res = eval0(expr, &rettv, eap, &evalarg); + if (eap->skip) { + emsg_skip--; + } + clear_evalarg(&evalarg, eap); + + if (!eap->skip && eval_res != FAIL) { + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op); + } + if (eval_res != FAIL) { + tv_clear(&rettv); } } @@ -500,13 +503,12 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) if (tofree != NULL) { name = tofree; } - if (get_var_tv(name, len, &tv, NULL, true, false) - == FAIL) { + if (eval_variable(name, len, &tv, NULL, true, false) == FAIL) { error = true; } else { // handle d.key, l[idx], f(expr) const char *const arg_subsc = arg; - if (handle_subscript(&arg, &tv, true, true) == FAIL) { + if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, true) == FAIL) { error = true; } else { if (arg == arg_subsc && len == 2 && name[1] == ':') { @@ -1073,8 +1075,8 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap /// @param dip non-NULL when typval's dict item is needed /// @param verbose may give error message /// @param no_autoload do not use script autoloading -int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose, - bool no_autoload) +int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose, + bool no_autoload) { int ret = OK; typval_T *tv = NULL; @@ -1149,7 +1151,7 @@ void vars_clear_ext(hashtab_T *ht, int free_val) } } hash_clear(ht); - ht->ht_used = 0; + hash_init(ht); } /// Delete a variable from hashtab "ht" at item "hi". @@ -1561,7 +1563,7 @@ static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv, tv_dict_set_ret(rettv, opts); done = true; } - } else if (get_option_tv(&varname, rettv, true) == OK) { + } else if (eval_option(&varname, rettv, true) == OK) { // Local option done = true; } @@ -1710,10 +1712,10 @@ bool var_exists(const char *var) if (tofree != NULL) { name = tofree; } - n = get_var_tv(name, len, &tv, NULL, false, true) == OK; + n = eval_variable(name, len, &tv, NULL, false, true) == OK; if (n) { // Handle d.key, l[idx], f(expr). - n = handle_subscript(&var, &tv, true, false) == OK; + n = handle_subscript(&var, &tv, &EVALARG_EVALUATE, false) == OK; if (n) { tv_clear(&tv); } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 2fd50a18d3..be6299db0e 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -197,12 +197,6 @@ module.cmds = { func='ex_bunload', }, { - command='behave', - flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK), - addr_type='ADDR_NONE', - func='ex_behave', - }, - { command='belowright', flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM), addr_type='ADDR_NONE', @@ -640,7 +634,7 @@ module.cmds = { command='const', flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', - func='ex_const', + func='ex_let', }, { command='copen', diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 629aaf14cf..7932649114 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -185,6 +185,7 @@ struct exarg { char *nextcmd; ///< next command (NULL if none) char *cmd; ///< the name of the command (except for :make) char **cmdlinep; ///< pointer to pointer of allocated cmdline + char *cmdline_tofree; ///< free later cmdidx_T cmdidx; ///< the index for the command uint32_t argt; ///< flags for the command int skip; ///< don't execute the command, only parse it diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 699c1da3a7..8e55672615 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -484,24 +484,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } } - if (cstack.cs_looplevel > 0) { - // Inside a while/for loop we need to store the lines and use them - // again. Pass a different "fgetline" function to do_one_cmd() - // below, so that it stores lines in or reads them from - // "lines_ga". Makes it possible to define a function inside a - // while/for loop. - cmd_getline = get_loop_line; - cmd_cookie = (void *)&cmd_loop_cookie; - cmd_loop_cookie.lines_gap = &lines_ga; - cmd_loop_cookie.current_line = current_line; - cmd_loop_cookie.getline = fgetline; - cmd_loop_cookie.cookie = cookie; - cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); - } else { - cmd_getline = fgetline; - cmd_cookie = cookie; - } - // 2. If no line given, get an allocated line with fgetline(). if (next_cmdline == NULL) { // Need to set msg_didout for the first line after an ":if", @@ -540,15 +522,37 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } cmdline_copy = next_cmdline; - // Save the current line when inside a ":while" or ":for", and when - // the command looks like a ":while" or ":for", because we may need it - // later. When there is a '|' and another command, it is stored - // separately, because we need to be able to jump back to it from an + int current_line_before = 0; + // Inside a while/for loop, and when the command looks like a ":while" + // or ":for", the line is stored, because we may need it later when + // looping. + // + // When there is a '|' and another command, it is stored separately, + // because we need to be able to jump back to it from an // :endwhile/:endfor. - if (current_line == lines_ga.ga_len - && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) { - store_loop_line(&lines_ga, next_cmdline); + // + // Pass a different "fgetline" function to do_one_cmd() below, + // that it stores lines in or reads them from "lines_ga". Makes it + // possible to define a function inside a while/for loop. + if ((cstack.cs_looplevel > 0 || has_loop_cmd(next_cmdline))) { + cmd_getline = get_loop_line; + cmd_cookie = (void *)&cmd_loop_cookie; + cmd_loop_cookie.lines_gap = &lines_ga; + cmd_loop_cookie.current_line = current_line; + cmd_loop_cookie.getline = fgetline; + cmd_loop_cookie.cookie = cookie; + cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); + + // Save the current line when encountering it the first time. + if (current_line == lines_ga.ga_len) { + store_loop_line(&lines_ga, next_cmdline); + } + current_line_before = current_line; + } else { + cmd_getline = fgetline; + cmd_cookie = cookie; } + did_endif = false; if (count++ == 0) { @@ -651,7 +655,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } else if (cstack.cs_lflags & CSL_HAD_LOOP) { // For a ":while" or ":for" we need to remember the line number. cstack.cs_lflags &= ~CSL_HAD_LOOP; - cstack.cs_line[cstack.cs_idx] = current_line - 1; + cstack.cs_line[cstack.cs_idx] = current_line_before; } } @@ -2328,6 +2332,7 @@ doend: } ex_nesting_level--; + xfree(ea.cmdline_tofree); return ea.nextcmd; } @@ -3750,7 +3755,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // Skip over `=expr`, wildcards in it are not expanded. if (p[0] == '`' && p[1] == '=') { p += 2; - (void)skip_expr(&p); + (void)skip_expr(&p, NULL); if (*p == '`') { p++; } @@ -3969,7 +3974,7 @@ void separate_nextcmd(exarg_T *eap) } else if (p[0] == '`' && p[1] == '=' && (eap->argt & EX_XFILE)) { // Skip over `=expr` when wildcards are expanded. p += 2; - (void)skip_expr(&p); + (void)skip_expr(&p, NULL); if (*p == NUL) { // stop at NUL after CTRL-V break; } @@ -4432,7 +4437,7 @@ static void ex_colorscheme(exarg_T *eap) char *expr = xstrdup("g:colors_name"); emsg_off++; - char *p = eval_to_string(expr, NULL, false); + char *p = eval_to_string(expr, false); emsg_off--; xfree(expr); @@ -7058,24 +7063,6 @@ void dialog_msg(char *buff, char *format, char *fname) vim_snprintf(buff, DIALOG_MSG_SIZE, format, fname); } -/// ":behave {mswin,xterm}" -static void ex_behave(exarg_T *eap) -{ - if (strcmp(eap->arg, "mswin") == 0) { - set_option_value_give_err("selection", 0L, "exclusive", 0); - set_option_value_give_err("selectmode", 0L, "mouse,key", 0); - set_option_value_give_err("mousemodel", 0L, "popup", 0); - set_option_value_give_err("keymodel", 0L, "startsel,stopsel", 0); - } else if (strcmp(eap->arg, "xterm") == 0) { - set_option_value_give_err("selection", 0L, "inclusive", 0); - set_option_value_give_err("selectmode", 0L, "", 0); - set_option_value_give_err("mousemodel", 0L, "extend", 0); - set_option_value_give_err("keymodel", 0L, "", 0); - } else { - semsg(_(e_invarg2), eap->arg); - } -} - static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index fed8f549e6..5404ae6731 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -792,10 +792,15 @@ void report_discard_pending(int pending, void *value) void ex_eval(exarg_T *eap) { typval_T tv; + evalarg_T evalarg; - if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) { + fill_evalarg_from_eap(&evalarg, eap, eap->skip); + + if (eval0(eap->arg, &tv, eap, &evalarg) == OK) { tv_clear(&tv); } + + clear_evalarg(&evalarg, eap); } /// Handle ":if". @@ -812,7 +817,7 @@ void ex_if(exarg_T *eap) int skip = CHECK_SKIP; bool error; - int result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + int result = eval_to_bool(eap->arg, &error, eap, skip); if (!skip && !error) { if (result) { @@ -907,7 +912,7 @@ void ex_else(exarg_T *eap) if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) { semsg(_(e_invexpr2), eap->arg); } else { - result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + result = eval_to_bool(eap->arg, &error, eap, skip); } // When throwing error exceptions, we want to throw always the first @@ -952,13 +957,12 @@ void ex_while(exarg_T *eap) eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR; int skip = CHECK_SKIP; - if (eap->cmdidx == CMD_while) { - // ":while bool-expr" - result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); - } else { + if (eap->cmdidx == CMD_while) { // ":while bool-expr" + result = eval_to_bool(eap->arg, &error, eap, skip); + } else { // ":for var in list-expr" + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, skip); void *fi; - - // ":for var in list-expr" if ((cstack->cs_lflags & CSL_HAD_LOOP) != 0) { // Jumping here from a ":continue" or ":endfor": use the // previously evaluated list. @@ -966,7 +970,7 @@ void ex_while(exarg_T *eap) error = false; } else { // Evaluate the argument and get the info in a structure. - fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip); + fi = eval_for_line(eap->arg, &error, eap, &evalarg); cstack->cs_forinfo[cstack->cs_idx] = fi; } @@ -981,6 +985,7 @@ void ex_while(exarg_T *eap) free_for_info(fi); cstack->cs_forinfo[cstack->cs_idx] = NULL; } + clear_evalarg(&evalarg, eap); } // If this cstack entry was just initialised and is active, set the @@ -1125,12 +1130,11 @@ void ex_endwhile(exarg_T *eap) /// Handle ":throw expr" void ex_throw(exarg_T *eap) { - const char *arg = eap->arg; + char *arg = eap->arg; char *value; if (*arg != NUL && *arg != '|' && *arg != '\n') { - value = eval_to_string_skip(arg, (const char **)&eap->nextcmd, - (bool)eap->skip); + value = eval_to_string_skip(arg, eap, eap->skip); } else { emsg(_(e_argreq)); value = NULL; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index ba403e3dd9..f3afbdcaaf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4209,7 +4209,8 @@ void f_setcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } - rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos); + // Use tv_get_string() to handle a NULL string like an empty string. + rettv->vval.v_number = set_cmdline_str(tv_get_string(&argvars[0]), pos); } /// "setcmdpos()" function diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 71984e806d..a0869b54c9 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1749,7 +1749,8 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo curbuf = wp->w_buffer; emsg_silent++; // handle exceptions, but don't display errors - text = eval_to_string_safe(wp->w_p_fdt, NULL, was_set_insecurely(wp, "foldtext", OPT_LOCAL)); + text = eval_to_string_safe(wp->w_p_fdt, + was_set_insecurely(wp, "foldtext", OPT_LOCAL)); emsg_silent--; if (text == NULL || did_emsg) { diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 6493408a58..3ab8a3bb88 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -1530,6 +1530,10 @@ static bool hlgroup2dict(Dictionary *hl, NS ns_id, int hl_id, Arena *arena) if (link == -1) { return false; } + if (ns_id == 0 && sgp->sg_cleared && sgp->sg_set == 0) { + // table entry was created but not ever set + return false; + } HlAttrs attr = syn_attr2entry(ns_id == 0 ? sgp->sg_attr : ns_get_hl(&ns_id, hl_id, false, sgp->sg_set)); *hl = arena_dict(arena, HLATTRS_DICT_SIZE + 1); diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 19a2aca75e..c1565a84f5 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -1632,7 +1632,7 @@ char *eval_map_expr(mapblock_T *mp, int c) api_clear_error(&err); } } else { - p = eval_to_string(expr, NULL, false); + p = eval_to_string(expr, false); xfree(expr); } expr_map_lock--; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 855fcb33ae..59ff3026b3 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -84,7 +84,7 @@ void clear_fmark(fmark_T *fm) FUNC_ATTR_NONNULL_ALL { free_fmark(*fm); - CLEAR_POINTER(fm); + *fm = (fmark_T)INIT_FMARK; } // Set named mark "c" to position "pos". diff --git a/src/nvim/mark.h b/src/nvim/mark.h index af0abba864..8c72579d0f 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -36,7 +36,7 @@ /// Clear given fmark #define CLEAR_FMARK(fmarkp_) \ - RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0, ((fmarkv_T) { 0 })) + RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0, ((fmarkv_T)INIT_FMARKV)) /// Set given extended mark (regular mark + file name) #define SET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \ diff --git a/src/nvim/normal.c b/src/nvim/normal.c index b11d5a3c32..718c51deb0 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1173,7 +1173,7 @@ static int normal_execute(VimState *state, int key) msg_col = 0; } - s->old_pos = curwin->w_cursor; // remember where cursor was + s->old_pos = curwin->w_cursor; // remember where the cursor was // When 'keymodel' contains "startsel" some keys start Select/Visual // mode. @@ -1997,13 +1997,21 @@ static void display_showcmd(void) showcmd_is_clear = (len == 0); if (*p_sloc == 's') { - win_redr_status(curwin); - setcursor(); // put cursor back where it belongs + if (showcmd_is_clear) { + curwin->w_redr_status = true; + } else { + win_redr_status(curwin); + setcursor(); // put cursor back where it belongs + } return; } if (*p_sloc == 't') { - draw_tabline(); - setcursor(); // put cursor back where it belongs + if (showcmd_is_clear) { + redraw_tabline = true; + } else { + draw_tabline(); + setcursor(); // put cursor back where it belongs + } return; } // 'showcmdloc' is "last" or empty diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9aacfcad30..b2c0dd6c01 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -763,7 +763,7 @@ char *get_expr_line(void) } nested++; - rv = eval_to_string(expr_copy, NULL, true); + rv = eval_to_string(expr_copy, true); nested--; xfree(expr_copy); return rv; diff --git a/src/nvim/option.c b/src/nvim/option.c index 386c6d88d9..6d4e7de1a3 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5266,7 +5266,7 @@ int option_set_callback_func(char *optval, Callback *optcb) || (strncmp(optval, "function(", 9) == 0) || (strncmp(optval, "funcref(", 8) == 0)) { // Lambda expression or a funcref - tv = eval_expr(optval); + tv = eval_expr(optval, NULL); if (tv == NULL) { return FAIL; } diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 0b06877f3c..26707fd6ca 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -603,7 +603,7 @@ void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool es if (src[0] == '`' && src[1] == '=') { var = src; src += 2; - (void)skip_expr(&src); + (void)skip_expr(&src, NULL); if (*src == '`') { src++; } diff --git a/src/nvim/path.c b/src/nvim/path.c index 368f3feb27..8bd3303166 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1371,7 +1371,7 @@ static int expand_backtick(garray_T *gap, char *pat, int flags) char *cmd = xstrnsave(pat + 1, strlen(pat) - 2); if (*cmd == '=') { // `={expr}`: Expand expression - buffer = eval_to_string(cmd + 1, &p, true); + buffer = eval_to_string(cmd + 1, true); } else { buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); } @@ -1662,7 +1662,7 @@ void simplify_filename(char *filename) static char *eval_includeexpr(const char *const ptr, const size_t len) { set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); - char *res = eval_to_string_safe(curbuf->b_p_inex, NULL, + char *res = eval_to_string_safe(curbuf->b_p_inex, was_set_insecurely(curwin, "includeexpr", OPT_LOCAL)); set_vim_var_string(VV_FNAME, NULL, 0); return res; diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index c8db9a64b5..ab6acf685f 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -5540,7 +5540,7 @@ msgstr "tekijät Bram Moolenaar et al." #~ msgstr "kirjoita :help iccf<Enter> lisätietoa varten " #, fuzzy -#~ msgid "type :CheckHealth<Enter> to optimize Nvim" +#~ msgid "type :checkhealth<Enter> to optimize Nvim" #~ msgstr "kirjoita :help iccf<Enter> lisätietoa varten " msgid "type :q<Enter> to exit " diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 262d87ba61..62eb14342c 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -6957,15 +6957,15 @@ void ex_cexpr(exarg_T *eap) // Evaluate the expression. When the result is a string or a list we can // use it to fill the errorlist. - typval_T tv; - if (eval0(eap->arg, &tv, &eap->nextcmd, true) == FAIL) { + typval_T *tv = eval_expr(eap->arg, eap); + if (tv == NULL) { return; } - if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || tv.v_type == VAR_LIST) { + if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL) + || tv->v_type == VAR_LIST) { incr_quickfix_busy(); - int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, + int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), (linenr_T)0, (linenr_T)0, @@ -6996,7 +6996,7 @@ void ex_cexpr(exarg_T *eap) emsg(_("E777: String or List expected")); } cleanup: - tv_clear(&tv); + tv_free(tv); } // Get the location list for ":lhelpgrep" diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 94796f0ed3..3ed32bf8af 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -1812,7 +1812,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen } tv_clear(&rettv); } else { - eval_result[nested] = eval_to_string(source + 2, NULL, true); + eval_result[nested] = eval_to_string(source + 2, true); } nesting--; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2d5eee7eec..78499922bf 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1307,6 +1307,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .mark = cur_entry.data.filemark.mark, .fnum = (buf == NULL ? 0 : buf->b_fnum), .timestamp = cur_entry.timestamp, + .view = INIT_FMARKV, .additional_data = cur_entry.data.filemark.additional_data, }, }; @@ -1388,6 +1389,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .mark = cur_entry.data.filemark.mark, .fnum = 0, .timestamp = cur_entry.timestamp, + .view = INIT_FMARKV, .additional_data = cur_entry.data.filemark.additional_data, }; if (cur_entry.type == kSDItemLocalMark) { diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 6c698f45be..05649e9b7f 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -985,7 +985,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n }; set_var(S_LEN("g:statusline_winid"), &tv, false); - usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); + usefmt = eval_to_string_safe(fmt + 2, use_sandbox); if (usefmt == NULL) { usefmt = fmt; } @@ -1457,7 +1457,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n } // Note: The result stored in `t` is unused. - str = eval_to_string_safe(out_p, &t, use_sandbox); + str = eval_to_string_safe(out_p, use_sandbox); curwin = save_curwin; curbuf = save_curbuf; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 401b43204e..cebc7d5d89 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -3322,11 +3322,10 @@ int get_tags(list_T *list, char *pat, char *buf_fname) } for (i = 0; i < num_matches; i++) { - int parse_result = parse_match(matches[i], &tp); - - // Avoid an unused variable warning in release builds. - (void)parse_result; - assert(parse_result == OK); + if (parse_match(matches[i], &tp) == FAIL) { + xfree(matches[i]); + continue; + } bool is_static = test_for_static(&tp); diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 4cd58bb91b..e859a96552 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -56,7 +56,6 @@ static const char e_no_such_user_defined_command_in_current_buffer_str[] static const char *command_complete[] = { [EXPAND_ARGLIST] = "arglist", [EXPAND_AUGROUP] = "augroup", - [EXPAND_BEHAVE] = "behave", [EXPAND_BUFFERS] = "buffer", [EXPAND_CHECKHEALTH] = "checkhealth", [EXPAND_COLORS] = "color", diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 00e28e3e68..44e3e9d86a 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -140,7 +140,6 @@ enum { EXPAND_SHELLCMD, EXPAND_SIGN, EXPAND_PROFILE, - EXPAND_BEHAVE, EXPAND_FILETYPE, EXPAND_FILES_IN_PATH, EXPAND_OWNSYNTAX, diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index a4bd574a56..a6e9f9a42b 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -575,4 +575,25 @@ describe('API: get highlight', function() meths.set_hl(0, 'Foo', hl) eq(hl, meths.get_hl(0, { name = 'Foo', link = true })) end) + + it("doesn't contain unset groups", function() + local id = meths.get_hl_id_by_name "@foobar.hubbabubba" + ok(id > 0) + + local data = meths.get_hl(0, {}) + eq(nil, data["@foobar.hubbabubba"]) + eq(nil, data["@foobar"]) + + command 'hi @foobar.hubbabubba gui=bold' + data = meths.get_hl(0, {}) + eq({bold = true}, data["@foobar.hubbabubba"]) + eq(nil, data["@foobar"]) + + -- @foobar.hubbabubba was explicitly cleared and thus shows up + -- but @foobar was never touched, and thus doesn't + command 'hi clear @foobar.hubbabubba' + data = meths.get_hl(0, {}) + eq({}, data["@foobar.hubbabubba"]) + eq(nil, data["@foobar"]) + end) end) diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua index 29d589a296..29a6171269 100644 --- a/test/functional/autocmd/autocmd_oldtest_spec.lua +++ b/test/functional/autocmd/autocmd_oldtest_spec.lua @@ -50,6 +50,7 @@ describe('oldtests', function() it('should fire on unload buf', function() funcs.writefile({'Test file Xxx1'}, 'Xxx1') funcs.writefile({'Test file Xxx2'}, 'Xxx2') + local fname = 'Xtest_functional_autocmd_unload' local content = [[ func UnloadAllBufs() @@ -69,15 +70,15 @@ describe('oldtests', function() q ]] - funcs.writefile(funcs.split(content, "\n"), 'Xtest') + funcs.writefile(funcs.split(content, "\n"), fname) funcs.delete('Xout') - funcs.system(meths.get_vvar('progpath') .. ' -u NORC -i NONE -N -S Xtest') + funcs.system(string.format('%s -u NORC -i NONE -N -S %s', meths.get_vvar('progpath'), fname)) eq(1, funcs.filereadable('Xout')) funcs.delete('Xxx1') funcs.delete('Xxx2') - funcs.delete('Xtest') + funcs.delete(fname) funcs.delete('Xout') end) end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index cc94623df3..b8a3c1dcd5 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -28,6 +28,9 @@ local meths = helpers.meths local alter_slashes = helpers.alter_slashes local is_os = helpers.is_os local dedent = helpers.dedent +local tbl_map = helpers.tbl_map +local tbl_filter = helpers.tbl_filter +local endswith = helpers.endswith local testfile = 'Xtest_startuptime' after_each(function() @@ -202,12 +205,12 @@ describe('startup', function() end) it('disables swapfile/shada/config/plugins', function() - assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scriptnames=1', + assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scripts=1', nil, nil, '-', - [[print(('updatecount=%d shadafile=%s loadplugins=%s scriptnames=%d'):format( - vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.split(vim.fn.execute('scriptnames'),'\n'))))]]) + [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]]) end) end) @@ -398,13 +401,13 @@ describe('startup', function() for _,arg in ipairs({'-es', '-Es'}) do local out = funcs.system({nvim_prog, arg, '+set swapfile? updatecount? shadafile?', - "+put =execute('scriptnames')", '+%print'}) + "+put =map(getscriptinfo(), {-> v:val.name})", '+%print'}) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. eq(" swapfile updatecount=0 shadafile=\n", line1) -- Standard plugins were loaded, but not user config. - eq('health.vim', string.match(out, 'health.vim')) - eq(nil, string.match(out, 'init.vim')) + ok(string.find(out, 'man.lua') ~= nil) + ok(string.find(out, 'init.vim') == nil) end end) @@ -865,6 +868,10 @@ describe('runtime:', function() local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep) local profiler_file = 'test_startuptime.log' + finally(function() + os.remove(profiler_file) + rmdir(plugin_path) + end) mkdir_p(plugin_folder_path) write_file(plugin_file_path, [[vim.g.lua_plugin = 2]]) @@ -872,18 +879,15 @@ describe('runtime:', function() clear{ args_rm={'-u'}, args={'--startuptime', profiler_file}, env=xenv } eq(2, eval('g:lua_plugin')) - -- Check if plugin_file_path is listed in :scriptname - local scripts = exec_capture('scriptnames') - assert(scripts:find(plugin_file_path)) + -- Check if plugin_file_path is listed in getscriptinfo() + local scripts = tbl_map(function(s) return s.name end, funcs.getscriptinfo()) + ok(#tbl_filter(function(s) return endswith(s, plugin_file_path) end, scripts) > 0) -- Check if plugin_file_path is listed in startup profile local profile_reader = io.open(profiler_file, 'r') local profile_log = profile_reader:read('*a') profile_reader:close() - assert(profile_log:find(plugin_file_path)) - - os.remove(profiler_file) - rmdir(plugin_path) + ok(profile_log:find(plugin_file_path) ~= nil) end) it('loads plugin/*.lua from site packages', function() diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 365f8527a0..a6e4b0c5eb 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -417,4 +417,48 @@ describe('named marks view', function() | ]]) end) + + it('fallback to standard behavior when mark is loaded from shada', function() + local screen = Screen.new(10, 6) + screen:attach() + command('edit ' .. file1) + feed('G') + feed('mA') + screen:expect([[ + 26 line | + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + command('set shadafile=Xtestfile-functional-editor-marks-shada') + finally(function() + command('set shadafile=NONE') + os.remove('Xtestfile-functional-editor-marks-shada') + end) + command('wshada!') + command('bwipe!') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + command('rshada!') + command('edit ' .. file1) + feed('`"') + screen:expect([[ + 26 line | + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + feed('`A') + screen:expect_unchanged() + end) end) diff --git a/test/functional/legacy/061_undo_tree_spec.lua b/test/functional/legacy/061_undo_tree_spec.lua index 1a8ef067d0..b5af8f7d52 100644 --- a/test/functional/legacy/061_undo_tree_spec.lua +++ b/test/functional/legacy/061_undo_tree_spec.lua @@ -22,29 +22,30 @@ end describe('undo tree:', function() before_each(clear) + local fname = 'Xtest_functional_legacy_undotree' teardown(function() - os.remove('Xtest.source') + os.remove(fname .. '.source') end) describe(':earlier and :later', function() before_each(function() - os.remove('Xtest') + os.remove(fname) end) teardown(function() - os.remove('Xtest') + os.remove(fname) end) it('time specifications, g- g+', function() -- We write the test text to a file in order to prevent nvim to record -- the inserting of the text into the undo history. - write_file('Xtest', '\n123456789\n') + write_file(fname, '\n123456789\n') -- `:earlier` and `:later` are (obviously) time-sensitive, so this test -- sometimes fails if the system is under load. It is wrapped in a local -- function to allow multiple attempts. local function test_earlier_later() clear() - feed_command('e Xtest') + feed_command('e ' .. fname) -- Assert that no undo history is present. eq({}, eval('undotree().entries')) -- Delete three characters and undo. @@ -103,7 +104,7 @@ describe('undo tree:', function() it('file-write specifications', function() feed('ione one one<esc>') - feed_command('w Xtest') + feed_command('w ' .. fname) feed('otwo<esc>') feed('otwo<esc>') feed_command('w') @@ -187,7 +188,7 @@ describe('undo tree:', function() it('undo an expression-register', function() local normal_commands = 'o1\027a2\018=string(123)\n\027' - write_file('Xtest.source', normal_commands) + write_file(fname .. '.source', normal_commands) feed('oa<esc>') feed('ob<esc>') @@ -221,7 +222,7 @@ describe('undo tree:', function() c 12]]) feed('od<esc>') - feed_command('so! Xtest.source') + feed_command('so! ' .. fname .. '.source') expect([[ a diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 6c72cde855..5c3f8a6f8c 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -247,6 +247,7 @@ describe('prompt buffer', function() func DoAppend() call appendbufline('prompt', '$', 'Test') + return '' endfunc ]]) feed('asomething<CR>') @@ -254,7 +255,12 @@ describe('prompt buffer', function() neq(prev_win, meths.get_current_win()) feed('exit<CR>') eq(prev_win, meths.get_current_win()) + eq({ mode = 'n', blocking = false }, meths.get_mode()) command('call DoAppend()') eq({ mode = 'n', blocking = false }, meths.get_mode()) + feed('i') + eq({ mode = 'i', blocking = false }, meths.get_mode()) + command('call DoAppend()') + eq({ mode = 'i', blocking = false }, meths.get_mode()) end) end) diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua new file mode 100644 index 0000000000..e2958d1592 --- /dev/null +++ b/test/functional/lua/loader_spec.lua @@ -0,0 +1,36 @@ +-- Test suite for testing interactions with API bindings +local helpers = require('test.functional.helpers')(after_each) + +local exec_lua = helpers.exec_lua +local command = helpers.command +local eq = helpers.eq + +describe('vim.loader', function() + before_each(helpers.clear) + + it('handles changing files (#23027)', function() + exec_lua[[ + vim.loader.enable() + ]] + + local tmp = helpers.tmpname() + command('edit ' .. tmp) + + eq(1, exec_lua([[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=1'}) + vim.cmd.write() + loadfile(...)() + return _G.TEST + ]], tmp)) + + -- fs latency + helpers.sleep(10) + + eq(2, exec_lua([[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=2'}) + vim.cmd.write() + loadfile(...)() + return _G.TEST + ]], tmp)) + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 45d9263c0c..1ee1a13fd5 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -461,6 +461,22 @@ describe('lua stdlib', function() pcall_err(exec_lua, [[return vim.pesc(2)]])) end) + it('vim.list_contains', function() + eq(true, exec_lua("return vim.list_contains({'a','b','c'}, 'c')")) + eq(false, exec_lua("return vim.list_contains({'a','b','c'}, 'd')")) + end) + + it('vim.tbl_contains', function() + eq(true, exec_lua("return vim.tbl_contains({'a','b','c'}, 'c')")) + eq(false, exec_lua("return vim.tbl_contains({'a','b','c'}, 'd')")) + eq(true, exec_lua("return vim.tbl_contains({[2]='a',foo='b',[5] = 'c'}, 'c')")) + eq(true, exec_lua([[ + return vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) + return vim.deep_equal(v, { 'b', 'c' }) + end, { predicate = true }) + ]])) + end) + it('vim.tbl_keys', function() eq({}, exec_lua("return vim.tbl_keys({})")) for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do @@ -505,6 +521,19 @@ describe('lua stdlib', function() ]])) end) + it('vim.tbl_isarray', function() + eq(true, exec_lua("return vim.tbl_isarray({})")) + eq(false, exec_lua("return vim.tbl_isarray(vim.empty_dict())")) + eq(true, exec_lua("return vim.tbl_isarray({'a', 'b', 'c'})")) + eq(false, exec_lua("return vim.tbl_isarray({'a', '32', a='hello', b='baz'})")) + eq(false, exec_lua("return vim.tbl_isarray({1, a='hello', b='baz'})")) + eq(false, exec_lua("return vim.tbl_isarray({a='hello', b='baz', 1})")) + eq(false, exec_lua("return vim.tbl_isarray({1, 2, nil, a='hello'})")) + eq(true, exec_lua("return vim.tbl_isarray({1, 2, nil, 4})")) + eq(true, exec_lua("return vim.tbl_isarray({nil, 2, 3, 4})")) + eq(false, exec_lua("return vim.tbl_isarray({1, [1.5]=2, [3]=3})")) + end) + it('vim.tbl_islist', function() eq(true, exec_lua("return vim.tbl_islist({})")) eq(false, exec_lua("return vim.tbl_islist(vim.empty_dict())")) @@ -513,6 +542,9 @@ describe('lua stdlib', function() eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})")) eq(false, exec_lua("return vim.tbl_islist({a='hello', b='baz', 1})")) eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, a='hello'})")) + eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, 4})")) + eq(false, exec_lua("return vim.tbl_islist({nil, 2, 3, 4})")) + eq(false, exec_lua("return vim.tbl_islist({1, [1.5]=2, [3]=3})")) end) it('vim.tbl_isempty', function() @@ -2297,6 +2329,10 @@ describe('lua stdlib', function() insert([[αα]]) eq({0,5}, exec_lua[[ return vim.region(0,{0,0},{0,4},'3',true)[0] ]]) end) + it('getpos() input', function() + insert('getpos') + eq({0,6}, exec_lua[[ return vim.region(0,{0,0},'.','v',true)[0] ]]) + end) end) describe('vim.on_key', function() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index da05b09593..5ba0706208 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -323,15 +323,13 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) + lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") - end; on_handler = function(_, _, ctx) if ctx.method == 'test' then eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc")) @@ -352,7 +350,8 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ vim.api.nvim_command('filetype plugin on') BUFFER_1 = vim.api.nvim_create_buf(false, true) @@ -360,14 +359,16 @@ describe('LSP', function() vim.api.nvim_buf_set_option(BUFFER_1, 'filetype', 'man') vim.api.nvim_buf_set_option(BUFFER_2, 'filetype', 'xml') ]] + + -- Sanity check to ensure that some values are set after setting filetype. eq('v:lua.require\'man\'.goto_tag', get_buf_option("tagfunc", "BUFFER_1")) eq('xmlcomplete#CompleteTags', get_buf_option("omnifunc", "BUFFER_2")) eq('xmlformat#Format()', get_buf_option("formatexpr", "BUFFER_2")) - end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)") - exec_lua("lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)") + + exec_lua [[ + lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID) + lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID) + ]] end; on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -389,18 +390,16 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_option(BUFFER, 'tagfunc', 'tfu') vim.api.nvim_buf_set_option(BUFFER, 'omnifunc', 'ofu') vim.api.nvim_buf_set_option(BUFFER, 'formatexpr', 'fex') + lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") - end; on_handler = function(_, _, ctx) if ctx.method == 'test' then eq('tfu', get_buf_option("tagfunc")) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index da2fbbe029..dad7aa851d 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -1115,5 +1115,3 @@ describe('ShaDa changes support code', function() eq(found, 100) end) end) - --- vim: ts=2 sw=2 diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index f88954c16b..3203b187cc 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -432,6 +432,25 @@ describe('float window', function() assert_alive() end) + it("should re-apply 'style' when present", function() + local float_opts = {style = 'minimal', relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local float_win = meths.open_win(0, true, float_opts) + meths.win_set_option(float_win, 'number', true) + float_opts.row = 2 + meths.win_set_config(float_win, float_opts) + eq(false, meths.win_get_option(float_win, 'number')) + end) + + it("should not re-apply 'style' when missing", function() + local float_opts = {style = 'minimal', relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local float_win = meths.open_win(0, true, float_opts) + meths.win_set_option(float_win, 'number', true) + float_opts.row = 2 + float_opts.style = nil + meths.win_set_config(float_win, float_opts) + eq(true, meths.win_get_option(float_win, 'number')) + end) + it("'scroll' is computed correctly when opening float with splitkeep=screen #20684", function() meths.set_option('splitkeep', 'screen') local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10} diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index fedfaca7ba..89b503141b 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -1411,10 +1411,10 @@ describe('ColorColumn highlight', function() [3] = {foreground = Screen.colors.Brown}, -- LineNr [4] = {foreground = Screen.colors.Brown, bold = true}, -- CursorLineNr [5] = {foreground = Screen.colors.Blue, bold = true}, -- NonText - -- NonText and ColorColumn [6] = {foreground = Screen.colors.Blue, background = Screen.colors.LightRed, bold = true}, [7] = {reverse = true, bold = true}, -- StatusLine [8] = {reverse = true}, -- StatusLineNC + [9] = {background = Screen.colors.Grey90, foreground = Screen.colors.Red}, }) screen:attach() end) @@ -1500,6 +1500,25 @@ describe('ColorColumn highlight', function() | ]]) end) + + it('is combined with low-priority CursorLine highlight #23016', function() + screen:try_resize(40, 2) + command('set colorcolumn=30 cursorline') + screen:expect([[ + {2:^ }{1: }{2: }| + | + ]]) + command('hi clear ColorColumn') + screen:expect([[ + {2:^ }| + | + ]]) + command('hi ColorColumn guifg=Red') + screen:expect([[ + {2:^ }{9: }{2: }| + | + ]]) + end) end) describe("MsgSeparator highlight and msgsep fillchar", function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 5220f3fa89..1a7fe26d26 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -22,6 +22,7 @@ local skip = helpers.skip describe('ui/ext_messages', function() local screen + local fname = 'Xtest_functional_ui_messages_spec' before_each(function() clear() @@ -41,7 +42,7 @@ describe('ui/ext_messages', function() }) end) after_each(function() - os.remove('Xtest') + os.remove(fname) end) it('msg_clear follows msg_show kind of confirm', function() @@ -126,7 +127,7 @@ describe('ui/ext_messages', function() feed('nq') -- kind=wmsg (editing readonly file) - command('write Xtest') + command('write ' .. fname) command('set readonly nohls') feed('G$x') screen:expect{grid=[[ @@ -912,9 +913,9 @@ stack traceback: end) it('does not truncate messages', function() - command('write Xtest') + command('write '.. fname) screen:expect({messages={ - {content = { { '"Xtest" [New] 0L, 0B written' } }, kind = "" } + {content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = "" } }}) end) end) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 0a253455ad..a2fe875e65 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -397,6 +397,29 @@ describe('statuscolumn', function() {0:~ }| | ]]) + -- Also test virt_lines when 'cpoptions' includes "n" + exec_lua([[ + vim.opt.cpoptions:append("n") + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 14, 0, { virt_lines = {{{"virt_line1", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 14, 0, { virt_lines = {{{"virt_line2", ""}}} }) + ]]) + screen:expect([[ + {1:buffer 0 13}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaa | + {1:buffer 0 14}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaa | + {1:buffer 0 15}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaa | + {1:virtual-2 15}virt_line1 | + {1:virtual-2 15}virt_line2 | + {1:buffer 0 16}{5:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {5:aaaaaaaaa }| + {1:virtual-1 16}END | + {0:~ }| + {0:~ }| + | + ]]) end) it("works with 'statuscolumn' clicks", function() @@ -593,4 +616,17 @@ describe('statuscolumn', function() | ]]) end) + + it("is only evaluated twice, once to estimate and once to draw", function() + command([[ + let g:stcnr = 0 + func! Stc() + let g:stcnr += 1 + return '12345' + endfunc + set stc=%!Stc() + norm ggdG + ]]) + eq(2, eval('g:stcnr')) + end) end) diff --git a/test/functional/ui/winbar_spec.lua b/test/functional/ui/winbar_spec.lua index ece27ec3ff..970f9c3d76 100644 --- a/test/functional/ui/winbar_spec.lua +++ b/test/functional/ui/winbar_spec.lua @@ -114,6 +114,41 @@ describe('winbar', function() {2:[No Name] [No Name] }| | ]]) + -- 'showcmdloc' "statusline" should not interfere with winbar redrawing #23030 + command('set showcmd showcmdloc=statusline') + feed('<C-W>w') + feed('<C-W>') + screen:expect([[ + {6:Set Up The Bars }│{6:Set Up The Bars }| + │ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{5:Set Up The Bars }| + {3:~ }│^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] ^W }| + {3:~ }│{6:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) + feed('w<C-W>W') + screen:expect([[ + {6:Set Up The Bars }│{6:Set Up The Bars }| + │ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{5:Set Up The Bars }| + {3:~ }│^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] }| + {3:~ }│{6:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) end) it('works when switching value of \'winbar\'', function() diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index b995aaa5a6..b411b1e379 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -220,3 +220,38 @@ describe('listing functions using :function', function() assert_alive() end) end) + +it('no double-free in garbage collection #16287', function() + clear() + -- Don't use exec() here as using a named script reproduces the issue better. + write_file('Xgarbagecollect.vim', [[ + func Foo() abort + let s:args = [a:000] + let foo0 = "" + let foo1 = "" + let foo2 = "" + let foo3 = "" + let foo4 = "" + let foo5 = "" + let foo6 = "" + let foo7 = "" + let foo8 = "" + let foo9 = "" + let foo10 = "" + let foo11 = "" + let foo12 = "" + let foo13 = "" + let foo14 = "" + endfunc + + set updatetime=1 + call Foo() + call Foo() + ]]) + finally(function() + os.remove('Xgarbagecollect.vim') + end) + command('source Xgarbagecollect.vim') + sleep(10) + assert_alive() +end) diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index 158dfe86d7..130d5d81fa 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -393,7 +393,7 @@ describe('system()', function() end) describe('with output containing NULs', function() - local fname = 'Xtest' + local fname = 'Xtest_functional_vimscript_system_nuls' before_each(create_file_with_nuls(fname)) after_each(delete_file(fname)) @@ -549,7 +549,7 @@ describe('systemlist()', function() end) describe('with output containing NULs', function() - local fname = 'Xtest' + local fname = 'Xtest_functional_vimscript_systemlist_nuls' before_each(function() command('set ff=unix') diff --git a/test/helpers.lua b/test/helpers.lua index 94dcf86c4d..8f06311a3c 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -3,7 +3,6 @@ local shared = vim local assert = require('luassert') local busted = require('busted') local luv = require('luv') -local relpath = require('pl.path').relpath local Paths = require('test.cmakeconfig.paths') assert:set_parameter('TableFormatLevel', 100) @@ -21,6 +20,16 @@ local module = { REMOVE_THIS = {}, } +--- @param p string +--- @return string +local function relpath(p) + p = vim.fs.normalize(p) + local cwd = luv.cwd() + return p:gsub("^" .. cwd) +end + +--- @param path string +--- @return boolean function module.isdir(path) if not path then return false @@ -32,6 +41,8 @@ function module.isdir(path) return stat.type == 'directory' end +--- @param path string +--- @return boolean function module.isfile(path) if not path then return false @@ -43,6 +54,7 @@ function module.isfile(path) return stat.type == 'file' end +--- @return string function module.argss_to_cmd(...) local cmd = '' for i = 1, select('#', ...) do @@ -457,6 +469,7 @@ function module.check_cores(app, force) -- luacheck: ignore end end +--- @return string? function module.repeated_read_cmd(...) for _ = 1, 10 do local stream = module.popen_r(...) @@ -556,6 +569,9 @@ function module.concat_tables(...) return ret end +--- @param str string +--- @param leave_indent? boolean +--- @return string function module.dedent(str, leave_indent) -- find minimum common indent across lines local indent = nil diff --git a/test/old/testdir/test_alot.vim b/test/old/testdir/test_alot.vim index a3d240f27e..4a22315b9f 100644 --- a/test/old/testdir/test_alot.vim +++ b/test/old/testdir/test_alot.vim @@ -2,7 +2,6 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_backup.vim -source test_behave.vim source test_compiler.vim source test_ex_equal.vim source test_ex_undo.vim diff --git a/test/old/testdir/test_arglist.vim b/test/old/testdir/test_arglist.vim index fb8b17cd16..de4e5e33d6 100644 --- a/test/old/testdir/test_arglist.vim +++ b/test/old/testdir/test_arglist.vim @@ -183,22 +183,25 @@ func Test_argument() let save_columns = &columns let &columns = 79 - exe 'args ' .. join(range(1, 81)) - call assert_equal(join([ - \ '', - \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ', - \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ', - \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ', - \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ', - \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ', - \ ], "\n"), - \ execute('args')) - - " No trailing newline with one item per row. - let long_arg = repeat('X', 81) - exe 'args ' .. long_arg - call assert_equal("\n[".long_arg.']', execute('args')) - let &columns = save_columns + try + exe 'args ' .. join(range(1, 81)) + call assert_equal(join([ + \ '', + \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ', + \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ', + \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ', + \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ', + \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ', + \ ], "\n"), + \ execute('args')) + + " No trailing newline with one item per row. + let long_arg = repeat('X', 81) + exe 'args ' .. long_arg + call assert_equal("\n[".long_arg.']', execute('args')) + finally + let &columns = save_columns + endtry " Setting argument list should fail when the current buffer has unsaved " changes diff --git a/test/old/testdir/test_behave.vim b/test/old/testdir/test_behave.vim deleted file mode 100644 index c26bfe7ce3..0000000000 --- a/test/old/testdir/test_behave.vim +++ /dev/null @@ -1,29 +0,0 @@ -" Test the :behave command - -func Test_behave() - behave mswin - call assert_equal('mouse,key', &selectmode) - call assert_equal('popup', &mousemodel) - call assert_equal('startsel,stopsel', &keymodel) - call assert_equal('exclusive', &selection) - - behave xterm - call assert_equal('', &selectmode) - call assert_equal('extend', &mousemodel) - call assert_equal('', &keymodel) - call assert_equal('inclusive', &selection) - - set selection& - set mousemodel& - set keymodel& - set selection& -endfunc - -func Test_behave_completion() - call feedkeys(":behave \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"behave mswin xterm', @:) -endfunc - -func Test_behave_error() - call assert_fails('behave x', 'E475:') -endfunc diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 1dceb43e5d..74dd7bf3c4 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -453,10 +453,10 @@ func Test_getcompletion() let l = getcompletion('blahblah', 'augroup') call assert_equal([], l) - let l = getcompletion('', 'behave') - call assert_true(index(l, 'mswin') >= 0) - let l = getcompletion('not', 'behave') - call assert_equal([], l) + " let l = getcompletion('', 'behave') + " call assert_true(index(l, 'mswin') >= 0) + " let l = getcompletion('not', 'behave') + " call assert_equal([], l) let l = getcompletion('', 'color') call assert_true(index(l, 'default') >= 0) @@ -2770,6 +2770,7 @@ endfunc " :behave suboptions fuzzy completion func Test_fuzzy_completion_behave() + throw 'Skipped: Nvim removed :behave' set wildoptions& call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx') call assert_equal('"behave xm', @:) @@ -3532,6 +3533,14 @@ endfunc func Test_setcmdline() func SetText(text, pos) + call assert_equal(0, setcmdline(v:_null_string)) + call assert_equal('', getcmdline()) + call assert_equal(1, getcmdpos()) + + call assert_equal(0, setcmdline(''[: -1])) + call assert_equal('', getcmdline()) + call assert_equal(1, getcmdpos()) + autocmd CmdlineChanged * let g:cmdtype = expand('<afile>') call assert_equal(0, setcmdline(a:text)) call assert_equal(a:text, getcmdline()) diff --git a/test/old/testdir/test_expr.vim b/test/old/testdir/test_expr.vim index 292a504df9..1810cf6741 100644 --- a/test/old/testdir/test_expr.vim +++ b/test/old/testdir/test_expr.vim @@ -39,6 +39,38 @@ func Test_version() call assert_false(has('patch-9.9.1')) endfunc +func Test_op_trinary() + call assert_equal('yes', 1 ? 'yes' : 'no') + call assert_equal('no', 0 ? 'yes' : 'no') + call assert_equal('no', 'x' ? 'yes' : 'no') + call assert_equal('yes', '1x' ? 'yes' : 'no') + + call assert_fails('echo [1] ? "yes" : "no"', 'E745:') + call assert_fails('echo {} ? "yes" : "no"', 'E728:') +endfunc + +func Test_op_falsy() + call assert_equal(v:true, v:true ?? 456) + call assert_equal(123, 123 ?? 456) + call assert_equal('yes', 'yes' ?? 456) + call assert_equal(0z00, 0z00 ?? 456) + call assert_equal([1], [1] ?? 456) + call assert_equal(#{one: 1}, #{one: 1} ?? 456) + if has('float') + call assert_equal(0.1, 0.1 ?? 456) + endif + + call assert_equal(456, v:false ?? 456) + call assert_equal(456, 0 ?? 456) + call assert_equal(456, '' ?? 456) + call assert_equal(456, 0z ?? 456) + call assert_equal(456, [] ?? 456) + call assert_equal(456, {} ?? 456) + if has('float') + call assert_equal(456, 0.0 ?? 456) + endif +endfunc + func Test_dict() let d = {'': 'empty', 'a': 'a', 0: 'zero'} call assert_equal('empty', d['']) diff --git a/test/old/testdir/test_help_tagjump.vim b/test/old/testdir/test_help_tagjump.vim index eae1a241e3..8a58d2f13c 100644 --- a/test/old/testdir/test_help_tagjump.vim +++ b/test/old/testdir/test_help_tagjump.vim @@ -35,9 +35,7 @@ func Test_help_tagjump() help ?? call assert_equal("help", &filetype) - " *??* tag needs patch 8.2.1794 - " call assert_true(getline('.') =~ '\*??\*') - call assert_true(getline('.') =~ '\*g??\*') + call assert_true(getline('.') =~ '\*??\*') helpclose help :? diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index 35745e9c6a..fe7633cf11 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -327,7 +327,7 @@ func Test_let_heredoc_fails() endfunc END call writefile(text, 'XheredocFail') - call assert_fails('source XheredocFail', 'E126:') + call assert_fails('source XheredocFail', 'E1145:') call delete('XheredocFail') let text =<< trim CodeEnd @@ -336,7 +336,7 @@ func Test_let_heredoc_fails() endfunc CodeEnd call writefile(text, 'XheredocWrong') - call assert_fails('source XheredocWrong', 'E126:') + call assert_fails('source XheredocWrong', 'E1145:') call delete('XheredocWrong') let text =<< trim TEXTend diff --git a/test/old/testdir/test_prompt_buffer.vim b/test/old/testdir/test_prompt_buffer.vim index 43d8bb4789..3dfbbcece6 100644 --- a/test/old/testdir/test_prompt_buffer.vim +++ b/test/old/testdir/test_prompt_buffer.vim @@ -271,6 +271,7 @@ func Test_prompt_appending_while_hidden() func DoAppend() call appendbufline('prompt', '$', 'Test') + return '' endfunc END call writefile(script, 'XpromptBuffer', 'D') @@ -282,12 +283,18 @@ func Test_prompt_appending_while_hidden() call TermWait(buf) call term_sendkeys(buf, "exit\<CR>") - call TermWait(buf) + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) call term_sendkeys(buf, ":call DoAppend()\<CR>") - call TermWait(buf) - call assert_notmatch('-- INSERT --', term_getline(buf, 10)) + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "i") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + call term_sendkeys(buf, "\<C-R>=DoAppend()\<CR>") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) endfunc diff --git a/test/old/testdir/test_usercommands.vim b/test/old/testdir/test_usercommands.vim index 898f7a59f1..e22f57b6f1 100644 --- a/test/old/testdir/test_usercommands.vim +++ b/test/old/testdir/test_usercommands.vim @@ -303,7 +303,7 @@ func Test_CmdErrors() call assert_fails('com! -complete=xxx DoCmd :', 'E180:') call assert_fails('com! -complete=custom DoCmd :', 'E467:') call assert_fails('com! -complete=customlist DoCmd :', 'E467:') - call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:') + " call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:') call assert_fails('com! -complete=file DoCmd :', 'E1208:') call assert_fails('com! -nargs=0 -complete=file DoCmd :', 'E1208:') call assert_fails('com! -nargs=x DoCmd :', 'E176:') @@ -391,9 +391,9 @@ func Test_CmdCompletion() call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com DoC', @:) - com! -nargs=1 -complete=behave DoCmd : - call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"DoCmd mswin xterm', @:) + " com! -nargs=1 -complete=behave DoCmd : + " call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"DoCmd mswin xterm', @:) " Test for file name completion com! -nargs=1 -complete=file DoCmd : diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index b0c4baf7c2..5988d6ed71 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -6746,7 +6746,7 @@ func Test_script_lines() \ ]) call assert_report("Shouldn't be able to define function") catch - call assert_exception('Vim(function):E126: Missing :endfunction') + call assert_exception('Vim(function):E1145: Missing heredoc end marker: .') endtry " :change @@ -6766,7 +6766,7 @@ func Test_script_lines() \ ]) call assert_report("Shouldn't be able to define function") catch - call assert_exception('Vim(function):E126: Missing :endfunction') + call assert_exception('Vim(function):E1145: Missing heredoc end marker: .') endtry " :insert @@ -6786,7 +6786,7 @@ func Test_script_lines() \ ]) call assert_report("Shouldn't be able to define function") catch - call assert_exception('Vim(function):E126: Missing :endfunction') + call assert_exception('Vim(function):E1145: Missing heredoc end marker: .') endtry endfunc @@ -7258,6 +7258,30 @@ func Test_typed_script_var() call StopVimInTerminal(buf) endfunc +" Test for issue6776 {{{1 +func Test_trinary_expression() + try + call eval('0 ? 0') + catch + endtry + " previous failure should not cause next expression to fail + call assert_equal(v:false, eval(string(v:false))) + + try + call eval('0 ? "burp') + catch + endtry + " previous failure should not cause next expression to fail + call assert_equal(v:false, eval(string(v:false))) + + try + call eval('1 ? 0 : "burp') + catch + endtry + " previous failure should not cause next expression to fail + call assert_equal(v:false, eval(string(v:false))) +endfunction + func Test_for_over_string() let res = '' for c in 'aéc̀d' diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 350c69fe4e..d10a946200 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -485,14 +485,16 @@ endfunc func Test_visual_block_put_invalid() enew! - behave mswin + " behave mswin + set selection=exclusive norm yy norm v)Ps/^/ " this was causing the column to become negative silent norm ggv)P bwipe! - behave xterm + " behave xterm + set selection& endfunc " Visual modes (v V CTRL-V) followed by an operator; count; repeating diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 9dd6b76de2..883f01bd84 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -512,7 +512,8 @@ end local function eval0(expr) local tv = ffi.gc(ffi.new('typval_T', {v_type=eval.VAR_UNKNOWN}), eval.tv_clear) - if eval.eval0(to_cstr(expr), tv, nil, true) == 0 then + local evalarg = ffi.new('evalarg_T', {eval_flags = eval.EVAL_EVALUATE}) + if eval.eval0(to_cstr(expr), tv, nil, evalarg) == 0 then return nil else return tv diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index 2fd37c599a..c94f0d88f7 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -154,6 +154,8 @@ local C_keywords = set { -- luacheck: ignore -- -- The first one will have a lot of false positives (the line '{' for -- example), the second one is more unique. +--- @param string +--- @return string local function formatc(str) local toks = TokeniseC(str) local result = {} diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 708929ad9f..10b7594a88 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -14,20 +14,15 @@ local map = global_helpers.tbl_map local eq = global_helpers.eq local trim = global_helpers.trim --- C constants. -local NULL = ffi.cast('void*', 0) - -local OK = 1 -local FAIL = 0 - -local cimport - -- add some standard header locations for _, p in ipairs(Paths.include_paths) do Preprocess.add_to_include_path(p) end -local child_pid = nil +local child_pid = nil --- @type integer +--- @generic F: function +--- @param func F +--- @return F local function only_separate(func) return function(...) if child_pid ~= 0 then @@ -36,9 +31,20 @@ local function only_separate(func) return func(...) end end -local child_calls_init = {} -local child_calls_mod = nil -local child_calls_mod_once = nil + +--- @class ChildCall +--- @field func function +--- @field args any[] + +--- @class ChildCallLog +--- @field func string +--- @field args any[] +--- @field ret any? + +local child_calls_init = {} --- @type ChildCall[] +local child_calls_mod = nil --- @type ChildCall[] +local child_calls_mod_once = nil --- @type ChildCall[]? + local function child_call(func, ret) return function(...) local child_calls = child_calls_mod or child_calls_init @@ -53,16 +59,16 @@ end -- Run some code at the start of the child process, before running the test -- itself. Is supposed to be run in `before_each`. +--- @param func function local function child_call_once(func, ...) if child_pid ~= 0 then - child_calls_mod_once[#child_calls_mod_once + 1] = { - func=func, args={...}} + child_calls_mod_once[#child_calls_mod_once + 1] = { func = func, args = {...} } else func(...) end end -local child_cleanups_mod_once = nil +local child_cleanups_mod_once = nil --- @type ChildCall[]? -- Run some code at the end of the child process, before exiting. Is supposed to -- be run in `before_each` because `after_each` is run after child has exited. @@ -125,8 +131,9 @@ local pragma_pack_id = 1 -- some things are just too complex for the LuaJIT C parser to digest. We -- usually don't need them anyway. +--- @param body string local function filter_complex_blocks(body) - local result = {} + local result = {} --- @type string[] for line in body:gmatch("[^\r\n]+") do if not (string.find(line, "(^)", 1, true) ~= nil @@ -153,18 +160,20 @@ typedef struct { char bytes[16]; } __attribute__((aligned(16))) __uint128_t; typedef struct { char bytes[16]; } __attribute__((aligned(16))) __float128; ]] -local preprocess_cache_init = {} +local preprocess_cache_init = {} --- @type table<string,string> local previous_defines_mod = '' -local preprocess_cache_mod = nil +local preprocess_cache_mod = nil --- @type table<string,string> local function is_child_cdefs() - return (os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1') + return os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1' end -- use this helper to import C files, you can pass multiple paths at once, -- this helper will return the C namespace of the nvim library. -cimport = function(...) - local previous_defines, preprocess_cache, cdefs +local function cimport(...) + local previous_defines --- @type string + local preprocess_cache --- @type table<string,string> + local cdefs if is_child_cdefs() and preprocess_cache_mod then preprocess_cache = preprocess_cache_mod previous_defines = previous_defines_mod @@ -180,7 +189,7 @@ cimport = function(...) path = './' .. path end if not preprocess_cache[path] then - local body + local body --- @type string body, previous_defines = Preprocess.preprocess(previous_defines, path) -- format it (so that the lines are "unique" statements), also filter out -- Objective-C blocks @@ -202,6 +211,7 @@ cimport = function(...) -- (they are needed in the right order with the struct definitions, -- otherwise luajit has wrong memory layouts for the sturcts) if line:match("#pragma%s+pack") then + --- @type string line = line .. " // " .. pragma_pack_id pragma_pack_id = pragma_pack_id + 1 end @@ -229,20 +239,21 @@ cimport = function(...) return lib end -local cimport_immediate = function(...) +local function cimport_immediate(...) local saved_pid = child_pid child_pid = 0 local err, emsg = pcall(cimport, ...) child_pid = saved_pid if not err then - emsg = tostring(emsg) - io.stderr:write(emsg .. '\n') + io.stderr:write(tostring(emsg) .. '\n') assert(false) else return lib end end +--- @param preprocess_cache table<string,string[]> +--- @param path string local function _cimportstr(preprocess_cache, path) if imported:contains(path) then return lib @@ -265,12 +276,14 @@ end local function alloc_log_new() local log = { - log={}, - lib=cimport('./src/nvim/memory.h'), - original_functions={}, + log={}, --- @type ChildCallLog[] + lib=cimport('./src/nvim/memory.h'), --- @type table<string,function> + original_functions={}, --- @type table<string,function> null={['\0:is_null']=true}, } + local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'} + function log:save_original_functions() for _, funcname in ipairs(allocator_functions) do if not self.original_functions[funcname] then @@ -278,13 +291,16 @@ local function alloc_log_new() end end end + log.save_original_functions = child_call(log.save_original_functions) + function log:set_mocks() for _, k in ipairs(allocator_functions) do do local kk = k self.lib['mem_' .. k] = function(...) - local log_entry = {func=kk, args={...}} + --- @type ChildCallLog + local log_entry = { func = kk, args = {...} } self.log[#self.log + 1] = log_entry if kk == 'free' then self.original_functions[kk](...) @@ -305,17 +321,21 @@ local function alloc_log_new() end end end + log.set_mocks = child_call(log.set_mocks) + function log:clear() self.log = {} end + function log:check(exp) eq(exp, self.log) self:clear() end + function log:clear_tmp_allocs(clear_null_frees) - local toremove = {} - local allocs = {} + local toremove = {} --- @type integer[] + local allocs = {} --- @type table<string,integer> for i, v in ipairs(self.log) do if v.func == 'malloc' or v.func == 'calloc' then allocs[tostring(v.ret)] = i @@ -338,26 +358,20 @@ local function alloc_log_new() table.remove(self.log, toremove[i]) end end - function log:restore_original_functions() - -- Do nothing: set mocks live in a separate process - return - --[[ - [ for k, v in pairs(self.original_functions) do - [ self.lib['mem_' .. k] = v - [ end - ]] - end + function log:setup() log:save_original_functions() log:set_mocks() end + function log:before_each() - return end + function log:after_each() - log:restore_original_functions() end + log:setup() + return log end @@ -374,98 +388,109 @@ local function to_cstr(string) end cimport_immediate('./test/unit/fixtures/posix.h') -local sc = { - fork = function() - return tonumber(ffi.C.fork()) - end, - pipe = function() - local ret = ffi.new('int[2]', {-1, -1}) - ffi.errno(0) - local res = ffi.C.pipe(ret) - if (res ~= 0) then + +local sc = {} + +function sc.fork() + return tonumber(ffi.C.fork()) +end + +function sc.pipe() + local ret = ffi.new('int[2]', {-1, -1}) + ffi.errno(0) + local res = ffi.C.pipe(ret) + if (res ~= 0) then + local err = ffi.errno(0) + assert(res == 0, ("pipe() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) + end + assert(ret[0] ~= -1 and ret[1] ~= -1) + return ret[0], ret[1] +end + +--- @return string +function sc.read(rd, len) + local ret = ffi.new('char[?]', len, {0}) + local total_bytes_read = 0 + ffi.errno(0) + while total_bytes_read < len do + local bytes_read = tonumber(ffi.C.read( + rd, + ffi.cast('void*', ret + total_bytes_read), + len - total_bytes_read)) + if bytes_read == -1 then local err = ffi.errno(0) - assert(res == 0, ("pipe() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - assert(ret[0] ~= -1 and ret[1] ~= -1) - return ret[0], ret[1] - end, - read = function(rd, len) - local ret = ffi.new('char[?]', len, {0}) - local total_bytes_read = 0 - ffi.errno(0) - while total_bytes_read < len do - local bytes_read = tonumber(ffi.C.read( - rd, - ffi.cast('void*', ret + total_bytes_read), - len - total_bytes_read)) - if bytes_read == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("read() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - elseif bytes_read == 0 then - break - else - total_bytes_read = total_bytes_read + bytes_read + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("read() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + elseif bytes_read == 0 then + break + else + total_bytes_read = total_bytes_read + bytes_read end - return ffi.string(ret, total_bytes_read) - end, - write = function(wr, s) - local wbuf = to_cstr(s) - local total_bytes_written = 0 - ffi.errno(0) - while total_bytes_written < #s do - local bytes_written = tonumber(ffi.C.write( - wr, - ffi.cast('void*', wbuf + total_bytes_written), - #s - total_bytes_written)) - if bytes_written == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("write() error: %u: %s ('%s')"):format( - err, ffi.string(ffi.C.strerror(err)), s)) - end - elseif bytes_written == 0 then - break - else - total_bytes_written = total_bytes_written + bytes_written + end + return ffi.string(ret, total_bytes_read) +end + +function sc.write(wr, s) + local wbuf = to_cstr(s) + local total_bytes_written = 0 + ffi.errno(0) + while total_bytes_written < #s do + local bytes_written = tonumber(ffi.C.write( + wr, + ffi.cast('void*', wbuf + total_bytes_written), + #s - total_bytes_written)) + if bytes_written == -1 then + local err = ffi.errno(0) + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("write() error: %u: %s ('%s')"):format( + err, ffi.string(ffi.C.strerror(err)), s)) end + elseif bytes_written == 0 then + break + else + total_bytes_written = total_bytes_written + bytes_written end - return total_bytes_written - end, - close = ffi.C.close, - wait = function(pid) - ffi.errno(0) - local stat_loc = ffi.new('int[1]', {0}) - while true do - local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED) - if r == -1 then - local err = ffi.errno(0) - if err == ffi.C.kPOSIXErrnoECHILD then - break - elseif err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("waitpid() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - else - assert(r == pid) + end + return total_bytes_written +end + +sc.close = ffi.C.close + +--- @param pid integer +--- @return integer +function sc.wait(pid) + ffi.errno(0) + local stat_loc = ffi.new('int[1]', {0}) + while true do + local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED) + if r == -1 then + local err = ffi.errno(0) + if err == ffi.C.kPOSIXErrnoECHILD then + break + elseif err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("waitpid() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + else + assert(r == pid) end - return stat_loc[0] - end, - exit = ffi.C._exit, -} + end + return stat_loc[0] +end + +sc.exit = ffi.C._exit +--- @param lst string[] +--- @return string local function format_list(lst) - local ret = '' + local ret = {} --- @type string[] for _, v in ipairs(lst) do - if ret ~= '' then ret = ret .. ', ' end - ret = ret .. assert:format({v, n=1})[1] + ret[#ret+1] = assert:format({v, n=1})[1] end - return ret + return table.concat(ret, ', ') end if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then @@ -513,19 +538,26 @@ local tracehelp = dedent([[ ]]) local function child_sethook(wr) - local trace_level = os.getenv('NVIM_TEST_TRACE_LEVEL') - if not trace_level or trace_level == '' then - trace_level = 0 - else - trace_level = tonumber(trace_level) + local trace_level_str = os.getenv('NVIM_TEST_TRACE_LEVEL') + local trace_level = 0 + if trace_level_str and trace_level_str ~= '' then + --- @type number + trace_level = assert(tonumber(trace_level_str)) end + if trace_level <= 0 then return end + local trace_only_c = trace_level <= 1 + --- @type debuginfo?, string?, integer local prev_info, prev_reason, prev_lnum + + --- @param reason string + --- @param lnum integer + --- @param use_prev boolean local function hook(reason, lnum, use_prev) - local info = nil + local info = nil --- @type debuginfo? if use_prev then info = prev_info elseif reason ~= 'tail return' then -- tail return @@ -533,6 +565,7 @@ local function child_sethook(wr) end if trace_only_c and (not info or info.what ~= 'C') and not use_prev then + --- @cast info -nil if info.source:sub(-9) == '_spec.lua' then prev_info = info prev_reason = 'saved' @@ -573,12 +606,8 @@ local function child_sethook(wr) end -- assert(-1 <= lnum and lnum <= 99999) - local lnum_s - if lnum == -1 then - lnum_s = 'nknwn' - else - lnum_s = ('%u'):format(lnum) - end + local lnum_s = lnum == -1 and 'nknwn' or ('%u'):format(lnum) + --- @type string local msg = ( -- lua does not support %* '' .. msgchar @@ -600,6 +629,7 @@ end local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2)) +--- @type function local _debug_log local debug_log = only_separate(function(...) @@ -607,6 +637,7 @@ local debug_log = only_separate(function(...) end) local function itp_child(wr, func) + --- @param s string _debug_log = function(s) s = s:sub(1, hook_msglen - 2) sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') @@ -638,7 +669,7 @@ local function itp_child(wr, func) end local function check_child_err(rd) - local trace = {} + local trace = {} --- @type string[] local did_traceline = false local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024 while true do @@ -668,11 +699,14 @@ local function check_child_err(rd) local len = tonumber(len_s) neq(0, len) if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then + --- @type string err = '\nTest failed, trace:\n' .. tracehelp for _, traceline in ipairs(trace) do + --- @type string err = err .. traceline end end + --- @type string err = err .. sc.read(rd, len + 1) end local eres = sc.read(rd, 2) @@ -686,10 +720,12 @@ local function check_child_err(rd) end end if not did_traceline then + --- @type string err = err .. '\nNo end of trace occurred' end local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) if not cc_err then + --- @type string err = err .. '\ncheck_cores failed: ' .. cc_emsg end end @@ -822,9 +858,9 @@ local module = { lib = lib, cstr = cstr, to_cstr = to_cstr, - NULL = NULL, - OK = OK, - FAIL = FAIL, + NULL = ffi.cast('void*', 0), + OK = 1, + FAIL = 0, alloc_log_new = alloc_log_new, gen_itp = gen_itp, only_separate = only_separate, diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 88f7a14d1f..c5fb62dce5 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -7,6 +7,9 @@ local global_helpers = require('test.helpers') local argss_to_cmd = global_helpers.argss_to_cmd local repeated_read_cmd = global_helpers.repeated_read_cmd +--- @alias Compiler {path: string[], type: string} + +--- @type Compiler[] local ccs = {} local env_cc = os.getenv("CC") @@ -27,6 +30,8 @@ table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"}) table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"}) -- parse Makefile format dependencies into a Lua table +--- @param deps string +--- @return string[] local function parse_make_deps(deps) -- remove line breaks and line concatenators deps = deps:gsub("\n", ""):gsub("\\", "") @@ -36,7 +41,7 @@ local function parse_make_deps(deps) deps = deps:gsub(" +", " ") -- split according to token (space in this case) - local headers = {} + local headers = {} --- @type string[] for token in deps:gmatch("[^%s]+") do -- headers[token] = true headers[#headers + 1] = token @@ -53,57 +58,58 @@ local function parse_make_deps(deps) return headers end --- will produce a string that represents a meta C header file that includes --- all the passed in headers. I.e.: --- --- headerize({"stdio.h", "math.h"}, true) --- produces: --- #include <stdio.h> --- #include <math.h> --- --- headerize({"vim.h", "memory.h"}, false) --- produces: --- #include "vim.h" --- #include "memory.h" +--- will produce a string that represents a meta C header file that includes +--- all the passed in headers. I.e.: +--- +--- headerize({"stdio.h", "math.h"}, true) +--- produces: +--- #include <stdio.h> +--- #include <math.h> +--- +--- headerize({"vim.h", "memory.h"}, false) +--- produces: +--- #include "vim.h" +--- #include "memory.h" +--- @param headers string[] +--- @param global? boolean +--- @return string local function headerize(headers, global) - local pre = '"' - local post = pre - if global then - pre = "<" - post = ">" - end - - local formatted = {} + local fmt = global and '#include <%s>' or '#include "%s"' + local formatted = {} --- @type string[] for _, hdr in ipairs(headers) do - formatted[#formatted + 1] = "#include " .. - tostring(pre) .. - tostring(hdr) .. - tostring(post) + formatted[#formatted + 1] = string.format(fmt, hdr) end return table.concat(formatted, "\n") end +--- @class Gcc +--- @field path string +--- @field preprocessor_extra_flags string[] +--- @field get_defines_extra_flags string[] +--- @field get_declarations_extra_flags string[] local Gcc = { preprocessor_extra_flags = {}, get_defines_extra_flags = {'-std=c99', '-dM', '-E'}, get_declarations_extra_flags = {'-std=c99', '-P', '-E'}, } +--- @param name string +--- @param args string[]? +--- @param val string? function Gcc:define(name, args, val) - local define = '-D' .. name - if args ~= nil then - define = define .. '(' .. table.concat(args, ',') .. ')' + local define = string.format('-D%s', name) + if args then + define = string.format('%s(%s)', define, table.concat(args, ',')) end - if val ~= nil then - define = define .. '=' .. val + if val then + define = string.format('%s=%s', define, val) end self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = define end function Gcc:undefine(name) - self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = ( - '-U' .. name) + self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = '-U' .. name end function Gcc:init_defines() @@ -128,6 +134,8 @@ function Gcc:init_defines() self:undefine('__BLOCKS__') end +--- @param obj? Compiler +--- @return Gcc function Gcc:new(obj) obj = obj or {} setmetatable(obj, self) @@ -136,6 +144,7 @@ function Gcc:new(obj) return obj end +--- @param ... string function Gcc:add_to_include_path(...) for i = 1, select('#', ...) do local path = select(i, ...) @@ -145,110 +154,115 @@ function Gcc:add_to_include_path(...) end -- returns a list of the headers files upon which this file relies +--- @param hdr string +--- @return string[]? function Gcc:dependencies(hdr) + --- @type string local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1' - local out = io.popen(cmd) + local out = assert(io.popen(cmd)) local deps = out:read("*a") out:close() if deps then return parse_make_deps(deps) - else - return nil end end +--- @param defines string +--- @return string function Gcc:filter_standard_defines(defines) if not self.standard_defines then local pseudoheader_fname = 'tmp_empty_pseudoheader.h' - local pseudoheader_file = io.open(pseudoheader_fname, 'w') + local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w')) pseudoheader_file:close() - local standard_defines = repeated_read_cmd(self.path, - self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local standard_defines = assert(repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname})) os.remove(pseudoheader_fname) - self.standard_defines = {} + self.standard_defines = {} --- @type table<string,true> for line in standard_defines:gmatch('[^\n]+') do self.standard_defines[line] = true end end - local ret = {} + + local ret = {} --- @type string[] for line in defines:gmatch('[^\n]+') do if not self.standard_defines[line] then ret[#ret + 1] = line end end + return table.concat(ret, "\n") end --- returns a stream representing a preprocessed form of the passed-in headers. --- Don't forget to close the stream by calling the close() method on it. +--- returns a stream representing a preprocessed form of the passed-in headers. +--- Don't forget to close the stream by calling the close() method on it. +--- @param previous_defines string +--- @param ... string +--- @return string, string function Gcc:preprocess(previous_defines, ...) -- create pseudo-header local pseudoheader = headerize({...}, false) local pseudoheader_fname = 'tmp_pseudoheader.h' - local pseudoheader_file = io.open(pseudoheader_fname, 'w') + local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w')) pseudoheader_file:write(previous_defines) pseudoheader_file:write("\n") pseudoheader_file:write(pseudoheader) pseudoheader_file:flush() pseudoheader_file:close() - local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local defines = assert(repeated_read_cmd(self.path, self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname})) defines = self:filter_standard_defines(defines) - local declarations = repeated_read_cmd(self.path, - self.preprocessor_extra_flags, - self.get_declarations_extra_flags, - {pseudoheader_fname}) + local declarations = assert(repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_declarations_extra_flags, + {pseudoheader_fname})) os.remove(pseudoheader_fname) - assert(declarations and defines) return declarations, defines end -local Clang = Gcc:new() -local Msvc = Gcc:new() - -local type_to_class = { - ["gcc"] = Gcc, - ["clang"] = Clang, - ["msvc"] = Msvc -} - -- find the best cc. If os.exec causes problems on windows (like popping up -- a console window) we might consider using something like this: -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec +--- @param compilers Compiler[] +--- @return Gcc? local function find_best_cc(compilers) for _, meta in pairs(compilers) do - local version = io.popen(tostring(meta.path) .. " -v 2>&1") + local version = assert(io.popen(tostring(meta.path) .. " -v 2>&1")) version:close() if version then - return type_to_class[meta.type]:new({path = meta.path}) + return Gcc:new({path = meta.path}) end end - return nil end -- find the best cc. If os.exec causes problems on windows (like popping up -- a console window) we might consider using something like this: -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec -local cc = nil -if cc == nil then - cc = find_best_cc(ccs) -end - -return { - includes = function(hdr) - return cc:dependencies(hdr) - end, - preprocess = function(...) - return cc:preprocess(...) - end, - add_to_include_path = function(...) - return cc:add_to_include_path(...) - end -} +local cc = assert(find_best_cc(ccs)) + +local M = {} + +--- @param hdr string +--- @return string[]? +function M.includes(hdr) + return cc:dependencies(hdr) +end + +--- @param ... string +--- @return string, string +function M.preprocess(...) + return cc:preprocess(...) +end + +--- @param ... string +function M.add_to_include_path(...) + return cc:add_to_include_path(...) +end + +return M diff --git a/test/unit/set.lua b/test/unit/set.lua index f3d68c3042..91b7e2dd70 100644 --- a/test/unit/set.lua +++ b/test/unit/set.lua @@ -4,10 +4,15 @@ -- other: -- 1) index => item -- 2) item => index +--- @class Set +--- @field nelem integer +--- @field items string[] +--- @field tbl table local Set = {} +--- @param items? string[] function Set:new(items) - local obj = {} + local obj = {} --- @ type Set setmetatable(obj, self) self.__index = self @@ -26,8 +31,9 @@ function Set:new(items) return obj end +--- @return Set function Set:copy() - local obj = {} + local obj = {} --- @ type Set obj.nelem = self.nelem obj.tbl = {} obj.items = {} @@ -43,6 +49,7 @@ function Set:copy() end -- adds the argument Set to this Set +--- @param other Set function Set:union(other) for e in other:iterator() do self:add(e) @@ -57,6 +64,7 @@ function Set:union_table(t) end -- subtracts the argument Set from this Set +--- @param other Set function Set:diff(other) if other:size() > self:size() then -- this set is smaller than the other set @@ -75,6 +83,7 @@ function Set:diff(other) end end +--- @param it string function Set:add(it) if not self:contains(it) then local idx = #self.tbl + 1 @@ -84,6 +93,7 @@ function Set:add(it) end end +--- @param it string function Set:remove(it) if self:contains(it) then local idx = self.items[it] @@ -93,10 +103,13 @@ function Set:remove(it) end end +--- @param it string +--- @return boolean function Set:contains(it) return self.items[it] or false end +--- @return integer function Set:size() return self.nelem end @@ -113,29 +126,18 @@ function Set:iterator() return pairs(self.items) end +--- @return string[] function Set:to_table() -- there might be gaps in @tbl, so we have to be careful and sort first - local keys - do - local _accum_0 = { } - local _len_0 = 1 - for idx, _ in pairs(self.tbl) do - _accum_0[_len_0] = idx - _len_0 = _len_0 + 1 - end - keys = _accum_0 + local keys = {} --- @type string[] + for idx, _ in pairs(self.tbl) do + keys[#keys+1] = idx end + table.sort(keys) - local copy - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #keys do - local idx = keys[_index_0] - _accum_0[_len_0] = self.tbl[idx] - _len_0 = _len_0 + 1 - end - copy = _accum_0 + local copy = {} --- @type string[] + for _, idx in ipairs(keys) do + copy[#copy+1] = self.tbl[idx] end return copy end |